pax_global_header00006660000000000000000000000064121420312610014502gustar00rootroot0000000000000052 comment=11eb06df0468a07e422ababe368bdba283ac4d5e .bzrignore000066400000000000000000000001201214203126100130410ustar00rootroot00000000000000build _trial_temp dist MANIFEST .coverage tags .testrepository .noseids apidocs .gitignore000066400000000000000000000001361214203126100130360ustar00rootroot00000000000000build _trial_temp dist MANIFEST .coverage tags .testrepository .noseids apidocs *.pyc *.so *~ .hgignore000066400000000000000000000000741214203126100126520ustar00rootroot00000000000000syntax: glob build _trial_temp dist MANIFEST .coverage tags .testr.conf000066400000000000000000000002431214203126100131330ustar00rootroot00000000000000[DEFAULT] test_command=PYTHONPATH=. python -m subunit.run $IDOPTION $LISTOPT subvertpy.tests.test_suite test_id_option=--load-list $IDFILE test_list_option=--list AUTHORS000066400000000000000000000004541214203126100121210ustar00rootroot00000000000000Original 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 COPYING000066400000000000000000000636371214203126100121200ustar00rootroot00000000000000 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! INSTALL000066400000000000000000000007561214203126100121070ustar00rootroot00000000000000Requirements ------------ 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.4 is required. Building -------- To build, simply run ``make`` or ``./setup.py build``. Installation ------------ To install, run ``./setup.py install``. MANIFEST.in000066400000000000000000000002021214203126100125760ustar00rootroot00000000000000include MANIFEST.in include subvertpy/*.c include subvertpy/*.h include AUTHORS COPYING INSTALL NEWS TODO include examples/*.py Makefile000066400000000000000000000013561214203126100125130ustar00rootroot00000000000000PYTHON = python PYDOCTOR = pydoctor SETUP = $(PYTHON) setup.py ifeq ($(shell $(PYTHON) -c "import sys; print sys.version_info >= (2, 7)"),True) TESTRUNNER = unittest else TESTRUNNER = unittest2.__main__ endif DEBUGGER ?= RUNTEST = PYTHONPATH=.:$(PYTHONPATH) $(DEBUGGER) $(PYTHON) -m $(TESTRUNNER) all: build build-inplace build:: $(SETUP) build 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" check-one:: $(MAKE) check TEST_OPTIONS=-f clean:: $(SETUP) clean rm -f subvertpy/*.so subvertpy/*.o subvertpy/*.pyc pydoctor: $(PYDOCTOR) --introspect-c-modules -c subvertpy.cfg --make-html NEWS000066400000000000000000000321401214203126100115450ustar00rootroot000000000000000.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. README000066400000000000000000000046461214203126100117400ustar00rootroot00000000000000Subvertpy ========= Homepage: http://samba.org/~jelmer/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.4 or later, 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). A port to Python 3 is planned but has not happened yet. Patches are welcome. 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. On Windows you may have to set various environment variables, see the next section for details. Build instructions for Windows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Install the SVN dev kit ZIP for Windows from http://sourceforge.net/projects/win32svn/files/ E.g. svn-win32-1.4.6_dev.zip * Find the SVN binary ZIP file with the binaries for your dev kit. E.g. svn-win32-1.4.6.zip Unzip this in the *same directory* as the dev kit - README.txt will be overwritten, but that is all. This is the default location the .ZIP file will suggest (ie, the directory embedded in both .zip files are the same) * Set SVN_DEV to point at this directory. * Install BDB. For Subversion 1.7.0 and later: http://www.oracle.com/technetwork/database/berkeleydb/downloads/index-082944.html download Berkeley DB 4.8.30.msi Windows installer and install it. For Subversion 1.6.17 and earlier: http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=688 download "db-4.4.20-win32.zip" or earlier version of BDB and extract it. * Set SVN_BDB to the installed directory or extracted directory. * Install SVN libintl. http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=2627 Download svn-win32-libintl.zip. extract it to the directory that you want. * Set SVN_LIBINTL to the extract dir.TODO000066400000000000000000000001001214203126100115250ustar00rootroot00000000000000- Add more docstrings - Add more examples - More protocol tests bin/000077500000000000000000000000001214203126100116165ustar00rootroot00000000000000bin/subvertpy-fast-export000077500000000000000000000166011214203126100160650ustar00rootroot00000000000000#!/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 trunk_path = '/trunk/' branches_path = '/branches/' tags_path = '/tags/' address = 'localhost' from cStringIO import StringIO import sys, os.path from optparse import OptionParser import stat from time import mktime, strptime from subvertpy.repos import PATH_CHANGE_DELETE, Repository ct_short = ['M', 'A', 'D', 'R', 'X'] def dump_file_blob(root, stream, stream_length): sys.stdout.write("data %s\n" % stream_length) sys.stdout.flush() sys.stdout.write(stream.read()) sys.stdout.write("\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 matches(self, path): return path.startswith(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.iteritems(): 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("link "): sys.stderr.write("special file '%s' is not a symlink, ignoring...\n" % path) continue mode = stat.S_IFLNK stream = StringIO(contents[len("link "):]) stream_length = len(stream.getvalue()) else: if props.get("svn:executable", ""): mode = 0755 else: mode = 0644 stream_length = root.file_length(path) stream = root.file_content(path) file_changes.append("M %o :%s %s" % ( mode, i, marks[i].lstrip("/"))) sys.stdout.write("blob\nmark :%s\n" % i) 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 props.has_key('svn:author'): 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')) sys.stdout.write("commit refs/heads/%s\n" % MATCHER.branchname()) sys.stdout.write("committer %s %s -0000\n" % (author, int(commit_time))) sys.stdout.write("data %s\n" % len(props['svn:log'])) sys.stdout.write(props['svn:log']) sys.stdout.write("\n") sys.stdout.write('\n'.join(file_changes)) sys.stdout.write("\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 xrange(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/(.*)`\nFirst 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 != None: trunk_path = options.trunk_path if options.branches_path != None: branches_path = options.branches_path if options.tags_path != None: tags_path = options.tags_path if options.address != 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 = '' try: import msvcrt msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) except ImportError: pass crawl_revisions(repos_path, first_rev=options.first_rev, final_rev=options.final_rev) examples/000077500000000000000000000000001214203126100126645ustar00rootroot00000000000000examples/ra_commit.py000077500000000000000000000026711214203126100152210ustar00rootroot00000000000000#!/usr/bin/python # Demonstrates how to do a new commit using Subvertpy import os from cStringIO import StringIO 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(StringIO("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() examples/ra_log.py000077500000000000000000000012051214203126100145020ustar00rootroot00000000000000#!/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) in changed_paths.iteritems(): print " %s (%s)" % (path, action) examples/ra_replay.py000077500000000000000000000034021214203126100152160ustar00rootroot00000000000000#!/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) examples/ra_shell.py000077500000000000000000000051371214203126100150400ustar00rootroot00000000000000#!/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 "" print "Changed paths:" for path, (action, from_path, from_rev) in changed_paths.iteritems(): 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) (fetched_rev, props) = conn.get_file(path, sys.stdout, 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)).iteritems(): 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() examples/wc.py000066400000000000000000000007161214203126100136530ustar00rootroot00000000000000#!/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 man/000077500000000000000000000000001214203126100116215ustar00rootroot00000000000000man/subvertpy-fast-export.1000066400000000000000000000023271214203126100162240ustar00rootroot00000000000000.\" 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 http://samba.org/~jelmer/subvertpy/ .BR http://samba.org/~jelmer/subvertpy/ setup.py000077500000000000000000000342011214203126100125630ustar00rootroot00000000000000#!/usr/bin/env python # Setup file for subvertpy # Copyright (C) 2005-2010 Jelmer Vernooij from distutils.core import setup from distutils.extension import Extension from distutils.command.install_lib import install_lib from distutils import log import sys import os import re class CommandException(Exception): """Encapsulate exit status of command execution""" def __init__(self, msg, cmd, arg, status, val): self.message = msg % (cmd, val) Exception.__init__(self, self.message) self.cmd = cmd self.arg = arg self.status = status def not_found(self): return os.WIFEXITED(self.status) and os.WEXITSTATUS(self.status) == 127 def split_shell_results(line): return [s for s in line.split(" ") if s != ""] def run_cmd(cmd, arg): """Run specified command with given arguments, handling status""" f = os.popen("'%s' %s" % (cmd, arg)) dir = f.read().rstrip("\n") status = f.close() if status is None: return dir if os.WIFEXITED(status): code = os.WEXITSTATUS(status) if code == 0: return dir raise CommandException("%s exited with status %d", cmd, arg, status, code) if os.WIFSIGNALED(status): signal = os.WTERMSIG(status) raise CommandException("%s killed by signal %d", cmd, arg, status, signal) raise CommandException("%s terminated abnormally (%d)", cmd, arg, status, status) def config_value(command, arg): cmds = [command] + [os.path.join(p, command) for p in ["/usr/local/apr/bin/", "/opt/local/bin/"]] for cmd in cmds: try: return run_cmd(cmd, arg) except CommandException: _, e, _ = sys.exc_info() if not e.not_found(): raise else: raise Exception("apr-config not found." " Please set APR_CONFIG environment variable") def apr_config(arg): config_cmd = os.getenv("APR_CONFIG") if config_cmd is None: return config_value("apr-1-config", arg) else: return run_cmd(config_cmd, arg) def apu_config(arg): config_cmd = os.getenv("APU_CONFIG") if config_cmd is None: return config_value("apu-1-config", arg) else: return run_cmd(config_cmd, arg) def apr_build_data(): """Determine the APR header file location.""" includedir = apr_config("--includedir") if not os.path.isdir(includedir): raise Exception("APR development headers not found") extra_link_flags = apr_config("--link-ld --libs") return (includedir, split_shell_results(extra_link_flags)) def apu_build_data(): """Determine the APR util header file location.""" includedir = apu_config("--includedir") if not os.path.isdir(includedir): raise Exception("APR util development headers not found") extra_link_flags = apu_config("--link-ld --libs") return (includedir, split_shell_results(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"] 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")], [], []) 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_in, gcc_out, gcc_err) = os.popen3(gcc_command_args) gcc_in.write(""" #include int main(int argc, const char* arv[]) { svn_auth_get_keychain_simple_provider(NULL, NULL); } """) gcc_in.close() gcc_out.read() return (gcc_out.close() is None) 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)) # Windows versions - we use environment variables to locate the directories # and hard-code a list of libraries. if os.name == "nt": def get_apr_version(): apr_version_file = os.path.join(os.environ["SVN_DEV"], r"include\apr\apr_version.h") if not os.path.isfile(apr_version_file): raise Exception( "Please check that your SVN_DEV location is correct.\n" "Unable to find required apr\\apr_version.h file.") query = VersionQuery(apr_version_file) return (query.grep("APR_MAJOR_VERSION"), query.grep("APR_MINOR_VERSION"), query.grep("APR_PATCH_VERSION")) def get_svn_version(): svn_version_file = os.path.join(os.environ["SVN_DEV"], r"include\svn_version.h") if not os.path.isfile(svn_version_file): raise Exception( "Please check that your SVN_DEV location is correct.\n" "Unable to find required svn_version.h file.") query = VersionQuery(svn_version_file) return (query.grep("SVN_VER_MAJOR"), query.grep("SVN_VER_MINOR"), query.grep("SVN_VER_PATCH")) # just clobber the functions above we can't use # for simplicitly, everything is done in the 'svn' one def apr_build_data(): return '.', [] def apu_build_data(): return '.', [] def svn_build_data(): # environment vars for the directories we need. svn_dev_dir = os.environ.get("SVN_DEV") if not svn_dev_dir or not os.path.isdir(svn_dev_dir): raise Exception( "Please set SVN_DEV to the location of the svn development " "packages.\nThese can be downloaded from:\n" "http://sourceforge.net/projects/win32svn/files/") svn_bdb_dir = os.environ.get("SVN_BDB") if not svn_bdb_dir or not os.path.isdir(svn_bdb_dir): raise Exception( "Please set SVN_BDB to the location of the svn BDB packages " "- see README.txt in the SVN_DEV dir") svn_libintl_dir = os.environ.get("SVN_LIBINTL") if not svn_libintl_dir or not os.path.isdir(svn_libintl_dir): raise Exception( "Please set SVN_LIBINTL to the location of the svn libintl " "packages - see README.txt in the SVN_DEV dir") svn_version = get_svn_version() apr_version = get_apr_version() includes = [ # apr dirs. os.path.join(svn_dev_dir, r"include\apr"), os.path.join(svn_dev_dir, r"include\apr-util"), os.path.join(svn_dev_dir, r"include\apr-iconv"), # svn dirs. os.path.join(svn_dev_dir, "include"), ] lib_dirs = [ os.path.join(svn_dev_dir, "lib"), os.path.join(svn_dev_dir, "lib", "apr"), os.path.join(svn_dev_dir, "lib", "apr-iconv"), os.path.join(svn_dev_dir, "lib", "apr-util"), os.path.join(svn_dev_dir, "lib", "neon"), os.path.join(svn_bdb_dir, "lib"), os.path.join(svn_libintl_dir, "lib"), ] aprlibs = """libapr libapriconv libaprutil""".split() if apr_version[0] == 1: aprlibs = [aprlib + "-1" for aprlib in aprlibs] elif apr_version[0] > 1: raise Exception( "You have apr version %d.%d.%d.\n" "This setup only knows how to build with 0.*.* or 1.*.*." % apr_version) libs = """libneon libsvn_subr-1 libsvn_client-1 libsvn_ra-1 libsvn_ra_dav-1 libsvn_ra_local-1 libsvn_ra_svn-1 libsvn_repos-1 libsvn_wc-1 libsvn_delta-1 libsvn_diff-1 libsvn_fs-1 libsvn_repos-1 libsvn_fs_fs-1 libsvn_fs_base-1 intl3_svn xml advapi32 shell32 ws2_32 zlibstat """.split() if svn_version >= (1,7,0): libs += ["libdb48"] else: libs += ["libdb44"] if svn_version >= (1,5,0): # Since 1.5.0 libsvn_ra_dav-1 was removed libs.remove("libsvn_ra_dav-1") return includes, lib_dirs, [], aprlibs+libs, (apr_includedir, apr_link_flags) = apr_build_data() (apu_includedir, apu_link_flags) = apu_build_data() (svn_includedirs, svn_libdirs, svn_link_flags, extra_libs) = svn_build_data() class SvnExtension(Extension): def __init__(self, name, *args, **kwargs): 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 + svn_link_flags if os.name == 'nt': # on windows, just ignore and overwrite the libraries! kwargs["libraries"] = extra_libs # 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')) ) Extension.__init__(self, name, *args, **kwargs) # On Windows, we install the apr binaries too. class install_lib_with_dlls(install_lib): def _get_dlls(self): # return a list of of (FQ-in-name, relative-out-name) tuples. ret = [] apr_bins = [libname + ".dll" for libname in extra_libs if libname.startswith("libapr")] if get_svn_version() >= (1,5,0): # Since 1.5.0 these libraries became shared apr_bins += """libsvn_client-1.dll libsvn_delta-1.dll libsvn_diff-1.dll libsvn_fs-1.dll libsvn_ra-1.dll libsvn_repos-1.dll libsvn_subr-1.dll libsvn_wc-1.dll libsasl.dll""".split() if get_svn_version() >= (1,7,0): apr_bins += ["libdb48.dll"] else: apr_bins += ["libdb44.dll"] apr_bins += """intl3_svn.dll libeay32.dll ssleay32.dll""".split() look_dirs = os.environ.get("PATH","").split(os.pathsep) look_dirs.insert(0, os.path.join(os.environ["SVN_DEV"], "bin")) for bin in apr_bins: for look in look_dirs: f = os.path.join(look, bin) if os.path.isfile(f): target = os.path.join(self.install_dir, "subvertpy", bin) ret.append((f, target)) break else: log.warn("Could not find required DLL %r to include", bin) log.debug("(looked in %s)", look_dirs) return ret def run(self): install_lib.run(self) # the apr binaries. # On Windows we package up the apr dlls with the plugin. for s, d in self._get_dlls(): self.copy_file(s, d) def get_outputs(self): ret = install_lib.get_outputs(self) ret.extend([info[1] for info in self._get_dlls()]) return ret cmdclass = {} if os.name == 'nt': cmdclass['install_lib'] = install_lib_with_dlls def source_path(filename): return os.path.join("subvertpy", filename) 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_subr-1", "svn_ra-1", "svn_wc-1"]), SvnExtension("subvertpy._ra", [source_path(n) for n in ("_ra.c", "util.c", "editor.c")], libraries=["svn_ra-1", "svn_delta-1", "svn_subr-1"]), SvnExtension("subvertpy.repos", [source_path(n) for n in ("repos.c", "util.c")], libraries=["svn_repos-1", "svn_subr-1", "svn_fs-1"]), SvnExtension("subvertpy.wc", [source_path(n) for n in ("wc.c", "util.c", "editor.c")], libraries=["svn_wc-1", "svn_subr-1"]) ] subvertpy_version = (0, 9, 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='http://samba.org/~jelmer/subvertpy', download_url="http://samba.org/~jelmer/subvertpy/subvertpy-%s.tar.gz" % ( subvertpy_version_string, ), license='LGPLv2.1 or later', author='Jelmer Vernooij', author_email='jelmer@samba.org', long_description=""" Alternative Python bindings for Subversion. The goal is to have complete, portable and "Pythonic" Python bindings. """, packages=['subvertpy', 'subvertpy.tests'], ext_modules=subvertpy_modules(), scripts=['bin/subvertpy-fast-export'], cmdclass=cmdclass, ) subvertpy.cfg000066400000000000000000000002131214203126100135660ustar00rootroot00000000000000packages: subvertpy docformat: restructuredtext projectname: subvertpy projecturl: http://samba.org/~jelmer/subverpty/ htmloutput: apidocs subvertpy/000077500000000000000000000000001214203126100131115ustar00rootroot00000000000000subvertpy/__init__.py000066400000000000000000000076601214203126100152330ustar00rootroot00000000000000# 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 " __version__ = (0, 9, 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_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 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, e: raise ImportError("Unable to load subvertpy extensions: %s" % e) subvertpy/_ra.c000066400000000000000000002646211214203126100140310ustar00rootroot00000000000000/* * 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 */ #include #include #include #include #include #include #include #include #include #include #include "editor.h" #include "util.h" #include "ra.h" #if ONLY_SINCE_SVN(1, 5) #define REPORTER_T svn_ra_reporter3_t #else #define REPORTER_T svn_ra_reporter2_t #endif static PyObject *busy_exc; staticforward PyTypeObject Reporter_Type; staticforward PyTypeObject RemoteAccess_Type; staticforward PyTypeObject AuthProvider_Type; staticforward PyTypeObject CredentialsIter_Type; staticforward PyTypeObject Auth_Type; static bool ra_check_svn_path(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) { 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, 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_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; char *root; } RemoteAccessObject; typedef struct { PyObject_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; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN(reporter->reporter->set_path(reporter->report_baton, path, revision, depth, start_empty, lock_token, reporter->pool)); #else if (depth != svn_depth_infinity) { PyErr_SetString(PyExc_NotImplementedError, "depth != infinity only supported for svn >= 1.5"); return NULL; } RUN_SVN(reporter->reporter->set_path(reporter->report_baton, path, revision, start_empty, lock_token, reporter->pool)); #endif 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:kink_path", &path, &url, &revision, &start_empty, &lock_token, &depth)) return NULL; if (reporter->ra == NULL) { PyErr_SetString(PyExc_RuntimeError, "Reporter already finished."); return NULL; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN(reporter->reporter->link_path(reporter->report_baton, path, url, revision, depth, start_empty, lock_token, reporter->pool)); #else if (depth != svn_depth_infinity) { PyErr_SetString(PyExc_NotImplementedError, "depth != infinity only supported for svn >= 1.5"); return NULL; } RUN_SVN(reporter->reporter->link_path(reporter->report_baton, path, url, revision, start_empty, lock_token, reporter->pool)); #endif 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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); } #if ONLY_SINCE_SVN(1, 5) 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, "slOb", 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; } #else static svn_error_t *py_ra_file_rev_handler(void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, 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, "slO", path, rev, py_rev_props); 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; } #endif 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; } #if ONLY_SINCE_SVN(1, 5) 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 = apr_pstrdup(pool, PyString_AsString(ret)); Py_DECREF(ret); PyGILState_Release(state); return NULL; } #endif /* 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_path_join (path, "subvertpy", pool); #if ONLY_SINCE_SVN(1, 6) SVN_ERR (svn_io_open_unique_file3(fp, NULL, path, svn_io_file_del_on_pool_cleanup, pool, pool)); #else SVN_ERR (svn_io_open_unique_file (fp, NULL, path, ".tmp", TRUE, pool)); #endif return NULL; } state = PyGILState_Ensure(); ret = PyObject_CallFunction(self->open_tmp_file_func, ""); CB_CHECK_PYRETVAL(ret); if (PyString_Check(ret)) { char* fname = PyString_AsString(ret); status = apr_file_open(fp, fname, APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, pool); if (status) { PyErr_SetAprStatus(status); Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } Py_DECREF(ret); } else if (PyFile_Check(ret)) { *fp = apr_file_from_object(ret, pool); Py_DECREF(ret); if (!*fp) { PyGILState_Release(state); return py_svn_error(); } } 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; } 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 *url = NULL, *uuid = NULL; 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, "s|OOOOOz", kwnames, &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->root = NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { Py_DECREF(ret); return NULL; } ret->url = svn_path_canonicalize(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; } ret->client_string_func = client_string_func; ret->open_tmp_file_func = open_tmp_file_func; Py_INCREF(client_string_func); 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; Py_INCREF(progress_cb); ret->progress_func = progress_cb; callbacks2->progress_baton = (void *)ret; #if ONLY_SINCE_SVN(1, 5) callbacks2->get_client_string = py_get_client_string; #endif config_hash = config_hash_from_object(config, ret->pool); if (config_hash == NULL) { Py_DECREF(ret); return NULL; } Py_BEGIN_ALLOW_THREADS #if ONLY_SINCE_SVN(1, 5) err = svn_ra_open3(&ret->ra, ret->url, uuid, callbacks2, ret, config_hash, ret->pool); #else if (uuid != NULL) { PyErr_SetString(PyExc_TypeError, "uuid argument not supported with svn 1.4"); Py_DECREF(ret); return NULL; } err = svn_ra_open2(&ret->ra, ret->url, callbacks2, ret, config_hash, ret->pool); #endif 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; #if ONLY_SINCE_SVN(1, 5) RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid2(ra->ra, &uuid, temp_pool)); #else RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_uuid(ra->ra, &uuid, temp_pool)); #endif ret = PyString_FromString(uuid); apr_pool_destroy(temp_pool); return ret; } /** Switch to a different url. */ static PyObject *ra_reparent(PyObject *self, PyObject *args) { char *url; apr_pool_t *temp_pool; RemoteAccessObject *ra = (RemoteAccessObject *)self; if (!PyArg_ParseTuple(args, "s:reparent", &url)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; ra->url = svn_path_canonicalize(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 PyInt_FromLong(latest_revnum); } 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_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (paths == Py_None) { /* The subversion libraries don't behave as expected, * so tweak our own parameters a bit. */ apr_paths = apr_array_make(temp_pool, 1, sizeof(char *)); APR_ARRAY_PUSH(apr_paths, char *) = apr_pstrdup(temp_pool, ""); } else if (!path_list_to_apr_array(temp_pool, paths, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_BEFORE_SVN(1, 5) if (revprops == Py_None) { PyErr_SetString(PyExc_NotImplementedError, "fetching all revision properties not supported"); apr_pool_destroy(temp_pool); return NULL; } else if (!PySequence_Check(revprops)) { PyErr_SetString(PyExc_TypeError, "revprops should be a sequence"); apr_pool_destroy(temp_pool); return NULL; } else { int i; for (i = 0; i < PySequence_Size(revprops); i++) { const char *n = PyString_AsString(PySequence_GetItem(revprops, i)); if (strcmp(SVN_PROP_REVISION_LOG, n) && strcmp(SVN_PROP_REVISION_AUTHOR, n) && strcmp(SVN_PROP_REVISION_DATE, n)) { PyErr_SetString(PyExc_NotImplementedError, "fetching custom revision properties not supported"); apr_pool_destroy(temp_pool); return NULL; } } } if (include_merged_revisions) { PyErr_SetString(PyExc_NotImplementedError, "include_merged_revisions not supported in Subversion 1.4"); apr_pool_destroy(temp_pool); return NULL; } #endif if (!string_list_to_apr_array(temp_pool, revprops, &apr_revprops)) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_SINCE_SVN(1, 5) 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)); #else RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_log(ra->ra, apr_paths, start, end, limit, discover_changed_paths, strict_node_history, py_svn_log_wrapper, callback, temp_pool)); #endif 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; #if ONLY_SINCE_SVN(1, 5) RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_repos_root2(ra->ra, &root, temp_pool)); #else RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_repos_root(ra->ra, &root, temp_pool)); #endif ra->root = apr_pstrdup(ra->pool, root); apr_pool_destroy(temp_pool); } return PyString_FromString(ra->root); } /** * Obtain the URL of this repository. */ static PyObject *ra_get_url(PyObject *self, void *closure) { const char *url; apr_pool_t *temp_pool; PyObject *r; RemoteAccessObject *ra = (RemoteAccessObject *)self; if (ra_check_busy(ra)) return NULL; #if ONLY_SINCE_SVN(1, 5) temp_pool = Pool(NULL); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_session_url(ra->ra, &url, temp_pool)); r = PyString_FromString(url); apr_pool_destroy(temp_pool); return r; #else PyErr_SetString(PyExc_NotImplementedError, "svn_ra_get_session_url only supported for svn >= 1.5"); return NULL; #endif } static PyObject *ra_do_update(PyObject *self, PyObject *args) { svn_revnum_t revision_to_update_to; char *update_target; bool recurse; PyObject *update_editor; const REPORTER_T *reporter; void *report_baton; svn_error_t *err; apr_pool_t *temp_pool; ReporterObject *ret; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_boolean_t send_copyfrom_args = FALSE; if (!PyArg_ParseTuple(args, "lsbO|b:do_update", &revision_to_update_to, &update_target, &recurse, &update_editor, &send_copyfrom_args)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; Py_INCREF(update_editor); #if ONLY_SINCE_SVN(1, 5) Py_BEGIN_ALLOW_THREADS err = svn_ra_do_update2(ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse?svn_depth_infinity:svn_depth_files, send_copyfrom_args, &py_editor, update_editor, temp_pool); #else if (send_copyfrom_args) { PyErr_SetString(PyExc_NotImplementedError, "send_copyfrom_args only supported for svn >= 1.5"); apr_pool_destroy(temp_pool); return NULL; } Py_BEGIN_ALLOW_THREADS err = svn_ra_do_update(ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse, &py_editor, update_editor, temp_pool); #endif 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_do_switch(PyObject *self, PyObject *args) { RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t revision_to_update_to; char *update_target; bool recurse; char *switch_url; PyObject *update_editor; const REPORTER_T *reporter; void *report_baton; apr_pool_t *temp_pool; ReporterObject *ret; svn_error_t *err; if (!PyArg_ParseTuple(args, "lsbsO:do_switch", &revision_to_update_to, &update_target, &recurse, &switch_url, &update_editor)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) { ra->busy = false; return NULL; } Py_INCREF(update_editor); Py_BEGIN_ALLOW_THREADS #if ONLY_SINCE_SVN(1, 5) err = svn_ra_do_switch2( ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse?svn_depth_infinity:svn_depth_files, switch_url, &py_editor, update_editor, temp_pool); #else err = svn_ra_do_switch( ra->ra, &reporter, &report_baton, revision_to_update_to, update_target, recurse, switch_url, &py_editor, update_editor, temp_pool); #endif 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) { apr_pool_destroy(temp_pool); ra->busy = false; 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_do_diff(PyObject *self, PyObject *args) { 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; if (!PyArg_ParseTuple(args, "lssO|bbb:do_diff", &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 #if ONLY_SINCE_SVN(1, 5) 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); #else err = svn_ra_do_diff2(ra->ra, &reporter, &report_baton, revision_to_update_to, diff_target, recurse, ignore_ancestry, text_deltas, versus_url, &py_editor, diff_editor, temp_pool); #endif 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) { RemoteAccessObject *ra = (RemoteAccessObject *)self; apr_pool_t *temp_pool; svn_revnum_t revision, low_water_mark; PyObject *update_editor; bool send_deltas = true; if (!PyArg_ParseTuple(args, "llO|b:replay", &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; } #if ONLY_SINCE_SVN(1, 5) 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 = (PyObject *)replay_baton; PyObject *py_start_fn = PyTuple_GetItem(cbs, 0); PyObject *py_revprops = prop_hash_to_dict(rev_props); PyObject *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(py_start_fn, "lO", revision, 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 = (PyObject *)replay_baton; PyObject *py_finish_fn = PyTuple_GetItem(cbs, 1); PyObject *py_revprops = prop_hash_to_dict(rev_props); PyObject *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(py_finish_fn, "lOO", revision, py_revprops, edit_baton); CB_CHECK_PYRETVAL(ret); Py_DECREF((PyObject *)edit_baton); Py_DECREF(ret); PyGILState_Release(state); return NULL; } #endif static PyObject *ra_replay_range(PyObject *self, PyObject *args) { #if ONLY_SINCE_SVN(1, 5) 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_ParseTuple(args, "lllO|b:replay_range", &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; #else PyErr_SetString(PyExc_NotImplementedError, "svn_ra_replay not available with Subversion 1.4"); return NULL; #endif } 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; #if ONLY_SINCE_SVN(1, 5) apr_hash_t *hash_revprops; #else PyObject *py_log_msg; #endif 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) return NULL; if (lock_tokens == Py_None) { hash_lock_tokens = NULL; } else { Py_ssize_t idx = 0; PyObject *k, *v; hash_lock_tokens = apr_hash_make(pool); while (PyDict_Next(lock_tokens, &idx, &k, &v)) { apr_hash_set(hash_lock_tokens, PyString_AsString(k), PyString_Size(k), PyString_AsString(v)); } } if (!PyDict_Check(revprops)) { apr_pool_destroy(pool); PyErr_SetString(PyExc_TypeError, "Expected dictionary with revision properties"); return NULL; } if (ra_check_busy(ra)) return NULL; Py_INCREF(commit_callback); #if ONLY_SINCE_SVN(1, 5) hash_revprops = prop_dict_to_hash(pool, revprops); if (hash_revprops == NULL) { apr_pool_destroy(pool); ra->busy = false; Py_DECREF(commit_callback); return NULL; } 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); #else /* Check that revprops has only one member named SVN_PROP_REVISION_LOG */ if (PyDict_Size(revprops) != 1) { PyErr_SetString(PyExc_ValueError, "Only svn:log can be set with Subversion 1.4"); apr_pool_destroy(pool); Py_DECREF(commit_callback); ra->busy = false; return NULL; } py_log_msg = PyDict_GetItemString(revprops, SVN_PROP_REVISION_LOG); if (py_log_msg == NULL) { PyErr_SetString(PyExc_ValueError, "Only svn:log can be set with Subversion 1.4."); apr_pool_destroy(pool); Py_DECREF(commit_callback); ra->busy = false; return NULL; } if (!PyString_Check(py_log_msg)) { PyErr_SetString(PyExc_ValueError, "svn:log property should be set to string."); apr_pool_destroy(pool); Py_DECREF(commit_callback); ra->busy = false; return NULL; } Py_BEGIN_ALLOW_THREADS err = svn_ra_get_commit_editor2(ra->ra, &editor, &edit_baton, PyString_AsString(py_log_msg), py_commit_callback, commit_callback, hash_lock_tokens, keep_locks, pool); #endif Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); Py_DECREF(commit_callback); apr_pool_destroy(pool); ra->busy = false; return NULL; } Py_INCREF(ra); return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, ra_done_handler, ra, commit_callback); } static PyObject *ra_change_rev_prop(PyObject *self, PyObject *args) { svn_revnum_t rev; char *name; RemoteAccessObject *ra = (RemoteAccessObject *)self; char *value; int vallen; apr_pool_t *temp_pool; svn_string_t *val_string; if (!PyArg_ParseTuple(args, "lss#:change_rev_prop", &rev, &name, &value, &vallen)) 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); RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_change_rev_prop(ra->ra, rev, name, 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_index_t *idx; apr_hash_t *props; svn_revnum_t fetch_rev; const char *key; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_dirent_t *dirent; apr_ssize_t klen; char *path; svn_revnum_t revision = -1; unsigned int dirent_fields = 0; PyObject *py_dirents, *py_props; char *kwnames[] = { "path", "revision", "fields", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|lI:get_dir", kwnames, &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; /* 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, svn_path_canonicalize(path, temp_pool), revision, dirent_fields, temp_pool)); if (dirents == NULL) { py_dirents = Py_None; Py_INCREF(py_dirents); } else { py_dirents = PyDict_New(); if (py_dirents == NULL) { apr_pool_destroy(temp_pool); 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) { apr_pool_destroy(temp_pool); Py_DECREF(py_dirents); return NULL; } if (key == NULL) { pykey = Py_None; Py_INCREF(pykey); } else { pykey = PyString_FromString((char *)key); } if (PyDict_SetItem(py_dirents, pykey, item) != 0) { Py_DECREF(py_dirents); Py_DECREF(item); Py_DECREF(pykey); apr_pool_destroy(temp_pool); return NULL; } Py_DECREF(pykey); Py_DECREF(item); idx = apr_hash_next(idx); } } py_props = prop_hash_to_dict(props); if (py_props == NULL) { Py_DECREF(py_dirents); apr_pool_destroy(temp_pool); return NULL; } apr_pool_destroy(temp_pool); return Py_BuildValue("(NlN)", py_dirents, fetch_rev, py_props); } static PyObject *ra_get_file(PyObject *self, PyObject *args) { char *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; if (!PyArg_ParseTuple(args, "sO|l:get_file", &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; /* Yuck. Subversion doesn't like leading slashes.. */ while (*path == '/') path++; RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_file(ra->ra, svn_path_canonicalize(path, temp_pool), revision, new_py_stream(temp_pool, py_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) { char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_lock_t *lock; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s:get_lock", &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_lock(ra->ra, &lock, path, temp_pool)); apr_pool_destroy(temp_pool); return wrap_lock(lock); } static PyObject *ra_check_path(PyObject *self, PyObject *args) { char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t revision; svn_node_kind_t kind; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "sl:check_path", &path, &revision)) 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_check_path(ra->ra, svn_path_canonicalize(path, temp_pool), revision, &kind, temp_pool)); apr_pool_destroy(temp_pool); return PyInt_FromLong(kind); } static PyObject *ra_stat(PyObject *self, PyObject *args) { char *path; RemoteAccessObject *ra = (RemoteAccessObject *)self; PyObject *ret; svn_revnum_t revision; svn_dirent_t *dirent; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "sl:stat", &path, &revision)) 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_stat(ra->ra, svn_path_canonicalize(path, temp_pool), 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) { #if ONLY_SINCE_SVN(1, 5) 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); #else PyErr_SetString(PyExc_NotImplementedError, "has_capability is only supported in Subversion >= 1.5"); return NULL; #endif } 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; apr_pool_t *temp_pool; apr_hash_t *hash_path_tokens; if (!PyArg_ParseTuple(args, "ObO:unlock", &path_tokens, &break_lock, &lock_func)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; hash_path_tokens = apr_hash_make(temp_pool); while (PyDict_Next(path_tokens, &idx, &k, &v)) { apr_hash_set(hash_path_tokens, PyString_AsString(k), PyString_Size(k), (char *)PyString_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; } 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)) return NULL; if (ra_check_busy(ra)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; 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 = PyInt_AsLong(v); if (*rev == -1 && PyErr_Occurred()) { apr_pool_destroy(temp_pool); return NULL; } apr_hash_set(hash_path_revs, PyString_AsString(k), PyString_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; } static PyObject *ra_get_locks(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; apr_hash_t *hash_locks; apr_hash_index_t *idx; RemoteAccessObject *ra = (RemoteAccessObject *)self; char *key; apr_ssize_t klen; svn_lock_t *lock; PyObject *ret; if (!PyArg_ParseTuple(args, "s:get_locks", &path)) 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_locks(ra->ra, &hash_locks, path, 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) { char *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, "slO:get_locations", &path, &peg_revision, &location_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_locations(ra->ra, &hash_locations, svn_path_canonicalize(path, temp_pool), peg_revision, revnum_list_to_apr_array(temp_pool, location_revisions), temp_pool)); ret = PyDict_New(); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } for (idx = apr_hash_first(temp_pool, hash_locations); idx != NULL; idx = apr_hash_next(idx)) { apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); if (PyDict_SetItem(ret, PyInt_FromLong(*key), PyString_FromString(val)) != 0) { Py_DECREF(ret); apr_pool_destroy(temp_pool); return NULL; } } apr_pool_destroy(temp_pool); return ret; } #if ONLY_SINCE_SVN(1, 5) static PyObject *range_to_tuple(svn_merge_range_t *range) { return Py_BuildValue("(llb)", range->start, range->end, range->inheritable); } 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; } #endif static PyObject *ra_mergeinfo(PyObject *self, PyObject *args) { #if ONLY_SINCE_SVN(1, 5) 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; svn_boolean_t include_descendants; if (!PyArg_ParseTuple(args, "O|lib:mergeinfo", &paths, &revision, &inherit, &include_descendants)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!path_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; #else PyErr_SetString(PyExc_NotImplementedError, "mergeinfo is only supported in Subversion >= 1.5"); return NULL; #endif } #if ONLY_SINCE_SVN(1, 5) 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; } #endif static PyObject *ra_get_location_segments(PyObject *self, PyObject *args) { #if ONLY_SINCE_SVN(1, 5) RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_revnum_t peg_revision, start_revision, end_revision; char *path; PyObject *py_rcvr; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "slllO:get_location_segments", &path, &peg_revision, &start_revision, &end_revision, &py_rcvr)) return NULL; if (ra_check_svn_path(path)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) 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; #else PyErr_SetString(PyExc_NotImplementedError, "mergeinfo is only supported in Subversion >= 1.5"); return NULL; #endif } static PyObject *ra_get_file_revs(PyObject *self, PyObject *args) { char *path; svn_revnum_t start, end; PyObject *file_rev_handler; apr_pool_t *temp_pool; RemoteAccessObject *ra = (RemoteAccessObject *)self; svn_boolean_t include_merged_revisions = FALSE; if (!PyArg_ParseTuple(args, "sllO|b:get_file_revs", &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; #if ONLY_SINCE_SVN(1, 5) 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)); #else if (include_merged_revisions) { PyErr_SetString(PyExc_NotImplementedError, "include_merged_revisions only supported with svn >= 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_RA_WITH_POOL(temp_pool, ra, svn_ra_get_file_revs(ra->ra, path, start, end, py_ra_file_rev_handler, (void *)file_rev_handler, temp_pool)); #endif 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 PyString_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 }, { NULL } }; #include "_ra_iter_log.c" static PyMethodDef ra_methods[] = { { "get_file_revs", ra_get_file_revs, METH_VARARGS, "S.get_file_revs(path, start_rev, end_revs, handler)" }, { "get_locations", ra_get_locations, METH_VARARGS, "S.get_locations(path, peg_revision, location_revisions)" }, { "get_locks", ra_get_locks, METH_VARARGS, "S.get_locks(path)" }, { "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", ra_mergeinfo, METH_VARARGS, "S.mergeinfo(&paths, revision, inherit, include_descendants)\n" }, { "get_location_segments", ra_get_location_segments, METH_VARARGS, "S.get_location_segments(path, peg_revision, start_revision, " "end_revision, rcvr)" }, { "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", ra_change_rev_prop, METH_VARARGS, "S.change_rev_prop(revnum, name, value)\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", ra_replay, METH_VARARGS, "S.replay(revision, low_water_mark, update_editor, send_deltas=True)\n" "Replay a revision, reporting changes to update_editor." }, { "replay_range", ra_replay_range, METH_VARARGS, "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", ra_do_switch, METH_VARARGS, "S.do_switch(revision_to_update_to, update_target, recurse, switch_url, update_editor)\n" }, { "do_update", ra_do_update, METH_VARARGS, "S.do_update(revision_to_update_to, update_target, recurse, update_editor)\n" }, { "do_diff", ra_do_diff, METH_VARARGS, "S.do_diff(revision_to_update_to, diff_target, versus_url, diff_editor, recurse, ignore_ancestry, text_deltas)\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_url", (PyCFunction)ra_get_url, METH_NOARGS, "S.get_url() -> url\n" "Return the URL of the repository." }, { "get_log", (PyCFunction)ra_get_log, METH_VARARGS|METH_KEYWORDS, "S.get_log(callback, paths, start, end, limit, discover_changed_paths, " "strict_node_history, include_merged_revisions, revprops)\n" }, { "iter_log", (PyCFunction)ra_iter_log, METH_VARARGS|METH_KEYWORDS, "S.get_log(paths, start, end, limit, discover_changed_paths, " "strict_node_history, include_merged_revisions, revprops)\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" }, { "url", T_STRING, offsetof(RemoteAccessObject, url), READONLY, "URL this connection is to" }, { NULL, } }; static PyTypeObject RemoteAccess_Type = { PyObject_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; */ NULL, /* printfunc tp_print; */ 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_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 = { PyObject_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 = PyInt_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 = apr_pstrdup(auth->pool, PyString_AsString(value)); } 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 PyInt_FromLong(*((apr_uint32_t *)value)); } else if (!strcmp(name, SVN_AUTH_PARAM_DEFAULT_USERNAME) || !strcmp(name, SVN_AUTH_PARAM_DEFAULT_PASSWORD)) { return PyString_FromString((const char *)value); } else { PyErr_Format(PyExc_TypeError, "Unsupported auth parameter %s", name); return NULL; } } typedef struct { PyObject_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); } 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); } 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); } 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); } 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); } 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 = { PyObject_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 */ (destructor)credentials_iter_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ (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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "sb", realm, may_save); CB_CHECK_PYRETVAL(ret); if (ret == Py_None) { PyGILState_Release(state); return NULL; } if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with username credentials"); PyGILState_Release(state); return py_svn_error(); } if (PyTuple_Size(ret) != 2) { PyErr_SetString(PyExc_TypeError, "expected tuple with username credentials to be size 2"); PyGILState_Release(state); return py_svn_error(); } 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"); PyGILState_Release(state); return py_svn_error(); } py_username = PyTuple_GetItem(ret, 0); CB_CHECK_PYRETVAL(py_username); if (!PyString_Check(py_username)) { PyErr_SetString(PyExc_TypeError, "username should be string"); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->username = apr_pstrdup(pool, PyString_AsString(py_username)); (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; } 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; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "ssb", realm, username, may_save); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with simple credentials"); PyGILState_Release(state); return py_svn_error(); } if (PyTuple_Size(ret) != 3) { PyErr_SetString(PyExc_TypeError, "expected tuple of size 3"); PyGILState_Release(state); return py_svn_error(); } 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"); PyGILState_Release(state); return py_svn_error(); } py_username = PyTuple_GetItem(ret, 0); CB_CHECK_PYRETVAL(py_username); if (!PyString_Check(py_username)) { PyErr_SetString(PyExc_TypeError, "username should be string"); PyGILState_Release(state); return py_svn_error(); } py_password = PyTuple_GetItem(ret, 1); CB_CHECK_PYRETVAL(py_password); if (!PyString_Check(py_password)) { PyErr_SetString(PyExc_TypeError, "password should be string"); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->username = apr_pstrdup(pool, PyString_AsString(py_username)); (*cred)->password = apr_pstrdup(pool, PyString_AsString(py_password)); (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; } 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, *py_may_save, *py_accepted_failures; PyGILState_STATE state = PyGILState_Ensure(); long 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, "slOb", realm, failures, py_cert, may_save); Py_DECREF(py_cert); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { Py_DECREF(ret); PyErr_SetString(PyExc_TypeError, "expected tuple with server trust credentials"); PyGILState_Release(state); return py_svn_error(); } if (PyTuple_Size(ret) != 2) { Py_DECREF(ret); PyErr_SetString(PyExc_TypeError, "expected tuple of size 2"); PyGILState_Release(state); return py_svn_error(); } py_accepted_failures = PyTuple_GetItem(ret, 0); if (!PyInt_Check(py_accepted_failures)) { Py_DECREF(ret); PyErr_SetString(PyExc_TypeError, "accepted_failures should be integer"); PyGILState_Release(state); return py_svn_error(); } py_may_save = PyTuple_GetItem(ret, 1); if (!PyBool_Check(py_may_save)) { Py_DECREF(ret); PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); PyGILState_Release(state); return py_svn_error(); } accepted_failures = PyInt_AsLong(py_accepted_failures); if (accepted_failures == -1 && PyErr_Occurred()) { Py_DECREF(ret); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->accepted_failures = accepted_failures; (*cred)->may_save = (py_may_save == Py_True); 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_may_save, *py_password; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallFunction(fn, "sb", realm, may_save); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with client cert pw credentials"); PyGILState_Release(state); return py_svn_error(); } if (PyTuple_Size(ret) != 2) { PyErr_SetString(PyExc_TypeError, "expected tuple of size 2"); PyGILState_Release(state); return py_svn_error(); } py_may_save = PyTuple_GetItem(ret, 1); if (!PyBool_Check(py_may_save)) { PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); PyGILState_Release(state); return py_svn_error(); } py_password = PyTuple_GetItem(ret, 0); if (!PyString_Check(py_password)) { PyErr_SetString(PyExc_TypeError, "password should be string"); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->password = apr_pstrdup(pool, PyString_AsString(py_password)); (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; } 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(); ret = PyObject_CallFunction(fn, "sb", realm, may_save); CB_CHECK_PYRETVAL(ret); if (!PyTuple_Check(ret)) { PyErr_SetString(PyExc_TypeError, "expected tuple with client cert credentials"); PyGILState_Release(state); return py_svn_error(); } if (PyTuple_Size(ret) != 2) { PyErr_SetString(PyExc_TypeError, "expected tuple of size 2"); PyGILState_Release(state); return py_svn_error(); } py_may_save = PyTuple_GetItem(ret, 1); if (!PyBool_Check(py_may_save)) { PyErr_SetString(PyExc_TypeError, "may_save should be boolean"); PyGILState_Release(state); return py_svn_error(); } py_cert_file = PyTuple_GetItem(ret, 0); if (!PyString_Check(py_cert_file)) { PyErr_SetString(PyExc_TypeError, "cert_file should be string"); PyGILState_Release(state); return py_svn_error(); } *cred = apr_pcalloc(pool, sizeof(**cred)); (*cred)->cert_file = apr_pstrdup(pool, PyString_AsString(py_cert_file)); (*cred)->may_save = (py_may_save == Py_True); Py_DECREF(ret); PyGILState_Release(state); return NULL; } 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); if (auth->pool == NULL) { PyObject_Del(auth); return NULL; } svn_auth_get_username_provider(&auth->provider, auth->pool); return (PyObject *)auth; } #if ONLY_SINCE_SVN(1, 6) 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); Py_DECREF(ret); PyGILState_Release(state); } return NULL; } #endif 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; #if ONLY_SINCE_SVN(1, 6) Py_INCREF(callback); auth->callback = callback; svn_auth_get_simple_provider2(&auth->provider, py_cb_get_simple_provider_prompt, auth->callback, auth->pool); #else auth->callback = NULL; auth->provider = NULL; if (callback != Py_None) { PyErr_SetString(PyExc_NotImplementedError, "callback not supported with svn < 1.6"); Py_DECREF(auth); return NULL; } svn_auth_get_simple_provider(&auth->provider, auth->pool); #endif 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; #if ONLY_SINCE_SVN(1, 6) svn_auth_get_ssl_client_cert_pw_file_provider2(&auth->provider, NULL, NULL, auth->pool); #else svn_auth_get_ssl_client_cert_pw_file_provider(&auth->provider, auth->pool); #endif 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 = PyString_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; } #if ONLY_SINCE_SVN(1, 5) 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 #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) { #if ONLY_SINCE_SVN(1, 6) /* 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; #else PyObject *pylist = PyList_New(0); PyObject *provider = NULL; if (pylist == NULL) { Py_DECREF(pylist); return NULL; } #if defined(WIN32) || defined(__CYGWIN__) provider = get_windows_simple_provider(self); if (provider == NULL) return NULL; PyList_Append(pylist, provider); Py_DECREF(provider); #if ONLY_SINCE_SVN(1, 5) provider = get_windows_ssl_server_trust_provider(self); if (provider == NULL) return NULL; PyList_Append(pylist, provider); Py_DECREF(provider); #endif /* 1.5 */ #endif /* WIN32 || __CYGWIN__ */ #if defined(SVN_KEYCHAIN_PROVIDER_AVAILABLE) provider = get_keychain_simple_provider(self); if (provider == NULL) return NULL; PyList_Append(pylist, provider); Py_DECREF(provider); #endif return pylist; #endif } 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 }, #if ONLY_SINCE_SVN(1, 5) { "get_windows_ssl_server_trust_provider", (PyCFunction)get_windows_ssl_server_trust_provider, METH_NOARGS, NULL }, #endif #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, } }; void init_ra(void) { static apr_pool_t *pool; PyObject *mod; if (PyType_Ready(&RemoteAccess_Type) < 0) return; if (PyType_Ready(&Editor_Type) < 0) return; if (PyType_Ready(&FileEditor_Type) < 0) return; if (PyType_Ready(&DirectoryEditor_Type) < 0) return; if (PyType_Ready(&Reporter_Type) < 0) return; if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0) return; if (PyType_Ready(&Auth_Type) < 0) return; if (PyType_Ready(&CredentialsIter_Type) < 0) return; if (PyType_Ready(&AuthProvider_Type) < 0) return; if (PyType_Ready(&LogIterator_Type) < 0) return; apr_initialize(); pool = Pool(NULL); if (pool == NULL) return; svn_ra_initialize(pool); PyEval_InitThreads(); mod = Py_InitModule3("_ra", ra_module_methods, "Remote Access"); if (mod == NULL) return; 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); #if ONLY_SINCE_SVN(1, 5) 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); #endif 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); #if ONLY_SINCE_SVN(1, 5) 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); #endif #ifdef SVN_VER_REVISION PyModule_AddIntConstant(mod, "SVN_REVISION", SVN_VER_REVISION); #endif } subvertpy/_ra_iter_log.c000066400000000000000000000257531214203126100157160ustar00rootroot00000000000000/* * 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_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 = { PyObject_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 */ (destructor)log_iter_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ Py_TPFLAGS_HAVE_ITER, /* 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 */ PyObject_SelfIter, /* getiterfunc tp_iter; */ (iternextfunc)log_iter_next, /* iternextfunc tp_iternext; */ }; #if ONLY_SINCE_SVN(1, 5) 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(); #if ONLY_SINCE_SVN(1, 6) py_changed_paths = pyify_changed_paths2(log_entry->changed_paths2, pool); #else py_changed_paths = pyify_changed_paths(log_entry->changed_paths, true, pool); #endif 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; } #else static svn_error_t *py_iter_log_cb(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, *obj, *tuple; LogIteratorObject *iter = (LogIteratorObject *)baton; PyGILState_STATE state; state = PyGILState_Ensure(); py_changed_paths = pyify_changed_paths(changed_paths, true, pool); if (py_changed_paths == NULL) { PyGILState_Release(state); return py_svn_error(); } revprops = PyDict_New(); if (revprops == NULL) { Py_DECREF(py_changed_paths); PyGILState_Release(state); return py_svn_error(); } if (message != NULL) { obj = PyString_FromString(message); PyDict_SetItemString(revprops, SVN_PROP_REVISION_LOG, obj); Py_DECREF(obj); } if (author != NULL) { obj = PyString_FromString(author); PyDict_SetItemString(revprops, SVN_PROP_REVISION_AUTHOR, obj); Py_DECREF(obj); } if (date != NULL) { obj = PyString_FromString(date); PyDict_SetItemString(revprops, SVN_PROP_REVISION_DATE, obj); Py_DECREF(obj); } tuple = Py_BuildValue("NlN", py_changed_paths, revision, revprops); if (tuple == NULL) { Py_DECREF(py_changed_paths); Py_DECREF(revprops); 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; } #endif static void py_iter_log(void *baton) { LogIteratorObject *iter = (LogIteratorObject *)baton; svn_error_t *error; PyGILState_STATE state; #if ONLY_SINCE_SVN(1, 5) 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); #else error = svn_ra_get_log(iter->ra->ra, iter->apr_paths, iter->start, iter->end, iter->limit, iter->discover_changed_paths, iter->strict_node_history, py_iter_log_cb, iter, iter->pool); #endif 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_check_busy(ra)) return NULL; pool = Pool(ra->pool); if (pool == NULL) return NULL; 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 (!path_list_to_apr_array(pool, paths, &apr_paths)) { apr_pool_destroy(pool); return NULL; } #if ONLY_BEFORE_SVN(1, 5) if (revprops == Py_None) { PyErr_SetString(PyExc_NotImplementedError, "fetching all revision properties not supported"); apr_pool_destroy(pool); return NULL; } else if (!PySequence_Check(revprops)) { PyErr_SetString(PyExc_TypeError, "revprops should be a sequence"); apr_pool_destroy(pool); return NULL; } else { int i; for (i = 0; i < PySequence_Size(revprops); i++) { const char *n = PyString_AsString(PySequence_GetItem(revprops, i)); if (strcmp(SVN_PROP_REVISION_LOG, n) && strcmp(SVN_PROP_REVISION_AUTHOR, n) && strcmp(SVN_PROP_REVISION_DATE, n)) { PyErr_SetString(PyExc_NotImplementedError, "fetching custom revision properties not supported"); apr_pool_destroy(pool); return NULL; } } } if (include_merged_revisions) { PyErr_SetString(PyExc_NotImplementedError, "include_merged_revisions not supported in Subversion 1.4"); apr_pool_destroy(pool); return NULL; } #endif if (!string_list_to_apr_array(pool, revprops, &apr_revprops)) { apr_pool_destroy(pool); 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/client.c000066400000000000000000002033671214203126100145460ustar00rootroot00000000000000/* * 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 #include "util.h" #include "ra.h" #include "wc.h" #if ONLY_SINCE_SVN(1, 6) #define INFO_SIZE size64 #define WORKING_SIZE working_size64 #else #define INFO_SIZE size #define WORKING_SIZE working_size #endif extern PyTypeObject Client_Type; extern PyTypeObject Config_Type; extern PyTypeObject ConfigItem_Type; extern PyTypeObject Info_Type; extern PyTypeObject WCInfo_Type; typedef struct { PyObject_HEAD svn_config_t *item; PyObject *parent; } ConfigItemObject; typedef struct { PyObject_HEAD #if ONLY_SINCE_SVN(1, 7) svn_wc_info_t info; #else svn_info_t info; #endif apr_pool_t *pool; } WCInfoObject; typedef struct { PyObject_HEAD #if ONLY_SINCE_SVN(1, 7) svn_client_info2_t info; #else svn_info_t info; #endif WCInfoObject *wc_info; apr_pool_t *pool; } InfoObject; static int client_set_auth(PyObject *self, PyObject *auth, void *closure); static int client_set_config(PyObject *self, PyObject *auth, void *closure); static bool to_opt_revision(PyObject *arg, svn_opt_revision_t *ret) { if (PyInt_Check(arg) || PyLong_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; } else if (arg == Py_None) { ret->kind = svn_opt_revision_unspecified; return true; } else if (PyString_Check(arg)) { char *text = PyString_AsString(arg); if (!strcmp(text, "HEAD")) { ret->kind = svn_opt_revision_head; return true; } else if (!strcmp(text, "WORKING")) { ret->kind = svn_opt_revision_working; return true; } else if (!strcmp(text, "BASE")) { ret->kind = svn_opt_revision_base; return true; } } 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; } #if ONLY_SINCE_SVN(1, 5) static svn_error_t *proplist_receiver(void *prop_list, const char *path, apr_hash_t *prop_hash, apr_pool_t *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(); } if (PyList_Append(prop_list, value) != 0) { PyGILState_Release(state); return py_svn_error(); } PyGILState_Release(state); return NULL; } #endif static svn_error_t *list_receiver(void *dict, const char *path, const svn_dirent_t *dirent, const svn_lock_t *lock, const char *abs_path, 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 (PyDict_SetItemString(dict, path, value) != 0) { Py_DECREF(value); PyGILState_Release(state); return py_svn_error(); } Py_DECREF(value); PyGILState_Release(state); return NULL; } #if ONLY_SINCE_SVN(1, 7) static PyObject *py_info(const svn_client_info2_t *info) #else static PyObject *py_info(const svn_info_t *info) #endif { 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; #if ONLY_SINCE_SVN(1, 7) 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); #else ret->info = *svn_info_dup(info, ret->pool); if (info->has_wc_info) { ret->wc_info->info = *svn_info_dup(info, ret->pool); } #endif return (PyObject *)ret; } static svn_error_t *info_receiver(void *dict, const char *path, #if ONLY_BEFORE_SVN(1, 7) const svn_info_t *info, #else const svn_client_info2_t *info, #endif 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 = PyString_AsString(py_log_msg); } if (py_tmp_file != Py_None) { *tmp_file = PyString_AsString(py_tmp_file); } Py_DECREF(ret); PyGILState_Release(state); return NULL; } static PyObject *py_commit_info_tuple(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_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_context(&ret->client, 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; apr_pool_t *temp_pool; char *kwnames[] = { "path", "recursive", "force", "no_ignore", "add_parents", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|bbbb", kwnames, &path, &recursive, &force, &no_ignore, &add_parents)) return NULL; #if ONLY_BEFORE_SVN(1, 4) if (add_parents == false) { PyErr_SetString(PyExc_NotImplementedError, "Subversion < 1.4 does not support add_parents=false"); return NULL; } #endif temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_add4(path, recursive?svn_depth_infinity:svn_depth_empty, force, no_ignore, add_parents, client->client, temp_pool) ); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_add3(path, recursive, force, no_ignore, client->client, temp_pool) ); #endif 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; char *url, *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, "ss|OObbb", kwnames, &url, &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; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_checkout3(&result_rev, url, svn_path_canonicalize(path, temp_pool), &c_peg_rev, &c_rev, recurse?svn_depth_infinity:svn_depth_files, ignore_externals, allow_unver_obstructions, client->client, temp_pool)); #else if (allow_unver_obstructions) { PyErr_SetString(PyExc_NotImplementedError, "allow_unver_obstructions not supported when built against svn<1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_checkout2(&result_rev, url, svn_path_canonicalize(path, temp_pool), &c_peg_rev, &c_rev, recurse, ignore_externals, client->client, temp_pool)); #endif apr_pool_destroy(temp_pool); return PyLong_FromLong(result_rev); } 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; svn_commit_info_t *commit_info = NULL; PyObject *ret; apr_array_header_t *apr_targets; PyObject *revprops = Py_None; char *kwnames[] = { "targets", "recurse", "keep_locks", "revprops", NULL }; #if ONLY_SINCE_SVN(1, 5) apr_hash_t *hash_revprops; #endif if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|bbO", kwnames, &targets, &recurse, &keep_locks, &revprops)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!path_list_to_apr_array(temp_pool, targets, &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 ONLY_SINCE_SVN(1, 5) 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; } /* FIXME: Support keep_changelist and changelists */ RUN_SVN_WITH_POOL(temp_pool, svn_client_commit4(&commit_info, apr_targets, recurse?svn_depth_infinity:svn_depth_files, keep_locks, false, NULL, hash_revprops, client->client, temp_pool)); #else if (revprops != Py_None && PyDict_Size(revprops) > 0) { PyErr_SetString(PyExc_NotImplementedError, "Setting revision properties only supported on svn > 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_commit3(&commit_info, apr_targets, recurse, keep_locks, client->client, temp_pool)); #endif ret = py_commit_info_tuple(commit_info); apr_pool_destroy(temp_pool); return ret; } 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", NULL }; svn_revnum_t result_rev; svn_opt_revision_t c_peg_rev, c_rev; 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; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OObbbb", kwnames, &from, &to, &rev, &peg_rev, &recurse, &ignore_externals, &overwrite, &native_eol)) 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; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_export4(&result_rev, from, svn_path_canonicalize(to, temp_pool), &c_peg_rev, &c_rev, overwrite, ignore_externals, recurse?svn_depth_infinity:svn_depth_files, native_eol, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_export3(&result_rev, from, svn_path_canonicalize(to, temp_pool), &c_peg_rev, &c_rev, overwrite, ignore_externals, recurse, native_eol, client->client, temp_pool)); #endif 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", 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; PyObject *py_stream; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|OO", kwnames, &path, &py_stream, &rev, &peg_rev)) 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; stream = new_py_stream(temp_pool, py_stream); RUN_SVN_WITH_POOL(temp_pool, svn_client_cat2(stream, path, &c_peg_rev, &c_rev, client->client, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *client_delete(PyObject *self, PyObject *args) { PyObject *paths; bool force=false, keep_local=false; apr_pool_t *temp_pool; svn_commit_info_t *commit_info = NULL; PyObject *ret; apr_array_header_t *apr_paths; ClientObject *client = (ClientObject *)self; if (!PyArg_ParseTuple(args, "O|bb", &paths, &force, &keep_local)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!path_list_to_apr_array(temp_pool, paths, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_delete3(&commit_info, apr_paths, force, keep_local, NULL, client->client, temp_pool)); #else if (keep_local) { PyErr_SetString(PyExc_ValueError, "keep_local not supported against svn 1.4"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_delete2(&commit_info, apr_paths, force, client->client, temp_pool)); #endif ret = py_commit_info_tuple(commit_info); apr_pool_destroy(temp_pool); return ret; } static PyObject *client_mkdir(PyObject *self, PyObject *args) { PyObject *paths, *revprops = NULL; svn_boolean_t make_parents=FALSE; apr_pool_t *temp_pool; svn_commit_info_t *commit_info = NULL; PyObject *ret; apr_array_header_t *apr_paths; apr_hash_t *hash_revprops; ClientObject *client = (ClientObject *)self; if (!PyArg_ParseTuple(args, "O|bO", &paths, &make_parents, &revprops)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!path_list_to_apr_array(temp_pool, paths, &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 ONLY_SINCE_SVN(1, 5) 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_mkdir3(&commit_info, apr_paths, make_parents, hash_revprops, client->client, temp_pool)); #else if (make_parents) { PyErr_SetString(PyExc_ValueError, "make_parents not supported against svn 1.4"); apr_pool_destroy(temp_pool); return NULL; } if (revprops != Py_None) { PyErr_SetString(PyExc_ValueError, "revprops not supported against svn 1.4"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_mkdir2(&commit_info, apr_paths, client->client, temp_pool)); #endif ret = py_commit_info_tuple(commit_info); apr_pool_destroy(temp_pool); return ret; } static PyObject *client_copy(PyObject *self, PyObject *args, PyObject *kwargs) { char *src_path, *dst_path; PyObject *src_rev = Py_None; svn_commit_info_t *commit_info = NULL; apr_pool_t *temp_pool; svn_opt_revision_t c_src_rev; bool copy_as_child = true, make_parents = false; PyObject *ret; apr_hash_t *revprops; bool ignore_externals = false; ClientObject *client = (ClientObject *)self; char *kwnames[] = { "src_path", "dst_path", "src_rev", "copy_as_child", "make_parents", "ignore_externals", "revprpos", NULL }; #if ONLY_SINCE_SVN(1, 4) PyObject *py_revprops = Py_None; #endif #if ONLY_SINCE_SVN(1, 5) apr_array_header_t *src_paths; svn_client_copy_source_t src; #endif if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss|ObbbO", kwnames, &src_path, &dst_path, &src_rev, ©_as_child, &make_parents, &ignore_externals, &py_revprops)) 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; } #if ONLY_BEFORE_SVN(1, 4) if (copy_as_child) { PyErr_SetString(PyExc_NotImplementedError, "copy_as_child not supported in svn < 1.4"); apr_pool_destroy(temp_pool); return NULL; } if (make_parents) { PyErr_SetString(PyExc_NotImplementedError, "make_parents not supported in svn < 1.4"); apr_pool_destroy(temp_pool); return NULL; } if (revprops) { PyErr_SetString(PyExc_NotImplementedError, "revprops not supported in svn < 1.4"); apr_pool_destroy(temp_pool); return NULL; } #endif #if ONLY_BEFORE_SVN(1, 5) if (ignore_externals) { PyErr_SetString(PyExc_NotImplementedError, "ignore_externals not supported in svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } #endif #if ONLY_SINCE_SVN(1, 5) 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_IDX(src_paths, 0, svn_client_copy_source_t *) = &src; #endif #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_client_copy5(&commit_info, src_paths, dst_path, copy_as_child, make_parents, ignore_externals, revprops, client->client, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_copy4(&commit_info, src_paths, dst_path, copy_as_child, make_parents, revprops, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_copy2(&commit_info, src_path, &c_src_rev, dst_path, client->client, temp_pool)); #endif ret = py_commit_info_tuple(commit_info); apr_pool_destroy(temp_pool); return ret; } static PyObject *client_propset(PyObject *self, PyObject *args) { char *propname; svn_string_t c_propval; int vallen; int recurse = true; int skip_checks = false; ClientObject *client = (ClientObject *)self; apr_pool_t *temp_pool; char *target; #if ONLY_SINCE_SVN(1, 5) svn_commit_info_t *commit_info = NULL; #endif PyObject *ret, *py_revprops = Py_None; svn_revnum_t base_revision_for_url = SVN_INVALID_REVNUM; apr_hash_t *revprops; if (!PyArg_ParseTuple(args, "sz#s|bblO", &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; } #if ONLY_SINCE_SVN(1, 5) /* 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); #else if (revprops) { PyErr_SetString(PyExc_NotImplementedError, "revprops not supported with svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_propset2(propname, &c_propval, target, recurse, skip_checks, client->client, temp_pool)); ret = Py_None; Py_INCREF(ret); #endif apr_pool_destroy(temp_pool); return ret; } static PyObject *client_propget(PyObject *self, PyObject *args) { 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; char *target; PyObject *peg_revision = Py_None; PyObject *revision; ClientObject *client = (ClientObject *)self; PyObject *ret; if (!PyArg_ParseTuple(args, "ssO|Ob", &propname, &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; #if ONLY_SINCE_SVN(1, 5) /* FIXME: Support changelists */ /* FIXME: Support actual_revnum */ /* FIXME: Support depth properly */ RUN_SVN_WITH_POOL(temp_pool, svn_client_propget3(&hash_props, propname, target, &c_peg_rev, &c_rev, NULL, recurse?svn_depth_infinity:svn_depth_files, NULL, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_propget2(&hash_props, propname, target, &c_peg_rev, &c_rev, recurse, client->client, temp_pool)); #endif 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; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_proplist3(target, &c_peg_rev, &c_rev, depth, NULL, proplist_receiver, prop_list, client->client, temp_pool)); apr_pool_destroy(temp_pool); #else { apr_array_header_t *props; int i; if (depth != svn_depth_infinity && depth != svn_depth_empty) { PyErr_SetString(PyExc_NotImplementedError, "depth can only be infinity or empty when built against svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_proplist2(&props, target, &c_peg_rev, &c_rev, (depth == svn_depth_infinity), client->client, temp_pool)); for (i = 0; i < props->nelts; i++) { svn_client_proplist_item_t *item; PyObject *prop_dict, *value; item = APR_ARRAY_IDX(props, i, svn_client_proplist_item_t *); prop_dict = prop_hash_to_dict(item->prop_hash); if (prop_dict == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(prop_list); return NULL; } value = Py_BuildValue("(sO)", item->node_name, prop_dict); if (value == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(prop_list); Py_DECREF(prop_dict); return NULL; } if (PyList_Append(prop_list, value) != 0) { apr_pool_destroy(temp_pool); Py_DECREF(prop_list); Py_DECREF(prop_dict); Py_DECREF(value); return NULL; } Py_DECREF(value); } apr_pool_destroy(temp_pool); } #endif return prop_list; } static PyObject *client_resolve(PyObject *self, PyObject *args) { #if ONLY_SINCE_SVN(1, 5) 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; #else PyErr_SetString(PyExc_NotImplementedError, "svn_client_resolve not available with Subversion < 1.5"); return NULL; #endif } 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; svn_boolean_t allow_unver_obstructions = FALSE, depth_is_sticky = FALSE; char *kwnames[] = { "path", "revision", "recurse", "ignore_externals", "depth_is_sticky", "allow_unver_obstructions", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Obbbb", kwnames, &paths, &rev, &recurse, &ignore_externals, &depth_is_sticky, &allow_unver_obstructions)) return NULL; if (!to_opt_revision(rev, &c_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!path_list_to_apr_array(temp_pool, paths, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_update3(&result_revs, apr_paths, &c_rev, recurse?svn_depth_infinity:svn_depth_files, depth_is_sticky, ignore_externals, allow_unver_obstructions, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_update2(&result_revs, apr_paths, &c_rev, recurse, ignore_externals, client->client, temp_pool)); #endif ret = PyList_New(result_revs->nelts); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } for (i = 0; i < result_revs->nelts; i++) { ret_rev = APR_ARRAY_IDX(result_revs, i, svn_revnum_t); if (PyList_SetItem(ret, i, PyLong_FromLong(ret_rev)) != 0) { Py_DECREF(ret); return NULL; } } apr_pool_destroy(temp_pool); return ret; } static PyObject *client_list(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "path", "peg_revision", "depth", "dirents", "revision", 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; PyObject *peg_revision = Py_None, *revision = Py_None; ClientObject *client = (ClientObject *)self; PyObject *entry_dict; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOi|iO", kwnames, &path, &peg_revision, &depth, &dirents, &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; entry_dict = PyDict_New(); if (entry_dict == NULL) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_list2(path, &c_peg_rev, &c_rev, depth, dirents, false, list_receiver, entry_dict, client->client, temp_pool)); #else if (depth != svn_depth_infinity && depth != svn_depth_empty) { PyErr_SetString(PyExc_NotImplementedError, "depth can only be infinity or empty when built against svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_client_list(path, &c_peg_rev, &c_rev, (depth == svn_depth_infinity)?TRUE:FALSE, dirents, false, list_receiver, entry_dict, client->client, temp_pool)); #endif apr_pool_destroy(temp_pool); return entry_dict; } static PyObject *client_diff(PyObject *self, PyObject *args, PyObject *kwargs) { #if ONLY_SINCE_SVN(1, 5) 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); #else PyErr_SetString(PyExc_NotImplementedError, "svn_client_diff4 not available with Subversion < 1.5"); return NULL; #endif } 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; svn_boolean_t 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; #if ONLY_SINCE_SVN(1, 6) svn_opt_revision_range_t revision_range; apr_array_header_t *revision_ranges; #endif 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 ONLY_BEFORE_SVN(1, 5) if (include_merged_revisions) { PyErr_SetString(PyExc_NotImplementedError, "include_merged_revisions not supported in svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } if (revprops) { PyErr_SetString(PyExc_NotImplementedError, "revprops not supported in svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } #endif if (!path_list_to_apr_array(temp_pool, paths, &apr_paths)) { apr_pool_destroy(temp_pool); return NULL; } if (revprops) { if (!path_list_to_apr_array(temp_pool, revprops, &apr_revprops)) { apr_pool_destroy(temp_pool); return NULL; } } #if ONLY_SINCE_SVN(1, 6) revision_range.start = c_start_rev; 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, strict_node_history, include_merged_revisions, apr_revprops, py_svn_log_entry_receiver, (void*)callback, client->client, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_client_log4(apr_paths, &c_peg_rev, &c_start_rev, &c_end_rev, limit, discover_changed_paths, strict_node_history, include_merged_revisions, apr_revprops, py_svn_log_entry_receiver, (void*)callback, client->client, temp_pool)); #elif ONLY_SINCE_SVN(1, 4) RUN_SVN_WITH_POOL(temp_pool, svn_client_log3(apr_paths, &c_peg_rev, &c_start_rev, &c_end_rev, limit, discover_changed_paths, strict_node_history, py_svn_log_wrapper, (void*)callback, client->client, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_client_log2(apr_paths, &c_start_rev, &c_end_rev, limit, discover_changed_paths, strict_node_history, py_svn_log_wrapper, (void*)callback, client->client, temp_pool)); #endif 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_boolean_t 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; #if ONLY_BEFORE_SVN(1, 5) if (depth != svn_depth_infinity && depth != svn_depth_empty) { PyErr_SetString(PyExc_NotImplementedError, "depth can only be infinity or empty when built against svn < 1.5"); return NULL; } #endif 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; #if ONLY_SINCE_SVN(1, 7) /* FIXME: Support changelists */ err = svn_client_info3(path, &c_peg_rev, &c_rev, depth, fetch_excluded, fetch_actual_only, NULL, info_receiver, entry_dict, client->client, temp_pool); #elif ONLY_SINCE_SVN(1, 5) /* FIXME: Support changelists */ err = svn_client_info2(path, &c_peg_rev, &c_rev, info_receiver, entry_dict, depth, NULL, client->client, temp_pool); #else err = svn_client_info(path, &c_peg_rev, &c_rev, info_receiver, entry_dict, (depth == svn_depth_infinity), client->client, temp_pool); #endif 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 PyMethodDef client_methods[] = { { "add", (PyCFunction)client_add, METH_VARARGS|METH_KEYWORDS, "S.add(path, recursive=True, force=False, no_ignore=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) -> (revnum, date, author)" }, { "delete", client_delete, METH_VARARGS, "S.delete(paths, force=False)" }, { "copy", (PyCFunction)client_copy, METH_VARARGS|METH_KEYWORDS, "S.copy(src_path, dest_path, srv_rev=None)" }, { "propset", client_propset, METH_VARARGS, "S.propset(name, value, target, recurse=True, skip_checks=False)" }, { "propget", client_propget, METH_VARARGS, "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) -> (revnum, date, author)" }, { "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" }, { 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 = PyString_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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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 = { PyObject_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_FromLong(self->info.size); } static PyGetSetDef info_getsetters[] = { { "size", info_get_size, NULL, "The size of the file in the repository.", }, { NULL } }; PyTypeObject Info_Type = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ NULL, /* struct PyMethodDef *tp_methods; */ 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 */ #if ONLY_SINCE_SVN(1, 7) { "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.recorded_time), READONLY, "" }, #else #if ONLY_SINCE_SVN(1, 5) { "depth", T_INT, offsetof(WCInfoObject, info.depth), READONLY, "" }, #endif { "recorded_size", T_PYSSIZET, offsetof(InfoObject, info.WORKING_SIZE), READONLY, "The size of the file in the repository.", }, { "text_time", T_LONG, offsetof(WCInfoObject, info.text_time), READONLY, "" }, { "prop_time", T_LONG, offsetof(WCInfoObject, info.prop_time), READONLY, "" }, #endif { NULL, } }; static void wcinfo_dealloc(PyObject *self) { PyObject_Del(self); } PyTypeObject WCInfo_Type = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ NULL, /* struct PyMethodDef *tp_methods; */ wc_info_members, /* struct PyMemberDef *tp_members; */ }; PyTypeObject Client_Type = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ "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 } }; void initclient(void) { PyObject *mod; if (PyType_Ready(&Client_Type) < 0) return; if (PyType_Ready(&Config_Type) < 0) return; if (PyType_Ready(&ConfigItem_Type) < 0) return; if (PyType_Ready(&Info_Type) < 0) return; if (PyType_Ready(&WCInfo_Type) < 0) return; /* Make sure APR is initialized */ apr_initialize(); mod = Py_InitModule3("client", client_mod_methods, "Client methods"); if (mod == NULL) return; 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); } subvertpy/delta.py000066400000000000000000000172671214203126100145710ustar00rootroot00000000000000# 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" import sys if sys.version_info < (2, 5): import md5 as _mod_md5 md5 = _mod_md5.new else: 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, (sview_offset, sview_len, tview_len, src_ops, ops, new_data)): """Apply a txdelta window to a buffer. :param sbuf: Source buffer (as bytestring) :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 = 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 = "".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 = "" for (action, offset, length) in ops: if action == TXDELTA_SOURCE: # Copy from source area. tview += sview[offset:offset+length] elif action == TXDELTA_TARGET: for i in xrange(length): tview += tview[offset+i] elif action == TXDELTA_NEW: tview += 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) 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 = "" while n > 0: n-=1 if n > 0: cont = 1 else: cont = 0 ret += chr(((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) | (ord(text[0]) & 0x7f)) next = ((ord(text[0]) >> 7) & 0x1) text = text[1:] return ret, text def pack_svndiff_instruction((action, offset, length)): """Pack a SVN diff instruction :param action: Action :param offset: Offset :param length: Length :return: encoded text """ if length < 0x3f: text = chr((action << 6) + length) else: text = chr((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 = (ord(text[0]) >> 6) length = (ord(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 = "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 = "" for op in ops: instrdata += pack_svndiff_instruction(op) ret.append(encode_length(len(instrdata))) ret.append(encode_length(len(new_data))) ret.append(instrdata) ret.append(new_data) return "".join(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/editor.c000066400000000000000000001024061214203126100145460ustar00rootroot00000000000000/* 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 */ #include #include #include #include #include #include #include "editor.h" #include "util.h" typedef struct EditorObject { PyObject_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); } /* paranoia check */ #if defined(SIZEOF_SIZE_T) && SIZEOF_SIZE_T != SIZEOF_LONG #error "Unable to determine PyArg_Parse format for size_t" #endif /* 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 "kkiOO", &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 { new_data.data = PyString_AsString(py_new_data); new_data.len = PyString_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, "ikk", &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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ 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; int 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ py_file_editor_methods, /* struct PyMethodDef *tp_methods; */ }; static PyObject *py_dir_editor_delete_entry(PyObject *self, PyObject *args) { EditorObject *editor = (EditorObject *)self; char *path; svn_revnum_t revision = -1; if (!PyArg_ParseTuple(args, "s|l", &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; } RUN_SVN(editor->editor->delete_entry(svn_path_canonicalize(path, editor->pool), revision, editor->baton, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_add_directory(PyObject *self, PyObject *args) { char *path; 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, "s|zl", &path, ©from_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; } RUN_SVN(editor->editor->add_directory( svn_path_canonicalize(path, editor->pool), editor->baton, copyfrom_path == NULL?NULL:svn_path_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) { char *path; EditorObject *editor = (EditorObject *)self; svn_revnum_t base_revision=-1; void *child_baton; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "s|l", &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; } RUN_SVN(editor->editor->open_directory( svn_path_canonicalize(path, editor->pool), 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; int 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) { char *path; EditorObject *editor = (EditorObject *)self; if (!PyArg_ParseTuple(args, "s", &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; } RUN_SVN(editor->editor->absent_directory( svn_path_canonicalize(path, editor->pool), editor->baton, editor->pool)); Py_RETURN_NONE; } static PyObject *py_dir_editor_add_file(PyObject *self, PyObject *args) { char *path, *copy_path=NULL; svn_revnum_t copy_rev=-1; void *file_baton = NULL; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "s|zl", &path, ©_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; } RUN_SVN(editor->editor->add_file(svn_path_canonicalize(path, editor->pool), editor->baton, copy_path == NULL?NULL:svn_path_canonicalize(copy_path, editor->pool), 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) { char *path; svn_revnum_t base_revision=-1; void *file_baton; EditorObject *editor = (EditorObject *)self; apr_pool_t *subpool; if (!PyArg_ParseTuple(args, "s|l", &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; } RUN_SVN(editor->editor->open_file(svn_path_canonicalize(path, editor->pool), 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) { char *path; EditorObject *editor = (EditorObject *)self; if (!PyArg_ParseTuple(args, "s", &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; } RUN_SVN(editor->editor->absent_file( svn_path_canonicalize(path, editor->pool), 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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 = PyString_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/editor.h000066400000000000000000000037051214203126100145550ustar00rootroot00000000000000/* * 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/marshall.py000066400000000000000000000101311214203126100152620ustar00rootroot00000000000000# 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: """A protocol literal.""" def __init__(self, txt): self.txt = txt def __str__(self): return self.txt def __repr__(self): return self.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 string """ if type(x) is int: return "%d " % x elif type(x) is list or type(x) is tuple: return "( " + "".join(map(marshall, x)) + ") " elif isinstance(x, literal): return "%s " % x elif type(x) is str: return "%d:%s " % (len(x), x) elif type(x) is unicode: return "%d:%s " % (len(x), x.encode("utf-8")) elif type(x) is bool: if x == True: return "true " elif x == False: return "false " raise MarshallError("Unable to marshall type %s" % x) def unmarshall(x): """Unmarshall the next item from a text. :param x: Text to parse :return: tuple with unpacked item and remaining text """ whitespace = ['\n', ' '] if len(x) == 0: raise NeedMoreData("Not enough data") if x[0] == "(": # list follows if len(x) <= 1: raise NeedMoreData("Missing whitespace") if x[1] != " ": raise MarshallError("missing whitespace after list start") x = x[2:] ret = [] try: while x[0] != ")": (x, n) = unmarshall(x) ret.append(n) except IndexError: raise NeedMoreData("List not terminated") if len(x) <= 1: raise NeedMoreData("Missing whitespace") if not x[1] in whitespace: raise MarshallError("Expected space, got %c" % x[1]) return (x[2:], ret) elif x[0].isdigit(): num = "" # Check if this is a string or a number while x[0].isdigit(): num += x[0] x = x[1:] num = int(num) if x[0] in whitespace: return (x[1:], num) elif x[0] == ":": if len(x) < num: raise NeedMoreData("Expected string of length %r" % num) return (x[num+2:], x[1:num+1]) else: raise MarshallError("Expected whitespace or ':', got '%c" % x[0]) elif x[0].isalpha(): ret = "" # Parse literal try: while x[0].isalpha() or x[0].isdigit() or x[0] == '-': ret += x[0] x = x[1:] except IndexError: raise NeedMoreData("Expected literal") if not x[0] in whitespace: raise MarshallError("Expected whitespace, got %c" % x[0]) return (x[1:], ret) else: raise MarshallError("Unexpected character '%c'" % x[0]) subvertpy/properties.py000066400000000000000000000215741214203126100156700ustar00rootroot00000000000000# 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, calendar, time, 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 not prop[0] in ":_": return False for c in prop[1:]: if not c.isalnum() and not c in "-:._": return False return True def time_to_cstring(timestamp): """Determine string representation of a time. :param timestamp: Timestamp :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: timestamp """ (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 (long(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 l in val.splitlines(): if l == "" or l[0] == "#": continue pts = l.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 l in text.splitlines(): (path, ranges) = l.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((start, end, inheritable)): 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.iteritems(): 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 = '*' 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 = '*' PROP_PREFIX = 'svn:' PROP_SPECIAL = 'svn:special' PROP_SPECIAL_VALUE = '*' 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.iteritems(): oldval = previous.get(key) if oldval != newval: ret[key] = (oldval, newval) return ret subvertpy/ra.h000066400000000000000000000020071214203126100136630ustar00rootroot00000000000000/* * 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/ra.py000066400000000000000000000031661214203126100140730ustar00rootroot00000000000000# 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 * from subvertpy import ra_svn import urllib 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 """ (type, opaque) = urllib.splittype(url) if not type in url_handlers: raise SubversionException("Unknown URL type '%s'" % type, ERR_BAD_URL) return url_handlers[type](url, *args, **kwargs) subvertpy/ra_svn.py000066400000000000000000001050211214203126100147520ustar00rootroot00000000000000# 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 " import SocketServer import base64 import os import socket import subprocess import urllib 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"), []]) auth = 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) = urllib.splittype(url) assert type in ("svn", "svn+ssh") (host, path) = urllib.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) = urllib.splitnport(host, SVN_PORT) sockaddrs = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, 0) self._socket = None for (family, socktype, proto, canonname, sockaddr) in sockaddrs: try: self._socket = socket.socket(family, socktype, proto) self._socket.connect(sockaddr) except socket.error, err: if self._socket is not None: self._socket.close() self._socket = None continue break if self._socket is None: raise err self._socket.setblocking(True) return (self._socket.recv, self._socket.send) def _connect_ssh(self, host): (user, host) = urllib.splituser(host) if user is not None: (user, password) = urllib.splitpassword(user) else: password = None (host, port) = urllib.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(lock_tokens.items()) else: args.append([]) args.append(keep_locks) if len(revprops) > 1: args.append(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: 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: 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: 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] 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) def send_greeting(self): 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.iteritems(): 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) = urllib.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(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.iteritems(): 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 not self.commands.has_key(cmd): 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(SocketServer.StreamRequestHandler): def __init__(self, request, client_address, server): self._server = server SocketServer.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, e: if e.args[0] == 32:# EPIPE return raise class TCPSVNServer(SocketServer.TCPServer): allow_reuse_address = True serve = SocketServer.TCPServer.serve_forever def __init__(self, backend, addr, logf=None): self._logf = logf self._backend = backend SocketServer.TCPServer.__init__(self, addr, TCPSVNRequestHandler) subvertpy/repos.c000066400000000000000000000626151214203126100144170ustar00rootroot00000000000000/* * 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_HEAD apr_pool_t *pool; svn_repos_t *repos; } RepositoryObject; static PyObject *repos_create(PyObject *self, PyObject *args) { char *path; PyObject *config=Py_None, *fs_config=Py_None; svn_repos_t *repos = NULL; apr_pool_t *pool; apr_hash_t *hash_config, *hash_fs_config; RepositoryObject *ret; if (!PyArg_ParseTuple(args, "s|OO:create", &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); /* FIXME */ if (hash_fs_config == NULL) { PyErr_SetString(PyExc_RuntimeError, "Unable to create fs config hash"); return NULL; } RUN_SVN_WITH_POOL(pool, svn_repos_create(&repos, svn_path_canonicalize(path, pool), 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) { char *path; char *kwnames[] = { "path", NULL }; svn_error_t *err; RepositoryObject *ret; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwnames, &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; } Py_BEGIN_ALLOW_THREADS err = svn_repos_open(&ret->repos, svn_path_canonicalize(path, ret->pool), 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_HEAD apr_pool_t *pool; svn_fs_root_t *root; } FileSystemRootObject; typedef struct { PyObject_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 = PyString_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 = PyInt_FromLong(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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ 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; svn_boolean_t 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, 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, "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) { #if ONLY_SINCE_SVN(1, 5) 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); #else PyErr_SetString(PyExc_NotImplementedError, "has_capability is only supported in Subversion >= 1.5"); return NULL; #endif } static PyObject *repos_verify(RepositoryObject *self, PyObject *args) { apr_pool_t *temp_pool; PyObject *py_feedback_stream; svn_revnum_t start_rev, end_rev; if (!PyArg_ParseTuple(args, "Oll", &py_feedback_stream, &start_rev, &end_rev)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_repos_verify_fs(self->repos, new_py_stream(temp_pool, py_feedback_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; if (baton == Py_None) return NULL; ret = PyObject_CallFunction((PyObject *)baton, "li", shard, action); if (ret == NULL) return py_svn_error(); Py_DECREF(ret); 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ "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 PyString_FromStringAndSize(str->data, str->len); } #if ONLY_BEFORE_SVN(1, 6) static PyObject *py_fs_path_change(svn_fs_path_change_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; } return ret; } #else 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; } #endif 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; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_fs_paths_changed2(&changed_paths, self->root, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_fs_paths_changed(&changed_paths, self->root, temp_pool)); #endif 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; #if ONLY_SINCE_SVN(1, 6) svn_fs_path_change2_t *val; #else svn_fs_path_change_t *val; #endif apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); #if ONLY_SINCE_SVN(1, 6) py_val = py_fs_path_change2(val); #else py_val = py_fs_path_change(val); #endif 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 PyInt_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; svn_boolean_t force = FALSE; char *path; #if ONLY_SINCE_SVN(1, 6) svn_checksum_kind_t kind; const char *cstr; svn_checksum_t *checksum; #else int kind; unsigned char checksum[APR_MD5_DIGESTSIZE]; #endif PyObject *ret; if (!PyArg_ParseTuple(args, "s|ib", &path, &kind, &force)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_checksum(&checksum, kind, self->root, path, force, temp_pool)); cstr = svn_checksum_to_cstring(checksum, temp_pool); if (cstr == NULL) { ret = Py_None; Py_INCREF(ret); } else { ret = PyString_FromString(cstr); } #else if (kind > 0) { PyErr_SetString(PyExc_ValueError, "Only MD5 checksums allowed with subversion < 1.6"); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_fs_file_md5_checksum(checksum, self->root, path, temp_pool)); ret = PyString_FromStringAndSize((char *)checksum, APR_MD5_DIGESTSIZE); #endif 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 = { PyObject_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; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ fs_root_methods, /* struct PyMethodDef *tp_methods; */ }; void initrepos(void) { static apr_pool_t *pool; PyObject *mod; if (PyType_Ready(&Repository_Type) < 0) return; if (PyType_Ready(&FileSystem_Type) < 0) return; if (PyType_Ready(&FileSystemRoot_Type) < 0) return; if (PyType_Ready(&Stream_Type) < 0) return; apr_initialize(); pool = Pool(NULL); if (pool == NULL) return; svn_fs_initialize(pool); mod = Py_InitModule3("repos", repos_module_methods, "Local repository management"); if (mod == NULL) return; PyModule_AddObject(mod, "LOAD_UUID_DEFAULT", PyLong_FromLong(svn_repos_load_uuid_default)); PyModule_AddObject(mod, "LOAD_UUID_IGNORE", PyLong_FromLong(svn_repos_load_uuid_ignore)); PyModule_AddObject(mod, "LOAD_UUID_FORCE", PyLong_FromLong(svn_repos_load_uuid_force)); PyModule_AddObject(mod, "PATH_CHANGE_MODIFY", PyInt_FromLong(svn_fs_path_change_modify)); PyModule_AddObject(mod, "PATH_CHANGE_ADD", PyInt_FromLong(svn_fs_path_change_add)); PyModule_AddObject(mod, "PATH_CHANGE_DELETE", PyInt_FromLong(svn_fs_path_change_delete)); PyModule_AddObject(mod, "PATH_CHANGE_REPLACE", PyInt_FromLong(svn_fs_path_change_replace)); #if ONLY_SINCE_SVN(1, 6) PyModule_AddObject(mod, "CHECKSUM_MD5", PyInt_FromLong(svn_checksum_md5)); PyModule_AddObject(mod, "CHECKSUM_SHA1", PyInt_FromLong(svn_checksum_sha1)); #else PyModule_AddObject(mod, "CHECKSUM_MD5", PyInt_FromLong(0)); #endif PyModule_AddObject(mod, "Repository", (PyObject *)&Repository_Type); Py_INCREF(&Repository_Type); PyModule_AddObject(mod, "Stream", (PyObject *)&Stream_Type); Py_INCREF(&Stream_Type); } subvertpy/server.py000066400000000000000000000041151214203126100147720ustar00rootroot00000000000000# 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/stdbool.h000066400000000000000000000002741214203126100147330ustar00rootroot00000000000000/** 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/tests/000077500000000000000000000000001214203126100142535ustar00rootroot00000000000000subvertpy/tests/__init__.py000066400000000000000000000341351214203126100163720ustar00rootroot00000000000000# 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 cStringIO import StringIO import os import shutil import stat import sys import tempfile import unittest try: from unittest import SkipTest except ImportError: try: from unittest2 import SkipTest except ImportError: from testtools.testcase import TestSkipped as SkipTest import urllib2 import urllib import urlparse 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 not (left is 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 = urllib2.randombytes(100) txdelta = self.file.apply_textdelta() delta.send_stream(StringIO(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() 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") f = open(revprop_hook, 'w') try: f.write("exit 0\n") finally: f.close() else: revprop_hook = os.path.join(abspath, "hooks", "pre-revprop-change") f = open(revprop_hook, 'w') try: f.write("#!/bin/sh\n") finally: f.close() os.chmod(revprop_hook, os.stat(revprop_hook).st_mode | 0111) if sys.platform == 'win32': return 'file:%s' % urllib.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 ret.values()[0] 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_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 = self.client_ctx.commit(["."], recursive, False) os.chdir(olddir) assert info is not None return info 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) assert isinstance(url, str) 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.iteritems(): if content is None: try: os.makedirs(name) except OSError: pass else: try: os.makedirs(os.path.dirname(name)) except OSError: pass f = open(name, 'w') try: f.write(content) finally: f.close() def make_client(self, repospath, clientpath, allow_revprop_changes=True): """Create a repository and a checkout. Return the checkout. :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', 'repos', 'server', '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/tests/test_client.py000066400000000000000000000234661214203126100171550ustar00rootroot00000000000000# 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 StringIO import StringIO import shutil import tempfile from subvertpy import ( SubversionException, NODE_DIR, NODE_FILE, client, ra, wc, ) from subvertpy.tests import ( SubversionTestCase, SkipTest, 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("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("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": "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_add_recursive(self): self.build_tree({"dc/trunk/foo": 'bla', "dc/trunk": None}) self.client.add("dc/trunk") if getattr(wc, "WorkingCopy", None) is None: raise SkipTest( "subversion 1.7 API not supported for WorkingCopy yet") adm = wc.WorkingCopy(None, os.path.join(os.getcwd(), "dc")) e = adm.entry(os.path.join(os.getcwd(), "dc", "trunk")) self.assertEqual(e.kind, NODE_DIR) adm2 = wc.WorkingCopy(None, os.path.join(os.getcwd(), "dc", "trunk")) e = adm2.entry(os.path.join(os.getcwd(), "dc", "trunk", "foo")) self.assertEqual(e.kind, NODE_FILE) self.assertEqual(e.revision, 0) 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 in ignores) finally: shutil.rmtree(base_dir) def test_diff(self): r = ra.RemoteAccess(self.repos_url, auth=ra.Auth([ra.get_username_provider()])) dc = self.get_commit_editor(self.repos_url) f = dc.add_file("foo") f.modify("foo1") dc.close() dc = self.get_commit_editor(self.repos_url) f = dc.open_file("foo") f.modify("foo2") dc.close() if client.api_version() < (1, 5): self.assertRaises(NotImplementedError, self.client.diff, 1, 2, self.repos_url, self.repos_url) return # Skip test (outf, errf) = self.client.diff(1, 2, self.repos_url, self.repos_url) self.addCleanup(outf.close) self.addCleanup(errf.close) self.assertEqual("""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 """, outf.read()) self.assertEqual("", errf.read()) def assertCatEquals(self, value, revision=None): io = StringIO() self.client.cat("dc/foo", io, revision) self.assertEqual(value, io.getvalue()) def test_cat(self): self.build_tree({"dc/foo": "bla"}) self.client.add("dc/foo") self.client.log_msg_func = lambda c: "Commit" self.client.commit(["dc"]) self.assertCatEquals("bla") self.build_tree({"dc/foo": "blabla"}) self.client.commit(["dc"]) self.assertCatEquals("blabla") self.assertCatEquals("bla", revision=1) self.assertCatEquals("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"], "%Y-%m-%dT%H:%M:%S.%fZ") self.assertTrue((actual - expected) < delta) def test_log(self): log_entries = [] commit_msg_1 = "Commit" commit_msg_2 = "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": "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") 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": "blabla", "dc/bar": "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", 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": "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"], info.keys()) self.assertEqual(1, info["foo"].revision) self.assertEqual(3L, info["foo"].size) if client.api_version() < (1, 7): # TODO: Why is this failing on 1.7? self.assertEqual(wc.SCHEDULE_NORMAL, info["foo"].wc_info.schedule) self.build_tree({"dc/bar": "blablabla"}) self.client.add(os.path.abspath("dc/bar")) def test_info_nonexistant(self): self.build_tree({"dc/foo": "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") subvertpy/tests/test_core.py000066400000000000000000000017631214203126100166230ustar00rootroot00000000000000# 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 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)) subvertpy/tests/test_delta.py000066400000000000000000000034021214203126100167540ustar00rootroot00000000000000# 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 cStringIO import StringIO from subvertpy.delta import ( decode_length, encode_length, pack_svndiff0, send_stream, unpack_svndiff0, ) 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 = StringIO("foo") send_stream(stream, self.storing_window_handler) self.assertEqual([(0, 0, 3, 0, [(2, 0, 3)], 'foo'), None], self.windows) class MarshallTests(TestCase): def test_encode_length(self): self.assertEqual("\x81\x02", encode_length(130)) def test_roundtrip_length(self): self.assertEqual((42, ""), decode_length(encode_length(42))) def test_roundtrip_window(self): mywindow = (0, 0, 3, 1, [(2, 0, 3)], 'foo') self.assertEqual([mywindow], list(unpack_svndiff0(pack_svndiff0([mywindow])))) subvertpy/tests/test_marshall.py000066400000000000000000000061011214203126100174650ustar00rootroot00000000000000# 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, literal, marshall, unmarshall, ) from subvertpy.tests import TestCase class TestMarshalling(TestCase): def test_literal_txt(self): l = literal("foo") self.assertEqual("foo", l.txt) def test_literal_str(self): l = literal("foo bar") self.assertEqual("foo bar", l.__str__()) def test_literal_rep(self): l = literal("foo bar") self.assertEqual("foo bar", l.__repr__()) def test_marshall_error(self): e = MarshallError("bla bla") self.assertEqual("bla bla", e.__str__()) def test_marshall_int(self): self.assertEqual("1 ", marshall(1)) def test_marshall_list(self): self.assertEqual("( 1 2 3 4 ) ", marshall([1,2,3,4])) def test_marshall_list_mixed(self): self.assertEqual("( 1 3 4 3:str ) ", marshall([1,3,4,"str"])) def test_marshall_literal(self): self.assertEqual("foo ", marshall(literal("foo"))) def test_marshall_string(self): self.assertEqual("3:foo ", marshall("foo")) def test_marshall_raises(self): self.assertRaises(MarshallError, marshall, dict()) def test_marshall_list_nested(self): self.assertEqual("( ( ( 3 ) 4 ) ) ", marshall([[[3], 4]])) def test_marshall_string_space(self): self.assertEqual("5:bla l ", marshall("bla l")) def test_unmarshall_string(self): self.assertEqual(('', "bla l"), unmarshall("5:bla l")) def test_unmarshall_list(self): self.assertEqual(('', [4,5]), unmarshall("( 4 5 ) ")) def test_unmarshall_int(self): self.assertEqual(('', 2), unmarshall("2 ")) def test_unmarshall_literal(self): self.assertEqual(('', literal("x")), unmarshall("x ")) def test_unmarshall_empty(self): self.assertRaises(MarshallError, unmarshall, "") def test_unmarshall_nospace(self): self.assertRaises(MarshallError, unmarshall, "nospace") def test_unmarshall_toolong(self): self.assertRaises(MarshallError, unmarshall, "43432432:bla") def test_unmarshall_literal(self): self.assertRaises(MarshallError, unmarshall, ":-3213") def test_unmarshall_open_list(self): self.assertRaises(MarshallError, unmarshall, "( 3 4 ") subvertpy/tests/test_properties.py000066400000000000000000000177611214203126100200740ustar00rootroot00000000000000# 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 subvertpy import properties from subvertpy.tests import ( SkipTest, TestCase, ) class TestProperties(TestCase): def setUp(self): super(TestProperties, self).setUp() def test_time_from_cstring(self): self.assertEqual(1225704780716938L, 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(1275295762430000L, 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(1225704780716938L)) 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)) subvertpy/tests/test_ra.py000066400000000000000000000410671214203126100162760ustar00rootroot00000000000000# 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 cStringIO import StringIO from subvertpy import ( NODE_DIR, NODE_NONE, NODE_UNKNOWN, 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://") 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.assertIsInstance(self.ra.get_uuid(), str) def test_get_repos_root(self): self.assertEqual(self.repos_url, self.ra.get_repos_root()) def test_get_url(self): if ra.api_version() < (1, 5): self.assertRaises(NotImplementedError, self.ra.get_url) else: self.assertEqual(self.repos_url, self.ra.get_url()) def test_reparent(self): self.ra.reparent(self.repos_url) def test_has_capability(self): if ra.api_version() < (1, 5): self.assertRaises(NotImplementedError, self.ra.has_capability, "FOO") else: 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"], props.keys()) if len(returned[1]) == 3: (paths, revnum, props) = returned[1] else: (paths, revnum, props, has_children) = returned[1] if ra.api_version() < (1, 6): self.assertEqual({'/foo': ('A', None, -1, NODE_UNKNOWN)}, paths) else: 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"], 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() self.assertEqual(set(['bar:foo', 'svn:author', 'svn:custom:blie', 'svn:date', 'svn:log']), set(self.ra.rev_proplist(1).keys())) 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_commit_file_props(self): cb = self.commit_editor() f = cb.add_file("bar") f.modify("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 = StringIO() props = self.ra.get_file("bar", stream, 1)[1] self.assertEqual("blie", props.get("bla:bar")) stream = StringIO() 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("a") cb.close() cb = self.commit_editor() f = cb.open_file("bar") f.modify("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("a") cb.close() stream = StringIO() self.ra.get_file("bar", stream, 1) stream.seek(0) self.assertEqual("a", stream.read()) stream = StringIO() self.ra.get_file("/bar", stream, 1) stream.seek(0) self.assertEqual("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_check_path_with_unsafe_path(self): # The SVN code asserts that paths do not have a leading '/'... And if # that assertion fires, it calls exit(1). That sucks. Make sure it # doesn't happen. self.assertRaises(ValueError, self.ra.check_path, "/bar", 1) self.assertRaises(ValueError, self.ra.check_path, "///bar", 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])) def test_get_locations_dir_with_unsafe_path(self): # Make sure that an invalid path won't trip an assertion error self.assertRaises(ValueError, self.ra.get_locations, "//bla", 2, [1,2]) 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), creds.next()) self.assertRaises(StopIteration, creds.next) 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), creds.next()) self.assertRaises(StopIteration, creds.next) 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), creds.next()) self.assertRaises(StopIteration, creds.next) 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), creds.next()) self.assertRaises(StopIteration, creds.next) 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), creds.next()) self.assertRaises(StopIteration, creds.next) 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), creds.next()) self.assertEqual(("somebody2", 0), creds.next()) self.assertEqual(("somebody3", 0), creds.next()) self.assertRaises(StopIteration, creds.next) 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()) subvertpy/tests/test_repos.py000066400000000000000000000117701214203126100170220ustar00rootroot00000000000000# 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 cStringIO import StringIO import os import textwrap from subvertpy import repos, SubversionException from subvertpy.tests import 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")) if repos.api_version() < (1, 5): self.assertRaises(NotImplementedError, r.has_capability, "mergeinfo") else: self.assertIsInstance(r.has_capability("mergeinfo"), bool) def test_verify_fs(self): r = repos.create(os.path.join(self.test_dir, "foo")) f = StringIO() r.verify_fs(f, 0, 0) self.assertEqual('* 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.assertIsInstance(repos.Repository("foo").fs().get_uuid(), str) 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_invalid(self): r = repos.create(os.path.join(self.test_dir, "foo")) dumpfile = "Malformed" feedback = StringIO() self.assertRaises(SubversionException, r.load_fs, StringIO(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 """) feedback = StringIO() r.load_fs(StringIO(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"], 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_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("")) 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("")) self.assertEqual(False, root.is_file("nonexistant")) class StreamTests(TestCase): def test_read(self): s = repos.Stream() if repos.api_version() < (1, 6): self.assertRaises(NotImplementedError, s.read) else: self.assertEqual("", s.read()) self.assertEqual("", s.read(15)) s.close() def test_write(self): s = repos.Stream() self.assertEqual(0, s.write("")) self.assertEqual(2, s.write("ab")) s.close() subvertpy/tests/test_server.py000066400000000000000000000015061214203126100171740ustar00rootroot00000000000000# 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.""" from subvertpy.ra_svn import SVNServer from subvertpy.tests import SubversionTestCase subvertpy/tests/test_wc.py000066400000000000000000000276211214203126100163050ustar00rootroot00000000000000# 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 StringIO import StringIO import os import subvertpy from subvertpy import ( NODE_FILE, wc, ) from subvertpy.tests import ( SubversionTestCase, TestCase, SkipTest, ) 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 WorkingCopyTests(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("_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_entry_prop(self): self.assertTrue(wc.is_entry_prop("svn:entry:foo")) def test_is_wc_prop(self): self.assertTrue(wc.is_wc_prop("svn:wc:foo")) def test_match_ignore_list(self): if wc.api_version() < (1, 5): self.assertRaises(NotImplementedError, wc.match_ignore_list, "foo", []) return # Skip test self.assertTrue(wc.match_ignore_list("foo", [ "f*"])) self.assertTrue(wc.match_ignore_list("foo", ["foo"])) self.assertFalse(wc.match_ignore_list("foo", [])) self.assertFalse(wc.match_ignore_list("foo", ["bar"])) class WcTests(SubversionTestCase): def test_revision_status(self): repos_url = self.make_client("repos", "checkout") ret = wc.revision_status("checkout") self.assertEqual((0, 0, 0, 0), ret) def test_revision_status_trailing(self): repos_url = self.make_client("repos", "checkout") ret = wc.revision_status("checkout/") self.assertEqual((0, 0, 0, 0), ret) class AdmTests(SubversionTestCase): def setUp(self): super(AdmTests, self).setUp() if getattr(wc, "WorkingCopy", None) is None: raise SkipTest( "Subversion 1.7 API for WorkingCopy not yet supported") def test_has_binary_prop(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "\x00\x01"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout") path = os.path.join(self.test_dir, "checkout/bar") self.assertFalse(adm.has_binary_prop(path)) adm.close() def test_get_ancestry(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "\x00\x01"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout") path = os.path.join(self.test_dir, "checkout/bar") self.assertEqual(("%s/bar" % repos_url, 0), adm.get_ancestry("checkout/bar")) adm.close() def test_maybe_set_repos_root(self): repos_url = self.make_client("repos", "checkout") adm = wc.WorkingCopy(None, "checkout") adm.maybe_set_repos_root(os.path.join(self.test_dir, "checkout"), repos_url) adm.close() def test_add_repos_file(self): repos_url = self.make_client("repos", "checkout") adm = wc.WorkingCopy(None, "checkout", True) adm.add_repos_file("checkout/bar", StringIO("basecontents"), StringIO("contents"), {}, {}) self.assertEqual("basecontents", wc.get_pristine_contents("checkout/bar").read()) def test_mark_missing_deleted(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "\x00\x01"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout", True) os.remove("checkout/bar") adm.mark_missing_deleted("checkout/bar") self.assertFalse(os.path.exists("checkout/bar")) def test_remove_from_revision_control(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "\x00\x01"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout", True) adm.remove_from_revision_control("bar") self.assertTrue(os.path.exists("checkout/bar")) def test_relocate(self): repos_url = self.make_client("repos", "checkout") adm = wc.WorkingCopy(None, "checkout", True) adm.relocate("checkout", "file://", "http://") def test_translated_stream(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "My id: $Id$"}) self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") adm = wc.WorkingCopy(None, "checkout", True) path = os.path.join(self.test_dir, "checkout/bar") stream = adm.translated_stream(path, path, wc.TRANSLATE_TO_NF) self.assertTrue(stream.read().startswith("My id: $Id: ")) def test_text_modified(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "My id: $Id$"}) self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") adm = wc.WorkingCopy(None, "checkout") self.assertFalse(adm.text_modified("checkout/bar")) self.build_tree({"checkout/bar": "gambon"}) self.assertTrue(adm.text_modified("checkout/bar", True)) def test_props_modified(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "My id: $Id$"}) self.client_add('checkout/bar') self.client_set_prop("checkout/bar", "svn:keywords", "Id\n") self.client_commit("checkout", "foo") adm = wc.WorkingCopy(None, "checkout", True) self.assertFalse(adm.props_modified("checkout/bar")) adm.prop_set("aprop", "avalue", "checkout/bar") self.assertTrue(adm.props_modified("checkout/bar")) def test_prop_set(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "file"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout", True) adm.prop_set("aprop", "avalue", "checkout/bar") self.assertEqual(adm.prop_get("aprop", "checkout/bar"), "avalue") adm.prop_set("aprop", None, "checkout/bar") self.assertEqual(adm.prop_get("aprop", "checkout/bar"), None) def test_committed_queue(self): if getattr(wc, "CommittedQueue", None) is None: raise SkipTest("CommittedQueue not available") cq = wc.CommittedQueue() repos_url = self.make_client("repos", "checkout") adm = wc.WorkingCopy(None, "checkout", True) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") def test_entry_not_found(self): repos_url = self.make_client("repos", "checkout") adm = wc.WorkingCopy(None, "checkout") self.assertRaises(KeyError, adm.entry, "bar") def test_entry(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "\x00\x01"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout") entry = adm.entry("checkout/bar") self.assertEqual("bar", entry.name) self.assertEqual(NODE_FILE, entry.kind) self.assertEqual(0, entry.revision) self.client_commit("checkout", "msg") adm = wc.WorkingCopy(None, "checkout") entry = adm.entry("checkout/bar") self.assertEqual("bar", entry.name) self.assertEqual(NODE_FILE, entry.kind) self.assertEqual(1, entry.revision) def test_get_actual_target(self): repos_url = self.make_client("repos", ".") self.assertEqual((self.test_dir, "bla"), wc.get_actual_target("%s/bla" % self.test_dir)) def test_is_wc_root(self): repos_url = self.make_client("repos", ".") self.build_tree({"bar": None}) self.client_add('bar') adm = wc.WorkingCopy(None, ".") self.assertTrue(adm.is_wc_root(self.test_dir)) self.assertFalse(adm.is_wc_root(os.path.join(self.test_dir, "bar"))) def test_status(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "text"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout") self.assertEqual(wc.STATUS_ADDED, adm.status('bar').status) self.client_commit("checkout", "foo") adm = wc.WorkingCopy(None, "checkout") self.assertEqual(wc.STATUS_NORMAL, adm.status('bar').status) def test_transmit_text_deltas(self): repos_url = self.make_client("repos", ".") self.build_tree({"bar": "blala"}) self.client_add('bar') adm = wc.WorkingCopy(None, ".", True) class Editor(object): """Editor""" def __init__(self): self._windows = [] def apply_textdelta(self, checksum): def window_handler(window): self._windows.append(window) return window_handler def close(self): pass editor = Editor() (tmpfile, digest) = adm.transmit_text_deltas("bar", True, editor) self.assertEqual(editor._windows, [(0L, 0, 5, 0, [(2, 0, 5)], 'blala'), None]) self.assertIsInstance(tmpfile, str) self.assertEqual(16, len(digest)) bar = adm.entry("bar") self.assertEqual(-1, bar.cmt_rev) self.assertEqual(0, bar.revision) cq = wc.CommittedQueue() cq.queue("bar", adm) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") bar = adm.entry("bar") self.assertEqual("bar", bar.name) self.assertEqual(NODE_FILE, bar.kind) self.assertEqual(wc.SCHEDULE_NORMAL, bar.schedule) self.assertIs(None, bar.checksum) self.assertEqual(1, bar.cmt_rev) self.assertEqual(1, bar.revision) def test_process_committed_queue(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "la"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout", True) cq = wc.CommittedQueue() cq.queue(os.path.join(self.test_dir, "checkout/bar"), adm) adm.process_committed_queue(cq, 1, "2010-05-31T08:49:22.430000Z", "jelmer") bar = adm.entry("checkout/bar") self.assertEqual("bar", bar.name) self.assertEqual(NODE_FILE, bar.kind) self.assertEqual(wc.SCHEDULE_ADD, bar.schedule) def test_probe_try(self): repos_url = self.make_client("repos", "checkout") self.build_tree({"checkout/bar": "la"}) self.client_add('checkout/bar') adm = wc.WorkingCopy(None, "checkout", True) try: self.assertIs(None, adm.probe_try(self.test_dir)) except subvertpy.SubversionException, (msg, num): if num != subvertpy.ERR_WC_NOT_WORKING_COPY: raise self.assertEqual("checkout", adm.probe_try(os.path.join("checkout", "bar")).access_path()) subvertpy/util.c000066400000000000000000000561221214203126100142400ustar00rootroot00000000000000/* * 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 #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))); } 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 = cls->tp_new(cls, args, NULL); if (cls->tp_init != NULL) cls->tp_init(child, args, NULL); Py_DECREF(cls); Py_DECREF(args); } else { child = Py_None; Py_INCREF(child); } #if ONLY_SINCE_SVN(1, 4) message = svn_err_best_message(error, buf, sizeof(buf)); #else message = error->message; #endif 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 (!PyString_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 *) = apr_pstrdup(pool, PyString_AsString(item)); } return true; } bool path_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 (PyString_Check(l)) { *ret = apr_array_make(pool, 1, sizeof(char *)); APR_ARRAY_PUSH(*ret, const char *) = svn_path_canonicalize(PyString_AsString(l), pool); } 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); if (!PyString_Check(item)) { PyErr_Format(PyExc_TypeError, "Expected list of strings, item was %s", item->ob_type->tp_name); return false; } APR_ARRAY_PUSH(*ret, const char *) = svn_path_canonicalize(PyString_AsString(item), pool); } } 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) return NULL; py_props = PyDict_New(); if (py_props == NULL) { apr_pool_destroy(pool); return NULL; } 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 = PyString_FromStringAndSize(val->data, val->len); } if (py_val == NULL) { Py_DECREF(py_props); apr_pool_destroy(pool); return NULL; } if (key == NULL) { py_key = Py_None; Py_INCREF(py_key); } else { py_key = PyString_FromString(key); } if (PyDict_SetItem(py_props, py_key, py_val) != 0) { Py_DECREF(py_key); Py_DECREF(py_val); Py_DECREF(py_props); apr_pool_destroy(pool); return NULL; } Py_DECREF(py_key); Py_DECREF(py_val); } apr_pool_destroy(pool); return py_props; } 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)) { if (!PyString_Check(k)) { PyErr_SetString(PyExc_TypeError, "property name should be string"); return NULL; } if (!PyString_Check(v)) { PyErr_SetString(PyExc_TypeError, "property value should be string"); return NULL; } val_string = svn_string_ncreate(PyString_AsString(v), PyString_Size(v), pool); apr_hash_set(hash_props, PyString_AsString(k), PyString_Size(k), val_string); } return hash_props; } 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)) { apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); if (node_kind) { pyval = Py_BuildValue("(czli)", val->action, val->copyfrom_path, val->copyfrom_rev, svn_node_unknown); } else { pyval = Py_BuildValue("(czl)", val->action, val->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; } if (PyDict_SetItemString(py_changed_paths, key, pyval) != 0) { Py_DECREF(py_changed_paths); Py_DECREF(pyval); return NULL; } Py_DECREF(pyval); } } return py_changed_paths; } #if ONLY_SINCE_SVN(1, 6) 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)) { apr_hash_this(idx, (const void **)&key, &klen, (void **)&val); pyval = Py_BuildValue("(czli)", val->action, val->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; } if (PyDict_SetItemString(py_changed_paths, key, pyval) != 0) { Py_DECREF(pyval); Py_DECREF(py_changed_paths); return NULL; } Py_DECREF(pyval); } } return py_changed_paths; } #endif #if ONLY_SINCE_SVN(1, 5) 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; } #endif 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, *obj; PyGILState_STATE state = PyGILState_Ensure(); /* FIXME: Support including node kind */ py_changed_paths = pyify_changed_paths(changed_paths, false, pool); CB_CHECK_PYRETVAL(py_changed_paths); revprops = PyDict_New(); if (revprops == NULL) { Py_DECREF(py_changed_paths); return NULL; } CB_CHECK_PYRETVAL(revprops); if (message != NULL) { obj = PyString_FromString(message); PyDict_SetItemString(revprops, SVN_PROP_REVISION_LOG, obj); Py_DECREF(obj); } if (author != NULL) { obj = PyString_FromString(author); PyDict_SetItemString(revprops, SVN_PROP_REVISION_AUTHOR, obj); Py_DECREF(obj); } if (date != NULL) { obj = PyString_FromString(date); PyDict_SetItemString(revprops, SVN_PROP_REVISION_DATE, obj); Py_DECREF(obj); } 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 = PyInt_AsLong(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 (!PyString_Check(ret)) { PyErr_SetString(PyExc_TypeError, "Expected stream read function to return string"); PyGILState_Release(state); return py_svn_error(); } *length = PyString_Size(ret); memcpy(buffer, PyString_AS_STRING(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; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "write", "s#", data, len[0]); 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)); 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) { obj = PyInt_FromLong(dirent->kind); 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 = PyString_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; int 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 PyInt_FromLong(length); } static PyObject *stream_read(StreamObject *self, PyObject *args) { PyObject *ret; apr_pool_t *temp_pool; long len = -1; if (!PyArg_ParseTuple(args, "|l", &len)) return NULL; if (self->closed) { return PyString_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(self->stream, buffer, &size)); ret = PyString_FromStringAndSize(buffer, size); apr_pool_destroy(temp_pool); return ret; } else { #if ONLY_SINCE_SVN(1, 6) 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 = PyString_FromStringAndSize(result->data, result->len); apr_pool_destroy(temp_pool); return ret; #else PyErr_SetString(PyExc_NotImplementedError, "Subversion 1.5 does not provide svn_string_from_stream()."); return NULL; #endif } } static PyMethodDef stream_methods[] = { { "read", (PyCFunction)stream_read, METH_VARARGS, NULL }, { "write", (PyCFunction)stream_write, METH_VARARGS, NULL }, { "close", (PyCFunction)stream_close, METH_NOARGS, NULL }, { NULL, } }; PyTypeObject Stream_Type = { PyObject_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 */ stream_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ "Byte stream", /* 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 */ stream_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; */ stream_init, /* tp_new tp_new */ }; subvertpy/util.h000066400000000000000000000102671214203126100142450ustar00rootroot00000000000000/* * 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 #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))) /* There's no Py_ssize_t in 2.4, apparently */ #if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION < 5 typedef int Py_ssize_t; #endif #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 path_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); 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 *PyOS_tmpfile(void); PyObject *pyify_changed_paths(apr_hash_t *changed_paths, bool node_kind, apr_pool_t *pool); #if ONLY_SINCE_SVN(1, 6) PyObject *pyify_changed_paths2(apr_hash_t *changed_paths2, apr_pool_t *pool); #endif apr_file_t *apr_file_from_object(PyObject *object, apr_pool_t *pool); #if ONLY_SINCE_SVN(1, 5) svn_error_t *py_svn_log_entry_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool); #endif #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; svn_boolean_t closed; } StreamObject; extern PyTypeObject Stream_Type; #endif /* _SUBVERTPY_UTIL_H_ */ subvertpy/wc.c000066400000000000000000002317241214203126100136770ustar00rootroot00000000000000/* * 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 #include "util.h" #include "editor.h" #include "wc.h" #ifndef T_BOOL #define T_BOOL T_BYTE #endif #if ONLY_SINCE_SVN(1, 5) #define REPORTER_T svn_ra_reporter3_t #else #define REPORTER_T svn_ra_reporter2_t #endif staticforward PyTypeObject Entry_Type; staticforward PyTypeObject Status_Type; staticforward PyTypeObject Adm_Type; static PyObject *py_entry(const svn_wc_entry_t *entry); static PyObject *py_status(const svn_wc_status2_t *status); #if ONLY_BEFORE_SVN(1, 5) struct svn_wc_committed_queue_t { apr_pool_t *pool; apr_array_header_t *queue; svn_boolean_t have_recursive; }; #if SVN_VER_MINOR < 5 typedef struct svn_wc_committed_queue_t svn_wc_committed_queue_t; #endif typedef struct { const char *path; svn_wc_adm_access_t *adm_access; svn_boolean_t recurse; svn_boolean_t remove_lock; apr_array_header_t *wcprop_changes; unsigned char *digest; } committed_queue_item_t; #if SVN_VER_MINOR < 5 static #endif svn_wc_committed_queue_t *svn_wc_committed_queue_create(apr_pool_t *pool) { svn_wc_committed_queue_t *q; q = apr_palloc(pool, sizeof(*q)); q->pool = pool; q->queue = apr_array_make(pool, 1, sizeof(committed_queue_item_t *)); q->have_recursive = FALSE; return q; } #if SVN_VER_MINOR < 5 static #endif svn_error_t *svn_wc_queue_committed(svn_wc_committed_queue_t **queue, const char *path, svn_wc_adm_access_t *adm_access, svn_boolean_t recurse, apr_array_header_t *wcprop_changes, svn_boolean_t remove_lock, svn_boolean_t remove_changelist, const unsigned char *digest, apr_pool_t *scratch_pool) { committed_queue_item_t *cqi; (*queue)->have_recursive |= recurse; /* Use the same pool as the one QUEUE was allocated in, to prevent lifetime issues. Intermediate operations should use SCRATCH_POOL. */ /* Add to the array with paths and options */ cqi = apr_palloc((*queue)->pool, sizeof(*cqi)); cqi->path = path; cqi->adm_access = adm_access; cqi->recurse = recurse; cqi->remove_lock = remove_lock; cqi->wcprop_changes = wcprop_changes; cqi->digest = digest; APR_ARRAY_PUSH((*queue)->queue, committed_queue_item_t *) = cqi; return SVN_NO_ERROR; } #endif typedef struct { PyObject_HEAD apr_pool_t *pool; svn_wc_committed_queue_t *queue; } CommittedQueueObject; staticforward PyTypeObject CommittedQueue_Type; #if ONLY_SINCE_SVN(1, 5) static svn_error_t *py_ra_report_set_path(void *baton, const char *path, svn_revnum_t revision, svn_depth_t depth, int start_empty, const char *lock_token, apr_pool_t *pool) { PyObject *self = (PyObject *)baton, *py_lock_token, *ret; PyGILState_STATE state = PyGILState_Ensure(); if (lock_token == NULL) { py_lock_token = Py_None; Py_INCREF(py_lock_token); } else { py_lock_token = PyString_FromString(lock_token); } ret = PyObject_CallMethod(self, "set_path", "slbOi", path, revision, start_empty, py_lock_token, depth); Py_DECREF(py_lock_token); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_ra_report_link_path(void *report_baton, const char *path, const char *url, svn_revnum_t revision, svn_depth_t depth, int start_empty, const char *lock_token, apr_pool_t *pool) { PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; PyGILState_STATE state = PyGILState_Ensure(); if (lock_token == NULL) { py_lock_token = Py_None; Py_INCREF(py_lock_token); } else { py_lock_token = PyString_FromString(lock_token); } ret = PyObject_CallMethod(self, "link_path", "sslbOi", path, url, revision, start_empty, py_lock_token, depth); Py_DECREF(py_lock_token); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } #else static svn_error_t *py_ra_report_set_path(void *baton, const char *path, svn_revnum_t revision, int start_empty, const char *lock_token, apr_pool_t *pool) { 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 = PyString_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_report_link_path(void *report_baton, const char *path, const char *url, svn_revnum_t revision, int start_empty, const char *lock_token, apr_pool_t *pool) { PyObject *self = (PyObject *)report_baton, *ret, *py_lock_token; PyGILState_STATE state = PyGILState_Ensure(); if (lock_token == NULL) { py_lock_token = Py_None; Py_INCREF(py_lock_token); } else { py_lock_token = PyString_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; } #endif static svn_error_t *py_ra_report_delete_path(void *baton, const char *path, apr_pool_t *pool) { PyObject *self = (PyObject *)baton, *ret; PyGILState_STATE state = PyGILState_Ensure(); ret = PyObject_CallMethod(self, "delete_path", "s", path); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } static svn_error_t *py_ra_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; } static const REPORTER_T py_ra_reporter = { py_ra_report_set_path, py_ra_report_delete_path, py_ra_report_link_path, py_ra_report_finish, py_ra_report_abort, }; /** * 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); } static svn_error_t *py_wc_found_entry(const char *path, const svn_wc_entry_t *entry, void *walk_baton, apr_pool_t *pool) { PyObject *fn, *ret; PyObject *callbacks = (PyObject *)walk_baton; PyGILState_STATE state = PyGILState_Ensure(); if (PyTuple_Check(callbacks)) { fn = PyTuple_GET_ITEM(callbacks, 0); } else { fn = (PyObject *)walk_baton; } ret = PyObject_CallFunction(fn, "sO", path, py_entry(entry)); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); return NULL; } #if ONLY_SINCE_SVN(1, 5) svn_error_t *py_wc_handle_error(const char *path, svn_error_t *err, void *walk_baton, apr_pool_t *pool) { PyObject *fn, *ret; PyObject *py_err; PyGILState_STATE state; PyObject *callbacks = (PyObject *)walk_baton; if (PyTuple_Check(callbacks)) { fn = PyTuple_GET_ITEM(callbacks, 1); } else { return err; } state = PyGILState_Ensure(); py_err = PyErr_NewSubversionException(err); ret = PyObject_CallFunction(fn, "sO", path, py_err); CB_CHECK_PYRETVAL(ret); Py_DECREF(ret); PyGILState_Release(state); Py_DECREF(py_err); return NULL; } static svn_wc_entry_callbacks2_t py_wc_entry_callbacks2 = { py_wc_found_entry, py_wc_handle_error, }; #else static svn_wc_entry_callbacks_t py_wc_entry_callbacks = { py_wc_found_entry }; #endif void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool) { PyObject *func = baton, *ret; if (func == Py_None) return; if (notify->err != NULL) { 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. */ } } typedef struct { PyObject_HEAD apr_pool_t *pool; svn_wc_entry_t entry; } EntryObject; static void entry_dealloc(PyObject *self) { apr_pool_destroy(((EntryObject *)self)->pool); PyObject_Del(self); } static PyMemberDef entry_members[] = { { "name", T_STRING, offsetof(EntryObject, entry.name), READONLY, "Name of the file"}, { "copyfrom_url", T_STRING, offsetof(EntryObject, entry.copyfrom_url), READONLY, "Copyfrom location" }, { "copyfrom_rev", T_LONG, offsetof(EntryObject, entry.copyfrom_rev), READONLY, "Copyfrom revision" }, { "uuid", T_STRING, offsetof(EntryObject, entry.uuid), READONLY, "UUID of repository" }, { "url", T_STRING, offsetof(EntryObject, entry.url), READONLY, "URL in repository" }, { "repos", T_STRING, offsetof(EntryObject, entry.repos), READONLY, "Canonical repository URL" }, { "schedule", T_INT, offsetof(EntryObject, entry.schedule), READONLY, "Scheduling (add, replace, delete, etc)" }, { "kind", T_INT, offsetof(EntryObject, entry.kind), READONLY, "Kind of file (file, dir, etc)" }, { "revision", T_LONG, offsetof(EntryObject, entry.revision), READONLY, "Base revision", }, { "cmt_rev", T_LONG, offsetof(EntryObject, entry.cmt_rev), READONLY, "Last revision this was changed" }, { "checksum", T_STRING, offsetof(EntryObject, entry.checksum), READONLY, "Hex MD5 checksum for the untranslated text base file" }, { "cmt_date", T_LONG, offsetof(EntryObject, entry.cmt_date), READONLY, "Last date this was changed" }, { "cmt_author", T_STRING, offsetof(EntryObject, entry.cmt_author), READONLY, "Last commit author of this item" }, { NULL, } }; static PyTypeObject Entry_Type = { PyObject_HEAD_INIT(NULL) 0, "wc.Entry", /* const char *tp_name; For printing, in format "." */ sizeof(EntryObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ entry_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ NULL, /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ NULL, /* struct PyMethodDef *tp_methods; */ entry_members, /* struct PyMemberDef *tp_members; */ }; static PyObject *py_entry(const svn_wc_entry_t *entry) { EntryObject *ret; ret = PyObject_New(EntryObject, &Entry_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) return NULL; ret->entry = *svn_wc_entry_dup(entry, ret->pool); return (PyObject *)ret; } typedef struct { PyObject_HEAD apr_pool_t *pool; svn_wc_status2_t status; PyObject *entry; } StatusObject; static void status_dealloc(PyObject *self) { apr_pool_destroy(((StatusObject *)self)->pool); Py_XDECREF(((StatusObject *)self)->entry); PyObject_Del(self); } static PyMemberDef status_members[] = { { "entry", T_OBJECT, offsetof(StatusObject, entry), READONLY, "Can be NULL if not under version control." }, { "locked", T_BOOL, offsetof(StatusObject, status.locked), READONLY, "a directory can be 'locked' if a working copy update was interrupted." }, { "copied", T_BOOL, offsetof(StatusObject, status.copied), READONLY, "a file or directory can be 'copied' if it's scheduled for addition-with-history (or part of a subtree that is scheduled as such.)." }, { "switched", T_BOOL, offsetof(StatusObject, status.switched), READONLY, "a file or directory can be 'switched' if the switch command has been used." }, { "url", T_STRING, offsetof(StatusObject, status.url), READONLY, "URL (actual or expected) in repository" }, { "revision", T_LONG, offsetof(StatusObject, status.ood_last_cmt_rev), READONLY, "Set to the youngest committed revision, or SVN_INVALID_REVNUM if not out of date.", }, { "kind", T_INT, offsetof(StatusObject, status.ood_kind), READONLY, "Set to the node kind of the youngest commit, or svn_node_none if not out of date.", }, { "status", T_INT, offsetof(StatusObject, status.text_status), READONLY, "The status of the entry.", }, { NULL, } }; static PyTypeObject Status_Type = { PyObject_HEAD_INIT(NULL) 0, "wc.Status", /* const char *tp_name; For printing, in format "." */ sizeof(StatusObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ status_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ NULL, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ NULL, /* reprfunc tp_str; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ "Working copy status object", /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ NULL, /* struct PyMethodDef *tp_methods; */ status_members, /* struct PyMemberDef *tp_members; */ }; static PyObject *py_status(const svn_wc_status2_t *status) { StatusObject *ret; svn_wc_status2_t *dup_status; ret = PyObject_New(StatusObject, &Status_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) { PyObject_Del(ret); return NULL; } dup_status = svn_wc_dup_status2(status, ret->pool); if (dup_status == NULL) { PyErr_NoMemory(); return NULL; } ret->status = *dup_status; ret->entry = py_entry(ret->status.entry); return (PyObject *)ret; } typedef struct { PyObject_HEAD svn_wc_adm_access_t *adm; apr_pool_t *pool; } AdmObject; #define ADM_CHECK_CLOSED(adm_obj) \ if (adm_obj->adm == NULL) { \ PyErr_SetString(PyExc_RuntimeError, "WorkingCopy instance already closed"); \ return NULL; \ } static PyObject *adm_init(PyTypeObject *self, PyObject *args, PyObject *kwargs) { PyObject *associated; char *path; bool write_lock=false; int depth=0; svn_wc_adm_access_t *parent_wc; svn_error_t *err; AdmObject *ret; char *kwnames[] = { "associated", "path", "write_lock", "depth", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|bi", kwnames, &associated, &path, &write_lock, &depth)) return NULL; ret = PyObject_New(AdmObject, &Adm_Type); if (ret == NULL) return NULL; ret->pool = Pool(NULL); if (ret->pool == NULL) return NULL; if (associated == Py_None) { parent_wc = NULL; } else { parent_wc = ((AdmObject *)associated)->adm; } Py_BEGIN_ALLOW_THREADS err = svn_wc_adm_open3(&ret->adm, parent_wc, svn_path_canonicalize(path, ret->pool), write_lock, depth, py_cancel_check, NULL, ret->pool); Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); Py_DECREF(ret); return NULL; } return (PyObject *)ret; } static PyObject *adm_access_path(PyObject *self) { AdmObject *admobj = (AdmObject *)self; ADM_CHECK_CLOSED(admobj); return PyString_FromString(svn_wc_adm_access_path(admobj->adm)); } static PyObject *adm_locked(PyObject *self) { AdmObject *admobj = (AdmObject *)self; ADM_CHECK_CLOSED(admobj); return PyBool_FromLong(svn_wc_adm_locked(admobj->adm)); } static PyObject *adm_prop_get(PyObject *self, PyObject *args) { char *name, *path; AdmObject *admobj = (AdmObject *)self; const svn_string_t *value; apr_pool_t *temp_pool; PyObject *ret; if (!PyArg_ParseTuple(args, "ss", &name, &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_get(&value, name, path, admobj->adm, temp_pool)); if (value == NULL || value->data == NULL) { ret = Py_None; Py_INCREF(ret); } else { ret = PyString_FromStringAndSize(value->data, value->len); } apr_pool_destroy(temp_pool); return ret; } static PyObject *adm_prop_set(PyObject *self, PyObject *args) { char *name, *value, *path; AdmObject *admobj = (AdmObject *)self; bool skip_checks=false; apr_pool_t *temp_pool; int vallen; svn_string_t *cvalue; PyObject *notify_func = Py_None; if (!PyArg_ParseTuple(args, "sz#s|bO", &name, &value, &vallen, &path, &skip_checks, ¬ify_func)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (value == NULL) { cvalue = NULL; } else { cvalue = svn_string_ncreate(value, vallen, temp_pool); } #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set3(name, cvalue, path, admobj->adm, skip_checks, py_wc_notify_func, (void *)notify_func, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_wc_prop_set2(name, cvalue, path, admobj->adm, skip_checks, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_entries_read(PyObject *self, PyObject *args) { apr_hash_t *entries; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; bool show_hidden=false; apr_hash_index_t *idx; const char *key; apr_ssize_t klen; svn_wc_entry_t *entry; PyObject *py_entries, *obj; if (!PyArg_ParseTuple(args, "|b", &show_hidden)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_entries_read(&entries, admobj->adm, show_hidden, temp_pool)); py_entries = PyDict_New(); if (py_entries == NULL) { apr_pool_destroy(temp_pool); return NULL; } idx = apr_hash_first(temp_pool, entries); while (idx != NULL) { apr_hash_this(idx, (const void **)&key, &klen, (void **)&entry); if (entry == NULL) { obj = Py_None; Py_INCREF(obj); } else { obj = py_entry(entry); } PyDict_SetItemString(py_entries, key, obj); Py_DECREF(obj); idx = apr_hash_next(idx); } apr_pool_destroy(temp_pool); return py_entries; } static PyObject *adm_walk_entries(PyObject *self, PyObject *args) { char *path; PyObject *callbacks; bool show_hidden=false; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; svn_depth_t depth = svn_depth_infinity; if (!PyArg_ParseTuple(args, "sO|bi", &path, &callbacks, &show_hidden, &depth)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries3( svn_path_canonicalize(path, temp_pool), admobj->adm, &py_wc_entry_callbacks2, (void *)callbacks, depth, show_hidden, py_cancel_check, NULL, temp_pool)); #else if (depth != svn_depth_infinity) { PyErr_SetString(PyExc_NotImplementedError, "depth != infinity not supported for svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_wc_walk_entries2( svn_path_canonicalize(path, temp_pool), admobj->adm, &py_wc_entry_callbacks, (void *)callbacks, show_hidden, py_cancel_check, NULL, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_entry(PyObject *self, PyObject *args) { char *path; bool show_hidden=false; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; const svn_wc_entry_t *entry; PyObject *ret; if (!PyArg_ParseTuple(args, "s|b", &path, &show_hidden)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_entry(&entry, svn_path_canonicalize(path, temp_pool), admobj->adm, show_hidden, temp_pool)); if (entry == NULL) { PyErr_Format(PyExc_KeyError, "No such entry '%s'", path); ret = NULL; } else { ret = py_entry(entry); } apr_pool_destroy(temp_pool); return ret; } static PyObject *adm_get_prop_diffs(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; apr_array_header_t *propchanges; apr_hash_t *original_props; AdmObject *admobj = (AdmObject *)self; svn_prop_t el; int i; PyObject *py_propchanges, *py_orig_props, *pyval; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_prop_diffs(&propchanges, &original_props, svn_path_canonicalize(path, temp_pool), admobj->adm, temp_pool)); py_propchanges = PyList_New(propchanges->nelts); if (py_propchanges == NULL) { apr_pool_destroy(temp_pool); return NULL; } for (i = 0; i < propchanges->nelts; i++) { el = APR_ARRAY_IDX(propchanges, i, svn_prop_t); if (el.value != NULL) pyval = Py_BuildValue("(sz#)", el.name, el.value->data, el.value->len); else pyval = Py_BuildValue("(sO)", el.name, Py_None); if (pyval == NULL) { apr_pool_destroy(temp_pool); Py_DECREF(py_propchanges); return NULL; } if (PyList_SetItem(py_propchanges, i, pyval) != 0) { Py_DECREF(py_propchanges); apr_pool_destroy(temp_pool); return NULL; } } py_orig_props = prop_hash_to_dict(original_props); apr_pool_destroy(temp_pool); if (py_orig_props == NULL) { Py_DECREF(py_propchanges); return NULL; } return Py_BuildValue("(NN)", py_propchanges, py_orig_props); } static PyObject *adm_add(PyObject *self, PyObject *args, PyObject *kwargs) { char *path, *copyfrom_url=NULL; svn_revnum_t copyfrom_rev=-1; char *kwnames[] = { "path", "copyfrom_url", "copyfrom_rev", "notify_func", "depth", NULL }; PyObject *notify_func=Py_None; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; svn_depth_t depth = svn_depth_infinity; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|zlOi", kwnames, &path, ©from_url, ©from_rev, ¬ify_func, &depth)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_add3( svn_path_canonicalize(path, temp_pool), admobj->adm, depth, copyfrom_url, copyfrom_rev, py_cancel_check, NULL, py_wc_notify_func, (void *)notify_func, temp_pool)); #else if (depth != svn_depth_infinity) { PyErr_SetString(PyExc_NotImplementedError, "depth != infinity not supported on svn < 1.6"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_wc_add2( svn_path_canonicalize(path, temp_pool), admobj->adm, copyfrom_url, copyfrom_rev, py_cancel_check, py_wc_notify_func, (void *)notify_func, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_copy(PyObject *self, PyObject *args) { AdmObject *admobj = (AdmObject *)self; char *src, *dst; PyObject *notify_func=Py_None; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "ss|O", &src, &dst, ¬ify_func)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_copy2(src, admobj->adm, dst, py_cancel_check, NULL, py_wc_notify_func, (void *)notify_func, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_delete(PyObject *self, PyObject *args, PyObject *kwargs) { AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; char *kwnames[] = { "path", "notify_func", "keep_local", NULL }; char *path; PyObject *notify_func=Py_None; bool keep_local = false; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|Ob:delete", kwnames, &path, ¬ify_func, &keep_local)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete3(path, admobj->adm, py_cancel_check, NULL, py_wc_notify_func, (void *)notify_func, keep_local, temp_pool)); #else if (keep_local) { PyErr_SetString(PyExc_NotImplementedError, "keep_local not supported on Subversion < 1.5"); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_wc_delete2(path, admobj->adm, py_cancel_check, NULL, py_wc_notify_func, (void *)notify_func, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_crawl_revisions(PyObject *self, PyObject *args, PyObject *kwargs) { char *path; PyObject *reporter; bool restore_files=true, recurse=true, use_commit_times=true; PyObject *notify_func=Py_None; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; svn_wc_traversal_info_t *traversal_info; svn_boolean_t depth_compatibility_trick = FALSE; svn_boolean_t honor_depth_exclude = FALSE; char *kwnames[] = { "path", "reporter", "restore_files", "recurse", "use_commit_times", "notify_func", "depth_compatibility_trick", "honor_depth_exclude,", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|bbbObb", kwnames, &path, &reporter, &restore_files, &recurse, &use_commit_times, ¬ify_func, &depth_compatibility_trick, &honor_depth_exclude)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; traversal_info = svn_wc_init_traversal_info(temp_pool); #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions4(svn_path_canonicalize(path, temp_pool), admobj->adm, &py_ra_reporter, (void *)reporter, restore_files, recurse?svn_depth_infinity:svn_depth_files, honor_depth_exclude, depth_compatibility_trick, use_commit_times, py_wc_notify_func, (void *)notify_func, traversal_info, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions3(svn_path_canonicalize(path, temp_pool), admobj->adm, &py_ra_reporter, (void *)reporter, restore_files, recurse?svn_depth_infinity:svn_depth_files, depth_compatibility_trick, use_commit_times, py_wc_notify_func, (void *)notify_func, traversal_info, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_wc_crawl_revisions2(svn_path_canonicalize(path, temp_pool), admobj->adm, &py_ra_reporter, (void *)reporter, restore_files, recurse, use_commit_times, py_wc_notify_func, (void *)notify_func, traversal_info, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static void wc_done_handler(void *self) { AdmObject *admobj = (AdmObject *)self; Py_DECREF(admobj); } static PyObject *adm_get_update_editor(PyObject *self, PyObject *args) { char *target; bool use_commit_times=true, recurse=true; PyObject * notify_func=Py_None; char *diff3_cmd=NULL; const svn_delta_editor_t *editor; AdmObject *admobj = (AdmObject *)self; void *edit_baton; apr_pool_t *pool; svn_revnum_t *latest_revnum; svn_error_t *err; svn_boolean_t allow_unver_obstructions = FALSE; svn_boolean_t depth_is_sticky = FALSE; if (!PyArg_ParseTuple(args, "s|bbOzbb", &target, &use_commit_times, &recurse, ¬ify_func, &diff3_cmd, &depth_is_sticky, &allow_unver_obstructions)) return NULL; ADM_CHECK_CLOSED(admobj); pool = Pool(NULL); if (pool == NULL) return NULL; latest_revnum = (svn_revnum_t *)apr_palloc(pool, sizeof(svn_revnum_t)); Py_BEGIN_ALLOW_THREADS #if ONLY_SINCE_SVN(1, 5) /* FIXME: Support all values of depth */ /* FIXME: Support fetch_func */ /* FIXME: Support conflict func */ err = svn_wc_get_update_editor3(latest_revnum, admobj->adm, target, use_commit_times, recurse?svn_depth_infinity:svn_depth_files, depth_is_sticky, allow_unver_obstructions, py_wc_notify_func, (void *)notify_func, py_cancel_check, NULL, NULL, NULL, NULL, NULL, diff3_cmd, NULL, &editor, &edit_baton, NULL, pool); #else if (allow_unver_obstructions) { PyErr_SetString(PyExc_NotImplementedError, "allow_unver_obstructions is not supported in svn < 1.5"); apr_pool_destroy(pool); PyEval_RestoreThread(_save); return NULL; } if (depth_is_sticky) { PyErr_SetString(PyExc_NotImplementedError, "depth_is_sticky is not supported in svn < 1.5"); apr_pool_destroy(pool); PyEval_RestoreThread(_save); return NULL; } err = svn_wc_get_update_editor2(latest_revnum, admobj->adm, target, use_commit_times, recurse, py_wc_notify_func, (void *)notify_func, py_cancel_check, NULL, diff3_cmd, &editor, &edit_baton, NULL, pool); #endif Py_END_ALLOW_THREADS if (err != NULL) { handle_svn_error(err); svn_error_clear(err); apr_pool_destroy(pool); return NULL; } Py_INCREF(admobj); return new_editor_object(NULL, editor, edit_baton, pool, &Editor_Type, wc_done_handler, admobj, NULL); } static bool py_dict_to_wcprop_changes(PyObject *dict, apr_pool_t *pool, apr_array_header_t **ret) { 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 = PyString_AsString(key); if (val == Py_None) { prop->value = NULL; } else { prop->value = svn_string_ncreate(PyString_AsString(val), PyString_Size(val), pool); } APR_ARRAY_PUSH(*ret, svn_prop_t *) = prop; } return true; } static PyObject *adm_has_binary_prop(PyObject *self, PyObject *args) { char *path; svn_boolean_t binary; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_has_binary_prop(&binary, path, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(binary); } static PyObject *adm_process_committed(PyObject *self, PyObject *args, PyObject *kwargs) { char *path, *rev_date = NULL, *rev_author = NULL; bool recurse, remove_lock = false; unsigned char *digest = NULL; svn_revnum_t new_revnum; PyObject *py_wcprop_changes = Py_None; apr_array_header_t *wcprop_changes = NULL; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; int digest_len; svn_boolean_t remove_changelist = FALSE; char *kwnames[] = { "path", "recurse", "new_revnum", "rev_date", "rev_author", "wcprop_changes", "remove_lock", "digest", "remove_changelist", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sblzz|Obz#b", kwnames, &path, &recurse, &new_revnum, &rev_date, &rev_author, &py_wcprop_changes, &remove_lock, &digest, &digest_len, &remove_changelist)) return NULL; #if PY_VERSION_HEX < 0x02050000 PyErr_Warn(PyExc_DeprecationWarning, "process_committed is deprecated. Use process_committed_queue instead."); #else PyErr_WarnEx(PyExc_DeprecationWarning, "process_committed is deprecated. Use process_committed_queue instead.", 2); #endif ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!py_dict_to_wcprop_changes(py_wcprop_changes, temp_pool, &wcprop_changes)) { apr_pool_destroy(temp_pool); return NULL; } #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed4( svn_path_canonicalize(path, temp_pool), admobj->adm, recurse, new_revnum, rev_date, rev_author, wcprop_changes, remove_lock, remove_changelist, digest, temp_pool)); #else if (remove_changelist) { PyErr_SetString(PyExc_NotImplementedError, "remove_changelist only supported in svn < 1.6"); apr_pool_destroy(temp_pool); return NULL; } RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(svn_path_canonicalize(path, temp_pool), admobj->adm, recurse, new_revnum, rev_date, rev_author, wcprop_changes, remove_lock, digest, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *adm_close(PyObject *self) { AdmObject *admobj = (AdmObject *)self; if (admobj->adm != NULL) { #if ONLY_SINCE_SVN(1, 6) apr_pool_t *temp_pool = Pool(NULL); Py_BEGIN_ALLOW_THREADS svn_wc_adm_close2(admobj->adm, temp_pool); apr_pool_destroy(temp_pool); #else Py_BEGIN_ALLOW_THREADS svn_wc_adm_close(admobj->adm); #endif Py_END_ALLOW_THREADS admobj->adm = NULL; } Py_RETURN_NONE; } static void adm_dealloc(PyObject *self) { apr_pool_destroy(((AdmObject *)self)->pool); PyObject_Del(self); } static PyObject *adm_repr(PyObject *self) { AdmObject *admobj = (AdmObject *)self; if (admobj->adm == NULL) { return PyString_FromFormat("", admobj); } else { return PyString_FromFormat("", svn_wc_adm_access_path(admobj->adm)); } } static PyObject *adm_remove_lock(PyObject *self, PyObject *args) { char *path; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_remove_lock(path, admobj->adm, temp_pool)) apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *get_ancestry(PyObject *self, PyObject *args) { char *path; char *url; svn_revnum_t rev; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_ancestry(&url, &rev, path, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); return Py_BuildValue("(si)", url, rev); } static PyObject *maybe_set_repos_root(PyObject *self, PyObject *args) { char *path, *repos; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "ss", &path, &repos)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_maybe_set_repos_root(admobj->adm, path, repos, temp_pool)); Py_RETURN_NONE; } static PyObject *add_repos_file(PyObject *self, PyObject *args, PyObject *kwargs) { char *kwnames[] = { "dst_path", "new_base_contents", "new_contents", "new_base_props", "new_props", "copyfrom_url", "copyfrom_rev", "notify", NULL }; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; char *dst_path, *copyfrom_url = NULL; svn_revnum_t copyfrom_rev = -1; PyObject *py_new_base_contents, *py_new_contents, *py_new_base_props, *py_new_props, *notify = Py_None; svn_stream_t *new_contents, *new_base_contents; apr_hash_t *new_props, *new_base_props; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sOOOO|zlO", kwnames, &dst_path, &py_new_base_contents, &py_new_contents, &py_new_base_props, &py_new_props, ©from_url, ©from_rev, ¬ify)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; new_base_props = prop_dict_to_hash(temp_pool, py_new_base_props); new_props = prop_dict_to_hash(temp_pool, py_new_props); new_base_contents = new_py_stream(temp_pool, py_new_base_contents); new_contents = new_py_stream(temp_pool, py_new_contents); #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_add_repos_file3(dst_path, admobj->adm, new_base_contents, new_contents, new_base_props, new_props, copyfrom_url, copyfrom_rev, py_cancel_check, NULL, py_wc_notify_func, notify, temp_pool)); #else PyErr_SetString(PyExc_NotImplementedError, "add_repos_file3 not supported on svn < 1.6"); apr_pool_destroy(temp_pool); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *mark_missing_deleted(PyObject *self, PyObject *args) { char *path; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_mark_missing_deleted(path, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *remove_from_revision_control(PyObject *self, PyObject *args) { char *name; svn_boolean_t destroy_wf = FALSE, instant_error = FALSE; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; if (!PyArg_ParseTuple(args, "s|bb", &name, &destroy_wf, &instant_error)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_remove_from_revision_control(admobj->adm, name, destroy_wf, instant_error, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } #if ONLY_SINCE_SVN(1, 6) static svn_error_t *wc_validator3(void *baton, const char *uuid, const char *url, const char *root_url, apr_pool_t *pool) { PyObject *py_validator = baton, *ret; if (py_validator == Py_None) { return NULL; } ret = PyObject_CallFunction(py_validator, "sss", uuid, url, root_url); if (ret == NULL) { return py_svn_error(); } Py_DECREF(ret); return NULL; } #else static 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; if (py_validator == Py_None) { return NULL; } ret = PyObject_CallFunction(py_validator, "ssO", uuid, url, Py_None); if (ret == NULL) { return py_svn_error(); } Py_DECREF(ret); return NULL; } #endif static PyObject *relocate(PyObject *self, PyObject *args) { char *path, *from, *to; AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; svn_boolean_t recurse = TRUE; PyObject *py_validator = Py_None; if (!PyArg_ParseTuple(args, "sss|bO:relocate", &path, &from, &to, &recurse, &py_validator)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate3(path, admobj->adm, from, to, recurse, wc_validator3, py_validator, temp_pool)); #else RUN_SVN_WITH_POOL(temp_pool, svn_wc_relocate2(path, admobj->adm, from, to, recurse, wc_validator2, py_validator, temp_pool)); #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *crop_tree(PyObject *self, PyObject *args) { char *target; svn_depth_t depth; PyObject *notify; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "si|O", &target, &depth, ¬ify)) return NULL; ADM_CHECK_CLOSED(admobj); #if ONLY_SINCE_SVN(1, 6) temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_crop_tree(admobj->adm, target, depth, py_wc_notify_func, notify, py_cancel_check, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; #else PyErr_SetString(PyExc_NotImplementedError, "crop_tree only available on subversion < 1.6"); return NULL; #endif } static PyObject *translated_stream(PyObject *self, PyObject *args) { char *path, *versioned_file; StreamObject *ret; svn_stream_t *stream; AdmObject *admobj = (AdmObject *)self; apr_pool_t *stream_pool; int flags; if (!PyArg_ParseTuple(args, "ssi", &path, &versioned_file, &flags)) return NULL; ADM_CHECK_CLOSED(admobj); #if ONLY_SINCE_SVN(1, 5) stream_pool = Pool(NULL); if (stream_pool == NULL) return NULL; RUN_SVN_WITH_POOL(stream_pool, svn_wc_translated_stream(&stream, path, versioned_file, admobj->adm, flags, stream_pool)); ret = PyObject_New(StreamObject, &Stream_Type); if (ret == NULL) return NULL; ret->pool = stream_pool; ret->closed = FALSE; ret->stream = stream; return (PyObject *)ret; #else PyErr_SetString(PyExc_NotImplementedError, "translated_stream() is only available on Subversion >= 1.5"); return NULL; #endif } static PyObject *adm_text_modified(PyObject *self, PyObject *args) { char *path; svn_boolean_t force_comparison = FALSE; apr_pool_t *temp_pool; svn_boolean_t ret; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "s|b", &path, &force_comparison)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_text_modified_p(&ret, path, force_comparison, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(ret); } static PyObject *adm_props_modified(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; svn_boolean_t ret; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_props_modified_p(&ret, path, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(ret); } static PyObject *adm_process_committed_queue(PyObject *self, PyObject *args) { apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; svn_revnum_t revnum; char *date, *author; CommittedQueueObject *py_queue; if (!PyArg_ParseTuple(args, "O!lss", &CommittedQueue_Type, &py_queue, &revnum, &date, &author)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed_queue(py_queue->queue, admobj->adm, revnum, date, author, temp_pool)); #else { int i; for (i = 0; i < py_queue->queue->queue->nelts; i++) { committed_queue_item_t *cqi = APR_ARRAY_IDX(py_queue->queue->queue, i, committed_queue_item_t *); RUN_SVN_WITH_POOL(temp_pool, svn_wc_process_committed3(cqi->path, admobj->adm, cqi->recurse, revnum, date, author, cqi->wcprop_changes, cqi->remove_lock, cqi->digest, temp_pool)); } } #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *get_actual_target(PyObject *self, PyObject *args) { char *path; const char *anchor = NULL, *target = NULL; apr_pool_t *temp_pool; PyObject *ret; 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_wc_get_actual_target(svn_path_canonicalize(path, temp_pool), &anchor, &target, temp_pool)); ret = Py_BuildValue("(ss)", anchor, target); apr_pool_destroy(temp_pool); return ret; } static PyObject *is_wc_root(PyObject *self, PyObject *args) { char *path; svn_boolean_t wc_root; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_is_wc_root(&wc_root, path, admobj->adm, temp_pool)); apr_pool_destroy(temp_pool); return PyBool_FromLong(wc_root); } static PyObject *transmit_text_deltas(PyObject *self, PyObject *args) { char *path; const char *tempfile; svn_boolean_t fulltext; PyObject *editor_obj, *py_digest; unsigned char digest[APR_MD5_DIGESTSIZE]; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; PyObject *ret; if (!PyArg_ParseTuple(args, "sbO", &path, &fulltext, &editor_obj)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; Py_INCREF(editor_obj); RUN_SVN_WITH_POOL(temp_pool, svn_wc_transmit_text_deltas2(&tempfile, digest, svn_path_canonicalize(path, temp_pool), admobj->adm, fulltext, &py_editor, editor_obj, temp_pool)); py_digest = PyString_FromStringAndSize((char *)digest, APR_MD5_DIGESTSIZE); if (py_digest == NULL) { apr_pool_destroy(temp_pool); return NULL; } ret = Py_BuildValue("sN", tempfile, py_digest); if (ret == NULL) { apr_pool_destroy(temp_pool); return NULL; } apr_pool_destroy(temp_pool); return ret; } static PyObject *transmit_prop_deltas(PyObject *self, PyObject *args) { char *path; PyObject *editor_obj; apr_pool_t *temp_pool; AdmObject *admobj = (AdmObject *)self; EntryObject *py_entry; if (!PyArg_ParseTuple(args, "sO!O", &path, &Entry_Type, &py_entry, &editor_obj)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; Py_INCREF(editor_obj); RUN_SVN_WITH_POOL(temp_pool, svn_wc_transmit_prop_deltas(svn_path_canonicalize(path, temp_pool), admobj->adm, &(py_entry->entry), &py_editor, editor_obj, NULL, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *retrieve(PyObject *self, PyObject *args) { char *path; svn_wc_adm_access_t *result; AdmObject *admobj = (AdmObject *)self, *ret; apr_pool_t *pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_wc_adm_retrieve(&result, admobj->adm, svn_path_canonicalize(path, pool), pool)); ret = PyObject_New(AdmObject, &Adm_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->adm = result; return (PyObject *)ret; } static PyObject *probe_retrieve(PyObject *self, PyObject *args) { char *path; svn_wc_adm_access_t *result; AdmObject *admobj = (AdmObject *)self, *ret; apr_pool_t *pool; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_retrieve(&result, admobj->adm, svn_path_canonicalize(path, pool), pool)); ret = PyObject_New(AdmObject, &Adm_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->adm = result; return (PyObject *)ret; } static PyObject *probe_try(PyObject *self, PyObject *args) { char *path; svn_wc_adm_access_t *result = NULL; AdmObject *admobj = (AdmObject *)self, *ret; apr_pool_t *pool; int levels_to_lock = -1; svn_boolean_t writelock = FALSE; if (!PyArg_ParseTuple(args, "s|bi", &path, &writelock, &levels_to_lock)) return NULL; ADM_CHECK_CLOSED(admobj); pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_wc_adm_probe_try3(&result, admobj->adm, svn_path_canonicalize(path, pool), writelock, levels_to_lock, py_cancel_check, NULL, pool)); if (result == NULL) { apr_pool_destroy(pool); Py_RETURN_NONE; } ret = PyObject_New(AdmObject, &Adm_Type); if (ret == NULL) return NULL; ret->pool = pool; ret->adm = result; return (PyObject *)ret; } static PyObject *resolved_conflict(PyObject *self, PyObject *args) { AdmObject *admobj = (AdmObject *)self; apr_pool_t *temp_pool; svn_boolean_t resolve_props, resolve_tree, resolve_text; int depth; #if ONLY_SINCE_SVN(1, 5) svn_wc_conflict_choice_t conflict_choice; #else int conflict_choice; #endif PyObject *notify_func = Py_None; char *path; if (!PyArg_ParseTuple(args, "sbbbii|O", &path, &resolve_text, &resolve_props, &resolve_tree, &depth, &conflict_choice, ¬ify_func)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_resolved_conflict4(path, admobj->adm, resolve_text, resolve_props, resolve_tree, depth, conflict_choice, py_wc_notify_func, (void *)notify_func, py_cancel_check, NULL, temp_pool)); #elif ONLY_SINCE_SVN(1, 5) if (resolve_tree) { PyErr_SetString(PyExc_NotImplementedError, "resolve_tree not supported with svn < 1.6"); apr_pool_destroy(temp_pool); return NULL; } else { RUN_SVN_WITH_POOL(temp_pool, svn_wc_resolved_conflict3(path, admobj->adm, resolve_text, resolve_props, depth, conflict_choice, py_wc_notify_func, (void *)notify_func, py_cancel_check, NULL, temp_pool)); } #else if (resolve_tree) { PyErr_SetString(PyExc_NotImplementedError, "resolve_tree not supported with svn < 1.6"); apr_pool_destroy(temp_pool); return NULL; } else if (depth != svn_depth_infinity && depth != svn_depth_files) { PyErr_SetString(PyExc_NotImplementedError, "only infinity and files values for depth are supported"); apr_pool_destroy(temp_pool); return NULL; } else if (conflict_choice != 0) { PyErr_SetString(PyExc_NotImplementedError, "conflict choice not supported with svn < 1.5"); apr_pool_destroy(temp_pool); return NULL; } else { RUN_SVN_WITH_POOL(temp_pool, svn_wc_resolved_conflict2(path, admobj->adm, resolve_text, resolve_props, (depth == svn_depth_infinity), py_wc_notify_func, (void *)notify_func, py_cancel_check, NULL, temp_pool)); } #endif apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyObject *conflicted(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; PyObject *ret; AdmObject *admobj = (AdmObject *)self; svn_boolean_t text_conflicted, prop_conflicted, tree_conflicted; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 6) RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted, &tree_conflicted, path, admobj->adm, temp_pool)); ret = Py_BuildValue("(bbb)", text_conflicted, prop_conflicted, tree_conflicted); #else RUN_SVN_WITH_POOL(temp_pool, svn_wc_conflicted_p(&text_conflicted, &prop_conflicted, path, admobj->adm, temp_pool)); ret = Py_BuildValue("(bbO)", text_conflicted, prop_conflicted, Py_None); #endif apr_pool_destroy(temp_pool); return ret; } /** * Determine the status of a file in the specified working copy. * * :return: A status object. */ static PyObject *ra_status(PyObject *self, PyObject *args) { char *path; svn_wc_status2_t *st; apr_pool_t *temp_pool; PyObject *ret; AdmObject *admobj = (AdmObject *)self; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; ADM_CHECK_CLOSED(admobj); temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_status2( &st, svn_path_canonicalize(svn_path_join(svn_wc_adm_access_path(admobj->adm), path, temp_pool), temp_pool), admobj->adm, temp_pool)); ret = py_status(st); apr_pool_destroy(temp_pool); return (PyObject*)ret; } static PyMethodDef adm_methods[] = { { "prop_set", adm_prop_set, METH_VARARGS, "S.prop_set(name, value, path, skip_checks=False)" }, { "access_path", (PyCFunction)adm_access_path, METH_NOARGS, "S.access_path() -> path\n" "Returns the base path for this working copy handle." }, { "prop_get", adm_prop_get, METH_VARARGS, "S.prop_get(name, path) -> value" }, { "entries_read", adm_entries_read, METH_VARARGS, "S.entries_read(include_hidden=False) -> dict" }, { "walk_entries", adm_walk_entries, METH_VARARGS, "S.walk_entries(path, callback, show_hidden=False)\n" "callback should be a function that takes a path and a wc entry" }, { "locked", (PyCFunction)adm_locked, METH_NOARGS, "S.locked() -> bool" }, { "get_prop_diffs", adm_get_prop_diffs, METH_VARARGS, "S.get_prop_diffs(path) -> (propchanges, originalprops)" }, { "add", (PyCFunction)adm_add, METH_VARARGS|METH_KEYWORDS, "S.add(path, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, { "copy", adm_copy, METH_VARARGS, "S.copy(src_path, dest_path, notify_func=None)" }, { "delete", (PyCFunction)adm_delete, METH_VARARGS|METH_KEYWORDS, "S.delete(path, notify_func=None, keep_local=False)" }, { "crawl_revisions", (PyCFunction)adm_crawl_revisions, METH_VARARGS|METH_KEYWORDS, "S.crawl_revisions(path, reporter, restore_files=True, recurse=True, use_commit_times=True, notify_func=None) -> None" }, { "get_update_editor", adm_get_update_editor, METH_VARARGS, NULL }, { "close", (PyCFunction)adm_close, METH_NOARGS, "S.close()" }, { "entry", (PyCFunction)adm_entry, METH_VARARGS, "s.entry(path, show_hidden=False) -> entry" }, { "process_committed", (PyCFunction)adm_process_committed, METH_VARARGS|METH_KEYWORDS, "S.process_committed(path, recurse, new_revnum, rev_date, rev_author, wcprop_changes=None, remove_lock=False, digest=None)" }, { "process_committed_queue", (PyCFunction)adm_process_committed_queue, METH_VARARGS, "S.process_committed_queue(queue, new_revnum, rev_date, rev_author)" }, { "remove_lock", (PyCFunction)adm_remove_lock, METH_VARARGS, "S.remove_lock(path)" }, { "has_binary_prop", (PyCFunction)adm_has_binary_prop, METH_VARARGS, "S.has_binary_prop(path) -> bool" }, { "text_modified", (PyCFunction)adm_text_modified, METH_VARARGS, "S.text_modified(filename, force_comparison=False) -> bool" }, { "props_modified", (PyCFunction)adm_props_modified, METH_VARARGS, "S.props_modified(filename) -> bool" }, { "get_ancestry", (PyCFunction)get_ancestry, METH_VARARGS, "S.get_ancestry(path) -> (url, rev)" }, { "maybe_set_repos_root", (PyCFunction)maybe_set_repos_root, METH_VARARGS, "S.maybe_set_repos_root(path, repos)" }, { "add_repos_file", (PyCFunction)add_repos_file, METH_KEYWORDS, "S.add_repos_file(dst_path, new_base_contents, new_contents, new_base_props, new_props, copyfrom_url=None, copyfrom_rev=-1, notify_func=None)" }, { "mark_missing_deleted", (PyCFunction)mark_missing_deleted, METH_VARARGS, "S.mark_missing_deleted(path)" }, { "remove_from_revision_control", (PyCFunction)remove_from_revision_control, METH_VARARGS, "S.remove_from_revision_control(name, destroy_wf=False, instant_error=False)" }, { "relocate", (PyCFunction)relocate, METH_VARARGS, "S.relocate(path, from, to, recurse=TRUE, validator=None)" }, { "crop_tree", (PyCFunction)crop_tree, METH_VARARGS, "S.crop_tree(target, depth, notify_func=None, cancel=None)" }, { "translated_stream", (PyCFunction)translated_stream, METH_VARARGS, "S.translated_stream(path, versioned_file, flags) -> stream" }, { "is_wc_root", (PyCFunction)is_wc_root, METH_VARARGS, "S.is_wc_root(path) -> wc_root" }, { "transmit_text_deltas", (PyCFunction)transmit_text_deltas, METH_VARARGS, "S.transmit_text_deltas(fulltext, editor) -> (tempfile, digest)" }, { "transmit_prop_deltas", (PyCFunction)transmit_prop_deltas, METH_VARARGS, "S.transmit_prop_deltas(path, entry, editor)" }, { "probe_retrieve", (PyCFunction)probe_retrieve, METH_VARARGS, "S.probe_retrieve(path) -> WorkingCopy" }, { "retrieve", (PyCFunction)retrieve, METH_VARARGS, "S.retrieve(path) -> WorkingCopy" }, { "probe_try", (PyCFunction)probe_try, METH_VARARGS, "S.probe_try(path, write_lock=False, levels_to_lock=-1)" }, { "conflicted", (PyCFunction)conflicted, METH_VARARGS, "S.conflicted(path) -> (text_conflicted, prop_conflicted, tree_conflicted)" }, { "resolved_conflict", (PyCFunction)resolved_conflict, METH_VARARGS, "S.resolved_conflict(path, resolve_text, resolve_props, resolve_tree, depth, conflict_choice, notify_func=None, cancel=None)" }, { "status", (PyCFunction)ra_status, METH_VARARGS, "status(wc_path) -> Status" }, { NULL, } }; static PyTypeObject Adm_Type = { PyObject_HEAD_INIT(NULL) 0, "wc.WorkingCopy", /* const char *tp_name; For printing, in format "." */ sizeof(AdmObject), 0,/* Py_ssize_t tp_basicsize, tp_itemsize; For allocation */ /* Methods to implement standard operations */ adm_dealloc, /* destructor tp_dealloc; */ NULL, /* printfunc tp_print; */ NULL, /* getattrfunc tp_getattr; */ NULL, /* setattrfunc tp_setattr; */ NULL, /* cmpfunc tp_compare; */ adm_repr, /* reprfunc tp_repr; */ /* Method suites for standard classes */ NULL, /* PyNumberMethods *tp_as_number; */ NULL, /* PySequenceMethods *tp_as_sequence; */ NULL, /* PyMappingMethods *tp_as_mapping; */ /* More standard operations (here for binary compatibility) */ NULL, /* hashfunc tp_hash; */ NULL, /* ternaryfunc tp_call; */ adm_repr, /* reprfunc tp_repr; */ NULL, /* getattrofunc tp_getattro; */ NULL, /* setattrofunc tp_setattro; */ /* Functions to access object as input/output buffer */ NULL, /* PyBufferProcs *tp_as_buffer; */ /* Flags to define presence of optional/expanded features */ 0, /* long tp_flags; */ "Local working copy", /* const char *tp_doc; Documentation string */ /* Assigned meaning in release 2.0 */ /* call function for all accessible objects */ NULL, /* traverseproc tp_traverse; */ /* delete references to contained objects */ NULL, /* inquiry tp_clear; */ /* Assigned meaning in release 2.1 */ /* rich comparisons */ NULL, /* richcmpfunc tp_richcompare; */ /* weak reference enabler */ 0, /* Py_ssize_t tp_weaklistoffset; */ /* Added in release 2.2 */ /* Iterators */ NULL, /* getiterfunc tp_iter; */ NULL, /* iternextfunc tp_iternext; */ /* Attribute descriptor and subclassing stuff */ adm_methods, /* struct PyMethodDef *tp_methods; */ NULL, /* struct PyMemberDef *tp_members; */ NULL, /* struct PyGetSetDef *tp_getset; */ NULL, /* struct _typeobject *tp_base; */ NULL, /* PyObject *tp_dict; */ NULL, /* descrgetfunc tp_descr_get; */ NULL, /* descrsetfunc tp_descr_set; */ 0, /* Py_ssize_t tp_dictoffset; */ NULL, /* initproc tp_init; */ NULL, /* allocfunc tp_alloc; */ adm_init, /* newfunc tp_new; */ }; static void 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 PyString_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) { char *path; AdmObject *admobj; PyObject *py_wcprop_changes = Py_None; svn_boolean_t remove_lock = FALSE, remove_changelist = FALSE; char *digest = NULL; svn_boolean_t recurse = FALSE; apr_pool_t *temp_pool; apr_array_header_t *wcprop_changes; int digest_len; if (!PyArg_ParseTuple(args, "sO!|bObbz#", &path, &Adm_Type, &admobj, &recurse, &py_wcprop_changes, &remove_lock, &remove_changelist, &digest, &digest_len)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; if (!py_dict_to_wcprop_changes(py_wcprop_changes, self->pool, &wcprop_changes)) { apr_pool_destroy(temp_pool); return NULL; } path = apr_pstrdup(self->pool, path); if (path == NULL) { PyErr_NoMemory(); return NULL; } if (digest != NULL) { if (digest_len != APR_MD5_DIGESTSIZE) { PyErr_SetString(PyExc_ValueError, "Invalid size for md5 digest"); apr_pool_destroy(temp_pool); return NULL; } digest = apr_pstrdup(self->pool, digest); if (digest == NULL) { PyErr_NoMemory(); return NULL; } } RUN_SVN_WITH_POOL(temp_pool, svn_wc_queue_committed(&self->queue, path, admobj->adm, recurse, wcprop_changes, remove_lock, remove_changelist, (unsigned char *)digest, temp_pool)); apr_pool_destroy(temp_pool); Py_RETURN_NONE; } static PyMethodDef committed_queue_methods[] = { { "queue", (PyCFunction)committed_queue_queue, METH_VARARGS, "S.queue(path, adm, recurse, wcprop_changes, remove_lock, remove_changelist, digest)" }, { NULL } }; static PyTypeObject CommittedQueue_Type = { PyObject_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; */ NULL, /* printfunc tp_print; */ 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; */ }; /** * 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 }; char *wc_path, *trail_url=NULL; bool committed=false; PyObject *ret; svn_wc_revision_status_t *revstatus; apr_pool_t *temp_pool; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|zb", kwnames, &wc_path, &trail_url, &committed)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_revision_status( &revstatus, svn_path_canonicalize(wc_path, temp_pool), 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) { char *name; apr_pool_t *pool; svn_boolean_t ret; if (!PyArg_ParseTuple(args, "s", &name)) return NULL; pool = Pool(NULL); if (pool == 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 = PyString_FromString(dir); apr_pool_destroy(pool); return ret; } static PyObject *set_adm_dir(PyObject *self, PyObject *args) { apr_pool_t *temp_pool; char *name; 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_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; char *path; PyObject *ret; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; #if PY_VERSION_HEX < 0x02050000 PyErr_Warn(PyExc_DeprecationWarning, "get_pristine_copy_path is deprecated. Use get_pristine_contents instead."); #else PyErr_WarnEx(PyExc_DeprecationWarning, "get_pristine_copy_path is deprecated. Use get_pristine_contents instead.", 2); #endif RUN_SVN_WITH_POOL(pool, svn_wc_get_pristine_copy_path(svn_path_canonicalize(path, pool), &pristine_path, pool)); ret = PyString_FromString(pristine_path); apr_pool_destroy(pool); return ret; } static PyObject *get_pristine_contents(PyObject *self, PyObject *args) { char *path; apr_pool_t *temp_pool; #if ONLY_SINCE_SVN(1, 6) apr_pool_t *stream_pool; StreamObject *ret; svn_stream_t *stream; #else PyObject *ret; const char *pristine_path; #endif if (!PyArg_ParseTuple(args, "s", &path)) return NULL; #if ONLY_SINCE_SVN(1, 6) 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; } RUN_SVN_WITH_POOL(stream_pool, svn_wc_get_pristine_contents(&stream, svn_path_canonicalize(path, temp_pool), 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; #else temp_pool = Pool(NULL); if (temp_pool == NULL) return NULL; RUN_SVN_WITH_POOL(temp_pool, svn_wc_get_pristine_copy_path(svn_path_canonicalize(path, temp_pool), &pristine_path, temp_pool)); ret = PyFile_FromString((char *)pristine_path, "rb"); apr_pool_destroy(temp_pool); return ret; #endif } static PyObject *ensure_adm(PyObject *self, PyObject *args, PyObject *kwargs) { char *path, *uuid, *url; char *repos=NULL; svn_revnum_t rev=-1; apr_pool_t *pool; char *kwnames[] = { "path", "uuid", "url", "repos", "rev", "depth", NULL }; svn_depth_t depth = svn_depth_infinity; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sss|sli", kwnames, &path, &uuid, &url, &repos, &rev, &depth)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; #if ONLY_SINCE_SVN(1, 5) RUN_SVN_WITH_POOL(pool, svn_wc_ensure_adm3(svn_path_canonicalize(path, pool), uuid, url, repos, rev, depth, pool)); #else if (depth != svn_depth_infinity) { PyErr_SetString(PyExc_NotImplementedError, "depth != infinity not supported with svn < 1.5"); apr_pool_destroy(pool); return NULL; } RUN_SVN_WITH_POOL(pool, svn_wc_ensure_adm2(svn_path_canonicalize(path, pool), uuid, url, repos, rev, pool)); #endif apr_pool_destroy(pool); Py_RETURN_NONE; } static PyObject *check_wc(PyObject *self, PyObject *args) { char *path; apr_pool_t *pool; int wc_format; if (!PyArg_ParseTuple(args, "s", &path)) return NULL; pool = Pool(NULL); if (pool == NULL) return NULL; RUN_SVN_WITH_POOL(pool, svn_wc_check_wc(svn_path_canonicalize(path, pool), &wc_format, pool)); apr_pool_destroy(pool); return PyLong_FromLong(wc_format); } static PyObject *cleanup_wc(PyObject *self, PyObject *args, PyObject *kwargs) { char *path; char *diff3_cmd = NULL; char *kwnames[] = { "path", "diff3_cmd", NULL }; apr_pool_t *temp_pool; if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|z", kwnames, &path, &diff3_cmd)) return NULL; temp_pool = Pool(NULL); if (temp_pool == NULL) 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) { #if ONLY_SINCE_SVN(1, 5) char *str; PyObject *py_list; apr_array_header_t *list; apr_pool_t *temp_pool; svn_boolean_t ret; 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); #else PyErr_SetNone(PyExc_NotImplementedError); return NULL; #endif } 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, } }; void initwc(void) { PyObject *mod; if (PyType_Ready(&Entry_Type) < 0) return; if (PyType_Ready(&Status_Type) < 0) return; if (PyType_Ready(&Adm_Type) < 0) return; if (PyType_Ready(&Editor_Type) < 0) return; if (PyType_Ready(&FileEditor_Type) < 0) return; if (PyType_Ready(&DirectoryEditor_Type) < 0) return; if (PyType_Ready(&TxDeltaWindowHandler_Type) < 0) return; if (PyType_Ready(&Stream_Type) < 0) return; if (PyType_Ready(&CommittedQueue_Type) < 0) return; apr_initialize(); mod = Py_InitModule3("wc", wc_methods, "Working Copies"); if (mod == NULL) return; PyModule_AddIntConstant(mod, "SCHEDULE_NORMAL", 0); PyModule_AddIntConstant(mod, "SCHEDULE_ADD", 1); PyModule_AddIntConstant(mod, "SCHEDULE_DELETE", 2); PyModule_AddIntConstant(mod, "SCHEDULE_REPLACE", 3); #if ONLY_SINCE_SVN(1, 5) 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); #endif 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); #if ONLY_SINCE_SVN(1, 5) 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); #endif #if ONLY_BEFORE_SVN(1, 7) /* Subversion 1.7 has a couple of significant behaviour changes that break subvertpy. * We haven't updated the code to deal with these changes in behaviour yet. * */ PyModule_AddObject(mod, "WorkingCopy", (PyObject *)&Adm_Type); Py_INCREF(&Adm_Type); PyModule_AddObject(mod, "CommittedQueue", (PyObject *)&CommittedQueue_Type); Py_INCREF(&CommittedQueue_Type); #endif } subvertpy/wc.h000066400000000000000000000021151214203126100136720ustar00rootroot00000000000000/* * 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 void py_wc_notify_func(void *baton, const svn_wc_notify_t *notify, apr_pool_t *pool); #ifdef __GNUC__ #pragma GCC visibility pop #endif #endif /* _BZR_SVN_WC_H_ */