subvertpy_0.11.1.orig/AUTHORS0000644000000000000000000000045412556577610012675 0ustar00Original author and maintainer: Jelmer Vernooij Dan Villiom Podlaski Christiansen Mark Lee Windows support: Alexey Borzenkov Mark Hammond Adrian Wilkins Mac OS X support: Guillermo Gonzalez Jean-Francois Roy Name: Paul Hummer subvertpy_0.11.1.orig/COPYING0000644000000000000000000006363711150076011012646 0ustar00 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library 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 library 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 library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! subvertpy_0.11.1.orig/INSTALL0000644000000000000000000000075413133244127012643 0ustar00Requirements ------------ Subversion development files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You need the Subversion libraries, including the development files. This should be readily packaged for most platforms. The package name for Debian and Debian-based distributions such as Ubuntu is ``libsvn-dev``. Python ~~~~~~ At least version 2.7 is required. Building -------- To build, simply run ``make`` or ``./setup.py build``. Installation ------------ To install, run ``./setup.py install``. subvertpy_0.11.1.orig/MANIFEST.in0000644000000000000000000000031313133767436013354 0ustar00include MANIFEST.in include subvertpy/*.c include subvertpy/*.h include AUTHORS COPYING INSTALL NEWS TODO include examples/*.py include README.md include Makefile include subvertpy.cfg include man/*.1 subvertpy_0.11.1.orig/Makefile0000644000000000000000000000207215154541453013254 0ustar00PYTHON = python3 RUFF ?= ruff PYDOCTOR = pydoctor PYDOCTOR_OPTIONS ?= SETUP = $(PYTHON) setup.py TESTRUNNER = unittest DEBUGGER ?= RUNTEST = PYTHONPATH=.:$(PYTHONPATH) $(DEBUGGER) $(PYTHON) -m $(TESTRUNNER) -v all: build build-inplace build:: $(SETUP) build build-nodeprecated: $(MAKE) build CFLAGS+=-Wno-deprecated-declarations build-inplace:: $(SETUP) build_ext --inplace install:: $(SETUP) install check:: build-inplace $(RUNTEST) $(TEST_OPTIONS) subvertpy.tests.test_suite 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 clean:: $(SETUP) clean rm -f subvertpy/*.so subvertpy/*.o subvertpy/*.pyc pydoctor: $(PYDOCTOR) $(PYDOCTOR_OPTIONS) --introspect-c-modules -c subvertpy.cfg --make-html style: $(RUFF) check $(RUFF) format --check subvertpy_0.11.1.orig/NEWS0000644000000000000000000004020315154541453012311 0ustar000.11.1 2026-03-11 BUG FIXES * Fix compilation warnings and errors with Python 3. (Antoine Lambert) * Fix crash when providing a URI to ``py_object_to_svn_abspath``. (Antoine Lambert) * Fix extension modules build when using subversion from vcpkg. (Antoine Lambert) * Fix crash when calling ``txdelta_call`` on Windows. (Antoine Lambert) * Fix svn path detection on macOS with latest brew. (Antoine Lambert) * Fix multiple bugs in C bindings. (Jelmer Vernooij) * Fix ``Context.ensure_adm()`` crash on Windows. (Jelmer Vernooij) * Fix ``_get_repo_path()`` for Windows ``file://`` URL handling. (Jelmer Vernooij) * Properly close editors in ``get_update_editor`` tests. (Jelmer Vernooij) IMPROVEMENTS * Add CI workflows for building binary wheels on all platforms. (Antoine Lambert) * Migrate to ruff for linting and formatting. (Jelmer Vernooij) 0.11.0 2021-01-11 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 * Bundle Makefile and subvertpy.cfg. (Jelmer Vernooij) * Fix some endianness issues. (Jelmer Vernooij) 0.10.0 2017-07-17 CHANGES * Drop support for Python versions before 2.7, in preparation of Python3 support. (Jelmer Vernooij) * Add support for Python 3.4, 3.5 and 3.6. (Jelmer Vernooij) * subvertpy.client methods no longer canonicalize paths and URLs, instead requiring the caller to do so. If uncanonicalized paths/URLs are passed in, a ValueError will be raised. (Jelmer Vernooij) IMPROVEMENTS * Add Python3 support. (Martin Panter, Yonggang Luo, Jelmer Vernooij). * Add constant ERR_RA_CANNOT_CREATE_SESSION. (Jelmer Vernooij) * Support `depth` argument to RemoteAccess.get_locks(). (Jelmer Vernooij) * Support `no_autoprops` argument to Client.add(). (Jelmer Vernooij) * Support `ignore_ancestry` and `send_copyfrom_args` arguments to RemoteAcess.do_switch. (Jelmer Vernooij) * Support `old_value` argument to RemoteAccess.change_rev_prop(). (Jelmer Vernooij) * Support `include_externals` argument to Client.list(). (Jelmer Vernooij) * Support `expand_keywords` argument to Client.cat(). (Jelmer Vernooij) * Support `revprops` argument to Client.delete(). (Jelmer Vernooij) BUG FIXES * Avoid using deprecated svn_path_canonicalize(). (Jelmer Vernooij) 0.9.3 2015-08-23 BUG FIXES * Disable tests probing for nonexistant paths in FS, as this causes a crash in newer versions of libsvn_repos. (Jelmer Vernooij) 0.9.2 2015-04-25 BUG FIXES * Support failing server certification check. (Mitsuhiro Koga, #1059821) CHANGES * Drop support for Python versions before 2.6. (Jelmer Vernooij) IMPROVEMENTS * Support PYDOCTOR_OPTIONS in Makefile. (Jelmer Vernooij) 0.9.1 2013-05-06 CHANGES * For the moment, disable subvertpy.wc.WorkingCopy when used with Subversion 1.7. Subversion 1.7 has some significant changes in the behaviour of the working copy API because of the WC-NG rewrite. Support for the WC API in newer versions of Subversion will be re-added later (help welcome), but since most users (bzr-svn is the only I'm aware of) don't seem to actually rely on it, it seemed better to disable it and support 1.7 than to prevent use of newer svn versions altogether. (Jelmer Vernooij) TESTS * Fix TestClient.test_info when run against Subversion 1.6. (Jelmer Vernooij) BUG FIXES * Use PyObject_AsFileDescriptor instead of PyFile_AsFile so apr_file_from_object works under Win32. (Yonggang Luo) 0.9.0 2012-07-08 IMPROVEMENTS * Some fixes to help with porting to Python 3. (Yonggang Luo) * Add bindings for svn.client.Client.info(), svn.client.Client.log() and svn.client.Client.cat(). (Mark Lee) * Add constant ERR_WC_UPGRADE_REQUIRED. (Jelmer Vernooij) * In Client.checkout, default to HEAD revision. (Jelmer Vernooij) TESTS * Remove readonly files properly in tests. (Yonggang Luo, #943131) DOCUMENTATION * Update documentation for building on Windows. (Yonggang Luo) BUG FIXES * Include *.c files in MANIFEST. (#951060, Shlomi Fish) 0.8.10 2012-01-23 BUG FIXES * Fix test suite on Cygwin. (Ronald Blaschke) * Support windows-cryptoapi on Cygwin. (Ronald Blaschke) * Include wc.h in wc.c. FIxes build on NetBSD 5.1/amd64. (#894659) 0.8.9 2011-10-25 BUG FIXES * Fix compilation with Python 2.4 and 2.5. (Jelmer Vernooij, #860621) FEATURES * Add constant ERR_WC_NODE_KIND_CHANGE. (Jelmer Vernooij) 0.8.8 2011-09-27 BUG FIXES * Fix several memory leaks and error checks in subvertpy.client pointed out by Barry Warsaw. (Jelmer Vernooij) * Drop broken code to support loading configuration from has. (Jelmer Vernooij) * WorkingCopy.prop_set() now accepts None as a value, removing a properties. (Jelmer Vernooij) FEATURES * Add constant ERR_BAD_FILENAME. (Jelmer Vernooij) 0.8.7 2011-09-19 BUG FIXES * Fix revnum types in subvertpy.repos.Repos.verify_fs(). (Jelmer Vernooij) 0.8.6 2011-09-19 FEATURES * Allow passing in log_msg_func when creating subvertpy.client.Client. (Jelmer Vernooij) * subvertpy.client.Client.update now accepts keyword arguments. (Jelmer Vernooij) * Editors will now raise an exception if the protocol is not followed; children have to be closed before their parents are accessed again. (Jelmer Vernooij) * Add constant ERR_DIR_NOT_EMPTY. (Jelmer Vernooij) * Add constant ERR_FS_ROOT_DIR. (Jelmer Vernooij) * Add `subvertpy.repos.Repository.verify_fs`. (Jelmer Vernooij) * Add `subvertpy.repos.Repository.pack_fs`. (Jelmer Vernooij) BUG FIXES * Fix memory leak in subvertpy.client.Client.list. (Wez Furlong) * Fix double free of apr pools from subverty.repos.Repos.fs. (Roland Mas, Jelmer Vernooij, #853960) 0.8.5 2011-08-21 "boomerang arrow" BUG FIXES * Fix compatibility with python < 2.6, where T_BOOL is not available. (Jelmer Vernooij, #829993) 0.8.4 2011-08-18 "mimic octopus" FEATURES * Fix argument count in subvertpy.client.Client.export. (Jelmer Vernooij, #820662) * Add subvertpy.wc.WorkingCopy.status. (Mark Lee) 0.8.3 2011-07-24 "manual override" BUG FIXES * Fix memory management in subvertpy.ra.Auth. (Jelmer Vernooij) * Fix type for revnum in process_committed_queue. (Jelmer Vernooij, #730931) * Fix double free when calling close() more than once on editor objects. (Jelmer Vernooij) * Fix too early free during iter_log. (Jelmer Vernooij) * Prevent freeing the RemoteAccess object before any individual request frees. Thanks very much to Steve Langasek for the help debugging this! (Jelmer Vernooij, #803353) * Error out properly when editor objects are used after they are closed. (Jelmer Vernooij) FEATURES * Add constant ERR_BAD_PROPERTY_VALUE. (Jelmer Vernooij) 0.8.2 2011-06-17 "mathematically annoying" BUG FIXES * Abort immediately if a callback raises an exception. (Jelmer Vernooij) * Fix leaking of error objects. (Jelmer Vernooij) 0.8.1 2011-06-02 "ornithologic reptile" FEATURES * Add subvertpy.wc.WorkingCopy.conflicted. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.resolved_conflict. (Jelmer Vernooij) * Add subvertpy.client.Client.mkdir. (Jelmer Vernooij) BUG FIXES * Fix compilation on OS X. (#728574) * Keep reference to commit callback in editor. (Jelmer Vernooij, #732120) * Properly check return values of PyInt_AsLong() in various places. * Convert times and file sizes to 64 bit integer objects. (Jelmer Vernooij, #786156) 0.8.0 2011-03-02 "(" FEATURES * Add --version option to subvertpy-fast-export. (Jelmer Vernooij) * Add basic manual page for subvertpy-fast-export. (Jelmer Vernooij) * Automatically convert the appropriate errors to socket.gaierror. (Jelmer Vernooij) * Add subvertpy.wc.set_adm_dir(). (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.has_binary_prop. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.get_ancestry. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.maybe_set_repos_root. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.add_repos_file. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.mark_missing_deleted. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.remove_from_revision_control. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.relocate. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.crop_tree. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.translated_stream. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.text_modified. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.props_modified. (Jelmer Vernooij) * Add subvertpy.wc.CommittedQueue. (Jelmer Vernooij) * Add subvertpy.wc.get_actual_target. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.is_wc_root. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.transmit_text_deltas. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.transmit_prop_deltas. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.probe_retrieve, subvertpy.wc.WorkingCopy.retrieve. (Jelmer Vernooij) * Add subvertpy.wc.WorkingCopy.probe_try. (Jelmer Vernooij) * Add subvertpy.client.export. (Jelmer Vernooij) DEPRECATIONS * ERR_UNKNOWN_HOSTNAME and ERR_EAI_NONAME are deprecated and will be removed in the next version of subvertpy. Instead, subvertpy will raise socket.gaierror. (Jelmer Vernooij) * Use svn_fs_paths_changed2() if available. (Jelmer Vernooij) BUG FIXES * Fix handling of DST in time_from_cstring. (Max Bowsher, #656226) * Re-using a WorkingCopy object after calling close() on it now raises an exception rather than trigger a segfault. (Jelmer Vernooij) * Fix type mismatch in svn.ra.get_dir(). (Jelmer Vernooij, #686663) * Cope with trailing slash being specified to subvertpy.wc.revision_stats(). (Ronny Pfannschmidt) API BREAKS * WorkingCopy.entry() will now raise KeyError if the specified entry was not found. (Jelmer Vernooij) 0.7.5 2010-10-26 BUG FIXES * Fix compatibility with Subversion 1.4 and 1.5. (Max Bowsher) 0.7.4 2010-09-25 FEATURES * Add constants ERR_APR_OS_START_EAIERR and ERR_APR_OS_ERRSPACE_SIZE. (Jelmer Vernooij) * Implement subvertpy.wc.match_ignore_list. (Jelmer Vernooij) * Add subvertpy.ra.RemoteAccess.iter_log(). (Jelmer Vernooij) * Add subvertpy.wc.STATUS_* constants. (Jelmer Vernooij) * Add subvertpy.wc.api_version(), subvertpy.ra.api_version(). (Dan Villiom Podlaski Christiansen, Jelmer Vernooij) DEPRECATION * Avoid deprecation warnings for svn.client.Client.copy, svn.client.Client.propset, svn.client.Client.propget, svn.client.Client.do_update. (Jelmer Vernooij) * Avoid deprecation warnings for svn.ra.RemoteAccess.do_update, svn.ra.RemoteAccess.do_switch, svn.ra.RemoteAccess.do_diff. (Jelmer Vernooij) * Avoid deprecation warnings for svn.wc.WorkingCopy.propset, svn.wc.WorkingCopy.walk_entries, svn.wc.WorkingCopy.add, svn.wc.WorkingCopy.close, svn.wc.WorkingCopy.crawl_revisions, svn.wc.WorkingCopy.update_editor, svn.wc.ensure_adm. (Jelmer Vernooij) * Add ERR_ENTRY_NOT_FOUND constant. (Jelmer Vernooij) * Add subvertpy.ra.RemoteAccess.get_url(). (Dan Villiom Podlaski Christiansen) BUG FIXES * Fix compilation using Visual C++. (Ronald Blaschke, #612056) * Return an empty dictionary rather than None when there are no revision properties. (Jelmer Vernooij) * Link against all libraries that are somehow called. (Daniel Johnson, Jelmer Vernooij, #615015) * Include check for apr util headers. (Jelmer Vernooij, #615017) * Fix reference leaks in Auth and SubversionException handling. (Jelmer Vernooij, #436406) * Canonicalize paths in subvertpy.ra.RemoteAccess.get_locations(). (Jelmer Vernooij) 0.7.3.1 2010-07-27 BUG FIXES * Fix compilation against Subversion 1.5. (Jelmer Vernooij) 0.7.3 2010-07-21 BUG FIXES * Canonicalize paths to subvertpy.wc.check_wc. (Ronny Pfannschmidt) * Canonicalize paths to subvertpy.repos.Repos. (Dan Villiom Podlaski Christiansen) * Cope with leading slashes in paths. (David Wolever, #527239) * Various methods are now a bit more liberal in accepting any sort of sequence rather than explicitly requiring lists. (Jelmer Vernooij) FEATURES * Implement subvertpy.client.Client.diff. (Dan Villiom Podlaski Christiansen, Jelmer Vernooij) * Support ``revprops`` argument to subvertpy.client.Client.commit. (Jelmer Vernooij) * Implement subvertpy.wc.get_pristine_contents(). (Jelmer Vernooij) * Use better error messages when possible. (Dan Villiom Podlaski Christiansen) * Implement subvertpy.ra.get_platform_specific_client_providers(). (Dan Villiom Podlaski Christiansen) 0.7.2 2010-01-03 BUG FIXES * Fix inconsistencies between PyArg_ParseTuple arguments and variables, which caused crashes on some 64-bit platforms. (Václav Slavík, #488780) * Add constant for ERR_NODE_UNKNOWN_KIND. (Dan Villiom Podlaski Christiansen) * Add svn.client.proplist() binding. (Dan Villiom Podlaski Christiansen, Jelmer Vernooij) * Add svn.client.list() binding. (Dan Villiom Podlaski Christiansen, Jelmer Vernooij) 0.7.1 2009-10-20 BUG FIXES * Fix APR include path on Windows. (Ronald Blaschke, #456321) 0.7.0 2009-10-20 FEATURES * Add definition for ERR_EAI_NONAME. (John Szakmeister) * Add subvertpy.repos.FileSystem methods: youngest_revision, revision_root, revision_proplist. (Jelmer Vernooij) * Add subvertpy.repos.{delete,Repository.has_capability,hotcopy}. (Jelmer Vernooij) * Add subvertpy-fast-export script, based on svn-fast-export.py by Chris Lee included with bzr-fast-import. (Jelmer Vernooij) 0.6.9 2009-09-10 BUG FIXES * Cope with leading slashes in paths specified to subvertpy.ra.RemoteAccess.get_{dir,file}(). Thanks Ronny for the bugreport. (#405713) * Don't send too large svndiff windows as newer versions of libsvn will barf. (CVE2009-2411, #413113) 0.6.8 2009-06-18 FEATURES * Support svn.client.Client.delete(keep_local=True) * Support svn.wc.WorkingCopy.delete(keep_local=True) * Define ERR_RA_DAV_FORBIDDEN. 0.6.7 2009-06-01 BUG FIXES * Replace PyExc_ArgumentError with PyExc_TypeError. * Fix compilation with MSVC++. FEATURES * Support RemoteAccess.stat. * Support svn.client.add(parents=false) * Support uuid argument to svn.ra.RemoteAccess(). 0.6.6 2009-05-04 FEATURES * wc.Entry.uuid is now provided * New error code ERR_MALFORMED_FILE. BUG FIXES * Fix RemoteAccess against http:// without an auth baton. * Cope with invalid arguments to Auth(). (#347155) * Support svn.client.resolve. * Allow non-canonical paths to some working copy methods * Fixed uninitialized memory bug when reading configuration. 0.6.5 2009-03-07 FEATURES * Add subvertpy.wc.cleanup() * Split out subvertpy.delta.txdelta_apply_window. Relicensed to LGPLv2.1 or later. 0.6.4 2009-02-17 BUG FIXES * Fix reference counting for progress callback in RemoteAccess. * Fix several (major) object leaks. 0.6.3 2009-02-12 FEATURES * Add svn:original-date constant. 0.6.2 2009-02-10 BUG FIXES * Install Windows binaries to the right directory. (#323276) * Fix import of warn(). FEATUREs * Add WorkingCopy.remove_lock(path) 0.6.1 2009-01-25 API BREAKS * subvertpy.properties.diff() now returns not just the new property value but a tuple with the old property value and the new property value. BUG FIXES * Fixed bdist_rpm. (#311712) * Fixed segfault when unsetting property values. (#319313) * Allow non-canonical path arguments in subvertpy.wc.WorkingCopy() * Convert APR-based OSError properly. (#323548) 0.6 2009-01-13 Initial release. subvertpy_0.11.1.orig/PKG-INFO0000644000000000000000000000350415154541453012712 0ustar00Metadata-Version: 2.4 Name: subvertpy Version: 0.11.1 Summary: Alternative Python bindings for Subversion Home-page: https://jelmer.uk/subvertpy Download-URL: https://jelmer.uk/subvertpy/tarball/subvertpy-0.11.1/ Author: Jelmer Vernooij Author-email: jelmer@jelmer.uk License: LGPLv2.1 or later Keywords: svn subvertpy subversion bindings 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 License-File: COPYING License-File: AUTHORS Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: download-url Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: summary 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). subvertpy_0.11.1.orig/README.md0000644000000000000000000000267313776732153013111 0ustar00[![Build Status](https://travis-ci.org/jelmer/subvertpy.png?branch=master)](https://travis-ci.org/jelmer/subvertpy) Subvertpy ========= Homepage: https://jelmer.uk/subvertpy/ Python bindings for the Subversion version control system that are aimed to be complete, fast and feel native to Python programmers. 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.9 or later. It should work on Windows as well as most POSIX-based platforms (including Linux, BSDs and Mac OS X). See https://subversion.apache.org/ for instructions on installing Subversion. Installation ------------ Standard distutils are used - use "setup.py build" to build and "setup.install" to install. On most platforms, setup will find the Python and Subversion development libraries by itself. Development ----------- If using GCC it might be useful to disable the deprecation warnings when compiling to see if there are any more serious warnings: make CFLAGS="-Wno-deprecated-declarations" subvertpy_0.11.1.orig/TODO0000644000000000000000000000010011133435062012261 0ustar00- Add more docstrings - Add more examples - More protocol tests subvertpy_0.11.1.orig/bin/0000755000000000000000000000000011265104376012361 5ustar00subvertpy_0.11.1.orig/examples/0000755000000000000000000000000011107042732013417 5ustar00subvertpy_0.11.1.orig/man/0000755000000000000000000000000011461665270012367 5ustar00subvertpy_0.11.1.orig/pyproject.toml0000644000000000000000000000016315153657106014531 0ustar00[tool.ruff] exclude = [ "build", ".git", "build-pypy", ".tox", "subversion-*", "serf-*", ] subvertpy_0.11.1.orig/setup.cfg0000644000000000000000000000004615154541453013434 0ustar00[egg_info] tag_build = tag_date = 0 subvertpy_0.11.1.orig/setup.py0000755000000000000000000002476415154541453013345 0ustar00#!/usr/bin/env python # Setup file for subvertpy # Copyright (C) 2005-2010 Jelmer Vernooij from setuptools import setup from setuptools.extension import Extension import errno import sys import os import re import shlex import subprocess def split_shell_results(line): return shlex.split(line) def config_value(command, env, args): command = os.environ.get(env, command) try: return subprocess.check_output([command] + args).strip().decode() except OSError as e: if e.errno == errno.ENOENT: raise Exception( "%s not found. Please set %s environment variable" % (command, env) ) raise def apr_config(args): return config_value("apr-1-config", "APR_CONFIG", args) def apu_config(args): return config_value("apu-1-config", "APU_CONFIG", args) def apr_build_data(): """Determine the APR header file location.""" try: includedir = os.environ["APR_INCLUDE_DIR"] except KeyError: if os.name != "nt": includedir = apr_config(["--includedir"]) if not os.path.isdir(includedir): raise Exception("APR development headers not found") try: extra_link_flags = split_shell_results(os.environ["APR_LINK_FLAGS"]) except KeyError: if os.name != "nt": extra_link_flags = split_shell_results(apr_config(["--link-ld", "--libs"])) else: extra_link_flags = [] return (includedir, extra_link_flags) def apu_build_data(): """Determine the APR util header file location.""" try: includedir = os.environ["APU_INCLUDE_DIR"] except KeyError: if os.name != "nt": includedir = apu_config(["--includedir"]) if not os.path.isdir(includedir): raise Exception("APR util development headers not found") try: extra_link_flags = split_shell_results(os.environ["APU_LINK_FLAGS"]) except KeyError: if os.name != "nt": extra_link_flags = split_shell_results(apu_config(["--link-ld", "--libs"])) else: extra_link_flags = [] return (includedir, extra_link_flags) def svn_build_data(): """Determine the Subversion header file location.""" if "SVN_HEADER_PATH" in os.environ and "SVN_LIBRARY_PATH" in os.environ: return ([os.getenv("SVN_HEADER_PATH")], [os.getenv("SVN_LIBRARY_PATH")], []) svn_prefix = os.getenv("SVN_PREFIX") if svn_prefix is None: basedirs = ["/usr/local", "/usr", "/opt/homebrew", "/opt/local"] for basedir in basedirs: includedir = os.path.join(basedir, "include/subversion-1") if os.path.isdir(includedir): svn_prefix = basedir break if svn_prefix is not None: return ( [os.path.join(svn_prefix, "include/subversion-1")], [os.path.join(svn_prefix, "lib"), os.path.join(svn_prefix, "lib", "db48")], [], ) raise Exception( "Subversion development files not found. " "Please set SVN_PREFIX or (SVN_LIBRARY_PATH and " "SVN_HEADER_PATH) environment variable. " ) def is_keychain_provider_available(): """ Checks for the availability of the Keychain simple authentication provider in Subversion by compiling a simple test program. """ abd = apr_build_data() sbd = svn_build_data() gcc_command_args = ( ["gcc"] + ["-I" + inc for inc in sbd[0]] + ["-L" + lib for lib in sbd[1]] + ["-I" + abd[0], "-lsvn_subr-1", "-x", "c", "-"] ) gcc = subprocess.Popen( gcc_command_args, stdin=subprocess.PIPE, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) gcc.communicate(""" #include int main(int argc, const char* arv[]) { svn_auth_get_keychain_simple_provider(NULL, NULL); } """) return gcc.returncode == 0 class VersionQuery(object): def __init__(self, filename): self.filename = filename f = open(filename, "rU") try: self.text = f.read() finally: f.close() def grep(self, what): m = re.search(r"^#define\s+%s\s+(\d+)\s*$" % (what,), self.text, re.MULTILINE) if not m: raise Exception( "Definition for %s was not found in file %s." % (what, self.filename) ) return int(m.group(1)) (apr_includedir, apr_link_flags) = apr_build_data() (apu_includedir, apu_link_flags) = apu_build_data() (svn_includedirs, svn_libdirs, svn_link_flags) = svn_build_data() class SvnExtension(Extension): def __init__(self, name, *args, **kwargs): if sys.platform == "win32": libraries = kwargs.get("libraries", []) libs = [("lib" + lib) for lib in libraries] modified = True while modified: modified = False for lib in libraries: for extra in deep_deps.get(lib, []): if extra not in libs: modified = True libs.append(extra) kwargs["libraries"] = libs kwargs["include_dirs"] = ( [apr_includedir, apu_includedir] + svn_includedirs + ["subvertpy"] ) kwargs["library_dirs"] = svn_libdirs # Note that the apr-util link flags are not included here, as # subvertpy only uses some apr util constants but does not use # the library directly. kwargs["extra_link_args"] = apr_link_flags + apu_link_flags + svn_link_flags if os.name == "nt": # APR needs WIN32 defined. kwargs["define_macros"] = [("WIN32", None)] if sys.platform == "darwin": # on Mac OS X, we need to check for Keychain availability if is_keychain_provider_available(): if "define_macros" not in kwargs: kwargs["define_macros"] = [] kwargs["define_macros"].extend( (("DARWIN", None), ("SVN_KEYCHAIN_PROVIDER_AVAILABLE", "1")) ) if sys.platform in ("darwin", "linux"): kwargs["extra_compile_args"] = [ "-std=c99" # for GCC >=15 as it is using c23 by default ] Extension.__init__(self, name, *args, **kwargs) def source_path(filename): return os.path.join("subvertpy", filename) # Urgh. It's a pain having to maintain these manually. But what else can we do? subr_deep_deps = [ "libsvn_subr-1", "libapr-1", "libaprutil-1", "sqlite3", "zlib", "advapi32", "crypt32", "libexpat", "shell32", "ws2_32", "mswsock", "version", "ole32", ] repos_deep_deps = [ "libsvn_repos-1", "libsvn_fs-1", "libsvn_fs_util-1", "libsvn_fs_fs-1", "libsvn_fs_x-1", "libsvn_delta-1", "libapr-1", "libaprutil-1", ] ra_deep_deps = [ "libsvn_ra_svn-1", "libsvn_ra_local-1", "libsvn_repos-1", "libapr-1", "libaprutil-1", ] deep_deps = { "svn_ra-1": ra_deep_deps, "svn_repos-1": repos_deep_deps, "svn_subr-1": subr_deep_deps, } def subvertpy_modules(): return [ SvnExtension( "subvertpy.client", [ source_path(n) for n in ("client.c", "editor.c", "util.c", "_ra.c", "wc.c") ], libraries=[ "svn_client-1", "svn_diff-1", "svn_delta-1", "svn_wc-1", "svn_ra-1", "svn_subr-1", ], ), SvnExtension( "subvertpy._ra", [source_path(n) for n in ("_ra.c", "util.c", "editor.c")], libraries=["svn_delta-1", "svn_ra-1", "svn_subr-1"], ), SvnExtension( "subvertpy.repos", [source_path(n) for n in ("repos.c", "util.c")], libraries=["svn_repos-1", "svn_subr-1"], ), SvnExtension( "subvertpy.wc", [source_path(n) for n in ["wc.c", "util.c", "editor.c"]], libraries=["svn_wc-1", "svn_diff-1", "svn_delta-1", "svn_subr-1"], ), SvnExtension( "subvertpy.subr", [source_path(n) for n in ["util.c", "subr.c"]], libraries=["svn_subr-1"], ), ] def package_data(): if sys.platform == "win32": return {"subvertpy": ["subvertpy/*.dll"]} else: return {} subvertpy_version = (0, 11, 1) subvertpy_version_string = ".".join(map(str, subvertpy_version)) if __name__ == "__main__": setup( name="subvertpy", description="Alternative Python bindings for Subversion", keywords="svn subvertpy subversion bindings", version=subvertpy_version_string, url="https://jelmer.uk/subvertpy", 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. 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"], package_data=package_data(), ext_modules=subvertpy_modules(), scripts=["bin/subvertpy-fast-export"], classifiers=[ "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU General Public " "License v2 or later (GPLv2+)", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Operating System :: POSIX", "Topic :: Software Development :: Version Control", ], ) subvertpy_0.11.1.orig/subvertpy.cfg0000644000000000000000000000020412556577515014346 0ustar00packages: subvertpy docformat: restructuredtext projectname: subvertpy projecturl: https://jelmer.uk/subvertpy/ htmloutput: apidocs subvertpy_0.11.1.orig/subvertpy.egg-info/0000755000000000000000000000000013776732153015357 5ustar00subvertpy_0.11.1.orig/subvertpy/0000755000000000000000000000000011071226625013651 5ustar00subvertpy_0.11.1.orig/bin/subvertpy-fast-export0000755000000000000000000001724613133767436016646 0ustar00#!/usr/bin/python # # svn-fast-export.py # ---------- # Walk through each revision of a local Subversion repository and export it # in a stream that git-fast-import can consume. # # Author: Chris Lee # License: MIT # # Adapted for subvertpy by Jelmer Vernooij from io import BytesIO import sys import os.path from optparse import OptionParser import stat from time import mktime, strptime from subvertpy.repos import PATH_CHANGE_DELETE, Repository trunk_path = '/trunk/' branches_path = '/branches/' tags_path = '/tags/' address = 'localhost' ct_short = ['M', 'A', 'D', 'R', 'X'] stdout = getattr(sys.stdout, 'buffer', sys.stdout) def dump_file_blob(root, stream, stream_length): stdout.write(("data %s\n" % stream_length).encode("ascii")) stdout.flush() stdout.write(stream.read()) stdout.write(b"\n") class Matcher(object): branch = None def __init__(self, trunk_path): self.trunk_path = trunk_path def branchname(self): return self.branch def __str__(self): return super(Matcher, self).__str__() + ":" + self.trunk_path @staticmethod def getMatcher(trunk_path): if trunk_path.startswith("regex:"): return RegexStringMatcher(trunk_path) else: return StaticStringMatcher(trunk_path) class StaticStringMatcher(Matcher): branch = "master" def __init__(self, trunk_path): if not trunk_path.startswith("/"): raise ValueError("Trunk path does not start with a slash (/)") Matcher.__init__(self, trunk_path) def matches(self, path): return path.startswith(self.trunk_path) def replace(self, path): return path.replace(self.trunk_path, '') class RegexStringMatcher(Matcher): def __init__(self, trunk_path): super(RegexStringMatcher, self).__init__(trunk_path) import re self.matcher = re.compile(self.trunk_path[len("regex:"):]) def matches(self, path): match = self.matcher.match(path) if match: self.branch = match.group(1) return True else: return False def replace(self, path): return self.matcher.sub("\g<2>", path) MATCHER = None def export_revision(rev, fs): sys.stderr.write("Exporting revision %s... " % rev) # Open a root object representing the youngest (HEAD) revision. root = fs.revision_root(rev) # And the list of what changed in this revision. changes = root.paths_changed() i = 1 marks = {} file_changes = [] for path, (node_id, change_type, text_changed, prop_changed) in changes.items(): if root.is_dir(path): continue if not MATCHER.matches(path): # We don't handle branches. Or tags. Yet. pass else: if change_type == PATH_CHANGE_DELETE: file_changes.append("D %s" % MATCHER.replace(path).lstrip("/")) else: props = root.proplist(path) marks[i] = MATCHER.replace(path) if props.get("svn:special", ""): contents = root.file_content(path).read() if not contents.startswith(b"link "): sys.stderr.write("special file '%s' is not a symlink, ignoring...\n" % path) continue mode = stat.S_IFLNK stream = BytesIO(contents[len(b"link "):]) stream_length = len(stream.getvalue()) else: if props.get("svn:executable", ""): mode = 0o755 else: mode = 0o644 stream_length = root.file_length(path) stream = root.file_content(path) file_changes.append("M %o :%s %s" % ( mode, i, marks[i].lstrip("/"))) stdout.write(("blob\nmark :%s\n" % i).encode("ascii")) dump_file_blob(root, stream, stream_length) stream.close() i += 1 # Get the commit author and message props = fs.revision_proplist(rev) # Do the recursive crawl. if 'svn:author' in props: author = "%s <%s@%s>" % (props['svn:author'], props['svn:author'], address) else: author = 'nobody ' if len(file_changes) == 0: sys.stderr.write("skipping.\n") return svndate = props['svn:date'][0:-8] commit_time = mktime(strptime(svndate, '%Y-%m-%dT%H:%M:%S')) line = "commit refs/heads/%s\n" % MATCHER.branchname() stdout.write(line.encode("utf-8")) line = "committer %s %s -0000\n" % (author, int(commit_time)) stdout.write(line.encode("utf-8")) stdout.write(("data %s\n" % len(props['svn:log'])).encode("ascii")) stdout.write(props['svn:log'].encode("utf-8")) stdout.write(b"\n") stdout.write(b'\n'.join(c.encode("utf-8") for c in file_changes)) stdout.write(b"\n\n") sys.stderr.write("done!\n") def crawl_revisions(repos_path, first_rev=None, final_rev=None): """Open the repository at REPOS_PATH, and recursively crawl all its revisions.""" # Open the repository at REPOS_PATH, and get a reference to its # versioning filesystem. fs_obj = Repository(repos_path).fs() # Query the current youngest revision. if first_rev is None: first_rev = 1 if final_rev is None: final_rev = fs_obj.youngest_revision() for rev in range(first_rev, final_rev + 1): export_revision(rev, fs_obj) if __name__ == '__main__': usage = '%prog [options] REPOS_PATH' parser = OptionParser() parser.set_usage(usage) parser.add_option('-f', '--final-rev', help='Final revision to import', dest='final_rev', metavar='FINAL_REV', type='int') parser.add_option('-r', '--first-rev', help='First revision to import', dest='first_rev', metavar='FIRST_REV', type='int') parser.add_option( '-t', '--trunk-path', help=( "Path in repo to /trunk, may be `regex:/cvs/(trunk)/proj1/(.*)`\n" "First group is used as branchname, second to match files"), dest='trunk_path', metavar='TRUNK_PATH') parser.add_option( '-b', '--branches-path', help='Path in repo to /branches', dest='branches_path', metavar='BRANCHES_PATH') parser.add_option( '-T', '--tags-path', help='Path in repo to /tags', dest='tags_path', metavar='TAGS_PATH') parser.add_option( '-a', '--address', help='Domain to put on users for their mail address', dest='address', metavar='hostname', type='string') parser.add_option( "--version", help="Print version and exit", action="store_true") (options, args) = parser.parse_args() if options.version: import subvertpy print(".".join(str(x) for x in subvertpy.__version__)) sys.exit(0) if options.trunk_path is not None: trunk_path = options.trunk_path if options.branches_path is not None: branches_path = options.branches_path if options.tags_path is not None: tags_path = options.tags_path if options.address is not None: address = options.address MATCHER = Matcher.getMatcher(trunk_path) sys.stderr.write("%s\n" % MATCHER) if len(args) != 1: parser.print_help() sys.exit(2) # Canonicalize (enough for Subversion, at least) the repository path. repos_path = os.path.normpath(args[0]) if repos_path == '.': repos_path = '' crawl_revisions(repos_path, first_rev=options.first_rev, final_rev=options.final_rev) subvertpy_0.11.1.orig/examples/ra_commit.py0000755000000000000000000000263415154541453015765 0ustar00#!/usr/bin/python # Demonstrates how to do a new commit using Subvertpy import os from io import BytesIO from subvertpy import delta, repos from subvertpy.ra import RemoteAccess, Auth, get_username_provider # Create a repository repos.create("tmprepo") # Connect to the "remote" repository using the file transport. # Note that a username provider needs to be provided, so that Subversion # knows who to record as the author of new commits made over this connection. repo_url = "file://%s" % os.path.abspath("tmprepo") conn = RemoteAccess(repo_url, auth=Auth([get_username_provider()])) # Simple commit that adds a directory editor = conn.get_commit_editor({"svn:log": "Commit message"}) root = editor.open_root() # Add a directory dir = root.add_directory("somedir") dir.close() # Add and edit a file file = root.add_file("somefile") # Set the svn:executable attribute file.change_prop("svn:executable", "*") # Obtain a textdelta handler and send the new file contents txdelta = file.apply_textdelta() delta.send_stream(BytesIO(b"new file contents"), txdelta) file.close() root.close() editor.close() # Rename the directory editor = conn.get_commit_editor({"svn:log": "Commit message"}) root = editor.open_root() # Create a new directory copied from somedir:1 dir = root.add_directory("new dir name", "%s/somedir" % repo_url, 1) dir.close() # Remove the original directory root.delete_entry("somedir") root.close() editor.close() subvertpy_0.11.1.orig/examples/ra_log.py0000755000000000000000000000121515154541453015250 0ustar00#!/usr/bin/python # Demonstrates how to iterate over the log of a Subversion repository. from subvertpy.ra import RemoteAccess conn = RemoteAccess("svn://svn.samba.org/subvertpy/trunk") for changed_paths, rev, revprops, has_children in conn.iter_log( paths=None, start=0, end=conn.get_latest_revnum(), discover_changed_paths=True ): print("=" * 79) print("%d:" % rev) print("Revision properties:") for entry in revprops.items(): print(" %s: %s" % entry) print("") print("Changed paths") for path, (action, from_path, from_rev, node_kind) in changed_paths.items(): print(" %s (%s)" % (path, action)) subvertpy_0.11.1.orig/examples/ra_replay.py0000755000000000000000000000340615154541453015767 0ustar00#!/usr/bin/python # Demonstrates how to use the replay function to fetch the # changes made in a revision. from subvertpy.ra import RemoteAccess, Auth, get_username_provider conn = RemoteAccess( "svn://svn.gnome.org/svn/gnome-specimen/trunk", auth=Auth([get_username_provider()]) ) class MyFileEditor: def change_prop(self, key, value): print("Change prop: %s -> %r" % (key, value)) def apply_textdelta(self, base_checksum): # This should return a function that can receive delta windows def apply_window(x): pass return apply_window def close(self): pass class MyDirEditor: def open_directory(self, *args): print("Open dir: %s (base revnum: %r)" % args) return MyDirEditor() def add_directory(self, path, copyfrom_path=None, copyfrom_rev=-1): print("Add dir: %s (from %r:%r)" % (path, copyfrom_path, copyfrom_rev)) return MyDirEditor() def open_file(self, *args): print("Open file: %s (base revnum: %r)" % args) return MyFileEditor() def add_file(self, path, copyfrom_path=None, copyfrom_rev=-1): print("Add file: %s (from %r:%r)" % (path, copyfrom_path, copyfrom_rev)) return MyFileEditor() def change_prop(self, key, value): print("Change prop %s -> %r" % (key, value)) def delete_entry(self, path, revision): print("Delete: %s" % path) def close(self): pass class MyEditor: def set_target_revision(self, revnum): print("Target revision: %d" % revnum) def abort(self): print("Aborted") def close(self): print("Closed") def open_root(self, base_revnum): print("/") return MyDirEditor() editor = MyEditor() conn.replay(230, 1, editor) subvertpy_0.11.1.orig/examples/ra_shell.py0000755000000000000000000000536415154541453015607 0ustar00#!/usr/bin/python import cmd import subvertpy from subvertpy.ra import RemoteAccess import sys if len(sys.argv) == 1: print("Usage: %s " % sys.argv) url = sys.argv[1] conn = RemoteAccess(url) def log_printer(changed_paths, rev, revprops, has_children=None): print("=" * 79) print("%d:" % rev) print("Revision properties:") for entry in revprops.items(): print(" %s: %s" % entry) print("") if changed_paths is None: return print("Changed paths:") for path, (action, from_path, from_rev) in changed_paths.items(): print(" %s (%s)" % (path, action)) class RaCmd(cmd.Cmd): @staticmethod def parse_path_revnum(line): args = line.split(" ") if len(args) == 0: return ".", -1 elif len(args) == 1: return args[0], -1 elif len(args) == 2: return args[0], int(args[1]) else: raise Exception("Too much arguments (%r), expected 2" % (args,)) def do_help(self, args): for name in sorted(self.__class__.__dict__): if name.startswith("do_"): print(name[3:]) def do_stat(self, args): path, revnum = self.parse_path_revnum(args) print(conn.stat(path, revnum)) def do_ls(self, args): path, revnum = self.parse_path_revnum(args) (dirents, fetched_rev, props) = conn.get_dir(path, revnum) for name in dirents: print(name) def do_cat(self, args): path, revnum = self.parse_path_revnum(args) outf = getattr(sys.stdout, "buffer", sys.stdout) (fetched_rev, props) = conn.get_file(path, outf, revnum) def do_reparent(self, args): conn.reparent(args) def do_set_revprop(self, args): (revnum, name, value) = args.split(" ", 2) conn.change_rev_prop(int(revnum), name, value) def do_has_capability(self, args): print(conn.has_capability(args)) def do_revprops(self, args): for item in conn.rev_proplist(int(args)).items(): print("%s: %s" % item) def do_check_path(self, args): path, revnum = self.parse_path_revnum(args) kind = conn.check_path(path, revnum) if kind == subvertpy.NODE_DIR: print("dir") elif kind == subvertpy.NODE_FILE: print("file") else: print("nonexistant") def do_uuid(self, args): print(conn.get_uuid()) def do_get_repos_root(self, args): print(conn.get_repos_root()) def do_log(self, args): conn.get_log( callback=log_printer, paths=None, start=0, end=conn.get_latest_revnum(), discover_changed_paths=True, ) cmdline = RaCmd() cmdline.cmdloop() subvertpy_0.11.1.orig/examples/wc.py0000644000000000000000000000072113133244127014405 0ustar00#!/usr/bin/python # Demonstrates how to do access the working tree using subvertpy import os from subvertpy import client, repos, wc from subvertpy.ra import Auth, get_username_provider # Create a repository repos.create("tmprepo") c = client.Client(auth=Auth([get_username_provider()])) c.checkout("file://" + os.getcwd() + "/tmprepo", "tmpco", "HEAD") w = wc.WorkingCopy(None, "tmpco") print(w) entry = w.entry("tmpco") print(entry.revision) print(entry.url) subvertpy_0.11.1.orig/man/subvertpy-fast-export.10000644000000000000000000000231112556577515016775 0ustar00.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.38.2. .TH SUBVERTPY-FAST-EXPORT "1" "October 2010" "subvertpy-fast-export" "subvertpy" .SH NAME subvertpy-fast-export \- generate fastexport stream from a Subversion repository .SH DESCRIPTION Generate fastexport stream from a Subversion repository. .SH SYNOPSIS .B subvertpy-fast-export [\fIoptions\fR] \fIREPOS_PATH\fR .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR show a help message and exit .TP \fB\-f\fR FINAL_REV, \fB\-\-final\-rev\fR=\fIFINAL_REV\fR Final revision to import .TP \fB\-r\fR FIRST_REV, \fB\-\-first\-rev\fR=\fIFIRST_REV\fR First revision to import .TP \fB\-t\fR TRUNK_PATH, \fB\-\-trunk\-path\fR=\fITRUNK_PATH\fR Path in repo to /trunk, may be `regex:/cvs/(trunk)/proj1/(.*)` First group is used as branchname, second to match files .TP \fB\-b\fR BRANCHES_PATH, \fB\-\-branches\-path\fR=\fIBRANCHES_PATH\fR Path in repo to /branches .TP \fB\-T\fR TAGS_PATH, \fB\-\-tags\-path\fR=\fITAGS_PATH\fR Path in repo to /tags .TP \fB\-a\fR hostname, \fB\-\-address\fR=\fIhostname\fR Domain to put on users for their mail address .TP \fB\-\-version\fR Print version and exit .SH "SEE ALSO" .UR https://jelmer.uk/subvertpy/ .BR https://jelmer.uk/subvertpy/ subvertpy_0.11.1.orig/subvertpy.egg-info/PKG-INFO0000644000000000000000000000350415154541453016447 0ustar00Metadata-Version: 2.4 Name: subvertpy Version: 0.11.1 Summary: Alternative Python bindings for Subversion Home-page: https://jelmer.uk/subvertpy Download-URL: https://jelmer.uk/subvertpy/tarball/subvertpy-0.11.1/ Author: Jelmer Vernooij Author-email: jelmer@jelmer.uk License: LGPLv2.1 or later Keywords: svn subvertpy subversion bindings 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 License-File: COPYING License-File: AUTHORS Dynamic: author Dynamic: author-email Dynamic: classifier Dynamic: description Dynamic: download-url Dynamic: home-page Dynamic: keywords Dynamic: license Dynamic: license-file Dynamic: summary 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). subvertpy_0.11.1.orig/subvertpy.egg-info/SOURCES.txt0000644000000000000000000000214315154541453017234 0ustar00AUTHORS COPYING INSTALL MANIFEST.in Makefile NEWS README.md TODO pyproject.toml setup.py subvertpy.cfg bin/subvertpy-fast-export examples/ra_commit.py examples/ra_log.py examples/ra_replay.py examples/ra_shell.py examples/wc.py man/subvertpy-fast-export.1 subvertpy/__init__.py subvertpy/_ra.c subvertpy/_ra_iter_log.c subvertpy/client.c subvertpy/delta.py subvertpy/editor.c subvertpy/editor.h subvertpy/marshall.py subvertpy/properties.py subvertpy/ra.h subvertpy/ra.py subvertpy/ra_svn.py subvertpy/repos.c subvertpy/server.py subvertpy/stdbool.h subvertpy/subr.c subvertpy/util.c subvertpy/util.h subvertpy/wc.c subvertpy/wc.h subvertpy.egg-info/PKG-INFO subvertpy.egg-info/SOURCES.txt subvertpy.egg-info/dependency_links.txt subvertpy.egg-info/top_level.txt subvertpy/tests/__init__.py subvertpy/tests/test_client.py subvertpy/tests/test_core.py subvertpy/tests/test_delta.py subvertpy/tests/test_marshall.py subvertpy/tests/test_properties.py subvertpy/tests/test_ra.py subvertpy/tests/test_ra_svn.py subvertpy/tests/test_repos.py subvertpy/tests/test_server.py subvertpy/tests/test_subr.py subvertpy/tests/test_wc.pysubvertpy_0.11.1.orig/subvertpy.egg-info/dependency_links.txt0000644000000000000000000000000113776732153021425 0ustar00 subvertpy_0.11.1.orig/subvertpy.egg-info/top_level.txt0000644000000000000000000000001213776732153020102 0ustar00subvertpy subvertpy_0.11.1.orig/subvertpy/__init__.py0000644000000000000000000001046115154541453015771 0ustar00# Copyright (C) 2006-2017 Jelmer Vernooij # 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 """Python bindings for Subversion.""" import os import platform __author__ = "Jelmer Vernooij " __version__ = (0, 11, 1) NODE_DIR = 2 NODE_FILE = 1 NODE_NONE = 0 NODE_UNKNOWN = 3 ERR_UNSUPPORTED_FEATURE = 200007 ERR_RA_SVN_UNKNOWN_CMD = 210001 ERR_RA_SVN_CONNECTION_CLOSED = 210002 ERR_WC_LOCKED = 155004 ERR_RA_NOT_AUTHORIZED = 170001 ERR_INCOMPLETE_DATA = 200003 ERR_DIR_NOT_EMPTY = 200011 ERR_RA_SVN_MALFORMED_DATA = 210004 ERR_RA_NOT_IMPLEMENTED = 170003 ERR_FS_NO_SUCH_REVISION = 160006 ERR_FS_TXN_OUT_OF_DATE = 160028 ERR_REPOS_DISABLED_FEATURE = 165006 ERR_STREAM_MALFORMED_DATA = 140001 ERR_RA_ILLEGAL_URL = 170000 ERR_RA_LOCAL_REPOS_OPEN_FAILED = 180001 ERR_BAD_FILENAME = 125001 ERR_BAD_URL = 125002 ERR_BAD_DATE = 125003 ERR_RA_DAV_REQUEST_FAILED = 175002 ERR_RA_DAV_PATH_NOT_FOUND = 175007 ERR_FS_NOT_DIRECTORY = 160016 ERR_FS_NOT_FOUND = 160013 ERR_FS_ALREADY_EXISTS = 160020 ERR_RA_SVN_REPOS_NOT_FOUND = 210005 ERR_WC_NOT_WORKING_COPY = ERR_WC_NOT_DIRECTORY = 155007 ERR_ENTRY_EXISTS = 150002 ERR_WC_PATH_NOT_FOUND = 155010 ERR_CANCELLED = 200015 ERR_WC_UNSUPPORTED_FORMAT = 155021 ERR_UNKNOWN_CAPABILITY = 200026 ERR_AUTHN_NO_PROVIDER = 215001 ERR_RA_DAV_RELOCATED = 175011 ERR_FS_NOT_FILE = 160017 ERR_WC_BAD_ADM_LOG = 155009 ERR_WC_BAD_ADM_LOG_START = 155020 ERR_WC_NOT_LOCKED = 155005 ERR_RA_DAV_NOT_VCC = 20014 ERR_REPOS_HOOK_FAILURE = 165001 ERR_XML_MALFORMED = 130003 ERR_MALFORMED_FILE = 200002 ERR_FS_PATH_SYNTAX = 160005 ERR_RA_DAV_FORBIDDEN = 175013 ERR_WC_SCHEDULE_CONFLICT = 155013 ERR_RA_DAV_PROPPATCH_FAILED = 175008 ERR_SVNDIFF_CORRUPT_WINDOW = 185001 ERR_FS_CONFLICT = 160024 ERR_NODE_UNKNOWN_KIND = 145000 ERR_RA_SERF_SSL_CERT_UNTRUSTED = 230001 ERR_ENTRY_NOT_FOUND = 150000 ERR_BAD_PROPERTY_VALUE = 125005 ERR_FS_ROOT_DIR = 160021 ERR_WC_NODE_KIND_CHANGE = 155018 ERR_WC_UPGRADE_REQUIRED = 155036 ERR_RA_CANNOT_CREATE_SESSION = 170013 ERR_REPOS_BAD_ARGS = 165002 ERR_APR_OS_START_EAIERR = 670000 ERR_APR_OS_ERRSPACE_SIZE = 50000 ERR_CATEGORY_SIZE = 5000 # These will be removed in the next version of subvertpy ERR_EAI_NONAME = 670008 ERR_UNKNOWN_HOSTNAME = 670002 AUTH_PARAM_DEFAULT_USERNAME = "svn:auth:username" AUTH_PARAM_DEFAULT_PASSWORD = "svn:auth:password" SSL_NOTYETVALID = 0x00000001 SSL_EXPIRED = 0x00000002 SSL_CNMISMATCH = 0x00000004 SSL_UNKNOWNCA = 0x00000008 SSL_OTHER = 0x40000000 class SubversionException(Exception): """A Subversion exception""" def __init__(self, msg, num, child=None, location=None): self.args = (msg, num) self.child = child self.location = location def _check_mtime(m): """Check whether a C extension is out of date. :param m: Python module that is a C extension """ import os (base, _) = os.path.splitext(m.__file__) c_file = "%s.c" % base if not os.path.exists(c_file): return True if os.path.getmtime(m.__file__) < os.path.getmtime(c_file): return False return True if platform.system() == "Windows": dll_dirs = [ dll_dir for dll_dir in os.environ.get("SUBVERTPY_DLL_PATH", "").split(";") if dll_dir ] for path in dll_dirs: path = os.path.abspath(path) if os.path.exists(path): os.add_dll_directory(path) try: from subvertpy import client, _ra, repos, wc for x in client, _ra, repos, wc: if not _check_mtime(x): from warnings import warn warn("subvertpy extensions are outdated and need to be rebuilt") break except ImportError as e: raise ImportError("Unable to load subvertpy extensions: %s" % e) subvertpy_0.11.1.orig/subvertpy/_ra.c0000644000000000000000000025472415154541453014601 0ustar00/* * Copyright © 2008-2009 Jelmer Vernooij * -*- 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 */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include #include #include "editor.h" #include "util.h" #include "ra.h" #define REPORTER_T svn_ra_reporter3_t static PyObject *busy_exc; static PyTypeObject Reporter_Type; static PyTypeObject RemoteAccess_Type; static PyTypeObject AuthProvider_Type; static PyTypeObject CredentialsIter_Type; static PyTypeObject Auth_Type; static bool ra_check_svn_path(const char *path) { /* svn_ra_check_path will raise an assertion error if the path has a * leading '/'. Raise a Python exception if there ar eleading '/'s so that * the Python interpreter won't crash and die. */ if (*path == '/') { PyErr_SetString(PyExc_ValueError, "invalid path has a leading '/'"); return true; } return false; } static svn_error_t *py_commit_callback(const svn_commit_info_t *commit_info, void *baton, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret; PyGILState_STATE state; if (fn == Py_None) return NULL; state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "lzz", commit_info->revision, commit_info->date, commit_info->author); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *pyify_lock(const svn_lock_t *lock) { if (lock == NULL) { Py_RETURN_NONE; } return Py_BuildValue("(ssszbLL)", lock->path, lock->token, lock->owner, lock->comment, lock->is_dav_comment, lock->creation_date, lock->expiration_date); } static svn_error_t *py_lock_func (void *baton, const char *path, int do_lock, const svn_lock_t *lock, svn_error_t *ra_err, apr_pool_t *pool) { PyObject *py_ra_err, *ret, *py_lock; PyGILState_STATE state = PyGILState_Ensure(); if (ra_err != NULL) { py_ra_err = PyErr_NewSubversionException(ra_err); } else { py_ra_err = Py_None; Py_INCREF(py_ra_err); } py_lock = pyify_lock(lock); ret = PyObject_CallFunction((PyObject *)baton, "zbOO", path, do_lock?true:false, py_lock, py_ra_err); Py_DECREF(py_lock); Py_DECREF(py_ra_err); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } /** Connection to a remote Subversion repository. */ typedef struct { PyObject_VAR_HEAD svn_ra_session_t *ra; apr_pool_t *pool; const char *url; PyObject *progress_func; AuthObject *auth; bool busy; PyObject *client_string_func; PyObject *open_tmp_file_func; const char *root; const char *corrected_url; } RemoteAccessObject; typedef struct { PyObject_VAR_HEAD const REPORTER_T *reporter; void *report_baton; apr_pool_t *pool; RemoteAccessObject *ra; } ReporterObject; static PyObject *reporter_set_path(PyObject *self, PyObject *args) { char *path; svn_revnum_t revision; bool start_empty; char *lock_token = NULL; svn_depth_t depth = svn_depth_infinity; ReporterObject *reporter = (ReporterObject *)self; if (!PyArg_ParseTuple(args, "slb|zi:set_path", &path, &revision, &start_empty, &lock_token, &depth)) return NULL; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } RUN_SVN(reporter->reporter->set_path(reporter->report_baton, path, revision, depth, start_empty, lock_token, reporter->pool)); Py_RETURN_NONE; } static PyObject *reporter_delete_path(PyObject *self, PyObject *args) { ReporterObject *reporter = (ReporterObject *)self; char *path; if (!PyArg_ParseTuple(args, "s:delete_path", &path)) return NULL; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } RUN_SVN(reporter->reporter->delete_path(reporter->report_baton, path, reporter->pool)); Py_RETURN_NONE; } static PyObject *reporter_link_path(PyObject *self, PyObject *args) { char *path, *url; svn_revnum_t revision; bool start_empty; char *lock_token = NULL; ReporterObject *reporter = (ReporterObject *)self; svn_depth_t depth = svn_depth_infinity; if (!PyArg_ParseTuple(args, "sslb|zi:link_path", &path, &url, &revision, &start_empty, &lock_token, &depth)) return NULL; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } RUN_SVN(reporter->reporter->link_path(reporter->report_baton, path, url, revision, depth, start_empty, lock_token, reporter->pool)); Py_RETURN_NONE; } static PyObject *reporter_finish(PyObject *self) { ReporterObject *reporter = (ReporterObject *)self; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } reporter->ra->busy = false; RUN_SVN(reporter->reporter->finish_report( reporter->report_baton, reporter->pool)); apr_pool_destroy(reporter->pool); Py_XDECREF(reporter->ra); reporter->ra = NULL; Py_RETURN_NONE; } static PyObject *reporter_abort(PyObject *self) { ReporterObject *reporter = (ReporterObject *)self; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } reporter->ra->busy = false; RUN_SVN(reporter->reporter->abort_report(reporter->report_baton, reporter->pool)); apr_pool_destroy(reporter->pool); Py_XDECREF(reporter->ra); reporter->ra = NULL; Py_RETURN_NONE; } static PyMethodDef reporter_methods[] = { { "abort", (PyCFunction)reporter_abort, METH_NOARGS, "S.abort()\n" "Abort this report." }, { "finish", (PyCFunction)reporter_finish, METH_NOARGS, "S.finish()\n" "Finish this report." }, { "link_path", (PyCFunction)reporter_link_path, METH_VARARGS, "S.link_path(path, url, revision, start_empty, lock_token=None)\n" }, { "set_path", (PyCFunction)reporter_set_path, METH_VARARGS, "S.set_path(path, revision, start_empty, lock_token=None)\n" }, { "delete_path", (PyCFunction)reporter_delete_path, METH_VARARGS, "S.delete_path(path)\n" }, { NULL, } }; static void reporter_dealloc(PyObject *self) { ReporterObject *reporter = (ReporterObject *)self; if (reporter->ra != NULL) { /* FIXME: Warn */ apr_pool_destroy(reporter->pool); Py_DECREF(reporter->ra); } PyObject_Del(self); } static PyTypeObject Reporter_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.Reporter", /* const char *tp_name; For printing, in format "." */ sizeof(ReporterObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ reporter_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ reporter_methods, /* struct PyMethodDef *tp_methods; */ }; /** * Get libsvn_ra version information. * * :return: tuple with major, minor, patch version number and tag. */ static PyObject *version(PyObject *self) { const svn_version_t *ver = svn_ra_version(); return Py_BuildValue("(iiis)", ver->major, ver->minor, ver->patch, ver->tag); } SVN_VERSION_DEFINE(svn_api_version); /** * Get compile-time libsvn_ra version information. * * :return: tuple with major, minor, patch version number and tag. */ 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); } static svn_error_t *py_file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_boolean_t result_of_merge, svn_txdelta_window_handler_t *delta_handler, void **delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret, *py_rev_props; PyGILState_STATE state = PyGILState_Ensure(); py_rev_props = prop_hash_to_dict(rev_props); CB_CHECK_PYRETVAL(py_rev_props); ret = PyObject_CallFunction(fn, "slOi", path, rev, py_rev_props, result_of_merge); Py_DECREF(py_rev_props); CB_CHECK_PYRETVAL(ret); if (delta_baton != NULL && delta_handler != NULL) { *delta_baton = (void *)ret; *delta_handler = py_txdelta_window_handler; } else { Py_DECREF(ret); } PyGILState_Release(state); return NULL; } static void ra_done_handler(void *_ra) { RemoteAccessObject *ra = (RemoteAccessObject *)_ra; ra->busy = false; Py_DECREF(ra); } #define RUN_RA_WITH_POOL(pool, ra, cmd) { \ svn_error_t *err; \ PyThreadState *_save; \ _save = PyEval_SaveThread(); \ err = (cmd); \ PyEval_RestoreThread(_save); \ if (err != NULL) { \ handle_svn_error(err); \ svn_error_clear(err); \ apr_pool_destroy(pool); \ ra->busy = false; \ return NULL; \ } \ ra->busy = false; \ } static bool ra_check_busy(RemoteAccessObject *raobj) { if (raobj->busy) { PyErr_SetString(busy_exc, "Remote access object already in use"); return true; } raobj->busy = true; return false; } static svn_error_t *py_get_client_string(void *baton, const char **name, apr_pool_t *pool) { RemoteAccessObject *self = (RemoteAccessObject *)baton; PyObject *ret; PyGILState_STATE state; if (self->client_string_func == Py_None) { *name = NULL; return NULL; } state = PyGILState_Ensure(); ret = PyObject_CallFunction(self->client_string_func, ""); CB_CHECK_PYRETVAL(ret); *name = py_object_to_svn_string(ret, pool); Py_DECREF(ret); PyGILState_Release(state); return NULL; } /* Based on svn_swig_py_make_file() from Subversion */ static svn_error_t *py_open_tmp_file(apr_file_t **fp, void *callback, apr_pool_t *pool) { RemoteAccessObject *self = (RemoteAccessObject *)callback; PyObject *ret; apr_status_t status; PyGILState_STATE state; if (self->open_tmp_file_func == Py_None) { const char *path; SVN_ERR (svn_io_temp_dir (&path, pool)); path = svn_dirent_join (path, "subvertpy", pool); SVN_ERR (svn_io_open_unique_file3(fp, NULL, path, svn_io_file_del_on_pool_cleanup, pool, pool)); return NULL; } state = PyGILState_Ensure(); ret = PyObject_CallFunction(self->open_tmp_file_func, ""); CB_CHECK_PYRETVAL(ret); if (PyUnicode_Check(ret)) { PyObject *orig_ret = ret; ret = PyUnicode_AsUTF8String(ret); Py_DECREF(orig_ret); } if (PyBytes_Check(ret)) { char* fname = PyBytes_AsString(ret); status = apr_file_open(fp, fname, APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, pool); if (status) { PyErr_SetAprStatus(status); goto fail_file; } Py_DECREF(ret); } else if (PyObject_AsFileDescriptor(ret) != -1) { *fp = apr_file_from_object(ret, pool); Py_DECREF(ret); if (!*fp) { goto fail; } } else { PyErr_SetString(PyExc_TypeError, "Unknown type for file variable"); Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } PyGILState_Release(state); return NULL; fail_file: Py_DECREF(ret); fail: PyGILState_Release(state); return py_svn_error(); } static void py_progress_func(apr_off_t progress, apr_off_t total, void *baton, apr_pool_t *pool) { PyGILState_STATE state = PyGILState_Ensure(); RemoteAccessObject *ra = (RemoteAccessObject *)baton; PyObject *fn = (PyObject *)ra->progress_func, *ret; if (fn != Py_None) { ret = PyObject_CallFunction(fn, "LL", progress, total); Py_XDECREF(ret); } PyGILState_Release(state); } static PyObject *ra_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "url", "progress_cb", "auth", "config", "client_string_func", "open_tmp_file_func", "uuid", NULL }; char *uuid = NULL; PyObject *py_url; PyObject *progress_cb = Py_None; AuthObject *auth = (AuthObject *)Py_None; PyObject *config = Py_None; PyObject *client_string_func = Py_None, *open_tmp_file_func = Py_None; RemoteAccessObject *ret; apr_hash_t *config_hash; svn_ra_callbacks2_t *callbacks2; svn_auth_baton_t *auth_baton; svn_error_t *err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOOOOz", kwnames, &py_url, &progress_cb, (PyObject **)&auth, &config, &client_string_func, &open_tmp_file_func, &uuid)) return NULL; ret = PyObject_New(RemoteAccessObject, &RemoteAccess_Type); if (ret == NULL) return NULL; ret->client_string_func = client_string_func; ret->open_tmp_file_func = open_tmp_file_func; Py_INCREF(client_string_func); Py_INCREF(progress_cb); ret->progress_func = progress_cb; ret->auth = NULL; ret->corrected_url = NULL; ret->root = NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { Py_DECREF(ret); return NULL; } ret->url = py_object_to_svn_uri(py_url, ret->pool); if (ret->url == NULL) { Py_DECREF(ret); return NULL; } if ((PyObject *)auth == Py_None) { ret->auth = NULL; svn_auth_open(&auth_baton, apr_array_make(ret->pool, 0, sizeof(svn_auth_provider_object_t *)), ret->pool); } else if (PyObject_TypeCheck(auth, &Auth_Type)) { Py_INCREF(auth); ret->auth = auth; auth_baton = ret->auth->auth_baton; } else { PyErr_SetString(PyExc_TypeError, "auth argument is not an Auth object"); Py_DECREF(ret); return NULL; } err = svn_ra_create_callbacks(&callbacks2, ret->pool); if (err != NULL) { handle_svn_error(err); svn_error_clear(err); Py_DECREF(ret); return NULL; } callbacks2->progress_baton = (void *)ret; callbacks2->progress_func = py_progress_func; callbacks2->auth_baton = auth_baton; callbacks2->open_tmp_file = py_open_tmp_file; callbacks2->cancel_func = py_cancel_check; callbacks2->get_client_string = py_get_client_string; config_hash = config_hash_from_object(config, ret->pool); if (config_hash == NULL) { Py_DECREF(ret); return NULL; } Py_BEGIN_ALLOW_THREADS err = svn_ra_open4(&ret->ra, &ret->corrected_url, ret->url, uuid, callbacks2, ret, config_hash, ret->pool); Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); Py_DECREF(ret); return NULL; } ret->busy = false; return (PyObject *)ret; } /** * Obtain the globally unique identifier for this repository. */ static PyObject *ra_get_uuid(PyObject *self) { const char *uuid; RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *ret; apr_pool_t *temp_pool; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid2(ra->ra, &uuid, temp_pool)); ret = PyUnicode_FromString(uuid); apr_pool_destroy(temp_pool); return ret; } /** Switch to a different url. */ static PyObject *ra_reparent(PyObject *self, PyObject *args) { PyObject *py_url; apr_pool_t *temp_pool; RemoteAccessObject *ra = (RemoteAccessObject *)self; if (!PyArg_ParseTuple(args, "O:reparent", &py_url)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; ra->url = py_object_to_svn_uri(py_url, ra->pool); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_reparent(ra->ra, ra->url, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } /** * Obtain the number of the latest committed revision in the * connected repository. */ static PyObject *ra_get_latest_revnum(PyObject *self) { RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t latest_revnum; apr_pool_t *temp_pool; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_latest_revnum(ra->ra, &latest_revnum, temp_pool)); apr_pool_destroy(temp_pool); return py_from_svn_revnum(latest_revnum); } static bool ra_get_log_prepare(RemoteAccessObject *ra, PyObject *paths, bool include_merged_revisions, PyObject *revprops, apr_pool_t **pool, apr_array_header_t **apr_paths, apr_array_header_t **apr_revprops) { if (ra_check_busy(ra)) goto fail_busy; *pool = Pool(NULL); if (*pool == NULL) goto fail_pool; if (paths == Py_None) { /* The subversion libraries don't behave as expected, * so tweak our own parameters a bit. */ *apr_paths = apr_array_make(*pool, 1, sizeof(char *)); APR_ARRAY_PUSH(*apr_paths, char *) = apr_pstrdup(*pool, ""); } else if (!relpath_list_to_apr_array(*pool, paths, apr_paths)) { goto fail_prep; } if (!string_list_to_apr_array(*pool, revprops, apr_revprops)) { goto fail_prep; } return true; fail_prep: apr_pool_destroy(*pool); fail_pool: ra->busy = false; fail_busy: return false; } static PyObject *ra_get_log(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "callback", "paths", "start", "end", "limit", "discover_changed_paths", "strict_node_history", "include_merged_revisions", "revprops", NULL }; PyObject *callback, *paths; svn_revnum_t start = 0, end = 0; int limit=0; bool discover_changed_paths=false, strict_node_history=true,include_merged_revisions=false; RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *revprops = Py_None; apr_pool_t *temp_pool; apr_array_header_t *apr_paths; apr_array_header_t *apr_revprops; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOll|ibbbO:get_log", kwnames, &callback, &paths, &start, &end, &limit, &discover_changed_paths, &strict_node_history, &include_merged_revisions, &revprops)) return NULL; if (!ra_get_log_prepare(ra, paths, include_merged_revisions, revprops, &temp_pool, &apr_paths, &apr_revprops)) { return NULL; } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_log2(ra->ra, apr_paths, start, end, limit, discover_changed_paths, strict_node_history, include_merged_revisions, apr_revprops, py_svn_log_entry_receiver, callback, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } /** * Obtain the URL of the root of this repository. */ static PyObject *ra_get_repos_root(PyObject *self) { RemoteAccessObject *ra = (RemoteAccessObject *)self; const char *root; apr_pool_t *temp_pool; if (ra->root == NULL) { if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_repos_root2(ra->ra, &root, temp_pool)); ra->root = svn_uri_canonicalize(root, ra->pool); apr_pool_destroy(temp_pool); } return PyUnicode_FromString(ra->root); } /** * Obtain the URL of this repository. */ 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; RemoteAccessObject *ra = (RemoteAccessObject *)self; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_session_url(ra->ra, &url, temp_pool)); r = PyUnicode_FromString(url); apr_pool_destroy(temp_pool); return r; } static PyObject *ra_do_update(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "revision_to_update_to", "update_target", "recurse", "update_editor", "send_copyfrom_args", "ignore_ancestry", NULL }; svn_revnum_t revision_to_update_to; char *update_target; bool recurse; bool ignore_ancestry = true; PyObject *update_editor; const REPORTER_T *reporter; void *report_baton; svn_error_t *err; apr_pool_t *temp_pool, *result_pool; ReporterObject *ret; RemoteAccessObject *ra = (RemoteAccessObject *)self; bool send_copyfrom_args = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lsbO|bb:do_update", kwnames, &revision_to_update_to, &update_target, &recurse, &update_editor, &send_copyfrom_args, &ignore_ancestry)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) { ra->busy = false; return NULL; } result_pool = Pool(NULL); if (result_pool == NULL) { apr_pool_destroy(temp_pool); ra->busy = false; return NULL; } Py_INCREF(update_editor); Py_BEGIN_ALLOW_THREADS err = svn_ra_do_update3(ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse?svn_depth_infinity:svn_depth_files, send_copyfrom_args, ignore_ancestry, &py_editor, update_editor, result_pool, temp_pool); Py_END_ALLOW_THREADS apr_pool_destroy(temp_pool); if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(result_pool); ra->busy = false; return NULL; } ret = PyObject_New(ReporterObject, &Reporter_Type); if (ret == NULL) { apr_pool_destroy(result_pool); ra->busy = false; return NULL; } ret->reporter = reporter; ret->report_baton = report_baton; ret->pool = result_pool; Py_INCREF(ra); ret->ra = ra; return (PyObject *)ret; } static PyObject *ra_do_switch(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "revision_to_update_to", "update_target", "recurse", "switch_url", "update_editor", "send_copyfrom_args", "ignore_ancestry", NULL }; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t revision_to_update_to; char *update_target; bool recurse; bool send_copyfrom_args = false; bool ignore_ancestry = true; 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_ParseTupleAndKeywords(args, kwargs, "lsbOO|bb:do_switch", kwnames, &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; temp_pool = Pool(NULL); if (temp_pool == NULL) { ra->busy = false; 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); ra->busy = false; return NULL; } Py_INCREF(update_editor); Py_BEGIN_ALLOW_THREADS err = svn_ra_do_switch3( ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse?svn_depth_infinity:svn_depth_files, switch_url, send_copyfrom_args, ignore_ancestry, &py_editor, update_editor, result_pool, temp_pool); Py_END_ALLOW_THREADS apr_pool_destroy(temp_pool); if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(result_pool); ra->busy = false; return NULL; } ret = PyObject_New(ReporterObject, &Reporter_Type); if (ret == NULL) { apr_pool_destroy(result_pool); ra->busy = false; return NULL; } ret->reporter = reporter; ret->report_baton = report_baton; ret->pool = result_pool; Py_INCREF(ra); ret->ra = ra; return (PyObject *)ret; } static PyObject *ra_do_diff(PyObject *self, PyObject *args, PyObject *kwargs) { svn_revnum_t revision_to_update_to; char *diff_target, *versus_url; PyObject *diff_editor; const REPORTER_T *reporter; void *report_baton; svn_error_t *err; apr_pool_t *temp_pool; bool ignore_ancestry = false, text_deltas = false, recurse=true; ReporterObject *ret; RemoteAccessObject *ra = (RemoteAccessObject *)self; char *kwnames[] = { "revision_to_update_to", "diff_target", "versus_url", "diff_editor", "recurse", "ignore_ancestry", "text_deltas", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lssO|bbb:do_diff", kwnames, &revision_to_update_to, &diff_target, &versus_url, &diff_editor, &recurse, &ignore_ancestry, &text_deltas)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; Py_INCREF(diff_editor); Py_BEGIN_ALLOW_THREADS err = svn_ra_do_diff3(ra->ra, &reporter, &report_baton, revision_to_update_to, diff_target, recurse?svn_depth_infinity:svn_depth_files, ignore_ancestry, text_deltas, versus_url, &py_editor, diff_editor, temp_pool); Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(temp_pool); ra->busy = false; return NULL; } ret = PyObject_New(ReporterObject, &Reporter_Type); if (ret == NULL) return NULL; ret->reporter = reporter; ret->report_baton = report_baton; ret->pool = temp_pool; Py_INCREF(ra); ret->ra = ra; return (PyObject *)ret; } static PyObject *ra_replay(PyObject *self, PyObject *args, PyObject *kwargs) { RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_pool_t *temp_pool; svn_revnum_t revision, low_water_mark; PyObject *update_editor; bool send_deltas = true; char *kwnames[] = { "revision", "low_water_mark", "update_editor", "send_deltas", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "llO|b:replay", kwnames, &revision, &low_water_mark, &update_editor, &send_deltas)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; /* Only INCREF here, py_editor takes care of the DECREF */ Py_INCREF(update_editor); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_replay(ra->ra, revision, low_water_mark, send_deltas, &py_editor, update_editor, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static svn_error_t *py_revstart_cb(svn_revnum_t revision, void *replay_baton, const svn_delta_editor_t **editor, void **edit_baton, apr_hash_t *rev_props, apr_pool_t *pool) { PyObject *cbs, *py_start_fn, *py_revprops, *ret; PyGILState_STATE state = PyGILState_Ensure(); cbs = (PyObject *)replay_baton; py_start_fn = PyTuple_GetItem(cbs, 0); py_revprops = prop_hash_to_dict(rev_props); ret = PyObject_CallFunction(py_start_fn, "lO", revision, py_revprops); Py_DECREF(py_revprops); CB_CHECK_PYRETVAL(ret); *editor = &py_editor; *edit_baton = ret; PyGILState_Release(state); return NULL; } static svn_error_t *py_revfinish_cb(svn_revnum_t revision, void *replay_baton, const svn_delta_editor_t *editor, void *edit_baton, apr_hash_t *rev_props, apr_pool_t *pool) { PyObject *cbs, *py_finish_fn, *py_revprops, *ret; PyGILState_STATE state = PyGILState_Ensure(); cbs = (PyObject *)replay_baton; py_finish_fn = PyTuple_GetItem(cbs, 1); py_revprops = prop_hash_to_dict(rev_props); ret = PyObject_CallFunction(py_finish_fn, "lOO", revision, py_revprops, edit_baton); Py_DECREF(py_revprops); CB_CHECK_PYRETVAL(ret); Py_DECREF((PyObject *)edit_baton); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *ra_replay_range(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "start_revision", "end_revision", "low_water_mark", "cbs", "send_deltas", NULL }; RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_pool_t *temp_pool; svn_revnum_t start_revision, end_revision, low_water_mark; PyObject *cbs; bool send_deltas = true; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lllO|b:replay_range", kwnames, &start_revision, &end_revision, &low_water_mark, &cbs, &send_deltas)) return NULL; if (!PyTuple_Check(cbs)) { PyErr_SetString(PyExc_TypeError, "Expected tuple with callbacks"); return NULL; } if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; Py_INCREF(cbs); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_replay_range(ra->ra, start_revision, end_revision, low_water_mark, send_deltas, py_revstart_cb, py_revfinish_cb, cbs, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *ra_rev_proplist(PyObject *self, PyObject *args) { apr_pool_t *temp_pool; apr_hash_t *props; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t rev; PyObject *py_props; if (!PyArg_ParseTuple(args, "l:rev_proplist", &rev)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_rev_proplist(ra->ra, rev, &props, temp_pool)); py_props = prop_hash_to_dict(props); apr_pool_destroy(temp_pool); return py_props; } static PyObject *get_commit_editor(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "revprops", "callback", "lock_tokens", "keep_locks", NULL }; PyObject *revprops, *commit_callback = Py_None, *lock_tokens = Py_None; bool keep_locks = false; apr_pool_t *pool; const svn_delta_editor_t *editor; void *edit_baton; RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_hash_t *hash_lock_tokens; apr_hash_t *hash_revprops; svn_error_t *err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OOb:get_commit_editor", kwnames, &revprops, &commit_callback, &lock_tokens, &keep_locks)) return NULL; pool = Pool(NULL); if (pool == NULL) goto fail_pool; if (lock_tokens == Py_None) { 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)) { key = py_object_to_svn_string(k, pool); if (key == NULL) { goto fail_prep; } val = apr_pmemdup(pool, PyBytes_AsString(v), PyBytes_Size(v)); apr_hash_set(hash_lock_tokens, key, strlen(key), val); } } if (ra_check_busy(ra)) goto fail_prep; Py_INCREF(commit_callback); hash_revprops = prop_dict_to_hash(pool, revprops); if (hash_revprops == NULL) { goto fail_prep; } Py_BEGIN_ALLOW_THREADS err = svn_ra_get_commit_editor3(ra->ra, &editor, &edit_baton, hash_revprops, py_commit_callback, commit_callback, hash_lock_tokens, keep_locks, pool); Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); goto fail_prep; } Py_INCREF(ra); return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, ra_done_handler, ra, commit_callback); fail_prep: Py_DECREF(commit_callback); ra->busy = false; apr_pool_destroy(pool); fail_pool: return NULL; } static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args, PyObject *kwargs) { svn_revnum_t rev; char *name; RemoteAccessObject *ra = (RemoteAccessObject *)self; char *value, *oldvalue = NULL; Py_ssize_t vallen, oldvallen = -2; apr_pool_t *temp_pool; svn_string_t *val_string; const svn_string_t *old_val_string; const svn_string_t *const *old_val_string_p; char *kwnames[] = { "revnum", "name", "value", "old_value", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lss#|z#:change_rev_prop", kwnames, &rev, &name, &value, &vallen, &oldvalue, &oldvallen)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; val_string = svn_string_ncreate(value, vallen, temp_pool); if (oldvallen != -2) { if (oldvalue == NULL) { old_val_string = NULL; } else { old_val_string = svn_string_ncreate(oldvalue, oldvallen, temp_pool); } old_val_string_p = &old_val_string; } else { old_val_string_p = NULL; } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_change_rev_prop2(ra->ra, rev, name, old_val_string_p, val_string, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *ra_get_dir(PyObject *self, PyObject *args, PyObject *kwargs) { apr_pool_t *temp_pool; apr_hash_t *dirents; apr_hash_t *props; svn_revnum_t fetch_rev; RemoteAccessObject *ra = (RemoteAccessObject *)self; const char *path; PyObject *py_path; svn_revnum_t revision = -1; unsigned int dirent_fields = 0; PyObject *py_dirents = NULL, *py_props; char *kwnames[] = { "path", "revision", "fields", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|lI:get_dir", kwnames, &py_path, &revision, &dirent_fields)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (revision != SVN_INVALID_REVNUM) fetch_rev = revision; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) return NULL; /* Yuck. Subversion doesn't like leading slashes.. */ while (*path == '/') path++; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_dir2(ra->ra, &dirents, &fetch_rev, &props, path, revision, dirent_fields, temp_pool)); if (dirents == NULL) { py_dirents = Py_None; Py_INCREF(py_dirents); } else { py_dirents = dirent_hash_to_dict(dirents, dirent_fields, temp_pool); if (py_dirents == NULL) { goto fail; } } py_props = prop_hash_to_dict(props); if (py_props == NULL) { goto fail; } apr_pool_destroy(temp_pool); return Py_BuildValue("(NlN)", py_dirents, fetch_rev, py_props); fail: Py_XDECREF(py_dirents); apr_pool_destroy(temp_pool); return NULL; } static PyObject *ra_get_file(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; svn_revnum_t revision = -1; RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_hash_t *props; svn_revnum_t fetch_rev; PyObject *py_stream, *py_props; apr_pool_t *temp_pool; svn_stream_t *stream; if (!PyArg_ParseTuple(args, "OO|l:get_file", &py_path, &py_stream, &revision)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (revision != SVN_INVALID_REVNUM) fetch_rev = revision; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) { apr_pool_destroy(temp_pool); return NULL; } /* Yuck. Subversion doesn't like leading slashes.. */ while (*path == '/') path++; stream = new_py_stream(temp_pool, py_stream); if (stream == NULL) { apr_pool_destroy(temp_pool); return NULL; } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_file(ra->ra, path, revision, stream, &fetch_rev, &props, temp_pool)); py_props = prop_hash_to_dict(props); if (py_props == NULL) { apr_pool_destroy(temp_pool); return NULL; } apr_pool_destroy(temp_pool); return Py_BuildValue("(lN)", fetch_rev, py_props); } static PyObject *ra_get_lock(PyObject *self, PyObject *args) { const char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_lock_t *lock = NULL; PyObject *py_path; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "O:get_lock", &py_path)) return NULL; if (ra_check_busy(ra)) return NULL; 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); if (lock == NULL) { Py_RETURN_NONE; } else { return wrap_lock(lock); } } static PyObject *ra_check_path(PyObject *self, PyObject *args) { const char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t revision; svn_node_kind_t kind; PyObject *py_path; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "Ol:check_path", &py_path, &revision)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) return NULL; if (ra_check_svn_path(path)) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_check_path(ra->ra, path, revision, &kind, temp_pool)); apr_pool_destroy(temp_pool); #if PY_MAJOR_VERSION < 3 return PyInt_FromLong(kind); #else return PyLong_FromLong(kind); #endif } static PyObject *ra_stat(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *ret; svn_revnum_t revision; svn_dirent_t *dirent; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "Ol:stat", &py_path, &revision)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) return NULL; if (ra_check_svn_path(path)) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_stat(ra->ra, path, revision, &dirent, temp_pool)); ret = py_dirent(dirent, SVN_DIRENT_ALL); apr_pool_destroy(temp_pool); return ret; } static PyObject *ra_has_capability(PyObject *self, PyObject *args) { char *capability; apr_pool_t *temp_pool; RemoteAccessObject *ra = (RemoteAccessObject *)self; int has = 0; if (!PyArg_ParseTuple(args, "s:has_capability", &capability)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_has_capability(ra->ra, &has, capability, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(has); } static PyObject *ra_unlock(PyObject *self, PyObject *args) { RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *path_tokens, *lock_func, *k, *v; bool break_lock; Py_ssize_t idx = 0; apr_pool_t *temp_pool; apr_hash_t *hash_path_tokens; if (!PyArg_ParseTuple(args, "ObO:unlock", &path_tokens, &break_lock, &lock_func)) goto fail_busy; if (ra_check_busy(ra)) goto fail_busy; temp_pool = Pool(NULL); if (temp_pool == NULL) goto fail_pool; hash_path_tokens = apr_hash_make(temp_pool); while (PyDict_Next(path_tokens, &idx, &k, &v)) { if (!PyBytes_Check(k)) { PyErr_SetString(PyExc_TypeError, "token not bytes"); goto fail_dict; } if (PyUnicode_Check(v)) { v = PyUnicode_AsUTF8String(v); } else { Py_INCREF(v); } if (!PyBytes_Check(v)) { PyErr_SetString(PyExc_TypeError, "path not bytestring or unicode string"); goto fail_dict; } apr_hash_set(hash_path_tokens, PyBytes_AsString(k), PyBytes_Size(k), (char *)PyBytes_AsString(v)); } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_unlock(ra->ra, hash_path_tokens, break_lock, py_lock_func, lock_func, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; fail_dict: apr_pool_destroy(temp_pool); fail_pool: ra->busy = false; fail_busy: return NULL; } static PyObject *ra_lock(PyObject *self, PyObject *args) { RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *path_revs; char *comment; int steal_lock; PyObject *lock_func, *k, *v; apr_pool_t *temp_pool; apr_hash_t *hash_path_revs; svn_revnum_t *rev; Py_ssize_t idx = 0; if (!PyArg_ParseTuple(args, "OsbO:lock", &path_revs, &comment, &steal_lock, &lock_func)) goto fail_busy; if (ra_check_busy(ra)) goto fail_busy; temp_pool = Pool(NULL); if (temp_pool == NULL) goto fail_pool; if (path_revs == Py_None) { hash_path_revs = NULL; } else { hash_path_revs = apr_hash_make(temp_pool); } while (PyDict_Next(path_revs, &idx, &k, &v)) { rev = (svn_revnum_t *)apr_palloc(temp_pool, sizeof(svn_revnum_t)); *rev = py_to_svn_revnum(v); if (*rev == -1 && PyErr_Occurred()) { goto fail_prep; } if (!PyBytes_Check(k)) { PyErr_SetString(PyExc_TypeError, "token not bytes"); goto fail_prep; } apr_hash_set(hash_path_revs, PyBytes_AsString(k), PyBytes_Size(k), rev); } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_lock(ra->ra, hash_path_revs, comment, steal_lock, py_lock_func, lock_func, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; fail_prep: apr_pool_destroy(temp_pool); fail_pool: ra->busy = false; fail_busy: return NULL; } static PyObject *ra_get_locks(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; apr_pool_t *temp_pool; apr_hash_t *hash_locks; apr_hash_index_t *idx; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_depth_t depth = svn_depth_infinity; char *key; apr_ssize_t klen; svn_lock_t *lock; PyObject *ret; if (!PyArg_ParseTuple(args, "O|i:get_locks", &py_path, &depth)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) return NULL; if (ra_check_svn_path(path)) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_locks2(ra->ra, &hash_locks, path, depth, temp_pool)); ret = PyDict_New(); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } for (idx = apr_hash_first(temp_pool, hash_locks); idx != NULL; idx = apr_hash_next(idx)) { PyObject *pyval; apr_hash_this(idx, (const void **)&key, &klen, (void **)&lock); pyval = pyify_lock(lock); if (pyval == NULL) { Py_DECREF(ret); apr_pool_destroy(temp_pool); return NULL; } if (PyDict_SetItemString(ret, key, pyval) != 0) { apr_pool_destroy(temp_pool); Py_DECREF(pyval); Py_DECREF(ret); return NULL; } Py_DECREF(pyval); } apr_pool_destroy(temp_pool); return ret; } static PyObject *ra_get_locations(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t peg_revision; PyObject *location_revisions; apr_pool_t *temp_pool; apr_hash_t *hash_locations; apr_hash_index_t *idx; svn_revnum_t *key; PyObject *ret; apr_ssize_t klen; char *val; if (!PyArg_ParseTuple(args, "OlO:get_locations", &py_path, &peg_revision, &location_revisions)) goto fail_busy; if (ra_check_busy(ra)) goto fail_busy; temp_pool = Pool(NULL); if (temp_pool == NULL) goto fail_pool; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) goto fail_dict; if (ra_check_svn_path(path)) goto fail_dict; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_locations(ra->ra, &hash_locations, path, peg_revision, revnum_list_to_apr_array(temp_pool, location_revisions), temp_pool)); ret = PyDict_New(); if (ret == NULL) { goto fail_dict; } for (idx = apr_hash_first(temp_pool, hash_locations); idx != NULL; idx = apr_hash_next(idx)) { PyObject *py_key, *py_val; apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); py_key = py_from_svn_revnum(*key); if (py_key == NULL) { goto fail_conv; } py_val = PyUnicode_FromString(val); if (py_val == NULL) { goto fail_conv; } if (PyDict_SetItem(ret, py_key, py_val) != 0) { goto fail_conv; } } apr_pool_destroy(temp_pool); return ret; fail_conv: Py_DECREF(ret); fail_dict: apr_pool_destroy(temp_pool); fail_pool: ra->busy = false; fail_busy: return NULL; } static PyObject *range_to_tuple(svn_merge_range_t *range) { return Py_BuildValue("(llb)", range->start, range->end, range->inheritable?true:false); } static PyObject *merge_rangelist_to_list(apr_array_header_t *rangelist) { PyObject *ret; int i; ret = PyList_New(rangelist->nelts); if (ret == NULL) return NULL; for (i = 0; i < rangelist->nelts; i++) { PyObject *pyval; pyval = range_to_tuple(APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *)); if (pyval == NULL) { Py_DECREF(ret); return NULL; } if (PyList_SetItem(ret, i, pyval) != 0) { Py_DECREF(ret); Py_DECREF(pyval); return NULL; } } return ret; } static PyObject *mergeinfo_to_dict(svn_mergeinfo_t mergeinfo, apr_pool_t *temp_pool) { PyObject *ret; char *key; apr_ssize_t klen; apr_hash_index_t *idx; apr_array_header_t *range; ret = PyDict_New(); if (ret == NULL) { return NULL; } for (idx = apr_hash_first(temp_pool, mergeinfo); idx != NULL; idx = apr_hash_next(idx)) { PyObject *pyval; apr_hash_this(idx, (const void **)&key, &klen, (void **)&range); pyval = merge_rangelist_to_list(range); if (pyval == NULL) { Py_DECREF(ret); return NULL; } if (PyDict_SetItemString(ret, key, pyval) != 0) { Py_DECREF(ret); Py_DECREF(pyval); return NULL; } Py_DECREF(pyval); } return ret; } static PyObject *ra_mergeinfo(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "paths", "revision", "inherit", "include_descendants", NULL }; RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_array_header_t *apr_paths; apr_pool_t *temp_pool; svn_mergeinfo_catalog_t catalog; apr_ssize_t klen; apr_hash_index_t *idx; svn_mergeinfo_t val; char *key; PyObject *ret; svn_revnum_t revision = -1; PyObject *paths; svn_mergeinfo_inheritance_t inherit = svn_mergeinfo_explicit; bool include_descendants; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|lib:mergeinfo", kwnames, &paths, &revision, &inherit, &include_descendants)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!relpath_list_to_apr_array(temp_pool, paths, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_mergeinfo(ra->ra, &catalog, apr_paths, revision, inherit, include_descendants, temp_pool)); ret = PyDict_New(); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } if (catalog != NULL) { for (idx = apr_hash_first(temp_pool, catalog); idx != NULL; idx = apr_hash_next(idx)) { PyObject *pyval; apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); pyval = mergeinfo_to_dict(val, temp_pool); if (pyval == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(ret); return NULL; } if (PyDict_SetItemString(ret, key, pyval) != 0) { apr_pool_destroy(temp_pool); Py_DECREF(pyval); Py_DECREF(ret); return NULL; } Py_DECREF(pyval); } } apr_pool_destroy(temp_pool); return ret; } static svn_error_t *py_location_segment_receiver(svn_location_segment_t *segment, void *baton, apr_pool_t *pool) { PyObject *fn = baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "llz", segment->range_start, segment->range_end, segment->path); CB_CHECK_PYRETVAL(ret); Py_XDECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *ra_get_location_segments(PyObject *self, PyObject *args) { RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t peg_revision, start_revision, end_revision; const char *path; PyObject *py_path; PyObject *py_rcvr; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "OlllO:get_location_segments", &py_path, &peg_revision, &start_revision, &end_revision, &py_rcvr)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; path = py_object_to_svn_relpath(py_path, temp_pool); if (path == NULL) return NULL; if (ra_check_svn_path(path)) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_location_segments(ra->ra, path, peg_revision, start_revision, end_revision, py_location_segment_receiver, py_rcvr, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *ra_get_file_revs(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "path", "start", "end", "file_rev_handler", "include_merged_revisions", NULL }; char *path; svn_revnum_t start, end; PyObject *file_rev_handler; apr_pool_t *temp_pool; RemoteAccessObject *ra = (RemoteAccessObject *)self; bool include_merged_revisions = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sllO|b:get_file_revs", kwnames, &path, &start, &end, &file_rev_handler, &include_merged_revisions)) return NULL; if (ra_check_svn_path(path)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_file_revs2(ra->ra, path, start, end, include_merged_revisions, py_file_rev_handler, (void *)file_rev_handler, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static void ra_dealloc(PyObject *self) { RemoteAccessObject *ra = (RemoteAccessObject *)self; Py_XDECREF(ra->client_string_func); Py_XDECREF(ra->progress_func); Py_XDECREF(ra->auth); apr_pool_destroy(ra->pool); PyObject_Del(self); } static PyObject *ra_repr(PyObject *self) { RemoteAccessObject *ra = (RemoteAccessObject *)self; return PyRepr_FromFormat("RemoteAccess(\"%s\")", ra->url); } static int ra_set_progress_func(PyObject *self, PyObject *value, void *closure) { RemoteAccessObject *ra = (RemoteAccessObject *)self; Py_XDECREF(ra->progress_func); ra->progress_func = value; Py_INCREF(ra->progress_func); return 0; } 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", (PyCFunction)ra_get_file_revs, METH_VARARGS|METH_KEYWORDS, "S.get_file_revs(path, start_rev, end_revs, handler, include_merged_revisions=False)" }, { "get_locations", ra_get_locations, METH_VARARGS, "S.get_locations(path, peg_revision, location_revisions)" }, { "get_locks", ra_get_locks, METH_VARARGS, "S.get_locks(path, depth=DEPTH_INFINITY)" }, { "lock", ra_lock, METH_VARARGS, "S.lock(path_revs, comment, steal_lock, lock_func)\n" }, { "unlock", ra_unlock, METH_VARARGS, "S.unlock(path_tokens, break_lock, lock_func)\n" }, { "mergeinfo", (PyCFunction)ra_mergeinfo, METH_VARARGS|METH_KEYWORDS, "S.mergeinfo(paths, revision=-1, inherit=MERGEINFO_EXPLICIT, include_descendants=False)\n" }, { "get_location_segments", ra_get_location_segments, METH_VARARGS, "S.get_location_segments(path, peg_revision, start_revision, " "end_revision, rcvr)\n" "The receiver is called as rcvr(range_start, range_end, path)\n" }, { "has_capability", ra_has_capability, METH_VARARGS, "S.has_capability(name) -> bool\n" "Check whether the specified capability is supported by the client and server" }, { "check_path", ra_check_path, METH_VARARGS, "S.check_path(path, revnum) -> node_kind\n" "Check the type of a path (one of NODE_DIR, NODE_FILE, NODE_UNKNOWN)" }, { "stat", ra_stat, METH_VARARGS, "S.stat(path, revnum) -> dirent\n" }, { "get_lock", ra_get_lock, METH_VARARGS, "S.get_lock(path) -> lock\n" }, { "get_dir", (PyCFunction)ra_get_dir, METH_VARARGS|METH_KEYWORDS, "S.get_dir(path, revision, dirent_fields=-1) -> (dirents, fetched_rev, properties)\n" "Get the contents of a directory. "}, { "get_file", ra_get_file, METH_VARARGS, "S.get_file(path, stream, revnum=-1) -> (fetched_rev, properties)\n" "Fetch a file. The contents will be written to stream." }, { "change_rev_prop", (PyCFunction)ra_change_rev_prop, METH_VARARGS|METH_KEYWORDS, "S.change_rev_prop(revnum, name, value, old_value=None)\n" "Change a revision property" }, { "get_commit_editor", (PyCFunction)get_commit_editor, METH_VARARGS|METH_KEYWORDS, "S.get_commit_editor(revprops, commit_callback, lock_tokens, keep_locks) -> editor\n" }, { "rev_proplist", ra_rev_proplist, METH_VARARGS, "S.rev_proplist(revnum) -> properties\n" "Return a dictionary with the properties set on the specified revision" }, { "replay", (PyCFunction)ra_replay, METH_VARARGS|METH_KEYWORDS, "S.replay(revision, low_water_mark, update_editor, send_deltas=True)\n" "Replay a revision, reporting changes to update_editor." }, { "replay_range", (PyCFunction)ra_replay_range, METH_VARARGS|METH_KEYWORDS, "S.replay_range(start_rev, end_rev, low_water_mark, cbs, send_deltas=True)\n" "Replay a range of revisions, reporting them to an update editor.\n" "cbs is a two-tuple with two callbacks:\n" "- start_rev_cb(revision, revprops) -> editor\n" "- finish_rev_cb(revision, revprops, editor)\n" }, { "do_switch", (PyCFunction)ra_do_switch, METH_VARARGS|METH_KEYWORDS, "S.do_switch(revision_to_update_to, update_target, recurse, switch_url, update_editor, send_copyfrom_args=False, ignore_ancestry=True)\n" }, { "do_update", (PyCFunction)ra_do_update, METH_VARARGS|METH_KEYWORDS, "S.do_update(revision_to_update_to, update_target, recurse, update_editor, send_copyfrom_args=False, ignore_ancestry=True)\n" }, { "do_diff", (PyCFunction)ra_do_diff, METH_VARARGS|METH_KEYWORDS, "S.do_diff(revision_to_update_to, diff_target, versus_url, diff_editor, recurse=True, ignore_ancestry=False, text_deltas=False) -> Reporter object\n" }, { "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_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, " "include_merged_revisions=False, revprops=None)\n" "The callback is passed three or four arguments:\n" "callback(changed_paths, revision, revprops[, has_children])\n" "The changed_paths argument may be None, or a dictionary mapping each\n" "path to a tuple:\n" "(action, from_path, from_rev)\n" }, { "iter_log", (PyCFunction)ra_iter_log, METH_VARARGS|METH_KEYWORDS, "S.iter_log(paths, start, end, limit=0, " "discover_changed_paths=False, strict_node_history=True, " "include_merged_revisions=False, revprops=None)\n" "Yields tuples of three or four elements:\n" "(changed_paths, revision, revprops[, has_children])\n" "The changed_paths element may be None, or a dictionary mapping each\n" "path to a tuple:\n" "(action, from_path, from_rev, node_kind)\n" "This method collects the log entries in another thread. Before calling\n" "any further methods, make sure the thread has completed by running the\n" "iterator to exhaustion (i.e. until StopIteration is raised, the \"for\"\n" "loop finishes, etc).\n" }, { "get_latest_revnum", (PyCFunction)ra_get_latest_revnum, METH_NOARGS, "S.get_latest_revnum() -> int\n" "Return the last revision committed in the repository." }, { "reparent", ra_reparent, METH_VARARGS, "S.reparent(url)\n" "Reparent to a new URL" }, { "get_uuid", (PyCFunction)ra_get_uuid, METH_NOARGS, "S.get_uuid() -> uuid\n" "Return the UUID of the repository." }, { NULL, } }; static PyMemberDef ra_members[] = { { "busy", T_BYTE, offsetof(RemoteAccessObject, busy), READONLY, "Whether this connection is in use at the moment" }, { "corrected_url", T_STRING, offsetof(RemoteAccessObject, corrected_url), READONLY, "Corrected URL" }, { NULL, } }; static PyTypeObject RemoteAccess_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.RemoteAccess", /* const char *tp_name; For printing, in format "." */ sizeof(RemoteAccessObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ ra_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ ra_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; */ 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 */ Py_TPFLAGS_BASETYPE, /* 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 */ ra_methods, /* struct PyMethodDef *tp_methods; */ ra_members, /* struct PyMemberDef *tp_members; */ ra_getsetters, /* 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; */ ra_new, /* newfunc tp_new; */ }; typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_auth_provider_object_t *provider; PyObject *callback; } AuthProviderObject; static void auth_provider_dealloc(PyObject *self) { AuthProviderObject *auth_provider = (AuthProviderObject *)self; Py_XDECREF(auth_provider->callback); auth_provider->callback = NULL; apr_pool_destroy(auth_provider->pool); PyObject_Del(self); } static PyTypeObject AuthProvider_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.AuthProvider", /* const char *tp_name; For printing, in format "." */ sizeof(AuthProviderObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ auth_provider_dealloc, /* destructor tp_dealloc; */ }; static PyObject *auth_init(PyTypeObject *type, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "providers", NULL }; apr_array_header_t *c_providers; svn_auth_provider_object_t **el; PyObject *providers; AuthObject *ret; int i; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &providers)) return NULL; ret = PyObject_New(AuthObject, &Auth_Type); if (ret == NULL) return NULL; ret->providers = NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { PyErr_NoMemory(); Py_DECREF(ret); return NULL; } if (!PySequence_Check(providers)) { PyErr_SetString(PyExc_TypeError, "Auth providers should be a sequence"); Py_DECREF(ret); return NULL; } Py_INCREF(providers); ret->providers = providers; c_providers = apr_array_make(ret->pool, PySequence_Size(providers), sizeof(svn_auth_provider_object_t *)); if (c_providers == NULL) { PyErr_NoMemory(); Py_DECREF(ret); return NULL; } for (i = 0; i < PySequence_Size(providers); i++) { AuthProviderObject *provider; el = (svn_auth_provider_object_t **)apr_array_push(c_providers); provider = (AuthProviderObject *)PySequence_GetItem(providers, i); if (!PyObject_TypeCheck(provider, &AuthProvider_Type)) { PyErr_SetString(PyExc_TypeError, "Invalid auth provider"); Py_DECREF(ret); return NULL; } *el = provider->provider; } svn_auth_open(&ret->auth_baton, c_providers, ret->pool); return (PyObject *)ret; } static PyObject *auth_set_parameter(PyObject *self, PyObject *args) { AuthObject *auth = (AuthObject *)self; char *name; PyObject *value; void *vvalue; if (!PyArg_ParseTuple(args, "sO:set_parameter", &name, &value)) return NULL; if (!strcmp(name, SVN_AUTH_PARAM_SSL_SERVER_FAILURES)) { long ret = PyLong_AsLong(value); if (ret == -1 && PyErr_Occurred()) return NULL; vvalue = apr_pcalloc(auth->pool, sizeof(apr_uint32_t)); *((apr_uint32_t *)vvalue) = ret; } else if (!strcmp(name, SVN_AUTH_PARAM_DEFAULT_USERNAME) || !strcmp(name, SVN_AUTH_PARAM_DEFAULT_PASSWORD)) { vvalue = py_object_to_svn_string(value, auth->pool); if (vvalue == NULL) { return NULL; } } else { PyErr_Format(PyExc_TypeError, "Unsupported auth parameter %s", name); return NULL; } svn_auth_set_parameter(auth->auth_baton, name, (char *)vvalue); Py_RETURN_NONE; } static PyObject *auth_get_parameter(PyObject *self, PyObject *args) { char *name; const void *value; AuthObject *auth = (AuthObject *)self; if (!PyArg_ParseTuple(args, "s:get_parameter", &name)) return NULL; value = svn_auth_get_parameter(auth->auth_baton, name); if (!strcmp(name, SVN_AUTH_PARAM_SSL_SERVER_FAILURES)) { return PyLong_FromLong(*((apr_uint32_t *)value)); } else if (!strcmp(name, SVN_AUTH_PARAM_DEFAULT_USERNAME) || !strcmp(name, SVN_AUTH_PARAM_DEFAULT_PASSWORD)) { return PyUnicode_FromString((const char *)value); } else { PyErr_Format(PyExc_TypeError, "Unsupported auth parameter %s", name); return NULL; } } typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; char *cred_kind; svn_auth_iterstate_t *state; void *credentials; } CredentialsIterObject; static PyObject *auth_first_credentials(PyObject *self, PyObject *args) { char *cred_kind; char *realmstring; AuthObject *auth = (AuthObject *)self; void *creds; apr_pool_t *pool; CredentialsIterObject *ret; svn_auth_iterstate_t *state; if (!PyArg_ParseTuple(args, "ss:credentials", &cred_kind, &realmstring)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_auth_first_credentials(&creds, &state, cred_kind, realmstring, auth->auth_baton, pool)); ret = PyObject_New(CredentialsIterObject, &CredentialsIter_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->cred_kind = apr_pstrdup(pool, cred_kind); ret->state = state; ret->credentials = creds; return (PyObject *)ret; } static void credentials_iter_dealloc(PyObject *self) { CredentialsIterObject *credsiter = (CredentialsIterObject *)self; apr_pool_destroy(credsiter->pool); PyObject_Del(self); } static PyObject *credentials_iter_next(CredentialsIterObject *iterator) { PyObject *ret; if (iterator->credentials == NULL) { PyErr_SetString(PyExc_StopIteration, "No more credentials available"); return NULL; } if (!strcmp(iterator->cred_kind, SVN_AUTH_CRED_SIMPLE)) { svn_auth_cred_simple_t *simple = iterator->credentials; ret = Py_BuildValue("(zzb)", simple->username, simple->password, simple->may_save?true:false); } else if (!strcmp(iterator->cred_kind, SVN_AUTH_CRED_USERNAME)) { svn_auth_cred_username_t *uname = iterator->credentials; ret = Py_BuildValue("(zb)", uname->username, uname->may_save?true:false); } else if (!strcmp(iterator->cred_kind, SVN_AUTH_CRED_SSL_CLIENT_CERT)) { svn_auth_cred_ssl_client_cert_t *ccert = iterator->credentials; ret = Py_BuildValue("(zb)", ccert->cert_file, ccert->may_save?true:false); } else if (!strcmp(iterator->cred_kind, SVN_AUTH_CRED_SSL_CLIENT_CERT_PW)) { svn_auth_cred_ssl_client_cert_pw_t *ccert = iterator->credentials; ret = Py_BuildValue("(zb)", ccert->password, ccert->may_save?true:false); } else if (!strcmp(iterator->cred_kind, SVN_AUTH_CRED_SSL_SERVER_TRUST)) { svn_auth_cred_ssl_server_trust_t *ccert = iterator->credentials; ret = Py_BuildValue("(ib)", ccert->accepted_failures, ccert->may_save?true:false); } else { PyErr_Format(PyExc_RuntimeError, "Unknown cred kind %s", iterator->cred_kind); return NULL; } RUN_SVN_WITH_POOL(iterator->pool, svn_auth_next_credentials(&iterator->credentials, iterator->state, iterator->pool)); return ret; } static PyTypeObject CredentialsIter_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.CredentialsIter", /* const char *tp_name; For printing, in format "." */ sizeof(CredentialsIterObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ .tp_dealloc = (destructor)credentials_iter_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION < 3 .tp_flags = Py_TPFLAGS_HAVE_ITER, /* long tp_flags; */ #endif .tp_iternext = (iternextfunc)credentials_iter_next, /* iternextfunc tp_iternext; */ }; static PyMethodDef auth_methods[] = { { "set_parameter", auth_set_parameter, METH_VARARGS, "S.set_parameter(key, value)\n" "Set a parameter" }, { "get_parameter", auth_get_parameter, METH_VARARGS, "S.get_parameter(key) -> value\n" "Get a parameter" }, { "credentials", auth_first_credentials, METH_VARARGS, "Credentials" }, { NULL, } }; static void auth_dealloc(PyObject *self) { AuthObject *auth = (AuthObject *)self; apr_pool_destroy(auth->pool); Py_XDECREF(auth->providers); PyObject_Del(auth); } static PyTypeObject Auth_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.Auth", /* const char *tp_name; For printing, in format "." */ sizeof(AuthObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ auth_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ auth_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; */ auth_init, /* newfunc tp_new; */ }; static svn_error_t *py_username_prompt(svn_auth_cred_username_t **cred, void *baton, const char *realm, int may_save, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret; PyObject *py_username, *py_may_save; char *username; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "si", realm, may_save); CB_CHECK_PYRETVAL(ret); if (ret == Py_None) { Py_DECREF(ret); PyGILState_Release(state); return NULL; } if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with username credentials"); goto fail; } if (PyTuple_Size(ret) != 2) { PyErr_SetString(PyExc_TypeError, "expected tuple with username credentials to be size 2"); goto fail; } py_may_save = PyTuple_GetItem(ret, 1); CB_CHECK_PYRETVAL(py_may_save); if (!PyBool_Check(py_may_save)) { PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); goto fail; } py_username = PyTuple_GetItem(ret, 0); CB_CHECK_PYRETVAL(py_username); username = py_object_to_svn_string(py_username, pool); if (username == NULL) { goto fail; } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->username = username; (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; fail: Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } static PyObject *get_username_prompt_provider(PyObject *self, PyObject *args) { AuthProviderObject *auth; PyObject *prompt_func; int retry_limit; if (!PyArg_ParseTuple(args, "Oi:get_username_prompt_provider", &prompt_func, &retry_limit)) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; Py_INCREF(prompt_func); auth->callback = prompt_func; svn_auth_get_username_prompt_provider(&auth->provider, py_username_prompt, (void *)prompt_func, retry_limit, auth->pool); return (PyObject *)auth; } static svn_error_t *py_simple_prompt(svn_auth_cred_simple_t **cred, void *baton, const char *realm, const char *username, int may_save, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret; PyObject *py_may_save, *py_username, *py_password; char *ret_username, *password; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "ssi", realm, username, may_save); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with simple credentials"); goto fail; } if (PyTuple_Size(ret) != 3) { PyErr_SetString(PyExc_TypeError, "expected tuple of size 3"); goto fail; } py_may_save = PyTuple_GetItem(ret, 2); CB_CHECK_PYRETVAL(py_may_save); if (!PyBool_Check(py_may_save)) { PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); goto fail; } py_username = PyTuple_GetItem(ret, 0); CB_CHECK_PYRETVAL(py_username); ret_username = py_object_to_svn_string(py_username, pool); if (ret_username == NULL) { goto fail; } py_password = PyTuple_GetItem(ret, 1); CB_CHECK_PYRETVAL(py_password); password = py_object_to_svn_string(py_password, pool); if (password == NULL) { goto fail; } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->username = ret_username; (*cred)->password = password; (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; fail: Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } static PyObject *get_simple_prompt_provider(PyObject *self, PyObject *args) { PyObject *prompt_func; int retry_limit; AuthProviderObject *auth; if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit)) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; Py_INCREF(prompt_func); auth->callback = prompt_func; svn_auth_get_simple_prompt_provider (&auth->provider, py_simple_prompt, (void *)prompt_func, retry_limit, auth->pool); return (PyObject *)auth; } static svn_error_t *py_ssl_server_trust_prompt(svn_auth_cred_ssl_server_trust_t **cred, void *baton, const char *realm, apr_uint32_t failures, const svn_auth_ssl_server_cert_info_t *cert_info, svn_boolean_t may_save, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton; PyObject *ret; PyObject *py_cert; PyGILState_STATE state = PyGILState_Ensure(); int accepted_failures; if (cert_info == NULL) { py_cert = Py_None; Py_INCREF(py_cert); } else { py_cert = Py_BuildValue("(sssss)", cert_info->hostname, cert_info->fingerprint, cert_info->valid_from, cert_info->valid_until, cert_info->issuer_dname, cert_info->ascii_cert); } CB_CHECK_PYRETVAL(py_cert); ret = PyObject_CallFunction(fn, "slOi", realm, failures, py_cert, may_save); Py_DECREF(py_cert); CB_CHECK_PYRETVAL(ret); if (ret == Py_None) { Py_DECREF(ret); PyGILState_Release(state); return NULL; } if (!PyArg_ParseTuple(ret, "ii", &accepted_failures, &may_save)) { Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->accepted_failures = accepted_failures; (*cred)->may_save = may_save; Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *get_ssl_server_trust_prompt_provider(PyObject *self, PyObject *args) { AuthProviderObject *auth; PyObject *prompt_func; if (!PyArg_ParseTuple(args, "O", &prompt_func)) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; Py_INCREF(prompt_func); auth->callback = prompt_func; svn_auth_get_ssl_server_trust_prompt_provider (&auth->provider, py_ssl_server_trust_prompt, (void *)prompt_func, auth->pool); return (PyObject *)auth; } static svn_error_t *py_ssl_client_cert_pw_prompt(svn_auth_cred_ssl_client_cert_pw_t **cred, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret, *py_password; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "si", realm, may_save); CB_CHECK_PYRETVAL(ret); if (!PyArg_ParseTuple(ret, "Oi", &py_password, &may_save)) { goto fail; } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->password = py_object_to_svn_string(py_password, pool); if ((*cred)->password == NULL) { goto fail; } (*cred)->may_save = may_save; Py_DECREF(ret); PyGILState_Release(state); return NULL; fail: Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } static svn_error_t *py_ssl_client_cert_prompt(svn_auth_cred_ssl_client_cert_t **cred, void *baton, const char *realm, svn_boolean_t may_save, apr_pool_t *pool) { PyObject *fn = (PyObject *)baton, *ret, *py_may_save, *py_cert_file; PyGILState_STATE state = PyGILState_Ensure(); char *cert_file; ret = PyObject_CallFunction(fn, "si", realm, may_save); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with client cert credentials"); goto fail; } if (PyTuple_Size(ret) != 2) { PyErr_SetString(PyExc_TypeError, "expected tuple of size 2"); goto fail; } py_may_save = PyTuple_GetItem(ret, 1); if (!PyBool_Check(py_may_save)) { PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); goto fail; } py_cert_file = PyTuple_GetItem(ret, 0); cert_file = py_object_to_svn_string(py_cert_file, pool); if (!cert_file) { goto fail; } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->cert_file = cert_file; (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; fail: Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } static PyObject *get_ssl_client_cert_pw_prompt_provider(PyObject *self, PyObject *args) { PyObject *prompt_func; int retry_limit; AuthProviderObject *auth; if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit)) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; Py_INCREF(prompt_func); auth->callback = prompt_func; svn_auth_get_ssl_client_cert_pw_prompt_provider (&auth->provider, py_ssl_client_cert_pw_prompt, (void *)prompt_func, retry_limit, auth->pool); return (PyObject *)auth; } static PyObject *get_ssl_client_cert_prompt_provider(PyObject *self, PyObject *args) { PyObject *prompt_func; int retry_limit; AuthProviderObject *auth; if (!PyArg_ParseTuple(args, "Oi", &prompt_func, &retry_limit)) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; Py_INCREF(prompt_func); auth->callback = prompt_func; svn_auth_get_ssl_client_cert_prompt_provider (&auth->provider, py_ssl_client_cert_prompt, (void *)prompt_func, retry_limit, auth->pool); return (PyObject *)auth; } static PyObject *get_username_provider(PyObject *self) { AuthProviderObject *auth; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->pool = Pool(NULL); auth->callback = NULL; if (auth->pool == NULL) { PyObject_Del(auth); return NULL; } svn_auth_get_username_provider(&auth->provider, auth->pool); return (PyObject *)auth; } static svn_error_t *py_cb_get_simple_provider_prompt(svn_boolean_t *may_save_plaintext, const char *realmstring, void *baton, apr_pool_t *pool) { if (baton == Py_None) { /* just disallow saving plaintext passwords on 1.6 and later */ *may_save_plaintext = FALSE; } else { PyObject *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(baton, "s", realmstring); CB_CHECK_PYRETVAL(ret); if (ret == NULL) { PyGILState_Release(state); return py_svn_error(); } *may_save_plaintext = PyObject_IsTrue(ret)?TRUE:FALSE; Py_DECREF(ret); PyGILState_Release(state); } return NULL; } static PyObject *get_simple_provider(PyObject *self, PyObject *args) { AuthProviderObject *auth; PyObject *callback = Py_None; apr_pool_t *pool; if (!PyArg_ParseTuple(args, "|O:get_simple_provider", &callback)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) { apr_pool_destroy(pool); return NULL; } auth->pool = pool; Py_INCREF(callback); auth->callback = callback; svn_auth_get_simple_provider2(&auth->provider, py_cb_get_simple_provider_prompt, auth->callback, auth->pool); return (PyObject *)auth; } static PyObject *get_ssl_server_trust_file_provider(PyObject *self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_ssl_server_trust_file_provider(&auth->provider, auth->pool); return (PyObject *)auth; } static PyObject *get_ssl_client_cert_file_provider(PyObject *self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_ssl_client_cert_file_provider(&auth->provider, auth->pool); return (PyObject *)auth; } static PyObject *get_ssl_client_cert_pw_file_provider(PyObject *self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_ssl_client_cert_pw_file_provider2(&auth->provider, NULL, NULL, auth->pool); return (PyObject *)auth; } static PyObject *print_modules(PyObject *self) { svn_stringbuf_t *stringbuf; svn_string_t *string; PyObject *ret; apr_pool_t *pool = Pool(NULL); if (pool == NULL) return NULL; stringbuf = svn_stringbuf_create("", pool); if (stringbuf == NULL) { apr_pool_destroy(pool); return NULL; } RUN_SVN_WITH_POOL(pool, svn_ra_print_modules(stringbuf, pool)); string = svn_string_create_from_buf(stringbuf, pool); if (string == NULL) { apr_pool_destroy(pool); return NULL; } ret = PyBytes_FromStringAndSize(string->data, string->len); apr_pool_destroy(pool); return ret; } #if defined(WIN32) || defined(__CYGWIN__) static PyObject *get_windows_simple_provider(PyObject* self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_windows_simple_provider(&auth->provider, auth->pool); return (PyObject *)auth; } static PyObject *get_windows_ssl_server_trust_provider(PyObject *self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_windows_ssl_server_trust_provider(&auth->provider, auth->pool); return (PyObject *)auth; } #endif #if defined(SVN_KEYCHAIN_PROVIDER_AVAILABLE) static PyObject *get_keychain_simple_provider(PyObject* self) { AuthProviderObject *auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (auth == NULL) return NULL; auth->callback = NULL; auth->pool = Pool(NULL); if (auth->pool == NULL) return NULL; svn_auth_get_keychain_simple_provider(&auth->provider, auth->pool); return (PyObject *)auth; } #endif static PyObject *get_platform_specific_client_providers(PyObject *self) { /* svn_auth_get_platform_specific_client_providers() allocates all the * providers in a single pool, so we can't use it :/ */ const char *provider_names[] = { "gnome_keyring", "keychain", "kwallet", "windows", NULL, }; const char *provider_types[] = { "simple", "ssl_client_cert_pw", "ssl_server_trust", NULL, }; PyObject *pylist; int i, j; pylist = PyList_New(0); if (pylist == NULL) { return NULL; } for (i = 0; provider_names[i] != NULL; i++) { for (j = 0; provider_types[j] != NULL; j++) { svn_auth_provider_object_t *c_provider = NULL; apr_pool_t *pool = Pool(NULL); AuthProviderObject *auth; if (pool == NULL) continue; RUN_SVN(svn_auth_get_platform_specific_provider(&c_provider, provider_names[i], provider_types[j], pool)); auth = PyObject_New(AuthProviderObject, &AuthProvider_Type); if (c_provider == NULL || auth == NULL) { apr_pool_destroy(pool); continue; } auth->pool = pool; auth->callback = NULL; auth->provider = c_provider; PyList_Append(pylist, (PyObject *)auth); Py_DECREF(auth); } } return pylist; } static PyMethodDef ra_module_methods[] = { { "version", (PyCFunction)version, METH_NOARGS, "version() -> (major, minor, micro, tag)\n" "Version of libsvn_ra currently used." }, { "api_version", (PyCFunction)api_version, METH_NOARGS, "api_version() -> (major, minor, patch, tag)\n\n" "Version of libsvn_ra Subvertpy was compiled against." }, { "get_ssl_client_cert_pw_file_provider", (PyCFunction)get_ssl_client_cert_pw_file_provider, METH_NOARGS, NULL }, { "get_ssl_client_cert_file_provider", (PyCFunction)get_ssl_client_cert_file_provider, METH_NOARGS, NULL }, { "get_ssl_server_trust_file_provider", (PyCFunction)get_ssl_server_trust_file_provider, METH_NOARGS, NULL }, { "get_simple_provider", (PyCFunction)get_simple_provider, METH_VARARGS, NULL }, #if defined(WIN32) || defined(__CYGWIN__) { "get_windows_simple_provider", (PyCFunction)get_windows_simple_provider, METH_NOARGS, NULL }, { "get_windows_ssl_server_trust_provider", (PyCFunction)get_windows_ssl_server_trust_provider, METH_NOARGS, NULL }, #endif #if defined(SVN_KEYCHAIN_PROVIDER_AVAILABLE) { "get_keychain_simple_provider", (PyCFunction)get_keychain_simple_provider, METH_NOARGS, NULL }, #endif { "get_username_prompt_provider", (PyCFunction)get_username_prompt_provider, METH_VARARGS, NULL }, { "get_simple_prompt_provider", (PyCFunction)get_simple_prompt_provider, METH_VARARGS, NULL }, { "get_ssl_server_trust_prompt_provider", (PyCFunction)get_ssl_server_trust_prompt_provider, METH_VARARGS, NULL }, { "get_ssl_client_cert_prompt_provider", (PyCFunction)get_ssl_client_cert_prompt_provider, METH_VARARGS, NULL }, { "get_ssl_client_cert_pw_prompt_provider", (PyCFunction)get_ssl_client_cert_pw_prompt_provider, METH_VARARGS, NULL }, { "get_username_provider", (PyCFunction)get_username_provider, METH_NOARGS, NULL }, { "get_platform_specific_client_providers", (PyCFunction)get_platform_specific_client_providers, METH_NOARGS, "Get a list of all available platform client providers.", }, { "print_modules", (PyCFunction)print_modules, METH_NOARGS, NULL }, { NULL, } }; static PyObject * moduleinit(void) { static apr_pool_t *pool; PyObject *mod; if (PyType_Ready(&RemoteAccess_Type) < 0) return NULL; if (PyType_Ready(&Editor_Type) < 0) return NULL; if (PyType_Ready(&FileEditor_Type) < 0) return NULL; if (PyType_Ready(&DirectoryEditor_Type) < 0) return NULL; if (PyType_Ready(&Reporter_Type) < 0) return NULL; if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0) return NULL; if (PyType_Ready(&Auth_Type) < 0) return NULL; if (PyType_Ready(&CredentialsIter_Type) < 0) return NULL; if (PyType_Ready(&AuthProvider_Type) < 0) return NULL; if (PyType_Ready(&LogIterator_Type) < 0) return NULL; apr_initialize(); pool = Pool(NULL); if (pool == NULL) return NULL; svn_ra_initialize(pool); PyEval_InitThreads(); #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "_ra", /* m_name */ "Remote Access", /* m_doc */ -1, /* m_size */ ra_module_methods, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear*/ NULL, /* m_free */ }; mod = PyModule_Create(&moduledef); #else mod = Py_InitModule3("_ra", ra_module_methods, "Remote Access"); #endif if (mod == NULL) return NULL; PyModule_AddObject(mod, "RemoteAccess", (PyObject *)&RemoteAccess_Type); Py_INCREF(&RemoteAccess_Type); PyModule_AddObject(mod, "Auth", (PyObject *)&Auth_Type); Py_INCREF(&Auth_Type); PyModule_AddObject(mod, "Editor", (PyObject *)&Editor_Type); Py_INCREF(&Editor_Type); busy_exc = PyErr_NewException("_ra.BusyException", NULL, NULL); PyModule_AddObject(mod, "BusyException", busy_exc); PyModule_AddIntConstant(mod, "DEPTH_UNKNOWN", svn_depth_unknown); PyModule_AddIntConstant(mod, "DEPTH_EXCLUDE", svn_depth_exclude); PyModule_AddIntConstant(mod, "DEPTH_EMPTY", svn_depth_empty); PyModule_AddIntConstant(mod, "DEPTH_FILES", svn_depth_files); PyModule_AddIntConstant(mod, "DEPTH_IMMEDIATES", svn_depth_immediates); PyModule_AddIntConstant(mod, "DEPTH_INFINITY", svn_depth_infinity); PyModule_AddIntConstant(mod, "DIRENT_KIND", SVN_DIRENT_KIND); PyModule_AddIntConstant(mod, "DIRENT_SIZE", SVN_DIRENT_SIZE); PyModule_AddIntConstant(mod, "DIRENT_HAS_PROPS", SVN_DIRENT_HAS_PROPS); PyModule_AddIntConstant(mod, "DIRENT_CREATED_REV", SVN_DIRENT_CREATED_REV); PyModule_AddIntConstant(mod, "DIRENT_TIME", SVN_DIRENT_TIME); PyModule_AddIntConstant(mod, "DIRENT_LAST_AUTHOR", SVN_DIRENT_LAST_AUTHOR); PyModule_AddIntConstant(mod, "DIRENT_ALL", SVN_DIRENT_ALL); PyModule_AddIntConstant(mod, "MERGEINFO_EXPLICIT", svn_mergeinfo_explicit); PyModule_AddIntConstant(mod, "MERGEINFO_INHERITED", svn_mergeinfo_inherited); PyModule_AddIntConstant(mod, "MERGEINFO_NEAREST_ANCESTOR", svn_mergeinfo_nearest_ancestor); #ifdef SVN_VER_REVISION PyModule_AddIntConstant(mod, "SVN_REVISION", SVN_VER_REVISION); #endif return mod; } #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit__ra(void) { return moduleinit(); } #else PyMODINIT_FUNC init_ra(void) { moduleinit(); } #endif subvertpy_0.11.1.orig/subvertpy/_ra_iter_log.c0000644000000000000000000001524413776732153016464 0ustar00/* * Copyright © 2010 Jelmer Vernooij * -*- 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 struct log_entry { PyObject *tuple; struct log_entry *next; }; typedef struct { PyObject_VAR_HEAD svn_revnum_t start, end; svn_boolean_t discover_changed_paths; svn_boolean_t strict_node_history; svn_boolean_t include_merged_revisions; int limit; apr_pool_t *pool; apr_array_header_t *apr_paths; apr_array_header_t *apr_revprops; RemoteAccessObject *ra; svn_boolean_t done; PyObject *exc_type; PyObject *exc_val; int queue_size; struct log_entry *head; struct log_entry *tail; } LogIteratorObject; static void log_iter_dealloc(PyObject *self) { LogIteratorObject *iter = (LogIteratorObject *)self; while (iter->head) { struct log_entry *e = iter->head; Py_DECREF(e->tuple); iter->head = e->next; free(e); } Py_XDECREF(iter->exc_type); Py_XDECREF(iter->exc_val); apr_pool_destroy(iter->pool); Py_DECREF(iter->ra); PyObject_Del(iter); } static PyObject *log_iter_next(LogIteratorObject *iter) { struct log_entry *first; PyObject *ret; Py_INCREF(iter); while (iter->head == NULL) { /* Done, raise exception */ if (iter->exc_type != NULL) { PyErr_SetObject(iter->exc_type, iter->exc_val); Py_DECREF(iter); return NULL; } else { Py_BEGIN_ALLOW_THREADS /* FIXME: Don't waste cycles */ Py_END_ALLOW_THREADS } } first = iter->head; ret = iter->head->tuple; iter->head = first->next; if (first == iter->tail) iter->tail = NULL; free(first); iter->queue_size--; Py_DECREF(iter); return ret; } static PyObject *py_iter_append(LogIteratorObject *iter, PyObject *tuple) { struct log_entry *entry; entry = calloc(sizeof(struct log_entry), 1); if (entry == NULL) { PyErr_NoMemory(); return NULL; } entry->tuple = tuple; if (iter->tail == NULL) { iter->tail = entry; } else { iter->tail->next = entry; iter->tail = entry; } if (iter->head == NULL) iter->head = entry; iter->queue_size++; Py_RETURN_NONE; } PyTypeObject LogIterator_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.LogIterator", /* const char *tp_name; For printing, in format "." */ sizeof(LogIteratorObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ .tp_dealloc = (destructor)log_iter_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION < 3 /* Flags to define presence of optional/expanded features */ .tp_flags = Py_TPFLAGS_HAVE_ITER, /* long tp_flags; */ #endif /* Iterators */ .tp_iter = PyObject_SelfIter, .tp_iternext = (iternextfunc)log_iter_next, }; static svn_error_t *py_iter_log_entry_cb(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) { PyObject *revprops, *py_changed_paths, *ret, *tuple; LogIteratorObject *iter = (LogIteratorObject *)baton; PyGILState_STATE state; state = PyGILState_Ensure(); py_changed_paths = pyify_changed_paths2(log_entry->changed_paths2, pool); if (py_changed_paths == NULL) { PyGILState_Release(state); return py_svn_error(); } revprops = prop_hash_to_dict(log_entry->revprops); if (revprops == NULL) { Py_DECREF(py_changed_paths); PyGILState_Release(state); return py_svn_error(); } tuple = Py_BuildValue("NlNb", py_changed_paths, log_entry->revision, revprops, log_entry->has_children); if (tuple == NULL) { Py_DECREF(revprops); Py_DECREF(py_changed_paths); PyGILState_Release(state); return py_svn_error(); } ret = py_iter_append(iter, tuple); if (ret == NULL) { Py_DECREF(tuple); PyGILState_Release(state); return py_svn_error(); } Py_DECREF(ret); PyGILState_Release(state); return NULL; } static void py_iter_log(void *baton) { LogIteratorObject *iter = (LogIteratorObject *)baton; svn_error_t *error; PyGILState_STATE state; error = svn_ra_get_log2(iter->ra->ra, iter->apr_paths, iter->start, iter->end, iter->limit, iter->discover_changed_paths, iter->strict_node_history, iter->include_merged_revisions, iter->apr_revprops, py_iter_log_entry_cb, iter, iter->pool); state = PyGILState_Ensure(); if (error != NULL) { iter->exc_type = (PyObject *)PyErr_GetSubversionExceptionTypeObject(); iter->exc_val = PyErr_NewSubversionException(error); svn_error_clear(error); } else { iter->exc_type = PyExc_StopIteration; Py_INCREF(iter->exc_type); iter->exc_val = Py_None; Py_INCREF(iter->exc_val); } iter->done = TRUE; iter->ra->busy = false; Py_DECREF(iter); PyGILState_Release(state); } PyObject *ra_iter_log(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "paths", "start", "end", "limit", "discover_changed_paths", "strict_node_history", "include_merged_revisions", "revprops", NULL }; PyObject *paths; svn_revnum_t start = 0, end = 0; int limit=0; bool discover_changed_paths=false, strict_node_history=true, include_merged_revisions=false; RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *revprops = Py_None; LogIteratorObject *ret; apr_pool_t *pool; apr_array_header_t *apr_paths; apr_array_header_t *apr_revprops; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Oll|ibbbO:iter_log", kwnames, &paths, &start, &end, &limit, &discover_changed_paths, &strict_node_history, &include_merged_revisions, &revprops)) return NULL; if (!ra_get_log_prepare(ra, paths, include_merged_revisions, revprops, &pool, &apr_paths, &apr_revprops)) { return NULL; } ret = PyObject_New(LogIteratorObject, &LogIterator_Type); ret->ra = ra; Py_INCREF(ret->ra); ret->start = start; ret->exc_type = NULL; ret->exc_val = NULL; ret->discover_changed_paths = discover_changed_paths; ret->end = end; ret->limit = limit; ret->apr_paths = apr_paths; ret->pool = pool; ret->include_merged_revisions = include_merged_revisions; ret->strict_node_history = strict_node_history; ret->apr_revprops = apr_revprops; ret->done = FALSE; ret->queue_size = 0; ret->head = NULL; ret->tail = NULL; Py_INCREF(ret); PyThread_start_new_thread(py_iter_log, ret); return (PyObject *)ret; } subvertpy_0.11.1.orig/subvertpy/client.c0000644000000000000000000020602215154541453015302 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include "util.h" #include "ra.h" #include "wc.h" #define INFO_SIZE size64 #define WORKING_SIZE working_size64 extern PyTypeObject Client_Type; extern PyTypeObject Config_Type; extern PyTypeObject ConfigItem_Type; extern PyTypeObject Info_Type; extern PyTypeObject WCInfo_Type; typedef struct { PyObject_VAR_HEAD svn_config_t *item; PyObject *parent; } ConfigItemObject; typedef struct { PyObject_VAR_HEAD svn_wc_info_t info; apr_pool_t *pool; } WCInfoObject; typedef struct { PyObject_VAR_HEAD svn_client_info2_t info; WCInfoObject *wc_info; 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_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; if (l == Py_None) { *ret = NULL; return true; } if (PyUnicode_Check(l) || PyBytes_Check(l)) { *ret = apr_array_make(pool, 1, sizeof(char *)); path = convert(l, pool); if (path == NULL) { return false; } APR_ARRAY_PUSH(*ret, const char *) = path; } else if (PyList_Check(l)) { *ret = apr_array_make(pool, (int)PyList_Size(l), sizeof(char *)); for (i = 0; i < PyList_GET_SIZE(l); i++) { PyObject *item = PyList_GET_ITEM(l, i); path = convert(item, pool); if (path == NULL) { return false; } APR_ARRAY_PUSH(*ret, const char *) = path; } } else { PyErr_Format(PyExc_TypeError, "Expected list of strings, got: %s", l->ob_type->tp_name); return false; } return true; } static bool to_opt_revision(PyObject *arg, svn_opt_revision_t *ret) { if (PyLong_Check(arg)) { ret->kind = svn_opt_revision_number; ret->value.number = PyLong_AsLong(arg); if (ret->value.number == -1 && PyErr_Occurred()) { return false; } return true; #if PY_MAJOR_VERSION < 3 } else if (PyInt_Check(arg)) { ret->kind = svn_opt_revision_number; ret->value.number = PyInt_AsLong(arg); if (ret->value.number == -1 && PyErr_Occurred()) { return false; } return true; #endif } else if (arg == Py_None) { ret->kind = svn_opt_revision_unspecified; return true; } else if (PyUnicode_Check(arg) || PyBytes_Check(arg)) { char *text; if (PyUnicode_Check(arg)) { arg = PyUnicode_AsUTF8String(arg); if (arg == NULL) { return false; } } else { Py_INCREF(arg); } text = PyBytes_AsString(arg); if (!strcmp(text, "HEAD")) { ret->kind = svn_opt_revision_head; Py_DECREF(arg); return true; } else if (!strcmp(text, "WORKING")) { ret->kind = svn_opt_revision_working; Py_DECREF(arg); return true; } else if (!strcmp(text, "BASE")) { ret->kind = svn_opt_revision_base; Py_DECREF(arg); return true; } Py_DECREF(arg); } PyErr_SetString(PyExc_ValueError, "Unable to parse revision"); return false; } static PyObject *wrap_py_commit_items(const apr_array_header_t *commit_items) { PyObject *ret; int i; ret = PyList_New(commit_items->nelts); if (ret == NULL) return NULL; assert(commit_items->elt_size == sizeof(svn_client_commit_item2_t *)); for (i = 0; i < commit_items->nelts; i++) { svn_client_commit_item2_t *commit_item = APR_ARRAY_IDX(commit_items, i, svn_client_commit_item2_t *); PyObject *item, *copyfrom; if (commit_item->copyfrom_url != NULL) { copyfrom = Py_BuildValue("(sl)", commit_item->copyfrom_url, commit_item->copyfrom_rev); if (copyfrom == NULL) { Py_DECREF(ret); return NULL; } } else { copyfrom = Py_None; Py_INCREF(copyfrom); } item = Py_BuildValue("(szlNi)", /* commit_item->path */ "foo", commit_item->url, commit_item->revision, copyfrom, commit_item->state_flags); if (item == NULL) { Py_DECREF(ret); return NULL; } if (PyList_SetItem(ret, i, item) != 0) { Py_DECREF(ret); return NULL; } } return ret; } static svn_error_t *proplist_receiver2( void *prop_list, const char *path, apr_hash_t *prop_hash, apr_array_header_t *inherited_props, apr_pool_t *scratch_pool) { PyGILState_STATE state = PyGILState_Ensure(); PyObject *prop_dict; PyObject *value; prop_dict = prop_hash_to_dict(prop_hash); if (prop_dict == NULL) { PyGILState_Release(state); return py_svn_error(); } value = Py_BuildValue("(sO)", path, prop_dict); if (value == NULL) { PyGILState_Release(state); return py_svn_error(); } /* TODO(jelmer): Convert inherited_props */ if (PyList_Append(prop_list, value) != 0) { PyGILState_Release(state); return py_svn_error(); } PyGILState_Release(state); return NULL; } static svn_error_t *list_receiver2(void *dict, const char *path, const svn_dirent_t *dirent, const svn_lock_t *lock, const char *abs_path, const char *external_parent_url, const char *external_target, apr_pool_t *pool) { PyGILState_STATE state = PyGILState_Ensure(); PyObject *value; value = py_dirent(dirent, SVN_DIRENT_ALL); if (value == NULL) { PyGILState_Release(state); return py_svn_error(); } if (external_parent_url != NULL || external_target != NULL) { value = Py_BuildValue("(Nzz)", value, external_parent_url, external_target); } if (PyDict_SetItemString(dict, path, value) != 0) { Py_DECREF(value); PyGILState_Release(state); return py_svn_error(); } Py_DECREF(value); PyGILState_Release(state); return NULL; } static PyObject *py_info(const svn_client_info2_t *info) { InfoObject *ret; ret = PyObject_New(InfoObject, &Info_Type); if (ret == NULL) return NULL; ret->wc_info = PyObject_New(WCInfoObject, &WCInfo_Type); if (ret->wc_info == NULL) return NULL; ret->pool = ret->wc_info->pool = Pool(NULL); if (ret->pool == NULL) return NULL; ret->info = *svn_client_info2_dup(info, ret->pool); if (info->wc_info != NULL) ret->wc_info->info = *svn_wc_info_dup(info->wc_info, ret->pool); return (PyObject *)ret; } static svn_error_t *info_receiver(void *dict, const char *path, const svn_client_info2_t *info, apr_pool_t *pool) { PyGILState_STATE state = PyGILState_Ensure(); PyObject *value; value = py_info(info); if (value == NULL) { PyGILState_Release(state); return py_svn_error(); } if (PyDict_SetItemString(dict, path, value) != 0) { Py_DECREF(value); PyGILState_Release(state); return py_svn_error(); } Py_DECREF(value); PyGILState_Release(state); return NULL; } static svn_error_t *py_log_msg_func2(const char **log_msg, const char **tmp_file, const apr_array_header_t *commit_items, void *baton, apr_pool_t *pool) { PyObject *py_commit_items, *ret, *py_log_msg, *py_tmp_file; PyGILState_STATE state; if (baton == Py_None) return NULL; state = PyGILState_Ensure(); py_commit_items = wrap_py_commit_items(commit_items); CB_CHECK_PYRETVAL(py_commit_items); ret = PyObject_CallFunction(baton, "O", py_commit_items); Py_DECREF(py_commit_items); CB_CHECK_PYRETVAL(ret); if (PyTuple_Check(ret)) { py_log_msg = PyTuple_GetItem(ret, 0); py_tmp_file = PyTuple_GetItem(ret, 1); } else { py_tmp_file = Py_None; py_log_msg = ret; } if (py_log_msg != Py_None) { *log_msg = py_object_to_svn_string(py_log_msg, pool); } if (py_tmp_file != Py_None) { *tmp_file = py_object_to_svn_string(py_tmp_file, pool); } Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *py_commit_info_tuple(const svn_commit_info_t *ci) { if (ci == NULL) Py_RETURN_NONE; if (ci->revision == SVN_INVALID_REVNUM) Py_RETURN_NONE; return Py_BuildValue("(lzz)", ci->revision, ci->date, ci->author); } typedef struct { PyObject_VAR_HEAD svn_client_ctx_t *client; apr_pool_t *pool; PyObject *callbacks; PyObject *py_auth; PyObject *py_config; } ClientObject; static PyObject *client_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { ClientObject *ret; PyObject *config = Py_None, *auth = Py_None, *log_msg_func = Py_None; char *kwnames[] = { "config", "auth", "log_msg_func", NULL }; svn_error_t *err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOO", kwnames, &config, &auth, &log_msg_func)) return NULL; ret = PyObject_New(ClientObject, &Client_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { Py_DECREF(ret); return NULL; } err = svn_client_create_context2(&ret->client, NULL, ret->pool); if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(ret->pool); PyObject_Del(ret); return NULL; } ret->py_auth = NULL; ret->py_config = NULL; ret->client->notify_func2 = NULL; ret->client->notify_baton2 = NULL; ret->client->cancel_func = py_cancel_check; ret->client->cancel_baton = NULL; if (log_msg_func != Py_None) { ret->client->log_msg_func2 = py_log_msg_func2; } else { ret->client->log_msg_func2 = NULL; } Py_INCREF(log_msg_func); ret->client->log_msg_baton2 = (void *)log_msg_func; client_set_config((PyObject *)ret, config, NULL); client_set_auth((PyObject *)ret, auth, NULL); return (PyObject *)ret; } static void client_dealloc(PyObject *self) { ClientObject *client = (ClientObject *)self; Py_XDECREF((PyObject *)client->client->notify_baton2); Py_XDECREF((PyObject *)client->client->log_msg_baton2); Py_XDECREF(client->py_auth); Py_XDECREF(client->py_config); if (client->pool != NULL) apr_pool_destroy(client->pool); PyObject_Del(self); } static PyObject *client_get_log_msg_func(PyObject *self, void *closure) { ClientObject *client = (ClientObject *)self; if (client->client->log_msg_func2 == NULL) Py_RETURN_NONE; return client->client->log_msg_baton2; } static int client_set_log_msg_func(PyObject *self, PyObject *func, void *closure) { ClientObject *client = (ClientObject *)self; if (client->client->log_msg_baton2 != NULL) { Py_DECREF((PyObject *)client->client->log_msg_baton2); } if (func == Py_None) { client->client->log_msg_func2 = NULL; client->client->log_msg_baton2 = Py_None; } else { client->client->log_msg_func2 = py_log_msg_func2; client->client->log_msg_baton2 = (void *)func; } Py_INCREF(func); return 0; } static PyObject *client_get_notify_func(PyObject *self, void *closure) { ClientObject *client = (ClientObject *)self; if (client->client->notify_func2 == NULL) Py_RETURN_NONE; Py_INCREF((PyObject *)client->client->notify_baton2); return client->client->notify_baton2; } static int client_set_notify_func(PyObject *self, PyObject *func, void *closure) { ClientObject *client = (ClientObject *)self; if (client->client->notify_baton2 != NULL) { Py_DECREF((PyObject *)client->client->notify_baton2); } if (func == Py_None) { client->client->notify_func2 = NULL; client->client->notify_baton2 = Py_None; } else { client->client->notify_func2 = py_wc_notify_func; client->client->notify_baton2 = (void *)func; } Py_INCREF(func); return 0; } static int client_set_auth(PyObject *self, PyObject *auth, void *closure) { ClientObject *client = (ClientObject *)self; apr_array_header_t *auth_providers; Py_XDECREF(client->py_auth); if (auth == Py_None) { auth_providers = apr_array_make(client->pool, 0, sizeof(svn_auth_provider_object_t *)); if (auth_providers == NULL) { PyErr_NoMemory(); return 1; } Py_BEGIN_ALLOW_THREADS svn_auth_open(&client->client->auth_baton, auth_providers, client->pool); Py_END_ALLOW_THREADS } else { client->client->auth_baton = ((AuthObject *)auth)->auth_baton; } client->py_auth = auth; Py_INCREF(auth); return 0; } static int client_set_config(PyObject *self, PyObject *config, void *closure) { ClientObject *client = (ClientObject *)self; Py_XDECREF(client->py_config); client->client->config = config_hash_from_object(config, client->pool); if (client->client->config == NULL) { client->py_config = NULL; return -1; } client->py_config = config; Py_INCREF(config); return 0; } static PyObject *client_add(PyObject *self, PyObject *args, PyObject *kwargs) { char *path; ClientObject *client = (ClientObject *)self; bool recursive=true, force=false, no_ignore=false; bool add_parents = false; bool no_autoprops = false; apr_pool_t *temp_pool; char *kwnames[] = { "path", "recursive", "force", "no_ignore", "add_parents", "no_autoprops", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|bbbbb", kwnames, &path, &recursive, &force, &no_ignore, &add_parents, &no_autoprops)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_client_add5(path, recursive?svn_depth_infinity:svn_depth_empty, force, no_ignore, no_autoprops, add_parents, client->client, temp_pool) ); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_checkout(PyObject *self, PyObject *args, PyObject *kwargs) { ClientObject *client = (ClientObject *)self; char *kwnames[] = { "url", "path", "rev", "peg_rev", "recurse", "ignore_externals", "allow_unver_obstructions", NULL }; svn_revnum_t result_rev; svn_opt_revision_t c_peg_rev, c_rev; const char *path; const char *url; PyObject *py_url = NULL, *py_path; apr_pool_t *temp_pool; PyObject *peg_rev=Py_None, *rev=Py_None; bool recurse=true, ignore_externals=false, allow_unver_obstructions=false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OObbb", kwnames, &py_url, &py_path, &rev, &peg_rev, &recurse, &ignore_externals, &allow_unver_obstructions)) { return NULL; } if (!to_opt_revision(peg_rev, &c_peg_rev)) { return NULL; } if (!to_opt_revision(rev, &c_rev)) { return NULL; } temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; } url = py_object_to_svn_uri(py_url, temp_pool); if (url == NULL) { apr_pool_destroy(temp_pool); 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_client_checkout3(&result_rev, url, path, &c_peg_rev, &c_rev, recurse?svn_depth_infinity:svn_depth_files, ignore_externals, allow_unver_obstructions, client->client, temp_pool)); apr_pool_destroy(temp_pool); return PyLong_FromLong(result_rev); } 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; } 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; 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; PyObject *callback = Py_None; char *kwnames[] = { "targets", "recurse", "keep_locks", "revprops", "keep_changelist", "commit_as_operations", "include_file_externals", "include_dir_externals", "callback", NULL }; apr_hash_t *hash_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_list_to_apr_array(temp_pool, targets, py_object_to_svn_path_or_url, &apr_targets)) { apr_pool_destroy(temp_pool); return NULL; } if (revprops != Py_None && !PyDict_Check(revprops)) { apr_pool_destroy(temp_pool); PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties"); return NULL; } if (revprops != Py_None) { hash_revprops = prop_dict_to_hash(temp_pool, revprops); if (hash_revprops == NULL) { apr_pool_destroy(temp_pool); return NULL; } } else { hash_revprops = NULL; } 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)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_export(PyObject *self, PyObject *args, PyObject *kwargs) { ClientObject *client = (ClientObject *)self; 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; const char *from, *to; apr_pool_t *temp_pool; char *native_eol = NULL; PyObject *peg_rev=Py_None, *rev=Py_None; bool recurse=true, ignore_externals=false, overwrite=false, ignore_keywords=false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OObbbzb", kwnames, &py_from, &py_to, &rev, &peg_rev, &recurse, &ignore_externals, &overwrite, &native_eol, &ignore_keywords)) return NULL; if (!to_opt_revision(peg_rev, &c_peg_rev)) return NULL; if (!to_opt_revision(rev, &c_rev)) return NULL; temp_pool = Pool(NULL); 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; } to = py_object_to_svn_dirent(py_to, temp_pool); if (to == NULL) { apr_pool_destroy(temp_pool); return NULL; } 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)); apr_pool_destroy(temp_pool); return PyLong_FromLong(result_rev); } static PyObject *client_cat(PyObject *self, PyObject *args, PyObject *kwargs) { ClientObject *client = (ClientObject *)self; char *kwnames[] = { "path", "output_stream", "revision", "peg_revision", "expand_keywords", 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; } 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; } apr_pool_destroy(temp_pool); return ret; } static PyObject *client_delete(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *paths; bool force=false, keep_local=false; apr_pool_t *temp_pool; PyObject *py_revprops = Py_None; apr_array_header_t *apr_paths; ClientObject *client = (ClientObject *)self; apr_hash_t *hash_revprops; PyObject *callback = Py_None; char *kwnames[] = { "paths", "force", "keep_local", "revprops", "callback", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bbOO", kwnames, &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; } RUN_SVN_WITH_POOL(temp_pool, svn_client_delete4( apr_paths, force, keep_local, hash_revprops, py_commit_callback2, callback, client->client, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_mkdir(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *paths, *revprops = NULL; bool make_parents = false; apr_pool_t *temp_pool; 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_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_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 != NULL && !PyDict_Check(revprops)) { apr_pool_destroy(temp_pool); PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties"); return NULL; } if (revprops != NULL && revprops != Py_None) { hash_revprops = prop_dict_to_hash(temp_pool, revprops); if (hash_revprops == NULL) { apr_pool_destroy(temp_pool); return NULL; } } else { hash_revprops = NULL; } 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)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) { char *src_path, *dst_path; PyObject *src_rev = Py_None; apr_pool_t *temp_pool; svn_opt_revision_t c_src_rev; bool copy_as_child = true, make_parents = false; apr_hash_t *revprops; bool ignore_externals = false; bool metadata_only = false; ClientObject *client = (ClientObject *)self; PyObject *callback = Py_None; bool pin_externals = false; apr_hash_t *pinned_externals = NULL; char *kwnames[] = { "src_path", "dst_path", "src_rev", "copy_as_child", "make_parents", "ignore_externals", "revprops", "metadata_only", "pin_externals", "callback", NULL }; PyObject *py_revprops = Py_None; apr_array_header_t *src_paths; svn_client_copy_source_t src; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|ObbbObbO", kwnames, &src_path, &dst_path, &src_rev, ©_as_child, &make_parents, &ignore_externals, &py_revprops, &metadata_only, &pin_externals, &callback)) return NULL; if (!to_opt_revision(src_rev, &c_src_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (py_revprops != Py_None) { revprops = prop_dict_to_hash(temp_pool, py_revprops); if (revprops == NULL) { apr_pool_destroy(temp_pool); return NULL; } } else { revprops = NULL; } src.path = src_path; src.revision = src.peg_revision = &c_src_rev; src_paths = apr_array_make(temp_pool, 1, sizeof(svn_client_copy_source_t *)); if (src_paths == NULL) { PyErr_NoMemory(); apr_pool_destroy(temp_pool); return NULL; } APR_ARRAY_PUSH(src_paths, svn_client_copy_source_t *) = &src; 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)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_propset(PyObject *self, PyObject *args, PyObject *kwargs) { char *propname; svn_string_t c_propval; Py_ssize_t vallen; int recurse = true; int skip_checks = false; ClientObject *client = (ClientObject *)self; apr_pool_t *temp_pool; char *target; PyObject *ret, *py_revprops = Py_None; svn_revnum_t base_revision_for_url = SVN_INVALID_REVNUM; apr_hash_t *revprops; svn_commit_info_t *commit_info = NULL; char *kwnames[] = { "name", "value", "target", "recurse", "skip_checks", "base_revision_for_url", "revprops", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sz#s|bblO", kwnames, &propname, &c_propval.data, &vallen, &target, &recurse, &skip_checks, &base_revision_for_url, &py_revprops)) return NULL; c_propval.len = vallen; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (py_revprops != Py_None) { revprops = prop_dict_to_hash(temp_pool, py_revprops); if (revprops == NULL) { apr_pool_destroy(temp_pool); return NULL; } } else { revprops = NULL; } /* FIXME: Support changelists */ /* FIXME: Support depth */ RUN_SVN_WITH_POOL(temp_pool, svn_client_propset3(&commit_info, propname, &c_propval, target, recurse?svn_depth_infinity:svn_depth_files, skip_checks, base_revision_for_url, NULL, revprops, client->client, temp_pool)); ret = py_commit_info_tuple(commit_info); apr_pool_destroy(temp_pool); return ret; } static PyObject *client_propget(PyObject *self, PyObject *args, PyObject *kwargs) { svn_opt_revision_t c_peg_rev; svn_opt_revision_t c_rev; apr_hash_t *hash_props; bool recurse = false; char *propname; apr_pool_t *temp_pool; const char *target; PyObject *peg_revision = Py_None; PyObject *revision; ClientObject *client = (ClientObject *)self; PyObject *ret, *py_target; char *kwnames[] = { "name", "target", "peg_revision", "revision", "recurse", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOO|Ob", kwnames, &propname, &py_target, &peg_revision, &revision, &recurse)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_rev)) return NULL; if (!to_opt_revision(revision, &c_rev)) return NULL; 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; } /* FIXME: Support changelists */ /* FIXME: Support actual_revnum */ /* FIXME: Support depth properly */ /* FIXME: Support inherited_props */ RUN_SVN_WITH_POOL(temp_pool, svn_client_propget5(&hash_props, NULL, propname, target, &c_peg_rev, &c_rev, NULL, recurse?svn_depth_infinity:svn_depth_files, NULL, client->client, temp_pool, temp_pool)); ret = prop_hash_to_dict(hash_props); apr_pool_destroy(temp_pool); return ret; } static PyObject *client_proplist(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "target", "peg_revision", "depth", "revision", NULL }; svn_opt_revision_t c_peg_rev; svn_opt_revision_t c_rev; int depth; apr_pool_t *temp_pool; char *target; PyObject *peg_revision = Py_None, *revision = Py_None; ClientObject *client = (ClientObject *)self; PyObject *prop_list; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOi|O", kwnames, &target, &peg_revision, &depth, &revision)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_rev)) return NULL; if (!to_opt_revision(revision, &c_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; prop_list = PyList_New(0); if (prop_list == NULL) { apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_proplist4(target, &c_peg_rev, &c_rev, depth, NULL, false, /* TODO(jelmer): Support get_target_inherited_props */ proplist_receiver2, prop_list, client->client, temp_pool)); apr_pool_destroy(temp_pool); return prop_list; } static PyObject *client_resolve(PyObject *self, PyObject *args) { svn_depth_t depth; svn_wc_conflict_choice_t choice; ClientObject *client = (ClientObject *)self; apr_pool_t *temp_pool; char *path; if (!PyArg_ParseTuple(args, "sii", &path, &depth, &choice)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_client_resolve(path, depth, choice, client->client, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_update(PyObject *self, PyObject *args, PyObject *kwargs) { bool recurse = true; bool ignore_externals = false; apr_pool_t *temp_pool; PyObject *rev = Py_None, *paths; apr_array_header_t *result_revs, *apr_paths; svn_opt_revision_t c_rev; svn_revnum_t ret_rev; PyObject *ret; int i = 0; ClientObject *client = (ClientObject *)self; 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|Obbbbbb", 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; } 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)); 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) { char *kwnames[] = { "path", "peg_revision", "depth", "dirents", "revision", "include_externals", NULL }; svn_opt_revision_t c_peg_rev; svn_opt_revision_t c_rev; int depth; int dirents = SVN_DIRENT_ALL; apr_pool_t *temp_pool; char *path; bool include_externals = false; PyObject *peg_revision = Py_None, *revision = Py_None; ClientObject *client = (ClientObject *)self; PyObject *entry_dict; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOi|iOb", kwnames, &path, &peg_revision, &depth, &dirents, &revision, &include_externals)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_rev)) return NULL; if (!to_opt_revision(revision, &c_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; entry_dict = PyDict_New(); if (entry_dict == NULL) { apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_list3(path, &c_peg_rev, &c_rev, depth, dirents, false, include_externals, list_receiver2, entry_dict, client->client, temp_pool)); apr_pool_destroy(temp_pool); return entry_dict; } static PyObject *client_diff(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "rev1", "rev2", "path1", "path2", "relative_to_dir", "diffopts", "encoding", "ignore_ancestry", "no_diff_deleted", "ignore_content_type", NULL, }; apr_pool_t *temp_pool; ClientObject *client = (ClientObject *)self; svn_opt_revision_t c_rev1, c_rev2; svn_depth_t depth = svn_depth_infinity; char *path1 = NULL, *path2 = NULL, *relative_to_dir = NULL; char *encoding = "utf-8"; PyObject *rev1 = Py_None, *rev2 = Py_None; int ignore_ancestry = true, no_diff_deleted = true, ignore_content_type = false; PyObject *diffopts = Py_None; apr_array_header_t *c_diffopts; PyObject *outfile, *errfile; apr_file_t *c_outfile, *c_errfile; apr_off_t offset; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|zzzOsbbb:diff", kwnames, &rev1, &rev2, &path1, &path2, &relative_to_dir, &diffopts, &encoding, &ignore_ancestry, &no_diff_deleted, &ignore_content_type)) return NULL; if (!to_opt_revision(rev1, &c_rev1) || !to_opt_revision(rev2, &c_rev2)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (diffopts == Py_None) diffopts = PyList_New(0); else Py_INCREF(diffopts); if (diffopts == NULL) { apr_pool_destroy(temp_pool); return NULL; } if (!string_list_to_apr_array(temp_pool, diffopts, &c_diffopts)) { apr_pool_destroy(temp_pool); Py_DECREF(diffopts); return NULL; } Py_DECREF(diffopts); outfile = PyOS_tmpfile(); if (outfile == NULL) { apr_pool_destroy(temp_pool); return NULL; } errfile = PyOS_tmpfile(); if (errfile == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(outfile); return NULL; } c_outfile = apr_file_from_object(outfile, temp_pool); if (c_outfile == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(outfile); Py_DECREF(errfile); return NULL; } c_errfile = apr_file_from_object(errfile, temp_pool); if (c_errfile == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(outfile); Py_DECREF(errfile); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_diff4(c_diffopts, path1, &c_rev1, path2, &c_rev2, relative_to_dir, depth, ignore_ancestry, no_diff_deleted, ignore_content_type, encoding, c_outfile, c_errfile, NULL, client->client, temp_pool)); offset = 0; apr_file_seek(c_outfile, APR_SET, &offset); offset = 0; apr_file_seek(c_errfile, APR_SET, &offset); apr_pool_destroy(temp_pool); return Py_BuildValue("(NN)", outfile, errfile); } static PyObject *client_log(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "callback", "paths", "start_rev", "end_rev", "limit", "peg_revision", "discover_changed_paths", "strict_node_history", "include_merged_revisions", "revprops", NULL, }; apr_pool_t *temp_pool; ClientObject *client = (ClientObject *)self; PyObject *callback, *paths, *start_rev = Py_None, *end_rev = Py_None, *peg_revision = Py_None, *revprops = NULL; int limit = 0; bool discover_changed_paths = false, strict_node_history = false, include_merged_revisions = false; apr_array_header_t *apr_paths, *apr_revprops = NULL; svn_opt_revision_t c_peg_rev, c_start_rev, c_end_rev; svn_opt_revision_range_t revision_range; apr_array_header_t *revision_ranges; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|OOiObbbO", kwnames, &callback, &paths, &start_rev, &end_rev, &limit, &peg_revision, &discover_changed_paths, &strict_node_history, &include_merged_revisions, &revprops)) return NULL; if (!to_opt_revision(start_rev, &c_start_rev)) return NULL; if (!to_opt_revision(end_rev, &c_end_rev)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_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 (revprops) { if (!string_list_to_apr_array(temp_pool, revprops, &apr_revprops)) { apr_pool_destroy(temp_pool); return NULL; } } revision_range.start = c_start_rev; revision_range.end = c_end_rev; revision_ranges = apr_array_make(temp_pool, 1, sizeof(svn_opt_revision_range_t *)); if (revision_ranges == NULL) { apr_pool_destroy(temp_pool); return NULL; } APR_ARRAY_PUSH(revision_ranges, svn_opt_revision_range_t *) = &revision_range; RUN_SVN_WITH_POOL(temp_pool, svn_client_log5(apr_paths, &c_peg_rev, revision_ranges, limit, discover_changed_paths?TRUE:FALSE, strict_node_history?TRUE:FALSE, include_merged_revisions?TRUE:FALSE, apr_revprops, py_svn_log_entry_receiver, (void*)callback, client->client, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_info(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "path", "revision", "peg_revision", "depth", "fetch_excluded", "fetch_actual_only", NULL, }; apr_pool_t *temp_pool; ClientObject *client = (ClientObject *)self; const char *path; int depth = svn_depth_empty; bool fetch_excluded = false, fetch_actual_only = false; PyObject *revision = Py_None, *peg_revision = Py_None; svn_opt_revision_t c_peg_rev, c_rev; PyObject *entry_dict; svn_error_t *err; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|OOibb", kwnames, &path, &revision, &peg_revision, &depth, &fetch_excluded, &fetch_actual_only)) return NULL; if (!to_opt_revision(revision, &c_rev)) return NULL; if (!to_opt_revision(peg_revision, &c_peg_rev)) return NULL; if (c_rev.kind == svn_opt_revision_unspecified) c_rev.kind = svn_opt_revision_head; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; entry_dict = PyDict_New(); if (entry_dict == NULL) { apr_pool_destroy(temp_pool); return NULL; } Py_BEGIN_ALLOW_THREADS; /* 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); Py_END_ALLOW_THREADS; if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(temp_pool); Py_DECREF(entry_dict); return NULL; } apr_pool_destroy(temp_pool); 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)" }, { "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, keep_changelist=False, commit_as_operations=False, include_file_externals=False, include_dir_externals=False, callback=None) -> (revnum, date, author)" }, { "delete", (PyCFunction)client_delete, METH_VARARGS|METH_KEYWORDS, "S.delete(paths, force=False, keep_local=False, revprops=None, callback=None)" }, { "copy", (PyCFunction)client_copy, METH_VARARGS|METH_KEYWORDS, "S.copy(src_path, dest_path, srv_rev=None)" }, { "propset", (PyCFunction)client_propset, METH_VARARGS|METH_KEYWORDS, "S.propset(name, value, target, recurse=True, skip_checks=False)" }, { "propget", (PyCFunction)client_propget, METH_VARARGS|METH_KEYWORDS, "S.propget(name, target, peg_revision, revision=None, recurse=False) -> value" }, { "proplist", (PyCFunction)client_proplist, METH_VARARGS|METH_KEYWORDS, "S.proplist(path, peg_revision, depth, revision=None)" }, { "resolve", client_resolve, METH_VARARGS, "S.resolve(path, depth, choice)" }, { "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, 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|METH_KEYWORDS, "S.lock(targets, comment, steal_lock=False)" }, { "unlock", (PyCFunction)client_unlock, METH_VARARGS|METH_KEYWORDS, "S.unlock(targets, break_lock=False)" }, { NULL, } }; static PyGetSetDef client_getset[] = { { "log_msg_func", client_get_log_msg_func, client_set_log_msg_func, NULL }, { "notify_func", client_get_notify_func, client_set_notify_func, NULL }, { "auth", NULL, client_set_auth, NULL }, { "config", NULL, client_set_config, NULL }, { NULL, } }; 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; } 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; } static PyMethodDef config_methods[] = { { "get_default_ignores", (PyCFunction)get_default_ignores, METH_NOARGS, NULL }, { NULL } }; static void config_dealloc(PyObject *obj) { apr_pool_t *pool = ((ConfigObject *)obj)->pool; if (pool != NULL) apr_pool_destroy(pool); PyObject_Del(obj); } PyTypeObject Config_Type = { PyVarObject_HEAD_INIT(NULL, 0) "client.Config", /* const char *tp_name; For printing, in format "." */ sizeof(ConfigObject), /* tp_basicsize */ 0, /* tp_itemsize; For allocation */ /* Methods to implement standard operations */ (destructor)config_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ config_methods, /* struct PyMethodDef *tp_methods; */ }; static void configitem_dealloc(PyObject *self) { ConfigItemObject *item = (ConfigItemObject *)self; Py_XDECREF(item->parent); PyObject_Del(item); } PyTypeObject ConfigItem_Type = { PyVarObject_HEAD_INIT(NULL, 0) "client.ConfigItem", /* const char *tp_name; For printing, in format "." */ sizeof(ConfigItemObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ (destructor)configitem_dealloc, /* destructor tp_dealloc; */ }; static void info_dealloc(PyObject *self) { apr_pool_t *pool = ((InfoObject *)self)->pool; if (pool != NULL) apr_pool_destroy(pool); PyObject_Del(self); } static PyMemberDef info_members[] = { { "url", T_STRING, offsetof(InfoObject, info.URL), READONLY, "Where the item lives in the repository." }, { "revision", T_LONG, offsetof(InfoObject, info.rev), READONLY, "The revision of the object.", }, { "kind", T_INT, offsetof(InfoObject, info.kind), READONLY, "The node's kind.", }, { "repos_root_url", T_STRING, offsetof(InfoObject, info.repos_root_URL), READONLY, "The root URL of the repository." }, { "repos_uuid", T_STRING, offsetof(InfoObject, info.repos_UUID), READONLY, "The repository's UUID." }, { "last_changed_revision", T_LONG, offsetof(InfoObject, info.last_changed_rev), READONLY, "The last revision in which this object changed.", }, { "last_changed_date", T_LONG, offsetof(InfoObject, info.last_changed_date), READONLY, "The date of the last_changed_revision." }, { "last_changed_author", T_STRING, offsetof(InfoObject, info.last_changed_author), READONLY, "The author of the last_changed_revision." }, { "wc_info", T_OBJECT, offsetof(InfoObject, wc_info), READONLY, "Possible information about the working copy, None if not valid." }, { NULL, } }; static PyObject *info_get_size(PyObject *_self, void *closure) { InfoObject *self = (InfoObject *)_self; if (self->info.size == SVN_WC_ENTRY_WORKING_SIZE_UNKNOWN) Py_RETURN_NONE; return PyLong_FromLongLong(self->info.size); } static PyGetSetDef info_getsetters[] = { { "size", info_get_size, NULL, "The size of the file in the repository.", }, { NULL } }; PyTypeObject Info_Type = { PyVarObject_HEAD_INIT(NULL, 0) "client.Info", /* const char *tp_name; For printing, in format "." */ sizeof(InfoObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ info_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ info_members, /* struct PyMemberDef *tp_members; */ info_getsetters, /* struct PyGetSetDef *tp_getsetters; */ }; static PyMemberDef wc_info_members[] = { { "schedule", T_INT, offsetof(WCInfoObject, info.schedule), READONLY, "" }, { "copyfrom_url", T_STRING, offsetof(WCInfoObject, info.copyfrom_url), READONLY, "" }, { "copyfrom_rev", T_LONG, offsetof(WCInfoObject, info.copyfrom_rev), READONLY, "" }, /* TODO add support for checksum */ /* TODO add support for conflicts */ { "changelist", T_STRING, offsetof(WCInfoObject, info.changelist), READONLY, "" }, { "recorded_size", T_PYSSIZET, offsetof(WCInfoObject, info.recorded_size), READONLY, "" }, { "recorded_time", T_LONG, offsetof(WCInfoObject, info.recorded_time), READONLY, "" }, { "wcroot_abspath", T_STRING, offsetof(WCInfoObject, info.wcroot_abspath), READONLY, "" }, { NULL, } }; static void wcinfo_dealloc(PyObject *self) { PyObject_Del(self); } PyTypeObject WCInfo_Type = { PyVarObject_HEAD_INIT(NULL, 0) "client.Info", /* const char *tp_name; For printing, in format "." */ sizeof(WCInfoObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ wcinfo_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ wc_info_members, /* struct PyMemberDef *tp_members; */ }; PyTypeObject Client_Type = { PyVarObject_HEAD_INIT(NULL, 0) /* PyObject_VAR_HEAD */ "client.Client", /* const char *tp_name; For printing, in format "." */ sizeof(ClientObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ client_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ "Subversion client", /* 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 */ client_methods, /* struct PyMethodDef *tp_methods; */ NULL, /* struct PyMemberDef *tp_members; */ client_getset, /* 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; */ client_new, /* newfunc tp_new; */ }; static PyObject *get_config(PyObject *self, PyObject *args) { char *config_dir = NULL; ConfigObject *data; if (!PyArg_ParseTuple(args, "|z", &config_dir)) return NULL; data = PyObject_New(ConfigObject, &Config_Type); if (data == NULL) return NULL; data->pool = Pool(NULL); if (data->pool == NULL) { PyObject_Del(data); return NULL; } RUN_SVN_WITH_POOL(data->pool, svn_config_get_config(&data->config, config_dir, data->pool)); return (PyObject *)data; } /** * Get runtime libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ 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); } SVN_VERSION_DEFINE(svn_api_version); /** * Get compile-time libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ 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); } 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 } }; static PyObject * moduleinit(void) { PyObject *mod; if (PyType_Ready(&Client_Type) < 0) return NULL; if (PyType_Ready(&Config_Type) < 0) return NULL; if (PyType_Ready(&ConfigItem_Type) < 0) return NULL; if (PyType_Ready(&Info_Type) < 0) return NULL; if (PyType_Ready(&WCInfo_Type) < 0) return NULL; /* 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); #else mod = Py_InitModule3("client", client_mod_methods, "Client methods"); #endif if (mod == NULL) return NULL; 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)); Py_INCREF(&Config_Type); PyModule_AddObject(mod, "Config", (PyObject *)&Config_Type); return mod; } #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_client(void) { return moduleinit(); } #else PyMODINIT_FUNC initclient(void) { moduleinit(); } #endif subvertpy_0.11.1.orig/subvertpy/delta.py0000644000000000000000000001755115154541453015332 0ustar00# Copyright (C) 2005-2006 Jelmer Vernooij # 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 """Subversion delta operations.""" __author__ = "Jelmer Vernooij " __docformat__ = "restructuredText" from hashlib import ( md5, ) TXDELTA_SOURCE = 0 TXDELTA_TARGET = 1 TXDELTA_NEW = 2 TXDELTA_INVALID = 3 MAX_ENCODED_INT_LEN = 10 DELTA_WINDOW_SIZE = 102400 def apply_txdelta_window(sbuf, window): """Apply a txdelta window to a buffer. :param sbuf: Source buffer (as bytestring) :param window: (sview_offset, sview_len, tview_len, src_ops, ops, new_data) :param sview_offset: Offset of the source view :param sview_len: Length of the source view :param tview_len: Target view length :param src_ops: Operations to apply to sview :param ops: Ops to apply :param new_data: Buffer with possible new data :return: Target buffer """ (sview_offset, sview_len, tview_len, src_ops, ops, new_data) = window sview = sbuf[sview_offset : sview_offset + sview_len] tview = txdelta_apply_ops(src_ops, ops, new_data, sview) if len(tview) != tview_len: raise AssertionError("%d != %d" % (len(tview), tview_len)) return tview def apply_txdelta_handler_chunks(source_chunks, target_chunks): """Return a function that can be called repeatedly with txdelta windows. :param sbuf: Source buffer :param target_stream: Target stream """ sbuf = bytes().join(source_chunks) def apply_window(window): if window is None: return # Last call target_chunks.append(apply_txdelta_window(sbuf, window)) return apply_window def apply_txdelta_handler(sbuf, target_stream): """Return a function that can be called repeatedly with txdelta windows. :param sbuf: Source buffer :param target_stream: Target stream """ def apply_window(window): if window is None: return # Last call target_stream.write(apply_txdelta_window(sbuf, window)) return apply_window def txdelta_apply_ops(src_ops, ops, new_data, sview): """Apply txdelta operations to a source view. :param src_ops: Source operations, ignored. :param ops: List of operations (action, offset, length). :param new_data: Buffer to fetch fragments with new data from :param sview: Source data :return: Result data """ tview = bytearray() for action, offset, length in ops: if action == TXDELTA_SOURCE: # Copy from source area. tview.extend(sview[offset : offset + length]) elif action == TXDELTA_TARGET: for i in range(length): tview.append(tview[offset + i]) elif action == TXDELTA_NEW: tview.extend(new_data[offset : offset + length]) else: raise Exception("Invalid delta instruction code") return tview def send_stream(stream, handler, block_size=DELTA_WINDOW_SIZE): """Send txdelta windows that create stream to handler :param stream: file-like object to read the file from :param handler: txdelta window handler function :return: MD5 hash over the stream """ hash = md5() text = stream.read(block_size) if not isinstance(text, bytes): raise TypeError("The stream should read out bytes") while text: hash.update(text) window = (0, 0, len(text), 0, [(TXDELTA_NEW, 0, len(text))], text) handler(window) text = stream.read(block_size) handler(None) return hash.digest() def encode_length(len): """Encode a length variable. :param len: Length to encode :return: String with encoded length """ # Based on encode_int() in subversion/libsvn_delta/svndiff.c assert len >= 0 assert isinstance(len, int), "expected int, got %r" % (len,) # Count number of required bytes v = len >> 7 n = 1 while v > 0: v = v >> 7 n += 1 assert n <= MAX_ENCODED_INT_LEN ret = bytearray() while n > 0: n -= 1 if n > 0: cont = 1 else: cont = 0 ret.append(((len >> (n * 7)) & 0x7F) | (cont << 7)) return ret def decode_length(text): """Decode a length variable. :param text: Bytestring to decode :return: Integer with actual length """ # Decode bytes until we're done. */ ret = 0 next = True while next: ret = (ret << 7) | (text[0] & 0x7F) next = (text[0] >> 7) & 0x1 text = text[1:] return ret, text def pack_svndiff_instruction(diff_params): """Pack a SVN diff instruction :param diff_params: (action, offset, length) :param action: Action :param offset: Offset :param length: Length :return: encoded text """ (action, offset, length) = diff_params if length < 0x3F: text = bytearray(((action << 6) + length,)) else: text = bytearray((action << 6,)) + encode_length(length) if action != TXDELTA_NEW: text += encode_length(offset) return text def unpack_svndiff_instruction(text): """Unpack a SVN diff instruction :param text: Text to parse :return: tuple with operation, remaining text """ action = text[0] >> 6 length = text[0] & 0x3F text = text[1:] assert action in (TXDELTA_NEW, TXDELTA_SOURCE, TXDELTA_TARGET) if length == 0: length, text = decode_length(text) if action != TXDELTA_NEW: offset, text = decode_length(text) else: offset = 0 return (action, offset, length), text SVNDIFF0_HEADER = b"SVN\0" def pack_svndiff0_window(window): """Pack an individual window using svndiff0. :param window: Window to pack :return: Packed diff (as bytestring) """ (sview_offset, sview_len, tview_len, src_ops, ops, new_data) = window ret = ( encode_length(sview_offset) + encode_length(sview_len) + encode_length(tview_len) ) instrdata = bytearray() for op in ops: instrdata += pack_svndiff_instruction(op) ret.extend(encode_length(len(instrdata))) ret.extend(encode_length(len(new_data))) ret.extend(instrdata) ret.extend(new_data) return ret def pack_svndiff0(windows): """Pack a SVN diff file. :param windows: Iterator over diff windows :return: text """ ret = SVNDIFF0_HEADER for window in windows: ret += pack_svndiff0_window(window) return ret def unpack_svndiff0(text): """Unpack a version 0 svndiff text. :param text: Text to unpack. :return: yields tuples with sview_offset, sview_len, tview_len, ops_len, ops, newdata """ assert text.startswith(SVNDIFF0_HEADER) text = text[4:] while text: sview_offset, text = decode_length(text) sview_len, text = decode_length(text) tview_len, text = decode_length(text) instr_len, text = decode_length(text) newdata_len, text = decode_length(text) instrdata = text[:instr_len] text = text[instr_len:] ops = [] while instrdata: op, instrdata = unpack_svndiff_instruction(instrdata) ops.append(op) newdata = text[:newdata_len] text = text[newdata_len:] yield (sview_offset, sview_len, tview_len, len(ops), ops, newdata) subvertpy_0.11.1.orig/subvertpy/editor.c0000644000000000000000000010474615154541453015324 0ustar00/* Copyright © 2008 Jelmer Vernooij * -*- 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 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 */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include "editor.h" #include "util.h" typedef struct EditorObject { PyObject_VAR_HEAD const svn_delta_editor_t *editor; void *baton; apr_pool_t *pool; void (*done_cb) (void *baton); void *done_baton; bool done; PyObject *commit_callback; bool active_child; struct EditorObject *parent; } EditorObject; static PyObject *py_editor_ctx_enter(PyObject *self) { Py_INCREF(self); return self; } PyObject *new_editor_object(struct EditorObject *parent, const svn_delta_editor_t *editor, void *baton, apr_pool_t *pool, PyTypeObject *type, void (*done_cb) (void *), void *done_baton, PyObject *commit_callback) { EditorObject *obj = PyObject_New(EditorObject, type); if (obj == NULL) return NULL; obj->editor = editor; obj->baton = baton; obj->pool = pool; obj->done_cb = done_cb; obj->done = false; obj->done_baton = done_baton; obj->commit_callback = commit_callback; obj->active_child = false; if (parent != NULL) { Py_INCREF(parent); parent->active_child = true; } obj->parent = (EditorObject *)parent; return (PyObject *)obj; } static void py_editor_dealloc(PyObject *self) { EditorObject *editor = (EditorObject *)self; Py_XDECREF(editor->commit_callback); if (editor->pool != NULL) { apr_pool_destroy(editor->pool); editor->pool = NULL; } PyObject_Del(self); } /* svn_filesize_t is always 64 bits */ #if SIZEOF_LONG == 8 #define SVN_FILESIZE_T_PYFMT "k" #elif SIZEOF_LONG_LONG == 8 #define SVN_FILESIZE_T_PYFMT "K" #else #error "Unable to determine PyArg_Parse format for size_t" #endif static PyObject *txdelta_call(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "window", NULL }; svn_txdelta_window_t window; TxDeltaWindowHandlerObject *obj = (TxDeltaWindowHandlerObject *)self; PyObject *py_window, *py_ops, *py_new_data; int i; svn_string_t new_data; svn_error_t *error; svn_txdelta_op_t *ops; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &py_window)) return NULL; if (py_window == Py_None) { RUN_SVN(obj->txdelta_handler(NULL, obj->txdelta_baton)); Py_RETURN_NONE; } if (!PyArg_ParseTuple(py_window, SVN_FILESIZE_T_PYFMT "nniOO", &window.sview_offset, &window.sview_len, &window.tview_len, &window.src_ops, &py_ops, &py_new_data)) return NULL; if (py_new_data == Py_None) { window.new_data = NULL; } else { if (!PyBytes_Check(py_new_data)) { PyErr_SetString(PyExc_TypeError, "delta data should be bytes"); return NULL; } new_data.data = PyBytes_AsString(py_new_data); new_data.len = PyBytes_Size(py_new_data); window.new_data = &new_data; } if (!PyList_Check(py_ops)) { PyErr_SetString(PyExc_TypeError, "ops not a list"); return NULL; } window.num_ops = PyList_Size(py_ops); window.ops = ops = malloc(sizeof(svn_txdelta_op_t) * window.num_ops); for (i = 0; i < window.num_ops; i++) { PyObject *windowitem = PyList_GetItem(py_ops, i); if (!PyArg_ParseTuple(windowitem, "inn", &ops[i].action_code, &ops[i].offset, &ops[i].length)) { free(ops); return NULL; } } Py_BEGIN_ALLOW_THREADS error = obj->txdelta_handler(&window, obj->txdelta_baton); Py_END_ALLOW_THREADS if (error != NULL) { handle_svn_error(error); svn_error_clear(error); free(ops); return NULL; } free(ops); Py_RETURN_NONE; } static void py_txdelta_window_handler_dealloc(PyObject *self) { PyObject_Del(self); } PyTypeObject TxDeltaWindowHandler_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.TxDeltaWindowHandler", /* const char *tp_name; For printing, in format "." */ sizeof(TxDeltaWindowHandlerObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ py_txdelta_window_handler_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ txdelta_call, /* ternaryfunc tp_call; */ }; static PyObject *py_file_editor_apply_textdelta(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; char *c_base_checksum = NULL; svn_txdelta_window_handler_t txdelta_handler; void *txdelta_baton; TxDeltaWindowHandlerObject *py_txdelta; if (!PyArg_ParseTuple(args, "|z", &c_base_checksum)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "file editor already closed"); return NULL; } RUN_SVN(editor->editor->apply_textdelta(editor->baton, c_base_checksum, editor->pool, &txdelta_handler, &txdelta_baton)); py_txdelta = PyObject_New(TxDeltaWindowHandlerObject, &TxDeltaWindowHandler_Type); py_txdelta->txdelta_handler = txdelta_handler; py_txdelta->txdelta_baton = txdelta_baton; return (PyObject *)py_txdelta; } static PyObject *py_file_editor_change_prop(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; char *name; svn_string_t c_value; Py_ssize_t vallen; if (!PyArg_ParseTuple(args, "sz#", &name, &c_value.data, &vallen)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "file editor already closed"); return NULL; } c_value.len = vallen; RUN_SVN(editor->editor->change_file_prop(editor->baton, name, (c_value.data == NULL)?NULL:&c_value, editor->pool)); Py_RETURN_NONE; } static PyObject *py_file_editor_close(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; char *c_checksum = NULL; if (!PyArg_ParseTuple(args, "|z", &c_checksum)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "file editor was already closed"); return NULL; } RUN_SVN(editor->editor->close_file(editor->baton, c_checksum, editor->pool)); editor->parent->active_child = false; Py_DECREF(editor->parent); editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; Py_RETURN_NONE; } static PyObject *py_file_editor_ctx_enter(PyObject *self) { Py_INCREF(self); return self; } static PyObject *py_file_editor_ctx_exit(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "file editor already closed"); return NULL; } RUN_SVN(editor->editor->close_file(editor->baton, NULL, editor->pool)); editor->parent->active_child = false; Py_DECREF(editor->parent); editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; Py_RETURN_FALSE; } static PyMethodDef py_file_editor_methods[] = { { "change_prop", py_file_editor_change_prop, METH_VARARGS, NULL }, { "close", py_file_editor_close, METH_VARARGS, NULL }, { "apply_textdelta", py_file_editor_apply_textdelta, METH_VARARGS, NULL }, { "__enter__", (PyCFunction)py_file_editor_ctx_enter, METH_NOARGS, NULL }, { "__exit__", py_file_editor_ctx_exit, METH_VARARGS, NULL }, { NULL } }; PyTypeObject FileEditor_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.FileEditor", /* const char *tp_name; For printing, in format "." */ sizeof(EditorObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ py_file_editor_methods, /* struct PyMethodDef *tp_methods; */ }; static PyObject *py_dir_editor_delete_entry(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; const char *path; PyObject *py_path; svn_revnum_t revision = -1; if (!PyArg_ParseTuple(args, "O|l", &py_path, &revision)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "a child is already open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { return NULL; } RUN_SVN(editor->editor->delete_entry(path, revision, editor->baton, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_add_directory(PyObject *self, PyObject *args) { PyObject *py_path, *py_copyfrom_path = Py_None; const char *path; 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|Ol", &py_path, &py_copyfrom_path, ©from_rev)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "child is already open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { 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), copyfrom_rev, editor->pool, &child_baton)); subpool = Pool(editor->pool); if (subpool == NULL) return NULL; return new_editor_object(editor, editor->editor, child_baton, subpool, &DirectoryEditor_Type, NULL, NULL, NULL); } static PyObject *py_dir_editor_open_directory(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; EditorObject *editor = (EditorObject *)self; svn_revnum_t base_revision=-1; void *child_baton; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "O|l", &py_path, &base_revision)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "child is already open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { return NULL; } RUN_SVN(editor->editor->open_directory( path, editor->baton, base_revision, editor->pool, &child_baton)); subpool = Pool(NULL); if (subpool == NULL) return NULL; return new_editor_object(editor, editor->editor, child_baton, subpool, &DirectoryEditor_Type, NULL, NULL, NULL); } static PyObject *py_dir_editor_change_prop(PyObject *self, PyObject *args) { char *name; svn_string_t c_value; EditorObject *editor = (EditorObject *)self; Py_ssize_t vallen; if (!PyArg_ParseTuple(args, "sz#", &name, &c_value.data, &vallen)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "child is already open"); return NULL; } c_value.len = vallen; RUN_SVN(editor->editor->change_dir_prop(editor->baton, name, (c_value.data == NULL)?NULL:&c_value, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_close(PyObject *self) { EditorObject *editor = (EditorObject *)self; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor was already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "child is still open"); return NULL; } RUN_SVN(editor->editor->close_directory(editor->baton, editor->pool)); if (editor->parent != NULL) { editor->parent->active_child = false; Py_DECREF(editor->parent); } editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; Py_RETURN_NONE; } static PyObject *py_dir_editor_absent_directory(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; EditorObject *editor = (EditorObject *)self; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "another child is still open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { return NULL; } RUN_SVN(editor->editor->absent_directory( path, editor->baton, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_add_file(PyObject *self, PyObject *args) { const char *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|Ol", &py_path, &py_copy_path, ©_rev)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "another child is still open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { 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, copy_rev, editor->pool, &file_baton)); subpool = Pool(NULL); if (subpool == NULL) return NULL; return new_editor_object(editor, editor->editor, file_baton, subpool, &FileEditor_Type, NULL, NULL, NULL); } static PyObject *py_dir_editor_open_file(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; svn_revnum_t base_revision=-1; void *file_baton; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "O|l", &py_path, &base_revision)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "another child is still open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { return NULL; } RUN_SVN(editor->editor->open_file(path, editor->baton, base_revision, editor->pool, &file_baton)); subpool = Pool(NULL); if (subpool == NULL) return NULL; return new_editor_object(editor, editor->editor, file_baton, subpool, &FileEditor_Type, NULL, NULL, NULL); } static PyObject *py_dir_editor_absent_file(PyObject *self, PyObject *args) { const char *path; PyObject *py_path; EditorObject *editor = (EditorObject *)self; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "another child is still open"); return NULL; } path = py_object_to_svn_relpath(py_path, editor->pool); if (path == NULL) { return NULL; } RUN_SVN(editor->editor->absent_file( path, editor->baton, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_ctx_enter(PyObject *self) { Py_INCREF(self); return self; } static PyObject *py_dir_editor_ctx_exit(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "directory editor already closed"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "a child is still open"); return NULL; } RUN_SVN(editor->editor->close_directory(editor->baton, editor->pool)); if (editor->parent != NULL) { editor->parent->active_child = false; Py_DECREF(editor->parent); } editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; Py_RETURN_FALSE; } static PyMethodDef py_dir_editor_methods[] = { { "absent_file", py_dir_editor_absent_file, METH_VARARGS, "S.absent_file(path)\n\n" "Indicate a file is not present." }, { "absent_directory", py_dir_editor_absent_directory, METH_VARARGS, NULL }, { "delete_entry", py_dir_editor_delete_entry, METH_VARARGS, NULL }, { "add_file", py_dir_editor_add_file, METH_VARARGS, NULL }, { "open_file", py_dir_editor_open_file, METH_VARARGS, NULL }, { "add_directory", py_dir_editor_add_directory, METH_VARARGS, NULL }, { "open_directory", py_dir_editor_open_directory, METH_VARARGS, NULL }, { "close", (PyCFunction)py_dir_editor_close, METH_NOARGS, NULL }, { "change_prop", py_dir_editor_change_prop, METH_VARARGS, NULL }, { "__enter__", (PyCFunction)py_dir_editor_ctx_enter, METH_NOARGS, NULL }, { "__exit__", py_dir_editor_ctx_exit, METH_VARARGS, NULL }, { NULL, } }; PyTypeObject DirectoryEditor_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.DirEditor", /* const char *tp_name; For printing, in format "." */ sizeof(EditorObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ py_dir_editor_methods, /* struct PyMethodDef *tp_methods; */ }; static PyObject *py_editor_set_target_revision(PyObject *self, PyObject *args) { svn_revnum_t target_revision; EditorObject *editor = (EditorObject *)self; if (!PyArg_ParseTuple(args, "l", &target_revision)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "Editor already closed/aborted"); return NULL; } RUN_SVN(editor->editor->set_target_revision(editor->baton, target_revision, editor->pool)); Py_RETURN_NONE; } static PyObject *py_editor_open_root(PyObject *self, PyObject *args) { svn_revnum_t base_revision=-1; void *root_baton; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "|l:open_root", &base_revision)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "Editor already closed/aborted"); return NULL; } RUN_SVN(editor->editor->open_root(editor->baton, base_revision, editor->pool, &root_baton)); subpool = Pool(NULL); if (subpool == NULL) return NULL; return new_editor_object(editor, editor->editor, root_baton, subpool, &DirectoryEditor_Type, NULL, NULL, NULL); } static PyObject *py_editor_close(PyObject *self) { EditorObject *editor = (EditorObject *)self; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "Editor already closed/aborted"); return NULL; } if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "a child is still open"); return NULL; } RUN_SVN(editor->editor->close_edit(editor->baton, editor->pool)); editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; if (editor->done_cb != NULL) editor->done_cb(editor->done_baton); Py_RETURN_NONE; } static PyObject *py_editor_abort(PyObject *self) { EditorObject *editor = (EditorObject *)self; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "Editor already closed/aborted"); return NULL; } /* FIXME: Check for open active childs ? */ RUN_SVN(editor->editor->abort_edit(editor->baton, editor->pool)); editor->done = true; apr_pool_destroy(editor->pool); editor->pool = NULL; if (editor->done_cb != NULL) editor->done_cb(editor->done_baton); Py_RETURN_NONE; } static PyObject *py_editor_ctx_exit(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; PyObject *exc_type, *exc_val, *exc_tb; if (!PyArg_ParseTuple(args, "OOO", &exc_type, &exc_val, &exc_tb)) return NULL; if (editor->done) { PyErr_SetString(PyExc_RuntimeError, "Editor already closed/aborted"); return NULL; } if (exc_type != Py_None) { RUN_SVN(editor->editor->abort_edit(editor->baton, editor->pool)); } else { if (editor->active_child) { PyErr_SetString(PyExc_RuntimeError, "a child is still open"); return NULL; } RUN_SVN(editor->editor->close_edit(editor->baton, editor->pool)); } if (editor->done_cb != NULL) editor->done_cb(editor->done_baton); Py_RETURN_FALSE; } static PyMethodDef py_editor_methods[] = { { "abort", (PyCFunction)py_editor_abort, METH_NOARGS, "S.abort()\n" "Close the editor, aborting the commit." }, { "close", (PyCFunction)py_editor_close, METH_NOARGS, "S.close()\n" "Close the editor, finalizing the commit." }, { "open_root", py_editor_open_root, METH_VARARGS, "S.open_root(base_revision=None) -> DirectoryEditor\n" "Open the root directory." }, { "set_target_revision", py_editor_set_target_revision, METH_VARARGS, "S.set_target_revision(target_revision)\n" "Set the target revision created by the reported revision."}, { "__enter__", (PyCFunction)py_editor_ctx_enter, METH_NOARGS, NULL }, { "__exit__", py_editor_ctx_exit, METH_VARARGS, NULL }, { NULL } }; PyTypeObject Editor_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_ra.Editor", /* const char *tp_name; For printing, in format "." */ sizeof(EditorObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ py_editor_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ py_editor_methods, /* struct PyMethodDef *tp_methods; */ }; static svn_error_t *py_cb_editor_set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool) { PyObject *self = (PyObject *)edit_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "set_target_revision", "l", target_revision); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { PyObject *self = (PyObject *)edit_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); *root_baton = NULL; ret = PyObject_CallMethod(self, "open_root", "l", base_revision); CB_CHECK_PYRETVAL(ret); *root_baton = (void *)ret; PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_delete_entry(const char *path, svn_revnum_t revision, void *parent_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "delete_entry", "sl", path, revision); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_add_directory(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *pool, void **child_baton) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); *child_baton = NULL; if (copyfrom_path == NULL) { ret = PyObject_CallMethod(self, "add_directory", "s", path); } else { ret = PyObject_CallMethod(self, "add_directory", "ssl", path, copyfrom_path, copyfrom_revision); } CB_CHECK_PYRETVAL(ret); *child_baton = (void *)ret; PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_open_directory(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **child_baton) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); *child_baton = NULL; ret = PyObject_CallMethod(self, "open_directory", "sl", path, base_revision); CB_CHECK_PYRETVAL(ret); *child_baton = (void *)ret; PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_change_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { PyObject *self = (PyObject *)dir_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); if (value != NULL) { ret = PyObject_CallMethod(self, "change_prop", "sz#", name, value->data, value->len); } else { ret = PyObject_CallMethod(self, "change_prop", "sO", name, Py_None); } CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_close_directory(void *dir_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)dir_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "close", ""); Py_DECREF(self); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_absent_directory(const char *path, void *parent_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "absent_directory", "s", path); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_add_file(const char *path, void *parent_baton, const char *copy_path, svn_revnum_t copy_revision, apr_pool_t *file_pool, void **file_baton) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); if (copy_path == NULL) { ret = PyObject_CallMethod(self, "add_file", "s", path); } else { ret = PyObject_CallMethod(self, "add_file", "ssl", path, copy_path, copy_revision); } CB_CHECK_PYRETVAL(ret); *file_baton = (void *)ret; PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_open_file(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *file_pool, void **file_baton) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "open_file", "sl", path, base_revision); CB_CHECK_PYRETVAL(ret); *file_baton = (void *)ret; PyGILState_Release(state); return NULL; } svn_error_t *py_txdelta_window_handler(svn_txdelta_window_t *window, void *baton) { int i; PyObject *ops, *ret; PyObject *fn = (PyObject *)baton, *py_new_data, *py_window; PyGILState_STATE state; if (fn == Py_None) { /* User doesn't care about deltas */ return NULL; } state = PyGILState_Ensure(); if (window == NULL) { py_window = Py_None; Py_INCREF(py_window); } else { ops = PyList_New(window->num_ops); if (ops == NULL) { PyGILState_Release(state); return NULL; } for (i = 0; i < window->num_ops; i++) { PyObject *pyval = Py_BuildValue("(iII)", window->ops[i].action_code, window->ops[i].offset, window->ops[i].length); CB_CHECK_PYRETVAL(pyval); if (PyList_SetItem(ops, i, pyval) != 0) { Py_DECREF(ops); Py_DECREF(pyval); PyGILState_Release(state); return NULL; } } if (window->new_data != NULL && window->new_data->data != NULL) { py_new_data = PyBytes_FromStringAndSize(window->new_data->data, window->new_data->len); } else { py_new_data = Py_None; Py_INCREF(py_new_data); } if (py_new_data == NULL) { Py_DECREF(ops); PyGILState_Release(state); return NULL; } py_window = Py_BuildValue("((LIIiNN))", window->sview_offset, window->sview_len, window->tview_len, window->src_ops, ops, py_new_data); CB_CHECK_PYRETVAL(py_window); /* FIXME: free ops and py_new_data */ } ret = PyObject_CallFunction(fn, "O", py_window); Py_DECREF(py_window); if (window == NULL) { /* Signals all delta windows have been received */ Py_DECREF(fn); } CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton) { PyObject *self = (PyObject *)file_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); *handler_baton = NULL; ret = PyObject_CallMethod(self, "apply_textdelta", "z", base_checksum); CB_CHECK_PYRETVAL(ret); *handler_baton = (void *)ret; *handler = py_txdelta_window_handler; PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) { PyObject *self = (PyObject *)file_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); if (text_checksum != NULL) { ret = PyObject_CallMethod(self, "close", ""); } else { ret = PyObject_CallMethod(self, "close", "s", text_checksum); } Py_DECREF(self); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_absent_file(const char *path, void *parent_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)parent_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "absent_file", "s", path); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_close_edit(void *edit_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)edit_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "close", ""); Py_DECREF(self); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_cb_editor_abort_edit(void *edit_baton, apr_pool_t *pool) { PyObject *self = (PyObject *)edit_baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "abort", ""); Py_DECREF(self); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } const svn_delta_editor_t py_editor = { py_cb_editor_set_target_revision, py_cb_editor_open_root, py_cb_editor_delete_entry, py_cb_editor_add_directory, py_cb_editor_open_directory, py_cb_editor_change_prop, py_cb_editor_close_directory, py_cb_editor_absent_directory, py_cb_editor_add_file, py_cb_editor_open_file, py_cb_editor_apply_textdelta, py_cb_editor_change_prop, py_cb_editor_close_file, py_cb_editor_absent_file, py_cb_editor_close_edit, py_cb_editor_abort_edit }; subvertpy_0.11.1.orig/subvertpy/editor.h0000644000000000000000000000374313133767436015332 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #ifndef _BZR_SVN_EDITOR_H_ #define _BZR_SVN_EDITOR_H_ #ifdef __GNUC__ #pragma GCC visibility push(hidden) #endif extern PyTypeObject DirectoryEditor_Type; extern PyTypeObject FileEditor_Type; extern PyTypeObject Editor_Type; extern PyTypeObject TxDeltaWindowHandler_Type; struct EditorObject; PyObject *new_editor_object( struct EditorObject *parent, const svn_delta_editor_t *editor, void *baton, apr_pool_t *pool, PyTypeObject *type, void (*done_cb) (void *baton), void *done_baton, PyObject *commit_callback); #define DirectoryEditor_Check(op) PyObject_TypeCheck(op, &DirectoryEditor_Type) #define FileEditor_Check(op) PyObject_TypeCheck(op, &FileEditor_Type) #define Editor_Check(op) PyObject_TypeCheck(op, &Editor_Type) #define TxDeltaWindowHandler_Check(op) PyObject_TypeCheck(op, &TxDeltaWindowHandler_Type) typedef struct { PyObject_HEAD svn_txdelta_window_handler_t txdelta_handler; void *txdelta_baton; } TxDeltaWindowHandlerObject; svn_error_t *py_txdelta_window_handler(svn_txdelta_window_t *window, void *baton); #ifdef __GNUC__ #pragma GCC visibility pop #endif extern const svn_delta_editor_t py_editor; #endif /* _BZR_SVN_EDITOR_H_ */ subvertpy_0.11.1.orig/subvertpy/marshall.py0000644000000000000000000001110415154541453016030 0ustar00# Copyright (C) 2006-2007 Jelmer Vernooij # # 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 """Marshalling for the svn_ra protocol.""" class literal(object): """A protocol literal.""" def __init__(self, txt): self.txt = txt def __str__(self): return self.txt def __repr__(self): return self.txt def __eq__(self, other): return isinstance(other, literal) and self.txt == other.txt # 1. Syntactic structure # ---------------------- # # The Subversion protocol is specified in terms of the following # syntactic elements, specified using ABNF [RFC 2234]: # # item = word / number / string / list # word = ALPHA *(ALPHA / DIGIT / "-") space # number = 1*DIGIT space # string = 1*DIGIT ":" *OCTET space # ; digits give the byte count of the *OCTET portion # list = "(" space *item ")" space # space = 1*(SP / LF) # class MarshallError(Exception): """A Marshall error.""" class NeedMoreData(MarshallError): """More data needed.""" def marshall(x): """Marshall a Python data item. :param x: Data item :return: encoded byte string """ if isinstance(x, int): return ("%d " % x).encode("ascii") elif isinstance(x, (list, tuple)): return b"( " + bytes().join(map(marshall, x)) + b") " elif isinstance(x, literal): return ("%s " % x).encode("ascii") elif isinstance(x, bytes): return ("%d:" % len(x)).encode("ascii") + x + b" " elif isinstance(x, str): x = x.encode("utf-8") return ("%d:" % len(x)).encode("ascii") + x + b" " elif isinstance(x, bool): if x is True: return b"true " elif x is False: return b"false " raise MarshallError("Unable to marshall type %s" % x) def unmarshall(x): """Unmarshall the next item from a buffer. :param x: Bytes to parse :return: tuple with unpacked item and remaining bytes """ whitespace = frozenset(b"\n ") if len(x) == 0: raise NeedMoreData("Not enough data") if x[0:1] == b"(": # list follows if len(x) <= 1: raise NeedMoreData("Missing whitespace") if x[1:2] != b" ": raise MarshallError("missing whitespace after list start") x = x[2:] ret = [] try: while x[0:1] != b")": (x, n) = unmarshall(x) ret.append(n) except IndexError: raise NeedMoreData("List not terminated") if len(x) <= 1: raise NeedMoreData("Missing whitespace") if x[1] not in whitespace: raise MarshallError("Expected space, got '%c'" % x[1]) return (x[2:], ret) elif x[0:1].isdigit(): num = bytearray() # Check if this is a string or a number while x[:1].isdigit(): num.append(x[0]) x = x[1:] num = int(num) if x[0] in whitespace: return (x[1:], num) elif x[0:1] == b":": if len(x) < num: raise NeedMoreData("Expected string of length %r" % num) return (x[num + 2 :], x[1 : num + 1]) elif not x: raise MarshallError("Expected whitespace, got end of string.") else: raise MarshallError("Expected whitespace or ':', got '%c'" % x[0]) elif x[:1].isalpha(): ret = bytearray() # Parse literal try: while x[:1].isalpha() or x[:1].isdigit() or x[0:1] == b"-": ret.append(x[0]) x = x[1:] except IndexError: raise NeedMoreData("Expected literal") if not x: raise MarshallError("Expected whitespace, got end of string.") if x[0] not in whitespace: raise MarshallError("Expected whitespace, got '%c'" % x[0]) return (x[1:], literal(ret.decode("ascii"))) else: raise MarshallError("Unexpected character '%c'" % x[0]) subvertpy_0.11.1.orig/subvertpy/properties.py0000644000000000000000000002231615154541453016430 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Handling of Subversion properties.""" __author__ = "Jelmer Vernooij " __docformat__ = "restructuredText" import bisect import calendar import time try: import urlparse except ImportError: import urllib.parse as urlparse class InvalidExternalsDescription(Exception): _fmt = """Unable to parse externals description.""" def is_valid_property_name(prop): """Check the validity of a property name. :param prop: Property name :return: Whether prop is a valid property name """ if not prop[0].isalnum() and prop[0] not in ":_": return False for c in prop[1:]: if not c.isalnum() and c not in "-:._": return False return True def time_to_cstring(timestamp): """Determine string representation of a time. :param timestamp: Number of microseconds since the start of 1970 :return: string with date """ tm_usec = timestamp % 1000000 (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday, tm_isdst) = ( time.gmtime(timestamp / 1000000) ) return "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" % ( tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_usec, ) def time_from_cstring(text): """Parse a time from a cstring. :param text: Parse text :return: number of microseconds since the start of 1970 """ (basestr, usecstr) = text.split(".", 1) assert usecstr[-1] == "Z" tm_usec = int(usecstr[:-1]) tm = time.strptime(basestr, "%Y-%m-%dT%H:%M:%S") return int(calendar.timegm(tm)) * 1000000 + tm_usec def parse_externals_description(base_url, val): """Parse an svn:externals property value. :param base_url: URL on which the property is set. Used for relative externals. :returns: dictionary with local names as keys, (revnum, url) as value. revnum is the revision number and is set to None if not applicable. """ def is_url(u): return "://" in u ret = {} for line in val.splitlines(): if line == "" or line[0] == "#": continue pts = line.rsplit(None, 3) if len(pts) == 4: if pts[0] == "-r": # -r X URL DIR revno = int(pts[1]) path = pts[3] relurl = pts[2] elif pts[1] == "-r": # DIR -r X URL revno = int(pts[2]) path = pts[0] relurl = pts[3] else: raise InvalidExternalsDescription() elif len(pts) == 3: if pts[1].startswith("-r"): # DIR -rX URL revno = int(pts[1][2:]) path = pts[0] relurl = pts[2] elif pts[0].startswith("-r"): # -rX URL DIR revno = int(pts[0][2:]) path = pts[2] relurl = pts[1] else: raise InvalidExternalsDescription() elif len(pts) == 2: if not is_url(pts[0]): relurl = pts[1] path = pts[0] else: relurl = pts[0] path = pts[1] revno = None else: raise InvalidExternalsDescription() if relurl.startswith("//"): raise NotImplementedError( "Relative to the scheme externals not yet supported" ) if relurl.startswith("^/"): raise NotImplementedError( "Relative to the repository root externals not yet supported" ) ret[path] = (revno, urlparse.urljoin(base_url + "/", relurl)) return ret def parse_mergeinfo_property(text): """Parse a mergeinfo property. :param text: Property contents """ ret = {} for line in text.splitlines(): (path, ranges) = line.rsplit(":", 1) assert path.startswith("/") ret[path] = [] for range in ranges.split(","): if range[-1] == "*": inheritable = False range = range[:-1] else: inheritable = True try: (start, end) = range.split("-", 1) ret[path].append((int(start), int(end), inheritable)) except ValueError: ret[path].append((int(range), int(range), inheritable)) return ret def generate_mergeinfo_property(merges): """Generate the contents of the svn:mergeinfo property :param merges: dictionary mapping paths to lists of ranges :return: Property contents """ def formatrange(range_params): (start, end, inheritable) = range_params suffix = "" if not inheritable: suffix = "*" if start == end: return "%d%s" % (start, suffix) else: return "%d-%d%s" % (start, end, suffix) text = "" for path, ranges in merges.items(): assert path.startswith("/") text += "%s:%s\n" % (path, ",".join(map(formatrange, ranges))) return text def range_includes_revnum(ranges, revnum): """Check if the specified range contains the mentioned revision number. :param ranges: list of ranges :param revnum: revision number :return: Whether or not the revision number is included """ i = bisect.bisect(ranges, (revnum, revnum, True)) if i == 0: return False (start, end, inheritable) = ranges[i - 1] return start <= revnum <= end def range_add_revnum(ranges, revnum, inheritable=True): """Add revision number to a list of ranges :param ranges: List of ranges :param revnum: Revision number to add :param inheritable: TODO :return: New list of ranges """ # TODO: Deal with inheritable item = (revnum, revnum, inheritable) if len(ranges) == 0: ranges.append(item) return ranges i = bisect.bisect(ranges, item) if i > 0: (start, end, inh) = ranges[i - 1] if start <= revnum <= end: # already there return ranges if end == revnum - 1: # Extend previous range ranges[i - 1] = (start, end + 1, inh) return ranges if i < len(ranges): (start, end, inh) = ranges[i] if start - 1 == revnum: # Extend next range ranges[i] = (start - 1, end, inh) return ranges ranges.insert(i, item) return ranges def mergeinfo_includes_revision(merges, path, revnum): """Check if the specified mergeinfo contains a path in revnum. :param merges: Dictionary with merges :param path: Merged path :param revnum: Revision number :return: Whether the revision is included """ assert path.startswith("/") try: ranges = merges[path] except KeyError: return False return range_includes_revnum(ranges, revnum) def mergeinfo_add_revision(mergeinfo, path, revnum): """Add a revision to a mergeinfo dictionary :param mergeinfo: Merginfo dictionary :param path: Merged path to add :param revnum: Merged revision to add :return: Updated dictionary """ assert path.startswith("/") mergeinfo[path] = range_add_revnum(mergeinfo.get(path, []), revnum) return mergeinfo PROP_EXECUTABLE = "svn:executable" PROP_EXECUTABLE_VALUE = b"*" PROP_EXTERNALS = "svn:externals" PROP_IGNORE = "svn:ignore" PROP_KEYWORDS = "svn:keywords" PROP_MIME_TYPE = "svn:mime-type" PROP_MERGEINFO = "svn:mergeinfo" PROP_NEEDS_LOCK = "svn:needs-lock" PROP_NEEDS_LOCK_VALUE = b"*" PROP_PREFIX = "svn:" PROP_SPECIAL = "svn:special" PROP_SPECIAL_VALUE = b"*" PROP_WC_PREFIX = "svn:wc:" PROP_ENTRY_PREFIX = "svn:entry" PROP_ENTRY_COMMITTED_DATE = "svn:entry:committed-date" PROP_ENTRY_COMMITTED_REV = "svn:entry:committed-rev" PROP_ENTRY_LAST_AUTHOR = "svn:entry:last-author" PROP_ENTRY_LOCK_TOKEN = "svn:entry:lock-token" PROP_ENTRY_UUID = "svn:entry:uuid" PROP_REVISION_LOG = "svn:log" PROP_REVISION_AUTHOR = "svn:author" PROP_REVISION_DATE = "svn:date" PROP_REVISION_ORIGINAL_DATE = "svn:original-date" def diff(current, previous): """Find the differences between two property dictionaries. :param current: Dictionary with current (new) properties :param previous: Dictionary with previous (old) properties :return: Dictionary that contains an entry for each property that was changed. Value is a tuple with the old and the new property value. """ ret = {} for key, newval in current.items(): oldval = previous.get(key) if oldval != newval: ret[key] = (oldval, newval) return ret subvertpy_0.11.1.orig/subvertpy/ra.h0000644000000000000000000000201213133767436014432 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #ifndef _BZR_SVN_RA_H_ #define _BZR_SVN_RA_H_ typedef struct { PyObject_HEAD svn_auth_baton_t *auth_baton; apr_pool_t *pool; PyObject *providers; } AuthObject; #endif /* _BZR_SVN_RA_H_ */ subvertpy_0.11.1.orig/subvertpy/ra.py0000644000000000000000000000341515154541453014635 0ustar00# Copyright (C) 2006-2008 Jelmer Vernooij # 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 """Python bindings for Subversion.""" __author__ = "Jelmer Vernooij " from subvertpy import SubversionException, ERR_BAD_URL from subvertpy import _ra from subvertpy._ra import * # noqa: F403,F401 from subvertpy import ra_svn # noqa: F401 try: from urllib2 import splittype except ImportError: from urllib.parse import splittype url_handlers = { "svn": _ra.RemoteAccess, # "svn": ra_svn.Client, "svn+ssh": _ra.RemoteAccess, # "svn+ssh": ra_svn.Client, "http": _ra.RemoteAccess, "https": _ra.RemoteAccess, "file": _ra.RemoteAccess, } def RemoteAccess(url, *args, **kwargs): """Connect to a remote Subversion server :param url: URL to connect to :return: RemoteAccess object """ if isinstance(url, bytes): url = url.decode("utf-8") (type, opaque) = splittype(url) if type not in url_handlers: raise SubversionException("Unknown URL type '%s'" % type, ERR_BAD_URL) return url_handlers[type](url, *args, **kwargs) subvertpy_0.11.1.orig/subvertpy/ra_svn.py0000644000000000000000000010701715154541453015526 0ustar00# Copyright (C) 2006-2008 Jelmer Vernooij # 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 """Python bindings for Subversion.""" __author__ = "Jelmer Vernooij " try: from SocketServer import StreamRequestHandler, TCPServer except ImportError: from socketserver import StreamRequestHandler, TCPServer import base64 import os import socket import subprocess from errno import EPIPE try: import urlparse except ImportError: import urllib.parse as urlparse from subvertpy import ( ERR_RA_SVN_UNKNOWN_CMD, ERR_UNSUPPORTED_FEATURE, NODE_DIR, NODE_FILE, NODE_UNKNOWN, NODE_NONE, SubversionException, properties, ) from subvertpy.delta import ( pack_svndiff0_window, unpack_svndiff0, SVNDIFF0_HEADER, ) from subvertpy.marshall import ( NeedMoreData, literal, marshall, unmarshall, ) from subvertpy.ra import ( DIRENT_CREATED_REV, DIRENT_HAS_PROPS, DIRENT_KIND, DIRENT_LAST_AUTHOR, DIRENT_SIZE, DIRENT_TIME, ) from subvertpy.server import ( generate_random_id, ) class SSHSubprocess(object): """A socket-like object that talks to an ssh subprocess via pipes.""" __slots__ = "proc" def __init__(self, proc): self.proc = proc def send(self, data): return os.write(self.proc.stdin.fileno(), data) def recv(self, count): return os.read(self.proc.stdout.fileno(), count) def close(self): self.proc.stdin.close() self.proc.stdout.close() self.proc.wait() def get_filelike_channels(self): return (self.proc.stdout, self.proc.stdin) class SSHVendor(object): def connect_ssh(self, username, password, host, port, command): args = ["ssh", "-x"] if port is not None: args.extend(["-p", str(port)]) if username is not None: host = "%s@%s" % (username, host) args.append(host) proc = subprocess.Popen( args + command, stdin=subprocess.PIPE, stdout=subprocess.PIPE ) return SSHSubprocess(proc) # Can be overridden by users get_ssh_vendor = SSHVendor class SVNConnection(object): def __init__(self, recv_fn, send_fn): self.inbuffer = "" self.recv_fn = recv_fn self.send_fn = send_fn def recv_msg(self): while True: try: (self.inbuffer, ret) = unmarshall(self.inbuffer) return ret except NeedMoreData: newdata = self.recv_fn(1) if newdata != "": # self.mutter("IN: %r" % newdata) self.inbuffer += newdata def send_msg(self, data): marshalled_data = marshall(data) # self.mutter("OUT: %r" % marshalled_data) self.send_fn(marshalled_data) def send_success(self, *contents): self.send_msg([literal("success"), list(contents)]) SVN_PORT = 3690 def feed_editor(conn, editor): tokens = {} diff = {} txdelta_handler = {} # Process commands while True: command, args = conn.recv_msg() if command == "target-rev": editor.set_target_revision(args[0]) elif command == "open-root": if len(args[0]) == 0: token = editor.open_root() else: token = editor.open_root(args[0][0]) tokens[args[1]] = token elif command == "delete-entry": tokens[args[2]].delete_entry(args[0], args[1]) elif command == "add-dir": if len(args[3]) == 0: token = tokens[args[1]].add_directory(args[0]) else: token = tokens[args[1]].add_directory(args[0], args[3][0], args[4][0]) tokens[args[2]] = token elif command == "open-dir": tokens[args[2]] = tokens[args[1]].open_directory(args[0], args[3]) elif command == "change-dir-prop": if len(args[2]) == 0: tokens[args[0]].change_prop(args[1], None) else: tokens[args[0]].change_prop(args[1], args[2][0]) elif command == "close-dir": tokens[args[0]].close() elif command == "absent-dir": tokens[args[1]].absent(args[0]) elif command == "add-file": if len(args[3]) == 0: token = tokens[args[1]].add_file(args[0]) else: token = tokens[args[1]].add_file(args[0], args[3][0], args[4][0]) tokens[args[2]] = token elif command == "open-file": tokens[args[2]] = tokens[args[1]].open_file(args[0], args[3]) elif command == "apply-textdelta": if len(args[1]) == 0: txdelta_handler[args[0]] = tokens[args[0]].apply_textdelta(None) else: txdelta_handler[args[0]] = tokens[args[0]].apply_textdelta(args[1][0]) diff[args[0]] = "" elif command == "textdelta-chunk": diff[args[0]] += args[1] elif command == "textdelta-end": for w in unpack_svndiff0(diff[args[0]]): txdelta_handler[args[0]](w) txdelta_handler[args[0]](None) elif command == "change-file-prop": if len(args[2]) == 0: tokens[args[0]].change_prop(args[1], None) else: tokens[args[0]].change_prop(args[1], args[2][0]) elif command == "close-file": if len(args[1]) == 0: tokens[args[0]].close() else: tokens[args[0]].close(args[1][0]) elif command == "close-edit": editor.close() break elif command == "abort-edit": editor.abort() break conn.send_success() conn._unpack() class Reporter(object): __slots__ = ("conn", "editor") def __init__(self, conn, editor): self.conn = conn self.editor = editor def set_path(self, path, rev, start_empty=False, lock_token=None, depth=None): args = [path, rev, start_empty] if lock_token is not None: args.append([lock_token]) else: args.append([]) if depth is not None: args.append(depth) self.conn.send_msg([literal("set-path"), args]) def delete_path(self, path): self.conn.send_msg([literal("delete-path"), [path]]) def link_path(self, path, url, rev, start_empty=False, lock_token=None, depth=None): args = [path, url, rev, start_empty] if lock_token is not None: args.append([lock_token]) else: args.append([]) if depth is not None: args.append(depth) self.conn.send_msg([literal("link-path"), args]) def finish(self): self.conn.send_msg([literal("finish-report"), []]) self.conn.recv_msg() feed_editor(self.conn, self.editor) self.conn.busy = False def abort(self): self.conn.send_msg([literal("abort-report"), []]) self.conn.busy = False class Editor(object): __slots__ = "conn" def __init__(self, conn): self.conn = conn def set_target_revision(self, revnum): self.conn.send_msg([literal("target-rev"), [revnum]]) def open_root(self, base_revision=None): id = generate_random_id() if base_revision is None: baserev = [] else: baserev = [base_revision] self.conn.send_msg([literal("open-root"), [baserev, id]]) self.conn._open_ids = [] return DirectoryEditor(self.conn, id) def close(self): self.conn.send_msg([literal("close-edit"), []]) def abort(self): self.conn.send_msg([literal("abort-edit"), []]) class DirectoryEditor(object): __slots__ = ("conn", "id") def __init__(self, conn, id): self.conn = conn self.id = id self.conn._open_ids.append(id) def add_file(self, path, copyfrom_path=None, copyfrom_rev=-1): self._is_last_open() child = generate_random_id() if copyfrom_path is not None: copyfrom_data = [copyfrom_path, copyfrom_rev] else: copyfrom_data = [] self.conn.send_msg([literal("add-file"), [path, self.id, child, copyfrom_data]]) return FileEditor(self.conn, child) def open_file(self, path, base_revnum): self._is_last_open() child = generate_random_id() self.conn.send_msg([literal("open-file"), [path, self.id, child, base_revnum]]) return FileEditor(self.conn, child) def delete_entry(self, path, base_revnum): self._is_last_open() self.conn.send_msg([literal("delete-entry"), [path, base_revnum, self.id]]) def add_directory(self, path, copyfrom_path=None, copyfrom_rev=-1): self._is_last_open() child = generate_random_id() if copyfrom_path is not None: copyfrom_data = [copyfrom_path, copyfrom_rev] else: copyfrom_data = [] self.conn.send_msg([literal("add-dir"), [path, self.id, child, copyfrom_data]]) return DirectoryEditor(self.conn, child) def open_directory(self, path, base_revnum): self._is_last_open() child = generate_random_id() self.conn.send_msg([literal("open-dir"), [path, self.id, child, base_revnum]]) return DirectoryEditor(self.conn, child) def change_prop(self, name, value): self._is_last_open() if value is None: value = [] else: value = [value] self.conn.send_msg([literal("change-dir-prop"), [self.id, name, value]]) def _is_last_open(self): assert self.conn._open_ids[-1] == self.id def close(self): self._is_last_open() self.conn._open_ids.pop() self.conn.send_msg([literal("close-dir"), [self.id]]) class FileEditor(object): __slots__ = ("conn", "id") def __init__(self, conn, id): self.conn = conn self.id = id self.conn._open_ids.append(id) def _is_last_open(self): assert self.conn._open_ids[-1] == self.id def close(self, checksum=None): self._is_last_open() self.conn._open_ids.pop() if checksum is None: checksum = [] else: checksum = [checksum] self.conn.send_msg([literal("close-file"), [self.id, checksum]]) def apply_textdelta(self, base_checksum=None): self._is_last_open() if base_checksum is None: base_check = [] else: base_check = [base_checksum] self.conn.send_msg([literal("apply-textdelta"), [self.id, base_check]]) self.conn.send_msg([literal("textdelta-chunk"), [self.id, SVNDIFF0_HEADER]]) def send_textdelta(delta): if delta is None: self.conn.send_msg([literal("textdelta-end"), [self.id]]) else: self.conn.send_msg( [literal("textdelta-chunk"), [self.id, pack_svndiff0_window(delta)]] ) return send_textdelta def change_prop(self, name, value): self._is_last_open() if value is None: value = [] else: value = [value] self.conn.send_msg([literal("change-file-prop"), [self.id, name, value]]) def mark_busy(unbound): def convert(self, *args, **kwargs): self.busy = True try: ret = unbound(self, *args, **kwargs) finally: self.busy = False return ret convert.__doc__ = unbound.__doc__ convert.__name__ = unbound.__name__ return convert def unmarshall_dirent(d): ret = { "name": d[0], "kind": d[1], "size": d[2], "has-props": bool(d[3]), "created-rev": d[4], } if d[5] != []: ret["created-date"] = d[5] if d[6] != []: ret["last-author"] = d[6] return ret class SVNClient(SVNConnection): def __init__( self, url, progress_cb=None, auth=None, config=None, client_string_func=None, open_tmp_file_func=None, ): self.url = url (type, opaque) = urlparse.splittype(url) assert type in ("svn", "svn+ssh") (host, path) = urlparse.splithost(opaque) self._progress_cb = progress_cb self._auth = auth self._config = config self._client_string_func = client_string_func # open_tmp_file_func is ignored, as it is not needed for svn:// if type == "svn": (recv_func, send_func) = self._connect(host) else: (recv_func, send_func) = self._connect_ssh(host) super(SVNClient, self).__init__(recv_func, send_func) (min_version, max_version, _, self._server_capabilities) = self._recv_greeting() self.send_msg( [ max_version, [literal(x) for x in CAPABILITIES if x in self._server_capabilities], self.url, ] ) (self._server_mechanisms, mech_arg) = self._unpack() if self._server_mechanisms != []: # FIXME: Support other mechanisms as well self.send_msg( [ literal("ANONYMOUS"), [base64.b64encode("anonymous@%s" % socket.gethostname())], ] ) self.recv_msg() msg = self._unpack() if len(msg) > 2: self._server_capabilities += msg[2] (self._uuid, self._root_url) = msg[0:2] self.busy = False def _unpack(self): msg = self.recv_msg() if msg[0] == "failure": if isinstance(msg[1], str): raise SubversionException(*msg[1]) num = msg[1][0][0] msg = msg[1][0][1] if num == ERR_RA_SVN_UNKNOWN_CMD: raise NotImplementedError(msg) raise SubversionException(msg, num) assert msg[0] == "success", "Got: %r" % msg assert len(msg) == 2 return msg[1] def _recv_greeting(self): greeting = self._unpack() assert len(greeting) == 4 return greeting _recv_ack = _unpack def _connect(self, host): (host, port) = urlparse.splitnport(host, SVN_PORT) sockaddrs = socket.getaddrinfo( host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, 0 ) self._socket = None 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 last_err self._socket.setblocking(True) return (self._socket.recv, self._socket.send) def _connect_ssh(self, host): (user, host) = urlparse.splituser(host) if user is not None: (user, password) = urlparse.splitpassword(user) else: password = None (host, port) = urlparse.splitnport(host, 22) self._tunnel = get_ssh_vendor().connect_ssh( user, password, host, port, ["svnserve", "-t"] ) return (self._tunnel.recv, self._tunnel.send) def get_file_revs(self, path, start, end, file_rev_handler): raise NotImplementedError(self.get_file_revs) @mark_busy def get_locations(self, path, peg_revision, location_revisions): self.send_msg( [literal("get-locations"), [path, peg_revision, location_revisions]] ) self._recv_ack() ret = {} while True: msg = self.recv_msg() if msg == "done": break ret[msg[0]] = msg[1] self._unparse() return ret def get_locks(self, path): self.send_msg([literal("get-lock"), [path]]) self._recv_ack() return self._unpack() def lock(self, path_revs, comment, steal_lock, lock_func): raise NotImplementedError(self.lock) def unlock(self, path_tokens, break_lock, lock_func): raise NotImplementedError(self.unlock) def mergeinfo(self, paths, revision=-1, inherit=None, include_descendants=False): raise NotImplementedError(self.mergeinfo) def location_segments( self, path, start_revision, end_revision, include_merged_revisions=False ): args = [path] if start_revision is None or start_revision == -1: args.append([]) else: args.append([start_revision]) if end_revision is None or end_revision == -1: args.append([]) else: args.append([end_revision]) args.append(include_merged_revisions) self.send_msg([literal("get-location-segments"), args]) self._recv_ack() while True: msg = self.recv_msg() if msg == "done": break yield msg self._unpack() def get_location_segments(self, path, start_revision, end_revision, rcvr): for msg in self.location_segments(path, start_revision, end_revision): rcvr(*msg) def has_capability(self, capability): return capability in self._server_capabilities @mark_busy def check_path(self, path, revision=None): args = [path] if revision is None or revision == -1: args.append([]) else: args.append([revision]) self.send_msg([literal("check-path"), args]) self._recv_ack() ret = self._unpack()[0] return { "dir": NODE_DIR, "file": NODE_FILE, "unknown": NODE_UNKNOWN, "none": NODE_NONE, }[ret] def get_lock(self, path): self.send_msg([literal("get-lock"), [path]]) self._recv_ack() ret = self._unpack() if len(ret) == 0: return None else: return ret[0] @mark_busy def get_dir( self, path, revision=-1, dirent_fields=0, want_props=True, want_contents=True ): args = [path] if revision is None or revision == -1: args.append([]) else: args.append([revision]) args += [want_props, want_contents] fields = [] if dirent_fields & DIRENT_KIND: fields.append(literal("kind")) if dirent_fields & DIRENT_SIZE: fields.append(literal("size")) if dirent_fields & DIRENT_HAS_PROPS: fields.append(literal("has-props")) if dirent_fields & DIRENT_CREATED_REV: fields.append(literal("created-rev")) if dirent_fields & DIRENT_TIME: fields.append(literal("time")) if dirent_fields & DIRENT_LAST_AUTHOR: fields.append(literal("last-author")) args.append(fields) self.send_msg([literal("get-dir"), args]) self._recv_ack() ret = self._unpack() fetch_rev = ret[0] props = dict(ret[1]) dirents = {} for d in ret[2]: entry = unmarshall_dirent(d) dirents[entry["name"]] = entry return (dirents, fetch_rev, props) @mark_busy def stat(self, path, revision=-1): args = [path] if revision is None or revision == -1: args.append([revision]) else: args.append([]) self.send_msg([literal("stat"), args]) self._recv_ack() ret = self._unpack() if len(ret) == 0: return None return unmarshall_dirent(ret[0]) @mark_busy def get_file(self, path, stream, revision=-1): raise NotImplementedError(self.get_file) def change_rev_prop(self, rev, name, value): args = [rev, name] if value is not None: args.append(value) self.send_msg([literal("change-rev-prop"), args]) self._recv_ack() self._unparse() def get_commit_editor( self, revprops, callback=None, lock_tokens=None, keep_locks=False ): args = [revprops[properties.PROP_REVISION_LOG]] if lock_tokens is not None: args.append(list(lock_tokens.items())) else: args.append([]) args.append(keep_locks) if len(revprops) > 1: args.append(list(revprops.items())) self.send_msg([literal("commit"), args]) self._recv_ack() raise NotImplementedError(self.get_commit_editor) def rev_proplist(self, revision): self.send_msg([literal("rev-proplist"), [revision]]) self._recv_ack() return dict(self._unpack()[0]) def rev_prop(self, revision, name): self.send_msg([literal("rev-prop"), [revision, name]]) self._recv_ack() ret = self._unpack() if len(ret) == 0: return None else: return ret[0] @mark_busy def replay(self, revision, low_water_mark, update_editor, send_deltas=True): self.send_msg([literal("replay"), [revision, low_water_mark, send_deltas]]) self._recv_ack() feed_editor(self, update_editor) self._unpack() @mark_busy def replay_range( self, start_revision, end_revision, low_water_mark, cbs, send_deltas=True ): self.send_msg( [ literal("replay-range"), [start_revision, end_revision, low_water_mark, send_deltas], ] ) self._recv_ack() for i in range(start_revision, end_revision + 1): msg = self.recv_msg() assert msg[0] == "revprops" edit = cbs[0](i, dict(msg[1])) feed_editor(self, edit) cbs[1](i, dict(msg[1]), edit) self._unpack() def do_switch( self, revision_to_update_to, update_target, recurse, switch_url, update_editor, depth=None, ): args = [] if revision_to_update_to is None or revision_to_update_to == -1: args.append([]) else: args.append([revision_to_update_to]) args.append(update_target) args.append(recurse) args.append(switch_url) if depth is not None: args.append(literal(depth)) self.busy = True try: self.send_msg([literal("switch"), args]) self._recv_ack() return Reporter(self, update_editor) except BaseException: self.busy = False raise def do_update( self, revision_to_update_to, update_target, recurse, update_editor, depth=None ): args = [] if revision_to_update_to is None or revision_to_update_to == -1: args.append([]) else: args.append([revision_to_update_to]) args.append(update_target) args.append(recurse) if depth is not None: args.append(literal(depth)) self.busy = True try: self.send_msg([literal("update"), args]) self._recv_ack() return Reporter(self, update_editor) except BaseException: self.busy = False raise def do_diff( self, revision_to_update, diff_target, versus_url, diff_editor, recurse=True, ignore_ancestry=False, text_deltas=False, depth=None, ): args = [] if revision_to_update is None or revision_to_update == -1: args.append([]) else: args.append([revision_to_update]) args += [diff_target, recurse, ignore_ancestry, versus_url, text_deltas] if depth is not None: args.append(literal(depth)) self.busy = True try: self.send_msg([literal("diff"), args]) self._recv_ack() return Reporter(self, diff_editor) except BaseException: self.busy = False raise def get_repos_root(self): return self._root_url @mark_busy def get_latest_revnum(self): self.send_msg([literal("get-latest-rev"), []]) self._recv_ack() return self._unpack()[0] @mark_busy def get_dated_rev(self, date): self.send_msg([literal("get-dated-rev"), [date]]) self._recv_ack() return self._unpack()[0] @mark_busy def reparent(self, url): self.send_msg([literal("reparent"), [url]]) self._recv_ack() self._unpack() self.url = url def get_uuid(self): return self._uuid @mark_busy def log( self, paths, start, end, limit=0, discover_changed_paths=True, strict_node_history=True, include_merged_revisions=True, revprops=None, ): args = [paths] if start is None or start == -1: args.append([]) else: args.append([start]) if end is None or end == -1: args.append([]) else: args.append([end]) args.append(discover_changed_paths) args.append(strict_node_history) args.append(limit) args.append(include_merged_revisions) if revprops is None: args.append(literal("all-revprops")) args.append([]) else: args.append(literal("revprops")) args.append(revprops) self.send_msg([literal("log"), args]) self._recv_ack() while True: msg = self.recv_msg() if msg == "done": break paths = {} for p, action, cfd in msg[0]: if len(cfd) == 0: paths[p] = (str(action), None, -1) else: paths[p] = (str(action), cfd[0], cfd[1]) if len(msg) > 5: has_children = msg[5] else: has_children = None if len(msg) > 6 and msg[6]: revno = None else: revno = msg[1] # noqa: F841 # TODO(jelmer): Do something with revno revprops = {} if len(msg[2]) != 0: revprops[properties.PROP_REVISION_AUTHOR] = msg[2][0] if len(msg[3]) != 0: revprops[properties.PROP_REVISION_DATE] = msg[3][0] if len(msg[4]) != 0: revprops[properties.PROP_REVISION_LOG] = msg[4][0] if len(msg) > 8: revprops.update(dict(msg[8])) yield paths, msg[1], revprops, has_children self._unpack() def get_log(self, callback, *args, **kwargs): for paths, rev, props, has_children in self.log(*args, **kwargs): if has_children is None: callback(paths, rev, props) else: callback(paths, rev, props, has_children) MIN_VERSION = 2 MAX_VERSION = 2 CAPABILITIES = ["edit-pipeline", "bazaar", "log-revprops"] MECHANISMS = ["ANONYMOUS"] class SVNServer(SVNConnection): def __init__(self, backend, recv_fn, send_fn, logf=None): self.backend = backend self._stop = False self._logf = logf super(SVNServer, self).__init__(recv_fn, send_fn) self.send_success( MIN_VERSION, MAX_VERSION, [literal(x) for x in MECHANISMS], [literal(x) for x in CAPABILITIES], ) def send_mechs(self): self.send_success([literal(x) for x in MECHANISMS], "") def send_failure(self, *contents): self.send_msg([literal("failure"), list(contents)]) def send_ack(self): self.send_success([], "") def send_unknown(self, cmd): self.send_failure( [ERR_RA_SVN_UNKNOWN_CMD, "Unknown command '%s'" % cmd, __file__, 52] ) def get_latest_rev(self): self.send_ack() self.send_success(self.repo_backend.get_latest_revnum()) def check_path(self, path, rev): if len(rev) == 0: revnum = None else: revnum = rev[0] kind = self.repo_backend.check_path(path, revnum) self.send_ack() self.send_success( literal( { NODE_NONE: "none", NODE_DIR: "dir", NODE_FILE: "file", NODE_UNKNOWN: "unknown", }[kind] ) ) def log( self, target_path, start_rev, end_rev, changed_paths, strict_node, limit=None, include_merged_revisions=False, all_revprops=None, revprops=None, ): def send_revision(revno, author, date, message, changed_paths=None): changes = [] if changed_paths is not None: for p, (action, cf, cr) in changed_paths.items(): if cf is not None: changes.append((p, literal(action), (cf, cr))) else: changes.append((p, literal(action), ())) self.send_msg([changes, revno, [author], [date], [message]]) self.send_ack() if len(start_rev) == 0: start_revnum = None else: start_revnum = start_rev[0] if len(end_rev) == 0: end_revnum = None else: end_revnum = end_rev[0] self.repo_backend.log( send_revision, target_path, start_revnum, end_revnum, changed_paths, strict_node, limit, ) self.send_msg(literal("done")) self.send_success() def open_backend(self, url): (rooturl, location) = urlparse.splithost(url) self.repo_backend, self.relpath = self.backend.open_repository(location) def reparent(self, parent): self.open_backend(parent) self.send_ack() self.send_success() def stat(self, path, rev): if len(rev) == 0: revnum = None else: revnum = rev[0] self.send_ack() dirent = self.repo_backend.stat(path, revnum) if dirent is None: self.send_success([]) else: args = [ dirent["name"], dirent["kind"], dirent["size"], dirent["has-props"], dirent["created-rev"], ] if "created-date" in dirent: args.append([dirent["created-date"]]) else: args.append([]) if "last-author" in dirent: args.append([dirent["last-author"]]) else: args.append([]) self.send_success([args]) def commit(self, logmsg, locks, keep_locks=False, rev_props=None): self.send_failure( [ERR_UNSUPPORTED_FEATURE, "commit not yet supported", __file__, 42] ) def rev_proplist(self, revnum): self.send_ack() revprops = self.repo_backend.rev_proplist(revnum) self.send_success(list(revprops.items())) def rev_prop(self, revnum, name): self.send_ack() revprops = self.repo_backend.rev_proplist(revnum) if name in revprops: self.send_success([revprops[name]]) else: self.send_success() def get_locations(self, path, peg_revnum, revnums): self.send_ack() locations = self.repo_backend.get_locations(path, peg_revnum, revnums) for rev, path in locations.items(): self.send_msg([rev, path]) self.send_msg(literal("done")) self.send_success() def update(self, rev, target, recurse, depth=None, send_copyfrom_param=True): self.send_ack() while True: msg = self.recv_msg() assert msg[0] in ["set-path", "finish-report"] if msg[0] == "finish-report": break self.send_ack() if len(rev) == 0: revnum = None else: revnum = rev[0] self.repo_backend.update(Editor(self), revnum, target, recurse) self.send_success() client_result = self.recv_msg() if client_result[0] == "success": return else: self.mutter("Client reported error during update: %r" % client_result) # Needs to be sent back to the client to display self.send_failure(client_result[1][0]) commands = { "get-latest-rev": get_latest_rev, "log": log, "update": update, "check-path": check_path, "reparent": reparent, "stat": stat, "commit": commit, "rev-proplist": rev_proplist, "rev-prop": rev_prop, "get-locations": get_locations, # FIXME: get-dated-rev # FIXME: get-file # FIXME: get-dir # FIXME: check-path # FIXME: switch # FIXME: status # FIXME: diff # FIXME: get-file-revs # FIXME: replay } def send_auth_request(self): pass def serve(self): self.send_greeting() msg = self.recv_msg() version = msg[0] capabilities = msg[1] url = msg[2] if len(msg) > 3: self.client_user_agent = msg[3] else: self.client_user_agent = None self.capabilities = capabilities self.version = version self.url = url self.mutter("client supports:") self.mutter(" version %r" % version) self.mutter(" capabilities %r " % capabilities) self.send_mechs() (mech, args) = self.recv_msg() # TODO: Proper authentication self.send_success() self.open_backend(url) self.send_success(self.repo_backend.get_uuid(), url) # Expect: while not self._stop: (cmd, args) = self.recv_msg() if cmd not in self.commands: self.mutter("client used unknown command %r" % cmd) self.send_unknown(cmd) return else: self.commands[cmd](self, *args) def close(self): self._stop = True def mutter(self, text): if self._logf is not None: self._logf.write("%s\n" % text) class TCPSVNRequestHandler(StreamRequestHandler): def __init__(self, request, client_address, server): self._server = server StreamRequestHandler.__init__(self, request, client_address, server) def handle(self): server = SVNServer( self._server._backend, self.rfile.read, self.wfile.write, self._server._logf ) try: server.serve() except socket.error as e: if e.args[0] == EPIPE: return raise class TCPSVNServer(TCPServer): allow_reuse_address = True serve = TCPServer.serve_forever def __init__(self, backend, addr, logf=None): self._logf = logf self._backend = backend TCPServer.__init__(self, addr, TCPSVNRequestHandler) subvertpy_0.11.1.orig/subvertpy/repos.c0000644000000000000000000006256015154541453015163 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 #include #include #include #include #include #include #include "util.h" extern PyTypeObject FileSystemRoot_Type; extern PyTypeObject Repository_Type; extern PyTypeObject FileSystem_Type; extern PyTypeObject Stream_Type; typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_repos_t *repos; } RepositoryObject; static PyObject *repos_create(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "path", "config", "fs_config", NULL }; const char *path; PyObject *config=Py_None, *fs_config=Py_None, *py_path; svn_repos_t *repos = NULL; apr_pool_t *pool; apr_hash_t *hash_config, *hash_fs_config; RepositoryObject *ret; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|OO:create", kwnames, &py_path, &config, &fs_config)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; hash_config = config_hash_from_object(config, pool); if (hash_config == NULL) { apr_pool_destroy(pool); return NULL; } hash_fs_config = apr_hash_make(pool); /* TODO(jelmer): Allow config hash */ if (hash_fs_config == NULL) { apr_pool_destroy(pool); PyErr_SetString(PyExc_RuntimeError, "Unable to create fs config hash"); 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_repos_create(&repos, path, NULL, NULL, hash_config, hash_fs_config, pool)); ret = PyObject_New(RepositoryObject, &Repository_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->repos = repos; return (PyObject *)ret; } static void repos_dealloc(PyObject *self) { RepositoryObject *repos = (RepositoryObject *)self; apr_pool_destroy(repos->pool); PyObject_Del(repos); } static PyObject *repos_init(PyTypeObject *type, PyObject *args, PyObject *kwargs) { const char *path; char *kwnames[] = { "path", NULL }; svn_error_t *err; RepositoryObject *ret; PyObject *py_path; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwnames, &py_path)) return NULL; ret = PyObject_New(RepositoryObject, &Repository_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { PyObject_DEL(ret); return NULL; } path = py_object_to_svn_dirent(py_path, ret->pool); if (path == NULL) { Py_DECREF(ret); return NULL; } Py_BEGIN_ALLOW_THREADS err = svn_repos_open(&ret->repos, path, 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; } typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_fs_root_t *root; } FileSystemRootObject; typedef struct { PyObject_VAR_HEAD RepositoryObject *repos; svn_fs_t *fs; } FileSystemObject; static PyObject *repos_fs(PyObject *self) { RepositoryObject *reposobj = (RepositoryObject *)self; FileSystemObject *ret; svn_fs_t *fs; fs = svn_repos_fs(reposobj->repos); if (fs == NULL) { PyErr_SetString(PyExc_RuntimeError, "Unable to obtain fs handle"); return NULL; } ret = PyObject_New(FileSystemObject, &FileSystem_Type); if (ret == NULL) return NULL; ret->fs = fs; ret->repos = reposobj; Py_INCREF(reposobj); return (PyObject *)ret; } static PyObject *fs_get_uuid(PyObject *self) { FileSystemObject *fsobj = (FileSystemObject *)self; const char *uuid; PyObject *ret; apr_pool_t *temp_pool; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_get_uuid(fsobj->fs, &uuid, temp_pool)); ret = PyUnicode_FromString(uuid); apr_pool_destroy(temp_pool); return ret; } static PyObject *fs_get_youngest_revision(FileSystemObject *self) { svn_revnum_t rev; apr_pool_t *temp_pool; PyObject *ret; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_youngest_rev(&rev, self->fs, temp_pool)); ret = py_from_svn_revnum(rev); apr_pool_destroy(temp_pool); return ret; } static PyObject *fs_get_revision_root(FileSystemObject *self, PyObject *args) { svn_revnum_t rev; FileSystemRootObject *ret; apr_pool_t *pool; svn_fs_root_t *root; if (!PyArg_ParseTuple(args, "l", &rev)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_fs_revision_root(&root, self->fs, rev, pool)); ret = PyObject_New(FileSystemRootObject, &FileSystemRoot_Type); if (ret == NULL) return NULL; ret->root = root; ret->pool = pool; return (PyObject *)ret; } static PyObject *fs_get_revision_proplist(FileSystemObject *self, PyObject *args) { svn_revnum_t rev; PyObject *ret; apr_pool_t *temp_pool; apr_hash_t *props; if (!PyArg_ParseTuple(args, "l", &rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_revision_proplist(&props, self->fs, rev, temp_pool)); ret = prop_hash_to_dict(props); apr_pool_destroy(temp_pool); return (PyObject *)ret; } static PyMethodDef fs_methods[] = { { "get_uuid", (PyCFunction)fs_get_uuid, METH_NOARGS, NULL }, { "youngest_revision", (PyCFunction)fs_get_youngest_revision, METH_NOARGS, NULL }, { "revision_root", (PyCFunction)fs_get_revision_root, METH_VARARGS, NULL }, { "revision_proplist", (PyCFunction)fs_get_revision_proplist, METH_VARARGS, NULL }, { NULL, } }; static void fs_dealloc(PyObject *self) { FileSystemObject *fsobj = (FileSystemObject *)self; Py_DECREF(fsobj->repos); PyObject_DEL(fsobj); } PyTypeObject FileSystem_Type = { PyVarObject_HEAD_INIT(NULL, 0) "repos.FileSystem", /* const char *tp_name; For printing, in format "." */ sizeof(FileSystemObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ fs_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ fs_methods, /* struct PyMethodDef *tp_methods; */ }; static PyObject *repos_load_fs(PyObject *self, PyObject *args, PyObject *kwargs) { const char *parent_dir = NULL; PyObject *dumpstream, *feedback_stream; unsigned char use_pre_commit_hook = 0, use_post_commit_hook = 0; char *kwnames[] = { "dumpstream", "feedback_stream", "uuid_action", "parent_dir", "use_pre_commit_hook", "use_post_commit_hook", NULL }; int uuid_action; apr_pool_t *temp_pool; RepositoryObject *reposobj = (RepositoryObject *)self; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OOi|zbb", kwnames, &dumpstream, &feedback_stream, &uuid_action, &parent_dir, &use_pre_commit_hook, &use_post_commit_hook)) return NULL; if (uuid_action != svn_repos_load_uuid_default && uuid_action != svn_repos_load_uuid_ignore && uuid_action != svn_repos_load_uuid_force) { PyErr_SetString(PyExc_RuntimeError, "Invalid UUID action"); return NULL; } temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_load_fs2(reposobj->repos, new_py_stream(temp_pool, dumpstream), new_py_stream(temp_pool, feedback_stream), uuid_action, parent_dir, use_pre_commit_hook, use_post_commit_hook, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *repos_delete(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_delete(path, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *repos_hotcopy(RepositoryObject *self, PyObject *args) { char *src_path, *dest_path; bool clean_logs = false; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "ss|b", &src_path, &dest_path, &clean_logs)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_hotcopy(src_path, dest_path, clean_logs?TRUE:FALSE, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } /** * Get runtime libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ static PyObject *version(PyObject *self) { const svn_version_t *ver = svn_repos_version(); return Py_BuildValue("(iiis)", ver->major, ver->minor, ver->patch, ver->tag); } SVN_VERSION_DEFINE(svn_api_version); /** * Get compile-time libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ 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); } static PyMethodDef repos_module_methods[] = { { "create", (PyCFunction)repos_create, METH_VARARGS|METH_KEYWORDS, "create(path, config=None, fs_config=None)\n\n" "Create a new repository." }, { "delete", (PyCFunction)repos_delete, METH_VARARGS, "delete(path)\n\n" "Delete a repository." }, { "hotcopy", (PyCFunction)repos_hotcopy, METH_VARARGS, "hotcopy(src_path, dest_path, clean_logs=False)\n\n" "Make a hot copy of a repository." }, { "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 *repos_has_capability(RepositoryObject *self, PyObject *args) { char *name; svn_boolean_t has; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_has_capability(self->repos, &has, name, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(has); } static PyObject *repos_verify(RepositoryObject *self, PyObject *args) { apr_pool_t *temp_pool; PyObject *py_feedback_stream; svn_revnum_t start_rev, end_rev; svn_stream_t *stream; if (!PyArg_ParseTuple(args, "Oll", &py_feedback_stream, &start_rev, &end_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; } stream = new_py_stream(temp_pool, py_feedback_stream); if (stream == NULL) { apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_repos_verify_fs(self->repos, stream, start_rev, end_rev, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } 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; PyGILState_STATE state; if (baton == Py_None) return NULL; state = PyGILState_Ensure(); ret = PyObject_CallFunction((PyObject *)baton, "li", shard, action); if (ret == NULL) { PyGILState_Release(state); return py_svn_error(); } Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *repos_pack(RepositoryObject *self, PyObject *args) { apr_pool_t *temp_pool; PyObject *notify_func = Py_None; if (!PyArg_ParseTuple(args, "|O", ¬ify_func)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_fs_pack(self->repos, py_pack_notify, notify_func, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyMethodDef repos_methods[] = { { "load_fs", (PyCFunction)repos_load_fs, METH_VARARGS|METH_KEYWORDS, NULL }, { "fs", (PyCFunction)repos_fs, METH_NOARGS, NULL }, { "has_capability", (PyCFunction)repos_has_capability, METH_VARARGS, NULL }, { "verify_fs", (PyCFunction)repos_verify, METH_VARARGS, "S.verify_repos(feedback_stream, start_revnum, end_revnum)" }, { "pack_fs", (PyCFunction)repos_pack, METH_VARARGS, NULL }, { NULL, } }; PyTypeObject Repository_Type = { PyVarObject_HEAD_INIT(NULL, 0) "repos.Repository", /* const char *tp_name; For printing, in format "." */ sizeof(RepositoryObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ repos_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ "Local repository", /* 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 */ repos_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; */ repos_init, /* newfunc tp_new; */ }; static void fs_root_dealloc(PyObject *self) { FileSystemRootObject *fsobj = (FileSystemRootObject *)self; apr_pool_destroy(fsobj->pool); PyObject_DEL(fsobj); } static PyObject *py_string_from_svn_node_id(const svn_fs_id_t *id) { apr_pool_t *temp_pool; svn_string_t *str; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; str = svn_fs_unparse_id(id, temp_pool); if (str == NULL) { apr_pool_destroy(temp_pool); return NULL; } return PyBytes_FromStringAndSize(str->data, str->len); } static PyObject *py_fs_path_change2(svn_fs_path_change2_t *val) { PyObject *ret, *py_node_id; py_node_id = py_string_from_svn_node_id(val->node_rev_id); if (py_node_id == NULL) { return NULL; } ret = Py_BuildValue("(Oibb)", py_node_id, val->change_kind, val->text_mod, val->prop_mod); Py_DECREF(py_node_id); if (ret == NULL) { return NULL; } /* FIXME: copyfrom information */ return ret; } static PyObject *fs_root_paths_changed(FileSystemRootObject *self) { apr_pool_t *temp_pool; apr_hash_t *changed_paths; const char *key; apr_ssize_t klen; apr_hash_index_t *idx; PyObject *ret; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_paths_changed2(&changed_paths, self->root, temp_pool)); ret = PyDict_New(); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } for (idx = apr_hash_first(temp_pool, changed_paths); idx != NULL; idx = apr_hash_next(idx)) { PyObject *py_val; svn_fs_path_change2_t *val; apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); py_val = py_fs_path_change2(val); if (py_val == NULL) { apr_pool_destroy(temp_pool); PyObject_Del(ret); return NULL; } if (PyDict_SetItemString(ret, key, py_val) != 0) { apr_pool_destroy(temp_pool); PyObject_Del(ret); Py_DECREF(py_val); return NULL; } Py_DECREF(py_val); } apr_pool_destroy(temp_pool); return ret; } static PyObject *fs_root_is_dir(FileSystemRootObject *self, PyObject *args) { svn_boolean_t is_dir; apr_pool_t *temp_pool; char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_is_dir(&is_dir, self->root, path, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(is_dir); } static PyObject *fs_root_is_file(FileSystemRootObject *self, PyObject *args) { svn_boolean_t is_file; apr_pool_t *temp_pool; char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_is_file(&is_file, self->root, path, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(is_file); } static PyObject *fs_root_file_length(FileSystemRootObject *self, PyObject *args) { svn_filesize_t filesize; apr_pool_t *temp_pool; char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_length(&filesize, self->root, path, temp_pool)); apr_pool_destroy(temp_pool); return PyLong_FromLong(filesize); } static PyObject *fs_node_file_proplist(FileSystemRootObject *self, PyObject *args) { apr_pool_t *temp_pool; PyObject *ret; char *path; apr_hash_t *proplist; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_node_proplist(&proplist, self->root, path, temp_pool)); ret = prop_hash_to_dict(proplist); apr_pool_destroy(temp_pool); return ret; } static PyObject *fs_root_file_checksum(FileSystemRootObject *self, PyObject *args) { apr_pool_t *temp_pool; bool force = false; char *path; svn_checksum_kind_t kind; const char *cstr; svn_checksum_t *checksum; PyObject *ret; if (!PyArg_ParseTuple(args, "s|ib", &path, &kind, &force)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_checksum( &checksum, kind, self->root, path, force?TRUE:FALSE, temp_pool)); cstr = svn_checksum_to_cstring(checksum, temp_pool); if (cstr == NULL) { ret = Py_None; Py_INCREF(ret); } else { ret = PyUnicode_FromString(cstr); } apr_pool_destroy(temp_pool); return ret; } static PyObject *fs_root_file_contents(FileSystemRootObject *self, PyObject *args) { svn_stream_t *stream; apr_pool_t *pool; StreamObject *ret; char *path; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_fs_file_contents(&stream, self->root, path, pool)); ret = PyObject_New(StreamObject, &Stream_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->closed = FALSE; ret->stream = stream; return (PyObject *)ret; } static PyMethodDef fs_root_methods[] = { { "paths_changed", (PyCFunction)fs_root_paths_changed, METH_NOARGS, NULL }, { "is_dir", (PyCFunction)fs_root_is_dir, METH_VARARGS, NULL }, { "is_file", (PyCFunction)fs_root_is_file, METH_VARARGS, NULL }, { "file_length", (PyCFunction)fs_root_file_length, METH_VARARGS, NULL }, { "file_content", (PyCFunction)fs_root_file_contents, METH_VARARGS, NULL }, { "file_checksum", (PyCFunction)fs_root_file_checksum, METH_VARARGS, NULL }, { "proplist", (PyCFunction)fs_node_file_proplist, METH_VARARGS, NULL }, { NULL, } }; PyTypeObject FileSystemRoot_Type = { PyVarObject_HEAD_INIT(NULL, 0) "repos.FileSystemRoot", /* const char *tp_name; For printing, in format "." */ sizeof(FileSystemRootObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ fs_root_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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 */ fs_root_methods, /* struct PyMethodDef *tp_methods; */ }; static PyObject *moduleinit(void) { static apr_pool_t *pool; PyObject *mod; if (PyType_Ready(&Repository_Type) < 0) return NULL; if (PyType_Ready(&FileSystem_Type) < 0) return NULL; if (PyType_Ready(&FileSystemRoot_Type) < 0) return NULL; if (PyType_Ready(&Stream_Type) < 0) return NULL; apr_initialize(); pool = Pool(NULL); if (pool == NULL) return NULL; svn_fs_initialize(pool); #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "repos", /* m_name */ "Local repository management.", /* m_doc */ -1, /* m_size */ repos_module_methods, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear*/ NULL, /* m_free */ }; mod = PyModule_Create(&moduledef); #else mod = Py_InitModule3("repos", repos_module_methods, "Local repository management"); #endif if (mod == NULL) return NULL; PyModule_AddIntConstant(mod, "LOAD_UUID_DEFAULT", svn_repos_load_uuid_default); PyModule_AddIntConstant(mod, "LOAD_UUID_IGNORE", svn_repos_load_uuid_ignore); PyModule_AddIntConstant(mod, "LOAD_UUID_FORCE", svn_repos_load_uuid_force); PyModule_AddIntConstant(mod, "PATH_CHANGE_MODIFY", svn_fs_path_change_modify); PyModule_AddIntConstant(mod, "PATH_CHANGE_ADD", svn_fs_path_change_add); PyModule_AddIntConstant(mod, "PATH_CHANGE_DELETE", svn_fs_path_change_delete); PyModule_AddIntConstant(mod, "PATH_CHANGE_REPLACE", svn_fs_path_change_replace); PyModule_AddIntConstant(mod, "CHECKSUM_MD5", svn_checksum_md5); PyModule_AddIntConstant(mod, "CHECKSUM_SHA1", svn_checksum_sha1); PyModule_AddObject(mod, "Repository", (PyObject *)&Repository_Type); Py_INCREF(&Repository_Type); PyModule_AddObject(mod, "Stream", (PyObject *)&Stream_Type); Py_INCREF(&Stream_Type); return mod; } #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_repos(void) { return moduleinit(); } #else PyMODINIT_FUNC initrepos(void) { moduleinit(); } #endif subvertpy_0.11.1.orig/subvertpy/server.py0000644000000000000000000000420215154541453015534 0ustar00# Copyright (C) 2006 Jelmer Vernooij # # 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 """Server backend base classes.""" class ServerBackend(object): """A server backend.""" def open_repository(self, location): raise NotImplementedError(self.open_repository) def generate_random_id(): """Create a UUID for a repository.""" import uuid return str(uuid.uuid4()) class ServerRepositoryBackend(object): def get_uuid(self): raise NotImplementedError(self.get_uuid) def get_latest_revnum(self): raise NotImplementedError(self.get_latest_revnum) def log( self, send_revision, target_path, start_rev, end_rev, changed_paths, strict_node, limit, ): raise NotImplementedError(self.log) def update(self, editor, revnum, target_path, recurse=True): raise NotImplementedError(self.update) def check_path(self, path, revnum): raise NotImplementedError(self.check_path) def stat(self, path, revnum): """Stat a path. Should return a dictionary with the following keys: name, kind, size, has-props, created-rev, created-date, last-author. """ raise NotImplementedError(self.stat) def rev_proplist(self, revnum): raise NotImplementedError(self.rev_proplist) def get_locations(self, path, peg_revnum, revnums): raise NotImplementedError(self.get_locations) subvertpy_0.11.1.orig/subvertpy/stdbool.h0000644000000000000000000000027411723766327015507 0ustar00/** Dummy header file to make some Windows compilers that aren't C99 compliant happy. */ #ifndef _STDBOOL_H_ #define _STDBOOL_H_ typedef char bool; #define true 1 #define false 0 #endif subvertpy_0.11.1.orig/subvertpy/subr.c0000644000000000000000000000643213301340277014773 0ustar00/* * Copyright © 2017 Jelmer Vernooij * -*- 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 #include #include #include #include #include #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 subvertpy_0.11.1.orig/subvertpy/tests/0000755000000000000000000000000011071226625015013 5ustar00subvertpy_0.11.1.orig/subvertpy/util.c0000644000000000000000000007106715154541453015012 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #define BZR_SVN_APR_ERROR_OFFSET (APR_OS_START_USERERR + \ (50 * SVN_ERR_CATEGORY_SIZE)) void PyErr_SetAprStatus(apr_status_t status) { char errmsg[1024]; PyErr_SetString(PyExc_Exception, apr_strerror(status, errmsg, sizeof(errmsg))); } const char *py_object_to_svn_path_or_url(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)) { PyErr_SetString(PyExc_TypeError, "URIs need to be UTF-8 bytestrings or unicode strings"); Py_DECREF(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; } const char *py_object_to_svn_abspath(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)) { PyErr_SetString(PyExc_TypeError, "URIs need to be UTF-8 bytestrings or unicode strings"); Py_DECREF(obj); return NULL; } ret = PyBytes_AsString(obj); ret = apr_pstrdup(pool, ret); Py_DECREF(obj); if (ret == NULL) { return NULL; } if (svn_path_is_url(ret)) { return svn_uri_canonicalize(ret, pool); } else 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_dirent(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_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; } } 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_uri(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_uri_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; } } 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; } PyTypeObject *PyErr_GetSubversionExceptionTypeObject(void) { PyObject *coremod, *excobj; coremod = PyImport_ImportModule("subvertpy"); if (coremod == NULL) { return NULL; } excobj = PyObject_GetAttrString(coremod, "SubversionException"); Py_DECREF(coremod); if (excobj == NULL) { PyErr_BadInternalCall(); return NULL; } return (PyTypeObject *)excobj; } PyTypeObject *PyErr_GetGaiExceptionTypeObject(void) { PyObject *socketmod, *excobj; socketmod = PyImport_ImportModule("socket"); if (socketmod == NULL) { return NULL; } excobj = PyObject_GetAttrString(socketmod, "gaierror"); Py_DECREF(socketmod); if (excobj == NULL) { PyErr_BadInternalCall(); return NULL; } return (PyTypeObject *)excobj; } PyObject *PyErr_NewSubversionException(svn_error_t *error) { PyObject *loc, *child; const char *message; char buf[1024]; if (error->file != NULL) { loc = Py_BuildValue("(si)", error->file, error->line); } else { loc = Py_None; Py_INCREF(loc); } if (error->child != NULL) { PyTypeObject *cls = PyErr_GetSubversionExceptionTypeObject(); PyObject *args = PyErr_NewSubversionException(error->child); child = PyObject_CallObject((PyObject *)cls, args); Py_DECREF(cls); Py_DECREF(args); } else { child = Py_None; Py_INCREF(child); } message = svn_err_best_message(error, buf, sizeof(buf)-1); return Py_BuildValue("(siNN)", message, error->apr_err, child, loc); } void PyErr_SetSubversionException(svn_error_t *error) { PyObject *excval, *excobj; if (error->apr_err < 1000) { PyObject *excval = Py_BuildValue("(iz)", error->apr_err, error->message); PyErr_SetObject(PyExc_OSError, excval); Py_DECREF(excval); return; } if (error->apr_err >= APR_OS_START_SYSERR && error->apr_err < APR_OS_START_SYSERR + APR_OS_ERRSPACE_SIZE) { PyObject *excval = Py_BuildValue("(iz)", error->apr_err - APR_OS_START_SYSERR, error->message); PyErr_SetObject(PyExc_OSError, excval); Py_DECREF(excval); return; } if (error->apr_err >= APR_OS_START_EAIERR && error->apr_err < APR_OS_START_EAIERR + APR_OS_ERRSPACE_SIZE) { excobj = (PyObject *)PyErr_GetGaiExceptionTypeObject(); if (excobj == NULL) return; excval = Py_BuildValue("(is)", error->apr_err - APR_OS_START_EAIERR, error->message); if (excval == NULL) return; PyErr_SetObject(excobj, excval); Py_DECREF(excval); Py_DECREF(excobj); return; } excobj = (PyObject *)PyErr_GetSubversionExceptionTypeObject(); if (excobj == NULL) return; excval = PyErr_NewSubversionException(error); PyErr_SetObject(excobj, excval); Py_DECREF(excval); Py_DECREF(excobj); } PyObject *PyOS_tmpfile(void) { PyObject *tempfile, *tmpfile_fn, *ret; tempfile = PyImport_ImportModule("tempfile"); if (tempfile == NULL) return NULL; tmpfile_fn = PyObject_GetAttrString(tempfile, "TemporaryFile"); Py_DECREF(tempfile); if (tmpfile_fn == NULL) return NULL; ret = PyObject_CallObject(tmpfile_fn, NULL); Py_DECREF(tmpfile_fn); return ret; } void handle_svn_error(svn_error_t *error) { if (error->apr_err == BZR_SVN_APR_ERROR_OFFSET) return; /* Just let Python deal with it */ if (error->apr_err == SVN_ERR_CANCELLED && error->child != NULL && error->child->apr_err == BZR_SVN_APR_ERROR_OFFSET) return; /* Cancelled because of a Python exception, let Python deal with it. */ if (error->apr_err == SVN_ERR_RA_SVN_UNKNOWN_CMD) { /* svnserve doesn't handle the 'failure' command sent back * by the client if one of the editor commands failed. * Rather than bouncing the error sent by the client * (BZR_SVN_APR_ERROR_OFFSET for example), it will send * SVN_ERR_RA_SVN_UNKNOWN_CMD. */ if (PyErr_Occurred() != NULL) return; } if (error->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { PyErr_SetString(PyExc_NotImplementedError, error->message); return; } PyErr_SetSubversionException(error); } bool string_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **ret) { int i; if (l == Py_None) { *ret = NULL; return true; } if (!PyList_Check(l)) { PyErr_Format(PyExc_TypeError, "Expected list of strings, got: %s", l->ob_type->tp_name); return false; } *ret = apr_array_make(pool, PyList_Size(l), sizeof(char *)); if (*ret == NULL) { PyErr_NoMemory(); return false; } for (i = 0; i < PyList_GET_SIZE(l); i++) { PyObject *item = PyList_GET_ITEM(l, i); if (!PyUnicode_Check(item) && !PyBytes_Check(item)) { PyErr_Format(PyExc_TypeError, "Expected list of strings, item was %s", item->ob_type->tp_name); return false; } APR_ARRAY_PUSH(*ret, char *) = py_object_to_svn_string(item, pool); } return true; } bool relpath_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **ret) { int i; const char *relpath; if (l == Py_None) { *ret = NULL; return true; } if (PyUnicode_Check(l) || PyBytes_Check(l)) { *ret = apr_array_make(pool, 1, sizeof(char *)); relpath = py_object_to_svn_relpath(l, pool); if (relpath == NULL) { return false; } APR_ARRAY_PUSH(*ret, const char *) = relpath; } 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); relpath = py_object_to_svn_relpath(item, pool); if (relpath == NULL) { return false; } APR_ARRAY_PUSH(*ret, const char *) = relpath; } } else { PyErr_Format(PyExc_TypeError, "Expected list of strings, got: %s", l->ob_type->tp_name); return false; } return true; } PyObject *prop_hash_to_dict(apr_hash_t *props) { const char *key; apr_hash_index_t *idx; apr_ssize_t klen; svn_string_t *val; apr_pool_t *pool; PyObject *py_props; if (props == NULL) { return PyDict_New(); } pool = Pool(NULL); if (pool == NULL) goto fail_pool; py_props = PyDict_New(); if (py_props == NULL) { goto fail_props; } for (idx = apr_hash_first(pool, props); idx != NULL; idx = apr_hash_next(idx)) { PyObject *py_key, *py_val; apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); if (val == NULL || val->data == NULL) { py_val = Py_None; Py_INCREF(py_val); } else { py_val = PyBytes_FromStringAndSize(val->data, val->len); } if (py_val == NULL) { goto fail_item; } if (key == NULL) { py_key = Py_None; Py_INCREF(py_key); } else { py_key = PyUnicode_FromStringAndSize(key, klen); } if (PyDict_SetItem(py_props, py_key, py_val) != 0) { Py_DECREF(py_key); Py_DECREF(py_val); goto fail_item; } Py_DECREF(py_key); Py_DECREF(py_val); } apr_pool_destroy(pool); return py_props; fail_item: Py_DECREF(py_props); fail_props: apr_pool_destroy(pool); fail_pool: return NULL; } apr_hash_t *prop_dict_to_hash(apr_pool_t *pool, PyObject *py_props) { Py_ssize_t idx = 0; PyObject *k, *v; apr_hash_t *hash_props; svn_string_t *val_string; if (!PyDict_Check(py_props)) { PyErr_SetString(PyExc_TypeError, "props should be dictionary"); return NULL; } hash_props = apr_hash_make(pool); if (hash_props == NULL) { PyErr_NoMemory(); return NULL; } while (PyDict_Next(py_props, &idx, &k, &v)) { char *key, *val; Py_ssize_t val_size; key = py_object_to_svn_string(k, pool); if (key == NULL) { return NULL; } if (PyUnicode_Check(v)) { v = PyUnicode_AsUTF8String(v); } else { Py_INCREF(v); } if (PyBytes_AsStringAndSize(v, &val, &val_size) == -1) { return NULL; } val_string = svn_string_ncreate(val, val_size, pool); Py_DECREF(v); apr_hash_set(hash_props, key, strlen(key), val_string); } return hash_props; } #if PY_MAJOR_VERSION >= 3 #define SOURCEPATH_FORMAT3 "(CNl)" #define SOURCEPATH_FORMAT4 "(CNli)" #else #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) { PyObject *py_changed_paths, *pyval; apr_hash_index_t *idx; const char *key; apr_ssize_t klen; svn_log_changed_path_t *val; if (changed_paths == NULL) { py_changed_paths = Py_None; Py_INCREF(py_changed_paths); } else { py_changed_paths = PyDict_New(); if (py_changed_paths == NULL) { return NULL; } 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, py_copyfrom_path, val->copyfrom_rev, svn_node_unknown); } else { pyval = Py_BuildValue(SOURCEPATH_FORMAT3, val->action, py_copyfrom_path, val->copyfrom_rev); } if (pyval == NULL) { Py_DECREF(py_changed_paths); return NULL; } if (key == NULL) { PyErr_SetString(PyExc_RuntimeError, "path can not be NULL"); Py_DECREF(pyval); Py_DECREF(py_changed_paths); return NULL; } 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); } } return py_changed_paths; } PyObject *pyify_changed_paths2(apr_hash_t *changed_paths, apr_pool_t *pool) { PyObject *py_changed_paths, *pyval; apr_hash_index_t *idx; const char *key; apr_ssize_t klen; svn_log_changed_path2_t *val; if (changed_paths == NULL) { py_changed_paths = Py_None; Py_INCREF(py_changed_paths); } else { py_changed_paths = PyDict_New(); if (py_changed_paths == NULL) { return NULL; } 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); 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); return NULL; } if (key == NULL) { PyErr_SetString(PyExc_RuntimeError, "path can not be NULL"); Py_DECREF(py_changed_paths); Py_DECREF(pyval); return NULL; } 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); } } return py_changed_paths; } bool pyify_log_message(apr_hash_t *changed_paths, const char *author, const char *date, const char *message, bool node_kind, apr_pool_t *pool, PyObject **py_changed_paths, PyObject **revprops) { PyObject *obj; *py_changed_paths = pyify_changed_paths(changed_paths, node_kind, pool); if (*py_changed_paths == NULL) { goto fail; } *revprops = PyDict_New(); if (*revprops == NULL) { goto fail_dict; } if (message != NULL) { obj = PyBytes_FromString(message); PyDict_SetItemString(*revprops, SVN_PROP_REVISION_LOG, obj); Py_DECREF(obj); } if (author != NULL) { obj = PyBytes_FromString(author); PyDict_SetItemString(*revprops, SVN_PROP_REVISION_AUTHOR, obj); Py_DECREF(obj); } if (date != NULL) { obj = PyBytes_FromString(date); PyDict_SetItemString(*revprops, SVN_PROP_REVISION_DATE, obj); Py_DECREF(obj); } return true; fail_dict: Py_DECREF(*py_changed_paths); fail: return false; } svn_error_t *py_svn_log_entry_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) { PyObject *revprops, *py_changed_paths, *ret; PyGILState_STATE state = PyGILState_Ensure(); /* FIXME: Support include node_kind */ py_changed_paths = pyify_changed_paths(log_entry->changed_paths, false, pool); CB_CHECK_PYRETVAL(py_changed_paths); revprops = prop_hash_to_dict(log_entry->revprops); CB_CHECK_PYRETVAL(revprops); ret = PyObject_CallFunction((PyObject *)baton, "OlOb", py_changed_paths, log_entry->revision, revprops, log_entry->has_children); Py_DECREF(py_changed_paths); Py_DECREF(revprops); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } svn_error_t *py_svn_log_wrapper(void *baton, apr_hash_t *changed_paths, svn_revnum_t revision, const char *author, const char *date, const char *message, apr_pool_t *pool) { PyObject *revprops, *py_changed_paths, *ret; PyGILState_STATE state = PyGILState_Ensure(); /* FIXME: Support including node kind */ if (!pyify_log_message(changed_paths, author, date, message, false, pool, &py_changed_paths, &revprops)) { PyGILState_Release(state); return py_svn_error(); } ret = PyObject_CallFunction((PyObject *)baton, "OlO", py_changed_paths, revision, revprops); Py_DECREF(py_changed_paths); Py_DECREF(revprops); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } svn_error_t *py_svn_error() { return svn_error_create(BZR_SVN_APR_ERROR_OFFSET, NULL, "Error occured in python bindings"); } PyObject *wrap_lock(svn_lock_t *lock) { return Py_BuildValue("(zzzbzz)", lock->path, lock->token, lock->owner, lock->comment, lock->is_dav_comment, lock->creation_date, lock->expiration_date); } apr_array_header_t *revnum_list_to_apr_array(apr_pool_t *pool, PyObject *l) { int i; apr_array_header_t *ret; if (l == Py_None) { return NULL; } if (!PyList_Check(l)) { PyErr_SetString(PyExc_TypeError, "expected list with revision numbers"); return NULL; } ret = apr_array_make(pool, PyList_Size(l), sizeof(svn_revnum_t)); if (ret == NULL) { PyErr_NoMemory(); return NULL; } for (i = 0; i < PyList_Size(l); i++) { PyObject *item = PyList_GetItem(l, i); long rev = py_to_svn_revnum(item); if (rev == -1 && PyErr_Occurred()) { return NULL; } APR_ARRAY_PUSH(ret, svn_revnum_t) = rev; } return ret; } static svn_error_t *py_stream_read(void *baton, char *buffer, apr_size_t *length) { PyObject *self = (PyObject *)baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "read", "i", *length); CB_CHECK_PYRETVAL(ret); if (!PyBytes_Check(ret)) { PyErr_SetString(PyExc_TypeError, "Expected stream read function to return bytes"); PyGILState_Release(state); return py_svn_error(); } *length = PyBytes_Size(ret); memcpy(buffer, PyBytes_AsString(ret), *length); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_stream_write(void *baton, const char *data, apr_size_t *len) { PyObject *self = (PyObject *)baton, *ret, *py_data; PyGILState_STATE state = PyGILState_Ensure(); py_data = PyBytes_FromStringAndSize(data, *len); CB_CHECK_PYRETVAL(py_data); ret = PyObject_CallMethod(self, "write", "O", py_data); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_stream_close(void *baton) { PyObject *self = (PyObject *)baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "close", ""); Py_DECREF(self); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } svn_stream_t *new_py_stream(apr_pool_t *pool, PyObject *py) { svn_stream_t *stream; stream = svn_stream_create((void *)py, pool); if (stream == NULL) { PyErr_SetString(PyExc_RuntimeError, "Unable to create a Subversion stream"); return NULL; } Py_INCREF(py); svn_stream_set_read(stream, py_stream_read); svn_stream_set_write(stream, py_stream_write); svn_stream_set_close(stream, py_stream_close); return stream; } svn_error_t *py_cancel_check(void *cancel_baton) { PyGILState_STATE state = PyGILState_Ensure(); if (PyErr_Occurred()) { PyGILState_Release(state); return svn_error_create(SVN_ERR_CANCELLED, py_svn_error(), "Python exception raised"); } PyGILState_Release(state); return NULL; } static apr_hash_t *get_default_config(void) { static bool initialised = false; static apr_pool_t *pool = NULL; static apr_hash_t *default_config = NULL; if (!initialised) { pool = Pool(NULL); RUN_SVN_WITH_POOL(pool, svn_config_get_config(&default_config, NULL, pool)); /* TODO(jelmer): Deal with pool */ initialised = true; } return default_config; } apr_hash_t *config_hash_from_object(PyObject *config, apr_pool_t *pool) { if (config == Py_None) { return get_default_config(); } else { return ((ConfigObject *)config)->config; } } PyObject *py_dirent(const svn_dirent_t *dirent, int dirent_fields) { PyObject *ret, *obj; ret = PyDict_New(); if (ret == NULL) return NULL; if (dirent_fields & SVN_DIRENT_KIND) { #if PY_MAJOR_VERSION < 3 obj = PyInt_FromLong(dirent->kind); #else obj = PyLong_FromLong(dirent->kind); #endif PyDict_SetItemString(ret, "kind", obj); Py_DECREF(obj); } if (dirent_fields & SVN_DIRENT_SIZE) { obj = PyLong_FromLongLong(dirent->size); PyDict_SetItemString(ret, "size", obj); Py_DECREF(obj); } if (dirent_fields & SVN_DIRENT_HAS_PROPS) { obj = PyBool_FromLong(dirent->has_props); PyDict_SetItemString(ret, "has_props", obj); Py_DECREF(obj); } if (dirent_fields & SVN_DIRENT_CREATED_REV) { obj = PyLong_FromLong(dirent->created_rev); PyDict_SetItemString(ret, "created_rev", obj); Py_DECREF(obj); } if (dirent_fields & SVN_DIRENT_TIME) { obj = PyLong_FromLongLong(dirent->time); PyDict_SetItemString(ret, "time", obj); Py_DECREF(obj); } if (dirent_fields & SVN_DIRENT_LAST_AUTHOR) { if (dirent->last_author != NULL) { obj = PyBytes_FromString(dirent->last_author); } else { obj = Py_None; Py_INCREF(obj); } PyDict_SetItemString(ret, "last_author", obj); Py_DECREF(obj); } return ret; } apr_file_t *apr_file_from_object(PyObject *object, apr_pool_t *pool) { apr_status_t status; int fd = -1; apr_file_t *fp = NULL; apr_os_file_t osfile; if ((fd = PyObject_AsFileDescriptor(object)) >= 0) { #ifdef WIN32 osfile = (apr_os_file_t)_get_osfhandle(fd); #else osfile = (apr_os_file_t)fd; #endif } else { PyErr_SetString(PyExc_TypeError, "Unknown type for file variable"); return NULL; } status = apr_os_file_put(&fp, &osfile, APR_FOPEN_WRITE | APR_FOPEN_CREATE, pool); if (status != 0) { PyErr_SetAprStatus(status); return NULL; } return fp; } static void stream_dealloc(PyObject *self) { StreamObject *streamself = (StreamObject *)self; apr_pool_destroy(streamself->pool); PyObject_Del(self); } static PyObject *stream_init(PyTypeObject *type, PyObject *args, PyObject *kwargs) { char *kwnames[] = { NULL }; StreamObject *ret; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwnames)) return NULL; ret = PyObject_New(StreamObject, &Stream_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) return NULL; ret->stream = svn_stream_empty(ret->pool); ret->closed = false; return (PyObject *)ret; } static PyObject *stream_close(StreamObject *self) { if (!self->closed) { svn_stream_close(self->stream); self->closed = true; } Py_RETURN_NONE; } static PyObject *stream_write(StreamObject *self, PyObject *args) { char *buffer; Py_ssize_t len; size_t length; if (!PyArg_ParseTuple(args, "s#", &buffer, &len)) return NULL; if (self->closed) { PyErr_SetString(PyExc_RuntimeError, "unable to write: stream already closed"); return NULL; } length = len; RUN_SVN(svn_stream_write(self->stream, buffer, &length)); return PyLong_FromLong(length); } static PyObject *stream_read_full(StreamObject *self, PyObject *args) { /* TODO(jelmer): Implemented stream_read2 */ PyObject *ret; apr_pool_t *temp_pool; long len = -1; if (!PyArg_ParseTuple(args, "|l", &len)) return NULL; if (self->closed) { return PyBytes_FromString(""); } temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (len != -1) { char *buffer; apr_size_t size = len; buffer = apr_palloc(temp_pool, len); if (buffer == NULL) { PyErr_NoMemory(); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_stream_read_full(self->stream, buffer, &size)); ret = PyBytes_FromStringAndSize(buffer, size); apr_pool_destroy(temp_pool); return ret; } else { svn_string_t *result; RUN_SVN_WITH_POOL(temp_pool, svn_string_from_stream(&result, self->stream, temp_pool, temp_pool)); self->closed = true; ret = PyBytes_FromStringAndSize(result->data, result->len); apr_pool_destroy(temp_pool); return ret; } } static PyMethodDef stream_methods[] = { { "read_full", (PyCFunction)stream_read_full, METH_VARARGS, NULL }, { "read", (PyCFunction)stream_read_full, METH_VARARGS, NULL }, { "write", (PyCFunction)stream_write, METH_VARARGS, NULL }, { "close", (PyCFunction)stream_close, METH_NOARGS, NULL }, { NULL, } }; PyTypeObject Stream_Type = { PyVarObject_HEAD_INIT(NULL, 0) "repos.Stream", /* const char *tp_name; For printing, in format "." */ sizeof(StreamObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ .tp_dealloc = stream_dealloc, /* destructor tp_dealloc; */ .tp_doc = "Byte stream", /* const char *tp_doc; Documentation string */ .tp_methods = stream_methods, /* struct PyMethodDef *tp_methods; */ .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; } subvertpy_0.11.1.orig/subvertpy/util.h0000644000000000000000000001253013776732153015014 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #ifndef _SUBVERTPY_UTIL_H_ #define _SUBVERTPY_UTIL_H_ #include #include /* for svn_stream_t */ #if SVN_VER_MAJOR != 1 #error "only svn 1.x is supported" #endif #ifdef SUBVERTPY_OVERRIDE_SVN_VER_MINOR #define ONLY_SINCE_SVN(maj, min) (SUBVERTPY_OVERRIDE_SVN_VER_MINOR >= (min)) #else #define ONLY_SINCE_SVN(maj, min) (SVN_VER_MINOR >= (min)) #endif #define ONLY_BEFORE_SVN(maj, min) (!(ONLY_SINCE_SVN(maj, min))) #ifdef __GNUC__ #pragma GCC visibility push(hidden) #endif svn_error_t *py_cancel_check(void *cancel_baton); __attribute__((warn_unused_result)) apr_pool_t *Pool(apr_pool_t *parent); void handle_svn_error(svn_error_t *error); bool string_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **); bool relpath_list_to_apr_array(apr_pool_t *pool, PyObject *l, apr_array_header_t **); PyObject *prop_hash_to_dict(apr_hash_t *props); 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); __attribute__((warn_unused_result)) svn_error_t *py_svn_error(void); void PyErr_SetSubversionException(svn_error_t *error); PyTypeObject *PyErr_GetSubversionExceptionTypeObject(void); #define RUN_SVN(cmd) { \ svn_error_t *err; \ PyThreadState *_save; \ _save = PyEval_SaveThread(); \ err = (cmd); \ PyEval_RestoreThread(_save); \ if (err != NULL) { \ handle_svn_error(err); \ svn_error_clear(err); \ return NULL; \ } \ } #define RUN_SVN_WITH_POOL(pool, cmd) { \ svn_error_t *err; \ PyThreadState *_save; \ _save = PyEval_SaveThread(); \ err = (cmd); \ PyEval_RestoreThread(_save); \ if (err != NULL) { \ handle_svn_error(err); \ svn_error_clear(err); \ apr_pool_destroy(pool); \ return NULL; \ } \ } PyObject *wrap_lock(svn_lock_t *lock); apr_array_header_t *revnum_list_to_apr_array(apr_pool_t *pool, PyObject *l); svn_stream_t *new_py_stream(apr_pool_t *pool, PyObject *py); 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( apr_hash_t *changed_paths, const char *author, const char *date, const char *message, bool node_kind, apr_pool_t *pool, PyObject **py_changed_paths, PyObject **revprops); PyObject *pyify_changed_paths2(apr_hash_t *changed_paths2, apr_pool_t *pool); apr_file_t *apr_file_from_object(PyObject *object, apr_pool_t *pool); svn_error_t *py_svn_log_entry_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool); #ifdef __GNUC__ #pragma GCC visibility pop #endif #define CB_CHECK_PYRETVAL(ret) \ if (ret == NULL) { \ PyGILState_Release(state); \ return py_svn_error(); \ } #if SVN_VER_MINOR < 5 typedef enum svn_depth_t { svn_depth_unknown = -2, svn_depth_exclude = -1, svn_depth_empty = 0, svn_depth_files = 1, svn_depth_immediates = 2, svn_depth_infinity = 3 } svn_depth_t; #endif typedef struct { PyObject_HEAD apr_hash_t *config; apr_pool_t *pool; } ConfigObject; typedef struct { PyObject_HEAD svn_stream_t *stream; apr_pool_t *pool; bool closed; } StreamObject; extern PyTypeObject Stream_Type; 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); 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 #else #define PyRepr_FromFormat PyString_FromFormat #endif #if PY_MAJOR_VERSION >= 3 #define py_from_svn_revnum PyLong_FromLong #define py_to_svn_revnum PyLong_AsLong #else #define py_from_svn_revnum PyInt_FromLong #define py_to_svn_revnum PyInt_AsLong #endif #endif /* _SUBVERTPY_UTIL_H_ */ subvertpy_0.11.1.orig/subvertpy/wc.c0000644000000000000000000017305715154541453014450 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 */ #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "editor.h" #include "wc.h" #ifndef T_BOOL #define T_BOOL T_BYTE #endif typedef struct { PyObject_HEAD svn_lock_t lock; apr_pool_t *pool; } LockObject; typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_wc_context_t *context; } ContextObject; static PyTypeObject Context_Type; typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_wc_committed_queue_t *queue; } CommittedQueueObject; extern PyTypeObject Lock_Type; svn_wc_committed_queue_t *PyObject_GetCommittedQueue(PyObject *obj) { return ((CommittedQueueObject *)obj)->queue; } 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; } 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; } 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; } 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, }; 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, }; /** * Get runtime libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ static PyObject *version(PyObject *self) { const svn_version_t *ver = svn_wc_version(); return Py_BuildValue("(iiis)", ver->major, ver->minor, ver->patch, ver->tag); } SVN_VERSION_DEFINE(svn_api_version); /** * Get compile-time libsvn_wc version information. * * :return: tuple with major, minor, patch version number and tag. */ 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); } void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool) { PyObject *func = baton, *ret; if (func == Py_None) 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); } } bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret) { PyObject *key, *val; Py_ssize_t idx; if (dict == Py_None) { *ret = NULL; return true; } if (!PyDict_Check(dict)) { PyErr_SetString(PyExc_TypeError, "Expected dictionary with property changes"); return false; } *ret = apr_array_make(pool, PyDict_Size(dict), sizeof(char *)); while (PyDict_Next(dict, &idx, &key, &val)) { svn_prop_t *prop = apr_palloc(pool, sizeof(svn_prop_t)); prop->name = py_object_to_svn_string(key, pool); if (prop->name == NULL) { return false; } if (val == Py_None) { prop->value = NULL; } else { if (!PyBytes_Check(val)) { PyErr_SetString(PyExc_TypeError, "property values should be bytes"); return false; } prop->value = svn_string_ncreate(PyBytes_AsString(val), PyBytes_Size(val), pool); } APR_ARRAY_PUSH(*ret, svn_prop_t *) = prop; } return true; } 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; } 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; } static PyObject *get_actual_target(PyObject *self, PyObject *args) { const char *path; const char *anchor = NULL, *target = NULL; apr_pool_t *temp_pool; PyObject *ret, *py_path; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; 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_actual_target(path, &anchor, &target, temp_pool)); ret = Py_BuildValue("(ss)", anchor, target); apr_pool_destroy(temp_pool); return ret; } /** * 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 *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; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|zb", kwnames, &py_wc_path, &trail_url, &committed)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) { return NULL; } 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; } static PyObject *is_normal_prop(PyObject *self, PyObject *args) { char *name; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; return PyBool_FromLong(svn_wc_is_normal_prop(name)); } static PyObject *is_adm_dir(PyObject *self, PyObject *args) { const char *name; PyObject *py_name; apr_pool_t *pool; svn_boolean_t ret; if (!PyArg_ParseTuple(args, "O", &py_name)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; name = py_object_to_svn_string(py_name, pool); if (name == NULL) { return NULL; } ret = svn_wc_is_adm_dir(name, pool); apr_pool_destroy(pool); return PyBool_FromLong(ret); } static PyObject *is_wc_prop(PyObject *self, PyObject *args) { char *name; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; return PyBool_FromLong(svn_wc_is_wc_prop(name)); } static PyObject *is_entry_prop(PyObject *self, PyObject *args) { char *name; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; return PyBool_FromLong(svn_wc_is_entry_prop(name)); } 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 *set_adm_dir(PyObject *self, PyObject *args) { apr_pool_t *temp_pool; char *name; PyObject *py_name; if (!PyArg_ParseTuple(args, "O", &py_name)) return 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 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; if (!PyArg_ParseTuple(args, "O", &py_path)) 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; } 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 *get_pristine_contents(PyObject *self, PyObject *args) { const char *path; apr_pool_t *temp_pool; PyObject *py_path; StreamObject *ret; apr_pool_t *stream_pool; svn_stream_t *stream; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; stream_pool = Pool(NULL); if (stream_pool == NULL) return NULL; temp_pool = Pool(stream_pool); if (temp_pool == NULL) { apr_pool_destroy(stream_pool); 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(stream_pool, svn_wc_get_pristine_contents(&stream, path, stream_pool, temp_pool)); apr_pool_destroy(temp_pool); if (stream == NULL) { apr_pool_destroy(stream_pool); Py_RETURN_NONE; } ret = PyObject_New(StreamObject, &Stream_Type); if (ret == NULL) return NULL; ret->pool = stream_pool; ret->closed = FALSE; ret->stream = stream; return (PyObject *)ret; } static PyObject *ensure_adm(PyObject *self, PyObject *args, PyObject *kwargs) { const char *path; char *uuid, *url = NULL; PyObject *py_path; 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_ParseTupleAndKeywords(args, kwargs, "Oss|sli", kwnames, &py_path, &uuid, &url, &repos, &rev, &depth)) return NULL; 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_ensure_adm3(path, uuid, url, repos, rev, depth, pool)); apr_pool_destroy(pool); Py_RETURN_NONE; } static PyObject *check_wc(PyObject *self, PyObject *args) { const char *path; apr_pool_t *pool; int wc_format; PyObject *py_path; if (!PyArg_ParseTuple(args, "O", &py_path)) return NULL; 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_check_wc(path, &wc_format, pool)); apr_pool_destroy(pool); return PyLong_FromLong(wc_format); } static PyObject *cleanup_wc(PyObject *self, 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; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|z", kwnames, &py_path, &diff3_cmd)) return NULL; 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_cleanup2(path, diff3_cmd, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *match_ignore_list(PyObject *self, PyObject *args) { char *str; PyObject *py_list; apr_array_header_t *list; apr_pool_t *temp_pool; svn_boolean_t ret; if (!PyArg_ParseTuple(args, "sO", &str, &py_list)) return NULL; temp_pool = Pool(NULL); if (!string_list_to_apr_array(temp_pool, py_list, &list)) { apr_pool_destroy(temp_pool); return NULL; } ret = svn_wc_match_ignore_list(str, list, temp_pool); apr_pool_destroy(temp_pool); return PyBool_FromLong(ret); } 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) { apr_pool_destroy(((CommittedQueueObject *)self)->pool); PyObject_Del(self); } static PyObject *committed_queue_repr(PyObject *self) { CommittedQueueObject *cqobj = (CommittedQueueObject *)self; return PyRepr_FromFormat("", cqobj->queue); } static PyObject *committed_queue_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) { CommittedQueueObject *ret; char *kwnames[] = { NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwnames)) return NULL; ret = PyObject_New(CommittedQueueObject, &CommittedQueue_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) return NULL; ret->queue = svn_wc_committed_queue_create(ret->pool); if (ret->queue == NULL) { PyObject_Del(ret); PyErr_NoMemory(); return NULL; } return (PyObject *)ret; } static PyObject *committed_queue_queue(CommittedQueueObject *self, PyObject *args, PyObject *kwargs) { 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_array_header_t *wcprop_changes; Py_ssize_t md5_digest_len, sha1_digest_len; svn_wc_context_t *context = NULL; char *kwnames[] = { "path", "adm", "recurse", "wcprop_changes", "remove_lock", "remove_changelist", "md5_digest", "sha1_digest", NULL }; 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; if (!py_dict_to_wcprop_changes(py_wcprop_changes, self->pool, &wcprop_changes)) { return NULL; } path = py_object_to_svn_abspath(py_path, self->pool); if (path == NULL) { return NULL; } if (md5_digest != NULL) { if (md5_digest_len != APR_MD5_DIGESTSIZE) { PyErr_SetString(PyExc_ValueError, "Invalid size for md5 digest"); return NULL; } } if (sha1_digest != NULL) { if (sha1_digest_len != APR_SHA1_DIGESTSIZE) { PyErr_SetString(PyExc_ValueError, "Invalid size for sha1 digest"); return NULL; } } if (PyObject_IsInstance(admobj, (PyObject *)&Context_Type)) { context = ((ContextObject*)admobj)->context; } else { PyErr_SetString(PyExc_TypeError, "Second arguments needs to be Context"); return NULL; } if (adm != NULL) { { svn_checksum_t *svn_checksum_p; 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( svn_wc_queue_committed2(self->queue, path, adm, recurse?TRUE:FALSE, wcprop_changes, remove_lock?TRUE:FALSE, remove_changelist?TRUE:FALSE, svn_checksum_p, self->pool)); } } else { svn_checksum_t *svn_checksum_p; 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)); } Py_RETURN_NONE; } static PyMethodDef committed_queue_methods[] = { { "queue", (PyCFunction)committed_queue_queue, METH_VARARGS|METH_KEYWORDS, "S.queue(path, adm, recurse=False, wcprop_changes=[], remove_lock=False, remove_changelist=False, digest=None)" }, { NULL } }; PyTypeObject CommittedQueue_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wc.CommittedQueue", /* const char *tp_name; For printing, in format "." */ sizeof(CommittedQueueObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ committed_queue_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ committed_queue_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; */ 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; */ "Committed queue", /* 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 */ committed_queue_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; */ committed_queue_init, /* newfunc tp_new; */ }; svn_lock_t *py_object_to_svn_lock(PyObject *py_lock, apr_pool_t *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; } static PyTypeObject Context_Type; 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; 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 *py_wc_context_check_wc(PyObject *self, PyObject *args) { 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, "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_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 *py_wc_context_text_modified_p2(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 modified; 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_text_modified_p2(&modified, wc_context, path, FALSE, pool)); apr_pool_destroy(pool); return PyBool_FromLong(modified); } static PyObject *py_wc_context_props_modified_p2(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 modified; 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_props_modified_p2(&modified, wc_context, path, pool)); apr_pool_destroy(pool); return PyBool_FromLong(modified); } static PyObject *py_wc_context_conflicted(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 text_conflicted, props_conflicted, tree_conflicted; 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_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) { 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 *cancel = Py_None; 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, &cancel, ¬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, &diff3_cmd, &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 }; PyObject *py_local_abspath; char *url; char *repos_root_url; char *repos_uuid; int revnum; int depth = svn_depth_infinity; apr_pool_t *pool; const char *local_abspath; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Osssi|i", kwnames, &py_local_abspath, &url, &repos_root_url, &repos_uuid, &revnum, &depth)) { return NULL; } pool = Pool(NULL); local_abspath = py_object_to_svn_abspath(py_local_abspath, pool); if (local_abspath == NULL) { apr_pool_destroy(pool); return 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; } typedef struct { PyObject_VAR_HEAD apr_pool_t *pool; svn_wc_status3_t status; } Status3Object; static void status_dealloc(PyObject *self) { apr_pool_t *pool = ((Status3Object *)self)->pool; if (pool != NULL) apr_pool_destroy(pool); PyObject_Del(self); } 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 } }; static PyTypeObject Status3_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wc.Status", /* const char *tp_name; For printing, in format "." */ sizeof(Status3Object), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ status_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ 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|ibbbO", 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; } } 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)); apr_pool_destroy(pool); Py_RETURN_NONE; } 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) { ContextObject *context_obj = (ContextObject *)self; svn_wc_context_destroy(context_obj->context); apr_pool_destroy(context_obj->pool); PyObject_Del(self); } static PyObject *context_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) { ContextObject *ret; char *kwnames[] = { NULL }; svn_config_t *config = NULL; // TODO(jelmer): Support passing in config if (!PyArg_ParseTupleAndKeywords(args, kwargs, "", kwnames)) return NULL; ret = PyObject_New(ContextObject, &Context_Type); if (ret == NULL) return NULL; 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 PyTypeObject Context_Type = { PyVarObject_HEAD_INIT(NULL, 0) "wc.Context", /* const char *tp_name; For printing, in format "." */ sizeof(ContextObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ context_dealloc, /* destructor tp_dealloc; */ #if PY_MAJOR_VERSION >= 3 0, /* Py_ssize_t tp_vectorcall_offset; */ #else NULL, /* printfunc tp_print; */ #endif 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; */ "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; */ }; static void lock_dealloc(PyObject *self) { LockObject *lockself = (LockObject *)self; apr_pool_destroy(lockself->pool); PyObject_Del(self); } static PyObject *lock_init(PyTypeObject *type, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "token", NULL }; LockObject *ret; char *token = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|z", kwnames, &token)) return NULL; ret = PyObject_New(LockObject, &Lock_Type); if (ret == NULL) return NULL; 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); } return (PyObject *)ret; } 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 int lock_set_path(PyObject *self, PyObject *value, void *closure) { LockObject *lock_obj = (LockObject *)self; char *path; path = PyBytes_AsString(value); if (path == NULL) { return -1; } lock_obj->lock.path = py_object_to_svn_string(value, lock_obj->pool); return 0; } static PyObject *lock_get_token(PyObject *self, void *closure) { LockObject *lock_obj = (LockObject *)self; if (lock_obj->lock.token == NULL) { Py_RETURN_NONE; } return PyBytes_FromString(lock_obj->lock.token); } 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 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 "." */ 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 * moduleinit(void) { PyObject *mod; if (PyType_Ready(&Context_Type) < 0) return NULL; if (PyType_Ready(&Editor_Type) < 0) return NULL; if (PyType_Ready(&FileEditor_Type) < 0) return NULL; if (PyType_Ready(&DirectoryEditor_Type) < 0) return NULL; if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0) return NULL; if (PyType_Ready(&Stream_Type) < 0) return NULL; if (PyType_Ready(&CommittedQueue_Type) < 0) return NULL; if (PyType_Ready(&Status3_Type) < 0) return NULL; if (PyType_Ready(&Lock_Type) < 0) return NULL; apr_initialize(); #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "wc", /* m_name */ "Working Copies", /* m_doc */ -1, /* m_size */ wc_methods, /* m_methods */ NULL, /* m_reload */ NULL, /* m_traverse */ NULL, /* m_clear*/ NULL, /* m_free */ }; mod = PyModule_Create(&moduledef); #else mod = Py_InitModule3("wc", wc_methods, "Working Copies"); #endif if (mod == NULL) return NULL; PyModule_AddIntConstant(mod, "SCHEDULE_NORMAL", 0); PyModule_AddIntConstant(mod, "SCHEDULE_ADD", 1); PyModule_AddIntConstant(mod, "SCHEDULE_DELETE", 2); PyModule_AddIntConstant(mod, "SCHEDULE_REPLACE", 3); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_POSTPONE", svn_wc_conflict_choose_postpone); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_BASE", svn_wc_conflict_choose_base); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_THEIRS_FULL", svn_wc_conflict_choose_theirs_full); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MINE_FULL", svn_wc_conflict_choose_mine_full); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_THEIRS_CONFLICT", svn_wc_conflict_choose_theirs_conflict); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MINE_CONFLICT", svn_wc_conflict_choose_mine_conflict); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MERGED", svn_wc_conflict_choose_merged); PyModule_AddIntConstant(mod, "STATUS_NONE", svn_wc_status_none); PyModule_AddIntConstant(mod, "STATUS_UNVERSIONED", svn_wc_status_unversioned); PyModule_AddIntConstant(mod, "STATUS_NORMAL", svn_wc_status_normal); PyModule_AddIntConstant(mod, "STATUS_ADDED", svn_wc_status_added); PyModule_AddIntConstant(mod, "STATUS_MISSING", svn_wc_status_missing); PyModule_AddIntConstant(mod, "STATUS_DELETED", svn_wc_status_deleted); PyModule_AddIntConstant(mod, "STATUS_REPLACED", svn_wc_status_replaced); PyModule_AddIntConstant(mod, "STATUS_MODIFIED", svn_wc_status_modified); PyModule_AddIntConstant(mod, "STATUS_MERGED", svn_wc_status_merged); PyModule_AddIntConstant(mod, "STATUS_CONFLICTED", svn_wc_status_conflicted); PyModule_AddIntConstant(mod, "STATUS_IGNORED", svn_wc_status_ignored); PyModule_AddIntConstant(mod, "STATUS_OBSTRUCTED", svn_wc_status_obstructed); PyModule_AddIntConstant(mod, "STATUS_EXTERNAL", svn_wc_status_external); PyModule_AddIntConstant(mod, "STATUS_INCOMPLETE", svn_wc_status_incomplete); PyModule_AddIntConstant(mod, "TRANSLATE_FROM_NF", SVN_WC_TRANSLATE_FROM_NF); PyModule_AddIntConstant(mod, "TRANSLATE_TO_NF", SVN_WC_TRANSLATE_TO_NF); PyModule_AddIntConstant(mod, "TRANSLATE_FORCE_EOL_REPAIR", SVN_WC_TRANSLATE_FORCE_EOL_REPAIR); PyModule_AddIntConstant(mod, "TRANSLATE_NO_OUTPUT_CLEANUP", SVN_WC_TRANSLATE_NO_OUTPUT_CLEANUP); PyModule_AddIntConstant(mod, "TRANSLATE_FORCE_COPY", SVN_WC_TRANSLATE_FORCE_COPY); PyModule_AddIntConstant(mod, "TRANSLATE_USE_GLOBAL_TMP", SVN_WC_TRANSLATE_USE_GLOBAL_TMP); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_POSTPONE", svn_wc_conflict_choose_postpone); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_BASE", svn_wc_conflict_choose_base); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_THEIRS_FULL", svn_wc_conflict_choose_theirs_full); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MINE_FULL", svn_wc_conflict_choose_mine_full); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_THEIRS_CONFLICT", svn_wc_conflict_choose_theirs_conflict); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MINE_CONFLICT", svn_wc_conflict_choose_mine_conflict); PyModule_AddIntConstant(mod, "CONFLICT_CHOOSE_MERGED", svn_wc_conflict_choose_merged); PyModule_AddObject(mod, "Lock", (PyObject *)&Lock_Type); Py_INCREF(&Lock_Type); PyModule_AddObject(mod, "CommittedQueue", (PyObject *)&CommittedQueue_Type); Py_INCREF(&CommittedQueue_Type); PyModule_AddObject(mod, "Context", (PyObject *)&Context_Type); Py_INCREF(&Context_Type); return mod; } #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit_wc(void) { return moduleinit(); } #else PyMODINIT_FUNC initwc(void) { moduleinit(); } #endif subvertpy_0.11.1.orig/subvertpy/wc.h0000644000000000000000000000333313776732153014451 0ustar00/* * Copyright © 2008 Jelmer Vernooij * -*- 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 WARWCNTY; 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 */ #ifndef _BZR_SVN_WC_H_ #define _BZR_SVN_WC_H_ #ifdef __GNUC__ #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); extern const svn_ra_reporter3_t py_ra_reporter3; extern const svn_ra_reporter2_t py_ra_reporter2; 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_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); #ifdef __GNUC__ #pragma GCC visibility pop #endif #endif /* _BZR_SVN_WC_H_ */ subvertpy_0.11.1.orig/subvertpy/tests/__init__.py0000644000000000000000000003475215154541453017144 0ustar00# Copyright (C) 2006-2008 Jelmer Vernooij # 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 """Tests for subvertpy.""" __author__ = "Jelmer Vernooij " __docformat__ = "restructuredText" from io import BytesIO import os import shutil import stat import sys import tempfile import unittest try: import urlparse except ImportError: import urllib.parse as urlparse try: from urllib import pathname2url except ImportError: from urllib.request import pathname2url from subvertpy import ( client, delta, properties, ra, repos, ) from subvertpy.ra import ( Auth, RemoteAccess, ) def rmtree_with_readonly(path): """Simple wrapper for shutil.rmtree that can remove read-only files. In Windows a read-only file cannot be removed, and shutil.rmtree fails. """ def force_rm_handle(remove_path, path, excinfo): os.chmod( path, os.stat(path).st_mode | stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH ) remove_path(path) shutil.rmtree(path, onerror=force_rm_handle) class TestCase(unittest.TestCase): """Base test case. :note: Adds assertIsInstance and assertIs. """ def assertIsInstance(self, obj, kls, msg=None): """Fail if obj is not an instance of kls""" if not isinstance(obj, kls): if msg is None: msg = "%r is an instance of %s rather than %s" % ( obj, obj.__class__, kls, ) self.fail(msg) def assertIs(self, left, right, message=None): if left is not right: if message is not None: raise AssertionError(message) else: raise AssertionError("%r is not %r." % (left, right)) class TestCaseInTempDir(TestCase): """Test case that runs in a temporary directory.""" def setUp(self): TestCase.setUp(self) self._oldcwd = os.getcwd() self.test_dir = tempfile.mkdtemp() os.chdir(self.test_dir) def tearDown(self): TestCase.tearDown(self) os.chdir(self._oldcwd) rmtree_with_readonly(self.test_dir) class TestFileEditor(object): """Simple file editor wrapper that doesn't require closing.""" def __init__(self, file): self.file = file self.is_closed = False def change_prop(self, name, value): self.file.change_prop(name, value) def modify(self, contents=None): if contents is None: contents = os.urandom(100) txdelta = self.file.apply_textdelta() delta.send_stream(BytesIO(contents), txdelta) def close(self): assert not self.is_closed self.is_closed = True self.file.close() class TestDirEditor(object): """Simple dir editor wrapper that doesn't require closing.""" def __init__(self, dir, baseurl, revnum): self.dir = dir self.baseurl = baseurl self.revnum = revnum self.is_closed = False self.children = [] def close_children(self): for c in reversed(self.children): if not c.is_closed: c.close() def close(self): assert not self.is_closed self.is_closed = True self.close_children() self.dir.close() def change_prop(self, name, value): self.close_children() self.dir.change_prop(name, value) def open_dir(self, path): self.close_children() child = TestDirEditor( self.dir.open_directory(path, -1), self.baseurl, self.revnum ) self.children.append(child) return child def open_file(self, path): self.close_children() child = TestFileEditor(self.dir.open_file(path, -1)) self.children.append(child) return child def add_dir(self, path, copyfrom_path=None, copyfrom_rev=-1): self.close_children() if copyfrom_path is not None: copyfrom_path = urlparse.urljoin(self.baseurl + "/", copyfrom_path) if copyfrom_path is not None and copyfrom_rev == -1: copyfrom_rev = self.revnum assert (copyfrom_path is None and copyfrom_rev == -1) or ( copyfrom_path is not None and copyfrom_rev > -1 ) child = TestDirEditor( self.dir.add_directory(path, copyfrom_path, copyfrom_rev), self.baseurl, self.revnum, ) self.children.append(child) return child def add_file(self, path, copyfrom_path=None, copyfrom_rev=-1): self.close_children() if copyfrom_path is not None: copyfrom_path = urlparse.urljoin(self.baseurl + "/", copyfrom_path) if copyfrom_path is not None and copyfrom_rev == -1: copyfrom_rev = self.revnum child = TestFileEditor(self.dir.add_file(path, copyfrom_path, copyfrom_rev)) self.children.append(child) return child def delete(self, path): self.close_children() self.dir.delete_entry(path) class TestCommitEditor(TestDirEditor): """Simple commit editor wrapper.""" def __init__(self, editor, baseurl, revnum): self.editor = editor TestDirEditor.__init__(self, self.editor.open_root(), baseurl, revnum) def close(self): 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 repositories.""" def _init_client(self): self.client_ctx = client.Client() self.client_ctx.auth = Auth( [ ra.get_simple_provider(), ra.get_username_provider(), ra.get_ssl_client_cert_file_provider(), ra.get_ssl_client_cert_pw_file_provider(), ra.get_ssl_server_trust_file_provider(), ] ) self.client_ctx.log_msg_func = self.log_message_func # self.client_ctx.notify_func = lambda err: mutter("Error: %s" % err) def setUp(self): super(SubversionTestCase, self).setUp() self._init_client() def tearDown(self): del self.client_ctx super(SubversionTestCase, self).tearDown() def log_message_func(self, items): return self.next_message def make_repository(self, relpath, allow_revprop_changes=True): """Create a repository. :return: Handle to the repository. """ abspath = os.path.join(self.test_dir, relpath) repos.create(abspath) if allow_revprop_changes: if sys.platform == "win32": revprop_hook = os.path.join(abspath, "hooks", "pre-revprop-change.bat") with open(revprop_hook, "w") as f: f.write("exit 0\n") else: revprop_hook = os.path.join(abspath, "hooks", "pre-revprop-change") with open(revprop_hook, "w") as f: f.write("#!/bin/sh\n") os.chmod(revprop_hook, os.stat(revprop_hook).st_mode | 0o111) if sys.platform == "win32": return "file:%s" % pathname2url(abspath) else: return "file://%s" % abspath def make_checkout(self, repos_url, relpath): """Create a new checkout.""" self.client_ctx.checkout(repos_url, relpath, "HEAD") def client_set_prop(self, path, name, value): """Set a property on a local file or directory.""" if value is None: value = "" self.client_ctx.propset(name, value, path, False, True) def client_get_prop(self, path, name, revnum=None, recursive=False): """Retrieve a property from a local or remote file or directory.""" if revnum is None: rev = "WORKING" else: rev = revnum ret = self.client_ctx.propget(name, path, rev, rev, recursive) if recursive: return ret else: return next(iter(ret.values())) def client_get_revprop(self, url, revnum, name): """Get the revision property. :param url: URL of the repository :param revnum: Revision number :param name: Property name :return: Revision property value """ r = ra.RemoteAccess(url) return r.rev_proplist(revnum)[name] def client_set_revprop(self, url, revnum, name, value): """Set a revision property on a repository. :param url: URL of the repository :param revnum: Revision number of the revision :param name: Name of the property :param value: Value of the property, None to remove """ r = ra.RemoteAccess(url, auth=Auth([ra.get_username_provider()])) r.change_rev_prop(revnum, name, value) def client_resolve(self, path, choice, depth=0): """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. :param dir: List of paths to commit. """ olddir = os.path.abspath(".") self.next_message = message os.chdir(dir) info = [] def add_info(*args): info.append(args) self.client_ctx.commit(["."], recursive, False, callback=add_info) os.chdir(olddir) assert len(info) == 1 return info[0] def client_add(self, relpath, recursive=True): """Add specified files to working copy. :param relpath: Path to the files to add. """ self.client_ctx.add(relpath, recursive, False, False) def client_log(self, url, start_revnum, stop_revnum): """Fetch the log :param url: URL to log :param start_revnum: Start revision of the range to log over :param start_revnum: Stop revision of the range to log over :return: Dictionary """ r = ra.RemoteAccess(url) ret = {} def rcvr(orig_paths, rev, revprops, has_children=None): ret[rev] = ( orig_paths, 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, revprops=[ properties.PROP_REVISION_AUTHOR, properties.PROP_REVISION_DATE, properties.PROP_REVISION_LOG, ], ) return ret def client_delete(self, relpath): """Remove specified files from working copy. :param relpath: Path to the files to remove. """ self.client_ctx.delete([relpath], True) def client_copy(self, oldpath, newpath, revnum=None): """Copy file in working copy. :param oldpath: Relative path to original file. :param newpath: Relative path to new file. """ if revnum is None: rev = "HEAD" else: rev = revnum self.client_ctx.copy(oldpath, newpath, rev) def client_update(self, path): """Update path. :param path: Path """ self.client_ctx.update([path], "HEAD", True) def build_tree(self, files): """Create a directory tree. :param files: Dictionary with filenames as keys, contents as values. None as value indicates a directory. """ for name, content in files.items(): if content is None: try: os.makedirs(name) except OSError: pass else: try: os.makedirs(os.path.dirname(name)) except OSError: pass with open(name, "wb") as f: f.write(content) def make_client(self, repospath, clientpath, allow_revprop_changes=True): """Create a repository and a checkout. Return the checkout. :param repospath: Optional relpath to check out if not the full repository. :param clientpath: Path to checkout :return: Repository URL. """ repos_url = self.make_repository( repospath, allow_revprop_changes=allow_revprop_changes ) self.make_checkout(repos_url, clientpath) return repos_url def open_fs(self, relpath): """Open a fs. :return: FS. """ return repos.Repository(relpath).fs() def get_commit_editor(self, url, message="Test commit"): """Obtain a commit editor. :param url: URL to connect to :param message: Commit message :return: Commit editor object """ ra_ctx = RemoteAccess( url.encode("utf-8"), auth=Auth([ra.get_username_provider()]) ) revnum = ra_ctx.get_latest_revnum() return TestCommitEditor( ra_ctx.get_commit_editor({"svn:log": message}), ra_ctx.url, revnum ) def test_suite(): names = [ "client", "core", "delta", "marshall", "properties", "ra", "ra_svn", "repos", "server", "subr", "wc", ] module_names = ["subvertpy.tests.test_" + name for name in names] result = unittest.TestSuite() loader = unittest.TestLoader() suite = loader.loadTestsFromNames(module_names) result.addTests(suite) return result subvertpy_0.11.1.orig/subvertpy/tests/test_client.py0000644000000000000000000006530515154541453017720 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion client library tests.""" from datetime import datetime, timedelta import os from io import BytesIO import shutil import tempfile from subvertpy import ( SubversionException, client, ra, ) from subvertpy.tests import ( SubversionTestCase, TestCase, ) class VersionTest(TestCase): def test_version_length(self): self.assertEqual(4, len(client.version())) def test_api_version_length(self): self.assertEqual(4, len(client.api_version())) def test_api_version_later_same(self): self.assertTrue(client.api_version() <= client.version()) class TestClient(SubversionTestCase): def setUp(self): super(TestClient, self).setUp() self.repos_url = self.make_client("d", "dc") self.client = client.Client(auth=ra.Auth([ra.get_username_provider()])) def tearDown(self): del self.client super(TestClient, self).tearDown() def test_add(self): self.build_tree({"dc/foo": None}) self.client.add("dc/foo") def test_commit(self): self.build_tree({"dc/foo": None}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Amessage" self.client.commit(["dc"]) r = ra.RemoteAccess(self.repos_url) revprops = r.rev_proplist(1) self.assertEqual(b"Amessage", revprops["svn:log"]) def test_commit_start(self): self.build_tree({"dc/foo": None}) self.client = client.Client( auth=ra.Auth([ra.get_username_provider()]), log_msg_func=lambda c: "Bmessage", ) self.client.add("dc/foo") self.client.commit(["dc"]) r = ra.RemoteAccess(self.repos_url) revprops = r.rev_proplist(1) self.assertEqual(b"Bmessage", revprops["svn:log"]) def test_mkdir(self): self.client.mkdir(["dc/foo"]) self.client.mkdir("dc/bar") self.client.mkdir("dc/bla", revprops={"svn:log": "foo"}) def test_export(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.commit(["dc"]) self.client.export(self.repos_url, "de") self.assertEqual(["foo"], os.listdir("de")) def test_export_new_option(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.commit(["dc"]) self.client.export( self.repos_url, "de", ignore_externals=True, ignore_keywords=True ) self.assertEqual(["foo"], os.listdir("de")) def test_set_config(self): config = client.get_config() self.client.config = config def test_set_config_none(self): self.client.config = None def test_get_config(self): self.assertIsInstance(client.get_config(), client.Config) try: base_dir = tempfile.mkdtemp() base_dir_basename = os.path.basename(base_dir) svn_cfg_dir = os.path.join(base_dir, ".subversion") os.mkdir(svn_cfg_dir) with open(os.path.join(svn_cfg_dir, "config"), "w") as svn_cfg: svn_cfg.write("[miscellany]\n") svn_cfg.write("global-ignores = %s" % base_dir_basename) config = client.get_config(svn_cfg_dir) self.assertIsInstance(config, client.Config) ignores = config.get_default_ignores() self.assertTrue( base_dir_basename.encode("utf-8") in ignores, "no %r in %r" % (base_dir_basename, ignores), ) finally: shutil.rmtree(base_dir) def test_diff(self): dc = self.get_commit_editor(self.repos_url) f = dc.add_file("foo") f.modify(b"foo1") dc.close() dc = self.get_commit_editor(self.repos_url) f = dc.open_file("foo") f.modify(b"foo2") dc.close() (outf, errf) = self.client.diff(1, 2, self.repos_url, self.repos_url) self.addCleanup(outf.close) self.addCleanup(errf.close) self.assertEqual( b"""Index: foo =================================================================== --- foo\t(revision 1) +++ foo\t(revision 2) @@ -1 +1 @@ -foo1 \\ No newline at end of file +foo2 \\ No newline at end of file """.splitlines(), outf.read().splitlines(), ) self.assertEqual(b"", errf.read()) def assertCatEquals(self, value, revision=None): io = BytesIO() self.client.cat("dc/foo", io, revision) self.assertEqual(value, io.getvalue()) def test_cat(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.assertCatEquals(b"bla") self.build_tree({"dc/foo": b"blabla"}) self.client.commit(["dc"]) self.assertCatEquals(b"blabla") self.assertCatEquals(b"bla", revision=1) self.assertCatEquals(b"blabla", revision=2) def assertLogEntryChangedPathsEquals(self, expected, entry): changed_paths = entry["changed_paths"] self.assertIsInstance(changed_paths, dict) self.assertEqual(sorted(expected), sorted(changed_paths.keys())) def assertLogEntryMessageEquals(self, expected, entry): self.assertEqual(expected, entry["revprops"]["svn:log"]) def assertLogEntryDateAlmostEquals(self, expected, entry, delta): actual = datetime.strptime( entry["revprops"]["svn:date"].decode("utf-8"), "%Y-%m-%dT%H:%M:%S.%fZ" ) self.assertTrue((actual - expected) < delta) def test_log(self): log_entries = [] commit_msg_1 = b"Commit" commit_msg_2 = b"Commit 2" delta = timedelta(hours=1) def cb(changed_paths, revision, revprops, has_children=False): log_entries.append( { "changed_paths": changed_paths, "revision": revision, "revprops": revprops, "has_children": has_children, } ) self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") 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", 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"]) self.assertLogEntryMessageEquals(commit_msg_1, log_entries[0]) self.assertLogEntryDateAlmostEquals(commit_1_dt, log_entries[0], delta) self.build_tree( { "dc/foo": b"blabla", "dc/bar": b"blablabla", } ) self.client.add("dc/bar") self.client.log_msg_func = lambda c: commit_msg_2 self.client.commit(["dc"]) commit_2_dt = datetime.utcnow() log_entries = [] 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"]) self.assertLogEntryMessageEquals(commit_msg_2, log_entries[0]) self.assertLogEntryDateAlmostEquals(commit_2_dt, log_entries[0], delta) self.assertLogEntryChangedPathsEquals(["/foo"], log_entries[1]) self.assertEqual(1, log_entries[1]["revision"]) self.assertLogEntryMessageEquals(commit_msg_1, log_entries[1]) self.assertLogEntryDateAlmostEquals(commit_1_dt, log_entries[1], delta) log_entries = [] self.client.log( cb, "dc/foo", start_rev=2, end_rev=2, discover_changed_paths=True ) self.assertEqual(1, len(log_entries)) self.assertLogEntryChangedPathsEquals(["/foo", "/bar"], log_entries[0]) self.assertEqual(2, log_entries[0]["revision"]) self.assertLogEntryMessageEquals(commit_msg_2, log_entries[0]) self.assertLogEntryDateAlmostEquals(commit_2_dt, log_entries[0], delta) def test_info(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) info = self.client.info("dc/foo") self.assertEqual(["foo"], list(info.keys())) self.assertEqual(1, info["foo"].revision) self.assertEqual(3, info["foo"].size) self.build_tree({"dc/bar": b"blablabla"}) self.client.add(os.path.abspath("dc/bar")) def test_info_nonexistant(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.assertRaises(SubversionException, self.client.info, "dc/missing") def test_set_get_prop_with_path(self): self.build_tree({"dc/foo": b"bla"}) self.client_add("dc/foo") self.client_set_prop("dc/foo", "svn:eol-style", "native") self.client_commit("dc", message="Commit") self.assertEqual( self.client_get_prop("dc/foo", "svn:eol-style", "HEAD"), b"native" ) def test_set_get_prop_with_url(self): self.build_tree({"dc/foo": b"bla"}) self.client_add("dc/foo") self.client_set_prop("dc/foo", "svn:eol-style", "native") self.client_commit("dc", message="Commit") self.assertEqual( self.client_get_prop(self.repos_url + "/foo", "svn:eol-style", "HEAD"), b"native", ) def test_checkout(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) checkout_dir = os.path.join(self.test_dir, "checkout2") self.client.checkout(self.repos_url, checkout_dir, "HEAD") self.assertTrue(os.path.exists(os.path.join(checkout_dir, "foo"))) def test_delete(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.delete(["dc/foo"]) self.assertFalse(os.path.exists("dc/foo")) def test_copy(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy("dc/foo", "dc/bar") self.assertTrue(os.path.exists("dc/bar")) def test_propset_propget(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("myprop", "myval", "dc/foo", False, True) ret = self.client.propget("myprop", "dc/foo", "WORKING", "WORKING") self.assertIsInstance(ret, dict) self.assertIn(b"myval", ret.values()) def test_propget_url(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("myprop", "myval", "dc/foo", False, True) self.client.commit(["dc"]) url = self.repos_url + "/foo" ret = self.client.propget("myprop", url, 2, 2) self.assertIsInstance(ret, dict) self.assertIn(b"myval", ret.values()) def test_propget_abspath(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("myprop", "myval", "dc/foo", False, True) abspath = os.path.abspath("dc/foo") ret = self.client.propget("myprop", abspath, "WORKING", "WORKING") self.assertIsInstance(ret, dict) self.assertIn(b"myval", ret.values()) def test_proplist(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("myprop", "myval", "dc/foo", False, True) self.client.commit(["dc"]) result = self.client.proplist("dc/foo", "WORKING", 0) self.assertIsInstance(result, list) def test_update(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.update(["dc"], "HEAD") def test_list(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) entries = self.client.list(self.repos_url, "HEAD", 0) self.assertIsInstance(entries, dict) def test_config(self): config = client.get_config() self.assertIsInstance(config, client.Config) def test_config_default_ignores(self): config = client.get_config() ignores = config.get_default_ignores() self.assertIsInstance(ignores, list) def test_lock_unlock(self): self.build_tree({"dc/foo": b"bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.lock(["dc/foo"], "test lock comment") self.client.unlock(["dc/foo"]) def test_resolve(self): self.build_tree({"dc/resolveme": b"content"}) self.client.add("dc/resolveme") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) # resolve on a non-conflicted path should succeed without error self.client.resolve("dc/resolveme", 0, 0) def test_add_with_options(self): self.build_tree({"dc/addopts": b"data"}) self.client.add( "dc/addopts", recursive=True, force=False, no_ignore=True, add_parents=False, no_autoprops=True, ) def test_commit_with_options(self): self.build_tree({"dc/commitopt": b"data"}) self.client.add("dc/commitopt") self.client.log_msg_func = lambda c: "Commit with opts" callbacks = [] self.client.commit( ["dc"], recurse=True, keep_locks=False, keep_changelist=False, commit_as_operations=True, include_file_externals=False, include_dir_externals=False, callback=lambda *args: callbacks.append(args), ) self.assertEqual(1, len(callbacks)) def test_commit_revprops(self): self.build_tree({"dc/rptest": b"data"}) self.client.add("dc/rptest") self.client.log_msg_func = lambda c: "revprop commit" self.client.commit(["dc"], revprops={"custom:testprop": "testval"}) def test_update_with_options(self): self.build_tree({"dc/updopt": b"data"}) self.client.add("dc/updopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.update( ["dc"], "HEAD", recurse=True, ignore_externals=True, depth_is_sticky=False, allow_unver_obstructions=False, adds_as_modification=True, make_parents=False, ) def test_checkout_with_options(self): self.build_tree({"dc/chkopt": b"data"}) self.client.add("dc/chkopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) checkout_dir = os.path.join(self.test_dir, "checkout3") self.client.checkout( self.repos_url, checkout_dir, "HEAD", peg_rev="HEAD", recurse=True, allow_unver_obstructions=False, ) self.assertTrue(os.path.exists(os.path.join(checkout_dir, "chkopt"))) def test_export_with_options(self): self.build_tree({"dc/expopt": b"data"}) self.client.add("dc/expopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) export_dir = os.path.join(self.test_dir, "export_opts") self.client.export( self.repos_url, export_dir, rev="HEAD", peg_rev="HEAD", recurse=True, overwrite=False, ) self.assertTrue(os.path.exists(os.path.join(export_dir, "expopt"))) def test_diff_with_options(self): dc = self.get_commit_editor(self.repos_url) f = dc.add_file("diffopt") f.modify(b"v1") dc.close() dc = self.get_commit_editor(self.repos_url) f = dc.open_file("diffopt") f.modify(b"v2") dc.close() (outf, errf) = self.client.diff( 1, 2, self.repos_url, self.repos_url, ignore_ancestry=True, no_diff_deleted=False, ignore_content_type=False, ) self.addCleanup(outf.close) self.addCleanup(errf.close) out = outf.read() self.assertIn(b"diffopt", out) def test_log_with_options(self): entries = [] def cb(changed_paths, revision, revprops, has_children=False): entries.append(revision) self.build_tree({"dc/logopt": b"data"}) self.client.add("dc/logopt") self.client.log_msg_func = lambda c: "Commit 1" self.client.commit(["dc"]) self.build_tree({"dc/logopt": b"data2"}) self.client.commit(["dc"]) self.client.log( cb, "dc/logopt", start_rev="HEAD", end_rev=1, limit=1, discover_changed_paths=True, strict_node_history=True, include_merged_revisions=False, ) self.assertEqual(1, len(entries)) def test_cat_with_peg_revision(self): self.build_tree({"dc/pegcat": b"original"}) self.client.add("dc/pegcat") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) io = BytesIO() self.client.cat("dc/pegcat", io, revision=1, peg_revision=1) self.assertEqual(b"original", io.getvalue()) def test_delete_keep_local(self): self.build_tree({"dc/keepme": b"data"}) self.client.add("dc/keepme") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.delete(["dc/keepme"], keep_local=True) # File should still exist on disk self.assertTrue(os.path.exists("dc/keepme")) def test_copy_with_options(self): self.build_tree({"dc/cpsrc": b"data"}) self.client.add("dc/cpsrc") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy( "dc/cpsrc", "dc/cpdst", copy_as_child=False, make_parents=False, metadata_only=False, ) self.assertTrue(os.path.exists("dc/cpdst")) def test_info_with_options(self): self.build_tree({"dc/infoopt": b"data"}) self.client.add("dc/infoopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) info = self.client.info( "dc/infoopt", revision=1, peg_revision=1, depth=0, fetch_excluded=False, fetch_actual_only=False, ) self.assertIn("infoopt", info) def test_lock_steal(self): self.build_tree({"dc/locksteal": b"data"}) self.client.add("dc/locksteal") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.lock(["dc/locksteal"], "lock comment", steal_lock=True) self.client.unlock(["dc/locksteal"], break_lock=True) def test_mkdir_make_parents(self): self.client.mkdir("dc/parent/child", make_parents=True) self.assertTrue(os.path.exists("dc/parent/child")) def test_mkdir_with_callback(self): commits = [] self.client.mkdir( [self.repos_url + "/remotedir"], callback=lambda *args: commits.append(args) ) def test_propset_skip_checks(self): self.build_tree({"dc/propskip": b"data"}) self.client.add("dc/propskip") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("custom:myprop", b"value", "dc/propskip", skip_checks=True) def test_propset_with_options(self): self.build_tree({"dc/propopt": b"data"}) self.client.add("dc/propopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.propset("myprop", "myval", "dc/propopt", False, True) ret = self.client.propget("myprop", "dc/propopt", "WORKING", "WORKING", True) self.assertIsInstance(ret, dict) def test_cat_expand_keywords(self): self.build_tree({"dc/kwcat": b"$Id$"}) self.client.add("dc/kwcat") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) io = BytesIO() self.client.cat("dc/kwcat", io, revision=1, expand_keywords=False) self.assertEqual(b"$Id$", io.getvalue()) def test_export_native_eol(self): self.build_tree({"dc/eolfile": b"line\n"}) self.client.add("dc/eolfile") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) export_dir = os.path.join(self.test_dir, "export_eol") self.client.export(self.repos_url, export_dir, native_eol="LF") self.assertTrue(os.path.exists(os.path.join(export_dir, "eolfile"))) def test_list_include_externals(self): self.build_tree({"dc/listfile": b"data"}) self.client.add("dc/listfile") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) entries = self.client.list(self.repos_url, "HEAD", 0, include_externals=False) self.assertIsInstance(entries, dict) def test_delete_with_callback(self): self.build_tree({"dc/delcb": b"data"}) self.client.add("dc/delcb") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) commits = [] self.client.delete( [self.repos_url + "/delcb"], force=True, callback=lambda *args: commits.append(args), ) def test_copy_with_src_rev(self): self.build_tree({"dc/cprev": b"data"}) self.client.add("dc/cprev") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy("dc/cprev", "dc/cprev2", src_rev=1) self.assertTrue(os.path.exists("dc/cprev2")) def test_get_config_with_dir(self): import tempfile cfg_dir = tempfile.mkdtemp() try: config = client.get_config(cfg_dir) self.assertIsInstance(config, client.Config) finally: import shutil shutil.rmtree(cfg_dir) def test_copy_pin_externals(self): self.build_tree({"dc/cppin": b"data"}) self.client.add("dc/cppin") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy("dc/cppin", "dc/cppin2", pin_externals=False) self.assertTrue(os.path.exists("dc/cppin2")) def test_copy_metadata_only(self): self.build_tree({"dc/cpmeta": b"data"}) self.client.add("dc/cpmeta") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy("dc/cpmeta", "dc/cpmeta2", metadata_only=False) self.assertTrue(os.path.exists("dc/cpmeta2")) def test_copy_with_callback(self): self.build_tree({"dc/cpcb": b"data"}) self.client.add("dc/cpcb") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.client.copy( self.repos_url + "/cpcb", self.repos_url + "/cpcb2", callback=lambda *args: None, ) def test_diff_relative_to_dir(self): self.build_tree({"dc/diffrel": b"line1\n"}) self.client.add("dc/diffrel") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.build_tree({"dc/diffrel": b"line1\nline2\n"}) (outfile, errfile) = self.client.diff( 1, "WORKING", "dc", "dc", relative_to_dir="dc" ) out = outfile.read() self.assertIn(b"line2", out) def test_diff_encoding(self): self.build_tree({"dc/diffenc": b"hello\n"}) self.client.add("dc/diffenc") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.build_tree({"dc/diffenc": b"hello\nworld\n"}) (outfile, errfile) = self.client.diff( 1, "WORKING", "dc", "dc", encoding="utf-8" ) out = outfile.read() self.assertIn(b"world", out) def test_diff_ignore_content_type(self): self.build_tree({"dc/diffict": b"hello\n"}) self.client.add("dc/diffict") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.build_tree({"dc/diffict": b"hello\nworld\n"}) (outfile, errfile) = self.client.diff( 1, "WORKING", "dc", "dc", ignore_content_type=True ) out = outfile.read() self.assertIn(b"world", out) def test_diff_diffopts(self): self.build_tree({"dc/diffopt": b"hello\n"}) self.client.add("dc/diffopt") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.build_tree({"dc/diffopt": b"hello\nworld\n"}) (outfile, errfile) = self.client.diff(1, "WORKING", "dc", "dc", diffopts=["-u"]) out = outfile.read() self.assertIn(b"world", out) def test_notify_func_set_get(self): def notify_cb(info): pass self.assertIsNone(self.client.notify_func) self.client.notify_func = notify_cb self.assertIs(notify_cb, self.client.notify_func) self.client.notify_func = None self.assertIsNone(self.client.notify_func) subvertpy_0.11.1.orig/subvertpy/tests/test_core.py0000644000000000000000000001356515154541453017373 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion core library tests.""" import os import tempfile import types import subvertpy from subvertpy.tests import TestCase class TestCore(TestCase): def setUp(self): super(TestCore, self).setUp() def test_exc(self): self.assertTrue(isinstance(subvertpy.SubversionException("foo", 1), Exception)) def test_exc_args(self): exc = subvertpy.SubversionException("test message", 42) self.assertEqual(exc.args, ("test message", 42)) def test_exc_child(self): child = subvertpy.SubversionException("child", 2) exc = subvertpy.SubversionException("parent", 1, child=child) self.assertEqual(exc.child, child) def test_exc_location(self): exc = subvertpy.SubversionException("msg", 1, location="file.c:42") self.assertEqual(exc.location, "file.c:42") def test_exc_child_default_none(self): exc = subvertpy.SubversionException("msg", 1) self.assertIsNone(exc.child) def test_exc_location_default_none(self): exc = subvertpy.SubversionException("msg", 1) self.assertIsNone(exc.location) class TestConstants(TestCase): def test_node_constants(self): self.assertEqual(subvertpy.NODE_NONE, 0) self.assertEqual(subvertpy.NODE_FILE, 1) self.assertEqual(subvertpy.NODE_DIR, 2) self.assertEqual(subvertpy.NODE_UNKNOWN, 3) def test_error_constants_are_ints(self): self.assertIsInstance(subvertpy.ERR_UNSUPPORTED_FEATURE, int) self.assertIsInstance(subvertpy.ERR_RA_SVN_UNKNOWN_CMD, int) self.assertIsInstance(subvertpy.ERR_FS_NO_SUCH_REVISION, int) self.assertIsInstance(subvertpy.ERR_CANCELLED, int) def test_error_constants_values(self): self.assertEqual(subvertpy.ERR_UNSUPPORTED_FEATURE, 200007) self.assertEqual(subvertpy.ERR_RA_SVN_UNKNOWN_CMD, 210001) self.assertEqual(subvertpy.ERR_RA_SVN_CONNECTION_CLOSED, 210002) self.assertEqual(subvertpy.ERR_WC_LOCKED, 155004) self.assertEqual(subvertpy.ERR_RA_NOT_AUTHORIZED, 170001) self.assertEqual(subvertpy.ERR_INCOMPLETE_DATA, 200003) self.assertEqual(subvertpy.ERR_FS_NO_SUCH_REVISION, 160006) self.assertEqual(subvertpy.ERR_CANCELLED, 200015) def test_wc_not_working_copy_aliases(self): self.assertEqual( subvertpy.ERR_WC_NOT_WORKING_COPY, subvertpy.ERR_WC_NOT_DIRECTORY ) def test_auth_param_constants(self): self.assertEqual(subvertpy.AUTH_PARAM_DEFAULT_USERNAME, "svn:auth:username") self.assertEqual(subvertpy.AUTH_PARAM_DEFAULT_PASSWORD, "svn:auth:password") def test_ssl_constants(self): self.assertEqual(subvertpy.SSL_NOTYETVALID, 0x00000001) self.assertEqual(subvertpy.SSL_EXPIRED, 0x00000002) self.assertEqual(subvertpy.SSL_CNMISMATCH, 0x00000004) self.assertEqual(subvertpy.SSL_UNKNOWNCA, 0x00000008) self.assertEqual(subvertpy.SSL_OTHER, 0x40000000) def test_apr_error_constants(self): self.assertEqual(subvertpy.ERR_APR_OS_START_EAIERR, 670000) self.assertEqual(subvertpy.ERR_APR_OS_ERRSPACE_SIZE, 50000) self.assertEqual(subvertpy.ERR_CATEGORY_SIZE, 5000) class TestCheckMtime(TestCase): def test_check_mtime_c_file_missing(self): # When no .c file exists, _check_mtime should return True module = types.ModuleType("fake") module.__file__ = "/nonexistent/path/fake.so" self.assertTrue(subvertpy._check_mtime(module)) def test_check_mtime_c_file_older(self): # When .c file exists but is older than the module, return True tmpdir = tempfile.mkdtemp() try: so_path = os.path.join(tmpdir, "mod.so") c_path = os.path.join(tmpdir, "mod.c") # Create .c first (older) with open(c_path, "w") as f: f.write("/* c */") # Then .so (newer) - ensure different mtime os.utime(c_path, (1000, 1000)) with open(so_path, "w") as f: f.write("so") os.utime(so_path, (2000, 2000)) module = types.ModuleType("mod") module.__file__ = so_path self.assertTrue(subvertpy._check_mtime(module)) finally: import shutil shutil.rmtree(tmpdir) def test_check_mtime_c_file_newer(self): # When .c file exists and is newer than the module, return False tmpdir = tempfile.mkdtemp() try: so_path = os.path.join(tmpdir, "mod.so") c_path = os.path.join(tmpdir, "mod.c") # Create .so first (older) with open(so_path, "w") as f: f.write("so") os.utime(so_path, (1000, 1000)) # Then .c (newer) with open(c_path, "w") as f: f.write("/* c */") os.utime(c_path, (2000, 2000)) module = types.ModuleType("mod") module.__file__ = so_path self.assertFalse(subvertpy._check_mtime(module)) finally: import shutil shutil.rmtree(tmpdir) def test_version(self): self.assertIsInstance(subvertpy.__version__, tuple) self.assertEqual(len(subvertpy.__version__), 3) subvertpy_0.11.1.orig/subvertpy/tests/test_delta.py0000644000000000000000000002404115154541453017523 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Tests for subvertpy.delta.""" from io import BytesIO from subvertpy.delta import ( apply_txdelta_handler, apply_txdelta_handler_chunks, apply_txdelta_window, decode_length, encode_length, pack_svndiff0, pack_svndiff0_window, pack_svndiff_instruction, send_stream, txdelta_apply_ops, unpack_svndiff0, unpack_svndiff_instruction, DELTA_WINDOW_SIZE, MAX_ENCODED_INT_LEN, SVNDIFF0_HEADER, TXDELTA_INVALID, TXDELTA_NEW, TXDELTA_SOURCE, TXDELTA_TARGET, ) from subvertpy.tests import TestCase class DeltaTests(TestCase): def setUp(self): super(DeltaTests, self).setUp() self.windows = [] def storing_window_handler(self, window): self.windows.append(window) def test_send_stream(self): stream = BytesIO(b"foo") send_stream(stream, self.storing_window_handler) self.assertEqual([(0, 0, 3, 0, [(2, 0, 3)], b"foo"), None], self.windows) def test_send_stream_returns_md5(self): import hashlib data = b"hello world" stream = BytesIO(data) result = send_stream(stream, self.storing_window_handler) self.assertEqual(hashlib.md5(data).digest(), result) def test_send_stream_empty(self): stream = BytesIO(b"") send_stream(stream, self.storing_window_handler) self.assertEqual([None], self.windows) def test_send_stream_non_bytes_raises(self): import io stream = io.StringIO("text") self.assertRaises(TypeError, send_stream, stream, self.storing_window_handler) def test_send_stream_custom_block_size(self): data = b"abcdef" stream = BytesIO(data) send_stream(stream, self.storing_window_handler, block_size=3) # Should get two windows plus None self.assertEqual(3, len(self.windows)) self.assertIsNone(self.windows[-1]) def test_apply_delta(self): stream = BytesIO() source = b"(source)" handler = apply_txdelta_handler(source, stream) new = b"(new)" ops = ( # (action, offset, length) (TXDELTA_NEW, 0, len(new)), (TXDELTA_SOURCE, 0, len(source)), (TXDELTA_TARGET, len(new), len(b"(s")), # Copy "(s" (TXDELTA_TARGET, len(b"(n"), len(b"ew)")), # Copy "ew)" # Copy as target is generated (TXDELTA_TARGET, len(new + source), len(b"(sew)") * 2), ) result = b"(new)(source)(sew)(sew)(sew)" # (source offset, source length, result length, src_ops, ops, new) handler((0, len(source), len(result), 0, ops, new)) handler(None) self.assertEqual(result, stream.getvalue()) def test_apply_txdelta_handler_none_is_noop(self): stream = BytesIO() handler = apply_txdelta_handler(b"", stream) handler(None) self.assertEqual(b"", stream.getvalue()) def test_apply_txdelta_handler_chunks(self): source_chunks = [b"hello", b" world"] target_chunks = [] handler = apply_txdelta_handler_chunks(source_chunks, target_chunks) new_data = b"replaced" ops = [(TXDELTA_NEW, 0, len(new_data))] handler((0, 11, len(new_data), 0, ops, new_data)) handler(None) self.assertEqual([bytearray(b"replaced")], target_chunks) def test_apply_txdelta_handler_chunks_source_copy(self): source_chunks = [b"hello"] target_chunks = [] handler = apply_txdelta_handler_chunks(source_chunks, target_chunks) ops = [(TXDELTA_SOURCE, 0, 5)] handler((0, 5, 5, 0, ops, b"")) handler(None) self.assertEqual([bytearray(b"hello")], target_chunks) class TxDeltaApplyOpsTests(TestCase): def test_source_copy(self): result = txdelta_apply_ops(0, [(TXDELTA_SOURCE, 0, 3)], b"", b"abc") self.assertEqual(bytearray(b"abc"), result) def test_new_data(self): result = txdelta_apply_ops(0, [(TXDELTA_NEW, 0, 3)], b"xyz", b"") self.assertEqual(bytearray(b"xyz"), result) def test_target_copy(self): # First add some new data, then copy from target ops = [ (TXDELTA_NEW, 0, 2), (TXDELTA_TARGET, 0, 4), ] result = txdelta_apply_ops(0, ops, b"ab", b"") self.assertEqual(bytearray(b"ababab"), result) def test_invalid_action_raises(self): self.assertRaises( Exception, txdelta_apply_ops, 0, [(TXDELTA_INVALID, 0, 1)], b"", b"x" ) def test_empty_ops(self): result = txdelta_apply_ops(0, [], b"", b"source") self.assertEqual(bytearray(b""), result) class ApplyTxDeltaWindowTests(TestCase): def test_simple_new(self): window = (0, 0, 5, 0, [(TXDELTA_NEW, 0, 5)], b"hello") result = apply_txdelta_window(b"", window) self.assertEqual(bytearray(b"hello"), result) def test_source_copy(self): window = (2, 3, 3, 0, [(TXDELTA_SOURCE, 0, 3)], b"") result = apply_txdelta_window(b"XXabcXX", window) self.assertEqual(bytearray(b"abc"), result) def test_tview_len_mismatch_raises(self): # tview_len (10) doesn't match actual result length (5) window = (0, 0, 10, 0, [(TXDELTA_NEW, 0, 5)], b"hello") self.assertRaises(AssertionError, apply_txdelta_window, b"", window) class MarshallTests(TestCase): def test_encode_length(self): self.assertEqual(bytearray(b"\x81\x02"), encode_length(130)) def test_encode_length_zero(self): self.assertEqual(bytearray(b"\x00"), encode_length(0)) def test_encode_length_small(self): self.assertEqual(bytearray(b"\x01"), encode_length(1)) self.assertEqual(bytearray(b"\x7f"), encode_length(127)) def test_encode_length_large(self): encoded = encode_length(16384) decoded, _ = decode_length(encoded) self.assertEqual(16384, decoded) def test_roundtrip_length(self): self.assertEqual((42, bytes()), decode_length(encode_length(42))) def test_roundtrip_length_various(self): for val in [0, 1, 127, 128, 255, 256, 1000, 65535, 100000]: decoded, remainder = decode_length(encode_length(val)) self.assertEqual(val, decoded) self.assertEqual(bytes(), remainder) def test_roundtrip_window(self): mywindow = (0, 0, 3, 1, [(2, 0, 3)], b"foo") self.assertEqual([mywindow], list(unpack_svndiff0(pack_svndiff0([mywindow])))) def test_roundtrip_window_multiple(self): w1 = (0, 0, 3, 1, [(TXDELTA_NEW, 0, 3)], b"foo") w2 = (0, 0, 3, 1, [(TXDELTA_NEW, 0, 3)], b"bar") result = list(unpack_svndiff0(pack_svndiff0([w1, w2]))) self.assertEqual([w1, w2], result) def test_pack_svndiff0_header(self): packed = pack_svndiff0([]) self.assertTrue(packed.startswith(SVNDIFF0_HEADER)) def test_unpack_svndiff0_empty(self): result = list(unpack_svndiff0(SVNDIFF0_HEADER)) self.assertEqual([], result) class SvndiffInstructionTests(TestCase): def test_pack_unpack_new_short(self): instr = (TXDELTA_NEW, 0, 10) packed = pack_svndiff_instruction(instr) unpacked, remainder = unpack_svndiff_instruction(packed) self.assertEqual(instr, unpacked) self.assertEqual(b"", remainder) def test_pack_unpack_source(self): instr = (TXDELTA_SOURCE, 5, 10) packed = pack_svndiff_instruction(instr) unpacked, remainder = unpack_svndiff_instruction(packed) self.assertEqual(instr, unpacked) self.assertEqual(b"", remainder) def test_pack_unpack_target(self): instr = (TXDELTA_TARGET, 3, 7) packed = pack_svndiff_instruction(instr) unpacked, remainder = unpack_svndiff_instruction(packed) self.assertEqual(instr, unpacked) self.assertEqual(b"", remainder) def test_pack_unpack_long_length(self): # Length >= 0x3f triggers the longer encoding path instr = (TXDELTA_NEW, 0, 100) packed = pack_svndiff_instruction(instr) unpacked, remainder = unpack_svndiff_instruction(packed) self.assertEqual(instr, unpacked) self.assertEqual(b"", remainder) def test_pack_unpack_source_long(self): instr = (TXDELTA_SOURCE, 200, 100) packed = pack_svndiff_instruction(instr) unpacked, _ = unpack_svndiff_instruction(packed) self.assertEqual(instr, unpacked) def test_roundtrip_through_window(self): """Pack instructions into a window, then unpack.""" ops = [ (TXDELTA_NEW, 0, 5), (TXDELTA_SOURCE, 0, 3), ] new_data = b"hello" window = (0, 3, 8, len(ops), ops, new_data) packed = pack_svndiff0_window(window) # Pack into full svndiff and unpack full = SVNDIFF0_HEADER + bytes(packed) result = list(unpack_svndiff0(full)) self.assertEqual(1, len(result)) self.assertEqual(window, result[0]) class ConstantsTests(TestCase): def test_txdelta_constants(self): self.assertEqual(TXDELTA_SOURCE, 0) self.assertEqual(TXDELTA_TARGET, 1) self.assertEqual(TXDELTA_NEW, 2) self.assertEqual(TXDELTA_INVALID, 3) def test_max_encoded_int_len(self): self.assertEqual(MAX_ENCODED_INT_LEN, 10) def test_delta_window_size(self): self.assertEqual(DELTA_WINDOW_SIZE, 102400) def test_svndiff0_header(self): self.assertEqual(SVNDIFF0_HEADER, b"SVN\0") subvertpy_0.11.1.orig/subvertpy/tests/test_marshall.py0000644000000000000000000001642215154541453020241 0ustar00# Copyright (C) 2006-2007 Jelmer Vernooij # # 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 """Tests for subvertpy.marshall.""" from subvertpy.marshall import ( MarshallError, NeedMoreData, literal, marshall, unmarshall, ) from subvertpy.tests import TestCase class TestMarshalling(TestCase): def test_literal_txt(self): line = literal("foo") self.assertEqual("foo", line.txt) def test_literal_str(self): line = literal("foo bar") self.assertEqual("foo bar", line.__str__()) def test_literal_rep(self): line = literal("foo bar") self.assertEqual("foo bar", line.__repr__()) def test_literal_eq_same(self): self.assertEqual(literal("foo"), literal("foo")) def test_literal_eq_different(self): self.assertNotEqual(literal("foo"), literal("bar")) def test_literal_eq_non_literal(self): self.assertNotEqual(literal("foo"), "foo") def test_marshall_error(self): err = MarshallError("bla bla") self.assertEqual("bla bla", err.__str__()) def test_need_more_data_is_marshall_error(self): self.assertTrue(issubclass(NeedMoreData, MarshallError)) def test_need_more_data(self): err = NeedMoreData("need more") self.assertIsInstance(err, MarshallError) self.assertEqual("need more", str(err)) def test_marshall_int(self): self.assertEqual(b"1 ", marshall(1)) def test_marshall_int_zero(self): self.assertEqual(b"0 ", marshall(0)) def test_marshall_int_large(self): self.assertEqual(b"999999 ", marshall(999999)) def test_marshall_list(self): self.assertEqual(b"( 1 2 3 4 ) ", marshall([1, 2, 3, 4])) def test_marshall_list_mixed(self): self.assertEqual(b"( 1 3 4 3:str ) ", marshall([1, 3, 4, "str"])) def test_marshall_literal(self): self.assertEqual(b"foo ", marshall(literal("foo"))) def test_marshall_string(self): self.assertEqual(b"3:foo ", marshall("foo")) def test_marshall_bytes(self): self.assertEqual(b"3:foo ", marshall(b"foo")) def test_marshall_bytes_binary(self): data = b"\x00\x01\x02" self.assertEqual(b"3:\x00\x01\x02 ", marshall(data)) def test_marshall_string_utf8(self): # UTF-8 multi-byte characters result = marshall("\u00e9") # é self.assertEqual(b"2:\xc3\xa9 ", result) def test_marshall_raises(self): self.assertRaises(MarshallError, marshall, dict()) def test_marshall_list_nested(self): self.assertEqual(b"( ( ( 3 ) 4 ) ) ", marshall([[[3], 4]])) def test_marshall_list_empty(self): self.assertEqual(b"( ) ", marshall([])) def test_marshall_tuple(self): self.assertEqual(b"( 1 2 ) ", marshall((1, 2))) def test_marshall_string_space(self): self.assertEqual(b"5:bla l ", marshall("bla l")) def test_marshall_string_empty(self): self.assertEqual(b"0: ", marshall("")) def test_unmarshall_string(self): self.assertEqual((b"", b"bla l"), unmarshall(b"5:bla l")) def test_unmarshall_list(self): self.assertEqual((b"", [4, 5]), unmarshall(b"( 4 5 ) ")) def test_unmarshall_list_empty(self): self.assertEqual((b"", []), unmarshall(b"( ) ")) def test_unmarshall_list_nested(self): self.assertEqual((b"", [[1, 2], 3]), unmarshall(b"( ( 1 2 ) 3 ) ")) def test_unmarshall_int(self): self.assertEqual((b"", 2), unmarshall(b"2 ")) def test_unmarshall_int_zero(self): self.assertEqual((b"", 0), unmarshall(b"0 ")) def test_unmarshall_int_large(self): self.assertEqual((b"", 123456), unmarshall(b"123456 ")) def test_unmarshall_literal(self): self.assertEqual((b"", literal("x")), unmarshall(b"x ")) def test_unmarshall_literal_with_digits(self): self.assertEqual((b"", literal("foo2")), unmarshall(b"foo2 ")) def test_unmarshall_literal_with_dash(self): self.assertEqual((b"", literal("foo-bar")), unmarshall(b"foo-bar ")) def test_unmarshall_empty(self): self.assertRaises(NeedMoreData, unmarshall, b"") def test_unmarshall_nospace(self): self.assertRaises(MarshallError, unmarshall, b"nospace") def test_unmarshall_toolong(self): self.assertRaises(NeedMoreData, unmarshall, b"43432432:bla") def test_unmarshall_literal_negative(self): self.assertRaises(MarshallError, unmarshall, b":-3213") def test_unmarshall_open_list(self): self.assertRaises(NeedMoreData, unmarshall, b"( 3 4 ") def test_unmarshall_remaining_data(self): # Two marshalled items concatenated data = marshall(b"foo") + marshall(b"bar") remainder, value = unmarshall(data) self.assertEqual(b"foo", value) _, value2 = unmarshall(remainder) self.assertEqual(b"bar", value2) def test_unmarshall_list_missing_space_after_open(self): self.assertRaises(MarshallError, unmarshall, b"(x") def test_unmarshall_need_more_data_list_start(self): self.assertRaises(NeedMoreData, unmarshall, b"(") def test_roundtrip_int(self): _, val = unmarshall(marshall(42)) self.assertEqual(42, val) def test_roundtrip_string(self): _, val = unmarshall(marshall(b"hello")) self.assertEqual(b"hello", val) def test_roundtrip_list(self): _, val = unmarshall(marshall([1, 2, 3])) self.assertEqual([1, 2, 3], val) def test_roundtrip_literal(self): _, val = unmarshall(marshall(literal("success"))) self.assertEqual(literal("success"), val) def test_roundtrip_nested(self): _, val = unmarshall(marshall([literal("cmd"), [1, b"data"]])) self.assertEqual([literal("cmd"), [1, b"data"]], val) def test_marshall_bool_true(self): # In Python 3, bool is a subclass of int, so True marshalls as 1 self.assertEqual(b"1 ", marshall(True)) def test_marshall_bool_false(self): # In Python 3, bool is a subclass of int, so False marshalls as 0 self.assertEqual(b"0 ", marshall(False)) def test_marshall_none_raises(self): self.assertRaises(MarshallError, marshall, None) def test_marshall_float_raises(self): self.assertRaises(MarshallError, marshall, 3.14) def test_unmarshall_literal_newline_separator(self): # newline is also valid whitespace self.assertEqual((b"", literal("x")), unmarshall(b"x\n")) def test_unmarshall_int_newline_separator(self): self.assertEqual((b"", 5), unmarshall(b"5\n")) def test_unmarshall_list_newline_separator(self): self.assertEqual((b"", [1]), unmarshall(b"( 1 )\n")) subvertpy_0.11.1.orig/subvertpy/tests/test_properties.py0000644000000000000000000003530515154541453020633 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion core library tests.""" import os import time from unittest import SkipTest from subvertpy import properties from subvertpy.tests import ( TestCase, ) class TestProperties(TestCase): def setUp(self): super(TestProperties, self).setUp() def test_time_from_cstring(self): self.assertEqual( 1225704780716938, properties.time_from_cstring("2008-11-03T09:33:00.716938Z"), ) def test_time_from_cstring_independent_from_dst(self): old_tz = os.environ.get("TZ", None) # On Windows, there is no tzset function, so skip this test. if getattr(time, "tzset", None) is None: raise SkipTest("tzset not available on Windows") try: # First specify a fixed timezone with known DST (late March to late # October) os.environ["TZ"] = "Europe/London" time.tzset() # Now test a time within that DST self.assertEqual( 1275295762430000, properties.time_from_cstring("2010-05-31T08:49:22.430000Z"), ) finally: if old_tz is None: del os.environ["TZ"] else: os.environ["TZ"] = old_tz time.tzset() def test_time_to_cstring(self): self.assertEqual( "2008-11-03T09:33:00.716938Z", properties.time_to_cstring(1225704780716938) ) class TestExternalsParser(TestCase): def test_parse_root_relative_externals(self): self.assertRaises( NotImplementedError, properties.parse_externals_description, "http://example.com", "third-party/skins ^/foo", ) def test_parse_scheme_relative_externals(self): self.assertRaises( NotImplementedError, properties.parse_externals_description, "http://example.com", "third-party/skins //foo", ) def test_parse_externals(self): self.assertEqual( { "third-party/sounds": (None, "http://sounds.red-bean.com/repos"), "third-party/skins": ( None, "http://skins.red-bean.com/repositories/skinproj", ), "third-party/skins/toolkit": ( 21, "http://svn.red-bean.com/repos/skin-maker", ), }, properties.parse_externals_description( "http://example.com", """\ third-party/sounds http://sounds.red-bean.com/repos third-party/skins http://skins.red-bean.com/repositories/skinproj third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker""", ), ) def test_parse_externals_space_revno(self): self.assertEqual( { "third-party/skins/toolkit": ( 21, "http://svn.red-bean.com/repos/skin-maker", ) }, properties.parse_externals_description( "http://example.com", """\ third-party/skins/toolkit -r 21 http://svn.red-bean.com/repos/skin-maker""", ), ) def test_parse_externals_swapped(self): self.assertEqual( {"third-party/sounds": (None, "http://sounds.red-bean.com/repos")}, properties.parse_externals_description( "http://example.com", """\ http://sounds.red-bean.com/repos third-party/sounds """, ), ) def test_parse_comment(self): self.assertEqual( {"third-party/sounds": (None, "http://sounds.red-bean.com/repos")}, properties.parse_externals_description( "http://example.com/", """\ third-party/sounds http://sounds.red-bean.com/repos #third-party/skins http://skins.red-bean.com/repositories/skinproj #third-party/skins/toolkit -r21 http://svn.red-bean.com/repos/skin-maker""", ), ) def test_parse_relative(self): self.assertEqual( { "third-party/sounds": (None, "http://example.com/branches/other"), }, properties.parse_externals_description( "http://example.com/trunk", "third-party/sounds ../branches/other", ), ) def test_parse_repos_root_relative(self): self.assertEqual( { "third-party/sounds": ( None, "http://example.com/bar/bla/branches/other", ), }, properties.parse_externals_description( "http://example.com/trunk", "third-party/sounds /bar/bla/branches/other", ), ) def test_parse_invalid_missing_url(self): """No URL specified.""" self.assertRaises( properties.InvalidExternalsDescription, lambda: properties.parse_externals_description( "http://example.com/", "bla" ), ) def test_parse_invalid_too_much_data(self): """No URL specified.""" self.assertRaises( properties.InvalidExternalsDescription, lambda: properties.parse_externals_description( None, "bla -R40 http://bla/" ), ) class MergeInfoPropertyParserTests(TestCase): def test_simple_range(self): self.assertEqual( {"/trunk": [(1, 2, True)]}, properties.parse_mergeinfo_property("/trunk:1-2\n"), ) def test_simple_range_uninheritable(self): self.assertEqual( {"/trunk": [(1, 2, False)]}, properties.parse_mergeinfo_property("/trunk:1-2*\n"), ) def test_simple_individual(self): self.assertEqual( {"/trunk": [(1, 1, True)]}, properties.parse_mergeinfo_property("/trunk:1\n"), ) def test_empty(self): self.assertEqual({}, properties.parse_mergeinfo_property("")) class MergeInfoPropertyCreatorTests(TestCase): def test_simple_range(self): self.assertEqual( "/trunk:1-2\n", properties.generate_mergeinfo_property({"/trunk": [(1, 2, True)]}), ) def test_simple_individual(self): self.assertEqual( "/trunk:1\n", properties.generate_mergeinfo_property({"/trunk": [(1, 1, True)]}), ) def test_empty(self): self.assertEqual("", properties.generate_mergeinfo_property({})) class RevnumRangeTests(TestCase): def test_add_revnum_empty(self): self.assertEqual([(1, 1, True)], properties.range_add_revnum([], 1)) def test_add_revnum_before(self): self.assertEqual( [(2, 2, True), (8, 8, True)], properties.range_add_revnum([(2, 2, True)], 8) ) def test_add_revnum_included(self): self.assertEqual([(1, 3, True)], properties.range_add_revnum([(1, 3, True)], 2)) def test_add_revnum_after(self): self.assertEqual( [(1, 3, True), (5, 5, True)], properties.range_add_revnum([(1, 3, True)], 5) ) def test_add_revnum_extend_before(self): self.assertEqual([(1, 3, True)], properties.range_add_revnum([(2, 3, True)], 1)) def test_add_revnum_extend_after(self): self.assertEqual([(1, 3, True)], properties.range_add_revnum([(1, 2, True)], 3)) def test_revnum_includes_empty(self): self.assertFalse(properties.range_includes_revnum([], 2)) def test_revnum_includes_oor(self): self.assertFalse( properties.range_includes_revnum([(1, 3, True), (4, 5, True)], 10) ) def test_revnum_includes_in(self): self.assertTrue( properties.range_includes_revnum([(1, 3, True), (4, 5, True)], 2) ) class MergeInfoIncludeTests(TestCase): def test_includes_individual(self): self.assertTrue( properties.mergeinfo_includes_revision( {"/trunk": [(1, 1, True)]}, "/trunk", 1 ) ) def test_includes_range(self): self.assertTrue( properties.mergeinfo_includes_revision( {"/trunk": [(1, 5, True)]}, "/trunk", 3 ) ) def test_includes_invalid_path(self): self.assertFalse( properties.mergeinfo_includes_revision( {"/somepath": [(1, 5, True)]}, "/trunk", 3 ) ) def test_includes_invalid_revnum(self): self.assertFalse( properties.mergeinfo_includes_revision( {"/trunk": [(1, 5, True)]}, "/trunk", 30 ) ) class MergeInfoAddRevisionTests(TestCase): def test_add_new_path(self): mergeinfo = {} result = properties.mergeinfo_add_revision(mergeinfo, "/trunk", 5) self.assertEqual({"/trunk": [(5, 5, True)]}, result) def test_add_to_existing_path(self): mergeinfo = {"/trunk": [(1, 3, True)]} result = properties.mergeinfo_add_revision(mergeinfo, "/trunk", 5) self.assertEqual({"/trunk": [(1, 3, True), (5, 5, True)]}, result) def test_add_extends_range(self): mergeinfo = {"/trunk": [(1, 3, True)]} result = properties.mergeinfo_add_revision(mergeinfo, "/trunk", 4) self.assertEqual({"/trunk": [(1, 4, True)]}, result) def test_add_already_included(self): mergeinfo = {"/trunk": [(1, 5, True)]} result = properties.mergeinfo_add_revision(mergeinfo, "/trunk", 3) self.assertEqual({"/trunk": [(1, 5, True)]}, result) class IsValidPropertyNameTests(TestCase): def test_simple_name(self): self.assertTrue(properties.is_valid_property_name("svn:log")) def test_name_with_colon(self): self.assertTrue(properties.is_valid_property_name(":foo")) def test_name_with_underscore_start(self): self.assertTrue(properties.is_valid_property_name("_foo")) def test_alphanumeric(self): self.assertTrue(properties.is_valid_property_name("abc123")) def test_with_dash(self): self.assertTrue(properties.is_valid_property_name("my-prop")) def test_with_dot(self): self.assertTrue(properties.is_valid_property_name("my.prop")) def test_invalid_start(self): self.assertFalse(properties.is_valid_property_name("-foo")) def test_invalid_char(self): self.assertFalse(properties.is_valid_property_name("foo bar")) def test_svn_prefix(self): self.assertTrue(properties.is_valid_property_name("svn:externals")) class PropertyDiffTests(TestCase): def test_diff_empty(self): self.assertEqual({}, properties.diff({}, {})) def test_diff_added(self): self.assertEqual({"key": (None, "val")}, properties.diff({"key": "val"}, {})) def test_diff_changed(self): self.assertEqual( {"key": ("old", "new")}, properties.diff({"key": "new"}, {"key": "old"}) ) def test_diff_unchanged(self): self.assertEqual({}, properties.diff({"key": "same"}, {"key": "same"})) def test_diff_multiple_changes(self): result = properties.diff( {"a": "1", "b": "changed", "c": "3"}, {"a": "1", "b": "original"} ) self.assertEqual({"b": ("original", "changed"), "c": (None, "3")}, result) def test_diff_only_reports_current_keys(self): # diff() only iterates over current.items(), so properties # that were deleted (in previous but not current) are not reported result = properties.diff({}, {"deleted": "val"}) self.assertEqual({}, result) class PropertyConstantsTests(TestCase): def test_prop_executable(self): self.assertEqual(properties.PROP_EXECUTABLE, "svn:executable") def test_prop_externals(self): self.assertEqual(properties.PROP_EXTERNALS, "svn:externals") def test_prop_mergeinfo(self): self.assertEqual(properties.PROP_MERGEINFO, "svn:mergeinfo") def test_prop_revision_log(self): self.assertEqual(properties.PROP_REVISION_LOG, "svn:log") def test_prop_revision_author(self): self.assertEqual(properties.PROP_REVISION_AUTHOR, "svn:author") def test_prop_revision_date(self): self.assertEqual(properties.PROP_REVISION_DATE, "svn:date") def test_prop_special(self): self.assertEqual(properties.PROP_SPECIAL, "svn:special") def test_prop_prefix(self): self.assertEqual(properties.PROP_PREFIX, "svn:") class ExternalsParserAdditionalTests(TestCase): def test_parse_swapped_with_revision_dash_r_x(self): self.assertEqual( {"ext": (10, "http://example.com/foo")}, properties.parse_externals_description( "http://example.com", "-r10 http://example.com/foo ext" ), ) def test_parse_dir_dash_r_x_url(self): self.assertEqual( {"ext": (10, "http://example.com/foo")}, properties.parse_externals_description( "http://example.com", "ext -r10 http://example.com/foo" ), ) def test_parse_empty(self): self.assertEqual( {}, properties.parse_externals_description("http://example.com", "") ) def test_parse_comment_only(self): self.assertEqual( {}, properties.parse_externals_description( "http://example.com", "# just a comment" ), ) class MergeInfoPropertyCreatorAdditionalTests(TestCase): def test_uninheritable_range(self): self.assertEqual( "/trunk:1-2*\n", properties.generate_mergeinfo_property({"/trunk": [(1, 2, False)]}), ) def test_uninheritable_individual(self): self.assertEqual( "/trunk:1*\n", properties.generate_mergeinfo_property({"/trunk": [(1, 1, False)]}), ) def test_multiple_ranges(self): result = properties.generate_mergeinfo_property( {"/trunk": [(1, 3, True), (5, 8, True)]} ) self.assertEqual("/trunk:1-3,5-8\n", result) subvertpy_0.11.1.orig/subvertpy/tests/test_ra.py0000644000000000000000000012771315154541453017046 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion ra library tests.""" from io import BytesIO from subvertpy import ( NODE_DIR, NODE_NONE, SubversionException, ra, ) from subvertpy.tests import ( SubversionTestCase, TestCase, ) class VersionTest(TestCase): def test_version_length(self): self.assertEqual(4, len(ra.version())) def test_api_version_length(self): self.assertEqual(4, len(ra.api_version())) def test_api_version_later_same(self): self.assertTrue(ra.api_version() <= ra.version()) class TestRemoteAccessUnknown(TestCase): def test_unknown_url(self): self.assertRaises(SubversionException, ra.RemoteAccess, "bla://") def test_unknown_url_bytes(self): self.assertRaises(SubversionException, ra.RemoteAccess, b"bla://") def test_url_handlers_populated(self): self.assertIn("svn", ra.url_handlers) self.assertIn("svn+ssh", ra.url_handlers) self.assertIn("http", ra.url_handlers) self.assertIn("https", ra.url_handlers) self.assertIn("file", ra.url_handlers) class TestRemoteAccess(SubversionTestCase): def setUp(self): super(TestRemoteAccess, self).setUp() self.repos_url = self.make_repository("d") self.ra = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]) ) def tearDown(self): del self.ra super(TestRemoteAccess, self).tearDown() def commit_editor(self): return self.get_commit_editor(self.repos_url) def do_commit(self): dc = self.get_commit_editor(self.repos_url) dc.add_dir("foo") dc.close() def test_repr(self): self.assertEqual('RemoteAccess("%s")' % self.repos_url, repr(self.ra)) def test_latest_revnum(self): self.assertEqual(0, self.ra.get_latest_revnum()) def test_latest_revnum_one(self): self.do_commit() self.assertEqual(1, self.ra.get_latest_revnum()) def test_get_uuid(self): self.assertEqual(36, len(self.ra.get_uuid())) def test_get_repos_root(self): self.assertEqual(self.repos_url, self.ra.get_repos_root()) def test_get_url(self): self.assertEqual(self.repos_url, self.ra.get_session_url()) def test_reparent(self): self.ra.reparent(self.repos_url) def test_has_capability(self): self.assertRaises(SubversionException, self.ra.has_capability, "FOO") def test_get_dir(self): ret = self.ra.get_dir("", 0) self.assertIsInstance(ret, tuple) def test_get_dir_leading_slash(self): ret = self.ra.get_dir("/", 0) self.assertIsInstance(ret, tuple) def test_get_dir_kind(self): self.do_commit() (dirents, fetch_rev, props) = self.ra.get_dir("/", 1, fields=ra.DIRENT_KIND) self.assertIsInstance(props, dict) self.assertEqual(1, fetch_rev) self.assertEqual(NODE_DIR, dirents["foo"]["kind"]) def test_change_rev_prop(self): self.do_commit() self.ra.change_rev_prop(1, "foo", "bar") def test_rev_proplist(self): self.assertIsInstance(self.ra.rev_proplist(0), dict) def test_do_diff(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra.do_diff(1, "", self.ra.get_repos_root(), MyEditor()) reporter.set_path("", 0, True) reporter.finish() self.assertRaises(RuntimeError, reporter.finish) self.assertRaises(RuntimeError, reporter.set_path, "", 0, True) def test_iter_log_invalid(self): self.assertRaises( SubversionException, list, self.ra.iter_log( ["idontexist"], 0, 0, revprops=["svn:date", "svn:author", "svn:log"] ), ) self.assertRaises( SubversionException, list, self.ra.iter_log( [""], 0, 1000, revprops=["svn:date", "svn:author", "svn:log"] ), ) def test_iter_log(self): def check_results(returned): self.assertEqual(2, len(returned)) self.assertTrue(len(returned[0]) in (3, 4)) if len(returned[0]) == 3: (paths, revnum, props) = returned[0] else: (paths, revnum, props, has_children) = returned[0] self.assertEqual(None, paths) self.assertEqual(revnum, 0) self.assertEqual(["svn:date"], list(props.keys())) if len(returned[1]) == 3: (paths, revnum, props) = returned[1] else: (paths, revnum, props, has_children) = returned[1] self.assertEqual({"/foo": ("A", None, -1, NODE_DIR)}, paths) self.assertEqual(revnum, 1) self.assertEqual( set(["svn:date", "svn:author", "svn:log"]), set(props.keys()) ) returned = list( self.ra.iter_log([""], 0, 0, revprops=["svn:date", "svn:author", "svn:log"]) ) self.assertEqual(1, len(returned)) self.do_commit() returned = list( self.ra.iter_log( None, 0, 1, discover_changed_paths=True, strict_node_history=False, revprops=["svn:date", "svn:author", "svn:log"], ) ) check_results(returned) def test_get_log(self): returned = [] def cb(*args): returned.append(args) def check_results(returned): self.assertEqual(2, len(returned)) self.assertTrue(len(returned[0]) in (3, 4)) if len(returned[0]) == 3: (paths, revnum, props) = returned[0] else: (paths, revnum, props, has_children) = returned[0] self.assertEqual(None, paths) self.assertEqual(revnum, 0) self.assertEqual(["svn:date"], list(props.keys())) if len(returned[1]) == 3: (paths, revnum, props) = returned[1] else: (paths, revnum, props, has_children) = returned[1] self.assertEqual({"/foo": ("A", None, -1)}, paths) self.assertEqual(revnum, 1) self.assertEqual( set(["svn:date", "svn:author", "svn:log"]), set(props.keys()) ) self.ra.get_log(cb, [""], 0, 0, revprops=["svn:date", "svn:author", "svn:log"]) self.assertEqual(1, len(returned)) self.do_commit() returned = [] self.ra.get_log( cb, None, 0, 1, discover_changed_paths=True, strict_node_history=False, revprops=["svn:date", "svn:author", "svn:log"], ) check_results(returned) def test_get_log_cancel(self): def cb(*args): raise KeyError self.do_commit() self.assertRaises( KeyError, self.ra.get_log, cb, [""], 0, 0, revprops=["svn:date", "svn:author", "svn:log"], ) def test_get_commit_editor_double_close(self): def mycb(*args): pass editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb) dir = editor.open_root() dir.close() self.assertRaises(RuntimeError, dir.close) editor.close() self.assertRaises(RuntimeError, editor.close) self.assertRaises(RuntimeError, editor.abort) def test_get_commit_editor_busy(self): def mycb(rev): pass editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb) self.assertRaises( ra.BusyException, self.ra.get_commit_editor, {"svn:log": "foo"}, mycb ) editor.abort() def test_get_commit_editor_double_open(self): def mycb(rev): pass editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb) root = editor.open_root() root.add_directory("somedir") self.assertRaises(RuntimeError, root.add_directory, "foo") def test_get_commit_editor_custom_revprops(self): if ra.version()[:2] < (1, 5): return def mycb(paths, rev, revprops): pass editor = self.ra.get_commit_editor( {"svn:log": "foo", "bar:foo": "bla", "svn:custom:blie": "bloe"}, mycb ) root = editor.open_root() root.add_directory("somedir").close() root.close() editor.close() revprops = self.ra.rev_proplist(1) self.assertEqual( set(["bar:foo", "svn:author", "svn:custom:blie", "svn:date", "svn:log"]), set(revprops.keys()), "result: %r" % revprops, ) def test_get_commit_editor_context_manager(self): def mycb(paths, rev, revprops): pass editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb) self.assertIs(editor, editor.__enter__()) dir = editor.open_root(0) subdir = dir.add_directory("foo") self.assertIs(subdir, subdir.__enter__()) subdir.__exit__(None, None, None) dir.__exit__(None, None, None) editor.__exit__(None, None, None) def test_get_commit_editor(self): def mycb(paths, rev, revprops): pass editor = self.ra.get_commit_editor({"svn:log": "foo"}, mycb) dir = editor.open_root(0) subdir = dir.add_directory("foo") subdir.close() dir.close() editor.close() def test_get_commit_editor_with_lock_tokens(self): def mycb(paths, rev, revprops): pass editor = self.ra.get_commit_editor( {"svn:log": "with locks"}, mycb, lock_tokens={}, keep_locks=True ) root = editor.open_root() root.close() editor.close() def test_get_log_with_limit(self): self.do_commit() returned = [] def cb(*args): returned.append(args) self.ra.get_log(cb, [""], 0, 1, limit=1, include_merged_revisions=False) self.assertEqual(1, len(returned)) def test_change_rev_prop_old_value(self): self.do_commit() old = self.ra.rev_proplist(1).get("foo") self.ra.change_rev_prop(1, "foo", "bar", old_value=old) self.assertEqual(b"bar", self.ra.rev_proplist(1)["foo"]) def test_do_diff_with_options(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra.do_diff( 1, "", self.ra.get_repos_root(), MyEditor(), recurse=True, ignore_ancestry=True, text_deltas=True, ) reporter.set_path("", 0, True) reporter.finish() def test_replay_send_deltas(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def apply_textdelta(self, base_checksum): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass self.ra.replay(1, 0, MyEditor(), send_deltas=False) def test_replay_range_send_deltas(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def apply_textdelta(self, base_checksum): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass editors = [] def revstart_cb(rev, revprops): e = MyEditor() editors.append(e) return e def revfinish_cb(rev, revprops, editor): pass self.ra.replay_range(1, 1, 0, (revstart_cb, revfinish_cb), send_deltas=False) self.assertEqual(1, len(editors)) def test_do_update_with_options(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra.do_update( 1, "", True, MyEditor(), send_copyfrom_args=True, ignore_ancestry=True ) reporter.set_path("", 0, True) reporter.finish() def test_do_switch_with_options(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra.do_switch( 1, "", True, self.repos_url, MyEditor(), send_copyfrom_args=True, ignore_ancestry=True, ) reporter.set_path("", 0, True) reporter.finish() def test_get_file_revs_include_merged(self): dc = self.commit_editor() f = dc.add_file("filerevs") f.modify(b"v1") dc.close() revs = [] def handler(path, rev, rev_props, prop_diffs=None): revs.append(rev) self.ra.get_file_revs("filerevs", 0, 1, handler, include_merged_revisions=True) self.assertTrue(len(revs) > 0) def test_mergeinfo_with_options(self): self.do_commit() result = self.ra.mergeinfo( [""], revision=1, inherit=ra.MERGEINFO_INHERITED, include_descendants=True ) if result is not None: self.assertIsInstance(result, dict) def test_commit_file_props(self): cb = self.commit_editor() f = cb.add_file("bar") f.modify(b"a") f.change_prop("bla:bar", "blie") cb.close() cb = self.commit_editor() f = cb.open_file("bar") f.change_prop("bla:bar", None) cb.close() stream = BytesIO() props = self.ra.get_file("bar", stream, 1)[1] self.assertEqual(b"blie", props.get("bla:bar")) stream = BytesIO() props = self.ra.get_file("bar", stream, 2)[1] self.assertIs(None, props.get("bla:bar")) def test_get_file_revs(self): cb = self.commit_editor() cb.add_file("bar").modify(b"a") cb.close() cb = self.commit_editor() f = cb.open_file("bar") f.modify(b"b") f.change_prop("bla", "bloe") cb.close() rets = [] def handle(path, rev, props, from_merge=None): rets.append((path, rev, props)) self.ra.get_file_revs("bar", 1, 2, handle) self.assertEqual(2, len(rets)) self.assertEqual(1, rets[0][1]) self.assertEqual(2, rets[1][1]) self.assertEqual("/bar", rets[0][0]) self.assertEqual("/bar", rets[1][0]) def test_get_file(self): cb = self.commit_editor() cb.add_file("bar").modify(b"a") cb.close() stream = BytesIO() self.ra.get_file("bar", stream, 1) stream.seek(0) self.assertEqual(b"a", stream.read()) stream = BytesIO() self.ra.get_file("/bar", stream, 1) stream.seek(0) self.assertEqual(b"a", stream.read()) def test_get_locations_root(self): self.assertEqual({0: "/"}, self.ra.get_locations("", 0, [0])) def test_check_path(self): cb = self.commit_editor() cb.add_dir("bar") cb.close() self.assertEqual(NODE_DIR, self.ra.check_path("bar", 1)) self.assertEqual(NODE_DIR, self.ra.check_path("bar/", 1)) self.assertEqual(NODE_NONE, self.ra.check_path("blaaaa", 1)) def test_stat(self): cb = self.commit_editor() cb.add_dir("bar") cb.close() ret = self.ra.stat("bar", 1) self.assertEqual( set(["last_author", "kind", "created_rev", "has_props", "time", "size"]), set(ret.keys()), ) def test_get_locations_dir(self): cb = self.commit_editor() cb.add_dir("bar") cb.close() cb = self.commit_editor() cb.add_dir("bla", "bar", 1) cb.close() cb = self.commit_editor() cb.delete("bar") cb.close() self.assertEqual( {1: "/bar", 2: "/bla"}, self.ra.get_locations("bla", 2, [1, 2]) ) self.assertEqual( {1: "/bar", 2: "/bar"}, self.ra.get_locations("bar", 1, [1, 2]) ) self.assertEqual( {1: "/bar", 2: "/bar"}, self.ra.get_locations("bar", 2, [1, 2]) ) self.assertEqual( {1: "/bar", 2: "/bla", 3: "/bla"}, self.ra.get_locations("bla", 3, [1, 2, 3]), ) class TestEditorOperations(SubversionTestCase): """Tests for editor operations: delete_entry, open_directory, etc.""" def setUp(self): super(TestEditorOperations, self).setUp() self.repos_url = self.make_repository("d") def commit_editor(self): return self.get_commit_editor(self.repos_url) def test_delete_entry(self): dc = self.commit_editor() dc.add_file("todelete").modify(b"bye") dc.close() dc = self.commit_editor() dc.delete("todelete") dc.close() r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) self.assertEqual(NODE_NONE, r.check_path("todelete", 2)) def test_open_directory(self): dc = self.commit_editor() subdir = dc.add_dir("mydir") subdir.add_file("mydir/inner").modify(b"data") dc.close() dc = self.commit_editor() subdir = dc.open_dir("mydir") subdir.add_file("mydir/another").modify(b"more") dc.close() r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) stream = BytesIO() r.get_file("mydir/another", stream, 2) stream.seek(0) self.assertEqual(b"more", stream.read()) def test_dir_change_prop(self): dc = self.commit_editor() subdir = dc.add_dir("propdir") subdir.change_prop("myprop", "myval") dc.close() r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) (dirents, rev, props) = r.get_dir("propdir", 1) self.assertIn("myprop", props) self.assertEqual(b"myval", props["myprop"]) def test_absent_file(self): # absent_file is used by editors to signal a file is not present # We test it via the low-level commit editor r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) editor = r.get_commit_editor({"svn:log": "absent test"}) root = editor.open_root() root.absent_file("ghost") root.close() editor.close() def test_absent_directory(self): r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) editor = r.get_commit_editor({"svn:log": "absent dir test"}) root = editor.open_root() root.absent_directory("ghostdir") root.close() editor.close() class AuthTests(TestCase): def test_not_list(self): self.assertRaises(TypeError, ra.Auth, ra.get_simple_provider()) def test_not_registered(self): auth = ra.Auth([]) self.assertRaises( SubversionException, auth.credentials, "svn.simple", "MyRealm" ) def test_simple(self): auth = ra.Auth( [ ra.get_simple_prompt_provider( lambda realm, uname, may_save: ("foo", "geheim", False), 0 ) ] ) creds = auth.credentials("svn.simple", "MyRealm") self.assertEqual(("foo", "geheim", 0), next(creds)) self.assertRaises(StopIteration, next, creds) def test_username(self): auth = ra.Auth( [ ra.get_username_prompt_provider( lambda realm, may_save: ("somebody", False), 0 ) ] ) creds = auth.credentials("svn.username", "MyRealm") self.assertEqual(("somebody", 0), next(creds)) self.assertRaises(StopIteration, next, creds) def test_client_cert(self): auth = ra.Auth( [ ra.get_ssl_client_cert_prompt_provider( lambda realm, may_save: ("filename", False), 0 ) ] ) creds = auth.credentials("svn.ssl.client-cert", "MyRealm") self.assertEqual(("filename", False), next(creds)) self.assertRaises(StopIteration, next, creds) def test_client_cert_pw(self): auth = ra.Auth( [ ra.get_ssl_client_cert_pw_prompt_provider( lambda realm, may_save: ("supergeheim", False), 0 ) ] ) creds = auth.credentials("svn.ssl.client-passphrase", "MyRealm") self.assertEqual(("supergeheim", False), next(creds)) self.assertRaises(StopIteration, next, creds) def test_server_trust(self): auth = ra.Auth( [ ra.get_ssl_server_trust_prompt_provider( lambda realm, failures, certinfo, may_save: (42, False) ) ] ) auth.set_parameter("svn:auth:ssl:failures", 23) creds = auth.credentials("svn.ssl.server", "MyRealm") self.assertEqual((42, 0), next(creds)) self.assertRaises(StopIteration, next, creds) def test_server_untrust(self): auth = ra.Auth( [ ra.get_ssl_server_trust_prompt_provider( lambda realm, failures, certinfo, may_save: None ) ] ) auth.set_parameter("svn:auth:ssl:failures", 23) creds = auth.credentials("svn.ssl.server", "MyRealm") self.assertRaises(StopIteration, next, creds) def test_retry(self): self.i = 0 def inc_foo(realm, may_save): self.i += 1 return ("somebody%d" % self.i, False) auth = ra.Auth([ra.get_username_prompt_provider(inc_foo, 2)]) creds = auth.credentials("svn.username", "MyRealm") self.assertEqual(("somebody1", 0), next(creds)) self.assertEqual(("somebody2", 0), next(creds)) self.assertEqual(("somebody3", 0), next(creds)) self.assertRaises(StopIteration, next, creds) def test_set_default_username(self): a = ra.Auth([]) a.set_parameter("svn:auth:username", "foo") self.assertEqual("foo", a.get_parameter("svn:auth:username")) def test_set_default_password(self): a = ra.Auth([]) a.set_parameter("svn:auth:password", "bar") self.assertEqual("bar", a.get_parameter("svn:auth:password")) def test_platform_auth_providers(self): ra.Auth(ra.get_platform_specific_client_providers()) class TestProviders(TestCase): def test_get_simple_provider(self): provider = ra.get_simple_provider() self.assertIsNotNone(provider) def test_get_username_provider(self): provider = ra.get_username_provider() self.assertIsNotNone(provider) def test_get_ssl_client_cert_file_provider(self): provider = ra.get_ssl_client_cert_file_provider() self.assertIsNotNone(provider) def test_get_ssl_client_cert_pw_file_provider(self): provider = ra.get_ssl_client_cert_pw_file_provider() self.assertIsNotNone(provider) def test_get_ssl_server_trust_file_provider(self): provider = ra.get_ssl_server_trust_file_provider() self.assertIsNotNone(provider) def test_print_modules(self): result = ra.print_modules() self.assertIsInstance(result, bytes) self.assertIn(b"ra_local", result) class TestRemoteAccessProperties(SubversionTestCase): def setUp(self): super(TestRemoteAccessProperties, self).setUp() self.repos_url = self.make_repository("d") self.ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]) ) def tearDown(self): del self.ra_ctx super(TestRemoteAccessProperties, self).tearDown() def test_url_property(self): self.assertEqual(self.repos_url, self.ra_ctx.url) def test_busy_property(self): self.assertFalse(self.ra_ctx.busy) def test_get_lock_nonexistent(self): self.do_commit() lock = self.ra_ctx.get_lock("/nonexistent") self.assertIsNone(lock) def test_has_capability_mergeinfo(self): result = self.ra_ctx.has_capability("mergeinfo") self.assertIsInstance(result, bool) def test_has_capability_unknown(self): self.assertRaises(SubversionException, self.ra_ctx.has_capability, "bogus") def do_commit(self): dc = self.get_commit_editor(self.repos_url) dc.add_dir("foo") dc.close() def test_do_update(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_update(1, "", True, MyEditor()) reporter.set_path("", 0, True) reporter.finish() def test_replay(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass self.ra_ctx.replay(1, 0, MyEditor()) def test_get_location_segments(self): self.do_commit() segments = [] def rcvr(range_start, range_end, path): segments.append((range_start, range_end, path)) self.ra_ctx.get_location_segments("foo", 1, 1, 0, rcvr) self.assertEqual(1, len(segments)) self.assertEqual("foo", segments[0][2]) def test_get_commit_editor_abort(self): def mycb(*args): pass editor = self.ra_ctx.get_commit_editor({"svn:log": "test"}, mycb) editor.abort() def test_editor_dir_context_manager(self): def mycb(*args): pass editor = self.ra_ctx.get_commit_editor({"svn:log": "test"}, mycb) with editor: root = editor.open_root() with root: subdir = root.add_directory("mydir") with subdir: pass def test_get_locks_empty(self): self.do_commit() locks = self.ra_ctx.get_locks("") self.assertIsInstance(locks, dict) self.assertEqual({}, locks) def test_lock_unlock(self): # Create a file to lock dc = self.get_commit_editor(self.repos_url) f = dc.add_file("lockme") f.modify(b"content") dc.close() lock_results = [] def lock_cb(path, do_lock, lock, ra_err): lock_results.append((path, do_lock, lock, ra_err)) self.ra_ctx.lock({b"lockme": 1}, "locking", False, lock_cb) self.assertEqual(1, len(lock_results)) # Verify lock exists locks = self.ra_ctx.get_locks("") self.assertIn("/lockme", locks) # Lock is a tuple: (path, token, owner, comment, is_dav_comment, # creation_date, expiration_date) lock_tuple = locks["/lockme"] token = lock_tuple[1] # Now unlock unlock_results = [] def unlock_cb(path, do_lock, lock, ra_err): unlock_results.append((path, do_lock, lock, ra_err)) self.ra_ctx.unlock({b"lockme": token}, False, unlock_cb) self.assertEqual(1, len(unlock_results)) # Verify lock is gone locks = self.ra_ctx.get_locks("") self.assertEqual({}, locks) def test_get_locks_with_lock(self): dc = self.get_commit_editor(self.repos_url) f = dc.add_file("myfile") f.modify(b"data") dc.close() def lock_cb(path, do_lock, lock, ra_err): pass self.ra_ctx.lock({b"myfile": 1}, "test lock", False, lock_cb) locks = self.ra_ctx.get_locks("") self.assertIn("/myfile", locks) # Lock is a tuple: (path, token, owner, comment, ...) lock_tuple = locks["/myfile"] self.assertIsInstance(lock_tuple, tuple) self.assertEqual("/myfile", lock_tuple[0]) # path self.assertIsNotNone(lock_tuple[1]) # token self.assertEqual("test lock", lock_tuple[3]) # comment # cleanup self.ra_ctx.unlock({b"myfile": lock_tuple[1]}, False, lock_cb) def test_get_locks_with_depth(self): dc = self.get_commit_editor(self.repos_url) f = dc.add_file("lockdepth") f.modify(b"data") dc.close() def lock_cb(path, do_lock, lock, ra_err): pass self.ra_ctx.lock({b"lockdepth": 1}, "depth lock", False, lock_cb) locks = self.ra_ctx.get_locks("", ra.DEPTH_INFINITY) self.assertIn("/lockdepth", locks) # cleanup lock_tuple = locks["/lockdepth"] self.ra_ctx.unlock({b"lockdepth": lock_tuple[1]}, False, lock_cb) def test_do_switch(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def open_directory(self, *args): return MyDirEditor() def open_file(self, *args): return MyFileEditor() def delete_entry(self, *args): pass def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_switch(1, "", True, self.repos_url, MyEditor()) reporter.set_path("", 0, True) reporter.finish() def test_replay_range(self): self.do_commit() # Create another commit dc = self.get_commit_editor(self.repos_url) dc.add_file("bar").modify(b"content") dc.close() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass def apply_textdelta(self, base_checksum=None): return None class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass editors = [] def revstart_cb(rev, revprops): e = MyEditor() editors.append(e) return e def revfinish_cb(rev, revprops, editor): pass self.ra_ctx.replay_range(1, 2, 0, (revstart_cb, revfinish_cb)) self.assertEqual(2, len(editors)) def test_constructor_client_string_func(self): def client_string_func(): return "test-client/1.0" ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]), client_string_func=client_string_func, ) self.assertIsNotNone(ra_ctx) ra_ctx.get_latest_revnum() del ra_ctx def test_constructor_uuid(self): ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]), uuid=self.ra_ctx.get_uuid(), ) self.assertIsNotNone(ra_ctx) del ra_ctx def test_constructor_progress_cb(self): progress_calls = [] def progress_cb(progress, total): progress_calls.append((progress, total)) ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]), progress_cb=progress_cb, ) ra_ctx.get_latest_revnum() del ra_ctx def test_get_simple_provider_callback(self): def simple_cb(realm, username, may_save): return ("user", "pass", False) provider = ra.get_simple_provider(simple_cb) self.assertIsNotNone(provider) def test_constructor_open_tmp_file_func(self): import tempfile def open_tmp_file(): return tempfile.mktemp() ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]), open_tmp_file_func=open_tmp_file, ) self.assertIsNotNone(ra_ctx) ra_ctx.get_latest_revnum() del ra_ctx def test_progress_func(self): progress_calls = [] def progress_cb(progress, total): progress_calls.append((progress, total)) ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]) ) ra_ctx.progress_func = progress_cb ra_ctx.get_latest_revnum() del ra_ctx def test_constructor_config(self): from subvertpy import client config = client.get_config() ra_ctx = ra.RemoteAccess( self.repos_url, auth=ra.Auth([ra.get_username_provider()]), config=config ) self.assertIsNotNone(ra_ctx) ra_ctx.get_latest_revnum() del ra_ctx def test_reporter_set_path_with_depth(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def delete_entry(self, *args): pass def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_update(1, "", True, MyEditor()) reporter.set_path("", 0, True, None, ra.DEPTH_INFINITY) reporter.finish() def test_reporter_link_path_with_options(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def open_directory(self, *args): return MyDirEditor() def open_file(self, *args): return MyFileEditor() def delete_entry(self, *args): pass def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_update(1, "", True, MyEditor()) reporter.set_path("", 0, True) reporter.link_path( "foo", self.repos_url + "/foo", 1, True, None, ra.DEPTH_INFINITY ) reporter.finish() def test_reporter_delete_path(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def delete_entry(self, *args): pass def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_update(1, "", True, MyEditor()) reporter.set_path("", 0, True) reporter.delete_path("foo") reporter.finish() def test_mergeinfo_empty(self): self.do_commit() result = self.ra_ctx.mergeinfo(["foo"]) # No mergeinfo set, so result should be None or empty if result is not None: self.assertIsInstance(result, dict) def test_reporter_link_path(self): self.do_commit() class MyFileEditor: def change_prop(self, name, val): pass def close(self, checksum=None): pass class MyDirEditor: def change_prop(self, name, val): pass def add_directory(self, *args): return MyDirEditor() def open_directory(self, *args): return MyDirEditor() def add_file(self, *args): return MyFileEditor() def open_file(self, *args): return MyFileEditor() def delete_entry(self, *args): pass def close(self): pass class MyEditor: def set_target_revision(self, rev): pass def open_root(self, base_rev): return MyDirEditor() def close(self): pass reporter = self.ra_ctx.do_update(1, "", True, MyEditor()) reporter.set_path("", 0, True) # link_path(path, url, revision, start_empty) reporter.link_path("foo", self.repos_url + "/foo", 1, True) reporter.finish() class ConstantsTests(TestCase): def test_depth_constants(self): self.assertIsInstance(ra.DEPTH_UNKNOWN, int) self.assertIsInstance(ra.DEPTH_EXCLUDE, int) self.assertIsInstance(ra.DEPTH_EMPTY, int) self.assertIsInstance(ra.DEPTH_FILES, int) self.assertIsInstance(ra.DEPTH_IMMEDIATES, int) self.assertIsInstance(ra.DEPTH_INFINITY, int) def test_dirent_constants(self): self.assertIsInstance(ra.DIRENT_KIND, int) self.assertIsInstance(ra.DIRENT_SIZE, int) self.assertIsInstance(ra.DIRENT_HAS_PROPS, int) self.assertIsInstance(ra.DIRENT_CREATED_REV, int) self.assertIsInstance(ra.DIRENT_TIME, int) self.assertIsInstance(ra.DIRENT_LAST_AUTHOR, int) self.assertIsInstance(ra.DIRENT_ALL, int) def test_mergeinfo_constants(self): self.assertIsInstance(ra.MERGEINFO_EXPLICIT, int) self.assertIsInstance(ra.MERGEINFO_INHERITED, int) self.assertIsInstance(ra.MERGEINFO_NEAREST_ANCESTOR, int) def test_svn_revision(self): self.assertIsInstance(ra.SVN_REVISION, int) subvertpy_0.11.1.orig/subvertpy/tests/test_ra_svn.py0000644000000000000000000004225315153660370017726 0ustar00# Copyright (C) 2006-2008 Jelmer Vernooij # 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 """Tests for subvertpy.ra_svn.""" from io import StringIO from subvertpy.marshall import literal, marshall from subvertpy.ra_svn import ( CAPABILITIES, MAX_VERSION, MECHANISMS, MIN_VERSION, SVN_PORT, SVNConnection, SVNServer, Editor, DirectoryEditor, FileEditor, Reporter, SSHSubprocess, SSHVendor, mark_busy, unmarshall_dirent, ) from subvertpy.tests import TestCase class MarkBusyTests(TestCase): def test_sets_and_clears_busy(self): class Obj: busy = False @mark_busy def do_thing(self): return self.busy obj = Obj() self.assertFalse(obj.busy) result = obj.do_thing() self.assertTrue(result) # was True during execution self.assertFalse(obj.busy) # cleared after def test_clears_busy_on_exception(self): class Obj: busy = False @mark_busy def do_fail(self): raise ValueError("fail") obj = Obj() self.assertRaises(ValueError, obj.do_fail) self.assertFalse(obj.busy) def test_preserves_docstring(self): class Obj: @mark_busy def do_thing(self): """My docstring.""" pass self.assertEqual("My docstring.", Obj.do_thing.__doc__) def test_preserves_name(self): class Obj: @mark_busy def do_thing(self): pass self.assertEqual("do_thing", Obj.do_thing.__name__) def test_returns_value(self): class Obj: busy = False @mark_busy def do_thing(self): return 42 obj = Obj() self.assertEqual(42, obj.do_thing()) class UnmarshallDirentTests(TestCase): def test_basic(self): d = ["file.txt", "file", 1234, True, 5, [], []] result = unmarshall_dirent(d) self.assertEqual("file.txt", result["name"]) self.assertEqual("file", result["kind"]) self.assertEqual(1234, result["size"]) self.assertTrue(result["has-props"]) self.assertEqual(5, result["created-rev"]) self.assertNotIn("created-date", result) self.assertNotIn("last-author", result) def test_with_date_and_author(self): d = ["dir", "dir", 0, False, 3, "2024-01-01T00:00:00.000000Z", "admin"] result = unmarshall_dirent(d) self.assertEqual("2024-01-01T00:00:00.000000Z", result["created-date"]) self.assertEqual("admin", result["last-author"]) def test_has_props_converted_to_bool(self): d = ["f", "file", 0, 0, 1, [], []] result = unmarshall_dirent(d) self.assertFalse(result["has-props"]) d2 = ["f", "file", 0, 1, 1, [], []] result2 = unmarshall_dirent(d2) self.assertTrue(result2["has-props"]) class SVNConnectionTests(TestCase): def test_send_msg(self): sent = [] def send_fn(data): sent.append(data) conn = SVNConnection(None, send_fn) conn.send_msg([1, 2, 3]) self.assertEqual(1, len(sent)) self.assertEqual(marshall([1, 2, 3]), sent[0]) def test_send_success(self): sent = [] def send_fn(data): sent.append(data) conn = SVNConnection(None, send_fn) conn.send_success(1, 2) self.assertEqual(1, len(sent)) expected = marshall([literal("success"), [1, 2]]) self.assertEqual(expected, sent[0]) def test_send_success_no_args(self): sent = [] def send_fn(data): sent.append(data) conn = SVNConnection(None, send_fn) conn.send_success() expected = marshall([literal("success"), []]) self.assertEqual(expected, sent[0]) def test_inbuffer_initialized(self): conn = SVNConnection(None, None) self.assertEqual("", conn.inbuffer) class EditorTests(TestCase): def setUp(self): super(EditorTests, self).setUp() self.sent = [] class MockConn: _open_ids = [] def send_msg(_, data): self.sent.append(data) self.conn = MockConn() def test_set_target_revision(self): editor = Editor(self.conn) editor.set_target_revision(5) self.assertEqual(1, len(self.sent)) self.assertEqual(literal("target-rev"), self.sent[0][0]) self.assertEqual([5], self.sent[0][1]) def test_close(self): editor = Editor(self.conn) editor.close() self.assertEqual(literal("close-edit"), self.sent[0][0]) def test_abort(self): editor = Editor(self.conn) editor.abort() self.assertEqual(literal("abort-edit"), self.sent[0][0]) def test_open_root_returns_directory_editor(self): editor = Editor(self.conn) de = editor.open_root() self.assertIsInstance(de, DirectoryEditor) def test_open_root_no_base_revision(self): editor = Editor(self.conn) editor.open_root() # baserev should be empty list when no base_revision self.assertEqual(literal("open-root"), self.sent[0][0]) self.assertEqual([], self.sent[0][1][0]) def test_open_root_with_base_revision(self): editor = Editor(self.conn) editor.open_root(base_revision=5) self.assertEqual(literal("open-root"), self.sent[0][0]) self.assertEqual([5], self.sent[0][1][0]) class DirectoryEditorTests(TestCase): def setUp(self): super(DirectoryEditorTests, self).setUp() self.sent = [] class MockConn: _open_ids = [] def send_msg(_, data): self.sent.append(data) self.conn = MockConn() self.dir_id = "test-id" self.conn._open_ids.append(self.dir_id) self.de = DirectoryEditor(self.conn, self.dir_id) def test_change_prop(self): self.de.change_prop("svn:log", "value") self.assertEqual(literal("change-dir-prop"), self.sent[0][0]) self.assertEqual([self.dir_id, "svn:log", ["value"]], self.sent[0][1]) def test_change_prop_none_value(self): self.de.change_prop("svn:log", None) self.assertEqual([self.dir_id, "svn:log", []], self.sent[0][1]) def test_add_file_returns_file_editor(self): fe = self.de.add_file("test.txt") self.assertIsInstance(fe, FileEditor) self.assertEqual(literal("add-file"), self.sent[0][0]) def test_add_file_with_copyfrom(self): self.de.add_file("test.txt", copyfrom_path="/trunk/old.txt", copyfrom_rev=3) args = self.sent[0][1] self.assertEqual("test.txt", args[0]) self.assertEqual(["/trunk/old.txt", 3], args[3]) def test_add_file_no_copyfrom(self): self.de.add_file("test.txt") args = self.sent[0][1] self.assertEqual([], args[3]) # empty copyfrom_data def test_add_directory_returns_directory_editor(self): de2 = self.de.add_directory("subdir") self.assertIsInstance(de2, DirectoryEditor) self.assertEqual(literal("add-dir"), self.sent[0][0]) def test_add_directory_with_copyfrom(self): self.de.add_directory("subdir", copyfrom_path="/trunk/old", copyfrom_rev=2) args = self.sent[0][1] self.assertEqual("subdir", args[0]) self.assertEqual(["/trunk/old", 2], args[3]) def test_add_directory_no_copyfrom(self): self.de.add_directory("subdir") args = self.sent[0][1] self.assertEqual([], args[3]) def test_open_file(self): fe = self.de.open_file("file.txt", 3) self.assertIsInstance(fe, FileEditor) self.assertEqual(literal("open-file"), self.sent[0][0]) args = self.sent[0][1] self.assertEqual("file.txt", args[0]) self.assertEqual(3, args[3]) def test_open_directory(self): de2 = self.de.open_directory("subdir", 2) self.assertIsInstance(de2, DirectoryEditor) self.assertEqual(literal("open-dir"), self.sent[0][0]) args = self.sent[0][1] self.assertEqual("subdir", args[0]) self.assertEqual(2, args[3]) def test_delete_entry(self): self.de.delete_entry("file.txt", 3) self.assertEqual(literal("delete-entry"), self.sent[0][0]) def test_close(self): # DirectoryEditor.__init__ appends the id to _open_ids, # and close() pops the last one initial_count = self.conn._open_ids.count(self.dir_id) self.de.close() self.assertEqual(literal("close-dir"), self.sent[0][0]) self.assertEqual(initial_count - 1, self.conn._open_ids.count(self.dir_id)) class FileEditorTests(TestCase): def setUp(self): super(FileEditorTests, self).setUp() self.sent = [] class MockConn: _open_ids = [] def send_msg(_, data): self.sent.append(data) self.conn = MockConn() self.file_id = "file-id" self.conn._open_ids.append(self.file_id) self.fe = FileEditor(self.conn, self.file_id) def test_change_prop(self): self.fe.change_prop("svn:mime-type", "text/plain") self.assertEqual(literal("change-file-prop"), self.sent[0][0]) self.assertEqual( [self.file_id, "svn:mime-type", ["text/plain"]], self.sent[0][1] ) def test_change_prop_none(self): self.fe.change_prop("svn:mime-type", None) self.assertEqual([self.file_id, "svn:mime-type", []], self.sent[0][1]) def test_close_no_checksum(self): self.fe.close() self.assertEqual(literal("close-file"), self.sent[0][0]) self.assertEqual([self.file_id, []], self.sent[0][1]) def test_close_with_checksum(self): self.fe.close(checksum="abc123") self.assertEqual([self.file_id, ["abc123"]], self.sent[0][1]) def test_apply_textdelta(self): handler = self.fe.apply_textdelta() self.assertTrue(callable(handler)) # Should have sent apply-textdelta and the SVN header chunk self.assertEqual(literal("apply-textdelta"), self.sent[0][0]) self.assertEqual(literal("textdelta-chunk"), self.sent[1][0]) def test_apply_textdelta_with_checksum(self): self.fe.apply_textdelta(base_checksum="md5sum") self.assertEqual([self.file_id, ["md5sum"]], self.sent[0][1]) def test_apply_textdelta_handler_sends_chunks(self): handler = self.fe.apply_textdelta() self.sent.clear() # Send a delta window (None signals end) handler(None) self.assertEqual(literal("textdelta-end"), self.sent[0][0]) def test_apply_textdelta_handler_sends_window(self): from subvertpy.delta import TXDELTA_NEW handler = self.fe.apply_textdelta() self.sent.clear() window = (0, 0, 3, 1, [(TXDELTA_NEW, 0, 3)], b"foo") handler(window) self.assertEqual(literal("textdelta-chunk"), self.sent[0][0]) # The second element should be the file_id and packed window data self.assertEqual(self.file_id, self.sent[0][1][0]) class SSHSubprocessTests(TestCase): def test_get_filelike_channels(self): class FakeProc: stdout = "fake_stdout" stdin = "fake_stdin" proc = FakeProc() ssh = SSHSubprocess(proc) stdout, stdin = ssh.get_filelike_channels() self.assertEqual("fake_stdout", stdout) self.assertEqual("fake_stdin", stdin) class SSHVendorTests(TestCase): def test_is_instantiable(self): vendor = SSHVendor() self.assertTrue(hasattr(vendor, "connect_ssh")) class ReporterTests(TestCase): def setUp(self): super(ReporterTests, self).setUp() self.sent = [] class MockConn: busy = True def send_msg(_, data): self.sent.append(data) self.conn = MockConn() def test_set_path_basic(self): reporter = Reporter(self.conn, None) reporter.set_path("path", 5) self.assertEqual(literal("set-path"), self.sent[0][0]) args = self.sent[0][1] self.assertEqual("path", args[0]) self.assertEqual(5, args[1]) self.assertFalse(args[2]) # start_empty default self.assertEqual([], args[3]) # no lock_token def test_set_path_start_empty(self): reporter = Reporter(self.conn, None) reporter.set_path("path", 5, start_empty=True) args = self.sent[0][1] self.assertTrue(args[2]) def test_set_path_with_lock_token(self): reporter = Reporter(self.conn, None) reporter.set_path("path", 5, lock_token="token123") args = self.sent[0][1] self.assertEqual(["token123"], args[3]) def test_set_path_with_depth(self): reporter = Reporter(self.conn, None) reporter.set_path("path", 5, depth="infinity") args = self.sent[0][1] self.assertEqual("infinity", args[4]) def test_delete_path(self): reporter = Reporter(self.conn, None) reporter.delete_path("deleted/path") self.assertEqual(literal("delete-path"), self.sent[0][0]) self.assertEqual(["deleted/path"], self.sent[0][1]) def test_link_path_basic(self): reporter = Reporter(self.conn, None) reporter.link_path("path", "svn://example.com/repo", 3) self.assertEqual(literal("link-path"), self.sent[0][0]) args = self.sent[0][1] self.assertEqual("path", args[0]) self.assertEqual("svn://example.com/repo", args[1]) self.assertEqual(3, args[2]) self.assertFalse(args[3]) # start_empty default self.assertEqual([], args[4]) # no lock_token def test_link_path_with_lock_token(self): reporter = Reporter(self.conn, None) reporter.link_path("path", "svn://example.com", 3, lock_token="tok") args = self.sent[0][1] self.assertEqual(["tok"], args[4]) def test_link_path_with_depth(self): reporter = Reporter(self.conn, None) reporter.link_path("path", "svn://example.com", 3, depth="files") args = self.sent[0][1] self.assertEqual("files", args[5]) def test_abort(self): reporter = Reporter(self.conn, None) reporter.abort() self.assertEqual(literal("abort-report"), self.sent[0][0]) self.assertFalse(self.conn.busy) class SVNServerMutterTests(TestCase): def test_mutter_with_logf(self): logf = StringIO() sent = [] def send_fn(data): sent.append(data) # Create SVNServer without full init (which sends greeting) server = SVNServer.__new__(SVNServer) server._logf = logf server._stop = False server.mutter("hello world") self.assertEqual("hello world\n", logf.getvalue()) def test_mutter_without_logf(self): server = SVNServer.__new__(SVNServer) server._logf = None server._stop = False # Should not raise server.mutter("hello world") def test_close_sets_stop(self): server = SVNServer.__new__(SVNServer) server._stop = False server.close() self.assertTrue(server._stop) def test_send_failure(self): sent = [] def send_fn(data): sent.append(data) server = SVNServer.__new__(SVNServer) server.send_fn = send_fn server.inbuffer = "" server.recv_fn = None server.send_failure([210001, "Unknown command", "file.py", 1]) self.assertEqual(1, len(sent)) def test_send_ack(self): sent = [] def send_fn(data): sent.append(data) server = SVNServer.__new__(SVNServer) server.send_fn = send_fn server.inbuffer = "" server.recv_fn = None server.send_ack() self.assertEqual(1, len(sent)) def test_send_unknown(self): sent = [] def send_fn(data): sent.append(data) server = SVNServer.__new__(SVNServer) server.send_fn = send_fn server.inbuffer = "" server.recv_fn = None server.send_unknown("bogus-cmd") self.assertEqual(1, len(sent)) class ConstantsTests(TestCase): def test_svn_port(self): self.assertEqual(3690, SVN_PORT) def test_min_version(self): self.assertEqual(2, MIN_VERSION) def test_max_version(self): self.assertEqual(2, MAX_VERSION) def test_capabilities(self): self.assertIsInstance(CAPABILITIES, list) self.assertIn("edit-pipeline", CAPABILITIES) def test_mechanisms(self): self.assertIsInstance(MECHANISMS, list) self.assertIn("ANONYMOUS", MECHANISMS) subvertpy_0.11.1.orig/subvertpy/tests/test_repos.py0000644000000000000000000002672015154541453017570 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion repository library tests.""" from io import BytesIO import os import textwrap from subvertpy import repos, SubversionException from subvertpy.tests import SubversionTestCase, TestCaseInTempDir, TestCase class VersionTest(TestCase): def test_version_length(self): self.assertEqual(4, len(repos.version())) def test_api_version_length(self): self.assertEqual(4, len(repos.api_version())) def test_api_version_later_same(self): self.assertTrue(repos.api_version() <= repos.version()) class TestRepository(TestCaseInTempDir): def setUp(self): super(TestRepository, self).setUp() def test_create(self): repos.create(os.path.join(self.test_dir, "foo")) def test_capability(self): r = repos.create(os.path.join(self.test_dir, "foo")) self.assertIsInstance(r.has_capability("mergeinfo"), bool) def test_verify_fs(self): r = repos.create(os.path.join(self.test_dir, "foo")) f = BytesIO() r.verify_fs(f, 0, 0) self.assertEqual(b"* Verified revision 0.\n", f.getvalue()) def test_open(self): repos.create(os.path.join(self.test_dir, "foo")) repos.Repository("foo") def test_uuid(self): repos.create(os.path.join(self.test_dir, "foo")) self.assertEqual(36, len(repos.Repository("foo").fs().get_uuid())) def test_youngest_rev(self): repos.create(os.path.join(self.test_dir, "foo")) self.assertEqual(0, repos.Repository("foo").fs().youngest_revision()) def test_rev_root(self): repos.create(os.path.join(self.test_dir, "foo")) self.assertTrue(repos.Repository("foo").fs().revision_root(0) is not None) def test_load_fs_with_hooks(self): r = repos.create(os.path.join(self.test_dir, "hooks_test")) dumpfile = textwrap.dedent("""\ SVN-fs-dump-format-version: 2 UUID: 38f0a982-fd1f-4e00-aa6b-a20720f4b9ca Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2011-08-26T13:08:30.187858Z PROPS-END """).encode("ascii") feedback = BytesIO() r.load_fs( BytesIO(dumpfile), feedback, repos.LOAD_UUID_DEFAULT, use_pre_commit_hook=False, use_post_commit_hook=False, ) def test_hotcopy(self): src = os.path.join(self.test_dir, "hotcopy_src") dest = os.path.join(self.test_dir, "hotcopy_dest") repos.create(src) repos.hotcopy(src, dest, True) self.assertTrue(os.path.exists(dest)) def test_load_fs_invalid(self): r = repos.create(os.path.join(self.test_dir, "foo")) dumpfile = b"Malformed" feedback = BytesIO() self.assertRaises( SubversionException, r.load_fs, BytesIO(dumpfile), feedback, repos.LOAD_UUID_DEFAULT, ) def test_load_fs(self): r = repos.create(os.path.join(self.test_dir, "foo")) dumpfile = textwrap.dedent("""\ SVN-fs-dump-format-version: 2 UUID: 38f0a982-fd1f-4e00-aa6b-a20720f4b9ca Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2011-08-26T13:08:30.187858Z PROPS-END """).encode("ascii") feedback = BytesIO() r.load_fs(BytesIO(dumpfile), feedback, repos.LOAD_UUID_DEFAULT) self.assertEqual(r.fs().get_uuid(), "38f0a982-fd1f-4e00-aa6b-a20720f4b9ca") def test_rev_props(self): repos.create(os.path.join(self.test_dir, "foo")) self.assertEqual( ["svn:date"], list(repos.Repository("foo").fs().revision_proplist(0).keys()) ) def test_rev_root_invalid(self): repos.create(os.path.join(self.test_dir, "foo")) self.assertRaises( SubversionException, repos.Repository("foo").fs().revision_root, 1 ) def test_pack_fs(self): r = repos.create(os.path.join(self.test_dir, "foo")) r.pack_fs() def test_pack_fs_with_notify(self): r = repos.create(os.path.join(self.test_dir, "pack_notify")) notifications = [] def notify_func(shard, action): notifications.append((shard, action)) r.pack_fs(notify_func) def test_create_with_config_none(self): path = os.path.join(self.test_dir, "cfg_repo") r = repos.create(path, config=None) self.assertIsNotNone(r) def test_create_with_fs_config_none(self): path = os.path.join(self.test_dir, "fscfg_repo") r = repos.create(path, fs_config=None) self.assertIsNotNone(r) def test_load_fs_with_parent_dir(self): r = repos.create(os.path.join(self.test_dir, "parentdir")) dumpfile = textwrap.dedent("""\ SVN-fs-dump-format-version: 2 UUID: 38f0a982-fd1f-4e00-aa6b-a20720f4b9ca Revision-number: 0 Prop-content-length: 56 Content-length: 56 K 8 svn:date V 27 2011-08-26T13:08:30.187858Z PROPS-END """).encode("ascii") feedback = BytesIO() r.load_fs(BytesIO(dumpfile), feedback, repos.LOAD_UUID_DEFAULT, parent_dir="") def test_paths_changed(self): repos.create(os.path.join(self.test_dir, "foo")) root = repos.Repository("foo").fs().revision_root(0) self.assertEqual({}, root.paths_changed()) def test_is_dir(self): repos.create(os.path.join(self.test_dir, "foo")) root = repos.Repository("foo").fs().revision_root(0) self.assertEqual(True, root.is_dir("")) # TODO(jelmer): Newer versions of libsvn_repos crash when passed a # nonexistant path. # self.assertEqual(False, root.is_dir("nonexistant")) def test_is_file(self): repos.create(os.path.join(self.test_dir, "foo")) root = repos.Repository("foo").fs().revision_root(0) self.assertEqual(False, root.is_file("")) # TODO(jelmer): Newer versions of libsvn_repos crash when passed a # nonexistant path. # self.assertEqual(False, root.is_file("nonexistant")) class TestRepositoryDelete(TestCaseInTempDir): def test_delete(self): path = os.path.join(self.test_dir, "todelete") repos.create(path) self.assertTrue(os.path.exists(path)) repos.delete(path) self.assertFalse(os.path.exists(path)) class TestRepositoryHotcopy(TestCaseInTempDir): def test_hotcopy(self): src = os.path.join(self.test_dir, "src") dest = os.path.join(self.test_dir, "dest") repos.create(src) repos.hotcopy(src, dest) self.assertTrue(os.path.exists(dest)) # Verify the copy is a valid repository r = repos.Repository(dest) self.assertEqual(0, r.fs().youngest_revision()) class TestFileSystemRoot(TestCaseInTempDir): def setUp(self): super(TestFileSystemRoot, self).setUp() self.repo_path = os.path.join(self.test_dir, "repo") repos.create(self.repo_path) def test_file_length(self): # Commit a file first using the repos API r = repos.Repository(self.repo_path) root = r.fs().revision_root(0) # Root has no files, so we just test on what's available # after creation, the root dir exists self.assertTrue(root.is_dir("")) def test_proplist_root(self): r = repos.Repository(self.repo_path) root = r.fs().revision_root(0) props = root.proplist("") self.assertIsInstance(props, dict) class TestFileSystemRootWithFile(SubversionTestCase): """Tests for FileSystemRoot methods that require a committed file.""" def setUp(self): super(TestFileSystemRootWithFile, self).setUp() self.repos_url = self.make_repository("d") dc = self.get_commit_editor(self.repos_url) f = dc.add_file("testfile") f.modify(b"hello world") dc.close() def _get_repo_path(self): # file:// URL to path import sys if sys.platform == "win32": from urllib.request import url2pathname # On Windows, repos_url is file:///D:/... via pathname2url return url2pathname(self.repos_url[len("file:") :]) return self.repos_url[len("file://") :] def test_file_length(self): r = repos.Repository(self._get_repo_path()) root = r.fs().revision_root(1) length = root.file_length("testfile") self.assertEqual(11, length) def test_file_content(self): r = repos.Repository(self._get_repo_path()) root = r.fs().revision_root(1) stream = root.file_content("testfile") data = stream.read() self.assertEqual(b"hello world", data) def test_file_checksum_md5(self): r = repos.Repository(self._get_repo_path()) root = r.fs().revision_root(1) # kind=0 is svn_checksum_md5 checksum = root.file_checksum("testfile", 0) self.assertIsInstance(checksum, str) self.assertEqual(32, len(checksum)) def test_file_checksum_sha1(self): r = repos.Repository(self._get_repo_path()) root = r.fs().revision_root(1) # kind=1 is svn_checksum_sha1 checksum = root.file_checksum("testfile", 1) self.assertIsInstance(checksum, str) self.assertEqual(40, len(checksum)) def test_file_checksum_force(self): r = repos.Repository(self._get_repo_path()) root = r.fs().revision_root(1) checksum = root.file_checksum("testfile", 0, True) self.assertIsInstance(checksum, str) self.assertEqual(32, len(checksum)) class StreamTests(TestCase): def test_read(self): s = repos.Stream() self.assertEqual(b"", s.read()) self.assertEqual(b"", s.read(15)) s.close() def test_write(self): s = repos.Stream() self.assertEqual(0, s.write(b"")) self.assertEqual(2, s.write(b"ab")) s.close() def test_close(self): s = repos.Stream() s.close() def test_read_full(self): s = repos.Stream() self.assertEqual(b"", s.read()) s.close() class ConstantsTests(TestCase): def test_load_uuid_constants(self): self.assertIsInstance(repos.LOAD_UUID_DEFAULT, int) self.assertIsInstance(repos.LOAD_UUID_IGNORE, int) self.assertIsInstance(repos.LOAD_UUID_FORCE, int) def test_path_change_constants(self): self.assertIsInstance(repos.PATH_CHANGE_MODIFY, int) self.assertIsInstance(repos.PATH_CHANGE_ADD, int) self.assertIsInstance(repos.PATH_CHANGE_DELETE, int) self.assertIsInstance(repos.PATH_CHANGE_REPLACE, int) def test_checksum_constants(self): self.assertIsInstance(repos.CHECKSUM_MD5, int) self.assertIsInstance(repos.CHECKSUM_SHA1, int) subvertpy_0.11.1.orig/subvertpy/tests/test_server.py0000644000000000000000000000535615154541453017750 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion server tests.""" import re from subvertpy.server import ( ServerBackend, ServerRepositoryBackend, generate_random_id, ) from subvertpy.tests import TestCase class GenerateRandomIdTests(TestCase): def test_returns_string(self): result = generate_random_id() self.assertIsInstance(result, str) def test_is_uuid_format(self): result = generate_random_id() uuid_re = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-" r"[0-9a-f]{4}-[0-9a-f]{12}$" ) self.assertRegex(result, uuid_re) def test_unique(self): id1 = generate_random_id() id2 = generate_random_id() self.assertNotEqual(id1, id2) class ServerBackendTests(TestCase): def test_open_repository_raises(self): backend = ServerBackend() self.assertRaises(NotImplementedError, backend.open_repository, "/test") class ServerRepositoryBackendTests(TestCase): def setUp(self): super(ServerRepositoryBackendTests, self).setUp() self.backend = ServerRepositoryBackend() def test_get_uuid_raises(self): self.assertRaises(NotImplementedError, self.backend.get_uuid) def test_get_latest_revnum_raises(self): self.assertRaises(NotImplementedError, self.backend.get_latest_revnum) def test_log_raises(self): self.assertRaises( NotImplementedError, self.backend.log, None, "/", 0, 1, True, True, 0 ) def test_update_raises(self): self.assertRaises(NotImplementedError, self.backend.update, None, 1, "/", True) def test_check_path_raises(self): self.assertRaises(NotImplementedError, self.backend.check_path, "/", 0) def test_stat_raises(self): self.assertRaises(NotImplementedError, self.backend.stat, "/", 0) def test_rev_proplist_raises(self): self.assertRaises(NotImplementedError, self.backend.rev_proplist, 0) def test_get_locations_raises(self): self.assertRaises(NotImplementedError, self.backend.get_locations, "/", 0, [0]) subvertpy_0.11.1.orig/subvertpy/tests/test_subr.py0000644000000000000000000000416015154541453017405 0ustar00# Copyright (C) 2017 Jelmer Vernooij # 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 . """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): path = "/foo/bar" if os.name != "nt" else "C:/foo/bar" self.assertEqual(path, abspath(path)) # os.getcwd() returns '/foo/bar' on linux/macos # while it returns 'c:\\foo\\bar' on windows self.assertEqual( os.path.join(os.getcwd(), "bar").replace("\\", "/").lower(), abspath("bar").lower(), ) self.assertEqual( os.path.join(os.getcwd(), "bar", "foo").replace("\\", "/").lower(), abspath("bar/foo").lower(), ) subvertpy_0.11.1.orig/subvertpy/tests/test_wc.py0000644000000000000000000006014615154541453017051 0ustar00# Copyright (C) 2005-2007 Jelmer Vernooij # 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 . """Subversion wc library tests.""" import os from subvertpy import ( wc, ) from subvertpy.tests import ( SubversionTestCase, TestCase, ) class VersionTest(TestCase): def test_version_length(self): self.assertEqual(4, len(wc.version())) def test_api_version_length(self): self.assertEqual(4, len(wc.api_version())) def test_api_version_later_same(self): self.assertTrue(wc.api_version() <= wc.version()) class AdmTests(TestCase): def test_get_adm_dir(self): 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("_svn", wc.get_adm_dir()) finally: wc.set_adm_dir(old_dir_name) def test_is_normal_prop(self): self.assertTrue(wc.is_normal_prop("svn:ignore")) def test_is_normal_prop_false(self): self.assertFalse(wc.is_normal_prop("svn:entry:foo")) self.assertFalse(wc.is_normal_prop("svn:wc:foo")) def test_is_entry_prop(self): self.assertTrue(wc.is_entry_prop("svn:entry:foo")) def test_is_entry_prop_false(self): self.assertFalse(wc.is_entry_prop("svn:ignore")) def test_is_wc_prop(self): self.assertTrue(wc.is_wc_prop("svn:wc:foo")) def test_is_wc_prop_false(self): self.assertFalse(wc.is_wc_prop("svn:ignore")) def test_is_adm_dir(self): self.assertTrue(wc.is_adm_dir(".svn")) self.assertFalse(wc.is_adm_dir("foo")) def test_match_ignore_list(self): 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"])) def test_check_wc_nonexistent(self): import tempfile tmpdir = tempfile.mkdtemp() try: result = wc.check_wc(tmpdir) self.assertEqual(0, result) finally: import shutil shutil.rmtree(tmpdir) def test_get_actual_target(self): result = wc.get_actual_target("/foo/bar") self.assertIsInstance(result, tuple) self.assertEqual(2, len(result)) class WcTests(SubversionTestCase): def test_revision_status(self): self.make_client("repos", "checkout") ret = wc.revision_status("checkout") self.assertEqual((0, 0, 0, 0), ret) def test_revision_status_trailing(self): self.make_client("repos", "checkout") ret = wc.revision_status("checkout/") self.assertEqual((0, 0, 0, 0), ret) def test_revision_status_committed(self): self.make_client("repos", "checkout") ret = wc.revision_status("checkout", committed=True) self.assertEqual((0, 0, 0, 0), ret) def test_revision_status_trail_url(self): self.make_client("repos", "checkout") ret = wc.revision_status("checkout", trail_url=None) self.assertEqual((0, 0, 0, 0), ret) class ContextTests(SubversionTestCase): def setUp(self): super(ContextTests, self).setUp() self.repos_url = self.make_client("repos", "checkout") def test_create_context(self): ctx = wc.Context() self.assertIsNotNone(ctx) def test_locked(self): ctx = wc.Context() result = ctx.locked(os.path.abspath("checkout")) self.assertIsInstance(result, tuple) self.assertEqual(2, len(result)) def test_text_modified(self): ctx = wc.Context() self.build_tree({"checkout/foo": b"content"}) self.client_add("checkout/foo") self.client_commit("checkout", message="add foo") # Unmodified file result = ctx.text_modified(os.path.abspath("checkout/foo")) self.assertFalse(result) # Modify the file self.build_tree({"checkout/foo": b"changed"}) result = ctx.text_modified(os.path.abspath("checkout/foo")) self.assertTrue(result) def test_props_modified(self): ctx = wc.Context() self.build_tree({"checkout/bar": b"content"}) self.client_add("checkout/bar") self.client_commit("checkout", message="add bar") result = ctx.props_modified(os.path.abspath("checkout/bar")) self.assertFalse(result) def test_conflicted(self): ctx = wc.Context() self.build_tree({"checkout/baz": b"content"}) self.client_add("checkout/baz") self.client_commit("checkout", message="add baz") result = ctx.conflicted(os.path.abspath("checkout/baz")) # Returns a tuple of (text_conflicted, prop_conflicted, tree_conflicted) self.assertIsInstance(result, tuple) def test_status(self): ctx = wc.Context() self.build_tree({"checkout/statusfile": b"content"}) self.client_add("checkout/statusfile") result = ctx.status(os.path.abspath("checkout/statusfile")) self.assertIsNotNone(result) def test_get_prop_diffs(self): ctx = wc.Context() self.build_tree({"checkout/propfile": b"content"}) self.client_add("checkout/propfile") self.client_commit("checkout", message="add") result = ctx.get_prop_diffs(os.path.abspath("checkout/propfile")) self.assertIsInstance(result, tuple) self.assertEqual(2, len(result)) def test_walk_status(self): ctx = wc.Context() self.build_tree({"checkout/walkfile": b"content"}) self.client_add("checkout/walkfile") self.client_commit("checkout", message="add walkfile") statuses = [] def receiver(path, status): statuses.append((path, status)) ctx.walk_status(os.path.abspath("checkout"), receiver) self.assertTrue(len(statuses) > 0) def test_walk_status_with_options(self): from subvertpy import ra ctx = wc.Context() self.build_tree({"checkout/walkopt": b"content"}) self.client_add("checkout/walkopt") self.client_commit("checkout", message="add walkopt") statuses = [] def receiver(path, status): statuses.append((path, status)) ctx.walk_status( os.path.abspath("checkout"), receiver, depth=ra.DEPTH_INFINITY, get_all=True, no_ignore=True, ) self.assertTrue(len(statuses) > 0) def test_walk_status_ignore_patterns(self): ctx = wc.Context() self.build_tree({"checkout/walkign": b"content"}) self.client_add("checkout/walkign") self.client_commit("checkout", message="add walkign") statuses = [] def receiver(path, status): statuses.append((path, status)) ctx.walk_status( os.path.abspath("checkout"), receiver, ignore_patterns=["*.pyc", "*.o"] ) self.assertTrue(len(statuses) > 0) def test_walk_status_ignore_text_mode(self): ctx = wc.Context() self.build_tree({"checkout/walktxt": b"content"}) self.client_add("checkout/walktxt") self.client_commit("checkout", message="add walktxt") statuses = [] def receiver(path, status): statuses.append((path, status)) ctx.walk_status(os.path.abspath("checkout"), receiver, ignore_text_mode=True) self.assertTrue(len(statuses) > 0) def test_crawl_revisions_with_options(self): ctx = wc.Context() self.build_tree({"checkout/crawlopt": b"data"}) self.client_add("checkout/crawlopt") self.client_commit("checkout", message="add crawlopt") class MockReporter: def __init__(self): self.calls = [] def set_path(self, path, revision, start_empty, lock_token, depth): self.calls.append(("set_path", path)) def finish(self): self.calls.append(("finish",)) def delete_path(self, path): pass def link_path(self, path, url, revision, start_empty, lock_token, depth): pass def abort(self): pass reporter = MockReporter() ctx.crawl_revisions( os.path.abspath("checkout"), reporter, restore_files=True, honor_depth_exclude=False, depth_compatibility_trick=True, use_commit_times=True, ) call_types = [c[0] for c in reporter.calls] self.assertIn("set_path", call_types) self.assertIn("finish", call_types) def test_add_from_disk_no_lock(self): ctx = wc.Context() self.build_tree({"checkout/diskfile": b"disk content"}) from subvertpy import SubversionException # add_from_disk requires a write lock self.assertRaises( SubversionException, ctx.add_from_disk, os.path.abspath("checkout/diskfile") ) def test_add_lock_requires_write_lock(self): ctx = wc.Context() self.build_tree({"checkout/lockwc": b"data"}) self.client_add("checkout/lockwc") self.client_commit("checkout", message="add lockwc") lock = wc.Lock(token="opaquelocktoken:test-token") from subvertpy import SubversionException # add_lock requires a write lock on the WC self.assertRaises( SubversionException, ctx.add_lock, os.path.abspath("checkout/lockwc"), lock ) def test_remove_lock_requires_write_lock(self): ctx = wc.Context() self.build_tree({"checkout/rmlockwc": b"data"}) self.client_add("checkout/rmlockwc") self.client_commit("checkout", message="add rmlockwc") from subvertpy import SubversionException self.assertRaises( SubversionException, ctx.remove_lock, os.path.abspath("checkout/rmlockwc") ) def test_crawl_revisions(self): ctx = wc.Context() self.build_tree({"checkout/crawlfile": b"data"}) self.client_add("checkout/crawlfile") self.client_commit("checkout", message="add crawlfile") class MockReporter: def __init__(self): self.calls = [] def set_path(self, path, revision, start_empty, lock_token, depth): self.calls.append(("set_path", path, revision, start_empty)) def finish(self): self.calls.append(("finish",)) def delete_path(self, path): self.calls.append(("delete_path", path)) def link_path(self, path, url, revision, start_empty, lock_token, depth): self.calls.append(("link_path", path, url)) def abort(self): self.calls.append(("abort",)) reporter = MockReporter() ctx.crawl_revisions(os.path.abspath("checkout"), reporter, restore_files=False) self.assertTrue(len(reporter.calls) > 0) # Should have called set_path at least once and finish call_types = [c[0] for c in reporter.calls] self.assertIn("set_path", call_types) self.assertIn("finish", call_types) def test_get_update_editor(self): ctx = wc.Context() self.build_tree({"checkout/updfile": b"data"}) self.client_add("checkout/updfile") self.client_commit("checkout", message="add updfile") editor = ctx.get_update_editor(os.path.abspath("checkout"), "") self.assertIsNotNone(editor) editor.abort() def test_get_update_editor_with_options(self): from subvertpy import ra ctx = wc.Context() self.build_tree({"checkout/upd2": b"data"}) self.client_add("checkout/upd2") self.client_commit("checkout", message="add upd2") editor = ctx.get_update_editor( os.path.abspath("checkout"), "", use_commit_times=True, depth=ra.DEPTH_INFINITY, depth_is_sticky=False, allow_unver_obstructions=False, adds_as_modification=True, server_performs_filtering=False, clean_checkout=False, ) self.assertIsNotNone(editor) editor.abort() def test_get_update_editor_preserved_exts(self): ctx = wc.Context() self.build_tree({"checkout/upd3": b"data"}) self.client_add("checkout/upd3") self.client_commit("checkout", message="add upd3") editor = ctx.get_update_editor( os.path.abspath("checkout"), "", preserved_exts=[".mine", ".theirs"] ) self.assertIsNotNone(editor) editor.abort() def test_get_update_editor_notify_func(self): ctx = wc.Context() self.build_tree({"checkout/upd4": b"data"}) self.client_add("checkout/upd4") self.client_commit("checkout", message="add upd4") notifications = [] def notify(info): notifications.append(info) editor = ctx.get_update_editor( os.path.abspath("checkout"), "", notify_func=notify ) self.assertIsNotNone(editor) editor.abort() def test_get_update_editor_conflict_func_not_implemented(self): ctx = wc.Context() self.build_tree({"checkout/upd5": b"data"}) self.client_add("checkout/upd5") self.client_commit("checkout", message="add upd5") self.assertRaises( NotImplementedError, ctx.get_update_editor, os.path.abspath("checkout"), "", conflict_func=lambda: None, ) def test_get_update_editor_external_func_not_implemented(self): ctx = wc.Context() self.build_tree({"checkout/upd6": b"data"}) self.client_add("checkout/upd6") self.client_commit("checkout", message="add upd6") self.assertRaises( NotImplementedError, ctx.get_update_editor, os.path.abspath("checkout"), "", external_func=lambda: None, ) def test_get_update_editor_dirents_func_not_implemented(self): ctx = wc.Context() self.build_tree({"checkout/upd7": b"data"}) self.client_add("checkout/upd7") self.client_commit("checkout", message="add upd7") self.assertRaises( NotImplementedError, ctx.get_update_editor, os.path.abspath("checkout"), "", dirents_func=lambda: None, ) def test_add_from_disk_with_props(self): ctx = wc.Context() self.build_tree({"checkout/diskprops": b"data"}) from subvertpy import SubversionException # add_from_disk requires a write lock, but we can test that # the props parameter is accepted self.assertRaises( SubversionException, ctx.add_from_disk, os.path.abspath("checkout/diskprops"), props={b"svn:eol-style": b"native"}, ) def test_add_from_disk_skip_checks(self): ctx = wc.Context() self.build_tree({"checkout/diskskip": b"data"}) from subvertpy import SubversionException # add_from_disk requires a write lock self.assertRaises( SubversionException, ctx.add_from_disk, os.path.abspath("checkout/diskskip"), skip_checks=True, ) def test_add_from_disk_notify(self): ctx = wc.Context() self.build_tree({"checkout/disknot": b"data"}) from subvertpy import SubversionException notifications = [] def notify_cb(info): notifications.append(info) # add_from_disk requires a write lock self.assertRaises( SubversionException, ctx.add_from_disk, os.path.abspath("checkout/disknot"), notify=notify_cb, ) def test_crawl_revisions_with_notify(self): ctx = wc.Context() self.build_tree({"checkout/crawlnot": b"data"}) self.client_add("checkout/crawlnot") self.client_commit("checkout", message="add crawlnot") class MockReporter: def __init__(self): self.calls = [] def set_path(self, path, revision, start_empty, lock_token, depth): self.calls.append(("set_path", path)) def finish(self): self.calls.append(("finish",)) def delete_path(self, path): pass def link_path(self, path, url, revision, start_empty, lock_token, depth): pass def abort(self): pass reporter = MockReporter() notifications = [] def notify_cb(info): notifications.append(info) ctx.crawl_revisions( os.path.abspath("checkout"), reporter, restore_files=True, notify=notify_cb ) call_types = [c[0] for c in reporter.calls] self.assertIn("set_path", call_types) self.assertIn("finish", call_types) def test_ensure_adm(self): from subvertpy import repos as svn_repos repo = svn_repos.Repository("repos") uuid = repo.fs().get_uuid() ctx = wc.Context() # ensure_adm on an existing checkout should succeed ctx.ensure_adm( os.path.abspath("checkout"), self.repos_url, self.repos_url, uuid, 0 ) def test_ensure_adm_with_depth(self): from subvertpy import repos as svn_repos, ra repo = svn_repos.Repository("repos") uuid = repo.fs().get_uuid() ctx = wc.Context() ctx.ensure_adm( os.path.abspath("checkout"), self.repos_url, self.repos_url, uuid, 0, depth=ra.DEPTH_INFINITY, ) def test_committed_queue_create(self): queue = wc.CommittedQueue() self.assertIsNotNone(queue) def test_committed_queue_queue(self): ctx = wc.Context() self.build_tree({"checkout/qfile": b"queue data"}) self.client_add("checkout/qfile") self.client_commit("checkout", message="add qfile") queue = wc.CommittedQueue() queue.queue(os.path.abspath("checkout/qfile"), ctx) def test_process_committed_queue_requires_write_lock(self): from subvertpy import SubversionException ctx = wc.Context() self.build_tree({"checkout/pcqfile": b"data"}) self.client_add("checkout/pcqfile") self.client_commit("checkout", message="add pcqfile") queue = wc.CommittedQueue() queue.queue(os.path.abspath("checkout/pcqfile"), ctx) self.assertRaises( SubversionException, ctx.process_committed_queue, queue, 1, "2026-01-01T00:00:00.000000Z", "testuser", ) class LockTests(TestCase): def test_create_lock(self): lock = wc.Lock() self.assertIsNotNone(lock) def test_create_lock_with_token(self): lock = wc.Lock(token="opaquelocktoken:test") self.assertEqual(b"opaquelocktoken:test", lock.token) def test_path_default(self): lock = wc.Lock() self.assertIsNone(lock.path) def test_set_path(self): lock = wc.Lock() lock.path = b"/some/path" self.assertEqual("/some/path", lock.path) def test_token_default(self): lock = wc.Lock() self.assertIsNone(lock.token) def test_set_token(self): lock = wc.Lock() lock.token = b"opaquelocktoken:abc" self.assertEqual(b"opaquelocktoken:abc", lock.token) class PristineTests(SubversionTestCase): def setUp(self): super(PristineTests, self).setUp() self.repos_url = self.make_client("repos", "checkout") def test_get_pristine_contents(self): self.build_tree({"checkout/pristfile": b"pristine data"}) self.client_add("checkout/pristfile") self.client_commit("checkout", message="add pristfile") stream = wc.get_pristine_contents(os.path.abspath("checkout/pristfile")) self.assertIsNotNone(stream) def test_get_pristine_copy_path(self): import warnings self.build_tree({"checkout/cpfile": b"data"}) self.client_add("checkout/cpfile") self.client_commit("checkout", message="add cpfile") with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) result = wc.get_pristine_copy_path(os.path.abspath("checkout/cpfile")) self.assertIsNotNone(result) class EnsureAdmTests(SubversionTestCase): def test_ensure_adm_bogus_url(self): from subvertpy import SubversionException self.make_client("repos", "checkout") self.assertRaises( SubversionException, wc.ensure_adm, "checkout", "fake-uuid", "file:///fake" ) def test_ensure_adm_with_repos_and_rev(self): from subvertpy import repos as svn_repos repos_url = self.make_client("repos", "checkout") repo = svn_repos.Repository("repos") uuid = repo.fs().get_uuid() wc.ensure_adm("checkout", uuid, repos_url, repos=repos_url, rev=0) def test_ensure_adm_with_depth(self): from subvertpy import repos as svn_repos, ra repos_url = self.make_client("repos", "checkout") repo = svn_repos.Repository("repos") uuid = repo.fs().get_uuid() wc.ensure_adm( "checkout", uuid, repos_url, repos=repos_url, rev=0, depth=ra.DEPTH_INFINITY ) class CleanupTests(SubversionTestCase): def test_cleanup(self): self.make_client("repos", "checkout") # cleanup on a clean working copy should succeed wc.cleanup("checkout") def test_cleanup_with_diff3_cmd(self): self.make_client("repos", "checkout") wc.cleanup("checkout", diff3_cmd=None) class ConstantsTests(TestCase): def test_schedule_constants(self): self.assertEqual(0, wc.SCHEDULE_NORMAL) self.assertEqual(1, wc.SCHEDULE_ADD) self.assertEqual(2, wc.SCHEDULE_DELETE) self.assertEqual(3, wc.SCHEDULE_REPLACE) def test_conflict_choose_constants(self): self.assertIsInstance(wc.CONFLICT_CHOOSE_POSTPONE, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_BASE, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_THEIRS_FULL, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_MINE_FULL, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_THEIRS_CONFLICT, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_MINE_CONFLICT, int) self.assertIsInstance(wc.CONFLICT_CHOOSE_MERGED, int) def test_status_constants(self): self.assertIsInstance(wc.STATUS_NONE, int) self.assertIsInstance(wc.STATUS_UNVERSIONED, int) self.assertIsInstance(wc.STATUS_NORMAL, int) self.assertIsInstance(wc.STATUS_ADDED, int) self.assertIsInstance(wc.STATUS_MISSING, int) self.assertIsInstance(wc.STATUS_DELETED, int) self.assertIsInstance(wc.STATUS_REPLACED, int) self.assertIsInstance(wc.STATUS_MODIFIED, int) self.assertIsInstance(wc.STATUS_MERGED, int) self.assertIsInstance(wc.STATUS_CONFLICTED, int) self.assertIsInstance(wc.STATUS_IGNORED, int) self.assertIsInstance(wc.STATUS_OBSTRUCTED, int) self.assertIsInstance(wc.STATUS_EXTERNAL, int) self.assertIsInstance(wc.STATUS_INCOMPLETE, int) def test_translate_constants(self): self.assertIsInstance(wc.TRANSLATE_FROM_NF, int) self.assertIsInstance(wc.TRANSLATE_TO_NF, int) self.assertIsInstance(wc.TRANSLATE_FORCE_EOL_REPAIR, int) self.assertIsInstance(wc.TRANSLATE_NO_OUTPUT_CLEANUP, int) self.assertIsInstance(wc.TRANSLATE_FORCE_COPY, int) self.assertIsInstance(wc.TRANSLATE_USE_GLOBAL_TMP, int)