diff options
author | Michal ÄŒihaÅ™ <michal@cihar.com> | 2016-07-27 12:52:13 +0200 |
---|---|---|
committer | Michal ÄŒihaÅ™ <michal@cihar.com> | 2016-07-27 12:52:13 +0200 |
commit | 947d5bf39c69df920e52daf2dc041448058db249 (patch) | |
tree | 01f45984facfa1fa63884fee546a1d1a887f2d1d | |
parent | 8eff0f2aa237e66b54f289e474921333e4b7abd6 (diff) |
Imported Upstream version 0.154.0
957 files changed, 34796 insertions, 0 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..bd9914c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: python +python: + - "2.7" + - "3.3" + - "3.4" + - "3.5-dev" +addons: + apt: + packages: + - diffstat +sudo: false +script: cd tests; python suite.py @@ -0,0 +1,23 @@ +Adrian Schroeter +Andreas Bauer +Christoph Thiel +David Mayr +Dirk Mueller +Juergen Weigert +Lars Rupp +Lenz Grimmer +Ludwig Nussel +Marcus Huewe +Marcus Rueckert +Martin Mohring +Michael Schroeder +Michael Wolf +Michal Marek +Pavol Rusnak +Peter Poeml +Sascha Peilicke +Susanne Oberhauser +Tom Patzig +Werner Fink +Will Stephenson +Jan-Simon Möller @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. @@ -0,0 +1,1093 @@ +0.154 + - switch to new obs_scm service when adding git URL's + - set OSC_VERSION environment for source services + (allows to work in local git checkouts when using obs_scm) + +0.153 + - "my sr" is using the server side request collection to get right results + - maintenance request offers to supersede old, but still open requests + - add build --vm-telnet option for getting debug shell in KVM builds + - add buildhistory --limit option + OBS 2.7 only: + - add "addchannels" and "enablechannel" commands + - support new package instances on branching when using -N parameter + - add --linkrev option to branch command + - add --add-repository-block option to branch command + - add --add-repository-rebuild option to branch command + - add service merge command + - add service wait command + +0.152 + - add support searching for groups via "group:" prefix + - show possible used incident projects on "maintained" command + OBS 2.7 only: + - support buildtime source services + - support maintenance_incident requests with acceptinfo data + - support maintenance_release requests with acceptinfo data + +0.151 + - fixed shell command injection via crafted _service files (CVE-2015-0778) + - fix times when data comes from OBS backend + - support updateing the link in target package for submit requests + - various minor bugfixes + +0.150 + - support local builds using builenv (for same build environment as a former build) + - add "osc api --edit" option to be able to edit some meta files directly + - follow the request order of the api (sorting according to priorization) + - add mr --release-project option for kgraft updates + - add support for makeoriginolder in request + +0.149 + - removed "--diff" option from the "createrequest" command + - introduced new "vc-cmd" config option, which is used to specify the path + to the vc script + - various bugfixes + +0.148 + - support new history including review history of OBS 2.6 + - display request priorities, if important or critical + - add "osc rq priorize" command to re-priorize existing requests + - allow also "osc rq ls" shortcut + - fish shell completion support + +0.147 + - support groups in maintainership requests + - fixing listing of review requests + - support expanded package listing (when using project links) + - fixing "osc add git://" behaviour + - using xz as default compression + - support local debian live (image) build format + - handle ppc64le for debian as well + - fix buildlog --strip-time + - some more minor bugfixes + - speedup update of a project working copy (in some cases) + +0.146 + - support maintenance release request with acceptinfo data (OBS 2.6) + - setlinkrev can be used to update frozen links to current revisions again + - report errors in case request accept fails + - support epoch number handling for local builds + - support bugowner request handling for groups + - support usage of fedoras mock to build packages + - support build --prefer-pkgs for Arch linux + - support bash-completion for .kiwi files + +0.145 + - allow to use the set-release option when running a manual release + - added support for "osc requestmaintainership PROJECT" + - various bugfixes: + - print_buildlog: do not strip tabs + - fixed "osc -H ..." in combination with a proxy + - fixed creation of ~/.osc_cookiejar + - Package.commit: create _meta for newly added packages + - fixed behavior of set_link_rev #72 + +0.144 + - allow commiting to package sources from linked projects. osc will ask to branch it first. + - group support in bugowner and maintainer command + +0.143 + - add option to add a auto-accept in future for delete requests (handy for admins) + - many bugfixes: + - plugin loading + - bugowner handling + - download of server side generated source "up -S" + - wipebinaries command + +0.142 + - ppc64p7 build support + - request --no-devel to disable request forwarding +# +# Features which requires OBS 2.5 +# + - authentification token support + +0.141 + - crash fixes + - support for kiwi appliance builds using obsrepositories:/ directive + - support for manual release of sources and binaries + - add --last parameter for build logs to show last finished log file, if currently building + - add signkey --sslcert option to fetch the optional create ssl certificate instead of gpg key + +0.140 + - support python 2.7 and python 3 in parallel now + - reworked plugin loading mechanism in order to avoid the (mass) breakage of existing + plugins due to the python 3 support. Nonetheless if a plugin uses the "@cmdln.option(...)" + decorator it has to import the cmdln module first via "from osc import cmdln". + - allow specifying directories as mv targets + - drop the support for deprecated cbinstall and cbpreinstall directives + - allow to set maintainer or bugowner ship for a binary package initially, but ask back if + this is the right place. + - support listing of deleted source files "ls -D $PROJECT $PACKAGE" + - build results do show that a succeeded is not yet published + - improved bash completions + - default build root includes repository and architecture name now + - --request-accept-or-revoke option, useful to handle mass approval of requests + - multiple minor bugfixes + +0.139 + - various bugfixes for owner search + - support generic emulator virtualization + - added "--host" argument to "osc build" (used to perform the build on a remote host) + - "search --maintained" is obsolete. Abort on usage. + - "maintainer --user" support to search for all official maintained instance for given user or group + - added support to abort a commit after displaying a default commit message in $EDITOR. As a result + other commands like "submitrequest" will also ask if the user wants to proceed if the default + comment/message wasn't changed. + +0.138 + - add support to remove repositories recursively (mostly only usefull for admins) + - submitrequest: old not anymore used maintenance code got removed. It is possible now + to create one request to submit all changed packages of an project in + one request. Just run "osc sr" in the checked out project directory. + - disable keyring usage by default. print warning about misconfigured keyrings. + - prdiff: new command to diff entire projects + +0.137 + - support single binary download via getbinaries command + - support to set the bugowner +# +# Features which requires OBS 2.4 +# + - offer to send set_bugowner request if target is not writeable + - support delete requests for repositories. + - support default maintainer/bugowner search based on binary package names + - support to lookup --all definitions of maintainers of bugowners. Either + for showing or setting them. + - buildinfo --debug option for verbose output of dependency calculation + +0.136 + - prefer TLS v1.1 or v1.2 if available + - declined is considered to be an open state (that is "osc rq list" also shows declined requests) + - added support to move files across packages via "osc mv" (fixes issue #10) + - various bugfixes: + * show source package name when running "osc se --binary ..." + * fixed encoding detection + * fixed build result listing for arch packages (affects "osc build") + * "osc ci --noservice" works also for "external"/flat packages + +0.135.1 + - do not forward requests to packages which do link anyway to original request target + +0.135 + - request accept is offering now to forward submit request if it is a devel area like webui does + - support archlinux builds (requires OBS 2.4) + - support maintenancerequest from local checkout + - bugfixes for review handling, result watching, gnome-keyring + +0.134 + - patchinfo call can work without checked out copy now + - use qemu as fallback for building not directly supported architectures + - "results --watch" option to watch build results until they finished building + - setlinkrev and linkpac ---current is setting vrev. this requires OBS 2.1.17 or 2.3 + - security fix for buildlog function, terminal control characters are limited now. +# +# Features which requires OBS 2.3 +# + - support dryrun of branching to preview the expected result. "osc sm" is doing this now by default. + - maintenance requests accept package lists as source and target incidents to be merged in + - add "setincident" command to "request" to re-direct a maintenance request + - ask user to create "maintenance incident" request when submit request is failing at release project + - "osc my patchinfos" is showing patchinfos where any open bug is assigned to user + - "osc my" or "osc my work" is including assigned patchinfos + - "osc branch --maintenance" is creating setups for maintenance + - "osc unlock" command to unlock packages or projects + +0.133 + - add --meta option also to "list", "cat" and "less" commands + - project checkout is skipping packages linking to project local packages by default + - add --keep-link option to copypac command + - source validators are not called by default anymore: + * They can get used via source services now + * Allows different validations based on the code streams +# +# Features which requires OBS 2.3 +# + - support source services using OBS project or package name + - support updateing _patchinfo file with new issues just by calling "osc patchinfo" again + - branch --add-repositories can be used to add repos from source project to target project + - branch --extend-package-names can be used to do mbranch like branch of a single package + - branch --new-package can be used to do branch from a not yet existing package (to define later submit target) + - show declined requests which created by user + +0.132 + - rdelete and undelete command requesting now a comment + - add 'requestbugownership' command for setting the bugowner via request +# +# Features which requires OBS 2.3 +# + - new command "createincident" to create maintenance incidents without a request + - support to create hidden project on "branch" and "createincident" commands + - osc waits and updates package after checkin when a source service is used + - support for the new service file mode for "update" and "checkout" command when + downloading server side generated files + - integration for local source services, they will replace the source_validator mechanism + +0.131 + - new command 'develproject' to print the devel project from the package meta. + - add blt and rblt commands, aka "buildlogtail" and "remotebuildlogtail" to show + just the end of a build log (for getting the fail reason faster). + CHANGE: the --start parameter is now called --offset + - add "createrequest -a add_group" option to create a group request + - add "createrequest -a add_me" shortcut + - add "less" command, doing the same as "osc cat" but with pager + - fallback to unexpanded diff mode on "osc diff" on merge error. + - support viewing the commit history of deleted packages + - show review states on "review list" + - new source service commands "localrun" and "disabledrun" to generate files without _service: prefix + - add "request supersede" and "review supersede" to supersede with existing request + - make it possible to run single source services, even when not specified in _service file. + (For example for doing a version update without creating a _service file: osc service lr update_source) + - protect rebuild and abortbuild commands with required "--all" option to mass failures by accident (similar to wipebinaries) + - "review accept/decline" is trying to change all reviews of a requests, if a specific one is not specified by user +# +# Features which requires OBS 2.3 +# + - "my requests" is doing faster and complete server side lookup now if available + - "review" command has been extended to handle reviews by project or by package maintainers + - support for new source service modes: disabled, trylocal and localonly + - support project wide source services + - support for armv7hl architecuture. used to denote armv7 + hardfloat binaries + - add force option to accept requests in review state. + - add "maintenancerequest" command to request a maintenance incident from maintenance team + - add "releaserequest" command run a maintenance update release process (for maintenance team only) + - allow to force the storage of project meta data (to ignore depending repositories for example) + - "my requests" is showing requests with open reviews also now + +0.130 + - new "revert" command to restore the original working copy file (without + downloading it) + - rewrote "diff" logic + - added new "--http-full-debug" option, "--http-debug" filters the + "Authentication" and "Set-Cookie" header + - added new "--disabled-cpio-bulk-download" option: disable downloading + packages as cpio archive from api + - added new "repairwc" command which tries to repair an inconsistent working + copy + - workaround for broken urllib2 in python 2.6.5: wrong credentials lead to an + infinite recursion + - support --interactive-review option when running "osc rq list <project>" + - improved "osc rq show <id> --interactive-review" + - do_config: added new options --stdin, --prompt, --no-echo: + --stdin: read value from stdin + --prompt: prompt for a value + --no-echo: prompt for a value but don't echo entered characters (for + instance to enter a passwd) + - added template support for a submitrequest accept/decline message + - lots of internal rewrites (new working copy handling etc.) + - support added for osc search 'perl(Foo::Bar)' + - New "service" command to run source services locally or trigger a re-run on the server. + - setlinkrev is setting now the revision to xsrcmd5 by default to avoid later breakage on indirect links by default. + + NOTE: + Due to the rewrite of the working copy handling osc might fail with the + following error: + Your working copy '.' is in an inconsistent state. + Please run 'osc repairwc .' (Note this might _remove_ + files from the .osc/ dir). Please check the state + of the working copy afterwards (via 'osc status .') + Simply run "osc repairwc" which might fetch files from the api + or delete some files from the storedir (.osc/). It won't touch + locally modified files. For more information see section + "WORKING COPY INCONSISTENT" in the README. + +# +# Feature which requires OBS 2.1 +# + - support reliable diff for an accepted request + +0.129 + - "dists" command to show the configured default base repos from the server. + - "review list" command to list open review requests + - "review add" command to add another reviewer for a request (either user or group) + - add "buildinfo --prefer-pkgs <dir>" option + - add "prjresults --hide-disabled" option to hide packages which are disabled/excluded + in all repos and repos which have only disabled/excluded packages + - harmonize "api"'s options with curl's options + - use builtin signature check by default (instead of verifying the signature with "rpm -K...") + - add "status --show-excluded" to show all files (except the store dir) + - new "osc reqmaintainership" command which is a shortcut for + "osc creq -a add_role USER maintainer PROJECT PACKAGE" +# +# Feature which requires OBS 2.1 +# + - add "osc aggregate --nosources" option + - add "request clone" command to clone all packages from a given request + - fixed references into en.opensuse.org to honor the new Wiki structure + - add cross build targets mips and mipsel for QEMU Usermode. needs also build update. + +0.128 + - better default commands selection for editor/pager + - support "osc rq reopen" to set a request in new state again + - "osc repos" and "wipebinaries" is checking for local project now + - "osc getbinaries" works in project dir now + - support added for SPARC builds + - support build --oldpackages + - introduced the "trusted projects" + - Fixes for default editor, api check on deleterequest call, tempfile leaks, getbinaries source package handling, results command +# +# Feature which require either OBS 2.1 or 2.0.4 +# + - add osc signkey --extend for extending the expiration date of the gpg public key + +0.127 + - add size limit mode, files can be ignored on checkout or update given a certain size limit. + - --csv/--format options for results command - using format user can explicitly specify what he wants print + - osc branch reads project/package in package directory + - fix creation of package link, when target project has the package via linked project + - add "osc rq approvenew $PROJECT" command to show and accept all request in new state. + This makes sense esp. for projects which work with default reviewers before. + - support external source validator scripts before commiting + - support request creation with multiple actions +# +# Features which require OBS 2.0 +# + - support "osc add http://...", this uses obs source service for downloading a file and verify it via sha256 verifier service + - add support for CBpreinstall/CBinstall + - support branch --force to override target + - support for "unresolvable" state of OBS 2.0 + - support undelete of project or package + - support for package meta data checkout + +0.126 + - added VM autosetup to osc. This requires appropriate OBS version and build script version. + - enhanced QEMU cross build support with 'armv4l' 'armv5el' 'armv6el' 'armv7el' 'armv8el' 'mips' 'mips64' 'ppc' 'ppc64' 'sh4' arch strings now supported on x86 host + - suggest git, svn, ... if indicated, after oscerr.NoWorkingCopy + - "osc cat" & "osc ls" now auto-expands through link. + - fixed "osc add" after "osc delete". + - fix "osc patchinfo" command (crashed before) + - fixed SSL proxy support + - fixed meta attribute create and set calls + - osc remotebuildlog supports a buildlogurl + - Allow --prefer-pkgs to parse repodata + - new "osc build --no-service" option to skip source service update + - fix linktobranch apiurl usage + - "maintained package" search is telling relevant projects now + * requires OBS 1.7.2 or 2.0 + - added "osc chroot" command + - fixed #547005 ("osc co could show download progress") + - added "--interactive" option to "osc request" + - store commit message so it doesn't get lost on failure + - added "--cpio-bulk-download" and "--download-api-only" options to "osc build" + - added "osc localbuildlog" command + - added "--build-uid uid:gid|caller" option to "osc build" to specify abuild id in chroot + - verify files using rpm bindings and keys supplied by buildservice + - added "--exclude-target-project <prj>" option to "osc rq list" + - added "--message" option to "osc branch" + - added "osc config" command to set/get/delete a config option + - added "--binary" and "--baseproject" options to "osc search" + - added "-o/--offline" and "-l/--preload" options to osc build + * osc build -l standard i586 foo.spec (to cache all dependencies) + * osc build -o standard i586 foo.spec (to build without contacting the api) + +0.125 + - add "osc pull" command to fetch and merge changes in the link target + - new proxy support via SSL + - when a broken link is encountered automatically switch to last working + version. use 'osc pull' to repair the broken link. + - osc my request is showing now also requests from other people target to + myself + + # + # Features which require OBS 1.7 + # + - new config option 'submitrequest_on_accept_action' to specify a default action + if a submitrequest has been accepted + - add "osc linktobranch" command to convert a classic link to a branch package + - show scheduler state for each repo with "results" and "prjresults" + +0.124 + - added 'osc bugowner' as a more intelligent version of 'osc maintainer -B' + - added option '-B' to osc maintainer, prints bugowner OR maintainer. + - added 'osc req help' as convenience alias to 'osc help req'. + - 'osc in' to be done. Its usage just prints a suggested zypper command line. + - give better hint how to use osc vc without network connectivity. + - added printing of cache statistices to osc build + - support http proxies when using python 2.6 or newer (#551004) + - partial fix for checkout problems (bnc#551147) + - fixed #477690 ("osc fetching binaries really slow") + - osc jobhistory accepts also "prj [pkg] repo arch" now + - osc buildinfo accepts now also "prj pkg repo arch [spec/dsc]" + - osc buildconfig accepts now also "prj pkg repo arch" + - fixed warning messages regarding SSL certificate on some plattforms (Fedora) + - support submit requests on project level, osc is checking which packages + have changed and submits only the changed after asking back. + - show worker/id on jobhistory and make it faster by adding a default limit of 20 + - add "osc build --root" option to allow to specify build root directory + - add "osc build --release" option to allow to specify a package release number + - added osc mv command which can rename file and leave them under version control + - added new commands "dependson" and "whatdependson" to find out which packages get + triggered before checkin/request accept. + - add new "osc build --linksource" option, speeds up esp. image building a lot + - add "osc triggerreason" command to show detail reason, why a package got triggered for build + - Incompatible changes: + * osc se now prints Project Package, instead of Package Project + for easier copy&paste. + * osc se uses exact search by default. Use osc se -s for + substring search + * osc repourls neither needs nor accepts a path to a package + working dir anymore + * osc repo neither needs nor accepts a path to a package or + project working dir anymore + # + # Features which require OBS 1.7 + # + - search: allow to limit results via existing attibutes + - added "osc meta attribute" for basic attribute creation, deletion, showing and value setting + - implement "osc mbranch" call to create projects with multiple source package (instances) + - new "osc patchinfo" command: basic patchinfo generation and modification support + - add support for _patchinfo package submissions in "osc sr" on project level + - support review handling of requests (new "osc review accept/decline $REQUEST_ID" command + +0.123 + - IMPORTANT: ssl certificate checks are actually performed now to + prevent man-in-the-middle-attacks. python-m2crypto is needed to + make this work. Certificate checks can be turned off per server + via 'sslcertck = 0' in .oscrc. + - 'osc list' option -D now only limits non-'new' requests. In state 'new' all are shown. + - suggest 'osc list' --bugowner option. Not implemented. + - added 'osc rq help' as convenience alias to 'osc help rq'. + - 'osc in' to be done. Its usage just prints a suggested zypper command line. + - Incompatible change: osc se now prints Project Package, instead of Package Project + for easier copy&paste. + - fix checkout of packages, which contain not committed files (but uploaded) + - add signing key management command (osc signkey) + * shows public part of project key + * allows (re)creation of a project key + * allows deletion of a project key + - support 100% offline build when using "osc build --noinit ..." + -> buildinfo gets cached in local directory as .buildinfo.xml + +0.122 + - added missing code for 'osc sr -l [ID]' + - allow osc cat with one parameter, if it is a url. + - make osc getpac really get the package (instead of branch only)! + - expanded several tabs to spaces. + - added default project to new getpac and bco subcommand. .oscrc:getpac_default_project = OpenSUSE:Factory + (not added to branch subcommand, to not interfere with its syntax.) + - add support for generic python-keyring lib, supports KWallet, Gnome keyring, MacOS and Windows. + - make buildhist command usable without checked out package + - rename old "platform/s" names to "repository/ies" (internal cleanup only) + - fixed osc diff -c N, it failed with int and string concatenation + - made osc diff and rdiff more similar: added -p, -c to rdiff, removed -u from rdiff. + made -u default for both, renamed --pretty to --plain as it is the opposite of -u + # + # Features which require OBS 1.7 + # + - option to download server side generated _service:* files on update + - support for running source services locally. Happens by default on source update + and build. + - support modification flages on creation of submit request + (for auto update or clean up packages or to avoid it, when submit request got accepted) + - show request ids from package source logs + - added support to require local packages which don't exist in the obs for a local build. This + fixes #377021, #481193 + +0.121.1 + - fixed creation of new ~/.oscrc files + - fixed "osc my request" command + +0.121 + - fixed osc rq list -U to not look into the local dir + - added osc my ... pkg/prj/req shorthand commands + - add 'osc se' alias for 'osc search -e' + - add -b -m -M to 'osc search' + - hack for _help_preprocess_cmd_option_list to survive setup.py build + - made rresults an alias for results. python decorators are a strange concept... + - asserting that ~/.oscrc remains mode 0600 + - no more plain text passwords in ~/.oscrc, we store now as bz2+base64 + - added verbosity control -v -q. To be used in guess_proj_pack() + - added 'll' and 'ls -l' as shorthand to 'list -v' + - started to change to explicit dual license GPLv2 or GPLv3 to conform to Novell policy. + - added revision parameter to show_upstream_srcmd5(), so that it can be used in do_cat later. + - allowed both integer and srcmd5 revisions in meta_get_filelist() + - added 'lL', 'LL': allowed -e and -v together in do_list(). Was an internal error before. + - added cat -e, to cat a file through a link. + 'cat -e -r 3' expands through the third revision of the _link. + - added subcmd bco as alias for branch -c + - added primitive experimental support for .oscrc:checkout_no_colon = 1 + - suggest using svn when .svn found. + - alias submitpac submitrequest + - osc bco now continues to checkout after branch target exists error. + - added .oscrc:plaintext_passwd=1 for backwards compatibility + - moved core.py:exclude_stuff to .oscrc:exclude_glob and expand it to catch *.orig etc. + - added osc rq list -a; a shorthand for enumerating all states + - osc rq list -D nnn limit to requests nnn days old. + - osc sr --diff option added + - improved help texts with repairlink to point to osc resolved. + - improved passx code when creating oscrc. + - osc metafromspec allows editing before send + - allow handling of other roles than "maintainer" with maintainer command + (-r role) + - fix and improve request list and show output + - new osc rremove command for remote source files removal + - first part of support to handle _service\* files correctly + - osc commit asks if some file has a '?' status (can be skipped by --force option) + - fixed request list for multiple states + - new option --overlay + - new option --rsync-src / --rsync-dest + +0.120: + - support "setlinkrev" for whole projects + - add "setlinkrev --unset" for removing revision references + - add "osc request list -t <type>" to list only submit, delete or develchange requests + - add shell completion scripts + - fix support of listing requests with multiple actions + - "osc maintainer" is following to the development project / package now + - "osc maintainer" list maintainer and bugowner roles now + +0.119: +- Support new request types + - "submitreq" command has a new syntax (incompatible !) + - new "deleterequest" command + - new "changedevelrequest" command + - new "request" command for showing/modifing requests + - Multiple actions in one request is not yet supported by osc + - The new commands require an OBS 1.7 server, submitreq is still working with + older servers. +- support of added .changes in commit message template +- make submit request listing fast by server side filtering +- allow pulling of conflicting changes via "osc repairlink" +- delete commands consolidated: + * deleteprj and deletepac are obsolete. + * delete and rdelete take over +- enable package tracking by default +- bugfix: templates in edit commit message causes an empty commit logs +- osc submitrequest consumes DESTPRJ [DESTPKG] arguments only +- osc build now also tested on native arm targets where uname -m reports a string like armv{4l,5el,6l,7el,7l} +- osc rlog now works with srcmd5 also +- plugins now should be placed in /usr/lib/osc-plugins to match FHS (the /var path is still supported though) +- osc now includes automatically generated man page +- osc can now store credentials in Gnome keyring if it is available +- new support for osc linkpac to specify cicount attribute +- new log/rlog output formats (CSV and XML) +- new jobhistory/buildhistory/search output format (CSV) +- new option to fetch buildlogs starting at given offset +- new option for copypac + * -r to specify source revision + * -m to specify a comment (and send default comment if not specified) +- new option to results(r), and rresults: + * -r|--repo to specify a repository(repositories) + * -a|--arch to specify a architexure(s) + * --xml for xml output (makes results_meta obsolete) +- request list -M shows open SRs created by the user. +- Fixed build support for images, only refered packages from buildinfo get used. (#485047) +- "req" command got renamed to "api" to avoid clash with "request" command +- osc build has a smarter default platform selection - it checks the + availibility config value, 'standard' and 'opensuse_Factory' in platforms list and in case + of fail it uses the last entry from that list +- new osc linkpac -f to allow to override existing _link files +- rename "rebuildpac" to "rebuild", but keep "rebuildpac" as alias + +0.117: +- support checkout of single package via "osc co PACKAGE" when local dir is project +- allow to specify target project and package on osc branch (requires server version 1.6) +- add option to automatic checkout a branched package +- support "osc getbinaries" in checkout packages +- new vc command for editing the changes files (requires build.rpm 2009.04.17 or newest) +- new repairlink command for repairing a broken source link (requires server version 1.6) +- '-b|--brief' option for osc submitreq show subcommand +- use "latest" commited revision on checkout, not "upload" (#441783) +- '-e|--just-open' option for vc command and used /usr/lib/build/vc as an executable + +0.116: +- support listings of older revisions with "osc ls -R" +- add --current parameter for linkpac to use current revision of source package fixed. +- add osc setlinkrev to add or update revision number in links easily +- fix streaming of binary files via "cat" (#493325) + +0.115: +- optional transfer of devel project during copy_pac and link_pac is fixing + opertation with remote build service instance +- "osc ci" fails uploading large files to Provo BuildService +- fixed support for accessing download repositories (worked only for download.o.o so far) + +0.114: +- the .oscrc config handling has been cleaned up: + * use "apiurl" for everything now (== <protocol>://<host>) + * added aliases support for [apiurl] sections in the ~/.oscrc. + Example: + [http(s)://foobar] + ... + aliases = foo, bar + => "osc -A foo <cmd>" will do the same as "osc -A http(s)://foobar ls" + * "scheme" and "apisrv" are deprecated and will produce a warning + * when writing a new ~/.oscrc, store the apiurl in the conffile (bnc#478054) + * fixed bug that made osc ask for credentials when -A was used (bnc#478054) + * fixed crash upon password entry (first startup) (bnc#478052) +- osc build: + * make product builds work + * speed up by using a cookie when fetching the binaries (bnc#477690) + * support for VM (kvm or xen) builds + * obsolete the need to configure download server, get it from the build + service instance instead. + * be a bit more verbose if the linked package isn't expanded (bnc#470948) +- osc branch: + * --develproject option fixed (the API calls it 'ignoredevel' instead of 'nodevelproject') + * --revision option added +- osc jobhistory: new command to see build job history of a project or a package +- osc results/rresults: option -l, --last-build added (show last build results) +- osc linkpac: fix failure when -A<url> is used (bnc#479156) +- osc commit: don't scare users if they want to commit a nonexistent file (bnc#469167) +- osc diff: bugfix to make --pretty option work +- 11.1 added to the osc project template + + +0.113: +- osc diff -rX:Y: the default is to return an unified diff (to get a pretty + diff use the --pretty option) +- osc rdiff: the default is to return a pretty diff (to get an unified diff use the --unified option) +- osc sr show --diff: the default is to return a pretty diff (to get an unified diff use the --unified option) +- osc getbinaries: optionally also download source rpms +- osc importsrcpkg: set the url in the package meta (bnc#458083) +- osc wipebinaries: added --expansion option +- added support for format strings like "%(project)s" and "%(package)s" which + can be used in the build-root config option. For example one could use a new + chroot for each package. +- osc updatepacmetafromspec: fix failure if %description is starting with newline (bnc#462869) +- catch OSError exceptions which might be raised by the subprocess module +- don't use a hardcoded path for the rpm binary otherwise it fails on + distributions like debian +- osc meta: be more verbose in case of failure (bnc#459292) +- osc mkpac: add info how to enable the package tracking feature (bnc#459288) + +0.112: +important bugfix: +- osc deletepac: prevent recursive deletion of a whole project [bnc#458535] +- osc build: support more options: --icecream, --ccache, --with, --without +- osc build: --keep-pkgs also saves the src.rpm now +- osc build: small fix in debuginfo handling +- osc build: new armv7el arch for all binaries for up to ARMv7 EABI with VFP + + +0.111: +- fix accidental truncation of .oscrc to 0 bytes +- fix osc's ignorance of the revision option (-r) for expanded links +- osc build: handle kiwi builds (local image build) +- osc build: cross build support +- osc build: support for ARMv5 EABI little endian arch added +- osc build: fixed detection of the build type (rpm or deb), after change in the buildinfo +- osc build: build debuginfo packages if enabled in the project/package meta (this partly fixes #421390) + + +0.110: + +- osc build: no working copy needed anymore when building a local package [bnc#431434] +- osc checkout: when checking out a project, and a linkerror occurs for one of + the packages, do a checkout in unexpanded form and continue checking out the + rest of the project [bnc#428303] +- osc deletepac, osc branch: allow slash notation for the project/package arguments +- fix deprecation warnings on Factory (which uses Python 2.6) +- fix to avoid (internal) stale Package objects [bnc#436932] + + +0.109: + +- osc getbinaries: new command to download binaries directly from the api server +- osc rlog: new command to show commit logs of remote packages +- osc build: --debug option to the build script which will take care of creating debuginfo packages +- add link to plugin API to osc help output +- avoid a hard dependency on the rpm-python bindings. +- fixed depracation warnings with Python 2.6 [bnc#426612] +- streaming of unfinished logfiles fixed +- fixed regression of .oscrc template [bnc#427118] +Changes were from Marcus_H, poeml, dmueller, tpatzig. + + +0.108: + +- osc submitreq: has two aliases now: "osc sr" and "osc submitrequest" +- osc sr create: prompt to revoke existing requests +- osc sr revoke: new command for to get rid of requests to projects one can't write to +- osc sr list: allow showing requests in a state other than "new" +- osc sr show: show the current state's comment +- osc sr log: new command to show the history of a given id +- osc sr: enable requests for submitting new packages +- osc build: implement --no-checks +- osc build: be less strict on the arguments, and guess what's needed. For instance: + * osc build PLATFORM ARCH BUILD_DESCR + * osc build PLATFORM (ARCH = hostarch, BUILD_DESCR guessed) + * osc build ARCH (PLATFORM = build_platform (config option), BUILD_DESCR guessed) + * osc build BUILD_DESCR (PLATFORM = build_platform (config option), ARCH = hostarch) + * osc build (PLATFORM = build_platform (config option), ARCH = hostarch, BUILD_DESCR guessed) +- osc build: download after the target architecture check +- osc addremove: bugfixes, --recursive option +- osc init: added support to initialize a project dir +- osc metafromspec: new alias for 'updatepacmetafromspec' which is hard to remember +- osc updatepacmetafromspec: also update URL +- osc buildlog: do not download entire log to memory +- new http_headers option to add arbitrary headers to HTTP requests +- bugfix to make osc work on Gentoo +- enhance/update the package and project template +- .netrc heritage from previous commandline client has been removed +- osc asks for password now, when used with -A + + +0.107: + +- osc build: the --extra-pkgs option is now a configurable setting in .oscrc. + Default is "extra-pkgs = vim gdb strace" +- .oscrc: make tilde expansion work on the packagecachedir setting +- osc update / checkout: don't check out a working copy, or update an existing + one, when a source link cannot be applied [bnc#409373] + + +0.106: + +- osc rdiff / osc submitreq show: diff the _expanded_ sources [bnc#408267] +- osc submitreq list: show author's name +- osc submitreq: shortcut alias 'sr' added + + +0.105: + +- osc submitreq list: + - can now be called without parameters, applying to the working copy then. + - calling it in a project directory is also possible now. + - output was improved. Newest requests are listed first. +- osc submitreq delete: a new action which has been added +- osc submitreq list/create: use api URL from the working copy +- osc meta: editing returns the API error description instead of a plain HTTP + error if available +- osc copypac: use the correct userid when copying to another api host +- osc importsrcpkg: disable signature check when getting data from a rpm file +- osc linkpac: --revision option added. +- osc search: added option -i|--involved, to show in which projects/packages + a developer is involved +- osc build: double check the buildinfo for local builds. Refuse to build for + architectures that are not supported by the host +- osc buildhist: change the output into a format which better matches actual + RPM filenames. +- osc commit: give commit message tempfiles a ".diff" suffix, so syntax + highlighting automatically works in capable editors +- other bug fixes: + - don't expand/unexpand if the working copy has local modifications - this is + an ugly workaround for #399247 but this way the working copy isn't screwed up + - work around a bug which causes packages to be cached locally under the + "None" architecture (and therefore causing issues when building for more + than one architecture via osc build). + - don't create _linkerror files in working copies + - better error handling (mostly printing more details) in a number of cases + - show error messages from the API also for type 500 errors + + +0.104: +- osc update: after update, reset the revision when updating multiple package. + Fixes "404: Not Found" type errors when updating an entire project. [bnc#399177] +- more/better error messages in some error scenarios +- osc wipebinaries: add missing check for commandline arguments, which could + cause a PACKAGE argument to be ignored +- fixed make_diff in order to avoid errors when committing a new package + (created with mkpac) + + +0.103: + +- osc submitreq create: simplify by make osc guess needed parameters, if + there is a working copy and it is a source link. +- osc submitreq create: don't stop on packages that have a devel project + defined, if the submit actually comes from that project. +- osc checkout: checkout of source links is now done in expanded form per + default. The new option --unexpand-link can be used to get the raw link file. +- show the API's error message for HTTP 403 (Forbidden) replies. + + +0.102: + +- osc branch: Show the actually created branch project name, not + a guessed one. Add --nodevelproject. +- osc submitreq: look up the develproject of the target, and if + there is one, don't create the request, unless forced with + --nodevelproject. +- make the global -d option work better under certain circumstances + +0.101: + +- add osc branch command, using the branch API call to branch a package to + home:poeml:branches:PRJ/PKG +- osc commit now opens $EDITOR for commit message +- improved error handling, when API returns HTTP status code 400 (bad request) +- osc status: implement -q/--quiet switch +- osc info: slightly more verbose +- osc deletepac: allow deletion of multiple packages at once +- make "osc meta prjconf <project> -e" work again (probably caused by r3702) + + +0.100: + +- improved error handling (babysitter.py wrapper, oscerr.py exception classes) + Tracebacks are mostly suppressed now. To enable them, use + -t, --traceback print call trace in case of errors + or set traceback=1 in .oscrc. +- other new global options for debugging: + --debugger jump into the debugger before executing anything + --post-mortem jump into the debugger in case of errors + -d, --debug print info useful for debugging +- make way for more seamless osc version updates (the .osc directory in working copies + will have its own versioning in the future) +- osc rprjresults and osc rresults: new commands to show remote build results +- osc build: added --baselibs and --jobs options +- osc copypac: added --keep-maintainers switch +- osc maintainer: new -D/--devel-project switch +- BUILD_DIST environment variable will be ignored (bnc#359846) + The following environment variables can still be used: + * OSC_SU_WRAPPER overrides the setting of su-wrapper. + * OSC_BUILD_ROOT overrides the setting of build-root. + * OSC_PACKAGECACHEDIR overrides the setting of packagecachedir. + + +0.99+patches (interim releases, including Wed Apr 2 16:36:40 CEST 2008) + +- new command submitreq, to handle "submit requests" (next generation build + service feature). See http://en.opensuse.org/openSUSE:Build_Service_Collaboration +- new link handling: + add support for handling linked packages in expanded form. They + can be checked out, updated (expanding or unexpanding them), + and built locally. + Newly introduced options are: + * osc checkout: --expand-link + * osc update: --expand-link and --unexpand-link +- new feature: package tracking. It's not enabled by default and + needs to be switched on with do_package_tracking=1 in .oscrc. + before using. See + http://lists.opensuse.org/opensuse-buildservice/2008-03/msg00114.html +- prjresults: add --csv option +- req: add option -a / --add-header to inject arbitrary request headers +- addremove (and others): ignore _all_ dot files (the buildservice doesn't + handle them) +- copypac: do a (quicker) server-side copy by default, when source and target + are on the same buildservice instance. +- build: + - add --debuginfo + - add --no-verify + - add --local-package to build a package which doesn't exist on the server + - add --alternative-project to specify a project, if the current one doesn't + exist on the server + - use api url from .osc/_apiurl [#355144] +- new command remotebuildlog +- diff: fix #347377 (diffing too many files) +- checkout: check for project existance beforehand +- rdiff: new command for server-side diffs between arbitrary packages +- cat: new command to print a file on the standard output +- diff: reworked functionality to show newly added files, and behaving more + like svn when doing diff against a certain revision +- bugfix in {link,aggregate,copy}_pac (<person> elements) +- checkout an empty project instead of doing nothing +- fix prjresults for newly added packages, where build status is missing + + +0.99: + +- aggregatepac: new command, similar to linkpac. Patch from Pavol Rusnak. +- wipebinaries: added --build-failed and --broken [#335498] +- deleteprj: enabled this command, as the backend now supports it +- maintainer: + - added --verbose option + - added functionality to add/remove users from a project/package +- print the list of URL to try, when in HTTP debug mode +- build: allow to use lbuild, a compatible replacement for build +- do not create dirs for non-existing packages during checkout [#259711] + + +0.98: + +- new maintainer command, to list the maintainers of a project or package +- ls: add -b option to list binaries +- make osc library simpler to use from external scripts +- new importfromsrcpkg command, to import a package src.rpm from file or URL +- new req command, to issue arbitrary requests to the API +- initial support for commit messages (ci -m/-F) +- implementing a log command to review the commit log +- renamed previous "log" command to "buildlog" (short: bl) +- new meta command, replacing editmeta, editprj, createprj, + editpac, createpac, edituser, pattern +- added search support +- show helpful xml error messages if broken metadata is uploaded + + +0.97: +- added initial revision handling: + - extended "osc co prj pac" to checkout a specific revision of pac + - extended "osc up" to update to a specific revision + - extended "osc diff" to diff the working copy against a + specific revision on the server. NOTE: comparing two + server-side revisions (osc diff -r 11:12) is currently + not supported! +- load subcommands from /var/lib/osc-plugins/ or ~/.osc-plugins/ +- updatepacmetafromspec scans for spec files automatically. Added --specfile option to updatepacmetafromspec. +- wipebinaries: allow to wipe all binaries of packages for which the build is disabled +- addremove: ignore foo.rXX, foo.mine for files which are in 'C' state +- ls: add verbose option to print extra information for packages +- for all server-side commands, allow arguments "foo/bar" instead of "foo bar" +- new wipebinaries and abortbuild commands, by courtesy of Marcus Huewe +- improved metadata error condition handling (thanks to Marcus Huewe) +- build: add --userootforbuild option +- build: implement -x/--extra-pkgs option (passed to backend and included in buildinfo result) +- make filling out of username in templates work again +- don't try to delete projects, as long it is not implemented in the backend +- use new API route for downloading binaries also in configured URLs +- make deletepac work again + + +0.96: +- following suggestions by Christian Boltz and Michal Marek, osc now memorizes + where a working copy was checked out from, saving the api server url to + .osc/_apiurl. +- implement 'info' subcommand +- use new api routes in all places +- buildhistory works again +- copypac: implement package copy from one buildservice instance to another + (--to-apiurl option) +- the results subcommand now handles multiple <working copy> arguments +- build: implement --prefer-pkgs and --keep-pkgs option +- applied patch from Michael Marek, fixing all places where error + messages were printed to stdout instead of stderr. [#239404] +- osc is now easier to work with when using alternative API servers. The + configured server can be overriden with -A <url> on the commandline. + "apisrv" in the config takes a URL now, so the variable "scheme" which was + needed in addition before becomes obsolete. For backward compatibility, a + hostname (and scheme variable) are accepted like before. Likewise, the auth + sections in the config take a URL now, or a hostname:port to keep old config + working. HTTP or HTTPS scheme is determined from the URL. Credentials must be + configured in .oscrc. +- build: use actual api server in urllist for downloading, instead of hardcoded + api.opensuse.org [#265211]. + + +0.95: +- rewrite the internal HTTP handling + - save and reuse HTTP server cookies, which can speed up HTTP requests up about + 5 times in an iChain setup + - adding http_GET/POST/PUT/DELETE() functions, which dispatch to + http_request(), and use them everywhere + - removing othermethods.py + - keeping urlopen(), in case it is used from externally, but have it print out + a "depracated" message + - finally, global option -H enables HTTP traffic debugging +- implement "rebuild all failed packages", via --failed option in rebuildpac + subcommand +- status -v shows all files, including unmodified ones +- suppress the legend in prjresults by default (show with -l) +- --version shows the program version number +- fix the commit subcommand's argument handling. The following works correctly + now: osc ci ../test/onlyinwc `pwd` fstab ../test/f2 +- fix the download progress meter to work with small terminals [#266989] +- update: when updating multiple packages, print each package name +- make 'results' subcommand many times faster, by making only a single request +- prjresults: sort package names +- build: run with --norootforbuild, thereby defaulting to build as abuild user +- build: fix (harmless) errors showing up in the build log during buildsystem + setup, by using the new <bdep> preinstall and runscripts attributes +- update: when updating, don't delete files with local modifications +- let the diff subcommand return 1 if differences were found +- fix important bug, which could lead to overwriting local modifications when + upstream changes are merged in +- if a merge fails, the store copy must be updated neverthelesss +- fix testsuite and add testcase for successful merging +- sort output of 'status' (unknown files first, filenames alphabetically) +- core: added class "metadata" (merge from Susannes /branches/froh/reponator/) +- added command alias 'stat' for 'status', like in svn +- improved documentation/examples (Lars + Susanne) +- print usage info if 'co' is called without arguments + +0.9: +- "iChain-ready" (works with API server now using iChain authentication) +- add runtime check for build.rpm version, so the rpm package dependency is + no longer required +- add 'edituser' command for editing the metadata of a user account. It tries + to create a user if it doesn't exist yet. A new command 'usermeta' replaces + 'id' respectively 'userid'. +- rewrite configuration handling. Now the API server can be set in .oscrc +- ignore '.gitignore', '.pc', '*~' (now using filename matching [#208969] +- fix 'status' to work with project directories as arguments +- fix 'status <filename>' +- 'rebuildpac' now accepts additional repo and arch argument. Note: + the syntax has changed. +- add 'prjresults' command to display aggregated build status over + an entire project +- add 'deleteprj' command (the API server doesn't seem to support + it yet, though) +- change 'buildhistory' to display human-readable text +- add 'copypac' subcommand, to copy a complete package to a new package, possibly cross-project +- don't die if user tries to 'add' a file which is already versioned +- don't die if 'addremove' encounters directories +- urlopen(): for server return code 500, print out the reply body + +0.8: +- build: use configuration from *local* specfile (e.g. BuildRequires) +- build: let envvars OSC_SU_WRAPPER and OSC_BUILD_ROOT override config +- build: allow 'dynamical' build-root setting by using %(repo)s and %(arch)s +- add 'createpac/editpac' and 'createprj/editprj' subcommands which + are similar to 'editmeta' but should be more logical to find +- added 'deletepac' subcommand +- added 'buildhistory' subcommand (formerly 'history'). This only + gives out raw xml at this time +- added 'linkpac' subcommand +- added ".git" to the excluded files +- adapt to API changes +- fixed issue with uploading files when an intercepting web proxy was + in between osc and the api server +- fixed creation of new packages/projects + +0.7: +- initial support for local builds (subcommand 'build') +- better error handling +- new subcommands buildconfig, buildinfo, repos +- remove requirement on pyxml package +- editmeta: add examples for package/project templates +- add support for streaming the build log (thanks to Christoph Thiel) +- add 'rebuildpac' subcommand +- add 'repourls' subcommand +- don't diff binary files +- don't try to merge binary files +- add a preliminary 'updatepacmetafromspec' subcommand, which takes package + metadata from a specfile +- fix profiling wrapper +- set User-agent +- bugfixes: + - fix handling of filenames with '+' signs + - make 'resolved' more robust + - fix merge on 'update' if called from another directory + - display reason for build status is 'broken' + - handle HTTP error codes != 404 when reading metadata in edit_meta() + - handle 'project not found' error in show_project_meta() + + +0.6: +- diff bugfix: sometimes displayed diff against obsolete files +- update bugfixes: fix update of working copy when adding a file from upstream + which is missing locally; fix update in directory with unmodified files: + don't try to merge if upstream file wasn't changed at all +- add: make it faster + + +0.5: +- help :-) +- add 'editmeta' subcommand: Edit project/package meta information, creating + new project or package if it doesn't exist. The user interface is $EDITOR +- fix status letter for files merged on update (in analogy to svn , it is + either G or U) +- if an old _files listing without any metadata is found, don't bother the user + with it +- make all subcommands properly importable functions +- bug in 'resolved' command fixed, which wouldn't clear the conflict state of a file + + +0.4: +- allow 'up' inside a project directory (will automatically pull in all new + packages). (For past checkouts, you may need to put the project name into + $prjdir/.osc/_project yourself). +- checkout: preserve mtimes +- add diff3 merge support. Locally modified files are merged with upstream changes + if possible, and go into Conflict state if that fails. +- add 'resolved' command to be used after manual merging. + + +0.3: +- use the new file metadata, which provides checksum, size and mtime +- faster 'status', 'update', 'diff' +- improve argument handling, now e.g. 'osc up *' is possible +- on first usage, ask for username and password and store them in .oscrc + (.netrc can still be used) + diff --git a/PROJ_PACK.txt b/PROJ_PACK.txt new file mode 100644 index 0000000..591676e --- /dev/null +++ b/PROJ_PACK.txt @@ -0,0 +1,92 @@ + jw, Tue Oct 20 22:09:16 CEST 2009 + +This is a feature suggestion for easier osc commandline handling. +Many commands require specifying Project and/or Package names. + +The current situation is not satisfying for the following reasons: + - inconsistent defaults. Some osc subcommands can take project + and/or package names from the current directory, if run inside a checkout + tree. If both project and package can use this default or only one, and if + one, which, depends on the command. Users have a hard time memorizing + which is which. + Examples as of osc version 0.123: + osc maintainer PRJ [PKG] + - does not look in the current directory. + - need at least PRJ. + osc list [PRJ [PKG]] + - Never looks at the current directory. + - lists all projects, if run without parameters. + osc checkout [PRJ] PKG + osc checkout PRJ + - takes project from current directory, if inside a checkout tree + - else operates on an entire project. + osc checkin [ARG] + - defaults to current project and package, + - if arg is a subdirectory, project is taken from current directory + - if arg is a file, both project and package are taken from current + directory. + osc results [PRJ PKG] + - takes either both or none from current directory. + - many commands do not look into the current directory, + they are cumbersome to use. + - sometimes PRJ/PKG can be used instead of PRJ PKG + + +Suggested solution +------------------ + +Instead of tuning (maybe optional) positional parameters. +We suggest to deprecate this syntax over time and instead favour an alternate +syntax: + osc CMD ... [--prj PRJ] [--pkg PKG] ... + osc CMD ... [--proj PRJ] [--pack PKG] ... + osc CMD ... [--project PRJ] [--package PKG] ... + +These six options are new to osc, currently no existing command uses +them. Thus the new syntax is conflict free wit the old syntax, both can be +used in parallel. + +--prj, --proj, --project are synonyms. +--pkg, --pack, --package are synonyms. + +osc shall support aliases, to save typing. Some implicit aliases exist, +with well defined magic effects. Aliases substitution is literal. +They can replace options including their parameters, or just the option, or +just the parameters. + - (a dash) expands to --prj openSUSE:Factory + (or --prj followed by any other project as defined in + ~/.oscrc:default_project ) + --prj - is synonymous to just -, for consistency. + + . (a dot) evaluates the current working directory, searching for + .osc/_apiurl, .osc/_project, and .osc/_package + Implicit --apiurl, --prj, or --pkg options are constructed as far + as available from the current directory and as far as not already + present in the command line. + If a dot is used as parameter to an option, it has a more + deterministic meaning. + --apiurl . Substitute only the current apiurl, + --prj . Substitute the current project name, and provides + a default for --apiurl unless given. + --pkg . Substitures current package name likewise. + + ./. expands to --prj . --pkg . + ./PKG expands to --prj . --pkg PKG + +Unless otherwise noted in the online help, magic aliases are only attempted onceper commandline, and will only apply to their respective options. +E.g. osc ci -m - will use a simple '-' as check in messages, and the absence of any project or package will default to the current project or package, just as +osc ci . -m - would do. + +Additionally, user defined aliases can be added to ~/.oscrc +If an alias expansion has effect on the command line, the expanded line is +printed as debug output. + +online help of osc commands shall refer to the above syntax like this: + + osc CMD ... PROJ/PACK + +An additional help entry + + osc help 'PROJ/PACK' + +shall explain the relevant details as presented herein. @@ -0,0 +1,271 @@ +osc -- opensuse-commander with svn like handling + + +Patches can be submitted via + * mail to opensuse-buildservice@opensuse.org + * Bugzilla: https://bugzilla.novell.com/enter_bug.cgi?product=openSUSE.org&component=BuildService + * or the official Git repository on Github: + https://github.com/openSUSE/osc + + +INSTALLATION: + +RPM packages are here (rpm-md repository): +http://download.opensuse.org/repositories/openSUSE:/Tools/ + +To install from git, do + + python setup.py build + python setup.py install + # create a symlink 'osc' in your path pointing to osc.py. + ln -s osc-wrapper.py /usr/bin/osc + +Alternatively, you can directly use osc-wrapper.py from the source dir +(which is easier if you develop on osc). + + +The program needs the cElementTree python module installed. On SUSE, the +respective package is called python-elementtree (before 10.2: python-xml). +For local building, you will need python-urlgrabber in addition. Those are +standard package on SUSE Linux since a while. If your version is too old, you +can find python-elementtree and python-urlgrabber here: +http://download.opensuse.org/repositories/devel:/languages:/python/ + + + +CONFIGURATION: + +When you use it for the first time, it will ask you for your username and +password, and store it in ~/.oscrc. + + +CONFIGURATION MIGRATION (only affects versions >= 0.114): + +Version 0.114 got some cleanups for the configfile handling and therefore some +options are now deprecated, namely: +* apisrv +* scheme + +One new option was added: +* apiurl = <protocol>://<somehost> # use this as the default apiurl. If this +option isn't specified the default (https://api.opensuse.org) is used. + +So far osc still has some backward compatibility for these options but it might +get removed in the future that's why it issues a deprecation warning in case +one of those options is still in use. + +The new configuration scheme looks like the following: + # entry for an apiurl + [<protocol>://<apiurl>] + user = <username> + password = <password> + ... + +'''Before starting the migration please save your ~/.oscrc file!''' + +If the migration doesn't work for whatever reason feel free to send me an email +or ask on the opensuse-buildservice mailinglist or in the #opensuse-buildservice +irc channel. + +=== Migration case I (apisrv only) === +The apisrv option is used to specify the default apihost. If apisrv isn't +specified at all the default ("api.opensuse.org") is used. +The current [general] section looks like this: + [general] + ... + apisrv = <somehost> + # or + apisrv = <protocol>://<somehost> + +apisrv got superseded by the new apiurl option which looks like this: + [general] + ... + apiurl = <protocol>://<somehost> + +If apisrv has no "<protocol>" https is used. Make sure all apiurl sections have +the new format which is described above. Afterwards apisrv can be removed. + +=== Migration case II (scheme only) === +The current [general] section looks like this: + [general] + ... + scheme = <protocol> + +This means every apiurl section which don't have the new format which is +described above for instance + [<somehost>] + user = <username> + password = <password> + ... + +has to be converted to + [<protocol>://<somehost>] + user = <username> + password = <password> + ... + +Afterwards the scheme option can be removed from the [general] section (it +might be the case that some sections already have the correct format). + +=== Migration case III (apisrv and scheme) === +The current [general] section looks like this: + [general] + ... + apisrv = <somehost> + scheme = <protocol> + +Both options can be removed if all apiurl sections have the new format which is +described above. So basically just adjust all apiurl sections (it might be the +case that some sections already have the correct format). + + +KEYRING USAGE + +Osc now can store passwords in keyrings instead of ~/.oscrc. To use it, +you need python-keyring and either python-keyring-kde or -gnome. + +If you want to switch to using a keyring you need to delete apiurl section +from ~/.oscrc and you will be asked for credentials again, which will be then +stored in the keyring application. + + +WORKING COPY INCONSISTENT (only affects version >= 0.130) + +osc's working copy handling was rewritten in 0.130. Thus some +consistency checks were added. As a result osc might complain +that some old working copies are in an inconsistent state: + Your working copy '.' is in an inconsistent state. + Please run 'osc repairwc .' (Note this might _remove_ + files from the .osc/ dir). Please check the state + of the working copy afterwards (via 'osc status .') +To fix this simply run "osc repairwc ." as suggested in the +error message. Note that "osc repairwc ." might need to contact +the api in order to fetch some missing files. Also it might remove +some files from the storedir (.osc/) but it won't touch any locally +modified files. +If it DOES NOT fix the problem please create a bug report and attach +your working copy to the bug (if possible). + + +USAGE EXAMPLES: +(online at http://en.opensuse.org/openSUSE:OSC ) + +To list existing content on the server + osc ls # list projects + osc ls Apache # list packages in a project + osc ls Apache subversion # list files of package of a project + +Check out content + osc co Apache # entire project + osc co Apache subversion # a package + osc co Apache subversion foo # single file + +Update a working copy + osc up + osc up [pac_dir] # update a single package by its path + osc up * # from within a project dir, update all packages + osc up # from within a project dir, update all packages + # AND check out all newly added packages + +If an update can't be merged automatically, a file is in 'C' (conflict) +state, and conflicts are marked with special <<<<<<< and >>>>>>> lines. +After manually resolving the problem, use + osc resolved foo + +Upload change content + osc ci # current dir + osc ci <dir> + osc ci file1 file2 ... + +Show the status (which files have been changed locally) + osc st + osc st <directory> + osc st file1 file2 ... + +Mark files to be added or removed on the next 'checkin' + osc add file1 file2 ... + osc rm file1 file2 ... + +Adds all new files in local copy and removes all disappeared files. + osc addremove + +Generates a diff, to view the changes + osc diff # current dir + osc diff file1 file2 ... + +Shows the build results of the package + osc results + osc results [repository] + +Shows the log file of a package (you need to be inside a package directory) + osc log <repository> <arch> + +Shows the URLs of .repo files which are packages sources for Yum/YaST/smart + osc repourls [dir] + +Triggers a package rebuild for all repositories/architectures of a package + osc rebuildpac [dir] + +Shows available repository/build targets + osc repository + +Shows the configured repository/build targets of a project + osc repository <project> + +Shows meta information + osc meta Apache + osc meta Apache subversion + osc id username + +Edit meta information +(Creates new package/project if it doesn't exist) + osc editmeta Apache + osc editmeta Apache subversion + +Update package meta data with metadata taken from spec file + osc updatepacmetafromspec <dir> + + +There are other commands, which you may not need (they may be useful in scripts): + osc repos + osc buildconfig + osc buildinfo + + +Locally build a package (see 'osc help build' for more info): + osc build <repo> <arch> specfile [--clean|--noinit] + + +Update a package to a different sources (directory foo_package_source): + cp -a foo_package_source foo; cd foo; osc init <prj> <pac>; osc addremove; osc ci; cd $OLDPWD; rm -r foo + + + +HINT FOR W3M USERS + +Putting the following in the file ~/.w3m/passwd will make +w3m know the credentials for the buildservice servers: + +""" +host api.opensuse.org + port 80 + realm Authentication required + login foo + password bar + +host build.opensuse.org + port 80 + realm openSUSE Build Service + login foo + password bar +""" + +chmod 0600 ~/.w3m/passwd + + +NOTES about the testsuite + +A new test suite has been created and should run via doing +# cd tests +# python suite.py + @@ -0,0 +1,87 @@ +FIXME: + - more command inconsistencies: + osc request show + -B, --bugowner also show requests about packages where I am bugowner + osc my + -b, --bugowner restrict listing to items where the user is bugowner + osc list + -b, --binaries list built binaries instead of sources + osc search + -B PROJECT, --baseproject=PROJECT + --binary search binary packages + -b, --bugowner as -i, but only bugowner + + osc checkout + -c, --current-dir place PACKAGE folder in the current directory instead + of a PROJECT/PACKAGE directory + osc branch + -c, --checkout Checkout branched package afterwards ('osc bco' is a + shorthand for this option) + # that means the branch checkout to cwd is not possible + + + +CRITICAL: + - webpage can create a _link in a fully populated package. + Need to prevent his somehow. + + - canonical option parser. + -A, -e, -u, -E <n>, should be univeral to all subconmmands that work on prj/pkg objects. + With all subcommands that work on prj/pkg, the following should all be synonyms: + -A apiurl prj pkg + -A apiurl --project prj --package=pkg + -A apiurl prj/pkg + -A apiurl prj:pkg + apiurl/source/prj/pkg + The current working directory or its descendants should provide defaults + for apiurl, prj and/or pkg. + See also http://en.opensuse.org/openSUSE:Build_Service_Concept_OscProjPack + +MAJOR: + +NORMAL: + + - split functionality that needs prj/pac as commandline arguments into a seperate tool (oscremote? osc -r?) + (update: we have some commands meanwhile which exist in an alternate form, + prefixed with r, which works remotely. E.g. rbuildlog, rprjresults, rresults) + - status: implement -u option as in svn [3] + - implement (svn-like) switch command + - implement 'mv' command + - commit: check if errors during PUT are handled sensibly, so the change is + not committed to localmeta + - add switch to commit to change repository options, like to e.g. disable publishing? + - implement optional logging to .osc/log, which could be useful for debugging bugs like + the one where api.opensuse.org sends empty replies (a hard-to-catch one) + + +MINOR: + + - osc checkout should display file download progress (bnc#442115) + - adjust zsh completion to work with cmdln.py implementation + - add support for adding tags to packages? + + + + +JW: +FIXME: osc bco ignores --nodevelproject ?? +FIXME: osc co overwrites local changes without warning. +FIXME: when branching, the user should be added to bugowner, for the branch project. +FIXME: 'osc rq' shall default to 'osc rq list -M -B -s all', + where -B shows requests related to packages where I am the bugowner. +FIXME: 'osc log openSUSE:Factory PKG' should also point to the bsdevelproject + +osc addrepo - obsolete zypper ar + => hm, addrepo could be used also to add a repo to a project. These functionalities + should not conflict +osc install - obsolete zypper in + - + +- german umlaut characters äöü do not work in the message for osc submitpac. + 404 not found, and no request sent. +- implement fedora style 'osc mock' - this requires anonymous read-only access to the build server. + this could use http://tmp.vuntz.net/opensuse-packages/browse.py?project=openSUSE:Factory + as a hacky solution, while we are waiting on fate#306192 + => we will not make rpm downloads anonymous possible, this would create too high load on the server. + Please improve build script instead. + diff --git a/dist/complete.csh b/dist/complete.csh new file mode 100644 index 0000000..0230502 --- /dev/null +++ b/dist/complete.csh @@ -0,0 +1,14 @@ +onintr - +if (! $?prompt || ! $?tcsh) goto end +if ($tcsh == 1) goto end +set rev=$tcsh:r +set rel=$rev:e +set pat=$tcsh:e +set rev=$rev:r +if ($rev > 5 && $rel > 1) then + if ( -s /usr/share/osc/complete ) complete osc 'p@*@`\/usr/share/osc/complete`@' + if ( -s /usr/lib64/osc/complete ) complete osc 'p@*@`\/usr/lib64/osc/complete`@' + if ( -s /usr/lib/osc/complete ) complete osc 'p@*@`\/usr/lib/osc/complete`@' +endif +end: + onintr diff --git a/dist/complete.sh b/dist/complete.sh new file mode 100644 index 0000000..6148b40 --- /dev/null +++ b/dist/complete.sh @@ -0,0 +1,6 @@ +test -z "$BASH_VERSION" && return +complete -o default _nullcommand >/dev/null 2>&1 || return +complete -r _nullcommand >/dev/null 2>&1 || return +test -s /usr/share/osc/complete && complete -o default -C /usr/share/osc/complete osc +test -s /usr/lib64/osc/complete && complete -o default -C /usr/lib64/osc/complete osc +test -s /usr/lib/osc/complete && complete -o default -C /usr/lib/osc/complete osc diff --git a/dist/osc.complete b/dist/osc.complete new file mode 100755 index 0000000..903d7c5 --- /dev/null +++ b/dist/osc.complete @@ -0,0 +1,1826 @@ +#!/bin/bash +# +# Helper script for completion, usage with tcsh: +# +# complete osc 'p@*@`\osc.complete`@' +# +# usage with bash +# +# complete -C osc.complete osc +# +# Author: Werner Fink <werner@suse.de> +# + +## For debugging only: +## Choose your terminal not identical with the test terminal +## exec 2>/dev/pts/9 +## set -x + +set -o noclobber +shopt -s extglob +typeset -i last +typeset -i off +typeset -i count +typeset -i offset +typeset -i remove +typeset -i colon +typeset -r OIFS="$IFS" + +if test "/proc/$PPID/exe" -ef /bin/tcsh ; then + export COMP_TYPE=63 + export COMP_KEY=9 + export COMP_LINE="${COMMAND_LINE}" + export COMP_POINT="${#COMMAND_LINE}" + let colon=0 +else + COMMAND_LINE="${COMP_LINE:0:$COMP_POINT}" + let colon=0 + case "$COMP_WORDBREAKS" in + *:*) let colon=1 + esac + [[ $COMMAND_LINE =~ \\: ]] && COMMAND_LINE="${COMMAND_LINE//\\:/:}" +fi +IFS="${IFS}=" +cmdline=($COMMAND_LINE) +IFS="$OIFS" +case "${cmdline[0]}" in +iosc|isc|osc) ;; +*) exit 1 +esac + +let last=${#COMMAND_LINE} +let last-- +let count=${#cmdline[@]} +let count-- +test "${COMMAND_LINE:$last}" = " " && let count++ +unset last + +oscopts=(--version --help --debugger --post-mortem --traceback --http-full-debug + --debug --apiurl -A --config -c --no-keyring --no-gnome-keyring --verbose --quiet) +osccmds=(abortbuild add addremove aggregatepac api ar bco bl blt branch branchco + bsdevelproject bse bugowner build buildconfig buildhist buildhistory buildinfo + buildlog buildlogtail cat changedevelreq changedevelrequest checkin checkout + chroot ci co commit config copypac cr createincident createrequest creq del + delete deletereq deleterequest dependson detachbranch develproject di diff + distributions dists dr dropreq droprequest getbinaries getpac help importsrcpkg + info init jobhist jobhistory lbl ldiff less linkdiff linkpac linktobranch list + LL localbuildlog log ls maintained maintainer maintenancerequest man mbranch + meta metafromspec mkpac mr mv my patchinfo pdiff platforms pr prdiff prjresults + projdiff projectdiff pull r rbl rblt rbuildlog rbuildlogtail rdelete rdiff + rebuild rebuildpac releaserequest remotebuildlog remotebuildlogtail remove + repairlink repairwc repos repositories repourls reqbs reqbugownership + reqmaintainership reqms request requestbugownership requestmaintainership + resolved results revert review rm rq rremove se search service setlinkrev + signkey sm sr st status submitpac submitreq submitrequest tr triggerreason + undelete unlock up update updatepacmetafromspec user vc whatdependson who whois + wipebinaries) +oscreq=(list log show accept decline revoke reopen setincident supersede approvenew + checkout clone) +oscrev=(show list add accept decline reopen supersede) +oscmy=(work pkg prj rq sr) + +oscprj="" +oscpkg="" +lnkprj="" +lnkpkg="" +apiurl="" +alias="" +test -s ${PWD}/.osc/_project && read -t 1 oscprj < ${PWD}/.osc/_project +test -s ${PWD}/.osc/_package && read -t 1 oscpkg < ${PWD}/.osc/_package +if test -s ${PWD}/.osc/_files ; then + lnkprj=$(command sed -rn '/<linkinfo/{s@.*[[:blank:]]project="([^"]+)".*@\1@p;}' < ${PWD}/.osc/_files) + lnkpkg=$(command sed -rn '/<linkinfo/{s@.*[[:blank:]]package="([^"]+)".*@\1@p;}' < ${PWD}/.osc/_files) +fi +if test -s ${PWD}/.osc/_apiurl ; then + read apiurl < ${PWD}/.osc/_apiurl + alias=$(sed -rn '\@^\['${apiurl}'@,\@=@{\@^aliases=@{s@[^=]+=([^,]+),.*@\1@p};}' < ~/.oscrc 2> /dev/null) +fi +if test "${cmdline[0]}" = isc ; then + alias=internal +fi + +projects=~/.osc.projects +command=osc + +case "${cmdline[1]}" in +-A|--apiurl) + if test -n "${cmdline[2]}" -a -s ~/.oscrc ; then + hints=($(sed -rn '/^(aliases=|\[http)/{s/,/ /g;s/(aliases=|\[|\])//gp}' < ~/.oscrc 2> /dev/null)) + for h in ${hints[@]} ; do + case "$h" in + http*) + tmp=$(sed -rn '\@^\['${h}'@,\@=@{\@^aliases=@{s@[^=]+=([^,]+),.*@\1@p};}' < ~/.oscrc 2> /dev/null) + if test "${cmdline[2]}" = "$h" ; then + alias=$tmp + break + fi + ;; + *) + if test "${cmdline[2]}" = "$h" ; then + alias=$h + break + fi + esac + done + fi +esac + +if test -n "$alias" ; then + projects="${projects}.${alias}" + command="$command -A $alias" +fi + +if test -s "${projects}" ; then + typeset -i ctime=$(command date -d "$(command stat -c '%z' ${projects})" +'%s') + typeset -i now=$(command date -d now +'%s') + if ((now - ctime > 86400)) ; then + if tmp=$(mktemp ${projects}.XXXXXX) ; then + command ${command} ls / >| $tmp + mv -uf $tmp ${projects} + fi + fi +else + command ${command} ls / >| "${projects}" +fi + +projects () +{ + local -a list + local -a argv + local -i argc=0 + local arg cur + for arg; do + if test $arg == "--" ; then + let argc++ + break + fi + argv[argc++]=$arg + done + shift $argc + cur="$1" + if test -n "${cur}" ; then + list=($(command grep -E "^${cur}" ${projects})) + else + list=($(command cat ${projects})) + fi + if ((colon)) ; then + local colon_word + colon_word=${cur%${cur##*:}} + builtin compgen -W "${list[*]}" -- "${cur}" | sed -r "s@^${colon_word}@@g" + else + builtin compgen -W "${list[*]}" -- "${cur}" + fi +} + +packages () +{ + local -a list + local -a argv + local -i argc=0 + local arg cur + for arg; do + if test $arg == "--" ; then + let argc++ + break + fi + argv[argc++]=$arg + done + shift $argc + cur="$1" + if test -n "${cur}" ; then + list=($(command ${command} ls ${argv[@]}|command grep -E "^${cur}")) + else + list=($(command ${command} ls ${argv[@]})) + fi + builtin compgen -W "${list[*]}" -- "${cur}" +} + +repositories () +{ + local -a list + local -a argv + local -i argc=0 + local arg + for arg; do + if test $arg == "--" ; then + let argc++ + break + fi + argv[argc++]=$arg + done + shift $argc + if test -n "$1" ; then + list=($(command ${command} meta prj ${argv[@]}|\ + command sed -rn '/<repository/{s@^\s*<.*name="([^"]*)".*@\1@p}'|\ + command sort -u|command grep -E "^$1")) + else + list=($(command ${command} meta prj ${argv[@]}|\ + command sed -rn '/<repository/{s@^\s*<.*name="([^"]*)".*@\1@p}'|\ + command sort -u)) + fi + builtin compgen -W "${list[*]}" -- ${1+"$@"} +} + +architectures () +{ + local -a list + local -a argv + local -i argc=0 + local arg + for arg; do + if test $arg == "--" ; then + let argc++ + break + fi + argv[argc++]=$arg + done + shift $argc + if test -n "$1" ; then + list=($(command ${command} meta prj ${argv[@]}|\ + command sed -rn '/<arch>/{s@^\s*<arch>(.*)</arch>@\1@p}'|\ + command sort -u|command grep -E "^$1")) + else + list=($(command ${command} meta prj ${argv[@]}|\ + command sed -rn '/<arch>/{s@^\s*<arch>(.*)</arch>@\1@p}'|\ + command sort -u)) + fi + builtin compgen -W "${list[*]}" -- ${1+"$@"} +} + +targets () +{ + local -a targets=() + local -a argv + local -i argc=0 + local arg + for arg; do + if test $arg == "--" ; then + let argc++ + break + fi + argv[argc++]=$arg + done + shift $argc + let argc=0 + for arg in $(builtin compgen -o filenames -o bashdefault -f -X '.osc' -- ${1+"$@"}); do + test -d $arg && targets[argc]=$arg/ || targets[argc]=$arg + let argc++ + done + builtin compgen -W "${argv[*]}${targets+ ${targets[*]}}" -- ${1+"$@"} +} + +users () +{ + if test -s ${projects} ; then + command sed -rn "/^home:$1/{ s/^home:([^:]*):.*/\1/p}" ${projects}|command sort -u + elif test -s ~/.oscrc; then + command sed -rn '/^(user=)/{s/(user=)//p}' ~/.oscrc|command sort -u + else + command id -un + fi +} + +submit () +{ + local -i pos=$1 + local target + + if ((pos == 1)) ; then + if test -n "${oscprj}" -a -z "${cmdline[2]}" ; then + builtin compgen -W "${oscprj}" -- "${cmdline[2]}" + else + if [[ -n "${oscprj}" && "${oscprj}" =~ "${cmdline[2]}" ]] ; then + builtin compgen -W "${oscprj}" -- "${cmdline[2]}" + else + projects -- "${cmdline[2]}" + fi + fi + elif ((pos == 2)) ; then + if test -n "${oscpkg}" -a -z "${cmdline[3]}" ; then + builtin compgen -W "${oscpkg}" -- "${cmdline[3]}" + else + if [[ -n "${oscpkg}" && "${oscpkg}" =~ "${cmdline[3]}" ]] ; then + builtin compgen -W "${oscpkg}" -- "${cmdline[3]}" + else + packages "${cmdline[2]}" -- "${cmdline[3]}" + fi + fi + elif ((pos == 3)) ; then + if test -n "${lnkprj}" -a -z "${cmdline[4]}" ; then + builtin compgen -W "${lnkprj}" -- "${cmdline[4]}" + else + projects -- "${cmdline[4]}" + fi + elif ((pos == 4)) ; then + target="${lnkpkg}" + target="${target:+$target }$oscpkg" + if test -n "${target}" ; then + builtin compgen -W "${target}" -- "${cmdline[5]}" + else + packages "${cmdline[4]}" -- "${cmdline[5]}" + fi + fi +} + +# +# The main options +# +let remove=0 +while test "${cmdline[1+remove]::1}" = "-" ; do + case "${cmdline[1+remove]}" in + -A|--apiurl) + if ((count-remove == 1)); then + builtin compgen -W "${oscopts[*]}" -- "${cmdline[1+remove]}" + exit + elif ((count-remove == 2)); then + if test -s ~/.oscrc ; then + hints=($(sed -rn '/^(aliases=|\[http)/{s/,/ /g;s/(aliases=|\[|\])//gp}' ~/.oscrc|sort -u)) + builtin compgen -W "${hints[*]}" -- "${cmdline[2+remove]}" + else + builtin compgen -P https:// -A hostname + fi + exit + fi + let remove+=2 + ;; + -c|--config) + if ((count-remove == 1)); then + builtin compgen -W "${oscopts[*]}" -- "${cmdline[1+remove]}" + exit + elif ((count-remove == 2)); then + builtin compgen -o filenames -o bashdefault -f -X '.osc' -- "${cmdline[2+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == 1)); then + builtin compgen -W "${oscopts[*]}" -- "${cmdline[1+remove]}" + exit + fi + let remove++ + ;; + *) break + esac +done +if ((remove)) ; then + cmdline=(${cmdline[0]} ${cmdline[@]:remove+1}) + let count-=remove + let remove=0 +fi + +case "${cmdline[1]}" in +add|addremove|ar) + opts=(--help --recursive) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + else + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count >= 2)) ; then + targets ${opts[*]} -- "${cmdline[count]}" + fi + ;; +build) + opts=(--help --oldpackages --disable-cpio-bulk-download --release --baselibs + --disable-debuginfo --debuginfo --alternative-project --vm-type --linksources + --local-package --build-uid --userootforbuild --define --without --with + --ccache --icecream --jobs --root --extra-pkgs --keep-pkgs --prefer-pkgs + --noservice --no-service --no-verify --nochecks --no-checks --noinit --no-init + --overlay --rsync-dest --rsync-src --no-changelog --preload --offline --clean) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --alternative-project) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + projects -- "${cmdline[count]}" + exit + elif ((count-remove == off+2)) ; then + repositories "${cmdline[off+1+remove]}" -- "${cmdline[off+2+$remove]}" + exit + elif ((count-remove == off+3)) ; then + architectures "${cmdline[off+1+remove]}" -- "${cmdline[off+3+remove]}" + exit + fi + let remove+=4 + ;; + --define) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + builtin compgen -P "'" -S "'" -W 'macro\ definition' -- "${cmdline[off+1+remvoe]}" + exit + elif ((count-remove == off+2)) ; then + exit + fi + let remove+=3 + ;; + --@(root|oldpackages|keep-pkgs|prefer-pkgs|rsync-dest|rsync-src)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + builtin compgen -o dirnames -d -- ${cmdline[off+1+remove]} + exit + fi + let remove+=2 + ;; + --@(release|icecream|jobs|without|with|overlay)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --build-uid) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + builtin compgen -W "399:399 $(id -u):$(id -g)" -- "${cmdline[off+2+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + specs=($(command ls *.spec)) + builtin compgen -W "${opts[*]} ${specs[*]}" -- "${cmdline[count]}" + fi + ;; +branch|getpac|bco|branchco) + opts=(--help --revision --new-package --maintenance --noaccess --extend-package-names + --add-repositories --force --nodevelproject) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + case "${cmdline[1]}" in + branch) opts[${#opts[@]}]=--checkout + esac + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --revision) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 6)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[4]}" + projects -- "${cmdline[4]}" + elif ((count == 5)) ; then + packages "${cmdline[4]}" -- "${cmdline[5]}" + elif ((count == 6)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[6]}" + fi + ;; +list|ls|ll|LL) + opts=(--help --meta --deleted --long --verbose --unexpand --expand --binaries + --repo --revision --arch) + if ((count == 1)) ; then + builtin compgen -W 'list ls ll LL' -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(revision|repo|arch)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + packages -u "${cmdline[2]}" "${cmdline[3]}" -- "${cmdline[4]}" + else + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; +less|cat) + opts=(--help --meta --unexpand --expand --revision) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --revision) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + packages -u "${cmdline[2]}" "${cmdline[3]}" -- "${cmdline[4]}" + else + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + fi + ;; +sr|submitpac|submitreq|submitrequest) + opts=(--help --yes --diff --no-update --no-cleanup --cleanup --seperate-requests + --nodevelproject --supersede --revision) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(revision|supersede)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 6)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count >= 2 && count <= 5)) ; then + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + fi + submit $((count-1)) 1 + elif ((count == 6)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[6]}" + fi + ;; +rq|request|review) + opts=(--help --involved-projects --exclude-target-project --non-interactive --interactive --edit) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + while test "${cmdline[2+remove]::1}" = "-" ; do + case "${cmdline[2+remove]}" in + --exclude-target-project) + if ((count-remove == 2)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + elif ((count-remove == 3)) ; then + builtin echo -n EXCLUDE_TARGET_PROJECT + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:2} ${cmdline[@]:remove+2}) + let count-=remove + let remove=0 + fi + fi + case "${cmdline[2]}" in + log|checkout) + opts=(--help) + if ((count == 2)) ; then + builtin compgen -W 'log checkout' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + builtin echo -n 'ID' + else + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; + revoke|clone) + opts=(--help) + if ((count == 2)) ; then + builtin compgen -W 'revoke clone' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 4)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + builtin echo -n 'ID' + elif ((count == 4)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[4]}" + fi + ;; + setincident) + opts=(--help) + if ((count == 2)) ; then + builtin compgen -W 'setincident' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 4)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + builtin echo -n 'ID' + elif ((count == 4)) ; then + builtin echo -n 'INCIDENT' + elif ((count == 5)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[5]}" + fi + ;; + supersede|add|accept|decline|reopen) + case "${cmdline[1]}" in + rq|request) opts=() ;; + review) opts=(--user --group --project --package) + esac + if ((count == 2)) ; then + builtin compgen -W 'supersede add accept decline reopen' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + typeset -i supersede=0 + case "${cmdline[2]}" in + supersede) let supersede=1 + esac + typeset project="" + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --user) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + user=($(users ${cmdline[off+1+remove]})) + builtin compgen -W "${user[*]}" -- ${cmdline[off+1+remove]} + fi + let remove+=2 + ;; + --group) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --project) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + projects -- "${cmdline[off+1+remove]}" + exit + else + project="${cmdline[off+1+remove]}" + fi + let remove+=2 + ;; + --package) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + test -z "$project" && project=PROJECT_REQUIRED + packages -u "$project" -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 4)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts+${opts[*]} }ID" -- "${cmdline[3]}" + elif ((count == 4)) ; then + if ((supersede)) ; then + builtin echo -n 'SUPERSEDING_ID' + else + builtin compgen -W '--message' -- "${cmdline[4]}" + fi + elif ((count == 5 && supersede)) ; then + builtin compgen -W '--message' -- "${cmdline[5]}" + fi + ;; + approvenew) + opts=(--help) + if ((count == 2)) ; then + builtin compgen -W 'approvenew' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 4)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + projects -- "${cmdline[3]}" + elif ((count == 4)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[4]}" + fi + ;; + show) + opts=(--diff --brief --source-buildstatus) + if ((count == 2)) ; then + builtin compgen -W 'show' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=("${cmdline[@]:0:off}" "${cmdline[@]:remove+off}") + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]} ID" -- "${cmdline[3]}" + else + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; + list) + case "${cmdline[1]}" in + rq|request) opts=(--mine --user --state -days --type --bugowner) ;; + review) opts=(--user --group --project --package) + esac + if ((count == 2)) ; then + builtin compgen -W 'list' -- "${cmdline[count]}" + elif ((count >= 3)) ; then + typeset project="" + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --user) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + user=($(users ${cmdline[off+1+remove]})) + builtin compgen -W "${user[*]}" -- ${cmdline[off+1+remove]} + fi + let remove+=2 + ;; + --group) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --project) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + projects -- "${cmdline[off+1+remove]}" + exit + else + project="${cmdline[off+1+remove]}" + fi + let remove+=2 + ;; + --package) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + test -z "$project" && project=PROJECT_REQUIRED + packages -u "$project" -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=("${cmdline[@]:0:off}" "${cmdline[@]:remove+off}") + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + projects -- "${cmdline[3]}" + fi + if ((count == 4)) ; then + packages -u "${cmdline[3]}" -- "${cmdline[4]}" + fi + ;; + *) + if ((count == 2)) ; then + case "${cmdline[1+remove]}" in + rq|request) builtin compgen -W "${opts[*]} ${oscreq[*]}" -- "${cmdline[2]}" ;; + review) builtin compgen -W "${opts[*]} ${oscrev[*]}" -- "${cmdline[2]}" ;; + esac + fi + esac + ;; +my) + opts=(--help --maintained --verbose --exclude-project --user --all --maintainer --bugowner) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --user) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + user=($(users ${cmdline[off+1+remove]})) + builtin compgen -W "${user[*]}" -- ${cmdline[off+1+remove]} + fi + let remove+=2 + ;; + --exclude-project) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + projects -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]} ${oscmy[*]}" -- "${cmdline[2]}" + elif ((count >= 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + fi + ;; +copypac|linkpac) + opts=(--help --expand --to-apiurl --revision --keep-develproject --keep-link + --keep-maintainers --client-side-copy) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(revision|to-apiurl)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + projects -- "${cmdline[4]}" + elif ((count == 5)) ; then + packages"${cmdline[4]}" -- "${cmdline[5]}" + elif ((count == 6)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[6]}" + fi + ;; +delete) + opts=(--help --force) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + else + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count >= 2)) ; then + targets ${opts[*]} -- "${cmdline[count]}" + fi + ;; +deleterequest|deletereq|droprequest|dropreq|dr) + typeset -i repository=0 + opts=(--help --repository --message) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count == 2)) ; then + projects -- "${cmdline[2]}" + elif ((count >= 3)) ; then + for ((off=3; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --repository) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + let repository++ + ;; + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 4)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 3)) ; then + if ((repository)) ; then + builtin compgen -W '--message' -- "${cmdline[4]}" + else + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + packages "${cmdline[2]}" -- "${cmdline[3]}" + fi + elif ((count == 4)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[4]}" + fi + ;; +changedevelrequest|changedevelreq|cr) + opts=(--help) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[4]}" + elif ((count == 5)) ; then + packages "${cmdline[4]}" -- "${cmdline[5]}" + fi + ;; +rdiff) + opts=(--help --unexpand --missingok --change --plain --revision --meta) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(revision|change)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[4]}" + projects -- "${cmdline[4]}" + elif ((count == 5)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[5]}" + packages "${cmdline[4]}" -- "${cmdline[5]}" + fi + ;; +ci|commit|checkin) + opts=(--help --skip-local-service-run --noservice --verbose --skip-validation --force + --file --message) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --file) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --message) exit ;; + -*) + if ((count-remove == off)) ; then + ((count >= 3)) && opts[${#opts[@]}]=--message + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + targets ${opts[*]} -- "${cmdline[2]}" + elif ((count == 3)) ; then + builtin compgen -W "--message ${opts[*]}" -- "${cmdline[3]}" + fi + ;; +co|checkout) + opts=(--help --limit-size --server-side-source-service-files --source-service-files + --output-dir --current-dir --meta --unexpand-link --expand-link --revision) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(output-dir|revision)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + ;; + *) break + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count == 4)) ; then + packages "${cmdline[2]}" "${cmdline[3]}" -- "${cmdline[4]}" + fi + ;; +maintainer) + opts=(--help --role --delete --set-bugowner-request --set-bugowner --all --add + --devel-project --verbose --nodevelproject --email --bugowner --bugowner-only) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(delete|set-bugowner-request|set-bugowner|add|devel-projec)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --devel-projec) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + projects -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + --role) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + builtin compgen -W 'bugowner maintainer involved' -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count > 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + if ((count == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + fi + fi + ;; +up|update) + opts=(--help --limit-size --server-side-source-service-files --source-service-files + --expand-link --unexpand-link --revision) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + while test "${cmdline[2+remove]::1}" = "-" ; do + case "${cmdline[2+remove]}" in + --revision) + if ((count-remove == 2)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + elif ((count-remove == 3)) && test -z "${cmdline[3+remove]}" ; then + hint="${cmdline[2+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + fi + let remove++ + esac + done + fi + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + ;; +meta) + opts=(--help --delete --set --remove-linking-repositories --create --edit --file + --force --attribute-project --attribute-defaults --attribute) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(attribute|file)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --set) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + builtin echo -n ATTRIBUTE_VALUES + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W 'prj pkg prjconf user pattern attribute' -- "${cmdline[2]}" + elif ((count == 3)) ; then + if test "${cmdline[2]}" = user ; then + user=($(users ${cmdline[3]})) + builtin compgen -W "${user[*]}" -- ${cmdline[3]} + else + projects -- "${cmdline[3]}" + fi + elif ((count == 4)) ; then + if test "${cmdline[2]}" = pkg ; then + packages "${cmdline[3]}" -- "${cmdline[4]}" + elif test "${cmdline[2]}" = attribute ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[4]}" + packages "${cmdline[3]}" -- "${cmdline[4]}" + elif test "${cmdline[2]}" = user ; then + user=($(users ${cmdline[4]})) + builtin compgen -W "${user[*]}" -- ${cmdline[4]} + else + builtin compgen -W "${opts[*]}" -- ${cmdline[4]} + fi + elif ((count == 5)) ; then + builtin compgen -W "${opts[*]}" -- ${cmdline[5]} + fi + ;; +wipebinaries) + opts=(--help --all --unresolvable --broken --build-failed --build-disabled --repo --arch) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(repo|arch)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count-remove == 3)) ; then + packages "${cmdline[2]}" -- "${cmdline[3]}" + fi + ;; +help) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + else + builtin compgen -W "${osccmds[*]}" -- "${cmdline[2]}" + fi + ;; +search) + opts=(--help --all --binaryversion --baseproject --binary --csv --mine + --maintained --maintainer --bugowner --involved --version --verbose + --limit-to-attribute --description --title --project --package + --substring --exact --repos-baseurl) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + while test "${cmdline[2+remove]::1}" = "-" ; do + case "${cmdline[2+remove]}" in + --@(binaryversion|baseproject|limit-to-attribute)) + if ((count-remove == 2)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + elif ((count-remove == 3)) && test -z "${cmdline[3+remove]}" ; then + hint="${cmdline[2+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --@(maintainer|bugowner|involved)) + if ((count-remove == 2)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + elif ((count-remove == 3)) ; then + user=($(users ${cmdline[3+remove]})) + builtin compgen -W "${user[*]}" -- ${cmdline[3+remove]} + fi + let remove+=2 + ;; + -*) + if ((count-remove == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2+remove]}" + exit + fi + let remove++ + esac + done + fi + if ((count-remove == 2)) ; then + builtin compgen -W "${opts[*]} SEARCH_TERM" -- "${cmdline[count]}" + fi + ;; +pr|prjresults) + opts=(--help --show-excluded --vertical --repo --arch --name-filter --status-filter + --xml --csv --hide-legend) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[2]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(repo|arch)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --name-filter) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + builtin echo -n EXPR + exit + fi + let remove+=2 + ;; + --status-filter) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) ; then + status=(disabled failed finished building succeeded broken scheduled unresolvable signing blocked) + builtin compgen -W "${status[*]}" -- "${cmdline[off+1+remove]}" + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + else + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; +r|results) + opts=(--help --format --csv --xml --watch --verbose --arch --repo --last-build) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(repo|arch|format)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count > 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; +diff|linkdiff) + opts=(--help --missingok --link --plain --revision --change) + typeset -i link=0 + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + case "${cmdline[1]}" in + linkdiff) let link++ ;; + esac + for ((off=2; off<=count; off++)) ; do + while test "${cmdline[off+remove]::1}" = "-" ; do + case "${cmdline[off+remove]}" in + --@(revision|change)) + if ((count-remove == off)); then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + elif ((count-remove == off+1)) && test -z "${cmdline[off+1+remove]}" ; then + hint="${cmdline[off+remove]^^}" + builtin echo -n ${hint##*-} + exit + fi + let remove+=2 + ;; + --link) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + let link++ + ;; + -*) + if ((count-remove == off)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[off+remove]}" + exit + fi + let remove++ + esac + done + if ((remove)) ; then + cmdline=(${cmdline[*]:0:off} ${cmdline[@]:remove+off}) + let count-=remove + let remove=0 + fi + done + fi + if ((count == 2)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[2]}" + ((link)) && projects -- "${cmdline[2]}" + elif ((count == 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[3]}" + ((link)) && packages "${cmdline[2]}" -- "${cmdline[3]}" + elif ((count > 3)) ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + fi + ;; +*) + opts=(--help) + if ((count == 1)) ; then + builtin compgen -W "${osccmds[*]} ${oscopts[*]}" -- "${cmdline[count]}" + elif ((count >= 2)) ; then + if test "${cmdline[count]::1}" = "-" ; then + builtin compgen -W "${opts[*]}" -- "${cmdline[count]}" + else + targets ${opts[*]} -- "${cmdline[count]}" + fi + fi +esac diff --git a/docs/_static/.keepme b/docs/_static/.keepme new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docs/_static/.keepme diff --git a/docs/api/modules.rst b/docs/api/modules.rst new file mode 100644 index 0000000..26c0985 --- /dev/null +++ b/docs/api/modules.rst @@ -0,0 +1,10 @@ +osc +=== + +These are the packages in the osc package. + +.. toctree:: + :maxdepth: 4 + + osc.core + osc.util diff --git a/docs/api/osc.core.rst b/docs/api/osc.core.rst new file mode 100644 index 0000000..b4840ba --- /dev/null +++ b/docs/api/osc.core.rst @@ -0,0 +1,20 @@ +.. py:module:: osc.core + +core +==== + +This is the osc core module. + +basic structures +---------------- + +.. autoclass:: File + :members: + + +.. autoclass:: Serviceinfo + :members: + + +.. autoclass:: Linkinfo + :members: diff --git a/docs/api/osc.util.rst b/docs/api/osc.util.rst new file mode 100644 index 0000000..e00bd62 --- /dev/null +++ b/docs/api/osc.util.rst @@ -0,0 +1,78 @@ +osc.util package +================ + +Submodules +---------- + +osc.util.ar module +------------------ + +.. automodule:: osc.util.ar + :members: + :undoc-members: + :show-inheritance: + +osc.util.archquery module +------------------------- + +.. automodule:: osc.util.archquery + :members: + :undoc-members: + :show-inheritance: + +osc.util.cpio module +-------------------- + +.. automodule:: osc.util.cpio + :members: + :undoc-members: + :show-inheritance: + +osc.util.debquery module +------------------------ + +.. automodule:: osc.util.debquery + :members: + :undoc-members: + :show-inheritance: + +osc.util.packagequery module +---------------------------- + +.. automodule:: osc.util.packagequery + :members: + :undoc-members: + :show-inheritance: + +osc.util.repodata module +------------------------ + +.. automodule:: osc.util.repodata + :members: + :undoc-members: + :show-inheritance: + +osc.util.rpmquery module +------------------------ + +.. automodule:: osc.util.rpmquery + :members: + :undoc-members: + :show-inheritance: + +osc.util.safewriter module +-------------------------- + +.. automodule:: osc.util.safewriter + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: osc.util + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/tutorial.rst b/docs/api/tutorial.rst new file mode 100644 index 0000000..01a440d --- /dev/null +++ b/docs/api/tutorial.rst @@ -0,0 +1,96 @@ +Tutorial +======== + +This is a tutorial on how to use the osc python api. + +Key to the |obs| are (remote): + + #. A **project** + #. A project has associated multiple **repositories** (linux distributions) + #. Multiple **packages** in a project will hold the builds against the difefrent **repositories** + + +A user will deal with local checkout of a project in a **working copy**: this is similar to the +subversion checkout model. + + +Initial config setup +-------------------- + +Osc the library requires an initial setup: + + >>> import osc.conf + >>> osc.conf.get_config() + +This will read all the external config files (eg. ~/.oscrc) and the internal configuration +values. + + +Acquiring the apiurl +-------------------- + +All the osc operation will use a **apiurl** to lookup for things like passwords, username and other parameters +while performing operations: + + >>> apiurl = osc.conf.config['apiurl'] + + +Operations on a remote build server +----------------------------------- + +osc is similar to subversion, it has a remote server and a local (checkout) **working** directory. +First we'll go through the remote operation on a server **NOT** requiring a checkout. +Operations are contained in the osc.core module: + + >>> import osc.core + + +List all the projects and packages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This will show all the projects on the remote |obs|: + + >>> for prj in osc.core.meta_get_project_list(apiurl, deleted=False): + print prj + + +A project has **repositories** associated with it (eg. linux distributions): + + >>> prj = 'home:cavallo71:opt-python-interpreters' + >>> for repo in osc.core.get_repos_of_project(apiurl, prj): + print repo + + +A project contains packages and to list them all: + + >>> prj = 'home:cavallo71:opt-python-interpreters' + >>> for pkg in osc.core.meta_get_packagelist(apiurl, prj): + print pkg + + +Add a package to an existing project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +Operations in a checked out **working copy** +-------------------------------------------- + + + +Create your first project: the hello project +-------------------------------------------- + +.. todo:: add he description on how to init a project + + +Adding your firs package to the project hello: the world package +---------------------------------------------------------------- + +.. todo:: add he description on how to add a package + + + +Setting the build architectures +------------------------------- + + diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..a3c582a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +# +# osc documentation build configuration file, created by +# sphinx-quickstart on Sun Jan 24 13:06:29 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + + +# top level dir (one above this file) +topdir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, topdir) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.ifconfig', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'osc' +copyright = u'2016, see authors list' +author = u'see authors list' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.2.3' +# The full version, including alpha/beta/rc tags. +release = '4.5.6' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'oscdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'osc.tex', u'osc Documentation', + u'see authors list', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'osc', u'osc Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'osc', u'osc Documentation', + author, 'osc', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +rst_epilog = """ +.. |obs| replace:: open build service +""" diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..2fcde0a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,33 @@ +.. osc documentation master file, created by + sphinx-quickstart on Sun Jan 24 13:06:29 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to osc's documentation! +=============================== + +This is the documentation for the osc python client to the |obs|. + +Tutorial + +.. TODO:: add more documentation + + + +API: + +.. toctree:: + :maxdepth: 2 + + api/tutorial + api/modules + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/fuse/fuseosc b/fuse/fuseosc new file mode 100755 index 0000000..08489ea --- /dev/null +++ b/fuse/fuseosc @@ -0,0 +1,234 @@ +#!/usr/bin/python + +# Copyright (c) 2008-2009 Pavol Rusnak <prusnak@suse.cz> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +import sys +import os +try: + import osc + import osc.conf + import osc.core +except: + # allow loading module from working copy if osc is not installed + sys.path.append(os.path.abspath(os.path.dirname(sys.argv[0]) + '/../osc')) + import osc + import osc.conf + import osc.core +import fuse +import stat +import errno +import tempfile + +fuse.fuse_python_api = (0, 2) + +projects = [] +cache = {} + +class EmptyStat(fuse.Stat): + def __init__(self): + self.st_mode = 0 + self.st_ino = 0 + self.st_dev = 0 + self.st_nlink = 0 + self.st_uid = 0 + self.st_gid = 0 + self.st_size = 0 + self.st_atime = 0 + self.st_mtime = 0 + self.st_ctime = 0 + +class CacheEntry(object): + def __init__(self): + self.stat = None + self.handle = None + self.tmpname = None + +class oscFS(fuse.Fuse): + + def __init__(self, *args, **kw): + fuse.Fuse.__init__(self, *args, **kw) + print 'OK' + + def getattr(self, path): + st = EmptyStat() + # path is project + if path == '/' or path in projects or len(filter(lambda x: x.startswith(path), projects)) > 0: + st.st_mode = stat.S_IFDIR | 0555 + st.st_nlink = 2 + return st + # path is package + if os.path.dirname(path) in projects: + st.st_mode = stat.S_IFDIR | 0555 + st.st_nlink = 2 + return st + # path is file + if cache.has_key(path): + return cache[path].stat + else: + return -errno.ENOENT + + def readdir(self, path, offset): + yield fuse.Direntry('.') + yield fuse.Direntry('..') + + if os.path.dirname(path) in projects: # path is package + prj = os.path.dirname(path).replace('/','') + pkg = os.path.basename(path) + for f in osc.core.meta_get_filelist(osc.conf.config['apiurl'], prj, pkg, verbose=True): + st = EmptyStat() + st.st_mode = stat.S_IFREG | 0444 + st.st_size = f.size + st.st_atime = f.mtime + st.st_ctime = f.mtime + st.st_mtime = f.mtime + cache[path + '/' + f.name] = CacheEntry() + cache[path + '/' + f.name].stat = st + yield fuse.Direntry(f.name) + return + + if path in projects: # path is project + prj = path.replace('/','') + for p in osc.core.meta_get_packagelist(osc.conf.config['apiurl'], prj): + yield fuse.Direntry(p) + + else: # path is project structure + if (path != '/'): + path += '/' + l = len(path) + for d in set( map(lambda x: x[l:].split('/')[0], filter(lambda x: x.startswith(path), projects) ) ) : + yield fuse.Direntry(d) + + def mythread ( self ): + print '*** mythread' + return -errno.ENOSYS + + def chmod ( self, path, mode ): + print '*** chmod', path, oct(mode) + return -errno.ENOSYS + + def chown ( self, path, uid, gid ): + print '*** chown', path, uid, gid + return -errno.ENOSYS + + def fsync ( self, path, isFsyncFile ): + print '*** fsync', path, isFsyncFile + return -errno.ENOSYS + + def link ( self, targetPath, linkPath ): + print '*** link', targetPath, linkPath + return -errno.ENOSYS + + def mkdir ( self, path, mode ): + print '*** mkdir', path, oct(mode) + return -errno.ENOSYS + + def mknod ( self, path, mode, dev ): + print '*** mknod', path, oct(mode), dev + return -errno.ENOSYS + + def open ( self, path, flags ): + file = os.path.basename(path) + d = os.path.dirname(path) + pkg = os.path.basename(d) + prj = os.path.dirname(d).replace('/','') + if not cache.has_key(path): + return -errno.ENOENT + if cache[path].stat == None: + return -errno.ENOENT + tmp = tempfile.mktemp(prefix = 'oscfs_') + osc.core.get_source_file(osc.conf.config['apiurl'], prj, pkg, file, tmp) + cache[path].handle = open(tmp, 'r') + cache[path].tmpname = tmp + + def read ( self, path, length, offset ): + if not cache.has_key(path): + return -errno.EACCES + f = cache[path].handle + f.seek(offset) + return f.read(length) + + def readlink ( self, path ): + print '*** readlink', path + return -errno.ENOSYS + + def release ( self, path, flags ): + if cache.has_key(path): + cache[path].handle.close() + cache[path].handle = None + os.unlink(f.cache[path].tmpname) + cache[path].tmpname = None + + def rename ( self, oldPath, newPath ): + print '*** rename', oldPath, newPath + return -errno.ENOSYS + + def rmdir ( self, path ): + print '*** rmdir', path + return -errno.ENOSYS + + def statfs ( self ): + print '*** statfs' + return -errno.ENOSYS + + def symlink ( self, targetPath, linkPath ): + print '*** symlink', targetPath, linkPath + return -errno.ENOSYS + + def truncate ( self, path, size ): + print '*** truncate', path, size + return -errno.ENOSYS + + def unlink ( self, path ): + print '*** unlink', path + return -errno.ENOSYS + + def utime ( self, path, times ): + print '*** utime', path, times + return -errno.ENOSYS + + def write ( self, path, buf, offset ): + print '*** write', path, buf, offset + return -errno.ENOSYS + +def fill_projects(): + try: + for prj in osc.core.meta_get_project_list(osc.conf.config['apiurl']): + projects.append( '/' + prj.replace(':', ':/') ) + except: + print 'failed' + sys.exit(1) + +if __name__ == '__main__': + print 'Loading config ...', + osc.conf.get_config() + print 'OK' + print 'Getting projects list ...', + fill_projects() + print 'OK' + print 'Starting FUSE ...', + oscfs = oscFS( version = '%prog ' + fuse.__version__, usage = '', dash_s_do = 'setsingle') + oscfs.flags = 0 + oscfs.multithreaded = 0 + oscfs.parse(values = oscfs, errex = 1) + oscfs.main() diff --git a/fuse/start b/fuse/start new file mode 100755 index 0000000..ebdde5e --- /dev/null +++ b/fuse/start @@ -0,0 +1,3 @@ +#!/bin/sh +mkdir -p ./test +./fuseosc ./test diff --git a/fuse/stop b/fuse/stop new file mode 100755 index 0000000..a1c55a7 --- /dev/null +++ b/fuse/stop @@ -0,0 +1,2 @@ +#!/bin/sh +fusermount -u ./test diff --git a/osc-wrapper.py b/osc-wrapper.py new file mode 100755 index 0000000..833729e --- /dev/null +++ b/osc-wrapper.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# this wrapper exists so it can be put into /usr/bin, but still allows the +# python module to be called within the source directory during development + +import locale +import sys +import os + +from osc import commandline, babysitter + +try: +# this is a hack to make osc work as expected with utf-8 characters, +# no matter how site.py is set... + reload(sys) + loc = locale.getpreferredencoding() + if not loc: + loc = sys.getpreferredencoding() + sys.setdefaultencoding(loc) + del sys.setdefaultencoding +except NameError: + #reload, neither setdefaultencoding are in python3 + pass + +# avoid buffering output on pipes (bnc#930137) +# Basically, a "print('foo')" call is translated to a corresponding +# fwrite call that writes to the stdout stream (cf. string_print +# (Objects/stringobject.c) and builtin_print (Python/bltinmodule.c)); +# If no pipe is used, stdout is a tty/refers to a terminal => +# the stream is line buffered (see _IO_file_doallocate (libio/filedoalloc.c)). +# If a pipe is used, stdout does not refer to a terminal anymore => +# the stream is fully buffered by default (see _IO_file_doallocate). +# The following fdopen call makes stdout line buffered again (at least on +# systems that support setvbuf - if setvbuf is not supported, the stream +# remains fully buffered (see PyFile_SetBufSize (Objects/fileobject.c))). +if not os.isatty(sys.stdout.fileno()): + sys.stdout = os.fdopen(sys.stdout.fileno(), sys.stdout.mode, 1) + +osccli = commandline.Osc() + +r = babysitter.run(osccli) +sys.exit(r) diff --git a/osc.fish b/osc.fish new file mode 100644 index 0000000..09dac9d --- /dev/null +++ b/osc.fish @@ -0,0 +1,116 @@ +# fish completion for git +# vim: smartindent:expandtab:ts=2:sw=2 + +function __fish_osc_needs_command + set cmd (commandline -opc) + if contains "$cmd" 'osc' 'osc help' + return 0 + end + return 1 +end + +function __fish_osc_using_command + set cmd (commandline -opc) + if [ (count $cmd) -gt 1 ] + for arg in $argv + if [ $arg = $cmd[2] ] + return 0 + end + end + end + return 1 +end + +# general options +complete -f -c osc -n 'not __fish_osc_needs_command' -s A -l apiurl -d 'specify URL to access API server at or an alias' +complete -f -c osc -n 'not __fish_osc_needs_command' -s c -l config -d 'specify alternate configuration file' +complete -f -c osc -n 'not __fish_osc_needs_command' -s d -l debug -d 'print info useful for debugging' +complete -f -c osc -n 'not __fish_osc_needs_command' -l debugger -d 'jump into the debugger before executing anything' +complete -f -c osc -n 'not __fish_osc_needs_command' -s h -l help -d 'show this help message and exit' +complete -f -c osc -n 'not __fish_osc_needs_command' -s H -l http-debug -d 'debug HTTP traffic (filters some headers)' +complete -f -c osc -n 'not __fish_osc_needs_command' -l http-full-debug -d 'debug HTTP traffic (filters no headers)' +complete -f -c osc -n 'not __fish_osc_needs_command' -l no-gnome-keyring -d 'disable usage of GNOME Keyring' +complete -f -c osc -n 'not __fish_osc_needs_command' -l no-keyring -d 'disable usage of desktop keyring system' +complete -f -c osc -n 'not __fish_osc_needs_command' -l post-mortem -d 'jump into the debugger in case of errors' +complete -f -c osc -n 'not __fish_osc_needs_command' -s q -l quiet -d 'be quiet, not verbose' +complete -f -c osc -n 'not __fish_osc_needs_command' -s t -l traceback -d 'print call trace in case of errors' +complete -f -c osc -n 'not __fish_osc_needs_command' -s v -l verbose -d 'increase verbosity' +complete -f -c osc -n 'not __fish_osc_needs_command' -l version -d 'show program\'s version number and exit' + +# osc commands +complete -f -c osc -n '__fish_osc_needs_command' -a 'add' -d 'Mark files to be added upon the next commit' +complete -f -c osc -n '__fish_osc_needs_command' -a 'addremove ar' -d 'Adds new files, removes disappeared files' +complete -f -c osc -n '__fish_osc_needs_command' -a 'aggregatepac' -d '"Aggregate" a package to another package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'api' -d 'Issue an arbitrary request to the API' +complete -f -c osc -n '__fish_osc_needs_command' -a 'branch bco branchco getpac' -d 'Branch a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'chroot' -d 'into the buildchroot' +complete -f -c osc -n '__fish_osc_needs_command' -a 'clean' -d 'removes all untracked files from the package working ...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'commit checkin ci' -d 'Upload content to the repository server' +complete -f -c osc -n '__fish_osc_needs_command' -a 'config' -d 'get/set a config option' +complete -f -c osc -n '__fish_osc_needs_command' -a 'copypac' -d 'Copy a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'createincident' -d 'Create a maintenance incident' +complete -f -c osc -n '__fish_osc_needs_command' -a 'createrequest creq' -d 'create multiple requests with a single command' +complete -f -c osc -n '__fish_osc_needs_command' -a 'delete del remove rm' -d 'Mark files or package directories to be deleted upon ...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'deleterequest deletereq dr dropreq droprequest' -d 'Request to delete (or "drop") a package or project' +complete -f -c osc -n '__fish_osc_needs_command' -a 'dependson whatdependson' -d 'Show the build dependencies' +complete -f -c osc -n '__fish_osc_needs_command' -a 'detachbranch' -d 'replace a link with its expanded sources' +complete -f -c osc -n '__fish_osc_needs_command' -a 'develproject bsdevelproject dp' -d 'print the devel project / package of a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'diff di ldiff linkdiff' -d 'Generates a diff' +complete -f -c osc -n '__fish_osc_needs_command' -a 'distributions dists' -d 'Shows all available distributions' +complete -f -c osc -n '__fish_osc_needs_command' -a 'getbinaries' -d 'Download binaries to a local directory' +complete -f -c osc -n '__fish_osc_needs_command' -a 'help ? h' -d 'give detailed help on a specific sub-command' +complete -f -c osc -n '__fish_osc_needs_command' -a 'importsrcpkg' -d 'Import a new package from a src.rpm' +complete -f -c osc -n '__fish_osc_needs_command' -a 'info' -d 'Print information about a working copy' +complete -f -c osc -n '__fish_osc_needs_command' -a 'init' -d 'Initialize a directory as working copy' +complete -f -c osc -n '__fish_osc_needs_command' -a 'jobhistory jobhist' -d 'Shows the job history of a project' +complete -f -c osc -n '__fish_osc_needs_command' -a 'linkpac' -d '"Link" a package to another package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'linktobranch' -d 'Convert a package containing a classic link with patc...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'list LL lL ll ls' -d 'List sources or binaries on the server' +complete -f -c osc -n '__fish_osc_needs_command' -a 'localbuildlog lbl' -d 'Shows the build log of a local buildchroot' +complete -f -c osc -n '__fish_osc_needs_command' -a 'log' -d 'Shows the commit log of a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'maintainer bugowner' -d 'Show maintainers according to server side configuration' +complete -f -c osc -n '__fish_osc_needs_command' -a 'maintenancerequest mr' -d 'Create a request for starting a maintenance incident.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'man' -d 'generates a man page' +complete -f -c osc -n '__fish_osc_needs_command' -a 'mbranch maintained sm' -d 'Search or banch multiple instances of a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'meta' -d 'Show meta information, or edit it' +complete -f -c osc -n '__fish_osc_needs_command' -a 'mkpac' -d 'Create a new package under version control' +complete -f -c osc -n '__fish_osc_needs_command' -a 'mv' -d 'Move SOURCE file to DEST and keep it under version co...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'my' -d 'show waiting work, packages, projects or requests inv...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'patchinfo' -d 'Generate and edit a patchinfo file.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'pdiff' -d 'Quick alias to diff the content of a package with its...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'prdiff projdiff projectdiff' -d 'Server-side diff of two projects' +complete -f -c osc -n '__fish_osc_needs_command' -a 'prjresults pr' -d 'Shows project-wide build results' +complete -f -c osc -n '__fish_osc_needs_command' -a 'pull' -d 'merge the changes of the link target into your workin...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'rdelete' -d 'Delete a project or packages on the server.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'rdiff' -d 'Server-side "pretty" diff of two packages' +complete -f -c osc -n '__fish_osc_needs_command' -a 'rebuild rebuildpac' -d 'Trigger package rebuilds' +complete -f -c osc -n '__fish_osc_needs_command' -a 'release' -d 'Release sources and binaries' +complete -f -c osc -n '__fish_osc_needs_command' -a 'releaserequest' -d 'Create a request for releasing a maintenance update.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'remotebuildlog rbl rblt rbuildlog rbuildlogtail remotebuildlogtail' -d 'Shows the build log of a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'repairlink' -d 'Repair a broken source link' +complete -f -c osc -n '__fish_osc_needs_command' -a 'repairwc' -d 'try to repair an inconsistent working copy' +complete -f -c osc -n '__fish_osc_needs_command' -a 'repositories platforms repos' -d 'shows repositories configured for a project. It skips...' +complete -f -c osc -n '__fish_osc_needs_command' -a 'repourls' -d 'Shows URLs of .repo files' +complete -f -c osc -n '__fish_osc_needs_command' -a 'request review rq' -d 'Show or modify requests and reviews' +complete -f -c osc -n '__fish_osc_needs_command' -a 'requestmaintainership reqbs reqbugownership reqmaintainership reqms requestbugownership' -d 'requests to add user as maintainer or bugowner' +complete -f -c osc -n '__fish_osc_needs_command' -a 'resolved' -d 'Remove "conflicted" state on working copy files' +complete -f -c osc -n '__fish_osc_needs_command' -a 'restartbuild abortbuild' -d 'Restart the build of a certain project or package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'results r' -d 'Shows the build results of a package or project' +complete -f -c osc -n '__fish_osc_needs_command' -a 'revert' -d 'Restore changed files or the entire working copy.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'rremove' -d 'Remove source files from selected package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'search bse se' -d 'Search for a project and/or package.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'service' -d 'Handle source services' +complete -f -c osc -n '__fish_osc_needs_command' -a 'setdevelproject sdp' -d 'Set the devel project / package of a package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'setlinkrev' -d 'Updates a revision number in a source link.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'signkey' -d 'Manage Project Signing Key' +complete -f -c osc -n '__fish_osc_needs_command' -a 'status st' -d 'Show status of files in working copy' +complete -f -c osc -n '__fish_osc_needs_command' -a 'submitrequest sr submitpac submitreq' -d 'Create request to submit source into another Project' +complete -f -c osc -n '__fish_osc_needs_command' -a 'token' -d 'Show and manage authentication token' +complete -f -c osc -n '__fish_osc_needs_command' -a 'triggerreason tr' -d 'Show reason why a package got triggered to build' +complete -f -c osc -n '__fish_osc_needs_command' -a 'undelete' -d 'Restores a deleted project or package on the server.' +complete -f -c osc -n '__fish_osc_needs_command' -a 'unlock' -d 'Unlocks a project or package' +complete -f -c osc -n '__fish_osc_needs_command' -a 'update up' -d 'Update a working copy' +complete -f -c osc -n '__fish_osc_needs_command' -a 'updatepacmetafromspec metafromspec updatepkgmetafromspec' -d 'Update package meta information from a specfile' +complete -f -c osc -n '__fish_osc_needs_command' -a 'vc' -d 'Edit the changes file' +complete -f -c osc -n '__fish_osc_needs_command' -a 'whois user who' -d 'Show fullname and email of a buildservice user' +complete -f -c osc -n '__fish_osc_needs_command' -a 'wipebinaries' -d 'Delete all binary packages of a certain project/package' Binary files differBinary files differdiff --git a/osc/OscConfigParser.py b/osc/OscConfigParser.py new file mode 100644 index 0000000..bf154b1 --- /dev/null +++ b/osc/OscConfigParser.py @@ -0,0 +1,360 @@ +# Copyright 2008,2009 Marcus Huewe <suse-tux@gmx.de> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation; +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import print_function + +import sys + +if sys.version_info >= ( 3, ): + import configparser +else: + #python 2.x + import ConfigParser as configparser + +import re + +# inspired from http://code.google.com/p/iniparse/ - although their implementation is +# quite different + +class ConfigLineOrder: + """ + A ConfigLineOrder() instance task is to preserve the order of a config file. + It keeps track of all lines (including comments) in the _lines list. This list + either contains SectionLine() instances or CommentLine() instances. + """ + def __init__(self): + self._lines = [] + + def _append(self, line_obj): + self._lines.append(line_obj) + + def _find_section(self, section): + for line in self._lines: + if line.type == 'section' and line.name == section: + return line + return None + + def add_section(self, sectname): + self._append(SectionLine(sectname)) + + def get_section(self, sectname): + section = self._find_section(sectname) + if section: + return section + section = SectionLine(sectname) + self._append(section) + return section + + def add_other(self, sectname, line): + if sectname: + self.get_section(sectname).add_other(line) + else: + self._append(CommentLine(line)) + + def keys(self): + return [ i.name for i in self._lines if i.type == 'section' ] + + def __setitem__(self, key, value): + section = SectionLine(key) + self._append(section) + + def __getitem__(self, key): + section = self._find_section(key) + if not section: + raise KeyError() + return section + + def __delitem__(self, key): + line = self._find_section(key) + if not line: + raise KeyError(key) + self._lines.remove(line) + + def __iter__(self): + #return self._lines.__iter__() + for line in self._lines: + if line.type == 'section': + yield line.name + raise StopIteration() + +class Line: + """Base class for all line objects""" + def __init__(self, name, type): + self.name = name + self.type = type + +class SectionLine(Line): + """ + This class represents a [section]. It stores all lines which belongs to + this certain section in the _lines list. The _lines list either contains + CommentLine() or OptionLine() instances. + """ + def __init__(self, sectname, dict = {}): + Line.__init__(self, sectname, 'section') + self._lines = [] + self._dict = dict + + def _find(self, name): + for line in self._lines: + if line.name == name: + return line + return None + + def _add_option(self, optname, value = None, line = None, sep = '='): + if value is None and line is None: + raise configparser.Error('Either value or line must be passed in') + elif value and line: + raise configparser.Error('value and line are mutually exclusive') + + if value is not None: + line = '%s%s%s' % (optname, sep, value) + opt = self._find(optname) + if opt: + opt.format(line) + else: + self._lines.append(OptionLine(optname, line)) + + def add_other(self, line): + self._lines.append(CommentLine(line)) + + def copy(self): + return dict(self.items()) + + def items(self): + return [ (i.name, i.value) for i in self._lines if i.type == 'option' ] + + def keys(self): + return [ i.name for i in self._lines ] + + def __setitem__(self, key, val): + self._add_option(key, val) + + def __getitem__(self, key): + line = self._find(key) + if not line: + raise KeyError(key) + return str(line) + + def __delitem__(self, key): + line = self._find(key) + if not line: + raise KeyError(key) + self._lines.remove(line) + + def __str__(self): + return self.name + + # XXX: needed to support 'x' in cp._sections['sectname'] + def __iter__(self): + for line in self._lines: + yield line.name + raise StopIteration() + + +class CommentLine(Line): + """Store a commentline""" + def __init__(self, line): + Line.__init__(self, line.strip('\n'), 'comment') + + def __str__(self): + return self.name + +class OptionLine(Line): + """ + This class represents an option. The class' "name" attribute is used + to store the option's name and the "value" attribute contains the option's + value. The "frmt" attribute preserves the format which was used in the configuration + file. + Example: + optionx:<SPACE><SPACE>value + => self.frmt = '%s:<SPACE><SPACE>%s' + optiony<SPACE>=<SPACE>value<SPACE>;<SPACE>some_comment + => self.frmt = '%s<SPACE>=<SPACE><SPACE>%s<SPACE>;<SPACE>some_comment + """ + + def __init__(self, optname, line): + Line.__init__(self, optname, 'option') + self.name = optname + self.format(line) + + def format(self, line): + mo = configparser.ConfigParser.OPTCRE.match(line.strip()) + key, val = mo.group('option', 'value') + self.frmt = line.replace(key.strip(), '%s', 1) + pos = val.find(' ;') + if pos >= 0: + val = val[:pos] + self.value = val + self.frmt = self.frmt.replace(val.strip(), '%s', 1).rstrip('\n') + + def __str__(self): + return self.value + + +class OscConfigParser(configparser.SafeConfigParser): + """ + OscConfigParser() behaves like a normal ConfigParser() object. The + only differences is that it preserves the order+format of configuration entries + and that it stores comments. + In order to keep the order and the format it makes use of the ConfigLineOrder() + class. + """ + def __init__(self, defaults={}): + configparser.SafeConfigParser.__init__(self, defaults) + self._sections = ConfigLineOrder() + + # XXX: unfortunately we have to override the _read() method from the ConfigParser() + # class because a) we need to store comments b) the original version doesn't use + # the its set methods to add and set sections, options etc. instead they use a + # dictionary (this makes it hard for subclasses to use their own objects, IMHO + # a bug) and c) in case of an option we need the complete line to store the format. + # This all sounds complicated but it isn't - we only needed some slight changes + def _read(self, fp, fpname): + """Parse a sectioned setup file. + + The sections in setup file contains a title line at the top, + indicated by a name in square brackets (`[]'), plus key/value + options lines, indicated by `name: value' format lines. + Continuations are represented by an embedded newline then + leading whitespace. Blank lines, lines beginning with a '#', + and just about everything else are ignored. + """ + cursect = None # None, or a dictionary + optname = None + lineno = 0 + e = None # None, or an exception + while True: + line = fp.readline() + if not line: + break + lineno = lineno + 1 + # comment or blank line? + if line.strip() == '' or line[0] in '#;': + self._sections.add_other(cursect, line) + continue + if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR": + # no leading whitespace + continue + # continuation line? + if line[0].isspace() and cursect is not None and optname: + value = line.strip() + if value: + #cursect[optname] = "%s\n%s" % (cursect[optname], value) + #self.set(cursect, optname, "%s\n%s" % (self.get(cursect, optname), value)) + if cursect == configparser.DEFAULTSECT: + self._defaults[optname] = "%s\n%s" % (self._defaults[optname], value) + else: + # use the raw value here (original version uses raw=False) + self._sections[cursect]._find(optname).value = '%s\n%s' % (self.get(cursect, optname, raw=True), value) + # a section header or option header? + else: + # is it a section header? + mo = self.SECTCRE.match(line) + if mo: + sectname = mo.group('header') + if sectname in self._sections: + cursect = self._sections[sectname] + elif sectname == configparser.DEFAULTSECT: + cursect = self._defaults + else: + #cursect = {'__name__': sectname} + #self._sections[sectname] = cursect + self.add_section(sectname) + self.set(sectname, '__name__', sectname) + # So sections can't start with a continuation line + cursect = sectname + optname = None + # no section header in the file? + elif cursect is None: + raise configparser.MissingSectionHeaderError(fpname, lineno, line) + # an option line? + else: + mo = self.OPTCRE.match(line) + if mo: + optname, vi, optval = mo.group('option', 'vi', 'value') + if vi in ('=', ':') and ';' in optval: + # ';' is a comment delimiter only if it follows + # a spacing character + pos = optval.find(';') + if pos != -1 and optval[pos-1].isspace(): + optval = optval[:pos] + optval = optval.strip() + # allow empty values + if optval == '""': + optval = '' + optname = self.optionxform(optname.rstrip()) + if cursect == configparser.DEFAULTSECT: + self._defaults[optname] = optval + else: + self._sections[cursect]._add_option(optname, line=line) + else: + # a non-fatal parsing error occurred. set up the + # exception but keep going. the exception will be + # raised at the end of the file and will contain a + # list of all bogus lines + if not e: + e = configparser.ParsingError(fpname) + e.append(lineno, repr(line)) + # if any parsing errors occurred, raise an exception + if e: + raise e # pylint: disable-msg=E0702 + + def write(self, fp, comments = False): + """ + write the configuration file. If comments is True all comments etc. + will be written to fp otherwise the ConfigParsers' default write method + will be called. + """ + if comments: + fp.write(str(self)) + fp.write('\n') + else: + configparser.SafeConfigParser.write(self, fp) + + def has_option(self, section, option, proper=False, **kwargs): + """ + Returns True, if the passed section contains the specified option. + If proper is True, True is only returned if the option is owned by + this section and not "inherited" from the default. + """ + if proper: + return self.optionxform(option) in self._sections[section].keys() + return configparser.SafeConfigParser.has_option(self, section, option, **kwargs) + + # XXX: simplify! + def __str__(self): + ret = [] + first = True + for line in self._sections._lines: + if line.type == 'section': + if first: + first = False + else: + ret.append('') + ret.append('[%s]' % line.name) + for sline in line._lines: + if sline.name == '__name__': + continue + if sline.type == 'option': + # special handling for continuation lines + val = '\n '.join(sline.value.split('\n')) + ret.append(sline.frmt % (sline.name, val)) + elif str(sline) != '': + ret.append(str(sline)) + else: + ret.append(str(line)) + return '\n'.join(ret) + +# vim: sw=4 et diff --git a/osc/__init__.py b/osc/__init__.py new file mode 100644 index 0000000..7872a4e --- /dev/null +++ b/osc/__init__.py @@ -0,0 +1,3 @@ +__all__ = ['babysitter', 'core', 'commandline', 'oscerr', 'othermethods', 'build', 'fetch', 'meter'] + +# vim: sw=4 et diff --git a/osc/babysitter.py b/osc/babysitter.py new file mode 100644 index 0000000..682de94 --- /dev/null +++ b/osc/babysitter.py @@ -0,0 +1,212 @@ +# Copyright (C) 2008 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +from __future__ import print_function + +import errno +import os.path +import pdb +import sys +import signal +import traceback +from urlgrabber.grabber import URLGrabError + +from osc import oscerr +from .oscsslexcp import NoSecureSSLError +from osc.util.cpio import CpioError +from osc.util.packagequery import PackageError + +try: + from M2Crypto.SSL.Checker import SSLVerificationError + from M2Crypto.SSL import SSLError as SSLError +except: + SSLError = None + SSLVerificationError = None + +try: + # import as RPMError because the class "error" is too generic + from rpm import error as RPMError +except: + # if rpm-python isn't installed (we might be on a debian system): + RPMError = None + +try: + from http.client import HTTPException, BadStatusLine + from urllib.error import URLError, HTTPError +except ImportError: + #python 2.x + from httplib import HTTPException, BadStatusLine + from urllib2 import URLError, HTTPError + +# the good things are stolen from Matt Mackall's mercurial + + +def catchterm(*args): + raise oscerr.SignalInterrupt + +for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': + num = getattr(signal, name, None) + if num: + signal.signal(num, catchterm) + + +def run(prg, argv=None): + try: + try: + if '--debugger' in sys.argv: + pdb.set_trace() + # here we actually run the program: + return prg.main(argv) + except: + # look for an option in the prg.options object and in the config + # dict print stack trace, if desired + if getattr(prg.options, 'traceback', None) or getattr(prg.conf, 'config', {}).get('traceback', None) or \ + getattr(prg.options, 'post_mortem', None) or getattr(prg.conf, 'config', {}).get('post_mortem', None): + traceback.print_exc(file=sys.stderr) + # we could use http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215 + # enter the debugger, if desired + if getattr(prg.options, 'post_mortem', None) or getattr(prg.conf, 'config', {}).get('post_mortem', None): + if sys.stdout.isatty() and not hasattr(sys, 'ps1'): + pdb.post_mortem(sys.exc_info()[2]) + else: + print('sys.stdout is not a tty. Not jumping into pdb.', file=sys.stderr) + raise + except oscerr.SignalInterrupt: + print('killed!', file=sys.stderr) + return 1 + except KeyboardInterrupt: + print('interrupted!', file=sys.stderr) + return 1 + except oscerr.UserAbort: + print('aborted.', file=sys.stderr) + return 1 + except oscerr.APIError as e: + print('BuildService API error:', e.msg, file=sys.stderr) + return 1 + except oscerr.LinkExpandError as e: + print('Link "%s/%s" cannot be expanded:\n' % (e.prj, e.pac), e.msg, file=sys.stderr) + print('Use "osc repairlink" to fix merge conflicts.\n', file=sys.stderr) + return 1 + except oscerr.WorkingCopyWrongVersion as e: + print(e, file=sys.stderr) + return 1 + except oscerr.NoWorkingCopy as e: + print(e, file=sys.stderr) + if os.path.isdir('.git'): + print("Current directory looks like git.", file=sys.stderr) + if os.path.isdir('.hg'): + print("Current directory looks like mercurial.", file=sys.stderr) + if os.path.isdir('.svn'): + print("Current directory looks like svn.", file=sys.stderr) + if os.path.isdir('CVS'): + print("Current directory looks like cvs.", file=sys.stderr) + return 1 + except HTTPError as e: + print('Server returned an error:', e, file=sys.stderr) + if hasattr(e, 'osc_msg'): + print(e.osc_msg, file=sys.stderr) + + try: + body = e.read() + except AttributeError: + body = '' + + if getattr(prg.options, 'debug', None) or \ + getattr(prg.conf, 'config', {}).get('debug', None): + print(e.hdrs, file=sys.stderr) + print(body, file=sys.stderr) + + if e.code in [400, 403, 404, 500]: + if '<summary>' in body: + msg = body.split('<summary>')[1] + msg = msg.split('</summary>')[0] + print(msg, file=sys.stderr) + if e.code >= 500 and e.code <= 599: + print('\nRequest: %s' % e.filename) + print('Headers:') + for h, v in e.hdrs.items(): + if h != 'Set-Cookie': + print("%s: %s" % (h, v)) + + return 1 + except BadStatusLine as e: + print('Server returned an invalid response:', e, file=sys.stderr) + print(e.line, file=sys.stderr) + return 1 + except HTTPException as e: + print(e, file=sys.stderr) + return 1 + except URLError as e: + print('Failed to reach a server:\n', e.reason, file=sys.stderr) + return 1 + except URLGrabError as e: + print('Failed to grab %s: %s' % (e.url, e.exception), file=sys.stderr) + return 1 + except IOError as e: + # ignore broken pipe + if e.errno != errno.EPIPE: + raise + return 1 + except OSError as e: + if e.errno != errno.ENOENT: + raise + print(e, file=sys.stderr) + return 1 + except (oscerr.ConfigError, oscerr.NoConfigfile) as e: + print(e.msg, file=sys.stderr) + return 1 + except oscerr.OscIOError as e: + print(e.msg, file=sys.stderr) + if getattr(prg.options, 'debug', None) or \ + getattr(prg.conf, 'config', {}).get('debug', None): + print(e.e, file=sys.stderr) + return 1 + except (oscerr.WrongOptions, oscerr.WrongArgs) as e: + print(e, file=sys.stderr) + return 2 + except oscerr.ExtRuntimeError as e: + print(e.file + ':', e.msg, file=sys.stderr) + return 1 + except oscerr.ServiceRuntimeError as e: + print(e.msg, file=sys.stderr) + except oscerr.WorkingCopyOutdated as e: + print(e, file=sys.stderr) + return 1 + except (oscerr.PackageExists, oscerr.PackageMissing, oscerr.WorkingCopyInconsistent) as e: + print(e.msg, file=sys.stderr) + return 1 + except oscerr.PackageInternalError as e: + print('a package internal error occured\n' \ + 'please file a bug and attach your current package working copy ' \ + 'and the following traceback to it:', file=sys.stderr) + print(e.msg, file=sys.stderr) + traceback.print_exc(file=sys.stderr) + return 1 + except oscerr.PackageError as e: + print(e.msg, file=sys.stderr) + return 1 + except PackageError as e: + print('%s:' % e.fname, e.msg, file=sys.stderr) + return 1 + except RPMError as e: + print(e, file=sys.stderr) + return 1 + except SSLError as e: + print("SSL Error:", e, file=sys.stderr) + return 1 + except SSLVerificationError as e: + print("Certificate Verification Error:", e, file=sys.stderr) + return 1 + except NoSecureSSLError as e: + print(e, file=sys.stderr) + return 1 + except CpioError as e: + print(e, file=sys.stderr) + return 1 + except oscerr.OscBaseError as e: + print('*** Error:', e, file=sys.stderr) + return 1 + +# vim: sw=4 et diff --git a/osc/build.py b/osc/build.py new file mode 100644 index 0000000..395ad12 --- /dev/null +++ b/osc/build.py @@ -0,0 +1,1110 @@ +# Copyright (C) 2006 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +from __future__ import print_function + +import os +import re +import sys +import shutil + +try: + from urllib.parse import urlsplit + from urllib.request import URLError, HTTPError +except ImportError: + #python 2.x + from urlparse import urlsplit + from urllib2 import URLError, HTTPError + +from tempfile import NamedTemporaryFile, mkdtemp +from osc.fetch import * +from osc.core import get_buildinfo, store_read_apiurl, store_read_project, store_read_package, meta_exists, quote_plus, get_buildconfig, is_package_dir +from osc.core import get_binarylist, get_binary_file, run_external, raw_input +from osc.util import rpmquery, debquery, archquery +import osc.conf +from . import oscerr +import subprocess +try: + from xml.etree import cElementTree as ET +except ImportError: + import cElementTree as ET + +from .conf import config, cookiejar + +change_personality = { + 'i686': 'linux32', + 'i586': 'linux32', + 'i386': 'linux32', + 'ppc': 'powerpc32', + 's390': 's390', + 'sparc': 'linux32', + 'sparcv8': 'linux32', + } + +# FIXME: qemu_can_build should not be needed anymore since OBS 2.3 +qemu_can_build = [ 'armv4l', 'armv5el', 'armv5l', 'armv6l', 'armv7l', 'armv6el', 'armv6hl', 'armv7el', 'armv7hl', 'armv8el', + 'sh4', 'mips', 'mipsel', + 'ppc', 'ppc64', + 's390', 's390x', + 'sparc64v', 'sparcv9v', 'sparcv9', 'sparcv8', 'sparc', + 'hppa', + ] + +can_also_build = { + 'aarch64': ['aarch64'], # only needed due to used heuristics in build parameter evaluation + 'armv6l': [ 'armv4l', 'armv5l', 'armv6l', 'armv5el', 'armv6el' ], + 'armv7l': [ 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el' ], + 'armv5el': [ 'armv4l', 'armv5l', 'armv5el' ], # not existing arch, just for compatibility + 'armv6el': [ 'armv4l', 'armv5l', 'armv6l', 'armv5el', 'armv6el' ], # not existing arch, just for compatibility + 'armv6hl': [ 'armv4l', 'armv5l', 'armv6l', 'armv5el', 'armv6el' ], + 'armv7el': [ 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el' ], # not existing arch, just for compatibility + 'armv7hl': [ 'armv7hl' ], # not existing arch, just for compatibility + 'armv8el': [ 'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv8el' ], # not existing arch, just for compatibility + 'armv8l': [ 'armv4l', 'armv5el', 'armv6el', 'armv7el', 'armv8el' ], # not existing arch, just for compatibility + 'armv5tel': [ 'armv4l', 'armv5el', 'armv5tel' ], + 's390x': ['s390' ], + 'ppc64': [ 'ppc', 'ppc64', 'ppc64p7', 'ppc64le' ], + 'ppc64le': [ 'ppc64le', 'ppc64' ], + 'i586': [ 'i386' ], + 'i686': [ 'i586', 'i386' ], + 'x86_64': ['i686', 'i586', 'i386' ], + 'sparc64': ['sparc64v', 'sparcv9v', 'sparcv9', 'sparcv8', 'sparc'], + 'parisc': ['hppa'], + } + +# real arch of this machine +hostarch = os.uname()[4] +if hostarch == 'i686': # FIXME + hostarch = 'i586' + +if hostarch == 'parisc': + hostarch = 'hppa' + +class Buildinfo: + """represent the contents of a buildinfo file""" + + def __init__(self, filename, apiurl, buildtype = 'spec', localpkgs = []): + try: + tree = ET.parse(filename) + except: + print('could not parse the buildinfo:', file=sys.stderr) + print(open(filename).read(), file=sys.stderr) + sys.exit(1) + + root = tree.getroot() + + self.apiurl = apiurl + + if root.find('error') != None: + sys.stderr.write('buildinfo is broken... it says:\n') + error = root.find('error').text + if error.startswith('unresolvable: '): + sys.stderr.write('unresolvable: ') + sys.stderr.write('\n '.join(error[14:].split(','))) + else: + sys.stderr.write(error) + sys.stderr.write('\n') + sys.exit(1) + + if not (apiurl.startswith('https://') or apiurl.startswith('http://')): + raise URLError('invalid protocol for the apiurl: \'%s\'' % apiurl) + + self.buildtype = buildtype + self.apiurl = apiurl + + # are we building .rpm or .deb? + # XXX: shouldn't we deliver the type via the buildinfo? + self.pacsuffix = 'rpm' + if self.buildtype == 'dsc' or self.buildtype == 'collax': + self.pacsuffix = 'deb' + if self.buildtype == 'arch': + self.pacsuffix = 'arch' + if self.buildtype == 'livebuild': + self.pacsuffix = 'deb' + + self.buildarch = root.find('arch').text + if root.find('hostarch') != None: + self.hostarch = root.find('hostarch').text + else: + self.hostarch = None + if root.find('release') != None: + self.release = root.find('release').text + else: + self.release = None + self.downloadurl = root.get('downloadurl') + self.debuginfo = 0 + if root.find('debuginfo') != None: + try: + self.debuginfo = int(root.find('debuginfo').text) + except ValueError: + pass + + self.deps = [] + self.projects = {} + self.keys = [] + self.prjkeys = [] + self.pathes = [] + for node in root.findall('bdep'): + p = Pac(node, self.buildarch, self.pacsuffix, + apiurl, localpkgs) + if p.project: + self.projects[p.project] = 1 + self.deps.append(p) + for node in root.findall('path'): + self.pathes.append(node.get('project')+"/"+node.get('repository')) + + self.vminstall_list = [ dep.name for dep in self.deps if dep.vminstall ] + self.preinstall_list = [ dep.name for dep in self.deps if dep.preinstall ] + self.runscripts_list = [ dep.name for dep in self.deps if dep.runscripts ] + self.noinstall_list = [ dep.name for dep in self.deps if dep.noinstall ] + self.installonly_list = [ dep.name for dep in self.deps if dep.installonly ] + + + def has_dep(self, name): + for i in self.deps: + if i.name == name: + return True + return False + + def remove_dep(self, name): + # we need to iterate over all deps because if this a + # kiwi build the same package might appear multiple times + # NOTE: do not loop and remove items, the second same one would not get catched + self.deps = [i for i in self.deps if not i.name == name] + + +class Pac: + """represent a package to be downloaded + + We build a map that's later used to fill our URL templates + """ + def __init__(self, node, buildarch, pacsuffix, apiurl, localpkgs = []): + + self.mp = {} + for i in ['binary', 'package', + 'epoch', 'version', 'release', 'hdrmd5', + 'project', 'repository', + 'preinstall', 'vminstall', 'noinstall', 'installonly', 'runscripts', + ]: + self.mp[i] = node.get(i) + + self.mp['buildarch'] = buildarch + self.mp['pacsuffix'] = pacsuffix + + self.mp['arch'] = node.get('arch') or self.mp['buildarch'] + self.mp['name'] = node.get('name') or self.mp['binary'] + + # this is not the ideal place to check if the package is a localdep or not + localdep = self.mp['name'] in localpkgs # and not self.mp['noinstall'] + if not localdep and not (node.get('project') and node.get('repository')): + raise oscerr.APIError('incomplete information for package %s, may be caused by a broken project configuration.' + % self.mp['name'] ) + + if not localdep: + self.mp['extproject'] = node.get('project').replace(':', ':/') + self.mp['extrepository'] = node.get('repository').replace(':', ':/') + self.mp['repopackage'] = node.get('package') or '_repository' + self.mp['repoarch'] = node.get('repoarch') or self.mp['buildarch'] + + if pacsuffix == 'deb' and not (self.mp['name'] and self.mp['arch'] and self.mp['version']): + raise oscerr.APIError( + "buildinfo for package %s/%s/%s is incomplete" + % (self.mp['name'], self.mp['arch'], self.mp['version'])) + + self.mp['apiurl'] = apiurl + + if pacsuffix == 'deb': + canonname = debquery.DebQuery.filename(self.mp['name'], self.mp['epoch'], self.mp['version'], self.mp['release'], self.mp['arch']) + elif pacsuffix == 'arch': + canonname = archquery.ArchQuery.filename(self.mp['name'], self.mp['epoch'], self.mp['version'], self.mp['release'], self.mp['arch']) + else: + canonname = rpmquery.RpmQuery.filename(self.mp['name'], self.mp['epoch'], self.mp['version'], self.mp['release'], self.mp['arch']) + + self.mp['canonname'] = canonname + # maybe we should rename filename key to binary + self.mp['filename'] = node.get('binary') or canonname + if self.mp['repopackage'] == '_repository': + self.mp['repofilename'] = self.mp['name'] + else: + # OBS 2.3 puts binary into product bdeps (noinstall ones) + self.mp['repofilename'] = self.mp['filename'] + + # make the content of the dictionary accessible as class attributes + self.__dict__.update(self.mp) + + + def makeurls(self, cachedir, urllist): + + self.urllist = [] + + # build up local URL + # by using the urlgrabber with local urls, we basically build up a cache. + # the cache has no validation, since the package servers don't support etags, + # or if-modified-since, so the caching is simply name-based (on the assumption + # that the filename is suitable as identifier) + self.localdir = '%s/%s/%s/%s' % (cachedir, self.project, self.repository, self.arch) + self.fullfilename = os.path.join(self.localdir, self.canonname) + self.url_local = 'file://%s' % self.fullfilename + + # first, add the local URL + self.urllist.append(self.url_local) + + # remote URLs + for url in urllist: + self.urllist.append(url % self.mp) + + def __str__(self): + return self.name + + def __repr__(self): + return "%s" % self.name + + + +def get_built_files(pacdir, buildtype): + if buildtype == 'spec': + b_built = subprocess.Popen(['find', os.path.join(pacdir, 'RPMS'), + '-name', '*.rpm'], + stdout=subprocess.PIPE).stdout.read().strip() + s_built = subprocess.Popen(['find', os.path.join(pacdir, 'SRPMS'), + '-name', '*.rpm'], + stdout=subprocess.PIPE).stdout.read().strip() + elif buildtype == 'kiwi': + b_built = subprocess.Popen(['find', os.path.join(pacdir, 'KIWI'), + '-type', 'f'], + stdout=subprocess.PIPE).stdout.read().strip() + s_built = '' + elif buildtype == 'dsc' or buildtype == 'collax': + b_built = subprocess.Popen(['find', os.path.join(pacdir, 'DEBS'), + '-name', '*.deb'], + stdout=subprocess.PIPE).stdout.read().strip() + s_built = subprocess.Popen(['find', os.path.join(pacdir, 'SOURCES.DEB'), + '-type', 'f'], + stdout=subprocess.PIPE).stdout.read().strip() + elif buildtype == 'arch': + b_built = subprocess.Popen(['find', os.path.join(pacdir, 'ARCHPKGS'), + '-name', '*.pkg.tar*'], + stdout=subprocess.PIPE).stdout.read().strip() + s_built = '' + elif buildtype == 'livebuild': + b_built = subprocess.Popen(['find', os.path.join(pacdir, 'OTHER'), + '-name', '*.iso*'], + stdout=subprocess.PIPE).stdout.read().strip() + s_built = '' + else: + print('WARNING: Unknown package type \'%s\'.' % buildtype, file=sys.stderr) + b_built = '' + s_built = '' + return s_built, b_built + +def get_repo(path): + """Walks up path looking for any repodata directories. + + @param path path to a directory + @return str path to repository directory containing repodata directory + """ + oldDirectory = None + currentDirectory = os.path.abspath(path) + repositoryDirectory = None + + # while there are still parent directories + while currentDirectory != oldDirectory: + children = os.listdir(currentDirectory) + + if "repodata" in children: + repositoryDirectory = currentDirectory + break + + # ascend + oldDirectory = currentDirectory + currentDirectory = os.path.abspath(os.path.join(oldDirectory, + os.pardir)) + + return repositoryDirectory + +def get_prefer_pkgs(dirs, wanted_arch, type, cpio): + import glob + from .util import repodata, packagequery + paths = [] + repositories = [] + + suffix = '*.rpm' + if type == 'dsc' or type == 'collax' or type == 'livebuild': + suffix = '*.deb' + elif type == 'arch': + suffix = '*.pkg.tar.xz' + + for dir in dirs: + # check for repodata + repository = get_repo(dir) + if repository is None: + paths += glob.glob(os.path.join(os.path.abspath(dir), suffix)) + else: + repositories.append(repository) + + packageQueries = packagequery.PackageQueries(wanted_arch) + + for repository in repositories: + repodataPackageQueries = repodata.queries(repository) + + for packageQuery in repodataPackageQueries: + packageQueries.add(packageQuery) + + for path in paths: + if path.endswith('.src.rpm') or path.endswith('.nosrc.rpm'): + continue + if path.endswith('.patch.rpm') or path.endswith('.delta.rpm'): + continue + if path.find('-debuginfo-') > 0: + continue + packageQuery = packagequery.PackageQuery.query(path) + packageQueries.add(packageQuery) + + prefer_pkgs = dict((name, packageQuery.path()) + for name, packageQuery in packageQueries.items()) + + depfile = create_deps(packageQueries.values()) + cpio.add('deps', '\n'.join(depfile)) + return prefer_pkgs + + +def create_deps(pkgqs): + """ + creates a list of dependencies which corresponds to build's internal + dependency file format + """ + depfile = [] + for p in pkgqs: + id = '%s.%s-0/0/0: ' % (p.name(), p.arch()) + depfile.append('P:%s%s' % (id, ' '.join(p.provides()))) + depfile.append('R:%s%s' % (id, ' '.join(p.requires()))) + d = p.conflicts() + if d: + depfile.append('C:%s%s' % (id, ' '.join(d))) + d = p.obsoletes() + if d: + depfile.append('O:%s%s' % (id, ' '.join(d))) + depfile.append('I:%s%s-%s 0-%s' % (id, p.name(), p.evr(), p.arch())) + return depfile + + +trustprompt = """Would you like to ... +0 - quit (default) +1 - always trust packages from '%(project)s' +2 - trust packages just this time +? """ +def check_trusted_projects(apiurl, projects): + trusted = config['api_host_options'][apiurl]['trusted_prj'] + tlen = len(trusted) + for prj in projects: + if not prj in trusted: + print("\nThe build root needs packages from project '%s'." % prj) + print("Note that malicious packages can compromise the build result or even your system.") + r = raw_input(trustprompt % { 'project': prj }) + if r == '1': + print("adding '%s' to ~/.oscrc: ['%s']['trusted_prj']" % (prj, apiurl)) + trusted.append(prj) + elif r != '2': + print("Well, good good bye then :-)") + raise oscerr.UserAbort() + + if tlen != len(trusted): + config['api_host_options'][apiurl]['trusted_prj'] = trusted + conf.config_set_option(apiurl, 'trusted_prj', ' '.join(trusted)) + +def main(apiurl, opts, argv): + + repo = argv[0] + arch = argv[1] + build_descr = argv[2] + xp = [] + build_root = None + cache_dir = None + build_uid = '' + vm_type = config['build-type'] + vm_telnet = None + + build_descr = os.path.abspath(build_descr) + build_type = os.path.splitext(build_descr)[1][1:] + if os.path.basename(build_descr) == 'PKGBUILD': + build_type = 'arch' + if os.path.basename(build_descr) == 'build.collax': + build_type = 'collax' + if build_type not in ['spec', 'dsc', 'kiwi', 'arch', 'collax', 'livebuild']: + raise oscerr.WrongArgs( + 'Unknown build type: \'%s\'. Build description should end in .spec, .dsc, .kiwi or .livebuild.' \ + % build_type) + if not os.path.isfile(build_descr): + raise oscerr.WrongArgs('Error: build description file named \'%s\' does not exist.' % build_descr) + + buildargs = [] + if not opts.userootforbuild: + buildargs.append('--norootforbuild') + if opts.clean: + buildargs.append('--clean') + if opts.noinit: + buildargs.append('--noinit') + if opts.nochecks: + buildargs.append('--no-checks') + if not opts.no_changelog: + buildargs.append('--changelog') + if opts.root: + build_root = opts.root + if opts.target: + buildargs.append('--target=%s' % opts.target) + if opts.threads: + buildargs.append('--threads=%s' % opts.threads) + if opts.jobs: + buildargs.append('--jobs=%s' % opts.jobs) + elif config['build-jobs'] > 1: + buildargs.append('--jobs=%s' % config['build-jobs']) + if opts.icecream or config['icecream'] != '0': + if opts.icecream: + num = opts.icecream + else: + num = config['icecream'] + + if int(num) > 0: + buildargs.append('--icecream=%s' % num) + xp.append('icecream') + xp.append('gcc-c++') + if opts.ccache: + buildargs.append('--ccache') + xp.append('ccache') + if opts.linksources: + buildargs.append('--linksources') + if opts.baselibs: + buildargs.append('--baselibs') + if opts.debuginfo: + buildargs.append('--debug') + if opts._with: + for o in opts._with: + buildargs.append('--with=%s' % o) + if opts.without: + for o in opts.without: + buildargs.append('--without=%s' % o) + if opts.define: + for o in opts.define: + buildargs.append('--define=%s' % o) + if config['build-uid']: + build_uid = config['build-uid'] + if opts.build_uid: + build_uid = opts.build_uid + if build_uid: + buildidre = re.compile('^[0-9]{1,5}:[0-9]{1,5}$') + if build_uid == 'caller': + buildargs.append('--uid=%s:%s' % (os.getuid(), os.getgid())) + elif buildidre.match(build_uid): + buildargs.append('--uid=%s' % build_uid) + else: + print('Error: build-uid arg must be 2 colon separated numerics: "uid:gid" or "caller"', file=sys.stderr) + return 1 + if opts.vm_type: + vm_type = opts.vm_type + if opts.vm_telnet: + vm_telnet = opts.vm_telnet + if opts.alternative_project: + prj = opts.alternative_project + pac = '_repository' + else: + prj = store_read_project(os.curdir) + if opts.local_package: + pac = '_repository' + else: + pac = store_read_package(os.curdir) + if opts.shell: + buildargs.append("--shell") + + orig_build_root = config['build-root'] + # make it possible to override configuration of the rc file + for var in ['OSC_PACKAGECACHEDIR', 'OSC_SU_WRAPPER', 'OSC_BUILD_ROOT']: + val = os.getenv(var) + if val: + if var.startswith('OSC_'): var = var[4:] + var = var.lower().replace('_', '-') + if var in config: + print('Overriding config value for %s=\'%s\' with \'%s\'' % (var, config[var], val)) + config[var] = val + + pacname = pac + if pacname == '_repository': + if not opts.local_package: + try: + pacname = store_read_package(os.curdir) + except oscerr.NoWorkingCopy: + opts.local_package = True + if opts.local_package: + pacname = os.path.splitext(build_descr)[0] + apihost = urlsplit(apiurl)[1] + if not build_root: + build_root = config['build-root'] + if build_root == orig_build_root: + # ENV var was not set + build_root = config['api_host_options'][apiurl].get('build-root', build_root) + try: + build_root = build_root % {'repo': repo, 'arch': arch, + 'project': prj, 'package': pacname, 'apihost': apihost} + except: + pass + + cache_dir = config['packagecachedir'] % {'apihost': apihost} + + extra_pkgs = [] + if not opts.extra_pkgs: + extra_pkgs = config['extra-pkgs'] + elif opts.extra_pkgs != ['']: + extra_pkgs = opts.extra_pkgs + + if xp: + extra_pkgs += xp + + prefer_pkgs = {} + build_descr_data = open(build_descr).read() + + # XXX: dirty hack but there's no api to provide custom defines + if opts.without: + s = '' + for i in opts.without: + s += "%%define _without_%s 1\n" % i + build_descr_data = s + build_descr_data + if opts._with: + s = '' + for i in opts._with: + s += "%%define _with_%s 1\n" % i + build_descr_data = s + build_descr_data + if opts.define: + s = '' + for i in opts.define: + s += "%%define %s\n" % i + build_descr_data = s + build_descr_data + + cpiodata = None + servicefile = os.path.join(os.path.dirname(build_descr), "_service") + if not os.path.isfile(servicefile): + servicefile = os.path.join(os.path.dirname(build_descr), "_service") + if not os.path.isfile(servicefile): + servicefile = None + else: + print('Using local _service file') + buildenvfile = os.path.join(os.path.dirname(build_descr), "_buildenv." + repo + "." + arch) + if not os.path.isfile(buildenvfile): + buildenvfile = os.path.join(os.path.dirname(build_descr), "_buildenv") + if not os.path.isfile(buildenvfile): + buildenvfile = None + else: + print('Using local buildenv file: %s' % os.path.basename(buildenvfile)) + if buildenvfile or servicefile: + from .util import cpio + if not cpiodata: + cpiodata = cpio.CpioWrite() + + if opts.prefer_pkgs: + print('Scanning the following dirs for local packages: %s' % ', '.join(opts.prefer_pkgs)) + from .util import cpio + if not cpiodata: + cpiodata = cpio.CpioWrite() + prefer_pkgs = get_prefer_pkgs(opts.prefer_pkgs, arch, build_type, cpiodata) + + if cpiodata: + cpiodata.add(os.path.basename(build_descr), build_descr_data) + # buildenv must come last for compatibility reasons... + if buildenvfile: + cpiodata.add("buildenv", open(buildenvfile).read()) + if servicefile: + cpiodata.add("_service", open(servicefile).read()) + build_descr_data = cpiodata.get() + + # special handling for overlay and rsync-src/dest + specialcmdopts = [] + if opts.rsyncsrc or opts.rsyncdest : + if not opts.rsyncsrc or not opts.rsyncdest: + raise oscerr.WrongOptions('When using --rsync-{src,dest} both parameters have to be specified.') + myrsyncsrc = os.path.abspath(os.path.expanduser(os.path.expandvars(opts.rsyncsrc))) + if not os.path.isdir(myrsyncsrc): + raise oscerr.WrongOptions('--rsync-src %s is no valid directory!' % opts.rsyncsrc) + # can't check destination - its in the target chroot ;) - but we can check for sanity + myrsyncdest = os.path.expandvars(opts.rsyncdest) + if not os.path.isabs(myrsyncdest): + raise oscerr.WrongOptions('--rsync-dest %s is no absolute path (starting with \'/\')!' % opts.rsyncdest) + specialcmdopts = ['--rsync-src='+myrsyncsrc, '--rsync-dest='+myrsyncdest] + if opts.overlay: + myoverlay = os.path.abspath(os.path.expanduser(os.path.expandvars(opts.overlay))) + if not os.path.isdir(myoverlay): + raise oscerr.WrongOptions('--overlay %s is no valid directory!' % opts.overlay) + specialcmdopts += ['--overlay='+myoverlay] + + bi_file = None + bc_file = None + bi_filename = '_buildinfo-%s-%s.xml' % (repo, arch) + bc_filename = '_buildconfig-%s-%s' % (repo, arch) + if is_package_dir('.') and os.access(osc.core.store, os.W_OK): + bi_filename = os.path.join(os.getcwd(), osc.core.store, bi_filename) + bc_filename = os.path.join(os.getcwd(), osc.core.store, bc_filename) + elif not os.access('.', os.W_OK): + bi_file = NamedTemporaryFile(prefix=bi_filename) + bi_filename = bi_file.name + bc_file = NamedTemporaryFile(prefix=bc_filename) + bc_filename = bc_file.name + else: + bi_filename = os.path.abspath(bi_filename) + bc_filename = os.path.abspath(bc_filename) + + try: + if opts.noinit: + if not os.path.isfile(bi_filename): + raise oscerr.WrongOptions('--noinit is not possible, no local buildinfo file') + print('Use local \'%s\' file as buildinfo' % bi_filename) + if not os.path.isfile(bc_filename): + raise oscerr.WrongOptions('--noinit is not possible, no local buildconfig file') + print('Use local \'%s\' file as buildconfig' % bc_filename) + elif opts.offline: + if not os.path.isfile(bi_filename): + raise oscerr.WrongOptions('--offline is not possible, no local buildinfo file') + print('Use local \'%s\' file as buildinfo' % bi_filename) + if not os.path.isfile(bc_filename): + raise oscerr.WrongOptions('--offline is not possible, no local buildconfig file') + else: + print('Getting buildinfo from server and store to %s' % bi_filename) + bi_text = ''.join(get_buildinfo(apiurl, + prj, + pac, + repo, + arch, + specfile=build_descr_data, + addlist=extra_pkgs)) + if not bi_file: + bi_file = open(bi_filename, 'w') + # maybe we should check for errors before saving the file + bi_file.write(bi_text) + bi_file.flush() + print('Getting buildconfig from server and store to %s' % bc_filename) + bc = get_buildconfig(apiurl, prj, repo) + if not bc_file: + bc_file = open(bc_filename, 'w') + bc_file.write(bc) + bc_file.flush() + except HTTPError as e: + if e.code == 404: + # check what caused the 404 + if meta_exists(metatype='prj', path_args=(quote_plus(prj), ), + template_args=None, create_new=False, apiurl=apiurl): + pkg_meta_e = None + try: + # take care, not to run into double trouble. + pkg_meta_e = meta_exists(metatype='pkg', path_args=(quote_plus(prj), + quote_plus(pac)), template_args=None, create_new=False, + apiurl=apiurl) + except: + pass + + if pkg_meta_e: + print('ERROR: Either wrong repo/arch as parameter or a parse error of .spec/.dsc/.kiwi file due to syntax error', file=sys.stderr) + else: + print('The package \'%s\' does not exist - please ' \ + 'rerun with \'--local-package\'' % pac, file=sys.stderr) + else: + print('The project \'%s\' does not exist - please ' \ + 'rerun with \'--alternative-project <alternative_project>\'' % prj, file=sys.stderr) + sys.exit(1) + else: + raise + + bi = Buildinfo(bi_filename, apiurl, build_type, list(prefer_pkgs.keys())) + + if bi.debuginfo and not (opts.disable_debuginfo or '--debug' in buildargs): + buildargs.append('--debug') + + if opts.release: + bi.release = opts.release + + if bi.release: + buildargs.append('--release=%s' % bi.release) + + # real arch of this machine + # vs. + # arch we are supposed to build for + if bi.hostarch != None: + if hostarch != bi.hostarch and not bi.hostarch in can_also_build.get(hostarch, []): + print('Error: hostarch \'%s\' is required.' % (bi.hostarch), file=sys.stderr) + return 1 + elif hostarch != bi.buildarch: + if not bi.buildarch in can_also_build.get(hostarch, []): + # OBSOLETE: qemu_can_build should not be needed anymore since OBS 2.3 + if vm_type != "emulator" and not bi.buildarch in qemu_can_build: + print('Error: hostarch \'%s\' cannot build \'%s\'.' % (hostarch, bi.buildarch), file=sys.stderr) + return 1 + print('WARNING: It is guessed to build on hostarch \'%s\' for \'%s\' via QEMU.' % (hostarch, bi.buildarch), file=sys.stderr) + + rpmlist_prefers = [] + if prefer_pkgs: + print('Evaluating preferred packages') + for name, path in prefer_pkgs.items(): + if bi.has_dep(name): + # We remove a preferred package from the buildinfo, so that the + # fetcher doesn't take care about them. + # Instead, we put it in a list which is appended to the rpmlist later. + # At the same time, this will make sure that these packages are + # not verified. + bi.remove_dep(name) + rpmlist_prefers.append((name, path)) + print(' - %s (%s)' % (name, path)) + + print('Updating cache of required packages') + + urllist = [] + if not opts.download_api_only: + # transform 'url1, url2, url3' form into a list + if 'urllist' in config: + if isinstance(config['urllist'], str): + re_clist = re.compile('[, ]+') + urllist = [ i.strip() for i in re_clist.split(config['urllist'].strip()) ] + else: + urllist = config['urllist'] + + # OBS 1.5 and before has no downloadurl defined in buildinfo + if bi.downloadurl: + urllist.append(bi.downloadurl + '/%(extproject)s/%(extrepository)s/%(arch)s/%(filename)s') + if opts.disable_cpio_bulk_download: + urllist.append( '%(apiurl)s/build/%(project)s/%(repository)s/%(repoarch)s/%(repopackage)s/%(repofilename)s' ) + + fetcher = Fetcher(cache_dir, + urllist = urllist, + api_host_options = config['api_host_options'], + offline = opts.noinit or opts.offline, + http_debug = config['http_debug'], + enable_cpio = not opts.disable_cpio_bulk_download, + cookiejar=cookiejar) + + if not opts.trust_all_projects: + # implicitly trust the project we are building for + check_trusted_projects(apiurl, [ i for i in bi.projects.keys() if not i == prj ]) + + # now update the package cache + fetcher.run(bi) + + old_pkg_dir = None + if opts.oldpackages: + old_pkg_dir = opts.oldpackages + if not old_pkg_dir.startswith('/') and not opts.offline: + data = [ prj, pacname, repo, arch] + if old_pkg_dir == '_link': + p = osc.core.findpacs(os.curdir)[0] + if not p.islink(): + raise oscerr.WrongOptions('package is not a link') + data[0] = p.linkinfo.project + data[1] = p.linkinfo.package + repos = osc.core.get_repositories_of_project(apiurl, data[0]) + # hack for links to e.g. Factory + if not data[2] in repos and 'standard' in repos: + data[2] = 'standard' + elif old_pkg_dir != '' and old_pkg_dir != '_self': + a = old_pkg_dir.split('/') + for i in range(0, len(a)): + data[i] = a[i] + + destdir = os.path.join(cache_dir, data[0], data[2], data[3]) + old_pkg_dir = None + try: + print("Downloading previous build from %s ..." % '/'.join(data)) + binaries = get_binarylist(apiurl, data[0], data[2], data[3], package=data[1], verbose=True) + except Exception as e: + print("Error: failed to get binaries: %s" % str(e)) + binaries = [] + + if binaries: + class mytmpdir: + """ temporary directory that removes itself""" + def __init__(self, *args, **kwargs): + self.name = mkdtemp(*args, **kwargs) + _rmtree = staticmethod(shutil.rmtree) + def cleanup(self): + self._rmtree(self.name) + def __del__(self): + self.cleanup() + def __exit__(self): + self.cleanup() + def __str__(self): + return self.name + + old_pkg_dir = mytmpdir(prefix='.build.oldpackages', dir=os.path.abspath(os.curdir)) + if not os.path.exists(destdir): + os.makedirs(destdir) + for i in binaries: + fname = os.path.join(destdir, i.name) + os.symlink(fname, os.path.join(str(old_pkg_dir), i.name)) + if os.path.exists(fname): + st = os.stat(fname) + if st.st_mtime == i.mtime and st.st_size == i.size: + continue + get_binary_file(apiurl, + data[0], + data[2], data[3], + i.name, + package = data[1], + target_filename = fname, + target_mtime = i.mtime, + progress_meter = True) + + if old_pkg_dir != None: + buildargs.append('--oldpackages=%s' % old_pkg_dir) + + # Make packages from buildinfo available as repos for kiwi + if build_type == 'kiwi': + if os.path.exists('repos'): + shutil.rmtree('repos') + os.mkdir('repos') + for i in bi.deps: + if not i.extproject: + # remove + bi.deps.remove(i) + continue + # project + pdir = str(i.extproject).replace(':/', ':') + # repo + rdir = str(i.extrepository).replace(':/', ':') + # arch + adir = i.repoarch + # project/repo + prdir = "repos/"+pdir+"/"+rdir + # project/repo/arch + pradir = prdir+"/"+adir + # source fullfilename + sffn = i.fullfilename + filename = sffn.split("/")[-1] + # target fullfilename + tffn = pradir+"/"+filename + if not os.path.exists(os.path.join(pradir)): + os.makedirs(os.path.join(pradir)) + if not os.path.exists(tffn): + print("Using package: "+sffn) + if opts.linksources: + os.link(sffn, tffn) + else: + os.symlink(sffn, tffn) + if prefer_pkgs: + for name, path in prefer_pkgs.items(): + if name == filename: + print("Using prefered package: " + path + "/" + filename) + os.unlink(tffn) + if opts.linksources: + os.link(path + "/" + filename, tffn) + else: + os.symlink(path + "/" + filename, tffn) + # Is a obsrepositories tag used? + try: + tree = ET.parse(build_descr) + except: + print('could not parse the kiwi file:', file=sys.stderr) + print(open(build_descr).read(), file=sys.stderr) + sys.exit(1) + root = tree.getroot() + # product + for xml in root.findall('instsource'): + if xml.find('instrepo').find('source').get('path') == 'obsrepositories:/': + print("obsrepositories:/ for product builds is not yet supported in osc!") + sys.exit(1) + # appliance + expand_obsrepos=None + for xml in root.findall('repository'): + if xml.find('source').get('path') == 'obsrepositories:/': + expand_obsrepos=True + if expand_obsrepos: + buildargs.append('--kiwi-parameter') + buildargs.append('--ignore-repos') + for xml in root.findall('repository'): + if xml.find('source').get('path') == 'obsrepositories:/': + for path in bi.pathes: + if not os.path.isdir("repos/"+path): + continue + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repo') + buildargs.append('--kiwi-parameter') + buildargs.append("repos/"+path) + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repotype') + buildargs.append('--kiwi-parameter') + buildargs.append('rpm-md') + if xml.get('priority'): + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repoprio='+xml.get('priority')) + else: + m = re.match(r"obs://[^/]+/([^/]+)/(\S+)", xml.find('source').get('path')) + if not m: + # short path without obs instance name + m = re.match(r"obs://([^/]+)/(.+)", xml.find('source').get('path')) + project=m.group(1).replace(":", ":/") + repo=m.group(2) + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repo') + buildargs.append('--kiwi-parameter') + buildargs.append("repos/"+project+"/"+repo) + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repotype') + buildargs.append('--kiwi-parameter') + buildargs.append('rpm-md') + if xml.get('priority'): + buildargs.append('--kiwi-parameter') + buildargs.append('--add-repopriority='+xml.get('priority')) + + if vm_type == "xen" or vm_type == "kvm" or vm_type == "lxc": + print('Skipping verification of package signatures due to secure VM build') + elif bi.pacsuffix == 'rpm': + if opts.no_verify: + print('Skipping verification of package signatures') + else: + print('Verifying integrity of cached packages') + verify_pacs(bi) + elif bi.pacsuffix == 'deb': + if opts.no_verify or opts.noinit: + print('Skipping verification of package signatures') + else: + print('WARNING: deb packages get not verified, they can compromise your system !') + else: + print('WARNING: unknown packages get not verified, they can compromise your system !') + + for i in bi.deps: + if i.hdrmd5: + from .util import packagequery + hdrmd5 = packagequery.PackageQuery.queryhdrmd5(i.fullfilename) + if not hdrmd5: + print("Error: cannot get hdrmd5 for %s" % i.fullfilename) + sys.exit(1) + if hdrmd5 != i.hdrmd5: + print("Error: hdrmd5 mismatch for %s: %s != %s" % (i.fullfilename, hdrmd5, i.hdrmd5)) + sys.exit(1) + + print('Writing build configuration') + + if build_type == 'kiwi': + rpmlist = [ '%s %s\n' % (i.name, i.fullfilename) for i in bi.deps if not i.noinstall ] + else: + rpmlist = [ '%s %s\n' % (i.name, i.fullfilename) for i in bi.deps ] + rpmlist += [ '%s %s\n' % (i[0], i[1]) for i in rpmlist_prefers ] + + rpmlist.append('preinstall: ' + ' '.join(bi.preinstall_list) + '\n') + rpmlist.append('vminstall: ' + ' '.join(bi.vminstall_list) + '\n') + rpmlist.append('runscripts: ' + ' '.join(bi.runscripts_list) + '\n') + if build_type != 'kiwi' and bi.noinstall_list: + rpmlist.append('noinstall: ' + ' '.join(bi.noinstall_list) + '\n') + if build_type != 'kiwi' and bi.installonly_list: + rpmlist.append('installonly: ' + ' '.join(bi.installonly_list) + '\n') + + rpmlist_file = NamedTemporaryFile(prefix='rpmlist.') + rpmlist_filename = rpmlist_file.name + rpmlist_file.writelines(rpmlist) + rpmlist_file.flush() + + subst = { 'repo': repo, 'arch': arch, 'project' : prj, 'package' : pacname } + vm_options = [] + # XXX check if build-device present + my_build_device = '' + if config['build-device']: + my_build_device = config['build-device'] % subst + else: + # obs worker uses /root here but that collides with the + # /root directory if the build root was used without vm + # before + my_build_device = build_root + '/img' + + need_root = True + if vm_type: + if config['build-swap']: + my_build_swap = config['build-swap'] % subst + else: + my_build_swap = build_root + '/swap' + + vm_options = [ '--vm-type=%s' % vm_type ] + if vm_telnet: + vm_options += [ '--vm-telnet=' + vm_telnet ] + if config['build-memory']: + vm_options += [ '--memory=' + config['build-memory'] ] + if vm_type != 'lxc': + vm_options += [ '--vm-disk=' + my_build_device ] + vm_options += [ '--vm-swap=' + my_build_swap ] + vm_options += [ '--logfile=%s/.build.log' % build_root ] + if vm_type == 'kvm': + if os.access(build_root, os.W_OK) and os.access('/dev/kvm', os.W_OK): + # so let's hope there's also an fstab entry + need_root = False + if config['build-kernel']: + vm_options += [ '--vm-kernel=' + config['build-kernel'] ] + if config['build-initrd']: + vm_options += [ '--vm-initrd=' + config['build-initrd'] ] + + build_root += '/.mount' + + if config['build-memory']: + vm_options += [ '--memory=' + config['build-memory'] ] + if config['build-vmdisk-rootsize']: + vm_options += [ '--vmdisk-rootsize=' + config['build-vmdisk-rootsize'] ] + if config['build-vmdisk-swapsize']: + vm_options += [ '--vmdisk-swapsize=' + config['build-vmdisk-swapsize'] ] + if config['build-vmdisk-filesystem']: + vm_options += [ '--vmdisk-filesystem=' + config['build-vmdisk-filesystem'] ] + if config['build-vm-user']: + vm_options += [ '--vm-user=' + config['build-vm-user'] ] + + + if opts.preload: + print("Preload done for selected repo/arch.") + sys.exit(0) + + print('Running build') + cmd = [ config['build-cmd'], '--root='+build_root, + '--rpmlist='+rpmlist_filename, + '--dist='+bc_filename, + '--arch='+bi.buildarch ] + cmd += specialcmdopts + vm_options + buildargs + cmd += [ build_descr ] + + if need_root: + sucmd = config['su-wrapper'].split() + if sucmd[0] == 'su': + if sucmd[-1] == '-c': + sucmd.pop() + cmd = sucmd + ['-s', cmd[0], 'root', '--' ] + cmd[1:] + else: + cmd = sucmd + cmd + + # change personality, if needed + if hostarch != bi.buildarch and bi.buildarch in change_personality: + cmd = [ change_personality[bi.buildarch] ] + cmd + + try: + rc = run_external(cmd[0], *cmd[1:]) + if rc: + print() + print('The buildroot was:', build_root) + sys.exit(rc) + except KeyboardInterrupt as i: + print("keyboard interrupt, killing build ...") + cmd.append('--kill') + run_external(cmd[0], *cmd[1:]) + raise i + + pacdir = os.path.join(build_root, '.build.packages') + if os.path.islink(pacdir): + pacdir = os.readlink(pacdir) + pacdir = os.path.join(build_root, pacdir) + + if os.path.exists(pacdir): + (s_built, b_built) = get_built_files(pacdir, bi.buildtype) + + print() + if s_built: print(s_built) + print() + print(b_built) + + if opts.keep_pkgs: + for i in b_built.splitlines() + s_built.splitlines(): + shutil.copy2(i, os.path.join(opts.keep_pkgs, os.path.basename(i))) + + if bi_file: + bi_file.close() + if bc_file: + bc_file.close() + rpmlist_file.close() + +# vim: sw=4 et diff --git a/osc/checker.py b/osc/checker.py new file mode 100644 index 0000000..f80a0da --- /dev/null +++ b/osc/checker.py @@ -0,0 +1,122 @@ +from __future__ import print_function + +from tempfile import mkdtemp +import os +from shutil import rmtree +import rpm +import base64 + +class KeyError(Exception): + def __init__(self, key, *args): + Exception.__init__(self) + self.args = args + self.key = key + def __str__(self): + return ''+self.key+' :'+' '.join(self.args) + +class Checker: + def __init__(self): + self.dbdir = mkdtemp(prefix='oscrpmdb') + self.imported = {} + rpm.addMacro('_dbpath', self.dbdir) + self.ts = rpm.TransactionSet() + self.ts.initDB() + self.ts.openDB() + self.ts.setVSFlags(0) + #self.ts.Debug(1) + + def readkeys(self, keys=[]): + rpm.addMacro('_dbpath', self.dbdir) + for key in keys: + try: + self.readkey(key) + except KeyError as e: + print(e) + + if not len(self.imported): + raise KeyError('', "no key imported") + + rpm.delMacro("_dbpath") + +# python is an idiot +# def __del__(self): +# self.cleanup() + + def cleanup(self): + self.ts.closeDB() + rmtree(self.dbdir) + + def readkey(self, file): + if file in self.imported: + return + + fd = open(file, "r") + line = fd.readline() + if line and line[0:14] == "-----BEGIN PGP": + line = fd.readline() + while line and line != "\n": + line = fd.readline() + if not line: + raise KeyError(file, "not a pgp public key") + else: + raise KeyError(file, "not a pgp public key") + + key = '' + line = fd.readline() + crc = None + while line: + if line[0:12] == "-----END PGP": + break + line = line.rstrip() + if (line[0] == '='): + crc = line[1:] + line = fd.readline() + break + else: + key += line + line = fd.readline() + fd.close() + if not line or line[0:12] != "-----END PGP": + raise KeyError(file, "not a pgp public key") + + # TODO: compute and compare CRC, see RFC 2440 + + bkey = base64.b64decode(key) + + r = self.ts.pgpImportPubkey(bkey) + if r != 0: + raise KeyError(file, "failed to import pubkey") + self.imported[file] = 1 + + def check(self, pkg): + # avoid errors on non rpm + if pkg[-4:] != '.rpm': + return + fd = None + try: + fd = os.open(pkg, os.O_RDONLY) + hdr = self.ts.hdrFromFdno(fd) + finally: + if fd is not None: + os.close(fd) + +if __name__ == "__main__": + import sys + keyfiles = [] + pkgs = [] + for arg in sys.argv[1:]: + if arg[-4:] == '.rpm': + pkgs.append(arg) + else: + keyfiles.append(arg) + + checker = Checker() + try: + checker.readkeys(keyfiles) + for pkg in pkgs: + checker.check(pkg) + except Exception as e: + checker.cleanup() + raise e + +# vim: sw=4 et diff --git a/osc/cmdln.py b/osc/cmdln.py new file mode 100644 index 0000000..da49ce6 --- /dev/null +++ b/osc/cmdln.py @@ -0,0 +1,1600 @@ +# Copyright (c) 2002-2005 ActiveState Corp. +# License: MIT (see LICENSE.txt for license details) +# Author: Trent Mick (TrentM@ActiveState.com) +# Home: http://trentm.com/projects/cmdln/ + +from __future__ import print_function + +"""An improvement on Python's standard cmd.py module. + +As with cmd.py, this module provides "a simple framework for writing +line-oriented command intepreters." This module provides a 'RawCmdln' +class that fixes some design flaws in cmd.Cmd, making it more scalable +and nicer to use for good 'cvs'- or 'svn'-style command line interfaces +or simple shells. And it provides a 'Cmdln' class that add +optparse-based option processing. Basically you use it like this: + + import cmdln + + class MySVN(cmdln.Cmdln): + name = "svn" + + @cmdln.alias('stat', 'st') + @cmdln.option('-v', '--verbose', action='store_true' + help='print verbose information') + def do_status(self, subcmd, opts, *paths): + print "handle 'svn status' command" + + #... + + if __name__ == "__main__": + shell = MySVN() + retval = shell.main() + sys.exit(retval) + +See the README.txt or <http://trentm.com/projects/cmdln/> for more +details. +""" + +__revision__ = "$Id: cmdln.py 1666 2007-05-09 03:13:03Z trentm $" +__version_info__ = (1, 0, 0) +__version__ = '.'.join(map(str, __version_info__)) + +import os +import re +import cmd +import optparse +import sys +from pprint import pprint +from datetime import date + +# this is python 2.x style +def introspect_handler_2(handler): + # Extract the introspection bits we need. + func = handler.im_func + if func.func_defaults: + func_defaults = func.func_defaults + else: + func_defaults = [] + return \ + func_defaults, \ + func.func_code.co_argcount, \ + func.func_code.co_varnames, \ + func.func_code.co_flags, \ + func + +def introspect_handler_3(handler): + defaults = handler.__defaults__ + if not defaults: + defaults = [] + else: + defaults = list(handler.__defaults__) + return \ + defaults, \ + handler.__code__.co_argcount, \ + handler.__code__.co_varnames, \ + handler.__code__.co_flags, \ + handler.__func__ + +if sys.version_info[0] == 2: + introspect_handler = introspect_handler_2 + bytes = lambda x, *args: x +else: + introspect_handler = introspect_handler_3 + + +#---- globals + +LOOP_ALWAYS, LOOP_NEVER, LOOP_IF_EMPTY = range(3) + +# An unspecified optional argument when None is a meaningful value. +_NOT_SPECIFIED = ("Not", "Specified") + +# Pattern to match a TypeError message from a call that +# failed because of incorrect number of arguments (see +# Python/getargs.c). +_INCORRECT_NUM_ARGS_RE = re.compile( + r"(takes [\w ]+ )(\d+)( arguments? \()(\d+)( given\))") + +# Static bits of man page +MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands" +.SH NAME +%(name)s \- Program to do useful things. +.SH SYNOPSIS +.B %(name)s +[\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...] +.br +.B %(name)s +\fIhelp SUBCOMMAND\fR +.SH DESCRIPTION +""" +MAN_COMMANDS_HEADER = r""" +.SS COMMANDS +""" +MAN_OPTIONS_HEADER = r""" +.SS GLOBAL OPTIONS +""" +MAN_FOOTER = r""" +.SH AUTHOR +This man page is automatically generated. +""" + +#---- exceptions + +class CmdlnError(Exception): + """A cmdln.py usage error.""" + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class CmdlnUserError(Exception): + """An error by a user of a cmdln-based tool/shell.""" + pass + + + +#---- public methods and classes + +def alias(*aliases): + """Decorator to add aliases for Cmdln.do_* command handlers. + + Example: + class MyShell(cmdln.Cmdln): + @cmdln.alias("!", "sh") + def do_shell(self, argv): + #...implement 'shell' command + """ + def decorate(f): + if not hasattr(f, "aliases"): + f.aliases = [] + f.aliases += aliases + return f + return decorate + +MAN_REPLACES = [ + (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5\-\6'), + (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4\-\5'), + (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3\-\4'), + (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3\-\4'), + (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2\-\3'), + (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,-]*)-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2\-\3'), + (re.compile(r'(^|[ \t\[\'\|])--([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\-\2'), + (re.compile(r'(^|[ \t\[\'\|])-([^/ \t/,\|-]*)(?=$|[ \t=\]\'/,\|])'), r'\1\-\2'), + (re.compile(r"^'"), r" '"), + ] + +def man_escape(text): + ''' + Escapes text to be included in man page. + + For now it only escapes dashes in command line options. + ''' + for repl in MAN_REPLACES: + text = repl[0].sub(repl[1], text) + return text + +class RawCmdln(cmd.Cmd): + """An improved (on cmd.Cmd) framework for building multi-subcommand + scripts (think "svn" & "cvs") and simple shells (think "pdb" and + "gdb"). + + A simple example: + + import cmdln + + class MySVN(cmdln.RawCmdln): + name = "svn" + + @cmdln.aliases('stat', 'st') + def do_status(self, argv): + print "handle 'svn status' command" + + if __name__ == "__main__": + shell = MySVN() + retval = shell.main() + sys.exit(retval) + + See <http://trentm.com/projects/cmdln> for more information. + """ + name = None # if unset, defaults basename(sys.argv[0]) + prompt = None # if unset, defaults to self.name+"> " + version = None # if set, default top-level options include --version + + # Default messages for some 'help' command error cases. + # They are interpolated with one arg: the command. + nohelp = "no help on '%s'" + unknowncmd = "unknown command: '%s'" + + helpindent = '' # string with which to indent help output + + # Default man page parts, please change them in subclass + man_header = MAN_HEADER + man_commands_header = MAN_COMMANDS_HEADER + man_options_header = MAN_OPTIONS_HEADER + man_footer = MAN_FOOTER + + def __init__(self, completekey='tab', + stdin=None, stdout=None, stderr=None): + """Cmdln(completekey='tab', stdin=None, stdout=None, stderr=None) + + The optional argument 'completekey' is the readline name of a + completion key; it defaults to the Tab key. If completekey is + not None and the readline module is available, command completion + is done automatically. + + The optional arguments 'stdin', 'stdout' and 'stderr' specify + alternate input, output and error output file objects; if not + specified, sys.* are used. + + If 'stdout' but not 'stderr' is specified, stdout is used for + error output. This is to provide least surprise for users used + to only the 'stdin' and 'stdout' options with cmd.Cmd. + """ + if self.name is None: + self.name = os.path.basename(sys.argv[0]) + if self.prompt is None: + self.prompt = self.name+"> " + self._name_str = self._str(self.name) + self._prompt_str = self._str(self.prompt) + if stdin is not None: + self.stdin = stdin + else: + self.stdin = sys.stdin + if stdout is not None: + self.stdout = stdout + else: + self.stdout = sys.stdout + if stderr is not None: + self.stderr = stderr + elif stdout is not None: + self.stderr = stdout + else: + self.stderr = sys.stderr + self.cmdqueue = [] + self.completekey = completekey + self.cmdlooping = False + + def get_optparser(self): + """Hook for subclasses to set the option parser for the + top-level command/shell. + + This option parser is used retrieved and used by `.main()' to + handle top-level options. + + The default implements a single '-h|--help' option. Sub-classes + can return None to have no options at the top-level. Typically + an instance of CmdlnOptionParser should be returned. + """ + version = (self.version is not None + and "%s %s" % (self._name_str, self.version) + or None) + return CmdlnOptionParser(self, version=version) + + def get_version(self): + """ + Returns version of program. To be replaced in subclass. + """ + return __version__ + + def postoptparse(self): + """Hook method executed just after `.main()' parses top-level + options. + + When called `self.values' holds the results of the option parse. + """ + pass + + def main(self, argv=None, loop=LOOP_NEVER): + """A possible mainline handler for a script, like so: + + import cmdln + class MyCmd(cmdln.Cmdln): + name = "mycmd" + ... + + if __name__ == "__main__": + MyCmd().main() + + By default this will use sys.argv to issue a single command to + 'MyCmd', then exit. The 'loop' argument can be use to control + interactive shell behaviour. + + Arguments: + "argv" (optional, default sys.argv) is the command to run. + It must be a sequence, where the first element is the + command name and subsequent elements the args for that + command. + "loop" (optional, default LOOP_NEVER) is a constant + indicating if a command loop should be started (i.e. an + interactive shell). Valid values (constants on this module): + LOOP_ALWAYS start loop and run "argv", if any + LOOP_NEVER run "argv" (or .emptyline()) and exit + LOOP_IF_EMPTY run "argv", if given, and exit; + otherwise, start loop + """ + if argv is None: + argv = sys.argv + else: + argv = argv[:] # don't modify caller's list + + self.optparser = self.get_optparser() + if self.optparser: # i.e. optparser=None means don't process for opts + try: + self.options, args = self.optparser.parse_args(argv[1:]) + except CmdlnUserError as ex: + msg = "%s: %s\nTry '%s help' for info.\n"\ + % (self.name, ex, self.name) + self.stderr.write(self._str(msg)) + self.stderr.flush() + return 1 + except StopOptionProcessing as ex: + return 0 + else: + self.options, args = None, argv[1:] + self.postoptparse() + + if loop == LOOP_ALWAYS: + if args: + self.cmdqueue.append(args) + return self.cmdloop() + elif loop == LOOP_NEVER: + if args: + return self.cmd(args) + else: + return self.emptyline() + elif loop == LOOP_IF_EMPTY: + if args: + return self.cmd(args) + else: + return self.cmdloop() + + def cmd(self, argv): + """Run one command and exit. + + "argv" is the arglist for the command to run. argv[0] is the + command to run. If argv is an empty list then the + 'emptyline' handler is run. + + Returns the return value from the command handler. + """ + assert isinstance(argv, (list, tuple)), \ + "'argv' is not a sequence: %r" % argv + retval = None + try: + argv = self.precmd(argv) + retval = self.onecmd(argv) + self.postcmd(argv) + except: + if not self.cmdexc(argv): + raise + retval = 1 + return retval + + def _str(self, s): + """Safely convert the given str/unicode to a string for printing.""" + try: + return str(s) + except UnicodeError: + #XXX What is the proper encoding to use here? 'utf-8' seems + # to work better than "getdefaultencoding" (usually + # 'ascii'), on OS X at least. + #return s.encode(sys.getdefaultencoding(), "replace") + return s.encode("utf-8", "replace") + + def cmdloop(self, intro=None): + """Repeatedly issue a prompt, accept input, parse into an argv, and + dispatch (via .precmd(), .onecmd() and .postcmd()), passing them + the argv. In other words, start a shell. + + "intro" (optional) is a introductory message to print when + starting the command loop. This overrides the class + "intro" attribute, if any. + """ + self.cmdlooping = True + self.preloop() + if self.use_rawinput and self.completekey: + try: + import readline + self.old_completer = readline.get_completer() + readline.set_completer(self.complete) + readline.parse_and_bind(self.completekey+": complete") + except ImportError: + pass + try: + if intro is None: + intro = self.intro + if intro: + intro_str = self._str(intro) + self.stdout.write(intro_str+'\n') + self.stop = False + retval = None + while not self.stop: + if self.cmdqueue: + argv = self.cmdqueue.pop(0) + assert isinstance(argv, (list, tuple)), \ + "item on 'cmdqueue' is not a sequence: %r" % argv + else: + if self.use_rawinput: + try: + try: + #python 2.x + line = raw_input(self._prompt_str) + except NameError: + line = input(self._prompt_str) + except EOFError: + line = 'EOF' + else: + self.stdout.write(self._prompt_str) + self.stdout.flush() + line = self.stdin.readline() + if not len(line): + line = 'EOF' + else: + line = line[:-1] # chop '\n' + argv = line2argv(line) + try: + argv = self.precmd(argv) + retval = self.onecmd(argv) + self.postcmd(argv) + except: + if not self.cmdexc(argv): + raise + retval = 1 + self.lastretval = retval + self.postloop() + finally: + if self.use_rawinput and self.completekey: + try: + import readline + readline.set_completer(self.old_completer) + except ImportError: + pass + self.cmdlooping = False + return retval + + def precmd(self, argv): + """Hook method executed just before the command argv is + interpreted, but after the input prompt is generated and issued. + + "argv" is the cmd to run. + + Returns an argv to run (i.e. this method can modify the command + to run). + """ + return argv + + def postcmd(self, argv): + """Hook method executed just after a command dispatch is finished. + + "argv" is the command that was run. + """ + pass + + def cmdexc(self, argv): + """Called if an exception is raised in any of precmd(), onecmd(), + or postcmd(). If True is returned, the exception is deemed to have + been dealt with. Otherwise, the exception is re-raised. + + The default implementation handles CmdlnUserError's, which + typically correspond to user error in calling commands (as + opposed to programmer error in the design of the script using + cmdln.py). + """ + exc_type, exc, traceback = sys.exc_info() + if isinstance(exc, CmdlnUserError): + msg = "%s %s: %s\nTry '%s help %s' for info.\n"\ + % (self.name, argv[0], exc, self.name, argv[0]) + self.stderr.write(self._str(msg)) + self.stderr.flush() + return True + + def onecmd(self, argv): + if not argv: + return self.emptyline() + self.lastcmd = argv + cmdname = self._get_canonical_cmd_name(argv[0]) + if cmdname: + handler = self._get_cmd_handler(cmdname) + if handler: + return self._dispatch_cmd(handler, argv) + return self.default(argv) + + def _dispatch_cmd(self, handler, argv): + return handler(argv) + + def default(self, argv): + """Hook called to handle a command for which there is no handler. + + "argv" is the command and arguments to run. + + The default implementation writes and error message to stderr + and returns an error exit status. + + Returns a numeric command exit status. + """ + errmsg = self._str(self.unknowncmd % (argv[0],)) + if self.cmdlooping: + self.stderr.write(errmsg+"\n") + else: + self.stderr.write("%s: %s\nTry '%s help' for info.\n" + % (self._name_str, errmsg, self._name_str)) + self.stderr.flush() + return 1 + + def parseline(self, line): + # This is used by Cmd.complete (readline completer function) to + # massage the current line buffer before completion processing. + # We override to drop special '!' handling. + line = line.strip() + if not line: + return None, None, line + elif line[0] == '?': + line = 'help ' + line[1:] + i, n = 0, len(line) + while i < n and line[i] in self.identchars: + i = i+1 + cmd, arg = line[:i], line[i:].strip() + return cmd, arg, line + + def helpdefault(self, cmd, known): + """Hook called to handle help on a command for which there is no + help handler. + + "cmd" is the command name on which help was requested. + "known" is a boolean indicating if this command is known + (i.e. if there is a handler for it). + + Returns a return code. + """ + if known: + msg = self._str(self.nohelp % (cmd,)) + if self.cmdlooping: + self.stderr.write(msg + '\n') + else: + self.stderr.write("%s: %s\n" % (self.name, msg)) + else: + msg = self.unknowncmd % (cmd,) + if self.cmdlooping: + self.stderr.write(msg + '\n') + else: + self.stderr.write("%s: %s\n" + "Try '%s help' for info.\n" + % (self.name, msg, self.name)) + self.stderr.flush() + return 1 + + + def do_help(self, argv): + """${cmd_name}: give detailed help on a specific sub-command + + usage: + ${name} help [SUBCOMMAND] + """ + if len(argv) > 1: # asking for help on a particular command + doc = None + cmdname = self._get_canonical_cmd_name(argv[1]) or argv[1] + if not cmdname: + return self.helpdefault(argv[1], False) + else: + helpfunc = getattr(self, "help_"+cmdname, None) + if helpfunc: + doc = helpfunc() + else: + handler = self._get_cmd_handler(cmdname) + if handler: + doc = handler.__doc__ + if doc is None: + return self.helpdefault(argv[1], handler != None) + else: # bare "help" command + doc = self.__class__.__doc__ # try class docstring + if doc is None: + # Try to provide some reasonable useful default help. + if self.cmdlooping: + prefix = "" + else: + prefix = self.name+' ' + doc = """usage: + %sSUBCOMMAND [ARGS...] + %shelp [SUBCOMMAND] + + ${option_list} + ${command_list} + ${help_list} + """ % (prefix, prefix) + cmdname = None + + if doc: # *do* have help content, massage and print that + doc = self._help_reindent(doc) + doc = self._help_preprocess(doc, cmdname) + doc = doc.rstrip() + '\n' # trim down trailing space + self.stdout.write(self._str(doc)) + self.stdout.flush() + do_help.aliases = ["?"] + + + def do_man(self, argv): + """${cmd_name}: generates a man page + + usage: + ${name} man + """ + self.stdout.write(bytes( + self.man_header % { + 'date': date.today().strftime('%b %Y'), + 'version': self.get_version(), + 'name': self.name, + 'ucname': self.name.upper() + }, + "utf-8")) + + self.stdout.write(bytes(self.man_commands_header, "utf-8")) + commands = self._help_get_command_list() + for command, doc in commands: + cmdname = command.split(' ')[0] + text = self._help_preprocess(doc, cmdname) + lines = [] + for line in text.splitlines(False): + if line[:8] == ' ' * 8: + line = line[8:] + lines.append(man_escape(line)) + + self.stdout.write(bytes( + '.TP\n\\fB%s\\fR\n%s\n' % (command, '\n'.join(lines)), "utf-8")) + + self.stdout.write(bytes(self.man_options_header, "utf-8")) + self.stdout.write(bytes( + man_escape(self._help_preprocess('${option_list}', None)), "utf-8")) + + self.stdout.write(bytes(self.man_footer, "utf-8")) + + self.stdout.flush() + + def _help_reindent(self, help, indent=None): + """Hook to re-indent help strings before writing to stdout. + + "help" is the help content to re-indent + "indent" is a string with which to indent each line of the + help content after normalizing. If unspecified or None + then the default is use: the 'self.helpindent' class + attribute. By default this is the empty string, i.e. + no indentation. + + By default, all common leading whitespace is removed and then + the lot is indented by 'self.helpindent'. When calculating the + common leading whitespace the first line is ignored -- hence + help content for Conan can be written as follows and have the + expected indentation: + + def do_crush(self, ...): + '''${cmd_name}: crush your enemies, see them driven before you... + + c.f. Conan the Barbarian''' + """ + if indent is None: + indent = self.helpindent + lines = help.splitlines(0) + _dedentlines(lines, skip_first_line=True) + lines = [(indent+line).rstrip() for line in lines] + return '\n'.join(lines) + + def _help_preprocess(self, help, cmdname): + """Hook to preprocess a help string before writing to stdout. + + "help" is the help string to process. + "cmdname" is the canonical sub-command name for which help + is being given, or None if the help is not specific to a + command. + + By default the following template variables are interpolated in + help content. (Note: these are similar to Python 2.4's + string.Template interpolation but not quite.) + + ${name} + The tool's/shell's name, i.e. 'self.name'. + ${option_list} + A formatted table of options for this shell/tool. + ${command_list} + A formatted table of available sub-commands. + ${help_list} + A formatted table of additional help topics (i.e. 'help_*' + methods with no matching 'do_*' method). + ${cmd_name} + The name (and aliases) for this sub-command formatted as: + "NAME (ALIAS1, ALIAS2, ...)". + ${cmd_usage} + A formatted usage block inferred from the command function + signature. + ${cmd_option_list} + A formatted table of options for this sub-command. (This is + only available for commands using the optparse integration, + i.e. using @cmdln.option decorators or manually setting the + 'optparser' attribute on the 'do_*' method.) + + Returns the processed help. + """ + preprocessors = { + "${name}": self._help_preprocess_name, + "${option_list}": self._help_preprocess_option_list, + "${command_list}": self._help_preprocess_command_list, + "${help_list}": self._help_preprocess_help_list, + "${cmd_name}": self._help_preprocess_cmd_name, + "${cmd_usage}": self._help_preprocess_cmd_usage, + "${cmd_option_list}": self._help_preprocess_cmd_option_list, + } + + for marker, preprocessor in preprocessors.items(): + if marker in help: + help = preprocessor(help, cmdname) + return help + + def _help_preprocess_name(self, help, cmdname=None): + return help.replace("${name}", self.name) + + def _help_preprocess_option_list(self, help, cmdname=None): + marker = "${option_list}" + indent, indent_width = _get_indent(marker, help) + suffix = _get_trailing_whitespace(marker, help) + + if self.optparser: + # Setup formatting options and format. + # - Indentation of 4 is better than optparse default of 2. + # C.f. Damian Conway's discussion of this in Perl Best + # Practices. + self.optparser.formatter.indent_increment = 4 + self.optparser.formatter.current_indent = indent_width + block = self.optparser.format_option_help() + '\n' + else: + block = "" + + help_msg = help.replace(indent+marker+suffix, block, 1) + return help_msg + + def _help_get_command_list(self): + # Find any aliases for commands. + token2canonical = self._get_canonical_map() + aliases = {} + for token, cmdname in token2canonical.items(): + if token == cmdname: + continue + aliases.setdefault(cmdname, []).append(token) + + # Get the list of (non-hidden) commands and their + # documentation, if any. + cmdnames = {} # use a dict to strip duplicates + for attr in self.get_names(): + if attr.startswith("do_"): + cmdnames[attr[3:]] = True + linedata = [] + for cmdname in sorted(cmdnames.keys()): + if aliases.get(cmdname): + a = sorted(aliases[cmdname]) + cmdstr = "%s (%s)" % (cmdname, ", ".join(a)) + else: + cmdstr = cmdname + doc = None + try: + helpfunc = getattr(self, 'help_'+cmdname) + except AttributeError: + handler = self._get_cmd_handler(cmdname) + if handler: + doc = handler.__doc__ + else: + doc = helpfunc() + + # Strip "${cmd_name}: " from the start of a command's doc. Best + # practice dictates that command help strings begin with this, but + # it isn't at all wanted for the command list. + to_strip = "${cmd_name}:" + if doc and doc.startswith(to_strip): + #log.debug("stripping %r from start of %s's help string", + # to_strip, cmdname) + doc = doc[len(to_strip):].lstrip() + if not getattr(self._get_cmd_handler(cmdname), "hidden", None): + linedata.append( (cmdstr, doc) ) + + return linedata + + def _help_preprocess_command_list(self, help, cmdname=None): + marker = "${command_list}" + indent, indent_width = _get_indent(marker, help) + suffix = _get_trailing_whitespace(marker, help) + + linedata = self._help_get_command_list() + + if linedata: + subindent = indent + ' '*4 + lines = _format_linedata(linedata, subindent, indent_width+4) + block = indent + "commands:\n" \ + + '\n'.join(lines) + "\n\n" + help = help.replace(indent+marker+suffix, block, 1) + return help + + def _help_preprocess_help_list(self, help, cmdname=None): + marker = "${help_list}" + indent, indent_width = _get_indent(marker, help) + suffix = _get_trailing_whitespace(marker, help) + + # Determine the additional help topics, if any. + helpnames = {} + token2cmdname = self._get_canonical_map() + for attr in self.get_names(): + if not attr.startswith("help_"): + continue + helpname = attr[5:] + if helpname not in token2cmdname: + helpnames[helpname] = True + + if helpnames: + helpnames = sorted(helpnames.keys()) + linedata = [(self.name+" help "+n, "") for n in helpnames] + + subindent = indent + ' '*4 + lines = _format_linedata(linedata, subindent, indent_width+4) + block = indent + "additional help topics:\n" \ + + '\n'.join(lines) + "\n\n" + else: + block = '' + help_msg = help.replace(indent+marker+suffix, block, 1) + return help_msg + + def _help_preprocess_cmd_name(self, help, cmdname=None): + marker = "${cmd_name}" + handler = self._get_cmd_handler(cmdname) + if not handler: + raise CmdlnError("cannot preprocess '%s' into help string: " + "could not find command handler for %r" + % (marker, cmdname)) + s = cmdname + if hasattr(handler, "aliases"): + s += " (%s)" % (", ".join(handler.aliases)) + help_msg = help.replace(marker, s) + return help_msg + + #TODO: this only makes sense as part of the Cmdln class. + # Add hooks to add help preprocessing template vars and put + # this one on that class. + def _help_preprocess_cmd_usage(self, help, cmdname=None): + marker = "${cmd_usage}" + handler = self._get_cmd_handler(cmdname) + if not handler: + raise CmdlnError("cannot preprocess '%s' into help string: " + "could not find command handler for %r" + % (marker, cmdname)) + indent, indent_width = _get_indent(marker, help) + suffix = _get_trailing_whitespace(marker, help) + + func_defaults, co_argcount, co_varnames, co_flags, _ = introspect_handler(handler) + CO_FLAGS_ARGS = 4 + CO_FLAGS_KWARGS = 8 + + # Adjust argcount for possible *args and **kwargs arguments. + argcount = co_argcount + if co_flags & CO_FLAGS_ARGS: + argcount += 1 + if co_flags & CO_FLAGS_KWARGS: + argcount += 1 + + # Determine the usage string. + usage = "%s %s" % (self.name, cmdname) + if argcount <= 2: # handler ::= do_FOO(self, argv) + usage += " [ARGS...]" + elif argcount >= 3: # handler ::= do_FOO(self, subcmd, opts, ...) + argnames = list(co_varnames[3:argcount]) + tail = "" + if co_flags & CO_FLAGS_KWARGS: + name = argnames.pop(-1) + import warnings + # There is no generally accepted mechanism for passing + # keyword arguments from the command line. Could + # *perhaps* consider: arg=value arg2=value2 ... + warnings.warn("argument '**%s' on '%s.%s' command " + "handler will never get values" + % (name, self.__class__.__name__, + getattr(func, "__name__", getattr(func, "func_name")))) + if co_flags & CO_FLAGS_ARGS: + name = argnames.pop(-1) + tail = "[%s...]" % name.upper() + while func_defaults: + func_defaults.pop(-1) + name = argnames.pop(-1) + tail = "[%s%s%s]" % (name.upper(), (tail and ' ' or ''), tail) + while argnames: + name = argnames.pop(-1) + tail = "%s %s" % (name.upper(), tail) + usage += ' ' + tail + + block_lines = [ + self.helpindent + "Usage:", + self.helpindent + ' '*4 + usage + ] + block = '\n'.join(block_lines) + '\n\n' + + help_msg = help.replace(indent+marker+suffix, block, 1) + return help_msg + + #TODO: this only makes sense as part of the Cmdln class. + # Add hooks to add help preprocessing template vars and put + # this one on that class. + def _help_preprocess_cmd_option_list(self, help, cmdname=None): + marker = "${cmd_option_list}" + handler = self._get_cmd_handler(cmdname) + if not handler: + raise CmdlnError("cannot preprocess '%s' into help string: " + "could not find command handler for %r" + % (marker, cmdname)) + indent, indent_width = _get_indent(marker, help) + suffix = _get_trailing_whitespace(marker, help) + if hasattr(handler, "optparser"): + # Setup formatting options and format. + # - Indentation of 4 is better than optparse default of 2. + # C.f. Damian Conway's discussion of this in Perl Best + # Practices. + handler.optparser.formatter.indent_increment = 4 + handler.optparser.formatter.current_indent = indent_width + block = handler.optparser.format_option_help() + '\n' + else: + block = "" + + help_msg = help.replace(indent+marker+suffix, block, 1) + return help_msg + + def _get_canonical_cmd_name(self, token): + c_map = self._get_canonical_map() + return c_map.get(token, None) + + def _get_canonical_map(self): + """Return a mapping of available command names and aliases to + their canonical command name. + """ + cacheattr = "_token2canonical" + if not hasattr(self, cacheattr): + # Get the list of commands and their aliases, if any. + token2canonical = {} + cmd2funcname = {} # use a dict to strip duplicates + for attr in self.get_names(): + if attr.startswith("do_"): + cmdname = attr[3:] + elif attr.startswith("_do_"): + cmdname = attr[4:] + else: + continue + cmd2funcname[cmdname] = attr + token2canonical[cmdname] = cmdname + for cmdname, funcname in cmd2funcname.items(): # add aliases + func = getattr(self, funcname) + aliases = getattr(func, "aliases", []) + for alias in aliases: + if alias in cmd2funcname: + import warnings + warnings.warn("'%s' alias for '%s' command conflicts " + "with '%s' handler" + % (alias, cmdname, cmd2funcname[alias])) + continue + token2canonical[alias] = cmdname + setattr(self, cacheattr, token2canonical) + return getattr(self, cacheattr) + + def _get_cmd_handler(self, cmdname): + handler = None + try: + handler = getattr(self, 'do_' + cmdname) + except AttributeError: + try: + # Private command handlers begin with "_do_". + handler = getattr(self, '_do_' + cmdname) + except AttributeError: + pass + return handler + + def _do_EOF(self, argv): + # Default EOF handler + # Note: an actual EOF is redirected to this command. + #TODO: separate name for this. Currently it is available from + # command-line. Is that okay? + self.stdout.write('\n') + self.stdout.flush() + self.stop = True + + def emptyline(self): + # Different from cmd.Cmd: don't repeat the last command for an + # emptyline. + if self.cmdlooping: + pass + else: + return self.do_help(["help"]) + + +#---- optparse.py extension to fix (IMO) some deficiencies +# +# See the class _OptionParserEx docstring for details. +# + +class StopOptionProcessing(Exception): + """Indicate that option *and argument* processing should stop + cleanly. This is not an error condition. It is similar in spirit to + StopIteration. This is raised by _OptionParserEx's default "help" + and "version" option actions and can be raised by custom option + callbacks too. + + Hence the typical CmdlnOptionParser (a subclass of _OptionParserEx) + usage is: + + parser = CmdlnOptionParser(mycmd) + parser.add_option("-f", "--force", dest="force") + ... + try: + opts, args = parser.parse_args() + except StopOptionProcessing: + # normal termination, "--help" was probably given + sys.exit(0) + """ + +class _OptionParserEx(optparse.OptionParser): + """An optparse.OptionParser that uses exceptions instead of sys.exit. + + This class is an extension of optparse.OptionParser that differs + as follows: + - Correct (IMO) the default OptionParser error handling to never + sys.exit(). Instead OptParseError exceptions are passed through. + - Add the StopOptionProcessing exception (a la StopIteration) to + indicate normal termination of option processing. + See StopOptionProcessing's docstring for details. + + I'd also like to see the following in the core optparse.py, perhaps + as a RawOptionParser which would serve as a base class for the more + generally used OptionParser (that works as current): + - Remove the implicit addition of the -h|--help and --version + options. They can get in the way (e.g. if want '-?' and '-V' for + these as well) and it is not hard to do: + optparser.add_option("-h", "--help", action="help") + optparser.add_option("--version", action="version") + These are good practices, just not valid defaults if they can + get in the way. + """ + def error(self, msg): + raise optparse.OptParseError(msg) + + def exit(self, status=0, msg=None): + if status == 0: + raise StopOptionProcessing(msg) + else: + #TODO: don't lose status info here + raise optparse.OptParseError(msg) + + + +#---- optparse.py-based option processing support + +class CmdlnOptionParser(_OptionParserEx): + """An optparse.OptionParser class more appropriate for top-level + Cmdln options. For parsing of sub-command options, see + SubCmdOptionParser. + + Changes: + - disable_interspersed_args() by default, because a Cmdln instance + has sub-commands which may themselves have options. + - Redirect print_help() to the Cmdln.do_help() which is better + equiped to handle the "help" action. + - error() will raise a CmdlnUserError: OptionParse.error() is meant + to be called for user errors. Raising a well-known error here can + make error handling clearer. + - Also see the changes in _OptionParserEx. + """ + def __init__(self, cmdln, **kwargs): + self.cmdln = cmdln + kwargs["prog"] = self.cmdln.name + _OptionParserEx.__init__(self, **kwargs) + self.disable_interspersed_args() + + def print_help(self, file=None): + self.cmdln.onecmd(["help"]) + + def error(self, msg): + raise CmdlnUserError(msg) + + +class SubCmdOptionParser(_OptionParserEx): + def set_cmdln_info(self, cmdln, subcmd): + """Called by Cmdln to pass relevant info about itself needed + for print_help(). + """ + self.cmdln = cmdln + self.subcmd = subcmd + + def print_help(self, file=None): + self.cmdln.onecmd(["help", self.subcmd]) + + def error(self, msg): + raise CmdlnUserError(msg) + + +def option(*args, **kwargs): + """Decorator to add an option to the optparser argument of a Cmdln + subcommand. + + Example: + class MyShell(cmdln.Cmdln): + @cmdln.option("-f", "--force", help="force removal") + def do_remove(self, subcmd, opts, *args): + #... + """ + #XXX Is there a possible optimization for many options to not have a + # large stack depth here? + def decorate(f): + if not hasattr(f, "optparser"): + f.optparser = SubCmdOptionParser() + f.optparser.add_option(*args, **kwargs) + return f + return decorate + +def hide(*args): + """For obsolete calls, hide them in help listings. + + Example: + class MyShell(cmdln.Cmdln): + @cmdln.hide() + def do_shell(self, argv): + #...implement 'shell' command + """ + def decorate(f): + f.hidden = 1 + return f + return decorate + + +class Cmdln(RawCmdln): + """An improved (on cmd.Cmd) framework for building multi-subcommand + scripts (think "svn" & "cvs") and simple shells (think "pdb" and + "gdb"). + + A simple example: + + import cmdln + + class MySVN(cmdln.Cmdln): + name = "svn" + + @cmdln.aliases('stat', 'st') + @cmdln.option('-v', '--verbose', action='store_true' + help='print verbose information') + def do_status(self, subcmd, opts, *paths): + print "handle 'svn status' command" + + #... + + if __name__ == "__main__": + shell = MySVN() + retval = shell.main() + sys.exit(retval) + + 'Cmdln' extends 'RawCmdln' by providing optparse option processing + integration. See this class' _dispatch_cmd() docstring and + <http://trentm.com/projects/cmdln> for more information. + """ + def _dispatch_cmd(self, handler, argv): + """Introspect sub-command handler signature to determine how to + dispatch the command. The raw handler provided by the base + 'RawCmdln' class is still supported: + + def do_foo(self, argv): + # 'argv' is the vector of command line args, argv[0] is + # the command name itself (i.e. "foo" or an alias) + pass + + In addition, if the handler has more than 2 arguments option + processing is automatically done (using optparse): + + @cmdln.option('-v', '--verbose', action='store_true') + def do_bar(self, subcmd, opts, *args): + # subcmd = <"bar" or an alias> + # opts = <an optparse.Values instance> + if opts.verbose: + print "lots of debugging output..." + # args = <tuple of arguments> + for arg in args: + bar(arg) + + TODO: explain that "*args" can be other signatures as well. + + The `cmdln.option` decorator corresponds to an `add_option()` + method call on an `optparse.OptionParser` instance. + + You can declare a specific number of arguments: + + @cmdln.option('-v', '--verbose', action='store_true') + def do_bar2(self, subcmd, opts, bar_one, bar_two): + #... + + and an appropriate error message will be raised/printed if the + command is called with a different number of args. + """ + co_argcount = introspect_handler(handler)[1] + if co_argcount == 2: # handler ::= do_foo(self, argv) + return handler(argv) + elif co_argcount >= 3: # handler ::= do_foo(self, subcmd, opts, ...) + try: + optparser = handler.optparser + except AttributeError: + optparser = introspect_handler(handler)[4].optparser = SubCmdOptionParser() + assert isinstance(optparser, SubCmdOptionParser) + optparser.set_cmdln_info(self, argv[0]) + try: + opts, args = optparser.parse_args(argv[1:]) + except StopOptionProcessing: + #TODO: this doesn't really fly for a replacement of + # optparse.py behaviour, does it? + return 0 # Normal command termination + + try: + return handler(argv[0], opts, *args) + except TypeError as ex: + # Some TypeError's are user errors: + # do_foo() takes at least 4 arguments (3 given) + # do_foo() takes at most 5 arguments (6 given) + # do_foo() takes exactly 5 arguments (6 given) + # Raise CmdlnUserError for these with a suitably + # massaged error message. + tb = sys.exc_info()[2] # the traceback object + if tb.tb_next is not None: + # If the traceback is more than one level deep, then the + # TypeError do *not* happen on the "handler(...)" call + # above. In that we don't want to handle it specially + # here: it would falsely mask deeper code errors. + raise + msg = ex.args[0] + match = _INCORRECT_NUM_ARGS_RE.search(msg) + if match: + msg = list(match.groups()) + msg[1] = int(msg[1]) - 3 + if msg[1] == 1: + msg[2] = msg[2].replace("arguments", "argument") + msg[3] = int(msg[3]) - 3 + msg = ''.join(map(str, msg)) + raise CmdlnUserError(msg) + else: + raise + else: + raise CmdlnError("incorrect argcount for %s(): takes %d, must " + "take 2 for 'argv' signature or 3+ for 'opts' " + "signature" % (handler.__name__, co_argcount)) + + + +#---- internal support functions + +def _format_linedata(linedata, indent, indent_width): + """Format specific linedata into a pleasant layout. + + "linedata" is a list of 2-tuples of the form: + (<item-display-string>, <item-docstring>) + "indent" is a string to use for one level of indentation + "indent_width" is a number of columns by which the + formatted data will be indented when printed. + + The <item-display-string> column is held to 15 columns. + """ + lines = [] + WIDTH = 78 - indent_width + SPACING = 3 + MAX_NAME_WIDTH = 15 + + NAME_WIDTH = min(max([len(s) for s, d in linedata]), MAX_NAME_WIDTH) + DOC_WIDTH = WIDTH - NAME_WIDTH - SPACING + for namestr, doc in linedata: + line = indent + namestr + if len(namestr) <= NAME_WIDTH: + line += ' ' * (NAME_WIDTH + SPACING - len(namestr)) + else: + lines.append(line) + line = indent + ' ' * (NAME_WIDTH + SPACING) + line += _summarize_doc(doc, DOC_WIDTH) + lines.append(line.rstrip()) + return lines + +def _summarize_doc(doc, length=60): + r"""Parse out a short one line summary from the given doclines. + + "doc" is the doc string to summarize. + "length" is the max length for the summary + + >>> _summarize_doc("this function does this") + 'this function does this' + >>> _summarize_doc("this function does this", 10) + 'this fu...' + >>> _summarize_doc("this function does this\nand that") + 'this function does this and that' + >>> _summarize_doc("this function does this\n\nand that") + 'this function does this' + """ + if doc is None: + return "" + assert length > 3, "length <= 3 is absurdly short for a doc summary" + doclines = doc.strip().splitlines(0) + if not doclines: + return "" + + summlines = [] + for i, line in enumerate(doclines): + stripped = line.strip() + if not stripped: + break + summlines.append(stripped) + if len(''.join(summlines)) >= length: + break + + summary = ' '.join(summlines) + if len(summary) > length: + summary = summary[:length-3] + "..." + return summary + + +def line2argv(line): + r"""Parse the given line into an argument vector. + + "line" is the line of input to parse. + + This may get niggly when dealing with quoting and escaping. The + current state of this parsing may not be completely thorough/correct + in this respect. + + >>> from cmdln import line2argv + >>> line2argv("foo") + ['foo'] + >>> line2argv("foo bar") + ['foo', 'bar'] + >>> line2argv("foo bar ") + ['foo', 'bar'] + >>> line2argv(" foo bar") + ['foo', 'bar'] + + Quote handling: + + >>> line2argv("'foo bar'") + ['foo bar'] + >>> line2argv('"foo bar"') + ['foo bar'] + >>> line2argv(r'"foo\"bar"') + ['foo"bar'] + >>> line2argv("'foo bar' spam") + ['foo bar', 'spam'] + >>> line2argv("'foo 'bar spam") + ['foo bar', 'spam'] + >>> line2argv("'foo") + Traceback (most recent call last): + ... + ValueError: command line is not terminated: unfinished single-quoted segment + >>> line2argv('"foo') + Traceback (most recent call last): + ... + ValueError: command line is not terminated: unfinished double-quoted segment + >>> line2argv('some\tsimple\ttests') + ['some', 'simple', 'tests'] + >>> line2argv('a "more complex" test') + ['a', 'more complex', 'test'] + >>> line2argv('a more="complex test of " quotes') + ['a', 'more=complex test of ', 'quotes'] + >>> line2argv('a more" complex test of " quotes') + ['a', 'more complex test of ', 'quotes'] + >>> line2argv('an "embedded \\"quote\\""') + ['an', 'embedded "quote"'] + """ + import string + line = line.strip() + argv = [] + state = "default" + arg = None # the current argument being parsed + i = -1 + while True: + i += 1 + if i >= len(line): + break + ch = line[i] + + if ch == "\\": # escaped char always added to arg, regardless of state + if arg is None: + arg = "" + i += 1 + arg += line[i] + continue + + if state == "single-quoted": + if ch == "'": + state = "default" + else: + arg += ch + elif state == "double-quoted": + if ch == '"': + state = "default" + else: + arg += ch + elif state == "default": + if ch == '"': + if arg is None: + arg = "" + state = "double-quoted" + elif ch == "'": + if arg is None: + arg = "" + state = "single-quoted" + elif ch in string.whitespace: + if arg is not None: + argv.append(arg) + arg = None + else: + if arg is None: + arg = "" + arg += ch + if arg is not None: + argv.append(arg) + if state != "default": + raise ValueError("command line is not terminated: unfinished %s " + "segment" % state) + return argv + + +def argv2line(argv): + r"""Put together the given argument vector into a command line. + + "argv" is the argument vector to process. + + >>> from cmdln import argv2line + >>> argv2line(['foo']) + 'foo' + >>> argv2line(['foo', 'bar']) + 'foo bar' + >>> argv2line(['foo', 'bar baz']) + 'foo "bar baz"' + >>> argv2line(['foo"bar']) + 'foo"bar' + >>> print argv2line(['foo" bar']) + 'foo" bar' + >>> print argv2line(["foo' bar"]) + "foo' bar" + >>> argv2line(["foo'bar"]) + "foo'bar" + """ + escapedArgs = [] + for arg in argv: + if ' ' in arg and '"' not in arg: + arg = '"'+arg+'"' + elif ' ' in arg and "'" not in arg: + arg = "'"+arg+"'" + elif ' ' in arg: + arg = arg.replace('"', r'\"') + arg = '"'+arg+'"' + escapedArgs.append(arg) + return ' '.join(escapedArgs) + + +# Recipe: dedent (0.1) in /Users/trentm/tm/recipes/cookbook +def _dedentlines(lines, tabsize=8, skip_first_line=False): + """_dedentlines(lines, tabsize=8, skip_first_line=False) -> dedented lines + + "lines" is a list of lines to dedent. + "tabsize" is the tab width to use for indent width calculations. + "skip_first_line" is a boolean indicating if the first line should + be skipped for calculating the indent width and for dedenting. + This is sometimes useful for docstrings and similar. + + Same as dedent() except operates on a sequence of lines. Note: the + lines list is modified **in-place**. + """ + DEBUG = False + if DEBUG: + print("dedent: dedent(..., tabsize=%d, skip_first_line=%r)"\ + % (tabsize, skip_first_line)) + indents = [] + margin = None + for i, line in enumerate(lines): + if i == 0 and skip_first_line: + continue + indent = 0 + for ch in line: + if ch == ' ': + indent += 1 + elif ch == '\t': + indent += tabsize - (indent % tabsize) + elif ch in '\r\n': + continue # skip all-whitespace lines + else: + break + else: + continue # skip all-whitespace lines + if DEBUG: + print("dedent: indent=%d: %r" % (indent, line)) + if margin is None: + margin = indent + else: + margin = min(margin, indent) + if DEBUG: + print("dedent: margin=%r" % margin) + + if margin is not None and margin > 0: + for i, line in enumerate(lines): + if i == 0 and skip_first_line: + continue + removed = 0 + for j, ch in enumerate(line): + if ch == ' ': + removed += 1 + elif ch == '\t': + removed += tabsize - (removed % tabsize) + elif ch in '\r\n': + if DEBUG: + print("dedent: %r: EOL -> strip up to EOL" % line) + lines[i] = lines[i][j:] + break + else: + raise ValueError("unexpected non-whitespace char %r in " + "line %r while removing %d-space margin" + % (ch, line, margin)) + if DEBUG: + print("dedent: %r: %r -> removed %d/%d"\ + % (line, ch, removed, margin)) + if removed == margin: + lines[i] = lines[i][j+1:] + break + elif removed > margin: + lines[i] = ' '*(removed-margin) + lines[i][j+1:] + break + return lines + +def _dedent(text, tabsize=8, skip_first_line=False): + """_dedent(text, tabsize=8, skip_first_line=False) -> dedented text + + "text" is the text to dedent. + "tabsize" is the tab width to use for indent width calculations. + "skip_first_line" is a boolean indicating if the first line should + be skipped for calculating the indent width and for dedenting. + This is sometimes useful for docstrings and similar. + + textwrap.dedent(s), but don't expand tabs to spaces + """ + lines = text.splitlines(1) + _dedentlines(lines, tabsize=tabsize, skip_first_line=skip_first_line) + return ''.join(lines) + + +def _get_indent(marker, s, tab_width=8): + """_get_indent(marker, s, tab_width=8) -> + (<indentation-of-'marker'>, <indentation-width>)""" + # Figure out how much the marker is indented. + INDENT_CHARS = tuple(' \t') + start = s.index(marker) + i = start + while i > 0: + if s[i-1] not in INDENT_CHARS: + break + i -= 1 + indent = s[i:start] + indent_width = 0 + for ch in indent: + if ch == ' ': + indent_width += 1 + elif ch == '\t': + indent_width += tab_width - (indent_width % tab_width) + return indent, indent_width + +def _get_trailing_whitespace(marker, s): + """Return the whitespace content trailing the given 'marker' in string 's', + up to and including a newline. + """ + suffix = '' + start = s.index(marker) + len(marker) + i = start + while i < len(s): + if s[i] in ' \t': + suffix += s[i] + elif s[i] in '\r\n': + suffix += s[i] + if s[i] == '\r' and i+1 < len(s) and s[i+1] == '\n': + suffix += s[i+1] + break + else: + break + i += 1 + return suffix + + +# vim: sw=4 et diff --git a/osc/commandline.py b/osc/commandline.py new file mode 100644 index 0000000..7a05e7f --- /dev/null +++ b/osc/commandline.py @@ -0,0 +1,8496 @@ +# Copyright (C) 2006 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or version 3 (at your option). + +from __future__ import print_function + +from . import cmdln +from . import conf +from . import oscerr +import sys +import time +import imp +import inspect +try: + from urllib.parse import urlsplit + from urllib.error import HTTPError + ET_ENCODING = "unicode" +except ImportError: + #python 2.x + from urlparse import urlsplit + from urllib2 import HTTPError + ET_ENCODING = "utf-8" + +from optparse import SUPPRESS_HELP + +from .core import * +from .util import safewriter + +MAN_HEADER = r""".TH %(ucname)s "1" "%(date)s" "%(name)s %(version)s" "User Commands" +.SH NAME +%(name)s \- openSUSE build service command-line tool. +.SH SYNOPSIS +.B %(name)s +[\fIGLOBALOPTS\fR] \fISUBCOMMAND \fR[\fIOPTS\fR] [\fIARGS\fR...] +.br +.B %(name)s +\fIhelp SUBCOMMAND\fR +.SH DESCRIPTION +openSUSE build service command-line tool. +""" +MAN_FOOTER = r""" +.SH "SEE ALSO" +Type 'osc help <subcommand>' for more detailed help on a specific subcommand. +.PP +For additional information, see + * http://en.opensuse.org/openSUSE:Build_Service_Tutorial + * http://en.opensuse.org/openSUSE:OSC +.PP +You can modify osc commands, or roll your own, via the plugin API: + * http://en.opensuse.org/openSUSE:OSC_plugins +.SH AUTHOR +osc was written by several authors. This man page is automatically generated. +""" + +class Osc(cmdln.Cmdln): + """Usage: osc [GLOBALOPTS] SUBCOMMAND [OPTS] [ARGS...] + or: osc help SUBCOMMAND + + openSUSE build service command-line tool. + Type 'osc help <subcommand>' for help on a specific subcommand. + + ${command_list} + ${help_list} + global ${option_list} + For additional information, see + * http://en.opensuse.org/openSUSE:Build_Service_Tutorial + * http://en.opensuse.org/openSUSE:OSC + + You can modify osc commands, or roll your own, via the plugin API: + * http://en.opensuse.org/openSUSE:OSC_plugins + """ + name = 'osc' + conf = None + + man_header = MAN_HEADER + man_footer = MAN_FOOTER + + def __init__(self, *args, **kwargs): + # the plugins have to be loaded before the + # superclass' __init__ method is called + self._load_plugins() + cmdln.Cmdln.__init__(self, *args, **kwargs) + cmdln.Cmdln.do_help.aliases.append('h') + sys.stderr = safewriter.SafeWriter(sys.stderr) + sys.stdout = safewriter.SafeWriter(sys.stdout) + + def get_version(self): + return get_osc_version() + + def get_optparser(self): + """this is the parser for "global" options (not specific to subcommand)""" + + optparser = cmdln.CmdlnOptionParser(self, version=get_osc_version()) + optparser.add_option('--debugger', action='store_true', + help='jump into the debugger before executing anything') + optparser.add_option('--post-mortem', action='store_true', + help='jump into the debugger in case of errors') + optparser.add_option('-t', '--traceback', action='store_true', + help='print call trace in case of errors') + optparser.add_option('-H', '--http-debug', action='store_true', + help='debug HTTP traffic (filters some headers)') + optparser.add_option('--http-full-debug', action='store_true', + help='debug HTTP traffic (filters no headers)') + optparser.add_option('-d', '--debug', action='store_true', + help='print info useful for debugging') + optparser.add_option('-A', '--apiurl', dest='apiurl', + metavar='URL/alias', + help='specify URL to access API server at or an alias') + optparser.add_option('-c', '--config', dest='conffile', + metavar='FILE', + help='specify alternate configuration file') + optparser.add_option('--no-keyring', action='store_true', + help='disable usage of desktop keyring system') + optparser.add_option('--no-gnome-keyring', action='store_true', + help='disable usage of GNOME Keyring') + optparser.add_option('-v', '--verbose', dest='verbose', action='count', default=0, + help='increase verbosity') + optparser.add_option('-q', '--quiet', dest='verbose', action='store_const', const=-1, + help='be quiet, not verbose') + return optparser + + + def postoptparse(self, try_again = True): + """merge commandline options into the config""" + try: + conf.get_config(override_conffile = self.options.conffile, + override_apiurl = self.options.apiurl, + override_debug = self.options.debug, + override_http_debug = self.options.http_debug, + override_http_full_debug = self.options.http_full_debug, + override_traceback = self.options.traceback, + override_post_mortem = self.options.post_mortem, + override_no_keyring = self.options.no_keyring, + override_no_gnome_keyring = self.options.no_gnome_keyring, + override_verbose = self.options.verbose) + except oscerr.NoConfigfile as e: + print(e.msg, file=sys.stderr) + print('Creating osc configuration file %s ...' % e.file, file=sys.stderr) + import getpass + config = {} + config['user'] = raw_input('Username: ') + config['pass'] = getpass.getpass() + if self.options.no_keyring: + config['use_keyring'] = '0' + if self.options.no_gnome_keyring: + config['gnome_keyring'] = '0' + if self.options.apiurl: + config['apiurl'] = self.options.apiurl + + conf.write_initial_config(e.file, config) + print('done', file=sys.stderr) + if try_again: + self.postoptparse(try_again = False) + except oscerr.ConfigMissingApiurl as e: + print(e.msg, file=sys.stderr) + import getpass + user = raw_input('Username: ') + passwd = getpass.getpass() + conf.add_section(e.file, e.url, user, passwd) + if try_again: + self.postoptparse(try_again = False) + + self.options.verbose = conf.config['verbose'] + self.download_progress = None + if conf.config.get('show_download_progress', False): + from .meter import TextMeter + self.download_progress = TextMeter(hide_finished=True) + + + def get_cmd_help(self, cmdname): + doc = self._get_cmd_handler(cmdname).__doc__ + doc = self._help_reindent(doc) + doc = self._help_preprocess(doc, cmdname) + doc = doc.rstrip() + '\n' # trim down trailing space + return self._str(doc) + + def get_api_url(self): + try: + localdir = os.getcwd() + except Exception as e: + ## check for Stale NFS file handle: '.' + try: + os.stat('.') + except Exception as ee: + e = ee + print("os.getcwd() failed: ", e, file=sys.stderr) + sys.exit(1) + + if (is_package_dir(localdir) or is_project_dir(localdir)) and not self.options.apiurl: + return store_read_apiurl(os.curdir) + else: + return conf.config['apiurl'] + + # overridden from class Cmdln() to use config variables in help texts + def _help_preprocess(self, help, cmdname): + help_msg = cmdln.Cmdln._help_preprocess(self, help, cmdname) + return help_msg % conf.config + + + def do_init(self, subcmd, opts, project, package=None): + """${cmd_name}: Initialize a directory as working copy + + Initialize an existing directory to be a working copy of an + (already existing) buildservice project/package. + + (This is the same as checking out a package and then copying sources + into the directory. It does NOT create a new package. To create a + package, use 'osc meta pkg ... ...') + + You wouldn't normally use this command. + + To get a working copy of a package (e.g. for building it or working on + it, you would normally use the checkout command. Use "osc help + checkout" to get help for it. + + usage: + osc init PRJ + osc init PRJ PAC + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + + if not package: + Project.init_project(apiurl, os.curdir, project, conf.config['do_package_tracking']) + print('Initializing %s (Project: %s)' % (os.curdir, project)) + else: + Package.init_package(apiurl, project, package, os.curdir) + store_write_string(os.curdir, '_files', show_files_meta(apiurl, project, package) + '\n') + print('Initializing %s (Project: %s, Package: %s)' % (os.curdir, project, package)) + + @cmdln.alias('ls') + @cmdln.alias('ll') + @cmdln.alias('lL') + @cmdln.alias('LL') + @cmdln.option('-a', '--arch', metavar='ARCH', + help='specify architecture (only for binaries)') + @cmdln.option('-r', '--repo', metavar='REPO', + help='specify repository (only for binaries)') + @cmdln.option('-b', '--binaries', action='store_true', + help='list built binaries instead of sources') + @cmdln.option('-e', '--expand', action='store_true', + help='expand linked package (only for sources)') + @cmdln.option('-u', '--unexpand', action='store_true', + help='always work with unexpanded (source) packages') + @cmdln.option('-v', '--verbose', action='store_true', + help='print extra information') + @cmdln.option('-l', '--long', action='store_true', dest='verbose', + help='print extra information') + @cmdln.option('-D', '--deleted', action='store_true', + help='show only the former deleted projects or packages') + @cmdln.option('-M', '--meta', action='store_true', + help='list meta data files') + @cmdln.option('-R', '--revision', metavar='REVISION', + help='specify revision (only for sources)') + def do_list(self, subcmd, opts, *args): + """${cmd_name}: List sources or binaries on the server + + Examples for listing sources: + ls # list all projects (deprecated) + ls / # list all projects + ls . # take PROJECT/PACKAGE from current dir. + ls PROJECT # list packages in a project + ls PROJECT PACKAGE # list source files of package of a project + ls PROJECT PACKAGE <file> # list <file> if this file exists + ls -v PROJECT PACKAGE # verbosely list source files of package + ls -l PROJECT PACKAGE # verbosely list source files of package + ll PROJECT PACKAGE # verbosely list source files of package + LL PROJECT PACKAGE # verbosely list source files of expanded link + + With --verbose, the following fields will be shown for each item: + MD5 hash of file + Revision number of the last commit + Size (in bytes) + Date and time of the last commit + + Examples for listing binaries: + ls -b PROJECT # list all binaries of a project + ls -b PROJECT -a ARCH # list ARCH binaries of a project + ls -b PROJECT -r REPO # list binaries in REPO + ls -b PROJECT PACKAGE REPO ARCH + + Usage: + ${cmd_name} [PROJECT [PACKAGE]] + ${cmd_name} -b [PROJECT [PACKAGE [REPO [ARCH]]]] + ${cmd_option_list} + """ + + args = slash_split(args) + if subcmd == 'll': + opts.verbose = True + if subcmd == 'lL' or subcmd == 'LL': + opts.verbose = True + opts.expand = True + + project = None + package = None + fname = None + if len(args) == 0: + # For consistency with *all* other commands + # this lists what the server has in the current wd. + # CAUTION: 'osc ls -b' already works like this. + pass + if len(args) > 0: + project = args[0] + if project == '/': + project = None + if project == '.': + cwd = os.getcwd() + if is_project_dir(cwd): + project = store_read_project(cwd) + elif is_package_dir(cwd): + project = store_read_project(cwd) + package = store_read_package(cwd) + if len(args) > 1: + package = args[1] + if len(args) > 2: + if opts.deleted: + raise oscerr.WrongArgs("Too many arguments when listing deleted packages") + if opts.binaries: + if opts.repo: + if opts.repo != args[2]: + raise oscerr.WrongArgs("conflicting repos specified ('%s' vs '%s')"%(opts.repo, args[2])) + else: + opts.repo = args[2] + else: + fname = args[2] + + if len(args) > 3: + if not opts.binaries: + raise oscerr.WrongArgs('Too many arguments') + if opts.arch: + if opts.arch != args[3]: + raise oscerr.WrongArgs("conflicting archs specified ('%s' vs '%s')"%(opts.arch, args[3])) + else: + opts.arch = args[3] + + + if opts.binaries and opts.expand: + raise oscerr.WrongOptions('Sorry, --binaries and --expand are mutual exclusive.') + + apiurl = self.get_api_url() + + # list binaries + if opts.binaries: + # ls -b toplevel doesn't make sense, so use info from + # current dir if available + if len(args) == 0: + cwd = os.getcwd() + if is_project_dir(cwd): + project = store_read_project(cwd) + elif is_package_dir(cwd): + project = store_read_project(cwd) + package = store_read_package(cwd) + + if not project: + raise oscerr.WrongArgs('There are no binaries to list above project level.') + if opts.revision: + raise oscerr.WrongOptions('Sorry, the --revision option is not supported for binaries.') + + repos = [] + + if opts.repo and opts.arch: + repos.append(Repo(opts.repo, opts.arch)) + elif opts.repo and not opts.arch: + repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.name == opts.repo] + elif opts.arch and not opts.repo: + repos = [repo for repo in get_repos_of_project(apiurl, project) if repo.arch == opts.arch] + else: + repos = get_repos_of_project(apiurl, project) + + results = [] + for repo in repos: + results.append((repo, get_binarylist(apiurl, project, repo.name, repo.arch, package=package, verbose=opts.verbose))) + + for result in results: + indent = '' + if len(results) > 1: + print('%s/%s' % (result[0].name, result[0].arch)) + indent = ' ' + + if opts.verbose: + for f in result[1]: + print("%9d %s %-40s" % (f.size, shorttime(f.mtime), f.name)) + else: + for f in result[1]: + print(indent+f) + + # list sources + elif not opts.binaries: + if not args: + for prj in meta_get_project_list(apiurl, opts.deleted): + print(prj) + + elif len(args) == 1: + if opts.verbose: + if self.options.verbose: + print('Sorry, the --verbose option is not implemented for projects.', file=sys.stderr) + for pkg in meta_get_packagelist(apiurl, project, deleted = opts.deleted, expand = opts.expand): + print(pkg) + + elif len(args) == 2 or len(args) == 3: + link_seen = False + print_not_found = True + rev = opts.revision + for i in [ 1, 2 ]: + l = meta_get_filelist(apiurl, + project, + package, + verbose=opts.verbose, + expand=opts.expand, + meta=opts.meta, + deleted=opts.deleted, + revision=rev) + link_seen = '_link' in l + if opts.verbose: + out = [ '%s %7s %9d %s %s' % (i.md5, i.rev, i.size, shorttime(i.mtime), i.name) \ + for i in l if not fname or fname == i.name ] + if len(out) > 0: + print_not_found = False + print('\n'.join(out)) + elif fname: + if fname in l: + print(fname) + print_not_found = False + else: + print('\n'.join(l)) + if opts.expand or opts.unexpand or not link_seen: + break + m = show_files_meta(apiurl, project, package) + li = Linkinfo() + li.read(ET.fromstring(''.join(m)).find('linkinfo')) + if li.haserror(): + raise oscerr.LinkExpandError(project, package, li.error) + project, package, rev = li.project, li.package, li.rev + if rev: + print('# -> %s %s (%s)' % (project, package, rev)) + else: + print('# -> %s %s (latest)' % (project, package)) + opts.expand = True + if fname and print_not_found: + print('file \'%s\' does not exist' % fname) + + + @cmdln.option('-s', '--skip-disabled', action='store_true', + help='Skip disabled channels. Otherwise the source gets added, but not the repositories.') + @cmdln.option('-e', '--enable-all', action='store_true', + help='Enable all added channels including the ones disabled by default.') + def do_addchannels(self, subcmd, opts, *args): + """${cmd_name}: Add channels to project. + + The command adds all channels which are defined to be used for a given source package. + The source link target is used to lookup the channels. The command can be + used for a certain package or for all in the specified project. + + In case no channel is defined the operation is just returning. + + Examples: + osc addchannels [PROJECT [PACKAGE]] + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + localdir = os.getcwd() + channel = None + if not args: + if is_project_dir(localdir) or is_package_dir(localdir): + project = store_read_project(localdir) + elif is_package_dir(localdir): + project = store_read_project(localdir) + channel = store_read_package(localdir) + else: + raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy') + else: + project = args[0] + + query = {'cmd': 'addchannels'} + + if opts.enable_all and opts.skip_disabled: + raise oscerr.WrongOptions('--enable-all and --skip-disabled options are mutually exclusive') + elif opts.enable_all: + query['mode'] = 'enable_all' + elif opts.skip_disabled: + query['mode'] = 'skip_disabled' + + print("Looking for channels...") + url = makeurl(apiurl, ['source', project], query=query) + if channel: + url = makeurl(apiurl, ['source', project, channel], query=query) + f = http_POST(url) + + @cmdln.alias('enablechannel') + def do_enablechannels(self, subcmd, opts, *args): + """${cmd_name}: Enables channels + + Enables existing channel packages in a project. Enabling means adding the + needed repositories for building. + The command can be used to enable a specific one or all channels of a project. + + Examples: + osc enablechannels [PROJECT [CHANNEL_PACKAGE]] + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + localdir = os.getcwd() + channel = None + if not args: + if is_project_dir(localdir): + project = store_read_project(localdir) + elif is_package_dir(localdir): + project = store_read_project(localdir) + channel = store_read_package(localdir) + else: + raise oscerr.WrongArgs('Either specify project [package] or call it from a project/package working copy') + else: + project = args[0] + if len(args) > 1: + channel = args[1] + + query = {} + if channel: + query['cmd'] = 'enablechannel' + else: + query = {'cmd': 'modifychannels', 'mode': 'enable_all'} + + print("Enable channel(s)...") + url = makeurl(apiurl, ['source', project], query=query) + if channel: + url = makeurl(apiurl, ['source', project, channel], query=query) + f = http_POST(url) + + @cmdln.option('-f', '--force', action='store_true', + help='force generation of new patchinfo file, do not update existing one.') + def do_patchinfo(self, subcmd, opts, *args): + """${cmd_name}: Generate and edit a patchinfo file. + + A patchinfo file describes the packages for an update and the kind of + problem it solves. + + This command either creates a new _patchinfo or updates an existing one. + + Examples: + osc patchinfo + osc patchinfo [PROJECT [PATCH_NAME]] + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + project_dir = localdir = os.getcwd() + patchinfo = 'patchinfo' + if len(args) == 0: + if is_project_dir(localdir): + project = store_read_project(localdir) + apiurl = self.get_api_url() + for p in meta_get_packagelist(apiurl, project): + if p.startswith("_patchinfo") or p.startswith("patchinfo"): + patchinfo = p + else: + if is_package_dir(localdir): + project = store_read_project(localdir) + patchinfo = store_read_package(localdir) + apiurl = self.get_api_url() + if not os.path.exists('_patchinfo'): + sys.exit('Current checked out package has no _patchinfo. Either call it from project level or specify patch name.') + else: + sys.exit('This command must be called in a checked out project or patchinfo package.') + else: + project = args[0] + if len(args) > 1: + patchinfo = args[1] + + filelist = None + if patchinfo: + try: + filelist = meta_get_filelist(apiurl, project, patchinfo) + except HTTPError: + pass + + if opts.force or not filelist or not '_patchinfo' in filelist: + print("Creating new patchinfo...") + query = 'cmd=createpatchinfo&name=' + patchinfo + if opts.force: + query += "&force=1" + url = makeurl(apiurl, ['source', project], query=query) + f = http_POST(url) + for p in meta_get_packagelist(apiurl, project): + if p.startswith("_patchinfo") or p.startswith("patchinfo"): + patchinfo = p + else: + print("Update existing _patchinfo file...") + query = 'cmd=updatepatchinfo' + url = makeurl(apiurl, ['source', project, patchinfo], query=query) + f = http_POST(url) + + # CAUTION: + # Both conf.config['checkout_no_colon'] and conf.config['checkout_rooted'] + # fool this test: + if is_package_dir(localdir): + pac = Package(localdir) + pac.update() + filename = "_patchinfo" + else: + checkout_package(apiurl, project, patchinfo, prj_dir=project_dir) + filename = project_dir + "/" + patchinfo + "/_patchinfo" + + run_editor(filename) + + @cmdln.alias('bsdevelproject') + @cmdln.alias('dp') + @cmdln.option('-r', '--raw', action='store_true', help='deprecated option') + def do_develproject(self, subcmd, opts, *args): + """${cmd_name}: print the devel project / package of a package + + Examples: + osc develproject PRJ PKG + osc develproject + ${cmd_option_list} + """ + args = slash_split(args) + apiurl = self.get_api_url() + + if len(args) == 0: + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + elif len(args) == 2: + project = args[0] + package = args[1] + else: + raise oscerr.WrongArgs('need Project and Package') + + devprj, devpkg = show_devel_project(apiurl, project, package) + if devprj is None: + print('%s / %s has no devel project' % (project, package)) + elif devpkg and devpkg != package: + print("%s %s" % (devprj, devpkg)) + else: + print(devprj) + + @cmdln.alias('sdp') + @cmdln.option('-u', '--unset', action='store_true', + help='remove devel project') + def do_setdevelproject(self, subcmd, opts, *args): + """${cmd_name}: Set the devel project / package of a package + + Examples: + osc setdevelproject [PRJ PKG] DEVPRJ [DEVPKG] + ${cmd_option_list} + """ + args = slash_split(args) + apiurl = self.get_api_url() + + devprj, devpkg = None, None + if len(args) == 3 or len(args) == 4: + project, package = args[0], args[1] + devprj = args[2] + if len(args) == 4: + devpkg = args[3] + elif len(args) >= 1 and len(args) <= 2: + project, package = store_read_project(os.curdir), store_read_package(os.curdir) + devprj = args[0] + if len(args) == 2: + devpkg = args[1] + else: + if opts.unset: + project, package = store_read_project(os.curdir), store_read_package(os.curdir) + else: + raise oscerr.WrongArgs('need at least DEVPRJ (and possibly DEVPKG)') + + set_devel_project(apiurl, project, package, devprj, devpkg) + + + @cmdln.option('-c', '--create', action='store_true', + help='Create a new token') + @cmdln.option('-d', '--delete', metavar='TOKENID', + help='Create a new token') + @cmdln.option('-t', '--trigger', metavar='TOKENID', + help='Trigger the action of a token') + def do_token(self, subcmd, opts, *args): + """${cmd_name}: Show and manage authentication token + + Authentication token can be used to run specific commands without + sending credentials. + + Usage: + osc token + osc token --create [<PROJECT> <PACKAGE>] + osc token --delete <TOKENID> + osc token --trigger <TOKENID> + ${cmd_option_list} + """ + + args = slash_split(args) + + apiurl = self.get_api_url() + url = apiurl + "/person/" + conf.get_apiurl_usr(apiurl) + "/token" + + if opts.create: + print("Create a new token") + url += "?cmd=create" + if len(args) > 1: + url += "&project=" + args[0] + url += "&package=" + args[1] + + f = http_POST(url) + while True: + buf = f.read(16384) + if not buf: + break + sys.stdout.write(buf) + + elif opts.delete: + print("Delete token") + url += "/" + opts.delete + http_DELETE(url) + elif opts.trigger: + print("Trigger token") + url = apiurl + "/trigger/runservice" + req = URLRequest(url) + req.get_method = lambda: "POST" + req.add_header('Content-Type', 'application/octet-stream') + req.add_header('Authorization', "Token "+opts.trigger) + fd = urlopen(req, data=None) + print(fd.read()) + else: + # just list token + for data in streamfile(url, http_GET): + sys.stdout.write(data) + + + @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE', + help='affect only a given attribute') + @cmdln.option('--attribute-defaults', action='store_true', + help='include defined attribute defaults') + @cmdln.option('--attribute-project', action='store_true', + help='include project values, if missing in packages ') + @cmdln.option('-f', '--force', action='store_true', + help='force the save operation, allows one to ignores some errors like depending repositories. For prj meta only.') + @cmdln.option('-F', '--file', metavar='FILE', + help='read metadata from FILE, instead of opening an editor. ' + '\'-\' denotes standard input. ') + @cmdln.option('-e', '--edit', action='store_true', + help='edit metadata') + @cmdln.option('-c', '--create', action='store_true', + help='create attribute without values') + @cmdln.option('-R', '--remove-linking-repositories', action='store_true', + help='Try to remove also all repositories building against remove ones.') + @cmdln.option('-s', '--set', metavar='ATTRIBUTE_VALUES', + help='set attribute values') + @cmdln.option('--delete', action='store_true', + help='delete a pattern or attribute') + def do_meta(self, subcmd, opts, *args): + """${cmd_name}: Show meta information, or edit it + + Show or edit build service metadata of type <prj|pkg|prjconf|user|pattern>. + + This command displays metadata on buildservice objects like projects, + packages, or users. The type of metadata is specified by the word after + "meta", like e.g. "meta prj". + + prj denotes metadata of a buildservice project. + prjconf denotes the (build) configuration of a project. + pkg denotes metadata of a buildservice package. + user denotes the metadata of a user. + pattern denotes installation patterns defined for a project. + + To list patterns, use 'osc meta pattern PRJ'. An additional argument + will be the pattern file to view or edit. + + With the --edit switch, the metadata can be edited. Per default, osc + opens the program specified by the environmental variable EDITOR with a + temporary file. Alternatively, content to be saved can be supplied via + the --file switch. If the argument is '-', input is taken from stdin: + osc meta prjconf home:user | sed ... | osc meta prjconf home:user -F - + + When trying to edit a non-existing resource, it is created implicitly. + + + Examples: + osc meta prj PRJ + osc meta pkg PRJ PKG + osc meta pkg PRJ PKG -e + + Usage: + osc meta <prj|pkg|prjconf|user|pattern> ARGS... + osc meta <prj|pkg|prjconf|user|pattern> -e|--edit ARGS... + osc meta <prj|pkg|prjconf|user|pattern> -F|--file ARGS... + osc meta pattern --delete PRJ PATTERN + osc meta attribute PRJ [PKG [SUBPACKAGE]] [--attribute ATTRIBUTE] [--create|--delete|--set [value_list]] + ${cmd_option_list} + """ + + args = slash_split(args) + + if not args or args[0] not in metatypes.keys(): + raise oscerr.WrongArgs('Unknown meta type. Choose one of %s.' \ + % ', '.join(metatypes)) + + cmd = args[0] + del args[0] + + if cmd in ['pkg']: + min_args, max_args = 0, 2 + elif cmd in ['pattern']: + min_args, max_args = 1, 2 + elif cmd in ['attribute']: + min_args, max_args = 1, 3 + elif cmd in ['prj', 'prjconf']: + min_args, max_args = 0, 1 + else: + min_args, max_args = 1, 1 + + if len(args) < min_args: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) > max_args: + raise oscerr.WrongArgs('Too many arguments.') + + apiurl = self.get_api_url() + + # Specific arguments + # + # If project or package arguments missing, assume to work + # with project and/or package in current local directory. + attributepath = [] + if cmd in ['prj', 'prjconf']: + if len(args) < 1: + apiurl = store_read_apiurl(os.curdir) + project = store_read_project(os.curdir) + else: + project = args[0] + + elif cmd == 'pkg': + if len(args) < 2: + apiurl = store_read_apiurl(os.curdir) + project = store_read_project(os.curdir) + if len(args) < 1: + package = store_read_package(os.curdir) + else: + package = args[0] + else: + project = args[0] + package = args[1] + + elif cmd == 'attribute': + project = args[0] + if len(args) > 1: + package = args[1] + else: + package = None + if opts.attribute_project: + raise oscerr.WrongOptions('--attribute-project works only when also a package is given') + if len(args) > 2: + subpackage = args[2] + else: + subpackage = None + attributepath.append('source') + attributepath.append(project) + if package: + attributepath.append(package) + if subpackage: + attributepath.append(subpackage) + attributepath.append('_attribute') + elif cmd == 'user': + user = args[0] + elif cmd == 'pattern': + project = args[0] + if len(args) > 1: + pattern = args[1] + else: + pattern = None + # enforce pattern argument if needed + if opts.edit or opts.file: + raise oscerr.WrongArgs('A pattern file argument is required.') + + # show + if not opts.edit and not opts.file and not opts.delete and not opts.create and not opts.set: + if cmd == 'prj': + sys.stdout.write(''.join(show_project_meta(apiurl, project))) + elif cmd == 'pkg': + sys.stdout.write(''.join(show_package_meta(apiurl, project, package))) + elif cmd == 'attribute': + sys.stdout.write(''.join(show_attribute_meta(apiurl, project, package, subpackage, + opts.attribute, opts.attribute_defaults, opts.attribute_project))) + elif cmd == 'prjconf': + sys.stdout.write(''.join(show_project_conf(apiurl, project))) + elif cmd == 'user': + r = get_user_meta(apiurl, user) + if r: + sys.stdout.write(''.join(r)) + elif cmd == 'pattern': + if pattern: + r = show_pattern_meta(apiurl, project, pattern) + if r: + sys.stdout.write(''.join(r)) + else: + r = show_pattern_metalist(apiurl, project) + if r: + sys.stdout.write('\n'.join(r) + '\n') + + # edit + if opts.edit and not opts.file: + if cmd == 'prj': + edit_meta(metatype='prj', + edit=True, + force=opts.force, + remove_linking_repositories=opts.remove_linking_repositories, + path_args=quote_plus(project), + apiurl=apiurl, + template_args=({ + 'name': project, + 'user': conf.get_apiurl_usr(apiurl)})) + elif cmd == 'pkg': + edit_meta(metatype='pkg', + edit=True, + path_args=(quote_plus(project), quote_plus(package)), + apiurl=apiurl, + template_args=({ + 'name': package, + 'user': conf.get_apiurl_usr(apiurl)})) + elif cmd == 'prjconf': + edit_meta(metatype='prjconf', + edit=True, + path_args=quote_plus(project), + apiurl=apiurl, + template_args=None) + elif cmd == 'user': + edit_meta(metatype='user', + edit=True, + path_args=(quote_plus(user)), + apiurl=apiurl, + template_args=({'user': user})) + elif cmd == 'pattern': + edit_meta(metatype='pattern', + edit=True, + path_args=(project, pattern), + apiurl=apiurl, + template_args=None) + + # create attribute entry + if (opts.create or opts.set) and cmd == 'attribute': + if not opts.attribute: + raise oscerr.WrongOptions('no attribute given to create') + values = '' + if opts.set: + opts.set = opts.set.replace('&', '&').replace('<', '<').replace('>', '>') + for i in opts.set.split(','): + values += '<value>%s</value>' % i + aname = opts.attribute.split(":") + if len(aname) != 2: + raise oscerr.WrongOptions('Given attribute is not in "NAMESPACE:NAME" style') + d = '<attributes><attribute namespace=\'%s\' name=\'%s\' >%s</attribute></attributes>' % (aname[0], aname[1], values) + url = makeurl(apiurl, attributepath) + for data in streamfile(url, http_POST, data=d): + sys.stdout.write(data) + + # upload file + if opts.file: + + if opts.file == '-': + f = sys.stdin.read() + else: + try: + f = open(opts.file).read() + except: + sys.exit('could not open file \'%s\'.' % opts.file) + + if cmd == 'prj': + edit_meta(metatype='prj', + data=f, + edit=opts.edit, + force=opts.force, + remove_linking_repositories=opts.remove_linking_repositories, + apiurl=apiurl, + path_args=quote_plus(project)) + elif cmd == 'pkg': + edit_meta(metatype='pkg', + data=f, + edit=opts.edit, + apiurl=apiurl, + path_args=(quote_plus(project), quote_plus(package))) + elif cmd == 'prjconf': + edit_meta(metatype='prjconf', + data=f, + edit=opts.edit, + apiurl=apiurl, + path_args=quote_plus(project)) + elif cmd == 'user': + edit_meta(metatype='user', + data=f, + edit=opts.edit, + apiurl=apiurl, + path_args=(quote_plus(user))) + elif cmd == 'pattern': + edit_meta(metatype='pattern', + data=f, + edit=opts.edit, + apiurl=apiurl, + path_args=(project, pattern)) + + + # delete + if opts.delete: + path = metatypes[cmd]['path'] + if cmd == 'pattern': + path = path % (project, pattern) + u = makeurl(apiurl, [path]) + http_DELETE(u) + elif cmd == 'attribute': + if not opts.attribute: + raise oscerr.WrongOptions('no attribute given to create') + attributepath.append(opts.attribute) + u = makeurl(apiurl, attributepath) + for data in streamfile(u, http_DELETE): + sys.stdout.write(data) + else: + raise oscerr.WrongOptions('The --delete switch is only for pattern metadata or attributes.') + + + # TODO: rewrite and consolidate the current submitrequest/createrequest "mess" + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-r', '--revision', metavar='REV', + help='specify a certain source revision ID (the md5 sum) for the source package') + @cmdln.option('-s', '--supersede', metavar='SUPERSEDE', + help='Superseding another request by this one') + @cmdln.option('--nodevelproject', action='store_true', + help='do not follow a defined devel project ' \ + '(primary project where a package is developed)') + @cmdln.option('--seperate-requests', action='store_true', + help='Create multiple request instead of a single one (when command is used for entire project)') + @cmdln.option('--cleanup', action='store_true', + help='remove package if submission gets accepted (default for home:<id>:branch projects)') + @cmdln.option('--no-cleanup', action='store_true', + help='never remove source package on accept, but update its content') + @cmdln.option('--no-update', action='store_true', + help='never touch source package on accept (will break source links)') + @cmdln.option('--update-link', action='store_true', + help='This transfers the source including the _link file.') + @cmdln.option('-d', '--diff', action='store_true', + help='show diff only instead of creating the actual request') + @cmdln.option('--yes', action='store_true', + help='proceed without asking.') + @cmdln.alias("sr") + @cmdln.alias("submitreq") + @cmdln.alias("submitpac") + def do_submitrequest(self, subcmd, opts, *args): + """${cmd_name}: Create request to submit source into another Project + + [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration for information + on this topic.] + + See the "request" command for showing and modifing existing requests. + + usage: + osc submitreq [OPTIONS] + osc submitreq [OPTIONS] DESTPRJ [DESTPKG] + osc submitreq [OPTIONS] SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG] + + osc submitpac ... is a shorthand for osc submitreq --cleanup ... + + ${cmd_option_list} + """ + + if opts.cleanup and opts.no_cleanup: + raise oscerr.WrongOptions('\'--cleanup\' and \'--no-cleanup\' are mutually exclusive') + + src_update = conf.config['submitrequest_on_accept_action'] or None + # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server + + if subcmd == 'submitpac' and not opts.no_cleanup: + opts.cleanup = True + + if opts.cleanup: + src_update = "cleanup" + elif opts.no_cleanup: + src_update = "update" + elif opts.no_update: + src_update = "noupdate" + + myreqs = [] + if opts.supersede: + myreqs = [opts.supersede] + + args = slash_split(args) + + # remove this block later again + oldcmds = ['create', 'list', 'log', 'show', 'decline', 'accept', 'delete', 'revoke'] + if args and args[0] in oldcmds: + print("************************************************************************", file=sys.stderr) + print("* WARNING: It looks that you are using this command with a *", file=sys.stderr) + print("* deprecated syntax. *", file=sys.stderr) + print("* Please run \"osc sr --help\" and \"osc rq --help\" *", file=sys.stderr) + print("* to see the new syntax. *", file=sys.stderr) + print("************************************************************************", file=sys.stderr) + if args[0] == 'create': + args.pop(0) + else: + sys.exit(1) + + if len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 2 and is_project_dir(os.getcwd()): + sys.exit('You can not specify a target package when submitting an entire project\n') + + apiurl = self.get_api_url() + + if len(args) < 2 and is_project_dir(os.getcwd()): + if opts.diff: + raise oscerr.WrongOptions('\'--diff\' is not supported in a project working copy') + import cgi + project = store_read_project(os.curdir) + + sr_ids = [] + # for single request + actionxml = "" + options_block = "<options>" + if src_update: + options_block += """<sourceupdate>%s</sourceupdate>""" % (src_update) + if opts.update_link: + options_block + """<updatelink>true</updatelink></options> """ + options_block += "</options>" + + # loop via all packages for checking their state + for p in meta_get_packagelist(apiurl, project): + # get _link info from server, that knows about the local state ... + u = makeurl(apiurl, ['source', project, p]) + f = http_GET(u) + root = ET.parse(f).getroot() + target_project = None + if len(args) == 1: + target_project = args[0] + linkinfo = root.find('linkinfo') + if linkinfo == None: + if len(args) < 1: + print("Package ", p, " is not a source link and no target specified.") + sys.exit("This is currently not supported.") + else: + if linkinfo.get('error'): + print("Package ", p, " is a broken source link.") + sys.exit("Please fix this first") + t = linkinfo.get('project') + if t: + if target_project == None: + target_project = t + if len(root.findall('entry')) > 1: # This is not really correct, but should work mostly + # Real fix is to ask the api if sources are modificated + # but there is no such call yet. + print("Submitting package ", p) + else: + print(" Skipping not modified package ", p) + continue + else: + print("Skipping package ", p, " since it is a source link pointing inside the project.") + continue + + serviceinfo = root.find('serviceinfo') + if serviceinfo != None: + if serviceinfo.get('code') != "succeeded": + print("Package ", p, " has a ", serviceinfo.get('code'), " source service") + sys.exit("Please fix this first") + if serviceinfo.get('error'): + print("Package ", p, " contains a failed source service.") + sys.exit("Please fix this first") + + # submitting this package + if opts.seperate_requests: + # create a single request + result = create_submit_request(apiurl, project, p) + if not result: + sys.exit("submit request creation failed") + sr_ids.append(result) + else: + s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \ + (project, p, t, p, options_block) + actionxml += s + + if actionxml != "": + xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \ + (actionxml, cgi.escape(opts.message or "")) + u = makeurl(apiurl, ['request'], query='cmd=create&addrevision=1') + f = http_POST(u, data=xml) + + root = ET.parse(f).getroot() + sr_ids.append(root.get('id')) + + print("Request created: ", end=' ') + for i in sr_ids: + print(i, end=' ') + + # was this project created by clone request ? + u = makeurl(apiurl, ['source', project, '_attribute', 'OBS:RequestCloned']) + f = http_GET(u) + root = ET.parse(f).getroot() + value = root.findtext('attribute/value') + if value and not opts.yes: + repl = '' + print('\n\nThere are already following submit request: %s.' % \ + ', '.join([str(i) for i in myreqs ])) + repl = raw_input('\nSupersede the old requests? (y/n) ') + if repl.lower() == 'y': + myreqs += [ value ] + + if len(myreqs) > 0: + for req in myreqs: + change_request_state(apiurl, str(req), 'superseded', + 'superseded by %s' % result, result) + + sys.exit('Successfully finished') + + elif len(args) <= 2: + # try using the working copy at hand + p = findpacs(os.curdir)[0] + src_project = p.prjname + src_package = p.name + apiurl = p.apiurl + if len(args) == 0 and p.islink(): + dst_project = p.linkinfo.project + dst_package = p.linkinfo.package + elif len(args) > 0: + dst_project = args[0] + if len(args) == 2: + dst_package = args[1] + else: + if p.islink(): + dst_package = p.linkinfo.package + else: + dst_package = src_package + else: + sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n' + 'Please provide it the target via commandline arguments.' % p.name) + + modified = [i for i in p.filenamelist if not p.status(i) in (' ', '?', 'S')] + if len(modified) > 0 and not opts.yes: + print('Your working copy has local modifications.') + repl = raw_input('Proceed without committing the local changes? (y|N) ') + if repl != 'y': + raise oscerr.UserAbort() + elif len(args) >= 3: + # get the arguments from the commandline + src_project, src_package, dst_project = args[0:3] + if len(args) == 4: + dst_package = args[3] + else: + dst_package = src_package + else: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('request')) + + # check for running source service + u = makeurl(apiurl, ['source', src_project, src_package]) + f = http_GET(u) + root = ET.parse(f).getroot() + serviceinfo = root.find('serviceinfo') + if serviceinfo != None: + if serviceinfo.get('code') != "succeeded": + print("Package ", src_package, " has a ", serviceinfo.get('code'), " source service") + sys.exit("Please fix this first") + if serviceinfo.get('error'): + print("Package ", src_package, " contains a failed source service.") + sys.exit("Please fix this first") + + if not opts.nodevelproject: + devloc = None + try: + devloc, _ = show_devel_project(apiurl, dst_project, dst_package) + except HTTPError: + print("""\ +Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \ + % (dst_project, dst_package), file=sys.stderr) + + if devloc and \ + dst_project != devloc and \ + src_project != devloc: + print("""\ +A different project, %s, is defined as the place where development +of the package %s primarily takes place. +Please submit there instead, or use --nodevelproject to force direct submission.""" \ + % (devloc, dst_package)) + if not opts.diff: + sys.exit(1) + + rev = opts.revision + if not rev: + # get _link info from server, that knows about the local state ... + u = makeurl(apiurl, ['source', src_project, src_package], query="expand=1") + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + rev = root.get('rev') + else: + if linkinfo.get('project') != dst_project or linkinfo.get('package') != dst_package: + # the submit target is not link target. use merged md5sum references to + # avoid not mergable sources when multiple request from same source get created. + rev = root.get('srcmd5') + + rdiff = None + if opts.diff or not opts.message: + try: + rdiff = 'old: %s/%s\nnew: %s/%s rev %s\n' % (dst_project, dst_package, src_project, src_package, rev) + rdiff += server_diff(apiurl, + dst_project, dst_package, None, + src_project, src_package, rev, True) + except: + rdiff = '' + + if opts.diff: + run_pager(rdiff) + return + supersede_existing = False + reqs = [] + if not opts.supersede: + (supersede_existing, reqs) = check_existing_requests(apiurl, + src_project, + src_package, + dst_project, + dst_package) + if not supersede_existing: + (supersede_existing, reqs) = check_existing_maintenance_requests(apiurl, + src_project, + [src_package], + dst_project, None) + if not opts.message: + difflines = [] + doappend = False + changes_re = re.compile(r'^--- .*\.changes ') + for line in rdiff.split('\n'): + if line.startswith('--- '): + if changes_re.match(line): + doappend = True + else: + doappend = False + if doappend: + difflines.append(line) + opts.message = edit_message(footer=rdiff, template='\n'.join(parse_diff_for_commit_message('\n'.join(difflines)))) + + result = create_submit_request(apiurl, + src_project, src_package, + dst_project, dst_package, + opts.message, orev=rev, + src_update=src_update, dst_updatelink=opts.update_link) + print('created request id', result) + + if supersede_existing: + for req in reqs: + change_request_state(apiurl, req.reqid, 'superseded', + 'superseded by %s' % result, result) + + if opts.supersede: + change_request_state(apiurl, opts.supersede, 'superseded', + opts.message or '', result) + + def _actionparser(self, opt_str, value, parser): + value = [] + if not hasattr(parser.values, 'actiondata'): + setattr(parser.values, 'actiondata', []) + if parser.values.actions == None: + parser.values.actions = [] + + rargs = parser.rargs + while rargs: + arg = rargs[0] + if ((arg[:2] == "--" and len(arg) > 2) or + (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")): + break + else: + value.append(arg) + del rargs[0] + + parser.values.actions.append(value[0]) + del value[0] + parser.values.actiondata.append(value) + + def _submit_request(self, args, opts, options_block): + actionxml = "" + apiurl = self.get_api_url() + if len(args) == 0 and is_project_dir(os.getcwd()): + # submit requests for multiple packages are currently handled via multiple requests + # They could be also one request with multiple actions, but that avoids to accepts parts of it. + project = store_read_project(os.curdir) + + pi = [] + pac = [] + targetprojects = [] + # loop via all packages for checking their state + for p in meta_get_packagelist(apiurl, project): + if p.startswith("_patchinfo:"): + pi.append(p) + else: + # get _link info from server, that knows about the local state ... + u = makeurl(apiurl, ['source', project, p]) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + print("Package ", p, " is not a source link.") + sys.exit("This is currently not supported.") + if linkinfo.get('error'): + print("Package ", p, " is a broken source link.") + sys.exit("Please fix this first") + t = linkinfo.get('project') + if t: + rdiff = '' + try: + rdiff = server_diff(apiurl, t, p, opts.revision, project, p, None, True) + except: + rdiff = '' + + if rdiff != '': + targetprojects.append(t) + pac.append(p) + else: + print("Skipping package ", p, " since it has no difference with the target package.") + else: + print("Skipping package ", p, " since it is a source link pointing inside the project.") + + # loop via all packages to do the action + for p in pac: + s = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \ + (project, p, opts.revision or show_upstream_rev(apiurl, project, p), t, p, options_block) + actionxml += s + + # create submit requests for all found patchinfos + for p in pi: + for t in targetprojects: + s = """<action type="submit"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> %s </action>""" % \ + (project, p, t, p, options_block) + actionxml += s + + return actionxml, [] + + elif len(args) <= 2: + # try using the working copy at hand + p = findpacs(os.curdir)[0] + src_project = p.prjname + src_package = p.name + if len(args) == 0 and p.islink(): + dst_project = p.linkinfo.project + dst_package = p.linkinfo.package + elif len(args) > 0: + dst_project = args[0] + if len(args) == 2: + dst_package = args[1] + else: + dst_package = src_package + else: + sys.exit('Package \'%s\' is not a source link, so I cannot guess the submit target.\n' + 'Please provide it the target via commandline arguments.' % p.name) + + modified = [i for i in p.filenamelist if p.status(i) != ' ' and p.status(i) != '?'] + if len(modified) > 0: + print('Your working copy has local modifications.') + repl = raw_input('Proceed without committing the local changes? (y|N) ') + if repl != 'y': + sys.exit(1) + elif len(args) >= 3: + # get the arguments from the commandline + src_project, src_package, dst_project = args[0:3] + if len(args) == 4: + dst_package = args[3] + else: + dst_package = src_package + else: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('request')) + + if not opts.nodevelproject: + devloc = None + try: + devloc, _ = show_devel_project(apiurl, dst_project, dst_package) + except HTTPError: + print("""\ +Warning: failed to fetch meta data for '%s' package '%s' (new package?) """ \ + % (dst_project, dst_package), file=sys.stderr) + + if devloc and \ + dst_project != devloc and \ + src_project != devloc: + print("""\ +A different project, %s, is defined as the place where development +of the package %s primarily takes place. +Please submit there instead, or use --nodevelproject to force direct submission.""" \ + % (devloc, dst_package)) + sys.exit(1) + + reqs = get_request_list(apiurl, dst_project, dst_package, req_type='submit', req_state=['new', 'review']) + user = conf.get_apiurl_usr(apiurl) + myreqs = [ i for i in reqs if i.state.who == user and i.reqid != opts.supersede ] + repl = 'y' + if len(myreqs) > 0 and not opts.yes: + print('You already created the following submit request: %s.' % \ + ', '.join([i.reqid for i in myreqs ])) + repl = raw_input('Supersede the old requests? (y/n/c) ') + if repl.lower() == 'c': + print('Aborting', file=sys.stderr) + sys.exit(1) + elif repl.lower() != 'y': + myreqs = [] + + actionxml = """<action type="submit"> <source project="%s" package="%s" rev="%s"/> <target project="%s" package="%s"/> %s </action>""" % \ + (src_project, src_package, opts.revision or show_upstream_rev(apiurl, src_project, src_package), dst_project, dst_package, options_block) + if opts.supersede: + myreqs.append(opts.supersede) + + #print 'created request id', result + return actionxml, myreqs + + def _delete_request(self, args, opts): + if len(args) < 1: + raise oscerr.WrongArgs('Please specify at least a project.') + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + + package = "" + if len(args) > 1: + package = """package="%s" """ % (args[1]) + actionxml = """<action type="delete"> <target project="%s" %s/> </action> """ % (args[0], package) + return actionxml + + def _changedevel_request(self, args, opts): + if len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 0 and is_package_dir('.') and find_default_project(): + wd = os.curdir + devel_project = store_read_project(wd) + devel_package = package = store_read_package(wd) + project = find_default_project(self.get_api_url(), package) + else: + if len(args) < 3: + raise oscerr.WrongArgs('Too few arguments.') + + devel_project = args[2] + project = args[0] + package = args[1] + devel_package = package + if len(args) > 3: + devel_package = args[3] + + actionxml = """ <action type="change_devel"> <source project="%s" package="%s" /> <target project="%s" package="%s" /> </action> """ % \ + (devel_project, devel_package, project, package) + + return actionxml + + def _add_me(self, args, opts): + if len(args) > 3: + raise oscerr.WrongArgs('Too many arguments.') + if len(args) < 2: + raise oscerr.WrongArgs('Too few arguments.') + + apiurl = self.get_api_url() + + user = conf.get_apiurl_usr(apiurl) + role = args[0] + project = args[1] + actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \ + (project, user, role) + + if len(args) > 2: + package = args[2] + actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \ + (project, package, user, role) + + if get_user_meta(apiurl, user) == None: + raise oscerr.WrongArgs('osc: an error occured.') + + return actionxml + + def _add_user(self, args, opts): + if len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + if len(args) < 3: + raise oscerr.WrongArgs('Too few arguments.') + + apiurl = self.get_api_url() + + user = args[0] + role = args[1] + project = args[2] + actionxml = """ <action type="add_role"> <target project="%s" /> <person name="%s" role="%s" /> </action> """ % \ + (project, user, role) + + if len(args) > 3: + package = args[3] + actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <person name="%s" role="%s" /> </action> """ % \ + (project, package, user, role) + + if get_user_meta(apiurl, user) == None: + raise oscerr.WrongArgs('osc: an error occured.') + + return actionxml + + def _add_group(self, args, opts): + if len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + if len(args) < 3: + raise oscerr.WrongArgs('Too few arguments.') + + apiurl = self.get_api_url() + + group = args[0] + role = args[1] + project = args[2] + actionxml = """ <action type="add_role"> <target project="%s" /> <group name="%s" role="%s" /> </action> """ % \ + (project, group, role) + + if len(args) > 3: + package = args[3] + actionxml = """ <action type="add_role"> <target project="%s" package="%s" /> <group name="%s" role="%s" /> </action> """ % \ + (project, package, group, role) + + if get_group(apiurl, group) == None: + raise oscerr.WrongArgs('osc: an error occured.') + + return actionxml + + def _set_bugowner(self, args, opts): + if len(args) > 3: + raise oscerr.WrongArgs('Too many arguments.') + if len(args) < 2: + raise oscerr.WrongArgs('Too few arguments.') + + apiurl = self.get_api_url() + + user = args[0] + project = args[1] + package = "" + if len(args) > 2: + package = """package="%s" """ % (args[2]) + + if user.startswith('group:'): + group = user.replace('group:', '') + actionxml = """ <action type="set_bugowner"> <target project="%s" %s /> <group name="%s" /> </action> """ % \ + (project, package, group) + if get_group(apiurl, group) == None: + raise oscerr.WrongArgs('osc: an error occured.') + else: + actionxml = """ <action type="set_bugowner"> <target project="%s" %s /> <person name="%s" /> </action> """ % \ + (project, package, user) + if get_user_meta(apiurl, user) == None: + raise oscerr.WrongArgs('osc: an error occured.') + + + return actionxml + + @cmdln.option('-a', '--action', action='callback', callback = _actionparser, dest = 'actions', + help='specify action type of a request, can be : submit/delete/change_devel/add_role/set_bugowner') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-r', '--revision', metavar='REV', + help='for "create", specify a certain source revision ID (the md5 sum)') + @cmdln.option('-s', '--supersede', metavar='SUPERSEDE', + help='Superseding another request by this one') + @cmdln.option('--nodevelproject', action='store_true', + help='do not follow a defined devel project ' \ + '(primary project where a package is developed)') + @cmdln.option('--cleanup', action='store_true', + help='remove package if submission gets accepted (default for home:<id>:branch projects)') + @cmdln.option('--no-cleanup', action='store_true', + help='never remove source package on accept, but update its content') + @cmdln.option('--no-update', action='store_true', + help='never touch source package on accept (will break source links)') + @cmdln.option('--yes', action='store_true', + help='proceed without asking.') + @cmdln.alias("creq") + def do_createrequest(self, subcmd, opts, *args): + """${cmd_name}: create multiple requests with a single command + + usage: + osc creq [OPTIONS] [ + -a submit SOURCEPRJ SOURCEPKG DESTPRJ [DESTPKG] + -a delete PROJECT [PACKAGE] + -a change_devel PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE] + -a add_me ROLE PROJECT [PACKAGE] + -a add_group GROUP ROLE PROJECT [PACKAGE] + -a add_role USER ROLE PROJECT [PACKAGE] + -a set_bugowner USER PROJECT [PACKAGE] + ] + + Option -m works for all types of request, the rest work only for submit. + example: + osc creq -a submit -a delete home:someone:branches:openSUSE:Tools -a change_devel openSUSE:Tools osc home:someone:branches:openSUSE:Tools -m ok + + This will submit all modified packages under current directory, delete project home:someone:branches:openSUSE:Tools and change the devel project to home:someone:branches:openSUSE:Tools for package osc in project openSUSE:Tools. + ${cmd_option_list} + """ + src_update = conf.config['submitrequest_on_accept_action'] or None + # we should check here for home:<id>:branch and default to update, but that would require OBS 1.7 server + if opts.cleanup: + src_update = "cleanup" + elif opts.no_cleanup: + src_update = "update" + elif opts.no_update: + src_update = "noupdate" + + options_block = "" + if src_update: + options_block = """<options><sourceupdate>%s</sourceupdate></options> """ % (src_update) + + args = slash_split(args) + + apiurl = self.get_api_url() + + i = 0 + actionsxml = "" + supersede = [] + for ai in opts.actions: + if ai == 'submit': + args = opts.actiondata[i] + i = i+1 + actions, to_supersede = self._submit_request(args, opts, options_block) + actionsxml += actions + supersede.extend(to_supersede) + elif ai == 'delete': + args = opts.actiondata[i] + actionsxml += self._delete_request(args, opts) + i = i+1 + elif ai == 'change_devel': + args = opts.actiondata[i] + actionsxml += self._changedevel_request(args, opts) + i = i+1 + elif ai == 'add_me': + args = opts.actiondata[i] + actionsxml += self._add_me(args, opts) + i = i+1 + elif ai == 'add_group': + args = opts.actiondata[i] + actionsxml += self._add_group(args, opts) + i = i+1 + elif ai == 'add_role': + args = opts.actiondata[i] + actionsxml += self._add_user(args, opts) + i = i+1 + elif ai == 'set_bugowner': + args = opts.actiondata[i] + actionsxml += self._set_bugowner(args, opts) + i = i+1 + else: + raise oscerr.WrongArgs('Unsupported action %s' % ai) + if actionsxml == "": + sys.exit('No actions need to be taken.') + + if not opts.message: + opts.message = edit_message() + + import cgi + xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \ + (actionsxml, cgi.escape(opts.message or "")) + u = makeurl(apiurl, ['request'], query='cmd=create') + f = http_POST(u, data=xml) + + root = ET.parse(f).getroot() + rid = root.get('id') + for srid in supersede: + change_request_state(apiurl, srid, 'superseded', + 'superseded by %s' % rid, rid) + return rid + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-r', '--role', metavar='role', + help='specify user role (default: maintainer)') + @cmdln.alias("reqbugownership") + @cmdln.alias("requestbugownership") + @cmdln.alias("reqmaintainership") + @cmdln.alias("reqms") + @cmdln.alias("reqbs") + def do_requestmaintainership(self, subcmd, opts, *args): + """${cmd_name}: requests to add user as maintainer or bugowner + + usage: + osc requestmaintainership # for current user in checked out package + osc requestmaintainership USER # for specified user in checked out package + osc requestmaintainership PROJECT # for current user if cwd is not a checked out package + osc requestmaintainership PROJECT PACKAGE # for current user + osc requestmaintainership PROJECT PACKAGE USER # request for specified user + osc requestmaintainership PROJECT PACKAGE group:NAME # request for specified group + + osc requestbugownership ... # accepts same parameters but uses bugowner role + + ${cmd_option_list} + """ + import cgi + args = slash_split(args) + apiurl = self.get_api_url() + + if len(args) == 2: + project = args[0] + package = args[1] + user = conf.get_apiurl_usr(apiurl) + elif len(args) == 3: + project = args[0] + package = args[1] + user = args[2] + elif len(args) < 2 and is_package_dir(os.curdir): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + if len(args) == 0: + user = conf.get_apiurl_usr(apiurl) + else: + user = args[0] + elif len(args) == 1: + user = conf.get_apiurl_usr(apiurl) + project = args[0] + package = None + else: + raise oscerr.WrongArgs('Wrong number of arguments.') + + role = 'maintainer' + if subcmd in ( 'reqbugownership', 'requestbugownership', 'reqbs' ): + role = 'bugowner' + if opts.role: + role = opts.role + if not role in ('maintainer', 'bugowner'): + raise oscerr.WrongOptions('invalid \'--role\': either specify \'maintainer\' or \'bugowner\'') + if not opts.message: + opts.message = edit_message() + + r = Request() + if user.startswith('group:'): + group = user.replace('group:', '') + if role == 'bugowner': + r.add_action('set_bugowner', tgt_project=project, tgt_package=package, + group_name=group) + else: + r.add_action('add_role', tgt_project=project, tgt_package=package, + group_name=group, group_role=role) + elif role == 'bugowner': + r.add_action('set_bugowner', tgt_project=project, tgt_package=package, + person_name=user) + else: + r.add_action('add_role', tgt_project=project, tgt_package=package, + person_name=user, person_role=role) + r.description = cgi.escape(opts.message or '') + r.create(apiurl) + print(r.reqid) + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-r', '--repository', metavar='REPOSITORY', + help='specify repository') + @cmdln.option('--accept-in-hours', metavar='HOURS', + help='specify time when request shall get accepted automatically. Only works with write permissions in target.') + @cmdln.alias("dr") + @cmdln.alias("dropreq") + @cmdln.alias("droprequest") + @cmdln.alias("deletereq") + def do_deleterequest(self, subcmd, opts, *args): + """${cmd_name}: Request to delete (or 'drop') a package or project + + usage: + osc deletereq [-m TEXT] # works in checked out project/package + osc deletereq [-m TEXT] PROJECT [PACKAGE] + osc deletereq [-m TEXT] PROJECT [--repository REPOSITORY] + ${cmd_option_list} + """ + import cgi + + args = slash_split(args) + + project = None + package = None + repository = None + + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + elif len(args) == 1: + project = args[0] + elif len(args) == 2: + project = args[0] + package = args[1] + elif is_project_dir(os.getcwd()): + project = store_read_project(os.curdir) + elif is_package_dir(os.getcwd()): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + else: + raise oscerr.WrongArgs('Please specify at least a project.') + + if opts.repository: + repository = opts.repository + + if not opts.message: + import textwrap + if package is not None: + footer = textwrap.TextWrapper(width = 66).fill( + 'please explain why you like to delete package %s of project %s' + % (package, project)) + else: + footer = textwrap.TextWrapper(width = 66).fill( + 'please explain why you like to delete project %s' % project) + opts.message = edit_message(footer) + + r = Request() + r.add_action('delete', tgt_project=project, tgt_package=package, tgt_repository=repository) + r.description = cgi.escape(opts.message) + if opts.accept_in_hours: + r.accept_at_in_hours(int(opts.accept_in_hours)) + r.create(self.get_api_url()) + print(r.reqid) + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.alias("cr") + @cmdln.alias("changedevelreq") + def do_changedevelrequest(self, subcmd, opts, *args): + """${cmd_name}: Create request to change the devel package definition. + + [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration + for information on this topic.] + + See the "request" command for showing and modifing existing requests. + + osc changedevelrequest PROJECT PACKAGE DEVEL_PROJECT [DEVEL_PACKAGE] + """ + import cgi + + if len(args) == 0 and is_package_dir('.') and find_default_project(): + wd = os.curdir + devel_project = store_read_project(wd) + devel_package = package = store_read_package(wd) + project = find_default_project(self.get_api_url(), package) + elif len(args) < 3: + raise oscerr.WrongArgs('Too few arguments.') + elif len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + else: + devel_project = args[2] + project = args[0] + package = args[1] + devel_package = package + if len(args) == 4: + devel_package = args[3] + + if not opts.message: + import textwrap + footer = textwrap.TextWrapper(width = 66).fill( + 'please explain why you like to change the devel project of %s/%s to %s/%s' + % (project, package, devel_project, devel_package)) + opts.message = edit_message(footer) + + r = Request() + r.add_action('change_devel', src_project=devel_project, src_package=devel_package, + tgt_project=project, tgt_package=package) + r.description = cgi.escape(opts.message) + r.create(self.get_api_url()) + print(r.reqid) + + + @cmdln.option('-d', '--diff', action='store_true', + help='generate a diff') + @cmdln.option('-u', '--unified', action='store_true', + help='output the diff in the unified diff format') + @cmdln.option('--no-devel', action='store_true', + help='Do not attempt to forward to devel project') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-t', '--type', metavar='TYPE', + help='limit to requests which contain a given action type (submit/delete/change_devel)') + @cmdln.option('-a', '--all', action='store_true', + help='all states. Same as\'-s all\'') + @cmdln.option('-f', '--force', action='store_true', + help='enforce state change, can be used to ignore open reviews') + @cmdln.option('-s', '--state', default='', # default is 'all' if no args given, 'declined,new,review' otherwise + help='only list requests in one of the comma separated given states (new/review/accepted/revoked/declined) or "all" [default="declined,new,review", or "all", if no args given]') + @cmdln.option('-D', '--days', metavar='DAYS', + help='only list requests in state "new" or changed in the last DAYS. [default=%(request_list_days)s]') + @cmdln.option('-U', '--user', metavar='USER', + help='requests or reviews limited for the specified USER') + @cmdln.option('-G', '--group', metavar='GROUP', + help='requests or reviews limited for the specified GROUP') + @cmdln.option('-P', '--project', metavar='PROJECT', + help='requests or reviews limited for the specified PROJECT') + @cmdln.option('-p', '--package', metavar='PACKAGE', + help='requests or reviews limited for the specified PACKAGE, requires also a PROJECT') + @cmdln.option('-b', '--brief', action='store_true', default=False, + help='print output in list view as list subcommand') + @cmdln.option('-M', '--mine', action='store_true', + help='only show requests created by yourself') + @cmdln.option('-B', '--bugowner', action='store_true', + help='also show requests about packages where I am bugowner') + @cmdln.option('-e', '--edit', action='store_true', + help='edit a submit action') + @cmdln.option('-i', '--interactive', action='store_true', + help='interactive review of request') + @cmdln.option('--or-revoke', action='store_true', + help='For automatisation scripts: accepts (if using with accept argument) a request when it is in new or review state. Or revoke it when it got declined. Otherwise just do nothing.') + @cmdln.option('--non-interactive', action='store_true', + help='non-interactive review of request') + @cmdln.option('--exclude-target-project', action='append', + help='exclude target project from request list') + @cmdln.option('--involved-projects', action='store_true', + help='show all requests for project/packages where USER is involved') + @cmdln.option('--source-buildstatus', action='store_true', + help='print the buildstatus of the source package (only works with "show" and the interactive review)') + @cmdln.alias("rq") + @cmdln.alias("review") + # FIXME: rewrite this mess and split request and review + def do_request(self, subcmd, opts, *args): + """${cmd_name}: Show or modify requests and reviews + + [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration + for information on this topic.] + + The 'request' command has the following sub commands: + + "list" lists open requests attached to a project or package or person. + Uses the project/package of the current directory if none of + -M, -U USER, project/package are given. + + "log" will show the history of the given ID + + "show" will show the request itself, and generate a diff for review, if + used with the --diff option. The keyword show can be omitted if the ID is numeric. + + "decline" will change the request state to "declined" + + "reopen" will set the request back to new or review. + + "setincident" will direct "maintenance" requests into specific incidents + + "supersede" will supersede one request with another existing one. + + "revoke" will set the request state to "revoked" + + "accept" will change the request state to "accepted" and will trigger + the actual submit process. That would normally be a server-side copy of + the source package to the target package. + + "checkout" will checkout the request's source package ("submit" requests only). + + "priorize" change the prioritity of a request to either "critical", "important", "moderate" or "low" + + + The 'review' command has the following sub commands: + + "list" lists open requests that need to be reviewed by the + specified user or group + + "add" adds a person or group as reviewer to a request + + "accept" mark the review positive + + "decline" mark the review negative. A negative review will + decline the request. + + usage: + osc request list [-M] [-U USER] [-s state] [-D DAYS] [-t type] [-B] [PRJ [PKG]] + osc request log ID + osc request [show] [-d] [-b] ID + + osc request accept [-m TEXT] ID + osc request decline [-m TEXT] ID + osc request revoke [-m TEXT] ID + osc request reopen [-m TEXT] ID + osc request setincident [-m TEXT] ID INCIDENT + osc request supersede [-m TEXT] ID SUPERSEDING_ID + osc request approvenew [-m TEXT] PROJECT + osc request priorize [-m TEXT] ID PRIORITY + + osc request checkout/co ID + osc request clone [-m TEXT] ID + + osc review show [-d] [-b] ID + osc review list [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] [-s state] + osc review add [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID + osc review accept [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID + osc review decline [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID + osc review reopen [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID + osc review supersede [-m TEXT] [-U USER] [-G GROUP] [-P PROJECT [-p PACKAGE]] ID SUPERSEDING_ID + + ${cmd_option_list} + """ + + args = slash_split(args) + + if opts.all and opts.state: + raise oscerr.WrongOptions('Sorry, the options \'--all\' and \'--state\' ' \ + 'are mutually exclusive.') + if opts.mine and opts.user: + raise oscerr.WrongOptions('Sorry, the options \'--user\' and \'--mine\' ' \ + 'are mutually exclusive.') + if opts.interactive and opts.non_interactive: + raise oscerr.WrongOptions('Sorry, the options \'--interactive\' and ' \ + '\'--non-interactive\' are mutually exclusive') + + if not args: + args = [ 'list' ] + opts.mine = 1 + if opts.state == '': + opts.state = 'all' + + if opts.state == '' and subcmd != 'review': + opts.state = 'declined,new,review' + + if args[0] == 'help': + return self.do_help(['help', 'request']) + + cmds = ['list', 'ls', 'log', 'show', 'decline', 'reopen', 'clone', 'accept', 'approvenew', 'wipe', 'setincident', 'supersede', 'revoke', 'checkout', 'co', 'priorize'] + if subcmd != 'review' and args[0] not in cmds: + raise oscerr.WrongArgs('Unknown request action %s. Choose one of %s.' \ + % (args[0], ', '.join(cmds))) + cmds = ['show', 'list', 'add', 'decline', 'accept', 'reopen', 'supersede'] + if subcmd == 'review' and args[0] not in cmds: + raise oscerr.WrongArgs('Unknown review action %s. Choose one of %s.' \ + % (args[0], ', '.join(cmds))) + + cmd = args[0] + del args[0] + if cmd == 'ls': + cmd = "list" + + apiurl = self.get_api_url() + + if cmd in ['list']: + min_args, max_args = 0, 2 + elif cmd in ['supersede', 'setincident', 'priorize']: + min_args, max_args = 2, 2 + else: + min_args, max_args = 1, 1 + if len(args) < min_args: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) > max_args: + raise oscerr.WrongArgs('Too many arguments.') + if cmd in ['add'] and not opts.user and not opts.group and not opts.project: + raise oscerr.WrongArgs('No reviewer specified.') + + source_buildstatus = conf.config['request_show_source_buildstatus'] or opts.source_buildstatus + + reqid = None + supersedid = None + if cmd == 'list' or cmd == 'approvenew': + package = None + project = None + if len(args) > 0: + project = args[0] + elif not opts.mine and not opts.user: + try: + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + except oscerr.NoWorkingCopy: + pass + elif opts.project: + project = opts.project + if opts.package: + package = opts.package + + if len(args) > 1: + package = args[1] + elif cmd == 'supersede': + reqid = args[0] + supersedid = args[1] + elif cmd == 'setincident': + reqid = args[0] + incident = args[1] + elif cmd == 'priorize': + reqid = args[0] + priority = args[1] + elif cmd in ['log', 'add', 'show', 'decline', 'reopen', 'clone', 'accept', 'wipe', 'revoke', 'checkout', 'co']: + reqid = args[0] + + # clone all packages from a given request + if cmd in ['clone']: + # should we force a message? + print('Cloned packages are available in project: %s' % clone_request(apiurl, reqid, opts.message)) + + # change incidents + elif cmd == 'setincident': + query = { 'cmd': 'setincident', 'incident': incident } + url = makeurl(apiurl, ['request', reqid], query) + r = http_POST(url, data=opts.message) + print(ET.parse(r).getroot().get('code')) + + # change priority + elif cmd == 'priorize': + query = { 'cmd': 'setpriority', 'priority': priority } + url = makeurl(apiurl, ['request', reqid], query) + r = http_POST(url, data=opts.message) + print(ET.parse(r).getroot().get('code')) + + # add new reviewer to existing request + elif cmd in ['add'] and subcmd == 'review': + query = { 'cmd': 'addreview' } + if opts.user: + query['by_user'] = opts.user + if opts.group: + query['by_group'] = opts.group + if opts.project: + query['by_project'] = opts.project + if opts.package: + query['by_package'] = opts.package + url = makeurl(apiurl, ['request', reqid], query) + if not opts.message: + opts.message = edit_message() + r = http_POST(url, data=opts.message) + print(ET.parse(r).getroot().get('code')) + + # list and approvenew + elif cmd == 'list' or cmd == 'approvenew': + states = ('new', 'accepted', 'revoked', 'declined', 'review', 'superseded') + who = '' + if cmd == 'approvenew': + states = ('new') + results = get_request_list(apiurl, project, package, '', ['new']) + else: + state_list = opts.state.split(',') + if state_list == ['']: + state_list = () + if opts.all: + state_list = ['all'] + else: + for s in state_list: + if not s in states and not s == 'all': + raise oscerr.WrongArgs('Unknown state \'%s\', try one of %s' % (s, ','.join(states))) + if opts.mine: + who = conf.get_apiurl_usr(apiurl) + if opts.user: + who = opts.user + + ## FIXME -B not implemented! + if opts.bugowner: + if (self.options.debug): + print('list: option --bugowner ignored: not impl.') + + if subcmd == 'review': + # FIXME: do the review list for the user and for all groups he belong to + results = get_review_list(apiurl, project, package, who, opts.group, opts.project, opts.package, state_list) + else: + if opts.involved_projects: + who = who or conf.get_apiurl_usr(apiurl) + results = get_user_projpkgs_request_list(apiurl, who, req_state=state_list, + req_type=opts.type, exclude_projects=opts.exclude_target_project or []) + else: + results = get_request_list(apiurl, project, package, who, + state_list, opts.type, opts.exclude_target_project or []) + + # Check if project actually exists if result list is empty + if not results: + if project: + msg = 'No results for %(kind)s %(entity)s' + emsg = '%(kind)s %(entity)s does not exist' + d = {'entity': [project], 'kind': 'project'} + meth = show_project_meta + if package: + d['kind'] = 'package' + d['entity'].append(package) + meth = show_package_meta + try: + entity = d['entity'] + d['entity'] = '/'.join(entity) + meth(apiurl, *entity) + print(msg % d) + except HTTPError: + print(emsg % d) + else: + print('No results') + return + + # we must not sort the results here, since the api is doing it already "the right way" + days = opts.days or conf.config['request_list_days'] + since = '' + try: + days = float(days) + except ValueError: + days = 0 + if days > 0: + since = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime(time.time()-days*24*3600)) + + skipped = 0 + ## bs has received 2009-09-20 a new xquery compare() function + ## which allows us to limit the list inside of get_request_list + ## That would be much faster for coolo. But counting the remainder + ## would not be possible with current xquery implementation. + ## Workaround: fetch all, and filter on client side. + + ## FIXME: date filtering should become implemented on server side + for result in results: + if days == 0 or result.state.when > since or result.state.name == 'new': + if (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive: + ignore_reviews = subcmd != 'review' + request_interactive_review(apiurl, result, group=opts.group, + ignore_reviews=ignore_reviews, + source_buildstatus=source_buildstatus) + else: + print(result.list_view(), '\n') + else: + skipped += 1 + if skipped: + print("There are %d requests older than %s days.\n" % (skipped, days)) + + if cmd == 'approvenew': + print("\n *** Approve them all ? [y/n] ***") + if sys.stdin.read(1) == "y": + + if not opts.message: + opts.message = edit_message() + for result in results: + print(result.reqid, ": ", end=' ') + r = change_request_state(apiurl, + result.reqid, 'accepted', opts.message or '', force=opts.force) + print('Result of change request state: %s' % r) + else: + print('Aborted...', file=sys.stderr) + raise oscerr.UserAbort() + + elif cmd == 'log': + for l in get_request_log(apiurl, reqid): + print(l) + + # show + elif cmd == 'show': + r = get_request(apiurl, reqid) + if opts.brief: + print(r.list_view()) + elif opts.edit: + if not r.get_actions('submit'): + raise oscerr.WrongOptions('\'--edit\' not possible ' \ + '(request has no \'submit\' action)') + return request_interactive_review(apiurl, r, 'e') + elif (opts.interactive or conf.config['request_show_interactive']) and not opts.non_interactive: + ignore_reviews = subcmd != 'review' + return request_interactive_review(apiurl, r, group=opts.group, + ignore_reviews=ignore_reviews, + source_buildstatus=source_buildstatus) + else: + print(r) + print_comments(apiurl, 'request', reqid) + if source_buildstatus: + sr_actions = r.get_actions('submit') + if not sr_actions: + raise oscerr.WrongOptions( '\'--source-buildstatus\' not possible ' \ + '(request has no \'submit\' actions)') + for action in sr_actions: + print('Buildstatus for \'%s/%s\':' % (action.src_project, action.src_package)) + print('\n'.join(get_results(apiurl, action.src_project, action.src_package))) + if opts.diff: + diff = '' + try: + # works since OBS 2.1 + diff = request_diff(apiurl, reqid) + except HTTPError as e: + # for OBS 2.0 and before + sr_actions = r.get_actions('submit') + if not r.get_actions('submit') and not r.get_actions('maintenance_incident') and not r.get_actions('maintenance_release'): + raise oscerr.WrongOptions('\'--diff\' not possible (request has no supported actions)') + for action in sr_actions: + diff += 'old: %s/%s\nnew: %s/%s\n' % (action.src_project, action.src_package, + action.tgt_project, action.tgt_package) + diff += submit_action_diff(apiurl, action) + diff += '\n\n' + run_pager(diff, tmp_suffix='') + + # checkout + elif cmd == 'checkout' or cmd == 'co': + r = get_request(apiurl, reqid) + sr_actions = r.get_actions('submit', 'maintenance_release') + if not sr_actions: + raise oscerr.WrongArgs('\'checkout\' not possible (request has no \'submit\' actions)') + for action in sr_actions: + checkout_package(apiurl, action.src_project, action.src_package, \ + action.src_rev, expand_link=True, prj_dir=action.src_project) + + else: + state_map = {'reopen' : 'new', 'accept' : 'accepted', 'decline' : 'declined', 'wipe' : 'deleted', 'revoke' : 'revoked', 'supersede' : 'superseded'} + # Change review state only + if subcmd == 'review': + if not opts.message: + opts.message = edit_message() + if cmd in ['accept', 'decline', 'reopen', 'supersede']: + if opts.user or opts.group or opts.project or opts.package: + r = change_review_state(apiurl, reqid, state_map[cmd], opts.user, opts.group, opts.project, + opts.package, opts.message or '', supersed=supersedid) + print(r) + else: + rq = get_request(apiurl, reqid) + if rq.state.name in ['new', 'review']: + for review in rq.reviews: # try all, but do not fail on error + try: + r = change_review_state(apiurl, reqid, state_map[cmd], review.by_user, review.by_group, + review.by_project, review.by_package, opts.message or '', supersed=supersedid) + print(r) + except HTTPError as e: + body = e.read() + if e.code in [403]: + if review.by_user: + print('No permission on review by user %s:' % review.by_user) + if review.by_group: + print('No permission on review by group %s' % review.by_group) + if review.by_package: + print('No permission on review by package %s / %s' % (review.by_project, review.by_package)) + elif review.by_project: + print('No permission on review by project %s' % review.by_project) + print(e, file=sys.stderr) + else: + print('Request is closed, please reopen the request first before changing any reviews.') + # Change state of entire request + elif cmd in ['reopen', 'accept', 'decline', 'wipe', 'revoke', 'supersede']: + rq = get_request(apiurl, reqid) + if opts.or_revoke: + if rq.state.name == "declined": + cmd = "revoke" + elif rq.state.name != "new" and rq.state.name != "review": + return 0 + if rq.state.name == state_map[cmd]: + repl = raw_input("\n *** The state of the request (#%s) is already '%s'. Change state anyway? [y/n] *** " % \ + (reqid, rq.state.name)) + if repl.lower() != 'y': + print('Aborted...', file=sys.stderr) + raise oscerr.UserAbort() + + if not opts.message: + tmpl = change_request_state_template(rq, state_map[cmd]) + opts.message = edit_message(template=tmpl) + try: + r = change_request_state(apiurl, + reqid, state_map[cmd], opts.message or '', supersed=supersedid, force=opts.force) + print('Result of change request state: %s' % r) + except HTTPError as e: + print(e, file=sys.stderr) + details = e.hdrs.get('X-Opensuse-Errorcode') + if details: + print(details, file=sys.stderr) + root = ET.fromstring(e.read()) + summary = root.find('summary') + if not summary is None: + print(summary.text) + if opts.or_revoke: + if e.code in [ 400, 403, 404, 500 ]: + print('Revoking it ...') + r = change_request_state(apiurl, + reqid, 'revoked', opts.message or '', supersed=supersedid, force=opts.force) + sys.exit(1) + + + # check for devel instances after accepted requests + if cmd in ['accept']: + import cgi + sr_actions = rq.get_actions('submit') + for action in sr_actions: + u = makeurl(apiurl, ['/search/package'], { + 'match' : "([devel/[@project='%s' and @package='%s']])" % (action.tgt_project, action.tgt_package) + }) + f = http_GET(u) + root = ET.parse(f).getroot() + if root.findall('package') and not opts.no_devel: + for node in root.findall('package'): + project = node.get('project') + package = node.get('name') + # skip it when this is anyway a link to me + link_url = makeurl(apiurl, ['source', project, package]) + links_to_project = links_to_package = None + try: + file = http_GET(link_url) + root = ET.parse(file).getroot() + link_node = root.find('linkinfo') + if link_node != None: + links_to_project = link_node.get('project') or project + links_to_package = link_node.get('package') or package + except HTTPError as e: + if e.code != 404: + print('Cannot get list of files for %s/%s: %s' % (project, package, e), file=sys.stderr) + except SyntaxError as e: + print('Cannot parse list of files for %s/%s: %s' % (project, package, e), file=sys.stderr) + if links_to_project == action.tgt_project and links_to_package == action.tgt_package: + # links to my request target anyway, no need to forward submit + continue + + print(project, end=' ') + if package != action.tgt_package: + print("/", package, end=' ') + repl = raw_input('\nForward this submit to it? ([y]/n)') + if repl.lower() == 'y' or repl == '': + (supersede, reqs) = check_existing_requests(apiurl, action.tgt_project, action.tgt_package, + project, package) + msg = "%s (forwarded request %s from %s)" % (rq.description, reqid, rq.get_creator()) + rid = create_submit_request(apiurl, action.tgt_project, action.tgt_package, + project, package, cgi.escape(msg)) + print(msg) + print("New request #", rid) + for req in reqs: + change_request_state(apiurl, req.reqid, 'superseded', + 'superseded by %s' % rid, rid) + + # editmeta and its aliases are all depracated + @cmdln.alias("editprj") + @cmdln.alias("createprj") + @cmdln.alias("editpac") + @cmdln.alias("createpac") + @cmdln.alias("edituser") + @cmdln.alias("usermeta") + @cmdln.hide(1) + def do_editmeta(self, subcmd, opts, *args): + """${cmd_name}: + + Obsolete command to edit metadata. Use 'meta' now. + + See the help output of 'meta'. + + """ + + print('This command is obsolete. Use \'osc meta <metatype> ...\'.', file=sys.stderr) + print('See \'osc help meta\'.', file=sys.stderr) + #self.do_help([None, 'meta']) + return 2 + + + @cmdln.option('-r', '--revision', metavar='rev', + help='use the specified revision.') + @cmdln.option('-R', '--use-plain-revision', action='store_true', + help='Do not expand revision the specified or latest rev') + @cmdln.option('-u', '--unset', action='store_true', + help='remove revision in link, it will point always to latest revision') + def do_setlinkrev(self, subcmd, opts, *args): + """${cmd_name}: Updates a revision number in a source link. + + This command adds or updates a specified revision number in a source link. + The current revision of the source is used, if no revision number is specified. + + usage: + osc setlinkrev + osc setlinkrev PROJECT [PACKAGE] + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + package = None + rev = parseRevisionOption(opts.revision)[0] or '' + if opts.unset: + rev = None + + if len(args) == 0: + p = findpacs(os.curdir)[0] + project = p.prjname + package = p.name + apiurl = p.apiurl + if not p.islink(): + sys.exit('Local directory is no checked out source link package, aborting') + elif len(args) == 2: + project = args[0] + package = args[1] + elif len(args) == 1: + project = args[0] + else: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('setlinkrev')) + + if package: + packages = [package] + else: + packages = meta_get_packagelist(apiurl, project) + + for p in packages: + rev = set_link_rev(apiurl, project, p, revision=rev, + expand=not opts.use_plain_revision) + if rev is None: + print('removed revision from link') + else: + print('set revision to %s for package %s' % (rev, p)) + + + def do_linktobranch(self, subcmd, opts, *args): + """${cmd_name}: Convert a package containing a classic link with patch to a branch + + This command tells the server to convert a _link with or without a project.diff + to a branch. This is a full copy with a _link file pointing to the branched place. + + usage: + osc linktobranch # can be used in checked out package + osc linktobranch PROJECT PACKAGE + ${cmd_option_list} + """ + args = slash_split(args) + apiurl = self.get_api_url() + + if len(args) == 0: + wd = os.curdir + project = store_read_project(wd) + package = store_read_package(wd) + update_local_dir = True + elif len(args) < 2: + raise oscerr.WrongArgs('Too few arguments (required none or two)') + elif len(args) > 2: + raise oscerr.WrongArgs('Too many arguments (required none or two)') + else: + project = args[0] + package = args[1] + update_local_dir = False + + # execute + link_to_branch(apiurl, project, package) + if update_local_dir: + pac = Package(wd) + pac.update(rev=pac.latest_rev()) + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + def do_detachbranch(self, subcmd, opts, *args): + """${cmd_name}: replace a link with its expanded sources + + If a package is a link it is replaced with its expanded sources. The link + does not exist anymore. + + usage: + osc detachbranch # can be used in package working copy + osc detachbranch PROJECT PACKAGE + ${cmd_option_list} + """ + args = slash_split(args) + apiurl = self.get_api_url() + if len(args) == 0: + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + elif len(args) == 2: + project, package = args + elif len(args) > 2: + raise oscerr.WrongArgs('Too many arguments (required none or two)') + else: + raise oscerr.WrongArgs('Too few arguments (required none or two)') + + try: + copy_pac(apiurl, project, package, apiurl, project, package, expand=True, comment=opts.message) + except HTTPError as e: + root = ET.fromstring(show_files_meta(apiurl, project, package, 'latest', expand=False)) + li = Linkinfo() + li.read(root.find('linkinfo')) + if li.islink() and li.haserror(): + raise oscerr.LinkExpandError(project, package, li.error) + elif not li.islink(): + print('package \'%s/%s\' is no link' % (project, package), file=sys.stderr) + else: + raise e + + + @cmdln.option('-C', '--cicount', choices=['add', 'copy', 'local'], + help='cicount attribute in the link, known values are add, copy, and local, default in buildservice is currently add.') + @cmdln.option('-c', '--current', action='store_true', + help='link fixed against current revision.') + @cmdln.option('-r', '--revision', metavar='rev', + help='link the specified revision.') + @cmdln.option('-f', '--force', action='store_true', + help='overwrite an existing link file if it is there.') + @cmdln.option('-d', '--disable-publish', action='store_true', + help='disable publishing of the linked package') + @cmdln.option('-N', '--new-package', action='store_true', + help='create a link to a not yet existing package') + def do_linkpac(self, subcmd, opts, *args): + """${cmd_name}: "Link" a package to another package + + A linked package is a clone of another package, but plus local + modifications. It can be cross-project. + + The DESTPAC name is optional; the source packages' name will be used if + DESTPAC is omitted. + + Afterwards, you will want to 'checkout DESTPRJ DESTPAC'. + + To add a patch, add the patch as file and add it to the _link file. + You can also specify text which will be inserted at the top of the spec file. + + See the examples in the _link file. + + NOTE: In case you want to fix or update another package, you should use the 'branch' + command. A branch has correct repositories (and a link) setup up by default and + will be cleaned up automatically after it was submitted back. + + usage: + osc linkpac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC] + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + + if not args or len(args) < 3: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('linkpac')) + + rev, dummy = parseRevisionOption(opts.revision) + vrev = None + + src_project = args[0] + src_package = args[1] + dst_project = args[2] + if len(args) > 3: + dst_package = args[3] + else: + dst_package = src_package + + if src_project == dst_project and src_package == dst_package: + raise oscerr.WrongArgs('Error: source and destination are the same.') + + if src_project == dst_project and not opts.cicount: + # in this case, the user usually wants to build different spec + # files from the same source + opts.cicount = "copy" + + if opts.current and not opts.new_package: + rev, vrev = show_upstream_rev_vrev(apiurl, src_project, src_package, expand=True) + if rev == None or len(rev) < 32: + # vrev is only needed for srcmd5 and OBS instances < 2.1.17 do not support it + vrev = None + + if rev and not checkRevision(src_project, src_package, rev): + print('Revision \'%s\' does not exist' % rev, file=sys.stderr) + sys.exit(1) + + link_pac(src_project, src_package, dst_project, dst_package, opts.force, rev, opts.cicount, opts.disable_publish, opts.new_package, vrev) + + @cmdln.option('--nosources', action='store_true', + help='ignore source packages when copying build results to destination project') + @cmdln.option('-m', '--map-repo', metavar='SRC=TARGET[,SRC=TARGET]', + help='Allows repository mapping(s) to be given as SRC=TARGET[,SRC=TARGET]') + @cmdln.option('-d', '--disable-publish', action='store_true', + help='disable publishing of the aggregated package') + def do_aggregatepac(self, subcmd, opts, *args): + """${cmd_name}: "Aggregate" a package to another package + + Aggregation of a package means that the build results (binaries) of a + package are basically copied into another project. + This can be used to make packages available from building that are + needed in a project but available only in a different project. Note + that this is done at the expense of disk space. See + http://en.opensuse.org/openSUSE:Build_Service_Tips_and_Tricks#link_and_aggregate + for more information. + + The DESTPAC name is optional; the source packages' name will be used if + DESTPAC is omitted. + + usage: + osc aggregatepac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC] + ${cmd_option_list} + """ + + args = slash_split(args) + + if not args or len(args) < 3: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('aggregatepac')) + + src_project = args[0] + src_package = args[1] + dst_project = args[2] + if len(args) > 3: + dst_package = args[3] + else: + dst_package = src_package + + if src_project == dst_project and src_package == dst_package: + raise oscerr.WrongArgs('Error: source and destination are the same.') + + repo_map = {} + if opts.map_repo: + for pair in opts.map_repo.split(','): + src_tgt = pair.split('=') + if len(src_tgt) != 2: + raise oscerr.WrongOptions('map "%s" must be SRC=TARGET[,SRC=TARGET]' % opts.map_repo) + repo_map[src_tgt[0]] = src_tgt[1] + + aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map, opts.disable_publish, opts.nosources) + + + @cmdln.option('-c', '--client-side-copy', action='store_true', + help='do a (slower) client-side copy') + @cmdln.option('-k', '--keep-maintainers', action='store_true', + help='keep original maintainers. Default is remove all and replace with the one calling the script.') + @cmdln.option('-K', '--keep-link', action='store_true', + help='keep the source link in target, this also expands the source') + @cmdln.option('-d', '--keep-develproject', action='store_true', + help='keep develproject tag in the package metadata') + @cmdln.option('-r', '--revision', metavar='rev', + help='copy the specified revision.') + @cmdln.option('-t', '--to-apiurl', metavar='URL', + help='URL of destination api server. Default is the source api server.') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-e', '--expand', action='store_true', + help='if the source package is a link then copy the expanded version of the link') + def do_copypac(self, subcmd, opts, *args): + """${cmd_name}: Copy a package + + A way to copy package to somewhere else. + + It can be done across buildservice instances, if the -t option is used. + In that case, a client-side copy and link expansion are implied. + + Using --client-side-copy always involves downloading all files, and + uploading them to the target. + + The DESTPAC name is optional; the source packages' name will be used if + DESTPAC is omitted. + + usage: + osc copypac SOURCEPRJ SOURCEPAC DESTPRJ [DESTPAC] + ${cmd_option_list} + """ + + args = slash_split(args) + + if not args or len(args) < 3: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('copypac')) + + src_project = args[0] + src_package = args[1] + dst_project = args[2] + if len(args) > 3: + dst_package = args[3] + else: + dst_package = src_package + + src_apiurl = conf.config['apiurl'] + if opts.to_apiurl: + dst_apiurl = conf.config['apiurl_aliases'].get(opts.to_apiurl, opts.to_apiurl) + else: + dst_apiurl = src_apiurl + + if src_apiurl != dst_apiurl: + opts.client_side_copy = True + opts.expand = True + + rev, dummy = parseRevisionOption(opts.revision) + + if opts.message: + comment = opts.message + else: + if not rev: + rev = show_upstream_rev(src_apiurl, src_project, src_package) + comment = 'osc copypac from project:%s package:%s revision:%s' % ( src_project, src_package, rev ) + if opts.keep_link: + comment += ", using keep-link" + if opts.expand: + comment += ", using expand" + if opts.client_side_copy: + comment += ", using client side copy" + + if src_project == dst_project and \ + src_package == dst_package and \ + not rev and \ + src_apiurl == dst_apiurl: + raise oscerr.WrongArgs('Source and destination are the same.') + + r = copy_pac(src_apiurl, src_project, src_package, + dst_apiurl, dst_project, dst_package, + client_side_copy=opts.client_side_copy, + keep_maintainers=opts.keep_maintainers, + keep_develproject=opts.keep_develproject, + expand=opts.expand, + revision=rev, + comment=comment, + keep_link=opts.keep_link) + print(r) + + + @cmdln.option('-r', '--repo', metavar='REPO', + help='Release only binaries from the specified repository') + @cmdln.option('--target-project', metavar='TARGETPROJECT', + help='Release only to specified project') + @cmdln.option('--target-repository', metavar='TARGETREPOSITORY', + help='Release only to specified repository') + @cmdln.option('--set-release', metavar='RELEASETAG', + help='rename binaries during release using this release tag') + def do_release(self, subcmd, opts, *args): + """${cmd_name}: Release sources and binaries + + This command is used to transfer sources and binaries without rebuilding them. + It requires defined release targets set to trigger="manual". Please refer the + release management chapter in the OBS book for details. + + usage: + osc release [ SOURCEPROJECT [ SOURCEPACKAGE ] ] + + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + + source_project = source_package = None + + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 0: + if is_project_dir(os.curdir): + source_project = store_read_project(os.curdir) + elif is_package_dir(os.curdir): + source_package = store_read_package(wd) + else: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) > 0: + source_project = args[0] + if len(args) > 1: + source_package = args[1] + + query = { 'cmd': 'release' } + if opts.target_project: + query["target_project"] = opts.target_project + if opts.target_repository: + query["target_repository"] = opts.target_repository + if opts.repo: + query["repository"] = opts.repo + if opts.set_release: + query["setrelease"] = opts.set_release + baseurl = ['source', source_project] + if source_package: + baseurl.append(source_package) + url = makeurl(apiurl, baseurl, query=query) + f = http_POST(url) + while True: + buf = f.read(16384) + if not buf: + break + sys.stdout.write(buf) + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + def do_releaserequest(self, subcmd, opts, *args): + """${cmd_name}: Create a request for releasing a maintenance update. + + [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide_draft/cha.obs.maintenance_setup.html + for information on this topic.] + + This command is used by the maintence team to start the release process of a maintenance update. + This includes usually testing based on the defined reviewers of the update project. + + usage: + osc releaserequest [ SOURCEPROJECT ] + + ${cmd_option_list} + """ + + # FIXME: additional parameters can be a certain repo list to create a partitial release + + args = slash_split(args) + apiurl = self.get_api_url() + + source_project = None + + if len(args) > 1: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 0 and is_project_dir(os.curdir): + source_project = store_read_project(os.curdir) + elif len(args) == 0: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) > 0: + source_project = args[0] + + if not opts.message: + opts.message = edit_message() + + r = create_release_request(apiurl, source_project, opts.message) + print(r.reqid) + + + + @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE', + help='Use this attribute to find default maintenance project (default is OBS:MaintenanceProject)') + @cmdln.option('--noaccess', action='store_true', + help='Create a hidden project') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + def do_createincident(self, subcmd, opts, *args): + """${cmd_name}: Create a maintenance incident + + [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide_draft/cha.obs.maintenance_setup.html + for information on this topic.] + + This command is asking to open an empty maintence incident. This can usually only be done by a responsible + maintenance team. + Please see the "mbranch" command on how to full such a project content and + the "patchinfo" command how add the required maintenance update information. + + usage: + osc createincident [ MAINTENANCEPROJECT ] + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + maintenance_attribute = conf.config['maintenance_attribute'] + if opts.attribute: + maintenance_attribute = opts.attribute + + source_project = target_project = None + + if len(args) > 1: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 1: + target_project = args[0] + else: + xpath = 'attribute/@name = \'%s\'' % maintenance_attribute + res = search(apiurl, project_id=xpath) + root = res['project_id'] + project = root.find('project') + if project is None: + sys.exit('Unable to find defined OBS:MaintenanceProject project on server.') + target_project = project.get('name') + print('Using target project \'%s\'' % target_project) + + query = { 'cmd': 'createmaintenanceincident' } + if opts.noaccess: + query["noaccess"] = 1 + url = makeurl(apiurl, ['source', target_project], query=query) + r = http_POST(url, data=opts.message) + project = None + for i in ET.fromstring(r.read()).findall('data'): + if i.get('name') == 'targetproject': + project = i.text.strip() + if project: + print("Incident project created: ", project) + else: + print(ET.parse(r).getroot().get('code')) + print(ET.parse(r).getroot().get('error')) + + + @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE', + help='Use this attribute to find default maintenance project (default is OBS:MaintenanceProject)') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('--release-project', metavar='RELEASEPROJECT', + help='Specify the release project') + @cmdln.option('--no-cleanup', action='store_true', + help='do not remove source project on accept') + @cmdln.option('--cleanup', action='store_true', + help='do remove source project on accept') + @cmdln.option('--incident', metavar='INCIDENT', + help='specify incident number to merge in') + @cmdln.option('--incident-project', metavar='INCIDENT_PROJECT', + help='specify incident project to merge in') + @cmdln.option('-s', '--supersede', metavar='SUPERSEDE', + help='Superseding another request by this one') + @cmdln.alias("mr") + def do_maintenancerequest(self, subcmd, opts, *args): + """${cmd_name}: Create a request for starting a maintenance incident. + + [See http://doc.opensuse.org/products/draft/OBS/obs-reference-guide_draft/cha.obs.maintenance_setup.html + for information on this topic.] + + This command is asking the maintence team to start a maintence incident based on a + created maintenance update. Please see the "mbranch" command on how to create such a project and + the "patchinfo" command how add the required maintenance update information. + + usage: + osc maintenancerequest [ SOURCEPROJECT [ SOURCEPACKAGES RELEASEPROJECT ] ] + osc maintenancerequest . + + The 2nd line when issued within a package directory provides a short cut to submit a single + package (the one in the current directory) from the project of this package to be submitted + to the release project this package links to. This syntax is only valid when specified from + a package subdirectory. + ${cmd_option_list} + """ + #FIXME: the follow syntax would make more sense and would obsolete the --release-project parameter + # but is incompatible with the current one + # osc maintenancerequest [ SOURCEPROJECT [ RELEASEPROJECT [ SOURCEPACKAGES ] ] + + args = slash_split(args) + apiurl = self.get_api_url() + maintenance_attribute = conf.config['maintenance_attribute'] + if opts.attribute: + maintenance_attribute = opts.attribute + + source_project = target_project = release_project = opt_sourceupdate = None + source_packages = [] + + if len(args) == 0 and (is_project_dir(os.curdir) or is_package_dir(os.curdir)): + source_project = store_read_project(os.curdir) + elif len(args) == 0: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) > 0: + if len(args) == 1 and args[0] == '.': + if is_package_dir(os.curdir): + source_project = store_read_project(os.curdir) + source_packages = [store_read_package(os.curdir)] + p = Package(os.curdir) + release_project = p.linkinfo.project + else: + raise oscerr.WrongArgs('No package directory') + else: + source_project = args[0] + if len(args) > 1: + if len(args) == 2: + sys.exit('Source package defined, but no release project.') + source_packages = args[1:] + release_project = args[-1] + source_packages.remove(release_project) + if opts.cleanup: + opt_sourceupdate = 'cleanup' + if not opts.no_cleanup: + default_branch = 'home:%s:branches:' % (conf.get_apiurl_usr(apiurl)) + if source_project.startswith(default_branch): + opt_sourceupdate = 'cleanup' + + if opts.release_project: + release_project = opts.release_project + + if opts.incident_project: + target_project = opts.incident_project + else: + xpath = 'attribute/@name = \'%s\'' % maintenance_attribute + res = search(apiurl, project_id=xpath) + root = res['project_id'] + project = root.find('project') + if project is None: + sys.exit('Unable to find defined OBS:MaintenanceProject project on server.') + target_project = project.get('name') + if opts.incident: + target_project += ":" + opts.incident + print('Using target project \'%s\'' % target_project) + + if not opts.message: + opts.message = edit_message() + + supersede_existing = False + reqs = [] + if not opts.supersede: + (supersede_existing, reqs) = check_existing_maintenance_requests(apiurl, + source_project, + source_packages, + target_project, + None) # unspecified release project + + r = create_maintenance_request(apiurl, source_project, source_packages, target_project, release_project, opt_sourceupdate, opts.message) + print(r.reqid) + + if supersede_existing: + for req in reqs: + change_request_state(apiurl, req.reqid, 'superseded', + 'superseded by %s' % r.reqid, r.reqid) + + if opts.supersede: + change_request_state(apiurl, opts.supersede, 'superseded', + opts.message or '', r.reqid) + + + @cmdln.option('-c', '--checkout', action='store_true', + help='Checkout branched package afterwards ' \ + '(\'osc bco\' is a shorthand for this option)' ) + @cmdln.option('-a', '--attribute', metavar='ATTRIBUTE', + help='Use this attribute to find affected packages (default is OBS:Maintained)') + @cmdln.option('-u', '--update-project-attribute', metavar='UPDATE_ATTRIBUTE', + help='Use this attribute to find update projects (default is OBS:UpdateProject) ') + @cmdln.option('--dryrun', action='store_true', + help='Just simulate the action and report back the result.') + @cmdln.option('--noaccess', action='store_true', + help='Create a hidden project') + @cmdln.option('--nodevelproject', action='store_true', + help='do not follow a defined devel project ' \ + '(primary project where a package is developed)') + @cmdln.alias('sm') + @cmdln.alias('maintained') + def do_mbranch(self, subcmd, opts, *args): + """${cmd_name}: Search or branch multiple instances of a package + + This command is used for searching all relevant instances of packages + and creating links of them in one project. + This is esp. used for maintenance updates. It can also be used to branch + all packages marked before with a given attribute. + + [See http://en.opensuse.org/openSUSE:Build_Service_Concept_Maintenance + for information on this topic.] + + The branched package will live in + home:USERNAME:branches:ATTRIBUTE:PACKAGE + if nothing else specified. + + usage: + osc sm [SOURCEPACKAGE] [-a ATTRIBUTE] + osc mbranch [ SOURCEPACKAGE [ TARGETPROJECT ] ] + ${cmd_option_list} + """ + args = slash_split(args) + apiurl = self.get_api_url() + tproject = None + + maintained_attribute = conf.config['maintained_attribute'] + if opts.attribute: + maintained_attribute = opts.attribute + maintained_update_project_attribute = conf.config['maintained_update_project_attribute'] + if opts.update_project_attribute: + maintained_update_project_attribute = opts.update_project_attribute + + if not len(args) or len(args) > 2: + raise oscerr.WrongArgs('Wrong number of arguments.') + if len(args) >= 1: + package = args[0] + if len(args) >= 2: + tproject = args[1] + + if subcmd == 'sm' or subcmd == 'maintained': + opts.dryrun = 1 + + result = attribute_branch_pkg(apiurl, maintained_attribute, maintained_update_project_attribute, \ + package, tproject, noaccess = opts.noaccess, nodevelproject=opts.nodevelproject, dryrun=opts.dryrun) + + if result is None: + print('ERROR: Attribute branch call came not back with a project.', file=sys.stderr) + sys.exit(1) + + if opts.dryrun: + for r in result.findall('package'): + line="%s/%s"%(r.get('project'), r.get('package')) + for d in r.findall('devel'): + line+=" using sources from %s/%s"%(d.get('project'), d.get('package')) + print(line) + return + + apiopt = '' + if conf.get_configParser().get('general', 'apiurl') != apiurl: + apiopt = '-A %s ' % apiurl + print('A working copy of the maintenance branch can be checked out with:\n\n' \ + 'osc %sco %s' \ + % (apiopt, result)) + + if opts.checkout: + Project.init_project(apiurl, result, result, conf.config['do_package_tracking']) + print(statfrmt('A', result)) + + # all packages + for package in meta_get_packagelist(apiurl, result): + try: + checkout_package(apiurl, result, package, expand_link = True, prj_dir = result) + except: + print('Error while checkout package:\n', package, file=sys.stderr) + + if conf.config['verbose']: + print('Note: You can use "osc delete" or "osc submitpac" when done.\n') + + + @cmdln.alias('branchco') + @cmdln.alias('bco') + @cmdln.alias('getpac') + @cmdln.option('--nodevelproject', action='store_true', + help='do not follow a defined devel project ' \ + '(primary project where a package is developed)') + @cmdln.option('-c', '--checkout', action='store_true', + help='Checkout branched package afterwards using "co -e -S"' \ + '(\'osc bco\' is a shorthand for this option)' ) + @cmdln.option('-f', '--force', default=False, action="store_true", + help='force branch, overwrite target') + @cmdln.option('--add-repositories', default=False, action="store_true", + help='Add repositories to target project (happens by default when project is new)') + @cmdln.option('--extend-package-names', default=False, action="store_true", + help='Extend packages names with project name as suffix') + @cmdln.option('--noaccess', action='store_true', + help='Create a hidden project') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify message TEXT') + @cmdln.option('-M', '--maintenance', default=False, action="store_true", + help='Create project and package in maintenance mode') + @cmdln.option('-N', '--new-package', action='store_true', + help='create a branch pointing to a not yet existing package') + @cmdln.option('-r', '--revision', metavar='rev', + help='branch against a specific revision') + @cmdln.option('--linkrev', metavar='linkrev', + help='specify the used revision in the link target.') + @cmdln.option('--add-repositories-block', metavar='add_repositories_block', + help='specify the used block strategy for new repositories') + @cmdln.option('--add-repositories-rebuild', metavar='add_repositories_rebuild', + help='specify the used rebuild strategy for new repositories') + def do_branch(self, subcmd, opts, *args): + """${cmd_name}: Branch a package + + [See http://en.opensuse.org/openSUSE:Build_Service_Collaboration + for information on this topic.] + + Create a source link from a package of an existing project to a new + subproject of the requesters home project (home:branches:) + + The branched package will live in + home:USERNAME:branches:PROJECT/PACKAGE + if nothing else specified. + + With getpac or bco, the branched package will come from one of + %(getpac_default_project)s + (list of projects from oscrc:getpac_default_project) + if nothing else is specfied on the command line. + + In case of branch errors, where the source has currently merge + conflicts use --linkrev=base option. + + usage: + osc branch + osc branch SOURCEPROJECT SOURCEPACKAGE + osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT + osc branch SOURCEPROJECT SOURCEPACKAGE TARGETPROJECT TARGETPACKAGE + osc getpac SOURCEPACKAGE + osc bco ... + ${cmd_option_list} + """ + + if subcmd == 'getpac' or subcmd == 'branchco' or subcmd == 'bco': + opts.checkout = True + args = slash_split(args) + tproject = tpackage = None + + if (subcmd == 'getpac' or subcmd == 'bco') and len(args) == 1: + def_p = find_default_project(self.get_api_url(), args[0]) + print('defaulting to %s/%s' % (def_p, args[0]), file=sys.stderr) + # python has no args.unshift ??? + args = [ def_p, args[0] ] + + if len(args) == 0 and is_package_dir('.'): + args = (store_read_project('.'), store_read_package('.')) + + if len(args) < 2 or len(args) > 4: + raise oscerr.WrongArgs('Wrong number of arguments.') + + apiurl = self.get_api_url() + + expected = 'home:%s:branches:%s' % (conf.get_apiurl_usr(apiurl), args[0]) + if len(args) >= 3: + expected = tproject = args[2] + if len(args) >= 4: + tpackage = args[3] + + try: + exists, targetprj, targetpkg, srcprj, srcpkg = \ + branch_pkg(apiurl, args[0], args[1], + nodevelproject=opts.nodevelproject, rev=opts.revision, + linkrev=opts.linkrev, + target_project=tproject, target_package=tpackage, + return_existing=opts.checkout, msg=opts.message or '', + force=opts.force, noaccess=opts.noaccess, + add_repositories=opts.add_repositories, + add_repositories_block=opts.add_repositories_block, + add_repositories_rebuild=opts.add_repositories_rebuild, + extend_package_names=opts.extend_package_names, + missingok=opts.new_package, + maintenance=opts.maintenance) + except oscerr.NotMissing as e: + print('NOTE: Package target exists already via project links, link will point to given project.') + print(' A submission will initialize a new instance.') + exists, targetprj, targetpkg, srcprj, srcpkg = \ + branch_pkg(apiurl, args[0], args[1], + nodevelproject=opts.nodevelproject, rev=opts.revision, + linkrev=opts.linkrev, + target_project=tproject, target_package=tpackage, + return_existing=opts.checkout, msg=opts.message or '', + force=opts.force, noaccess=opts.noaccess, + add_repositories=opts.add_repositories, + add_repositories_block=opts.add_repositories_block, + add_repositories_rebuild=opts.add_repositories_rebuild, + extend_package_names=opts.extend_package_names, + missingok=False, + maintenance=opts.maintenance, + newinstance=opts.new_package) + + if exists: + print('Using existing branch project: %s' % targetprj, file=sys.stderr) + + devloc = None + if not exists and (srcprj != args[0] or srcpkg != args[1]): + try: + root = ET.fromstring(''.join(show_attribute_meta(apiurl, args[0], None, None, + conf.config['maintained_update_project_attribute'], False, False))) + # this might raise an AttributeError + uproject = root.find('attribute').find('value').text + print('\nNote: The branch has been created from the configured update project: %s' \ + % uproject) + except (AttributeError, HTTPError) as e: + devloc = srcprj + print('\nNote: The branch has been created of a different project,\n' \ + ' %s,\n' \ + ' which is the primary location of where development for\n' \ + ' that package takes place.\n' \ + ' That\'s also where you would normally make changes against.\n' \ + ' A direct branch of the specified package can be forced\n' \ + ' with the --nodevelproject option.\n' % devloc) + + package = targetpkg or args[1] + if opts.checkout: + checkout_package(apiurl, targetprj, package, server_service_files=False, + expand_link=True, prj_dir=targetprj) + if conf.config['verbose']: + print('Note: You can use "osc delete" or "osc submitpac" when done.\n') + else: + apiopt = '' + if conf.get_configParser().get('general', 'apiurl') != apiurl: + apiopt = '-A %s ' % apiurl + print('A working copy of the branched package can be checked out with:\n\n' \ + 'osc %sco %s/%s' \ + % (apiopt, targetprj, package)) + print_request_list(apiurl, args[0], args[1]) + if devloc: + print_request_list(apiurl, devloc, srcpkg) + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify log message TEXT') + def do_undelete(self, subcmd, opts, *args): + """${cmd_name}: Restores a deleted project or package on the server. + + The server restores a package including the sources and meta configuration. + Binaries remain to be lost and will be rebuild. + + usage: + osc undelete PROJECT + osc undelete PROJECT PACKAGE [PACKAGE ...] + + ${cmd_option_list} + """ + + args = slash_split(args) + if len(args) < 1: + raise oscerr.WrongArgs('Missing argument.') + + msg = '' + if opts.message: + msg = opts.message + else: + msg = edit_message() + + apiurl = self.get_api_url() + prj = args[0] + pkgs = args[1:] + + if pkgs: + for pkg in pkgs: + undelete_package(apiurl, prj, pkg, msg) + else: + undelete_project(apiurl, prj, msg) + + + @cmdln.option('-r', '--recursive', action='store_true', + help='deletes a project with packages inside') + @cmdln.option('-f', '--force', action='store_true', + help='deletes a project where other depends on') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify log message TEXT') + def do_rdelete(self, subcmd, opts, *args): + """${cmd_name}: Delete a project or packages on the server. + + As a safety measure, project must be empty (i.e., you need to delete all + packages first). Also, packages must have no requests pending (i.e., you need + to accept/revoke such requests first). + If you are sure that you want to remove this project and all + its packages use \'--recursive\' switch. + It may still not work because other depends on it. If you want to ignore this as + well use \'--force\' switch. + + usage: + osc rdelete [-r] [-f] PROJECT [PACKAGE] + + ${cmd_option_list} + """ + + args = slash_split(args) + if len(args) < 1 or len(args) > 2: + raise oscerr.WrongArgs('Wrong number of arguments') + + apiurl = self.get_api_url() + prj = args[0] + + msg = '' + if opts.message: + msg = opts.message + else: + msg = edit_message() + + # empty arguments result in recursive project delete ... + if not len(prj): + raise oscerr.WrongArgs('Project argument is empty') + + if len(args) > 1: + pkg = args[1] + + if not len(pkg): + raise oscerr.WrongArgs('Package argument is empty') + + ## FIXME: core.py:commitDelPackage() should have something similar + rlist = get_request_list(apiurl, prj, pkg) + for rq in rlist: + print(rq) + if len(rlist) >= 1 and not opts.force: + print('Package has pending requests. Deleting the package will break them. '\ + 'They should be accepted/declined/revoked before deleting the package. '\ + 'Or just use \'--force\'.', file=sys.stderr) + sys.exit(1) + + delete_package(apiurl, prj, pkg, opts.force, msg) + + elif (not opts.recursive) and len(meta_get_packagelist(apiurl, prj)) >= 1: + print('Project contains packages. It must be empty before deleting it. ' \ + 'If you are sure that you want to remove this project and all its ' \ + 'packages use the \'--recursive\' switch.', file=sys.stderr) + sys.exit(1) + else: + delete_project(apiurl, prj, opts.force, msg) + + + def do_lock(self, subcmd, opts, project, package=None): + """${cmd_name}: Locks a project or package. + + usage: + osc lock PROJECT [PACKAGE] + + ${cmd_option_list} + """ + apiurl = self.get_api_url() + kind = 'prj' + path_args = (project,) + if package is not None: + kind = 'pkg' + path_args = (project, package) + meta = meta_exists(kind, path_args, create_new=False, apiurl=apiurl) + root = ET.fromstring(''.join(meta)) + if root.find('lock') is not None: + print('Already locked', file=sys.stderr) + sys.exit(1) + # alternatively, we could also use the set_flag api call + # instead of manually manipulating the xml + lock = ET.SubElement(root, 'lock') + ET.SubElement(lock, 'enable') + meta = ET.tostring(root) + edit_meta(kind, path_args=path_args, data=meta) + + + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify log message TEXT') + def do_unlock(self, subcmd, opts, *args): + """${cmd_name}: Unlocks a project or package + + Unlocks a locked project or package. A comment is required. + + usage: + osc unlock PROJECT [PACKAGE] + + ${cmd_option_list} + """ + + args = slash_split(args) + if len(args) < 1 or len(args) > 2: + raise oscerr.WrongArgs('Wrong number of arguments') + + apiurl = self.get_api_url() + prj = args[0] + + msg = '' + if opts.message: + msg = opts.message + else: + msg = edit_message() + + # empty arguments result in recursive project delete ... + if not len(prj): + raise oscerr.WrongArgs('Project argument is empty') + + if len(args) > 1: + pkg = args[1] + + if not len(pkg): + raise oscerr.WrongArgs('Package argument is empty') + + unlock_package(apiurl, prj, pkg, msg) + + else: + unlock_project(apiurl, prj, msg) + + + @cmdln.hide(1) + def do_deletepac(self, subcmd, opts, *args): + print("""${cmd_name} is obsolete ! + + Please use either + osc delete for checked out packages or projects + or + osc rdelete for server side operations.""") + + sys.exit(1) + + @cmdln.hide(1) + @cmdln.option('-f', '--force', action='store_true', + help='deletes a project and its packages') + def do_deleteprj(self, subcmd, opts, project): + """${cmd_name} is obsolete ! + + Please use + osc rdelete PROJECT + """ + sys.exit(1) + + @cmdln.alias('metafromspec') + @cmdln.alias('updatepkgmetafromspec') + @cmdln.option('', '--specfile', metavar='FILE', + help='Path to specfile. (if you pass more than working copy this option is ignored)') + def do_updatepacmetafromspec(self, subcmd, opts, *args): + """${cmd_name}: Update package meta information from a specfile + + ARG, if specified, is a package working copy. + + ${cmd_usage} + ${cmd_option_list} + """ + + args = parseargs(args) + if opts.specfile and len(args) == 1: + specfile = opts.specfile + else: + specfile = None + pacs = findpacs(args) + for p in pacs: + p.read_meta_from_spec(specfile) + p.update_package_meta() + + + @cmdln.alias('linkdiff') + @cmdln.alias('ldiff') + @cmdln.alias('di') + @cmdln.option('-c', '--change', metavar='rev', + help='the change made by revision rev (like -r rev-1:rev).' + 'If rev is negative this is like -r rev:rev-1.') + @cmdln.option('-r', '--revision', metavar='rev1[:rev2]', + help='If rev1 is specified it will compare your working copy against ' + 'the revision (rev1) on the server. ' + 'If rev1 and rev2 are specified it will compare rev1 against rev2 ' + '(NOTE: changes in your working copy are ignored in this case)') + @cmdln.option('-p', '--plain', action='store_true', + help='output the diff in plain (not unified) diff format') + @cmdln.option('-l', '--link', action='store_true', + help='(osc linkdiff): compare against the base revision of the link') + @cmdln.option('--missingok', action='store_true', + help='do not fail if the source or target project/package does not exist on the server') + def do_diff(self, subcmd, opts, *args): + """${cmd_name}: Generates a diff + + Generates a diff, comparing local changes against the repository + server. + + ${cmd_usage} + ARG, if specified, is a filename to include in the diff. + Default: all files. + + osc diff --link + osc linkdiff + Compare current checkout directory against the link base. + + osc diff --link PROJ PACK + osc linkdiff PROJ PACK + Compare a package against the link base (ignoring working copy changes). + + ${cmd_option_list} + """ + + if (subcmd == 'ldiff' or subcmd == 'linkdiff'): + opts.link = True + args = parseargs(args) + + pacs = None + if not opts.link or not len(args) == 2: + pacs = findpacs(args) + + + if opts.link: + query = { 'rev': 'latest' } + if pacs: + u = makeurl(pacs[0].apiurl, ['source', pacs[0].prjname, pacs[0].name], query=query) + else: + u = makeurl(self.get_api_url(), ['source', args[0], args[1]], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + raise oscerr.APIError('package is not a source link') + baserev = linkinfo.get('baserev') + opts.revision = baserev + if pacs: + print("diff working copy against last commited version\n") + else: + print("diff commited package against linked revision %s\n" % baserev) + run_pager(server_diff(self.get_api_url(), linkinfo.get('project'), linkinfo.get('package'), baserev, + args[0], args[1], linkinfo.get('lsrcmd5'), not opts.plain, opts.missingok)) + return + + if opts.change: + try: + rev = int(opts.change) + if rev > 0: + rev1 = rev - 1 + rev2 = rev + elif rev < 0: + rev1 = -rev + rev2 = -rev - 1 + else: + return + except: + print('Revision \'%s\' not an integer' % opts.change, file=sys.stderr) + return + else: + rev1, rev2 = parseRevisionOption(opts.revision) + diff = '' + for pac in pacs: + if not rev2: + for i in pac.get_diff(rev1): + diff += ''.join(i) + else: + diff += server_diff_noex(pac.apiurl, pac.prjname, pac.name, rev1, + pac.prjname, pac.name, rev2, not opts.plain, opts.missingok) + run_pager(diff) + + + @cmdln.option('--oldprj', metavar='OLDPRJ', + help='project to compare against' + ' (deprecated, use 3 argument form)') + @cmdln.option('--oldpkg', metavar='OLDPKG', + help='package to compare against' + ' (deprecated, use 3 argument form)') + @cmdln.option('-M', '--meta', action='store_true', + help='diff meta data') + @cmdln.option('-r', '--revision', metavar='N[:M]', + help='revision id, where N = old revision and M = new revision') + @cmdln.option('-p', '--plain', action='store_true', + help='output the diff in plain (not unified) diff format') + @cmdln.option('-c', '--change', metavar='rev', + help='the change made by revision rev (like -r rev-1:rev). ' + 'If rev is negative this is like -r rev:rev-1.') + @cmdln.option('--missingok', action='store_true', + help='do not fail if the source or target project/package does not exist on the server') + @cmdln.option('-u', '--unexpand', action='store_true', + help='diff unexpanded version if sources are linked') + def do_rdiff(self, subcmd, opts, *args): + """${cmd_name}: Server-side "pretty" diff of two packages + + Compares two packages (three or four arguments) or shows the + changes of a specified revision of a package (two arguments) + + If no revision is specified the latest revision is used. + + Note that this command doesn't return a normal diff (which could be + applied as patch), but a "pretty" diff, which also compares the content + of tarballs. + + + usage: + osc ${cmd_name} OLDPRJ OLDPAC NEWPRJ [NEWPAC] + osc ${cmd_name} PROJECT PACKAGE + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + + rev1 = None + rev2 = None + + old_project = None + old_package = None + new_project = None + new_package = None + + if len(args) == 2: + new_project = args[0] + new_package = args[1] + if opts.oldprj: + old_project = opts.oldprj + if opts.oldpkg: + old_package = opts.oldpkg + elif len(args) == 3 or len(args) == 4: + if opts.oldprj or opts.oldpkg: + raise oscerr.WrongArgs('--oldpkg and --oldprj are only valid with two arguments') + old_project = args[0] + new_package = old_package = args[1] + new_project = args[2] + if len(args) == 4: + new_package = args[3] + elif len(args) == 1 and opts.meta: + new_project = args[0] + new_package = '_project' + else: + raise oscerr.WrongArgs('Wrong number of arguments') + + if opts.meta: + opts.unexpand = True + + if opts.change: + try: + rev = int(opts.change) + if rev > 0: + rev1 = rev - 1 + rev2 = rev + elif rev < 0: + rev1 = -rev + rev2 = -rev - 1 + else: + return + except: + print('Revision \'%s\' not an integer' % opts.change, file=sys.stderr) + return + else: + if opts.revision: + rev1, rev2 = parseRevisionOption(opts.revision) + + rdiff = server_diff_noex(apiurl, + old_project, old_package, rev1, + new_project, new_package, rev2, not opts.plain, opts.missingok, + meta=opts.meta, + expand=not opts.unexpand) + + run_pager(rdiff) + + def _pdiff_raise_non_existing_package(self, project, package, msg = None): + raise oscerr.PackageMissing(project, package, msg or '%s/%s does not exist.' % (project, package)) + + def _pdiff_package_exists(self, apiurl, project, package): + try: + show_package_meta(apiurl, project, package) + return True + except HTTPError as e: + if e.code != 404: + print('Cannot check that %s/%s exists: %s' % (project, package, e), file=sys.stderr) + return False + + def _pdiff_guess_parent(self, apiurl, project, package, check_exists_first = False): + # Make sure the parent exists + if check_exists_first and not self._pdiff_package_exists(apiurl, project, package): + self._pdiff_raise_non_existing_package(project, package) + + if project.startswith('home:'): + guess = project[len('home:'):] + # remove user name + pos = guess.find(':') + if pos > 0: + guess = guess[guess.find(':') + 1:] + if guess.startswith('branches:'): + guess = guess[len('branches:'):] + return (guess, package) + + return (None, None) + + def _pdiff_get_parent_from_link(self, apiurl, project, package): + link_url = makeurl(apiurl, ['source', project, package, '_link']) + + try: + file = http_GET(link_url) + root = ET.parse(file).getroot() + except HTTPError as e: + return (None, None) + except SyntaxError as e: + print('Cannot parse %s/%s/_link: %s' % (project, package, e), file=sys.stderr) + return (None, None) + + parent_project = root.get('project') + parent_package = root.get('package') or package + + if parent_project is None: + return (None, None) + + return (parent_project, parent_package) + + def _pdiff_get_exists_and_parent(self, apiurl, project, package): + link_url = makeurl(apiurl, ['public', 'source', project, package]) + try: + file = http_GET(link_url) + root = ET.parse(file).getroot() + except HTTPError as e: + if e.code != 404: + print('Cannot get list of files for %s/%s: %s' % (project, package, e), file=sys.stderr) + return (None, None, None) + except SyntaxError as e: + print('Cannot parse list of files for %s/%s: %s' % (project, package, e), file=sys.stderr) + return (None, None, None) + + link_node = root.find('linkinfo') + if link_node is None: + return (True, None, None) + + parent_project = link_node.get('project') + parent_package = link_node.get('package') or package + + if parent_project is None: + raise oscerr.APIError('%s/%s is a link with no parent?' % (project, package)) + + return (True, parent_project, parent_package) + + @cmdln.option('-p', '--plain', action='store_true', + dest='plain', + help='output the diff in plain (not unified) diff format') + @cmdln.option('-n', '--nomissingok', action='store_true', + dest='nomissingok', + help='fail if the parent package does not exist on the server') + def do_pdiff(self, subcmd, opts, *args): + """${cmd_name}: Quick alias to diff the content of a package with its parent. + + Usage: + osc pdiff [--plain|-p] [--nomissing-ok|-n] + osc pdiff [--plain|-p] [--nomissing-ok|-n] PKG + osc pdiff [--plain|-p] [--nomissing-ok|-n] PRJ PKG + + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + args = slash_split(args) + + unified = not opts.plain + noparentok = not opts.nomissingok + + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 0: + if not is_package_dir(os.getcwd()): + raise oscerr.WrongArgs('Current directory is not a checked out package. Please specify a project and a package.') + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + elif len(args) == 1: + if not is_project_dir(os.getcwd()): + raise oscerr.WrongArgs('Current directory is not a checked out project. Please specify a project and a package.') + project = store_read_project(os.curdir) + package = args[0] + elif len(args) == 2: + project = args[0] + package = args[1] + else: + raise RuntimeError('Internal error: bad check for arguments.') + + ## Find parent package + + # Old way, that does one more request to api + #(parent_project, parent_package) = self._pdiff_get_parent_from_link(apiurl, project, package) + #if not parent_project: + # (parent_project, parent_package) = self._pdiff_guess_parent(apiurl, project, package, check_exists_first = True) + # if parent_project and parent_package: + # print 'Guessed that %s/%s is the parent package.' % (parent_project, parent_package) + + # New way + (exists, parent_project, parent_package) = self._pdiff_get_exists_and_parent (apiurl, project, package) + if not exists: + self._pdiff_raise_non_existing_package(project, package) + if not parent_project: + (parent_project, parent_package) = self._pdiff_guess_parent(apiurl, project, package, check_exists_first = False) + if parent_project and parent_package: + print('Guessed that %s/%s is the parent package.' % (parent_project, parent_package)) + + if not parent_project or not parent_package: + print('Cannot find a parent for %s/%s to diff against.' % (project, package), file=sys.stderr) + return 1 + + if not noparentok and not self._pdiff_package_exists(apiurl, parent_project, parent_package): + self._pdiff_raise_non_existing_package(parent_project, parent_package, + msg = 'Parent for %s/%s (%s/%s) does not exist.' % \ + (project, package, parent_project, parent_package)) + + rdiff = server_diff(apiurl, parent_project, parent_package, None, project, + package, None, unified = unified, missingok = noparentok) + + run_pager(rdiff) + + def _get_branch_parent(self, prj): + m = re.match('^home:[^:]+:branches:(.+)', prj) + # OBS_Maintained is a special case + if m and prj.find(':branches:OBS_Maintained:') == -1: + return m.group(1) + return None + + def _prdiff_skip_package(self, opts, pkg): + if opts.exclude and re.search(opts.exclude, pkg): + return True + + if opts.include and not re.search(opts.include, pkg): + return True + + return False + + def _prdiff_output_diff(self, opts, rdiff): + if opts.diffstat: + print() + p = subprocess.Popen("diffstat", + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + close_fds=True) + p.stdin.write(rdiff.encode()) + p.stdin.close() + print("".join(x.decode() for x in p.stdout.readlines())) + elif opts.unified: + print() + print(rdiff) + #run_pager(rdiff) + + def _prdiff_output_matching_requests(self, opts, requests, + srcprj, pkg): + """ + Search through the given list of requests and output any + submitrequests which target pkg and originate from srcprj. + """ + for req in requests: + for action in req.get_actions('submit'): + if action.src_project != srcprj: + continue + + if action.tgt_package != pkg: + continue + + print() + print(req.list_view()) + break + + @cmdln.alias('projectdiff') + @cmdln.alias('projdiff') + @cmdln.option('-r', '--requests', action='store_true', + help='show open requests for any packages with differences') + @cmdln.option('-e', '--exclude', metavar='REGEXP', dest='exclude', + help='skip packages matching REGEXP') + @cmdln.option('-i', '--include', metavar='REGEXP', dest='include', + help='only consider packages matching REGEXP') + @cmdln.option('-n', '--show-not-in-old', action='store_true', + help='show packages only in the new project') + @cmdln.option('-o', '--show-not-in-new', action='store_true', + help='show packages only in the old project') + @cmdln.option('-u', '--unified', action='store_true', + help='show full unified diffs of differences') + @cmdln.option('-d', '--diffstat', action='store_true', + help='show diffstat of differences') + + def do_prdiff(self, subcmd, opts, *args): + """${cmd_name}: Server-side diff of two projects + + Compares two projects and either summarises or outputs the + differences in full. In the second form, a project is compared + with one of its branches inside a home:$USER project (the branch + is treated as NEWPRJ). The home branch is optional if the current + working directory is a checked out copy of it. + + Usage: + osc prdiff [OPTIONS] OLDPRJ NEWPRJ + osc prdiff [OPTIONS] [home:$USER:branch:$PRJ] + + ${cmd_option_list} + """ + + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + + if len(args) == 0: + if is_project_dir(os.curdir): + newprj = Project('.', getPackageList=False).name + oldprj = self._get_branch_parent(newprj) + if oldprj is None: + raise oscerr.WrongArgs('Current directory is not a valid home branch.') + else: + raise oscerr.WrongArgs('Current directory is not a project.') + elif len(args) == 1: + newprj = args[0] + oldprj = self._get_branch_parent(newprj) + if oldprj is None: + raise oscerr.WrongArgs('Single-argument form must be for a home branch.') + elif len(args) == 2: + oldprj, newprj = args + else: + raise RuntimeError('BUG in argument parsing, please report.\n' + 'args: ' + repr(args)) + + if opts.diffstat and opts.unified: + print('error - cannot specify both --diffstat and --unified', file=sys.stderr) + sys.exit(1) + + apiurl = self.get_api_url() + + old_packages = meta_get_packagelist(apiurl, oldprj) + new_packages = meta_get_packagelist(apiurl, newprj) + + if opts.requests: + requests = get_request_list(apiurl, project=oldprj, + req_state=('new', 'review')) + + for pkg in old_packages: + if self._prdiff_skip_package(opts, pkg): + continue + + if pkg not in new_packages: + if opts.show_not_in_new: + print("old only: %s" % pkg) + continue + + rdiff = server_diff_noex( + apiurl, + oldprj, pkg, None, + newprj, pkg, None, + unified=True, missingok=False, meta=False, expand=True + ) + + if rdiff: + print("differs: %s" % pkg) + self._prdiff_output_diff(opts, rdiff) + + if opts.requests: + self._prdiff_output_matching_requests(opts, requests, + newprj, pkg) + else: + print("identical: %s" % pkg) + + for pkg in new_packages: + if self._prdiff_skip_package(opts, pkg): + continue + + if pkg not in old_packages: + if opts.show_not_in_old: + print("new only: %s" % pkg) + + + def do_repourls(self, subcmd, opts, *args): + """${cmd_name}: Shows URLs of .repo files + + Shows URLs on which to access the project .repos files (yum-style + metadata) on download.opensuse.org. + + usage: + osc repourls [PROJECT] + + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + + if len(args) == 1: + project = args[0] + elif len(args) == 0: + project = store_read_project('.') + else: + raise oscerr.WrongArgs('Wrong number of arguments') + + root = ET.fromstring(''.join(show_configuration(apiurl))) + elm = root.find('download_url') + if elm is None or not elm.text: + raise oscerr.APIError('download_url configuration element expected') + + url_tmpl = elm.text + '/%s/%s/%s.repo' + repos = get_repositories_of_project(apiurl, project) + for repo in repos: + print(url_tmpl % (project.replace(':', ':/'), repo, project)) + + + @cmdln.option('-r', '--revision', metavar='rev', + help='checkout the specified revision. ' + 'NOTE: if you checkout the complete project ' + 'this option is ignored!') + @cmdln.option('-e', '--expand-link', action='store_true', + help='if a package is a link, check out the expanded ' + 'sources (no-op, since this became the default)') + @cmdln.option('-u', '--unexpand-link', action='store_true', + help='if a package is a link, check out the _link file ' \ + 'instead of the expanded sources') + @cmdln.option('-M', '--meta', action='store_true', + help='checkout out meta data instead of sources' ) + @cmdln.option('-c', '--current-dir', action='store_true', + help='place PACKAGE folder in the current directory' \ + 'instead of a PROJECT/PACKAGE directory') + @cmdln.option('-o', '--output-dir', metavar='outdir', + help='place package in the specified directory' \ + 'instead of a PROJECT/PACKAGE directory') + @cmdln.option('-s', '--source-service-files', action='store_true', + help='Run source services.' ) + @cmdln.option('-S', '--server-side-source-service-files', action='store_true', + help='Use server side generated sources instead of local generation.' ) + @cmdln.option('-l', '--limit-size', metavar='limit_size', + help='Skip all files with a given size') + @cmdln.alias('co') + def do_checkout(self, subcmd, opts, *args): + """${cmd_name}: Check out content from the repository + + Check out content from the repository server, creating a local working + copy. + + When checking out a single package, the option --revision can be used + to specify a revision of the package to be checked out. + + When a package is a source link, then it will be checked out in + expanded form. If --unexpand-link option is used, the checkout will + instead produce the raw _link file plus patches. + + usage: + osc co PROJECT [PACKAGE] [FILE] + osc co PROJECT # entire project + osc co PROJECT PACKAGE # a package + osc co PROJECT PACKAGE FILE # single file -> to current dir + + while inside a project directory: + osc co PACKAGE # check out PACKAGE from project + + with the result of rpm -q --qf '%%{DISTURL}\\n' PACKAGE + osc co obs://API/PROJECT/PLATFORM/REVISION-PACKAGE + + ${cmd_option_list} + """ + + if opts.unexpand_link: + expand_link = False + else: + expand_link = True + + if not args: + raise oscerr.WrongArgs('Incorrect number of arguments.\n\n' \ + + self.get_cmd_help('checkout')) + + # XXX: this too openSUSE-setup specific... + # FIXME: this should go into ~jw/patches/osc/osc.proj_pack_20101201.diff + # to be available to all subcommands via @cmdline.prep(proj_pack) + # obs://build.opensuse.org/openSUSE:11.3/standard/fc6c25e795a89503e99d59da5dc94a79-screen + m = re.match(r"obs://([^/]+)/(\S+)/([^/]+)/([A-Fa-f\d]+)\-(\S+)", args[0]) + if m and len(args) == 1: + apiurl = "https://" + m.group(1) + project = project_dir = m.group(2) + # platform = m.group(3) + opts.revision = m.group(4) + package = m.group(5) + apiurl = apiurl.replace('/build.', '/api.') + filename = None + else: + args = slash_split(args) + project = package = filename = None + apiurl = self.get_api_url() + try: + project = project_dir = args[0] + package = args[1] + filename = args[2] + except: + pass + + if len(args) == 1 and is_project_dir(os.curdir): + project = store_read_project(os.curdir) + project_dir = os.curdir + package = args[0] + + rev, dummy = parseRevisionOption(opts.revision) + if rev == None: + rev = "latest" + + if rev and rev != "latest" and not checkRevision(project, package, rev): + print('Revision \'%s\' does not exist' % rev, file=sys.stderr) + sys.exit(1) + + if filename: + # Note: same logic as with 'osc cat' (not 'osc ls', which never merges!) + if expand_link: + rev = show_upstream_srcmd5(apiurl, project, package, expand=True, revision=rev) + get_source_file(apiurl, project, package, filename, revision=rev, progress_obj=self.download_progress) + + elif package: + if opts.current_dir: + project_dir = None + checkout_package(apiurl, project, package, rev, expand_link=expand_link, \ + prj_dir=project_dir, service_files = opts.source_service_files, \ + server_service_files=opts.server_side_source_service_files, \ + progress_obj=self.download_progress, size_limit=opts.limit_size, \ + meta=opts.meta, outdir=opts.output_dir) + print_request_list(apiurl, project, package) + + elif project: + prj_dir = project + if sys.platform[:3] == 'win': + prj_dir = prj_dir.replace(':', ';') + if os.path.exists(prj_dir): + sys.exit('osc: project \'%s\' already exists' % project) + + # check if the project does exist (show_project_meta will throw an exception) + show_project_meta(apiurl, project) + + if opts.output_dir is not None: + init_dir=opts.output_dir + else: + init_dir=prj_dir + Project.init_project(apiurl, init_dir, project, conf.config['do_package_tracking']) + print(statfrmt('A', prj_dir)) + + # all packages + for package in meta_get_packagelist(apiurl, project): + if opts.output_dir is not None: + outputdir = os.path.join(opts.output_dir, package) + if not os.path.exists(opts.output_dir): + os.mkdir(os.path.join(opts.output_dir)) + else: + outputdir=None + + # don't check out local links by default + try: + m = show_files_meta(apiurl, project, package) + li = Linkinfo() + li.read(ET.fromstring(''.join(m)).find('linkinfo')) + if not li.haserror(): + if li.project == project: + print(statfrmt('S', package + " link to package " + li.package)) + continue + except: + pass + + try: + checkout_package(apiurl, project, package, expand_link = expand_link, \ + prj_dir = prj_dir, service_files = opts.source_service_files, \ + server_service_files = opts.server_side_source_service_files, \ + progress_obj=self.download_progress, size_limit=opts.limit_size, \ + meta=opts.meta,outdir=outputdir) + except oscerr.LinkExpandError as e: + print('Link cannot be expanded:\n', e, file=sys.stderr) + print('Use "osc repairlink" for fixing merge conflicts:\n', file=sys.stderr) + # check out in unexpanded form at least + checkout_package(apiurl, project, package, expand_link = False, \ + prj_dir = prj_dir, service_files = opts.source_service_files, \ + server_service_files = opts.server_side_source_service_files, \ + progress_obj=self.download_progress, size_limit=opts.limit_size, \ + meta=opts.meta) + print_request_list(apiurl, project) + + else: + raise oscerr.WrongArgs('Missing argument.\n\n' \ + + self.get_cmd_help('checkout')) + + + @cmdln.option('-q', '--quiet', action='store_true', + help='print as little as possible') + @cmdln.option('-v', '--verbose', action='store_true', + help='print extra information') + @cmdln.option('-e', '--show-excluded', action='store_true', + help='also show files which are excluded by the ' \ + '"exclude_glob" config option') + @cmdln.alias('st') + def do_status(self, subcmd, opts, *args): + """${cmd_name}: Show status of files in working copy + + Show the status of files in a local working copy, indicating whether + files have been changed locally, deleted, added, ... + + The first column in the output specifies the status and is one of the + following characters: + ' ' no modifications + 'A' Added + 'C' Conflicted + 'D' Deleted + 'M' Modified + '?' item is not under version control + '!' item is missing (removed by non-osc command) or incomplete + 'S' item is skipped (item exceeds a file size limit or is _service:* file) + 'F' Frozen (use "osc pull" to merge conflicts) (package-only state) + + examples: + osc st + osc st <directory> + osc st file1 file2 ... + + usage: + osc status [OPTS] [PATH...] + ${cmd_option_list} + """ + + if opts.quiet and opts.verbose: + raise oscerr.WrongOptions('\'--quiet\' and \'--verbose\' are mutually exclusive') + + args = parseargs(args) + lines = [] + excl_states = (' ',) + if opts.quiet: + excl_states += ('?',) + elif opts.verbose: + excl_states = () + for arg in args: + if is_project_dir(arg): + prj = Project(arg, False) + # don't exclude packages with state ' ' because the packages + # might have modified etc. files + prj_excl = [st for st in excl_states if st != ' '] + for st, pac in sorted(prj.get_status(*prj_excl), lambda x, y: cmp(x[1], y[1])): + p = prj.get_pacobj(pac) + if p is None: + # state is != ' ' + lines.append(statfrmt(st, os.path.normpath(os.path.join(prj.dir, pac)))) + continue + if p.isfrozen(): + lines.append(statfrmt('F', os.path.normpath(os.path.join(prj.dir, pac)))) + elif st == ' ' and opts.verbose or st != ' ': + lines.append(statfrmt(st, os.path.normpath(os.path.join(prj.dir, pac)))) + states = p.get_status(opts.show_excluded, *excl_states) + for st, filename in sorted(states, lambda x, y: cmp(x[1], y[1])): + lines.append(statfrmt(st, os.path.normpath(os.path.join(p.dir, filename)))) + else: + p = findpacs([arg])[0] + for st, filename in sorted(p.get_status(opts.show_excluded, *excl_states), lambda x, y: cmp(x[1], y[1])): + lines.append(statfrmt(st, os.path.normpath(os.path.join(p.dir, filename)))) + if lines: + print('\n'.join(lines)) + + + def do_add(self, subcmd, opts, *args): + """${cmd_name}: Mark files to be added upon the next commit + + In case a URL is given the file will get downloaded and registered to be downloaded + by the server as well via the download_url source service. + + This is recommended for release tar balls to track their source and to help + others to review your changes esp. on version upgrades. + + usage: + osc add URL [URL...] + osc add FILE [FILE...] + ${cmd_option_list} + """ + if not args: + raise oscerr.WrongArgs('Missing argument.\n\n' \ + + self.get_cmd_help('add')) + + # Do some magic here, when adding a url. We want that the server to download the tar ball and to verify it + for arg in parseargs(args): + if arg.startswith('http://') or arg.startswith('https://') or arg.startswith('ftp://') or arg.startswith('git://'): + if arg.endswith('.git'): + addGitSource(arg) + else: + addDownloadUrlService(arg) + else: + addFiles([arg]) + + + def do_mkpac(self, subcmd, opts, *args): + """${cmd_name}: Create a new package under version control + + usage: + osc mkpac new_package + ${cmd_option_list} + """ + if not conf.config['do_package_tracking']: + print("to use this feature you have to enable \'do_package_tracking\' " \ + "in the [general] section in the configuration file", file=sys.stderr) + sys.exit(1) + + if len(args) != 1: + raise oscerr.WrongArgs('Wrong number of arguments.') + + createPackageDir(args[0]) + + @cmdln.option('-r', '--recursive', action='store_true', + help='If CWD is a project dir then scan all package dirs as well') + @cmdln.alias('ar') + def do_addremove(self, subcmd, opts, *args): + """${cmd_name}: Adds new files, removes disappeared files + + Adds all files new in the local copy, and removes all disappeared files. + + ARG, if specified, is a package working copy. + + ${cmd_usage} + ${cmd_option_list} + """ + + args = parseargs(args) + arg_list = args[:] + for arg in arg_list: + if is_project_dir(arg) and conf.config['do_package_tracking']: + prj = Project(arg, False) + for pac in prj.pacs_unvers: + pac_dir = getTransActPath(os.path.join(prj.dir, pac)) + if os.path.isdir(pac_dir): + addFiles([pac_dir], prj) + for pac in prj.pacs_broken: + if prj.get_state(pac) != 'D': + prj.set_state(pac, 'D') + print(statfrmt('D', getTransActPath(os.path.join(prj.dir, pac)))) + if opts.recursive: + for pac in prj.pacs_have: + state = prj.get_state(pac) + if state != None and state != 'D': + pac_dir = getTransActPath(os.path.join(prj.dir, pac)) + args.append(pac_dir) + args.remove(arg) + prj.write_packages() + elif is_project_dir(arg): + print('osc: addremove is not supported in a project dir unless ' \ + '\'do_package_tracking\' is enabled in the configuration file', file=sys.stderr) + sys.exit(1) + + pacs = findpacs(args) + for p in pacs: + todo = list(set(p.filenamelist + p.filenamelist_unvers + p.to_be_added)) + for filename in todo: + abs_filename = os.path.join(p.absdir, filename) + if os.path.isdir(abs_filename): + continue + # ignore foo.rXX, foo.mine for files which are in 'C' state + if os.path.splitext(filename)[0] in p.in_conflict: + continue + state = p.status(filename) + if state == '?': + # TODO: should ignore typical backup files suffix ~ or .orig + p.addfile(filename) + elif state == 'D' and os.path.isfile(abs_filename): + # if the "deleted" file exists in the wc, track it again + p.addfile(filename) + elif state == '!': + p.delete_file(filename) + print(statfrmt('D', getTransActPath(os.path.join(p.dir, filename)))) + + @cmdln.alias('ci') + @cmdln.alias('checkin') + @cmdln.option('-m', '--message', metavar='TEXT', + help='specify log message TEXT') + @cmdln.option('-n', '--no-message', default=False, action='store_true', + help='do not specify a log message') + @cmdln.option('-F', '--file', metavar='FILE', + help='read log message from FILE, \'-\' denotes standard input.') + @cmdln.option('-f', '--force', default=False, action="store_true", + help='force commit, even if there were no changes') + @cmdln.option('--skip-validation', default=False, action="store_true", + help='deprecated, don\'t use it') + @cmdln.option('-v', '--verbose', default=False, action="store_true", + help='Run the source services with verbose information') + @cmdln.option('--skip-local-service-run', '--noservice', default=False, action="store_true", + help='Skip service run of configured source services for local run') + def do_commit(self, subcmd, opts, *args): + """${cmd_name}: Upload content to the repository server + + Upload content which is changed in your working copy, to the repository + server. + + examples: + osc ci # current dir + osc ci <dir> + osc ci file1 file2 ... + + ${cmd_usage} + ${cmd_option_list} + """ + args = parseargs(args) + + if opts.skip_validation: + print("WARNING: deprecated option --skip-validation ignored.", file=sys.stderr) + + msg = '' + if opts.message: + msg = opts.message + elif opts.file: + if opts.file == '-': + msg = sys.stdin.read() + else: + try: + msg = open(opts.file).read() + except: + sys.exit('could not open file \'%s\'.' % opts.file) + skip_local_service_run = False + if not conf.config['local_service_run'] or opts.skip_local_service_run: + skip_local_service_run = True + arg_list = args[:] + for arg in arg_list: + if conf.config['do_package_tracking'] and is_project_dir(arg): + try: + prj = Project(arg) + if not msg and not opts.no_message: + msg = edit_message() + + # check any of the packages is a link, if so, as for branching + pacs = (Package(os.path.join(prj.dir, pac)) + for pac in prj.pacs_have if prj.get_state(pac) == ' ') + can_branch = False + if any(pac.is_link_to_different_project() for pac in pacs): + repl = raw_input('Some of the packages are links to a different project!\n' \ + 'Create a local branch before commit? (y|N) ') + if repl in('y', 'Y'): + can_branch = True + + prj.commit(msg=msg, skip_local_service_run=skip_local_service_run, verbose=opts.verbose, can_branch=can_branch) + except oscerr.ExtRuntimeError as e: + print("ERROR: service run failed", e, file=sys.stderr) + return 1 + args.remove(arg) + + pacs = findpacs(args) + + if conf.config['do_package_tracking'] and len(pacs) > 0: + prj_paths = {} + single_paths = [] + files = {} + # XXX: this is really ugly + pac_objs = {} + # it is possible to commit packages from different projects at the same + # time: iterate over all pacs and put each pac to the right project in the dict + for pac in pacs: + path = os.path.normpath(os.path.join(pac.dir, os.pardir)) + if is_project_dir(path): + pac_path = os.path.basename(os.path.normpath(pac.absdir)) + prj_paths.setdefault(path, []).append(pac_path) + pac_objs.setdefault(path, []).append(pac) + files[pac_path] = pac.todo + else: + single_paths.append(pac.dir) + if not pac.todo: + pac.todo = pac.filenamelist + pac.filenamelist_unvers + pac.todo.sort() + for prj_path, packages in prj_paths.items(): + prj = Project(prj_path) + if not msg and not opts.no_message: + msg = get_commit_msg(prj.absdir, pac_objs[prj_path]) + + # check any of the packages is a link, if so, as for branching + can_branch = False + if any(pac.is_link_to_different_project() for pac in pacs): + repl = raw_input('Some of the packages are links to a different project!\n' \ + 'Create a local branch before commit? (y|N) ') + if repl in('y', 'Y'): + can_branch = True + + prj.commit(packages, msg=msg, files=files, skip_local_service_run=skip_local_service_run, verbose=opts.verbose, can_branch=can_branch, force=opts.force) + store_unlink_file(prj.absdir, '_commit_msg') + for pac in single_paths: + p = Package(pac) + if not msg and not opts.no_message: + msg = get_commit_msg(p.absdir, [p]) + p.commit(msg, skip_local_service_run=skip_local_service_run, verbose=opts.verbose, force=opts.force) + store_unlink_file(p.absdir, '_commit_msg') + else: + for p in pacs: + if not p.todo: + p.todo = p.filenamelist + p.filenamelist_unvers + p.todo.sort() + if not msg and not opts.no_message: + msg = get_commit_msg(p.absdir, [p]) + p.commit(msg, skip_local_service_run=skip_local_service_run, verbose=opts.verbose, force=opts.force) + store_unlink_file(p.absdir, '_commit_msg') + + @cmdln.option('-r', '--revision', metavar='REV', + help='update to specified revision (this option will be ignored ' + 'if you are going to update the complete project or more than ' + 'one package)') + @cmdln.option('-u', '--unexpand-link', action='store_true', + help='if a package is an expanded link, update to the raw _link file') + @cmdln.option('-e', '--expand-link', action='store_true', + help='if a package is a link, update to the expanded sources') + @cmdln.option('-s', '--source-service-files', action='store_true', + help='Run local source services after update.' ) + @cmdln.option('-S', '--server-side-source-service-files', action='store_true', + help='Use server side generated sources instead of local generation.' ) + @cmdln.option('-l', '--limit-size', metavar='limit_size', + help='Skip all files with a given size') + @cmdln.alias('up') + def do_update(self, subcmd, opts, *args): + """${cmd_name}: Update a working copy + + examples: + + 1. osc up + If the current working directory is a package, update it. + If the directory is a project directory, update all contained + packages, AND check out newly added packages. + + To update only checked out packages, without checking out new + ones, you might want to use "osc up *" from within the project + dir. + + 2. osc up PAC + Update the packages specified by the path argument(s) + + When --expand-link is used with source link packages, the expanded + sources will be checked out. Without this option, the _link file and + patches will be checked out. The option --unexpand-link can be used to + switch back to the "raw" source with a _link file plus patch(es). + + ${cmd_usage} + ${cmd_option_list} + """ + + if (opts.expand_link and opts.unexpand_link) \ + or (opts.expand_link and opts.revision) \ + or (opts.unexpand_link and opts.revision): + raise oscerr.WrongOptions('Sorry, the options --expand-link, --unexpand-link and ' + '--revision are mutually exclusive.') + + args = parseargs(args) + arg_list = args[:] + + for arg in arg_list: + if is_project_dir(arg): + prj = Project(arg, progress_obj=self.download_progress) + + if conf.config['do_package_tracking']: + prj.update(expand_link=opts.expand_link, + unexpand_link=opts.unexpand_link) + args.remove(arg) + else: + # if not tracking package, and 'update' is run inside a project dir, + # it should do the following: + # (a) update all packages + args += prj.pacs_have + # (b) fetch new packages + prj.checkout_missing_pacs(expand_link = not opts.unexpand_link) + args.remove(arg) + print_request_list(prj.apiurl, prj.name) + + args.sort() + pacs = findpacs(args, progress_obj=self.download_progress) + + if opts.revision and len(args) == 1: + rev, dummy = parseRevisionOption(opts.revision) + if not checkRevision(pacs[0].prjname, pacs[0].name, rev, pacs[0].apiurl): + print('Revision \'%s\' does not exist' % rev, file=sys.stderr) + sys.exit(1) + else: + rev = None + + for p in pacs: + if len(pacs) > 1: + print('Updating %s' % p.name) + + # this shouldn't be needed anymore with the new update mechanism + # an expand/unexpand update is treated like a normal update (there's nothing special) + # FIXME: ugly workaround for #399247 +# if opts.expand_link or opts.unexpand_link: +# if [ i for i in p.filenamelist+p.filenamelist_unvers if p.status(i) != ' ' and p.status(i) != '?']: +# print >>sys.stderr, 'osc: cannot expand/unexpand because your working ' \ +# 'copy has local modifications.\nPlease revert/commit them ' \ +# 'and try again.' +# sys.exit(1) + + if not rev: + if opts.expand_link: + rev = p.latest_rev(expand=True) + if p.islink() and not p.isexpanded(): + print('Expanding to rev', rev) + elif opts.unexpand_link and p.islink() and p.isexpanded(): + rev = show_upstream_rev(p.apiurl, p.prjname, p.name, meta=p.meta) + print('Unexpanding to rev', rev) + elif (p.islink() and p.isexpanded()) or opts.server_side_source_service_files: + rev = p.latest_rev(include_service_files=opts.server_side_source_service_files) + + p.update(rev, opts.server_side_source_service_files, opts.limit_size) + if opts.source_service_files: + print('Running local source services') + p.run_source_services() + if opts.unexpand_link: + p.unmark_frozen() + rev = None + print_request_list(p.apiurl, p.prjname, p.name) + + + @cmdln.option('-f', '--force', action='store_true', + help='forces removal of entire package and its files') + @cmdln.alias('rm') + @cmdln.alias('del') + @cmdln.alias('remove') + def do_delete(self, subcmd, opts, *args): + """${cmd_name}: Mark files or package directories to be deleted upon the next 'checkin' + + usage: + cd .../PROJECT/PACKAGE + osc delete FILE [...] + cd .../PROJECT + osc delete PACKAGE [...] + + This command works on check out copies. Use "rdelete" for working on server + side only. This is needed for removing the entire project. + + As a safety measure, projects must be empty (i.e., you need to delete all + packages first). + + If you are sure that you want to remove a package and all + its files use \'--force\' switch. Sometimes this also works without --force. + + ${cmd_option_list} + """ + + if not args: + raise oscerr.WrongArgs('Missing argument.\n\n' \ + + self.get_cmd_help('delete')) + + args = parseargs(args) + # check if args contains a package which was removed by + # a non-osc command and mark it with the 'D'-state + arg_list = args[:] + for i in arg_list: + if not os.path.exists(i): + prj_dir, pac_dir = getPrjPacPaths(i) + if is_project_dir(prj_dir): + prj = Project(prj_dir, False) + if i in prj.pacs_broken: + if prj.get_state(i) != 'A': + prj.set_state(pac_dir, 'D') + else: + prj.del_package_node(i) + print(statfrmt('D', getTransActPath(i))) + args.remove(i) + prj.write_packages() + pacs = findpacs(args) + + for p in pacs: + if not p.todo: + prj_dir, pac_dir = getPrjPacPaths(p.absdir) + if is_project_dir(prj_dir): + if conf.config['do_package_tracking']: + prj = Project(prj_dir, False) + prj.delPackage(p, opts.force) + else: + print("WARNING: package tracking is disabled, operation skipped !", file=sys.stderr) + else: + pathn = getTransActPath(p.dir) + for filename in p.todo: + p.clear_from_conflictlist(filename) + ret, state = p.delete_file(filename, opts.force) + if ret: + print(statfrmt('D', os.path.join(pathn, filename))) + continue + if state == '?': + sys.exit('\'%s\' is not under version control' % filename) + elif state in ['A', 'M'] and not opts.force: + sys.exit('\'%s\' has local modifications (use --force to remove this file)' % filename) + elif state == 'S': + sys.exit('\'%s\' is marked as skipped and no local file with this name exists' % filename) + + + def do_resolved(self, subcmd, opts, *args): + """${cmd_name}: Remove 'conflicted' state on working copy files + + If an upstream change can't be merged automatically, a file is put into + in 'conflicted' ('C') state. Within the file, conflicts are marked with + special <<<<<<< as well as ======== and >>>>>>> lines. + + After manually resolving all conflicting parts, use this command to + remove the 'conflicted' state. + + Note: this subcommand does not semantically resolve conflicts or + remove conflict markers; it merely removes the conflict-related + artifact files and allows PATH to be committed again. + + usage: + osc resolved FILE [FILE...] + ${cmd_option_list} + """ + + if not args: + raise oscerr.WrongArgs('Missing argument.\n\n' \ + + self.get_cmd_help('resolved')) + + args = parseargs(args) + pacs = findpacs(args) + + for p in pacs: + for filename in p.todo: + print('Resolved conflicted state of "%s"' % filename) + p.clear_from_conflictlist(filename) + + + @cmdln.alias('dists') +# FIXME: using just ^DISCONTINUED as match is not a general approach and only valid for one instance +# we need to discuss an api call for that, if we need this +# @cmdln.option('-d', '--discontinued', action='store_true', +# help='show discontinued distributions') + def do_distributions(self, subcmd, opts, *args): + """${cmd_name}: Shows all available distributions + + This command shows the available distributions. For active distributions + it shows the name, project and name of the repository and a suggested default repository name. + + usage: + osc distributions + + ${cmd_option_list} + """ + apiurl = self.get_api_url() + + print('\n'.join(get_distibutions(apiurl)))#FIXME:, opts.discontinued)) + + @cmdln.hide(1) + def do_results_meta(self, subcmd, opts, *args): + print("Command results_meta is obsolete. Please use: osc results --xml") + sys.exit(1) + + @cmdln.hide(1) + @cmdln.option('-l', '--last-build', action='store_true', + help='show last build results (succeeded/failed/unknown)') + @cmdln.option('-r', '--repo', action='append', default = [], + help='Show results only for specified repo(s)') + @cmdln.option('-a', '--arch', action='append', default = [], + help='Show results only for specified architecture(s)') + @cmdln.option('', '--xml', action='store_true', + help='generate output in XML (former results_meta)') + def do_rresults(self, subcmd, opts, *args): + print("Command rresults is obsolete. Running 'osc results' instead") + self.do_results('results', opts, *args) + sys.exit(1) + + + @cmdln.option('-f', '--force', action='store_true', default=False, + help="Don't ask and delete files") + def do_rremove(self, subcmd, opts, project, package, *files): + """${cmd_name}: Remove source files from selected package + + ${cmd_usage} + ${cmd_option_list} + """ + apiurl = self.get_api_url() + + if len(files) == 0: + if not '/' in project: + raise oscerr.WrongArgs("Missing operand, type osc help rremove for help") + else: + files = (package, ) + project, package = project.split('/') + + for filename in files: + if not opts.force: + resp = raw_input("rm: remove source file `%s' from `%s/%s'? (yY|nN) " % (filename, project, package)) + if resp not in ('y', 'Y'): + continue + try: + delete_files(apiurl, project, package, (filename, )) + except HTTPError as e: + if opts.force: + print(e, file=sys.stderr) + body = e.read() + if e.code in [ 400, 403, 404, 500 ]: + if '<summary>' in body: + msg = body.split('<summary>')[1] + msg = msg.split('</summary>')[0] + print(msg, file=sys.stderr) + else: + raise e + + @cmdln.alias('r') + @cmdln.option('-l', '--last-build', action='store_true', + help='show last build results (succeeded/failed/unknown)') + @cmdln.option('-r', '--repo', action='append', default = [], + help='Show results only for specified repo(s)') + @cmdln.option('-a', '--arch', action='append', default = [], + help='Show results only for specified architecture(s)') + @cmdln.option('-v', '--verbose', action='store_true', default=False, + help='more verbose output') + @cmdln.option('-w', '--watch', action='store_true', default=False, + help='watch the results until all finished building') + @cmdln.option('', '--xml', action='store_true', default=False, + help='generate output in XML (former results_meta)') + @cmdln.option('', '--csv', action='store_true', default=False, + help='generate output in CSV format') + @cmdln.option('', '--format', default='%(repository)s|%(arch)s|%(state)s|%(dirty)s|%(code)s|%(details)s', + help='format string for csv output') + def do_results(self, subcmd, opts, *args): + """${cmd_name}: Shows the build results of a package or project + + Usage: + osc results # (inside working copy of PRJ or PKG) + osc results PROJECT [PACKAGE] + + ${cmd_option_list} + """ + + args = slash_split(args) + + apiurl = self.get_api_url() + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments (required none, one, or two)') + project = package = None + wd = os.curdir + if is_project_dir(wd): + project = store_read_project(wd) + elif is_package_dir(wd): + project = store_read_project(wd) + package = store_read_package(wd) + if len(args) > 0: + project = args[0] + if len(args) > 1: + package = args[1] + + if project == None: + raise oscerr.WrongOptions("No project given") + + if package == None: + if opts.arch == []: + opts.arch = None + if opts.repo == []: + opts.repo = None + opts.hide_legend = None + opts.name_filter = None + opts.status_filter = None + opts.vertical = None + opts.show_non_building = None + opts.show_excluded = None + self.do_prjresults('prjresults', opts, *args) + return + + if opts.xml and opts.csv: + raise oscerr.WrongOptions("--xml and --csv are mutual exclusive") + + kwargs = {'apiurl': apiurl, 'project': project, 'package': package, + 'lastbuild': opts.last_build, 'repository': opts.repo, + 'arch': opts.arch, 'wait': opts.watch} + if opts.xml or opts.csv: + for xml in get_package_results(**kwargs): + if opts.xml: + print(xml, end='') + else: + # csv formatting + results = result_xml_to_dicts(xml) + print('\n'.join(format_results(results, opts.format))) + else: + kwargs['verbose'] = opts.verbose + kwargs['wait'] = opts.watch + kwargs['printJoin'] = '\n' + get_results(**kwargs) + + + # WARNING: this function is also called by do_results. You need to set a default there + # as well when adding a new option! + @cmdln.option('-q', '--hide-legend', action='store_true', + help='hide the legend') + @cmdln.option('-c', '--csv', action='store_true', + help='csv output') + @cmdln.option('', '--xml', action='store_true', default=False, + help='generate output in XML') + @cmdln.option('-s', '--status-filter', metavar='STATUS', + help='show only packages with buildstatus STATUS (see legend)') + @cmdln.option('-n', '--name-filter', metavar='EXPR', + help='show only packages whose names match EXPR') + @cmdln.option('-a', '--arch', metavar='ARCH', + help='show results only for specified architecture(s)') + @cmdln.option('-r', '--repo', metavar='REPO', + help='show results only for specified repo(s)') + @cmdln.option('-V', '--vertical', action='store_true', + help='list packages vertically instead horizontally') + @cmdln.option('--show-excluded', action='store_true', + help='show packages that are excluded in all repos, also hide repos that have only excluded packages') + @cmdln.alias('pr') + def do_prjresults(self, subcmd, opts, *args): + """${cmd_name}: Shows project-wide build results + + Usage: + osc prjresults (inside working copy) + osc prjresults PROJECT + + ${cmd_option_list} + """ + apiurl = self.get_api_url() + + if args: + if len(args) == 1: + project = args[0] + else: + raise oscerr.WrongArgs('Wrong number of arguments.') + else: + wd = os.curdir + project = store_read_project(wd) + + if opts.xml: + print(''.join(show_prj_results_meta(apiurl, project))) + return + + print('\n'.join(get_prj_results(apiurl, project, hide_legend=opts.hide_legend, \ + csv=opts.csv, status_filter=opts.status_filter, \ + name_filter=opts.name_filter, repo=opts.repo, \ + arch=opts.arch, vertical=opts.vertical, \ + show_excluded=opts.show_excluded))) + + @cmdln.option('-q', '--hide-legend', action='store_true', + help='hide the legend') + @cmdln.option('-c', '--csv', action='store_true', + help='csv output') + @cmdln.option('-s', '--status-filter', metavar='STATUS', + help='show only packages with buildstatus STATUS (see legend)') + @cmdln.option('-n', '--name-filter', metavar='EXPR', + help='show only packages whose names match EXPR') + + @cmdln.hide(1) + def do_rprjresults(self, subcmd, opts, *args): + print("Command rprjresults is obsolete. Please use 'osc prjresults'") + sys.exit(1) + + @cmdln.alias('bl') + @cmdln.alias('blt') + @cmdln.alias('buildlogtail') + @cmdln.option('-l', '--last', action='store_true', + help='Show the last finished log file') + @cmdln.option('-o', '--offset', metavar='OFFSET', + help='get log start or end from the offset') + @cmdln.option('-s', '--strip-time', action='store_true', + help='strip leading build time from the log') + def do_buildlog(self, subcmd, opts, *args): + """${cmd_name}: Shows the build log of a package + + Shows the log file of the build of a package. Can be used to follow the + log while it is being written. + Needs to be called from within a package directory. + + When called as buildlogtail (or blt) it just shows the end of the logfile. + This is useful to see just a build failure reasons. + + The arguments REPOSITORY and ARCH are the first two columns in the 'osc + results' output. If the buildlog url is used buildlog command has the + same behavior as remotebuildlog. + + ${cmd_usage} [REPOSITORY ARCH | BUILDLOGURL] + ${cmd_option_list} + """ + import osc.build + + project = package = repository = arch = None + + apiurl = self.get_api_url() + + if len(args) == 1 and args[0].startswith('http'): + apiurl, project, package, repository, arch = parse_buildlogurl(args[0]) + else: + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + if len(args) == 1: + repository, arch = self._find_last_repo_arch(args[0], fatal=False) + if repository is None: + # no local build with this repo was done + print('failed to guess arch, using hostarch') + repository = args[0] + arch = osc.build.hostarch + elif len(args) < 2: + self.print_repos() + elif len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + else: + repository = args[0] + arch = args[1] + + offset = 0 + if subcmd == "blt" or subcmd == "buildlogtail": + query = { 'view': 'entry' } + if opts.last: + query['last'] = 1 + u = makeurl(self.get_api_url(), ['build', project, repository, arch, package, '_log'], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + offset = int(root.find('entry').get('size')) + if opts.offset: + offset = offset - int(opts.offset) + else: + offset = offset - ( 8 * 1024 ) + if offset < 0: + offset = 0 + elif opts.offset: + offset = int(opts.offset) + strip_time = opts.strip_time or conf.config['buildlog_strip_time'] + print_buildlog(apiurl, project, package, repository, arch, offset, strip_time, opts.last) + + + def print_repos(self, repos_only=False, exc_class=oscerr.WrongArgs, exc_msg='Missing arguments'): + wd = os.curdir + doprint = False + if is_package_dir(wd): + msg = "package" + doprint = True + elif is_project_dir(wd): + msg = "project" + doprint = True + + if doprint: + print('Valid arguments for this %s are:' % msg) + print() + if repos_only: + self.do_repositories("repos_only", None) + else: + self.do_repositories(None, None) + raise exc_class(exc_msg) + + @cmdln.alias('rbl') + @cmdln.alias('rbuildlog') + @cmdln.alias('rblt') + @cmdln.alias('rbuildlogtail') + @cmdln.alias('remotebuildlogtail') + @cmdln.option('-l', '--last', action='store_true', + help='Show the last finished log file') + @cmdln.option('-o', '--offset', metavar='OFFSET', + help='get log starting or ending from the offset') + @cmdln.option('-s', '--strip-time', action='store_true', + help='strip leading build time from the log') + def do_remotebuildlog(self, subcmd, opts, *args): + """${cmd_name}: Shows the build log of a package + + Shows the log file of the build of a package. Can be used to follow the + log while it is being written. + + remotebuildlogtail shows just the tail of the log file. + + usage: + osc remotebuildlog project package repository arch + or + osc remotebuildlog project/package/repository/arch + or + osc remotebuildlog buildlogurl + ${cmd_option_list} + """ + if len(args) == 1 and args[0].startswith('http'): + apiurl, project, package, repository, arch = parse_buildlogurl(args[0]) + else: + args = slash_split(args) + apiurl = self.get_api_url() + if len(args) < 4: + raise oscerr.WrongArgs('Too few arguments.') + elif len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + else: + project, package, repository, arch = args + + offset = 0 + if subcmd == "rblt" or subcmd == "rbuildlogtail" or subcmd == "remotebuildlogtail": + query = { 'view': 'entry' } + if opts.last: + query['last'] = 1 + u = makeurl(self.get_api_url(), ['build', project, repository, arch, package, '_log'], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + offset = int(root.find('entry').get('size')) + if opts.offset: + offset = offset - int(opts.offset) + else: + offset = offset - ( 8 * 1024 ) + if offset < 0: + offset = 0 + elif opts.offset: + offset = int(opts.offset) + strip_time = opts.strip_time or conf.config['buildlog_strip_time'] + print_buildlog(apiurl, project, package, repository, arch, offset, strip_time, opts.last) + + def _find_last_repo_arch(self, repo=None, fatal=True): + import glob + files = glob.glob(os.path.join(os.getcwd(), store, "_buildinfo-*")) + if repo is not None: + files = [f for f in files + if os.path.basename(f).replace('_buildinfo-', '').startswith(repo + '-')] + if not files: + if not fatal: + return None, None + self.print_repos() + cfg = files[0] + # find newest file + for f in files[1:]: + if os.stat(f).st_atime > os.stat(cfg).st_atime: + cfg = f + root = ET.parse(cfg).getroot() + repo = root.get("repository") + arch = root.find("arch").text + return repo, arch + + @cmdln.alias('lbl') + @cmdln.option('-o', '--offset', metavar='OFFSET', + help='get log starting from offset') + @cmdln.option('-s', '--strip-time', action='store_true', + help='strip leading build time from the log') + def do_localbuildlog(self, subcmd, opts, *args): + """${cmd_name}: Shows the build log of a local buildchroot + + usage: + osc lbl [REPOSITORY [ARCH]] + osc lbl # show log of newest last local build + + ${cmd_option_list} + """ + if conf.config['build-type']: + # FIXME: raise Exception instead + print('Not implemented for VMs', file=sys.stderr) + sys.exit(1) + + if len(args) == 0 or len(args) == 1: + project = store_read_project('.') + package = store_read_package('.') + repo = None + if args: + repo = args[0] + repo, arch = self._find_last_repo_arch(repo) + elif len(args) == 2: + project = store_read_project('.') + package = store_read_package('.') + repo = args[0] + arch = args[1] + else: + if is_package_dir(os.curdir): + self.print_repos() + raise oscerr.WrongArgs('Wrong number of arguments.') + + # TODO: refactor/unify buildroot calculation and move it to core.py + buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root']) + apihost = urlsplit(self.get_api_url())[1] + buildroot = buildroot % {'project': project, 'package': package, + 'repo': repo, 'arch': arch, 'apihost': apihost} + offset = 0 + if opts.offset: + offset = int(opts.offset) + logfile = os.path.join(buildroot, '.build.log') + if not os.path.isfile(logfile): + raise oscerr.OscIOError(None, 'logfile \'%s\' does not exist' % logfile) + f = open(logfile, 'r') + f.seek(offset) + data = f.read(BUFSIZE) + while len(data): + if opts.strip_time or conf.config['buildlog_strip_time']: + data = buildlog_strip_time(data) + sys.stdout.write(data) + data = f.read(BUFSIZE) + f.close() + + @cmdln.alias('tr') + def do_triggerreason(self, subcmd, opts, *args): + """${cmd_name}: Show reason why a package got triggered to build + + The server decides when a package needs to get rebuild, this command + shows the detailed reason for a package. A brief reason is also stored + in the jobhistory, which can be accessed via "osc jobhistory". + + Trigger reasons might be: + - new build (never build yet or rebuild manually forced) + - source change (eg. on updating sources) + - meta change (packages which are used for building have changed) + - rebuild count sync (In case that it is configured to sync release numbers) + + usage in package or project directory: + osc reason REPOSITORY ARCH + osc reason PROJECT PACKAGE REPOSITORY ARCH + + ${cmd_option_list} + """ + wd = os.curdir + args = slash_split(args) + project = package = repository = arch = None + + if len(args) < 2: + self.print_repos() + + apiurl = self.get_api_url() + + if len(args) == 2: # 2 + if is_package_dir('.'): + package = store_read_package(wd) + else: + raise oscerr.WrongArgs('package is not specified.') + project = store_read_project(wd) + repository = args[0] + arch = args[1] + elif len(args) == 4: + project = args[0] + package = args[1] + repository = args[2] + arch = args[3] + else: + raise oscerr.WrongArgs('Too many arguments.') + + print(apiurl, project, package, repository, arch) + xml = show_package_trigger_reason(apiurl, project, package, repository, arch) + root = ET.fromstring(xml) + reason = root.find('explain').text + print(reason) + if reason == "meta change": + print("changed keys:") + for package in root.findall('packagechange'): + print(" ", package.get('change'), package.get('key')) + + + # FIXME: the new osc syntax should allow to specify multiple packages + # FIXME: the command should optionally use buildinfo data to show all dependencies + @cmdln.alias('whatdependson') + def do_dependson(self, subcmd, opts, *args): + """${cmd_name}: Show the build dependencies + + The command dependson and whatdependson can be used to find out what + will be triggered when a certain package changes. + This is no guarantee, since the new build might have changed dependencies. + + dependson shows the build dependencies inside of a project, valid for a + given repository and architecture. + NOTE: to see all binary packages, which can trigger a build you need to + refer the buildinfo, since this command shows only the dependencies + inside of a project. + + The arguments REPOSITORY and ARCH can be taken from the first two columns + of the 'osc repos' output. + + usage in package or project directory: + osc dependson REPOSITORY ARCH + osc whatdependson REPOSITORY ARCH + + usage: + osc dependson PROJECT [PACKAGE] REPOSITORY ARCH + osc whatdependson PROJECT [PACKAGE] REPOSITORY ARCH + + ${cmd_option_list} + """ + wd = os.curdir + args = slash_split(args) + project = packages = repository = arch = reverse = None + + if len(args) < 2 and (is_package_dir('.') or is_project_dir('.')): + self.print_repos() + + if len(args) > 4: + raise oscerr.WrongArgs('Too many arguments.') + + apiurl = self.get_api_url() + + if len(args) < 3: # 2 + if is_package_dir('.'): + packages = [store_read_package(wd)] + elif not is_project_dir('.'): + raise oscerr.WrongArgs('Project and package is not specified.') + project = store_read_project(wd) + repository = args[0] + arch = args[1] + + if len(args) == 3: + project = args[0] + repository = args[1] + arch = args[2] + + if len(args) == 4: + project = args[0] + packages = [args[1]] + repository = args[2] + arch = args[3] + + if subcmd == 'whatdependson': + reverse = 1 + + xml = get_dependson(apiurl, project, repository, arch, packages, reverse) + + root = ET.fromstring(xml) + for package in root.findall('package'): + print(package.get('name'), ":") + for dep in package.findall('pkgdep'): + print(" ", dep.text) + + + @cmdln.option('-d', '--debug', action='store_true', + help='verbose output of build dependencies') + @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append', + help='Add this package when computing the buildinfo') + @cmdln.option('-p', '--prefer-pkgs', metavar='DIR', action='append', + help='Prefer packages from this directory when installing the build-root') + def do_buildinfo(self, subcmd, opts, *args): + """${cmd_name}: Shows the build info + + Shows the build "info" which is used in building a package. + This command is mostly used internally by the 'build' subcommand. + It needs to be called from within a package directory. + + The BUILD_DESCR argument is optional. BUILD_DESCR is a local RPM specfile + or Debian "dsc" file. If specified, it is sent to the server, and the + buildinfo will be based on it. If the argument is not supplied, the + buildinfo is derived from the specfile which is currently on the source + repository server. + + The returned data is XML and contains a list of the packages used in + building, their source, and the expanded BuildRequires. + + The arguments REPOSITORY and ARCH are optional. They can be taken from + the first two columns of the 'osc repos' output. If not specified, + REPOSITORY defaults to the 'build_repositoy' config entry in your '.oscrc' + and ARCH defaults to your host architecture. + + usage: + in a package working copy: + osc buildinfo [OPTS] REPOSITORY ARCH BUILD_DESCR + osc buildinfo [OPTS] REPOSITORY (ARCH = hostarch, BUILD_DESCR is detected automatically) + osc buildinfo [OPTS] ARCH (REPOSITORY = build_repository (config option), BUILD_DESCR is detected automatically) + osc buildinfo [OPTS] BUILD_DESCR (REPOSITORY = build_repository (config option), ARCH = hostarch) + osc buildinfo [OPTS] (REPOSITORY = build_repository (config option), ARCH = hostarch, BUILD_DESCR is detected automatically) + Note: if BUILD_DESCR does not exist locally the remote BUILD_DESCR is used + + osc buildinfo [OPTS] PROJECT PACKAGE REPOSITORY ARCH [BUILD_DESCR] + + ${cmd_option_list} + """ + wd = os.curdir + args = slash_split(args) + + project = package = repository = arch = build_descr = None + if len(args) <= 3: + if not is_package_dir('.'): + raise oscerr.WrongArgs('Incorrect number of arguments (Note: \'.\' is no package wc)') + project = store_read_project('.') + package = store_read_package('.') + repository, arch, build_descr = self.parse_repoarchdescr(args, ignore_descr=True) + elif len(args) == 4 or len(args) == 5: + project = args[0] + package = args[1] + repository = args[2] + arch = args[3] + if len(args) == 5: + build_descr = args[4] + else: + raise oscerr.WrongArgs('Too many arguments.') + + apiurl = self.get_api_url() + + build_descr_data = None + if not build_descr is None: + build_descr_data = open(build_descr, 'r').read() + if opts.prefer_pkgs and build_descr_data is None: + raise oscerr.WrongArgs('error: a build description is needed if \'--prefer-pkgs\' is used') + elif opts.prefer_pkgs: + from .build import get_prefer_pkgs + print('Scanning the following dirs for local packages: %s' % ', '.join(opts.prefer_pkgs)) + prefer_pkgs, cpio = get_prefer_pkgs(opts.prefer_pkgs, arch, os.path.splitext(build_descr)[1]) + cpio.add(os.path.basename(build_descr), build_descr_data) + build_descr_data = cpio.get() + + print(''.join(get_buildinfo(apiurl, + project, package, repository, arch, + specfile=build_descr_data, + debug=opts.debug, + addlist=opts.extra_pkgs))) + + + def do_buildconfig(self, subcmd, opts, *args): + """${cmd_name}: Shows the build config + + Shows the build configuration which is used in building a package. + This command is mostly used internally by the 'build' command. + + The returned data is the project-wide build configuration in a format + which is directly readable by the build script. It contains RPM macros + and BuildRequires expansions, for example. + + The argument REPOSITORY an be taken from the first column of the + 'osc repos' output. + + usage: + osc buildconfig REPOSITORY (in pkg or prj dir) + osc buildconfig PROJECT REPOSITORY + ${cmd_option_list} + """ + + wd = os.curdir + args = slash_split(args) + + if len(args) < 1 and (is_package_dir('.') or is_project_dir('.')): + self.print_repos(True) + + if len(args) > 2: + raise oscerr.WrongArgs('Too many arguments.') + + apiurl = self.get_api_url() + + if len(args) == 1: + #FIXME: check if args[0] is really a repo and not a project, need a is_project() function for this + project = store_read_project(wd) + repository = args[0] + elif len(args) == 2: + project = args[0] + repository = args[1] + else: + raise oscerr.WrongArgs('Wrong number of arguments.') + + print(''.join(get_buildconfig(apiurl, project, repository))) + + + @cmdln.alias('repos') + @cmdln.alias('platforms') + def do_repositories(self, subcmd, opts, *args): + """${cmd_name}: shows repositories configured for a project. + It skips repositories by default which are disabled for a given package. + + usage: + osc repos + osc repos [PROJECT] [PACKAGE] + + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + project = None + package = None + disabled = None + + if len(args) == 1: + project = args[0] + elif len(args) == 2: + project = args[0] + package = args[1] + elif len(args) == 0: + if is_package_dir('.'): + package = store_read_package('.') + project = store_read_project('.') + elif is_project_dir('.'): + project = store_read_project('.') + else: + raise oscerr.WrongArgs('Wrong number of arguments') + + if project is None: + raise oscerr.WrongArgs('No project specified') + + if package is not None: + disabled = show_package_disabled_repos(apiurl, project, package) + + if subcmd == 'repos_only': + for repo in get_repositories_of_project(apiurl, project): + if (disabled is None) or ((disabled is not None) and (repo not in disabled)): + print(repo) + else: + data = [] + for repo in get_repos_of_project(apiurl, project): + if (disabled is None) or ((disabled is not None) and (repo.name not in disabled)): + data += [repo.name, repo.arch] + + for row in build_table(2, data, width=2): + print(row) + + + def parse_repoarchdescr(self, args, noinit = False, alternative_project = None, ignore_descr = False, vm_type = None): + """helper to parse the repo, arch and build description from args""" + import osc.build + import glob + arg_arch = arg_repository = arg_descr = None + if len(args) < 3: + # some magic, works only sometimes, but people seem to like it :/ + all_archs = [] + for mainarch in osc.build.can_also_build: + all_archs.append(mainarch) + for subarch in osc.build.can_also_build.get(mainarch): + all_archs.append(subarch) + for arg in args: + if arg.endswith('.spec') or arg.endswith('.dsc') or arg.endswith('.kiwi') or arg.endswith('.livebuild') or arg == 'PKGBUILD' or arg == 'build.collax': + arg_descr = arg + else: + if (arg == osc.build.hostarch or arg in all_archs) and arg_arch is None: + # it seems to be an architecture in general + arg_arch = arg + if not (arg in osc.build.can_also_build.get(osc.build.hostarch) or arg == osc.build.hostarch): + print("WARNING: native compile is not possible, an emulator must be configured!") + elif not arg_repository: + arg_repository = arg + else: +# raise oscerr.WrongArgs('\'%s\' is neither a build description nor a supported arch' % arg) + # take it as arch (even though this is no supported arch) - hopefully, this invalid + # arch will be detected below + arg_arch = arg + else: + arg_repository, arg_arch, arg_descr = args + + arg_arch = arg_arch or osc.build.hostarch + + repositories = [] + # store list of repos for potential offline use + repolistfile = os.path.join(os.getcwd(), osc.core.store, "_build_repositories") + if noinit: + repositories = Repo.fromfile(repolistfile) + else: + project = alternative_project or store_read_project('.') + apiurl = self.get_api_url() + repositories = list(get_repos_of_project(apiurl, project)) + if not len(repositories): + raise oscerr.WrongArgs('no repositories defined for project \'%s\'' % project) + if alternative_project is None: + # only persist our own repos + Repo.tofile(repolistfile, repositories) + + repo_names = sorted(set([r.name for r in repositories])) + if not arg_repository and repositories: + # XXX: we should avoid hardcoding repository names + # Use a default value from config, but just even if it's available + # unless try standard, or openSUSE_Factory, or openSUSE_Tumbleweed + arg_repository = repositories[-1].name + for repository in (conf.config['build_repository'], 'standard', 'openSUSE_Factory', 'openSUSE_Tumbleweed'): + if repository in repo_names: + arg_repository = repository + break + + if not arg_repository: + raise oscerr.WrongArgs('please specify a repository') + if not noinit: + if not arg_repository in repo_names: + raise oscerr.WrongArgs('%s is not a valid repository, use one of: %s' % (arg_repository, ', '.join(repo_names))) + arches = [r.arch for r in repositories if r.name == arg_repository and r.arch] + if arches and not arg_arch in arches: + raise oscerr.WrongArgs('%s is not a valid arch for the repository %s, use one of: %s' % (arg_arch, arg_repository, ', '.join(arches))) + + # can be implemented using + # reduce(lambda x, y: x + y, (glob.glob(x) for x in ('*.spec', '*.dsc', '*.kiwi'))) + # but be a bit more readable :) + descr = glob.glob('*.spec') + glob.glob('*.dsc') + glob.glob('*.kiwi') + glob.glob('*.livebuild') + glob.glob('PKGBUILD') + glob.glob('build.collax') + + # FIXME: + # * request repos from server and select by build type. + if not arg_descr and len(descr) == 1: + arg_descr = descr[0] + elif not arg_descr: + msg = None + if len(descr) > 1: + # guess/prefer build descrs like the following: + # <pac>-<repo>.<ext> > <pac>.<ext> + # no guessing for arch's PKGBUILD files (the backend does not do any guessing, too) + pac = os.path.basename(os.getcwd()) + if is_package_dir(os.getcwd()): + pac = store_read_package(os.getcwd()) + extensions = ['spec', 'dsc', 'kiwi', 'livebuild'] + cands = [i for i in descr for ext in extensions if i == '%s-%s.%s' % (pac, arg_repository, ext)] + if len(cands) == 1: + arg_descr = cands[0] + else: + cands = [i for i in descr for ext in extensions if i == '%s.%s' % (pac, ext)] + if len(cands) == 1: + arg_descr = cands[0] + if not arg_descr: + msg = 'Multiple build description files found: %s' % ', '.join(descr) + elif not ignore_descr: + msg = 'Missing argument: build description (spec, dsc, kiwi or livebuild file)' + try: + p = Package('.') + if p.islink() and not p.isexpanded(): + msg += ' (this package is not expanded - you might want to try osc up --expand)' + except: + pass + if msg: + raise oscerr.WrongArgs(msg) + + return arg_repository, arg_arch, arg_descr + + + @cmdln.option('--clean', action='store_true', + help='Delete old build root before initializing it') + @cmdln.option('-o', '--offline', action='store_true', + help='Start with cached prjconf and packages without contacting the api server') + @cmdln.option('-l', '--preload', action='store_true', + help='Preload all files into the cache for offline operation') + @cmdln.option('--no-changelog', action='store_true', + help='don\'t update the package changelog from a changes file') + @cmdln.option('--rsync-src', metavar='RSYNCSRCPATH', dest='rsyncsrc', + help='Copy folder to buildroot after installing all RPMs. Use together with --rsync-dest. This is the path on the HOST filesystem e.g. /tmp/linux-kernel-tree. It defines RSYNCDONE 1 .') + @cmdln.option('--rsync-dest', metavar='RSYNCDESTPATH', dest='rsyncdest', + help='Copy folder to buildroot after installing all RPMs. Use together with --rsync-src. This is the path on the TARGET filesystem e.g. /usr/src/packages/BUILD/linux-2.6 .') + @cmdln.option('--overlay', metavar='OVERLAY', + help='Copy overlay filesystem to buildroot after installing all RPMs .') + @cmdln.option('--noinit', '--no-init', action='store_true', + help='Skip initialization of build root and start with build immediately.') + @cmdln.option('--nochecks', '--no-checks', action='store_true', + help='Do not run build checks on the resulting packages.') + @cmdln.option('--no-verify', '--noverify', action='store_true', + help='Skip signature verification of packages used for build. (Global config in .oscrc: no_verify)') + @cmdln.option('--noservice', '--no-service', action='store_true', + help='Skip run of local source services as specified in _service file.') + @cmdln.option('-p', '--prefer-pkgs', metavar='DIR', action='append', + help='Prefer packages from this directory when installing the build-root') + @cmdln.option('-k', '--keep-pkgs', metavar='DIR', + help='Save built packages into this directory') + @cmdln.option('-x', '--extra-pkgs', metavar='PAC', action='append', + help='Add this package when installing the build-root') + @cmdln.option('--root', metavar='ROOT', + help='Build in specified directory') + @cmdln.option('-j', '--jobs', metavar='N', + help='Compile with N jobs') + @cmdln.option('-t', '--threads', metavar='N', + help='Compile with N threads') + @cmdln.option('--icecream', metavar='N', + help='use N parallel build jobs with icecream') + @cmdln.option('--ccache', action='store_true', + help='use ccache to speed up rebuilds') + @cmdln.option('--with', metavar='X', dest='_with', action='append', + help='enable feature X for build') + @cmdln.option('--without', metavar='X', action='append', + help='disable feature X for build') + @cmdln.option('--define', metavar='\'X Y\'', action='append', + help='define macro X with value Y') + @cmdln.option('--userootforbuild', action='store_true', + help='Run build as root. The default is to build as ' + 'unprivileged user. Note that a line "# norootforbuild" ' + 'in the spec file will invalidate this option.') + @cmdln.option('--build-uid', metavar='uid:gid|"caller"', + help='specify the numeric uid:gid pair to assign to the ' + 'unprivileged "abuild" user or use "caller" to use the current user uid:gid') + @cmdln.option('--local-package', action='store_true', + help='build a package which does not exist on the server') + @cmdln.option('--linksources', action='store_true', + help='use hard links instead of a deep copied source') + @cmdln.option('--vm-type', metavar='TYPE', + help='use VM type TYPE (e.g. kvm)') + @cmdln.option('--vm-telnet', metavar='TELNET', + help='Launch a telnet server inside of VM build') + @cmdln.option('--target', metavar='TARGET', + help='define target platform') + @cmdln.option('--alternative-project', metavar='PROJECT', + help='specify the build target project') + @cmdln.option('-d', '--debuginfo', action='store_true', + help='also build debuginfo sub-packages') + @cmdln.option('--disable-debuginfo', action='store_true', + help='disable build of debuginfo packages') + @cmdln.option('-b', '--baselibs', action='store_true', + help='Create -32bit/-64bit/-x86 rpms for other architectures') + @cmdln.option('--release', metavar='N', + help='set release number of the package to N') + @cmdln.option('--disable-cpio-bulk-download', action='store_true', + help='disable downloading packages as cpio archive from api') + @cmdln.option('--cpio-bulk-download', action='store_false', + dest='disable_cpio_bulk_download', help=SUPPRESS_HELP) + @cmdln.option('--download-api-only', action='store_true', + help='only fetch packages from the api') + @cmdln.option('--oldpackages', metavar='DIR', + help='take previous build from DIR (special values: _self, _link)') + @cmdln.option('--shell', action='store_true', + help=SUPPRESS_HELP) + @cmdln.option('--host', metavar='HOST', + help='perform the build on a remote server - user@server:~/remote/directory') + @cmdln.option('--trust-all-projects', action='store_true', + help='trust packages from all projects') + def do_build(self, subcmd, opts, *args): + """${cmd_name}: Build a package on your local machine + + You need to call the command inside a package directory, which should be a + buildsystem checkout. (Local modifications are fine.) + + The arguments REPOSITORY and ARCH can be taken from the first two columns + of the 'osc repos' output. BUILD_DESCR is either a RPM spec file, or a + Debian dsc file. + + The command honours packagecachedir, build-root and build-uid + settings in .oscrc, if present. You may want to set su-wrapper = 'sudo' + in .oscrc, and configure sudo with option NOPASSWD for /usr/bin/build. + + If neither --clean nor --noinit is given, build will reuse an existing + build-root again, removing unneeded packages and add missing ones. This + is usually the fastest option. + + If the package doesn't exist on the server please use the --local-package + option. + If the project of the package doesn't exist on the server please use the + --alternative-project <alternative-project> option: + Example: + osc build [OPTS] --alternative-project openSUSE:10.3 standard i586 BUILD_DESCR + + usage: + osc build [OPTS] REPOSITORY ARCH BUILD_DESCR + osc build [OPTS] REPOSITORY ARCH + osc build [OPTS] REPOSITORY (ARCH = hostarch, BUILD_DESCR is detected automatically) + osc build [OPTS] ARCH (REPOSITORY = build_repository (config option), BUILD_DESCR is detected automatically) + osc build [OPTS] BUILD_DESCR (REPOSITORY = build_repository (config option), ARCH = hostarch) + osc build [OPTS] (REPOSITORY = build_repository (config option), ARCH = hostarch, BUILD_DESCR is detected automatically) + + # Note: + # Configuration can be overridden by envvars, e.g. + # OSC_SU_WRAPPER overrides the setting of su-wrapper. + # OSC_BUILD_ROOT overrides the setting of build-root. + # OSC_PACKAGECACHEDIR overrides the setting of packagecachedir. + + ${cmd_option_list} + """ + + import osc.build + + if which(conf.config['build-cmd']) is None: + print('Error: build (\'%s\') command not found' % conf.config['build-cmd'], file=sys.stderr) + print('Install the build package from http://download.opensuse.org/repositories/openSUSE:/Tools/', file=sys.stderr) + return 1 + + if opts.debuginfo and opts.disable_debuginfo: + raise oscerr.WrongOptions('osc: --debuginfo and --disable-debuginfo are mutual exclusive') + + if len(args) > 3: + raise oscerr.WrongArgs('Too many arguments') + + args = self.parse_repoarchdescr(args, opts.noinit or opts.offline, opts.alternative_project, False, opts.vm_type) + + # check for source services + r = None + try: + if not opts.offline and not opts.noservice: + p = Package('.') + r = p.run_source_services(verbose=True) + except: + print("WARNING: package is not existing on server yet") + opts.local_package = True + + if opts.offline or opts.local_package or r == None: + print("WARNING: source service from package or project will not be executed. This may not be the same build as on server!") + elif (conf.config['local_service_run'] and not opts.noservice) and not opts.noinit: + if r != 0: + print('Source service run failed!', file=sys.stderr) + sys.exit(1) + # that is currently unreadable on cli, we should not have a backtrace on standard errors: + #raise oscerr.ServiceRuntimeError('Service run failed: \'%s\'', r) + + if conf.config['no_verify']: + opts.no_verify = True + + if opts.keep_pkgs and not os.path.isdir(opts.keep_pkgs): + if os.path.exists(opts.keep_pkgs): + raise oscerr.WrongOptions('Preferred save location \'%s\' is not a directory' % opts.keep_pkgs) + else: + os.makedirs(opts.keep_pkgs) + + if opts.prefer_pkgs: + for d in opts.prefer_pkgs: + if not os.path.isdir(d): + raise oscerr.WrongOptions('Preferred package location \'%s\' is not a directory' % d) + + if opts.offline and opts.preload: + raise oscerr.WrongOptions('--offline and --preload are mutually exclusive') + + print('Building %s for %s/%s' % (args[2], args[0], args[1])) + if not opts.host: + return osc.build.main(self.get_api_url(), opts, args) + else: + return self._do_rbuild(subcmd, opts, *args) + + def _do_rbuild(self, subcmd, opts, *args): + + # drop the --argument, value tuple from the list + def drop_arg2(lst, name): + if not name: + return lst + while name in lst: + i = lst.index(name) + lst.pop(i+1) + lst.pop(i) + return lst + + # change the local directory to more suitable remote one in hostargs + # and perform the rsync to such location as well + def rsync_dirs_2host(hostargs, short_name, long_name, dirs): + + drop_arg2(hostargs, short_name) + drop_arg2(hostargs, long_name) + + for pdir in dirs: + # drop the last '/' from pdir name - this is because + # rsync foo remote:/bar create /bar/foo on remote machine + # rsync foo/ remote:/bar copy the content of foo in the /bar + if pdir[-1:] == os.path.sep: + pdir = pdir[:-1] + + hostprefer = os.path.join( + hostpath, + basename, + "%s__" % (long_name.replace('-', '_')), + os.path.basename(os.path.abspath(pdir))) + hostargs.append(long_name) + hostargs.append(hostprefer) + + rsync_prefer_cmd = ['rsync', '-az', '--delete', '-e', 'ssh', + pdir, + "%s:%s" % (hostname, os.path.dirname(hostprefer))] + print('Run: %s' % " ".join(rsync_prefer_cmd)) + ret = run_external(rsync_prefer_cmd[0], *rsync_prefer_cmd[1:]) + if ret != 0: + return ret + + return 0 + + + cwd = os.getcwd() + basename = os.path.basename(cwd) + if not ':' in opts.host: + hostname = opts.host + hostpath = "~/" + else: + hostname, hostpath = opts.host.split(':', 1) + + # arguments for build: use all arguments behind build and drop --host 'HOST' + hostargs = sys.argv[sys.argv.index(subcmd)+1:] + drop_arg2(hostargs, '--host') + + # global arguments: use first '-' up to subcmd + gi = 0 + for i, a in enumerate(sys.argv): + if a == subcmd: + break + if a[0] == '-': + gi = i + break + + if gi: + hostglobalargs = sys.argv[gi : sys.argv.index(subcmd)+1] + else: + hostglobalargs = (subcmd, ) + + # keep-pkgs + hostkeep = None + if opts.keep_pkgs: + drop_arg2(hostargs, '-k') + drop_arg2(hostargs, '--keep-pkgs') + hostkeep = os.path.join( + hostpath, + basename, + "__keep_pkgs__", + "") # <--- this adds last '/', thus triggers correct rsync behavior + hostargs.append('--keep-pkgs') + hostargs.append(hostkeep) + + ### run all commands ### + # 1.) rsync sources + rsync_source_cmd = ['rsync', '-az', '--delete', '-e', 'ssh', cwd, "%s:%s" % (hostname, hostpath)] + print('Run: %s' % " ".join(rsync_source_cmd)) + ret = run_external(rsync_source_cmd[0], *rsync_source_cmd[1:]) + if ret != 0: + return ret + + # 2.) rsync prefer-pkgs dirs, overlay and rsyns-src + if opts.prefer_pkgs: + ret = rsync_dirs_2host(hostargs, '-p', '--prefer-pkgs', opts.prefer_pkgs) + if ret != 0: + return ret + + for arg, long_name in ((opts.rsyncsrc, '--rsync-src'), (opts.overlay, '--overlay')): + if not arg: + continue + ret = rsync_dirs_2host(hostargs, None, long_name, (arg, )) + if ret != 0: + return ret + + # 3.) call osc build + osc_cmd = "osc" + for var in ('OSC_SU_WRAPPER', 'OSC_BUILD_ROOT', 'OSC_PACKAGECACHEDIR'): + if os.getenv(var): + osc_cmd = "%s=%s %s" % (var, os.getenv(var), osc_cmd) + + ssh_cmd = \ + ['ssh', '-t', hostname, + "cd %(remote_dir)s; %(osc_cmd)s %(global_args)s %(local_args)s" % dict( + remote_dir = os.path.join(hostpath, basename), + osc_cmd = osc_cmd, + global_args = " ".join(hostglobalargs), + local_args = " ".join(hostargs)) + ] + print('Run: %s' % " ".join(ssh_cmd)) + build_ret = run_external(ssh_cmd[0], *ssh_cmd[1:]) + if build_ret != 0: + return build_ret + + # 4.) get keep-pkgs back + if opts.keep_pkgs: + ret = rsync_keep_cmd = ['rsync', '-az', '-e', 'ssh', "%s:%s" % (hostname, hostkeep), opts.keep_pkgs] + print('Run: %s' % " ".join(rsync_keep_cmd)) + ret = run_external(rsync_keep_cmd[0], *rsync_keep_cmd[1:]) + if ret != 0: + return ret + + return build_ret + + + @cmdln.option('--local-package', action='store_true', + help='package doesn\'t exist on the server') + @cmdln.option('--alternative-project', metavar='PROJECT', + help='specify the used build target project') + @cmdln.option('--noinit', '--no-init', action='store_true', + help='do not guess/verify specified repository') + @cmdln.option('-r', '--login-as-root', action='store_true', + help='login as root instead of abuild') + @cmdln.option('--root', metavar='ROOT', + help='Path to the buildroot') + @cmdln.option('-o', '--offline', action='store_true', + help='Use cached data without contacting the api server') + def do_chroot(self, subcmd, opts, *args): + """${cmd_name}: opens a shell inside of the build root + + chroot into the build root for the given repository, arch and build description + (NOTE: this command does not work if a VM is used) + + usage: + osc chroot [OPTS] REPOSITORY ARCH BUILD_DESCR + osc chroot [OPTS] REPOSITORY (ARCH = hostarch, BUILD_DESCR is detected automatically) + osc chroot [OPTS] ARCH (REPOSITORY = build_repository (config option), BUILD_DESCR is detected automatically) + osc chroot [OPTS] BUILD_DESCR (REPOSITORY = build_repository (config option), ARCH = hostarch) + osc chroot [OPTS] (REPOSITORY = build_repository (config option), ARCH = hostarch, BUILD_DESCR is detected automatically) + ${cmd_option_list} + """ + if len(args) > 3: + raise oscerr.WrongArgs('Too many arguments') + if conf.config['build-type'] and conf.config['build-type'] != "lxc": + print('Not implemented for VMs', file=sys.stderr) + sys.exit(1) + + user = 'abuild' + if opts.login_as_root: + user = 'root' + buildroot = opts.root + if buildroot is None: + repository, arch, descr = self.parse_repoarchdescr(args, opts.noinit or opts.offline, opts.alternative_project) + project = opts.alternative_project or store_read_project('.') + if opts.local_package: + package = os.path.splitext(descr)[0] + else: + package = store_read_package('.') + apihost = urlsplit(self.get_api_url())[1] + if buildroot is None: + buildroot = os.environ.get('OSC_BUILD_ROOT', conf.config['build-root']) \ + % {'repo': repository, 'arch': arch, 'project': project, 'package': package, 'apihost': apihost} + if not os.path.isdir(buildroot): + raise oscerr.OscIOError(None, '\'%s\' is not a directory' % buildroot) + + suwrapper = os.environ.get('OSC_SU_WRAPPER', conf.config['su-wrapper']) + sucmd = suwrapper.split()[0] + suargs = ' '.join(suwrapper.split()[1:]) + if suwrapper.startswith('su '): + cmd = [sucmd, '%s chroot "%s" su - %s' % (suargs, buildroot, user)] + else: + cmd = [sucmd, 'chroot', buildroot, 'su', '-', user] + if suargs: + cmd[1:1] = suargs.split() + print('running: %s' % ' '.join(cmd)) + os.execvp(sucmd, cmd) + + + @cmdln.option('', '--csv', action='store_true', + help='generate output in CSV (separated by |)') + @cmdln.option('-l', '--limit', metavar='limit', + help='for setting the number of results') + @cmdln.alias('buildhist') + def do_buildhistory(self, subcmd, opts, *args): + """${cmd_name}: Shows the build history of a package + + The arguments REPOSITORY and ARCH can be taken from the first two columns + of the 'osc repos' output. + + usage: + osc buildhist REPOSITORY ARCHITECTURE + osc buildhist PROJECT PACKAGE REPOSITORY ARCHITECTURE + ${cmd_option_list} + """ + + args = slash_split(args) + + if len(args) < 2 and is_package_dir('.'): + self.print_repos() + + apiurl = self.get_api_url() + + if len(args) == 4: + project = args[0] + package = args[1] + repository = args[2] + arch = args[3] + elif len(args) == 2: + wd = os.curdir + package = store_read_package(wd) + project = store_read_project(wd) + repository = args[0] + arch = args[1] + else: + raise oscerr.WrongArgs('Wrong number of arguments') + + format = 'text' + if opts.csv: + format = 'csv' + + print('\n'.join(get_buildhistory(apiurl, project, package, repository, arch, format, opts.limit))) + + @cmdln.option('', '--csv', action='store_true', + help='generate output in CSV (separated by |)') + @cmdln.option('-l', '--limit', metavar='limit', + help='for setting the number of results') + @cmdln.alias('jobhist') + def do_jobhistory(self, subcmd, opts, *args): + """${cmd_name}: Shows the job history of a project + + The arguments REPOSITORY and ARCH can be taken from the first two columns + of the 'osc repos' output. + + usage: + osc jobhist REPOSITORY ARCHITECTURE (in project dir) + osc jobhist PROJECT [PACKAGE] REPOSITORY ARCHITECTURE + ${cmd_option_list} + """ + wd = os.curdir + args = slash_split(args) + + if len(args) < 2 and (is_project_dir('.') or is_package_dir('.')): + self.print_repos() + + apiurl = self.get_api_url() + + if len(args) == 4: + project = args[0] + package = args[1] + repository = args[2] + arch = args[3] + elif len(args) == 3: + project = args[0] + package = None # skipped = prj + repository = args[1] + arch = args[2] + elif len(args) == 2: + package = None + try: + package = store_read_package(wd) + except: + pass + project = store_read_project(wd) + repository = args[0] + arch = args[1] + else: + raise oscerr.WrongArgs('Wrong number of arguments') + + format = 'text' + if opts.csv: + format = 'csv' + + print_jobhistory(apiurl, project, package, repository, arch, format, opts.limit) + + @cmdln.hide(1) + def do_rlog(self, subcmd, opts, *args): + print("Command rlog is obsolete. Please use 'osc log'") + sys.exit(1) + + + @cmdln.option('-r', '--revision', metavar='rev', + help='show log of the specified revision') + @cmdln.option('', '--csv', action='store_true', + help='generate output in CSV (separated by |)') + @cmdln.option('', '--xml', action='store_true', + help='generate output in XML') + @cmdln.option('-D', '--deleted', action='store_true', + help='work on deleted package') + @cmdln.option('-M', '--meta', action='store_true', + help='checkout out meta data instead of sources' ) + def do_log(self, subcmd, opts, *args): + """${cmd_name}: Shows the commit log of a package + + Usage: + osc log (inside working copy) + osc log remote_project [remote_package] + + ${cmd_option_list} + """ + + args = slash_split(args) + apiurl = self.get_api_url() + + if len(args) == 0: + wd = os.curdir + if is_project_dir(wd) or is_package_dir(wd): + project = store_read_project(wd) + if is_project_dir(wd): + package = "_project" + else: + package = store_read_package(wd) + else: + raise oscerr.NoWorkingCopy("Error: \"%s\" is not an osc working copy." % os.path.abspath(wd)) + elif len(args) < 1: + raise oscerr.WrongArgs('Too few arguments (required none or two)') + elif len(args) > 2: + raise oscerr.WrongArgs('Too many arguments (required none or two)') + elif len(args) == 1: + project = args[0] + package = "_project" + else: + project = args[0] + package = args[1] + + rev, rev_upper = parseRevisionOption(opts.revision) + if rev and not checkRevision(project, package, rev, apiurl, opts.meta): + print('Revision \'%s\' does not exist' % rev, file=sys.stderr) + sys.exit(1) + + format = 'text' + if opts.csv: + format = 'csv' + if opts.xml: + format = 'xml' + + log = '\n'.join(get_commitlog(apiurl, project, package, rev, format, opts.meta, opts.deleted, rev_upper)) + run_pager(log) + + def do_service(self, subcmd, opts, *args): + """${cmd_name}: Handle source services + + Source services can be used to modify sources like downloading files, + verify files, generating files or modify existing files. + + usage: + osc service COMMAND (inside working copy) + osc service run [SOURCE_SERVICE] + osc service localrun + osc service disabledrun + osc service remoterun [PROJECT PACKAGE] + osc service merge [PROJECT PACKAGE] + osc service wait [PROJECT PACKAGE] + + COMMAND can be: + run r run defined services locally, it takes an optional parameter to run only a + specified source service. In case parameters exist for this one in _service file + they are used. + localrun lr run services locally and store files as local created + disabledrun dr run disabled or server side only services locally and store files as local created + remoterun rr trigger a re-run on the server side + merge commits all server side generated files and drops the _service definition + wait waits until the service finishes and returns with an error if it failed + + ${cmd_option_list} + """ + + args = slash_split(args) + project = package = singleservice = mode = None + apiurl = self.get_api_url() + + if len(args) < 1: + raise oscerr.WrongArgs('No command given.') + elif len(args) < 3: + if is_package_dir(os.curdir): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + else: + raise oscerr.WrongArgs('Too few arguments.') + if len(args) == 2: + singleservice = args[1] + elif len(args) == 3 and args[0] in ('remoterun', 'rr', 'merge', 'wait'): + project = args[1] + package = args[2] + else: + raise oscerr.WrongArgs('Too many arguments.') + + command = args[0] + + if not (command in ( 'run', 'localrun', 'disabledrun', 'remoterun', 'lr', 'dr', 'r', 'rr', 'merge', 'wait' )): + raise oscerr.WrongArgs('Wrong command given.') + + if command == "remoterun" or command == "rr": + print(runservice(apiurl, project, package)) + return + + if command == "wait": + print(waitservice(apiurl, project, package)) + return + + if command == "merge": + print(mergeservice(apiurl, project, package)) + return + + if command in ('run', 'localrun', 'disabledrun', 'lr', 'dr', 'r'): + if not is_package_dir(os.curdir): + raise oscerr.WrongArgs('Local directory is no package') + p = Package(".") + if command == "localrun" or command == "lr": + mode = "local" + elif command == "disabledrun" or command == "dr": + mode = "disabled" + + return p.run_source_services(mode, singleservice) + + @cmdln.option('-a', '--arch', metavar='ARCH', + help='trigger rebuilds for a specific architecture') + @cmdln.option('-r', '--repo', metavar='REPO', + help='trigger rebuilds for a specific repository') + @cmdln.option('-f', '--failed', action='store_true', + help='rebuild all failed packages') + @cmdln.option('--all', action='store_true', + help='Rebuild all packages of entire project') + @cmdln.alias('rebuildpac') + def do_rebuild(self, subcmd, opts, *args): + """${cmd_name}: Trigger package rebuilds + + Note that it is normally NOT needed to kick off rebuilds like this, because + they principally happen in a fully automatic way, triggered by source + check-ins. In particular, the order in which packages are built is handled + by the build service. + + The arguments REPOSITORY and ARCH can be taken from the first two columns + of the 'osc repos' output. + + usage: + osc rebuild [PROJECT [PACKAGE [REPOSITORY [ARCH]]]] + ${cmd_option_list} + """ + + args = slash_split(args) + + package = repo = arch = code = None + apiurl = self.get_api_url() + + if opts.repo: + repo = opts.repo + + if opts.arch: + arch = opts.arch + + if len(args) < 1: + if is_package_dir(os.curdir): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + apiurl = store_read_apiurl(os.curdir) + elif is_project_dir(os.curdir): + project = store_read_project(os.curdir) + apiurl = store_read_apiurl(os.curdir) + else: + raise oscerr.WrongArgs('Too few arguments.') + else: + project = args[0] + if len(args) > 1: + package = args[1] + + if len(args) > 2: + repo = args[2] + if len(args) > 3: + arch = args[3] + + if opts.failed: + code = 'failed' + + if not (opts.all or package or repo or arch or code): + raise oscerr.WrongOptions('No option has been provided. If you want to rebuild all packages of the entire project, use --all option.') + + print(rebuild(apiurl, project, package, repo, arch, code)) + + + def do_info(self, subcmd, opts, *args): + """${cmd_name}: Print information about a working copy + + Print information about each ARG (default: '.') + ARG is a working-copy path. + + ${cmd_usage} + ${cmd_option_list} + """ + + args = parseargs(args) + pacs = findpacs(args) + + for p in pacs: + print(p.info()) + + + @cmdln.option('-a', '--arch', metavar='ARCH', + help='Restart builds for a specific architecture') + @cmdln.option('-r', '--repo', metavar='REPO', + help='Restart builds for a specific repository') + @cmdln.option('--all', action='store_true', + help='Restart all running builds of entire project') + @cmdln.alias('abortbuild') + def do_restartbuild(self, subcmd, opts, *args): + """${cmd_name}: Restart the build of a certain project or package + + usage: + osc restartbuild [PROJECT [PACKAGE [REPOSITORY [ARCH]]]] + ${cmd_option_list} + """ + args = slash_split(args) + + package = repo = arch = code = None + apiurl = self.get_api_url() + + if opts.repo: + repo = opts.repo + + if opts.arch: + arch = opts.arch + + if len(args) < 1: + if is_package_dir(os.curdir): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + apiurl = store_read_apiurl(os.curdir) + elif is_project_dir(os.curdir): + project = store_read_project(os.curdir) + apiurl = store_read_apiurl(os.curdir) + else: + raise oscerr.WrongArgs('Too few arguments.') + else: + project = args[0] + if len(args) > 1: + package = args[1] + + if len(args) > 2: + repo = args[2] + if len(args) > 3: + arch = args[3] + + if not (opts.all or package or repo or arch): + raise oscerr.WrongOptions('No option has been provided. If you want to restart all packages of the entire project, use --all option.') + + print(cmdbuild(apiurl, subcmd, project, package, opts.arch, opts.repo)) + + + @cmdln.option('-a', '--arch', metavar='ARCH', + help='Delete all binary packages for a specific architecture') + @cmdln.option('-r', '--repo', metavar='REPO', + help='Delete all binary packages for a specific repository') + @cmdln.option('--build-disabled', action='store_true', + help='Delete all binaries of packages for which the build is disabled') + @cmdln.option('--build-failed', action='store_true', + help='Delete all binaries of packages for which the build failed') + @cmdln.option('--broken', action='store_true', + help='Delete all binaries of packages for which the package source is bad') + @cmdln.option('--unresolvable', action='store_true', + help='Delete all binaries of packages which have dependency errors') + @cmdln.option('--all', action='store_true', + help='Delete all binaries regardless of the package status (previously default)') + def do_wipebinaries(self, subcmd, opts, *args): + """${cmd_name}: Delete all binary packages of a certain project/package + + With the optional argument <package> you can specify a certain package + otherwise all binary packages in the project will be deleted. + + usage: + osc wipebinaries OPTS # works in checked out project dir + osc wipebinaries OPTS PROJECT [PACKAGE] + ${cmd_option_list} + """ + + args = slash_split(args) + + package = project = None + apiurl = self.get_api_url() + + # try to get project and package from checked out dirs + if len(args) < 1: + if is_project_dir(os.getcwd()): + project = store_read_project(os.curdir) + if is_package_dir(os.getcwd()): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + if project is None: + raise oscerr.WrongArgs('Missing <project> argument.') + if len(args) > 2: + raise oscerr.WrongArgs('Wrong number of arguments.') + + # respect given project and package + if len(args) >= 1: + project = args[0] + + if len(args) == 2: + package = args[1] + + codes = [] + if opts.build_disabled: + codes.append('disabled') + if opts.build_failed: + codes.append('failed') + if opts.broken: + codes.append('broken') + if opts.unresolvable: + codes.append('unresolvable') + if opts.all or opts.repo or opts.arch: + codes.append(None) + + if len(codes) == 0: + raise oscerr.WrongOptions('No option has been provided. If you want to delete all binaries, use --all option.') + + # make a new request for each code= parameter + for code in codes: + print(wipebinaries(apiurl, project, package, opts.arch, opts.repo, code)) + + + @cmdln.option('-q', '--quiet', action='store_true', + help='do not show downloading progress') + @cmdln.option('-d', '--destdir', default='./binaries', metavar='DIR', + help='destination directory') + @cmdln.option('--sources', action="store_true", + help='also fetch source packages') + @cmdln.option('--debug', action="store_true", + help='also fetch debug packages') + def do_getbinaries(self, subcmd, opts, *args): + """${cmd_name}: Download binaries to a local directory + + This command downloads packages directly from the api server. + Thus, it directly accesses the packages that are used for building + others even when they are not "published" yet. + + usage: + osc getbinaries REPOSITORY # works in checked out project/package (check out all archs in subdirs) + osc getbinaries REPOSITORY ARCHITECTURE # works in checked out project/package + osc getbinaries PROJECT PACKAGE REPOSITORY ARCHITECTURE + osc getbinaries PROJECT PACKAGE REPOSITORY ARCHITECTURE FILE + ${cmd_option_list} + """ + + args = slash_split(args) + + apiurl = self.get_api_url() + project = None + package = None + binary = None + + if len(args) < 1 and is_package_dir('.'): + self.print_repos() + + architecture = None + if len(args) == 4 or len(args) == 5: + project = args[0] + package = args[1] + repository = args[2] + architecture = args[3] + if len(args) == 5: + binary = args[4] + elif len(args) >= 1 and len(args) <= 2: + if is_package_dir(os.getcwd()): + project = store_read_project(os.curdir) + package = store_read_package(os.curdir) + elif is_project_dir(os.getcwd()): + project = store_read_project(os.curdir) + else: + raise oscerr.WrongArgs('Missing arguments: either specify <project> and ' \ + '<package> or move to a project or package working copy') + repository = args[0] + if len(args) == 2: + architecture = args[1] + else: + raise oscerr.WrongArgs('Need either 1, 2 or 4 arguments') + + repos = list(get_repos_of_project(apiurl, project)) + if not [i for i in repos if repository == i.name]: + self.print_repos(exc_msg='Invalid repository \'%s\'' % repository) + + arches = [architecture] + if architecture is None: + arches = [i.arch for i in repos if repository == i.name] + + if package is None: + package = meta_get_packagelist(apiurl, project) + else: + package = [package] + + # Set binary target directory and create if not existing + target_dir = os.path.normpath(opts.destdir) + if not os.path.isdir(target_dir): + print('Creating %s' % target_dir) + os.makedirs(target_dir, 0o755) + + for arch in arches: + for pac in package: + binaries = get_binarylist(apiurl, project, repository, arch, + package=pac, verbose=True) + if not binaries: + print('no binaries found: Either the package %s ' \ + 'does not exist or no binaries have been built.' % pac, file=sys.stderr) + continue + + for i in binaries: + if binary != None and binary != i.name: + continue + # skip source rpms + if not opts.sources and (i.name.endswith('src.rpm') or i.name.endswith('sdeb')): + continue + if not opts.debug: + if i.name.find('-debuginfo-') >= 0: + continue + if i.name.find('-debugsource-') >= 0: + continue + fname = '%s/%s' % (target_dir, i.name) + if os.path.exists(fname): + st = os.stat(fname) + if st.st_mtime == i.mtime and st.st_size == i.size: + continue + get_binary_file(apiurl, + project, + repository, arch, + i.name, + package = pac, + target_filename = fname, + target_mtime = i.mtime, + progress_meter = not opts.quiet) + + + @cmdln.option('-b', '--bugowner', action='store_true', + help='restrict listing to items where the user is bugowner') + @cmdln.option('-m', '--maintainer', action='store_true', + help='restrict listing to items where the user is maintainer') + @cmdln.option('-a', '--all', action='store_true', + help='all involvements') + @cmdln.option('-U', '--user', metavar='USER', + help='search for USER instead of yourself') + @cmdln.option('--exclude-project', action='append', + help='exclude requests for specified project') + @cmdln.option('-v', '--verbose', action='store_true', + help='verbose listing') + @cmdln.option('--maintained', action='store_true', + help='limit search results to packages with maintained attribute set.') + def do_my(self, subcmd, opts, *args): + """${cmd_name}: show waiting work, packages, projects or requests involving yourself + + Examples: + # list all open tasks for me + osc ${cmd_name} [work] + # list packages where I am bugowner + osc ${cmd_name} pkg -b + # list projects where I am maintainer + osc ${cmd_name} prj -m + # list request for all my projects and packages + osc ${cmd_name} rq + # list requests, excluding project 'foo' and 'bar' + osc ${cmd_name} rq --exclude-project foo,bar + # list requests I made + osc ${cmd_name} sr + + ${cmd_usage} + where TYPE is one of requests, submitrequests, + projects or packages (rq, sr, prj or pkg) + + ${cmd_option_list} + """ + + # TODO: please clarify the difference between sr and rq. + # My first implementeation was to make no difference between requests FROM one + # of my projects and TO one of my projects. The current implementation appears to make this difference. + # The usage above indicates, that sr would be a subset of rq, which is no the case with my tests. + # jw. + + args_rq = ('requests', 'request', 'req', 'rq', 'work') + args_sr = ('submitrequests', 'submitrequest', 'submitreq', 'submit', 'sr') + args_prj = ('projects', 'project', 'projs', 'proj', 'prj') + args_pkg = ('packages', 'package', 'pack', 'pkgs', 'pkg') + args_patchinfos = ('patchinfos', 'work') + + if opts.bugowner and opts.maintainer: + raise oscerr.WrongOptions('Sorry, \'--bugowner\' and \'maintainer\' are mutually exclusive') + elif opts.all and (opts.bugowner or opts.maintainer): + raise oscerr.WrongOptions('Sorry, \'--all\' and \'--bugowner\' or \'--maintainer\' are mutually exclusive') + + apiurl = self.get_api_url() + + exclude_projects = [] + for i in opts.exclude_project or []: + prj = i.split(',') + if len(prj) == 1: + exclude_projects.append(i) + else: + exclude_projects.extend(prj) + if not opts.user: + user = conf.get_apiurl_usr(apiurl) + else: + user = opts.user + + what = {'project': '', 'package': ''} + type = "work" + if len(args) > 0: + type = args[0] + + list_patchinfos = list_requests = False + if type in args_patchinfos: + list_patchinfos = True + if type in args_rq: + list_requests = True + elif type in args_prj: + what = {'project': ''} + elif type in args_sr: + requests = get_request_collection(apiurl, 'creator', req_who=user) + for r in sorted(requests): + print(r.list_view(), '\n') + return + elif not type in args_pkg: + raise oscerr.WrongArgs("invalid type %s" % type) + + role_filter = '' + if opts.maintainer: + role_filter = 'maintainer' + elif opts.bugowner: + role_filter = 'bugowner' + elif list_requests: + role_filter = 'maintainer' + if opts.all: + role_filter = '' + + if list_patchinfos: + u = makeurl(apiurl, ['/search/package'], { + 'match' : "([kind='patchinfo' and issue/[@state='OPEN' and owner/@login='%s']])" % user + }) + f = http_GET(u) + root = ET.parse(f).getroot() + if root.findall('package'): + print("Patchinfos with open bugs assigned to you:\n") + for node in root.findall('package'): + project = node.get('project') + package = node.get('name') + print(project, "/", package, '\n') + p = makeurl(apiurl, ['source', project, package], { 'view': 'issues' }) + fp = http_GET(p) + issues = ET.parse(fp).findall('issue') + for issue in issues: + if issue.find('state') == None or issue.find('state').text != "OPEN": + continue + if issue.find('owner') == None or issue.find('owner').find('login').text != user: + continue + print(" #", issue.find('label').text, ': ', end=' ') + desc = issue.find('summary') + if desc != None: + print(desc.text) + else: + print("\n") + print("") + + if list_requests: + # try api side search as supported since OBS 2.2 + try: + requests = [] + # open reviews + u = makeurl(apiurl, ['request'], { + 'view': 'collection', + 'states': 'review', + 'reviewstates': 'new', + 'roles': 'reviewer', + 'user': user, + }) + f = http_GET(u) + root = ET.parse(f).getroot() + if root.findall('request'): + print("Requests which request a review by you:\n") + for node in root.findall('request'): + r = Request() + r.read(node) + print(r.list_view(), '\n') + print("") + # open requests + u = makeurl(apiurl, ['request'], { + 'view': 'collection', + 'states': 'new', + 'roles': 'maintainer', + 'user': user, + }) + f = http_GET(u) + root = ET.parse(f).getroot() + if root.findall('request'): + print("Requests for your packages:\n") + for node in root.findall('request'): + r = Request() + r.read(node) + print(r.list_view(), '\n') + print("") + # declined requests submitted by me + u = makeurl(apiurl, ['request'], { + 'view': 'collection', + 'states': 'declined', + 'roles': 'creator', + 'user': user, + }) + f = http_GET(u) + root = ET.parse(f).getroot() + if root.findall('request'): + print("Declined requests created by you (revoke, reopen or supersede):\n") + for node in root.findall('request'): + r = Request() + r.read(node) + print(r.list_view(), '\n') + print("") + return + except HTTPError as e: + if e.code == 400: + # skip it ... try again with old style below + pass + + res = get_user_projpkgs(apiurl, user, role_filter, exclude_projects, + 'project' in what, 'package' in what, + opts.maintained, opts.verbose) + + # map of project =>[list of packages] + # if list of packages is empty user is maintainer of the whole project + request_todo = {} + + roles = {} + if len(what.keys()) == 2: + for i in res.get('project_id', res.get('project', {})).findall('project'): + request_todo[i.get('name')] = [] + roles[i.get('name')] = [p.get('role') for p in i.findall('person') if p.get('userid') == user] + for i in res.get('package_id', res.get('package', {})).findall('package'): + prj = i.get('project') + roles['/'.join([prj, i.get('name')])] = [p.get('role') for p in i.findall('person') if p.get('userid') == user] + if not prj in request_todo or request_todo[prj] != []: + request_todo.setdefault(prj, []).append(i.get('name')) + else: + for i in res.get('project_id', res.get('project', {})).findall('project'): + roles[i.get('name')] = [p.get('role') for p in i.findall('person') if p.get('userid') == user] + + if list_requests: + # old style, only for OBS 2.1 and before. Should not be used, since it is slow and incomplete + requests = get_user_projpkgs_request_list(apiurl, user, projpkgs=request_todo) + for r in sorted(requests): + print(r.list_view(), '\n') + if not len(requests): + print(" -> try also 'osc my sr' to see more.") + else: + for i in sorted(roles.keys()): + out = '%s' % i + prjpac = i.split('/') + if type in args_pkg and len(prjpac) == 1 and not opts.verbose: + continue + if opts.verbose: + out = '%s (%s)' % (i, ', '.join(sorted(roles[i]))) + if len(prjpac) == 2: + out = ' %s (%s)' % (prjpac[1], ', '.join(sorted(roles[i]))) + print(out) + + + @cmdln.option('--repos-baseurl', action='store_true', + help='show base URLs of download repositories') + @cmdln.option('-e', '--exact', action='store_true', + help='show only exact matches, this is default now') + @cmdln.option('-s', '--substring', action='store_true', + help='Show also results where the search term is a sub string, slower search') + @cmdln.option('--package', action='store_true', + help='search for a package') + @cmdln.option('--project', action='store_true', + help='search for a project') + @cmdln.option('--title', action='store_true', + help='search for matches in the \'title\' element') + @cmdln.option('--description', action='store_true', + help='search for matches in the \'description\' element') + @cmdln.option('-a', '--limit-to-attribute', metavar='ATTRIBUTE', + help='match only when given attribute exists in meta data') + @cmdln.option('-v', '--verbose', action='store_true', + help='show more information') + @cmdln.option('-V', '--version', action='store_true', + help='show package version, revision, and srcmd5. CAUTION: This is slow and unreliable') + @cmdln.option('-i', '--involved', action='store_true', + help='show projects/packages where given person (or myself) is involved as bugowner or maintainer [[{group|person}/]<name>] default: person') + @cmdln.option('-b', '--bugowner', action='store_true', + help='as -i, but only bugowner') + @cmdln.option('-m', '--maintainer', action='store_true', + help='as -i, but only maintainer') + @cmdln.option('--maintained', action='store_true', + help='OBSOLETE: please use maintained command instead.') + @cmdln.option('-M', '--mine', action='store_true', + help='shorthand for --bugowner --package') + @cmdln.option('--csv', action='store_true', + help='generate output in CSV (separated by |)') + @cmdln.option('--binary', action='store_true', + help='search binary packages') + @cmdln.option('-B', '--baseproject', metavar='PROJECT', + help='search packages built for PROJECT (implies --binary)') + @cmdln.option('--binaryversion', metavar='VERSION', + help='search for binary with specified version (implies --binary)') + @cmdln.alias('se') + @cmdln.alias('bse') + def do_search(self, subcmd, opts, *args): + """${cmd_name}: Search for a project and/or package. + + If no option is specified osc will search for projects and + packages which contains the \'search term\' in their name, + title or description. + + usage: + osc search \'search term\' <options> + osc bse ... ('osc search --binary') + osc se 'perl(Foo::Bar)' ('osc --package perl-Foo-Bar') + ${cmd_option_list} + """ + def build_xpath(attr, what, substr = False): + if substr: + return 'contains(%s, \'%s\')' % (attr, what) + else: + return '%s = \'%s\'' % (attr, what) + + search_term = '' + if len(args) > 1: + raise oscerr.WrongArgs('Too many arguments') + elif len(args) == 0: + if opts.involved or opts.bugowner or opts.maintainer or opts.mine: + search_term = conf.get_apiurl_usr(conf.config['apiurl']) + else: + raise oscerr.WrongArgs('Too few arguments') + else: + search_term = args[0] + + if opts.maintained: + raise oscerr.WrongOptions('The --maintained option is not anymore supported. Please use the maintained command instead.') + + # XXX: is it a good idea to make this the default? + # support perl symbols: + if re.match('^perl\(\w+(::\w+)*\)$', search_term): + search_term = re.sub('\)', '', re.sub('(::|\()', '-', search_term)) + opts.package = True + + if opts.mine: + opts.bugowner = True + opts.package = True + + if (opts.title or opts.description) and (opts.involved or opts.bugowner or opts.maintainer): + raise oscerr.WrongOptions('Sorry, the options \'--title\' and/or \'--description\' ' \ + 'are mutually exclusive with \'-i\'/\'-b\'/\'-m\'/\'-M\'') + if opts.substring and opts.exact: + raise oscerr.WrongOptions('Sorry, the options \'--substring\' and \'--exact\' are mutually exclusive') + + if not opts.substring: + opts.exact = True + if subcmd == 'bse' or opts.baseproject or opts.binaryversion: + opts.binary = True + + if opts.binary and (opts.title or opts.description or opts.involved or opts.bugowner or opts.maintainer + or opts.project or opts.package): + raise oscerr.WrongOptions('Sorry, \'--binary\' and \'--title\' or \'--description\' or \'--involved ' \ + 'or \'--bugowner\' or \'--maintainer\' or \'--limit-to-attribute <attr>\ ' \ + 'or \'--project\' or \'--package\' are mutually exclusive') + + apiurl = self.get_api_url() + + xpath = '' + if opts.title: + xpath = xpath_join(xpath, build_xpath('title', search_term, opts.substring), inner=True) + if opts.description: + xpath = xpath_join(xpath, build_xpath('description', search_term, opts.substring), inner=True) + if opts.project or opts.package or opts.binary: + xpath = xpath_join(xpath, build_xpath('@name', search_term, opts.substring), inner=True) + # role filter + role_filter = '' + if opts.bugowner or opts.maintainer or opts.involved: + tmp = search_term.split(':') + if len(tmp) > 1: + search_type, search_term = [tmp[0], tmp[1]] + else: + search_type = 'person' + search_dict = { 'person' : 'userid', + 'group' : 'groupid' } + try: + search_id = search_dict[ search_type ] + except KeyError: + search_type, search_id = [ 'person', 'userid' ] + xpath = xpath_join(xpath, '%s/@%s = \'%s\'' % (search_type, search_id, search_term), inner=True) + role_filter = '%s (%s)' % (search_term, search_type) + role_filter_xpath = xpath + if opts.bugowner and not opts.maintainer: + xpath = xpath_join(xpath, '%s/@role=\'bugowner\'' % search_type, op='and') + role_filter = 'bugowner' + elif not opts.bugowner and opts.maintainer: + xpath = xpath_join(xpath, '%s/@role=\'maintainer\'' % search_type, op='and') + role_filter = 'maintainer' + if opts.limit_to_attribute: + xpath = xpath_join(xpath, 'attribute/@name=\'%s\'' % opts.limit_to_attribute, op='and') + if opts.baseproject: + xpath = xpath_join(xpath, 'path/@project=\'%s\'' % opts.baseproject, op='and') + if opts.binaryversion: + m = re.match(r'(.+)-(.*?)$', opts.binaryversion) + if m: + if m.group(2) != '': + xpath = xpath_join(xpath, '@versrel=\'%s\'' % opts.binaryversion, op='and') + else: + xpath = xpath_join(xpath, '@version=\'%s\'' % m.group(1), op='and') + else: + xpath = xpath_join(xpath, '@version=\'%s\'' % opts.binaryversion, op='and') + + if not xpath: + xpath = xpath_join(xpath, build_xpath('@name', search_term, opts.substring), inner=True) + xpath = xpath_join(xpath, build_xpath('title', search_term, opts.substring), inner=True) + xpath = xpath_join(xpath, build_xpath('description', search_term, opts.substring), inner=True) + what = {'project': xpath, 'package': xpath} + if opts.project and not opts.package: + what = {'project': xpath} + elif not opts.project and opts.package: + what = {'package': xpath} + elif opts.binary: + what = {'published/binary/id': xpath} + try: + res = search(apiurl, **what) + except HTTPError as e: + if e.code != 400 or not role_filter: + raise e + # backward compatibility: local role filtering + if opts.limit_to_attribute: + role_filter_xpath = xpath_join(role_filter_xpath, 'attribute/@name=\'%s\'' % opts.limit_to_attribute, op='and') + what = dict([[kind, role_filter_xpath] for kind in what.keys()]) + res = search(apiurl, **what) + filter_role(res, search_term, role_filter) + if role_filter: + role_filter = '%s (%s)' % (search_term, role_filter) + kind_map = {'published/binary/id': 'binary'} + for kind, root in res.items(): + results = [] + for node in root.findall(kind_map.get(kind, kind)): + result = [] + project = node.get('project') + package = None + if project is None: + project = node.get('name') + else: + if kind == 'published/binary/id': + package = node.get('package') + else: + package = node.get('name') + + result.append(project) + if not package is None: + result.append(package) + + if opts.version and package != None: + sr = get_source_rev(apiurl, project, package) + v = sr.get('version') + r = sr.get('rev') + s = sr.get('srcmd5') + if not v or v == 'unknown': + v = '-' + if not r: + r = '-' + if not s: + s = '-' + result.append(v) + result.append(r) + result.append(s) + + if opts.verbose: + title = node.findtext('title').strip() + if len(title) > 60: + title = title[:61] + '...' + result.append(title) + + if opts.repos_baseurl: + # FIXME: no hardcoded URL of instance + result.append('http://download.opensuse.org/repositories/%s/' % project.replace(':', ':/')) + if kind == 'published/binary/id': + result.append(node.get('filepath')) + results.append(result) + + if not len(results): + print('No matches found for \'%s\' in %ss' % (role_filter or search_term, kind)) + continue + # construct a sorted, flat list + # Sort by first column, follwed by second column if we have two columns, else sort by first. + results.sort(lambda x, y: ( cmp(x[0], y[0]) or + (len(x)>1 and len(y)>1 and cmp(x[1], y[1])) )) + new = [] + for i in results: + new.extend(i) + results = new + headline = [] + if kind == 'package' or kind == 'published/binary/id': + headline = [ '# Project', '# Package' ] + else: + headline = [ '# Project' ] + if opts.version and kind == 'package': + headline.append('# Ver') + headline.append('Rev') + headline.append('Srcmd5') + if opts.verbose: + headline.append('# Title') + if opts.repos_baseurl: + headline.append('# URL') + if opts.binary: + headline.append('# filepath') + if not opts.csv: + if len(what.keys()) > 1: + print('#' * 68) + print('matches for \'%s\' in %ss:\n' % (role_filter or search_term, kind)) + for row in build_table(len(headline), results, headline, 2, csv = opts.csv): + print(row) + + + @cmdln.option('-p', '--project', metavar='project', + help='specify the path to a project') + @cmdln.option('-n', '--name', metavar='name', + help='specify a package name') + @cmdln.option('-t', '--title', metavar='title', + help='set a title') + @cmdln.option('-d', '--description', metavar='description', + help='set the description of the package') + @cmdln.option('', '--delete-old-files', action='store_true', + help='delete existing files from the server') + @cmdln.option('-c', '--commit', action='store_true', + help='commit the new files') + def do_importsrcpkg(self, subcmd, opts, srpm): + """${cmd_name}: Import a new package from a src.rpm + + A new package dir will be created inside the project dir + (if no project is specified and the current working dir is a + project dir the package will be created in this project). If + the package does not exist on the server it will be created + too otherwise the meta data of the existing package will be + updated (<title /> and <description />). + The src.rpm will be extracted into the package dir. The files + won't be committed unless you explicitly pass the --commit switch. + + SRPM is the path of the src.rpm in the local filesystem, + or an URL. + + ${cmd_usage} + ${cmd_option_list} + """ + import glob + from .util import rpmquery + + if opts.delete_old_files and conf.config['do_package_tracking']: + # IMHO the --delete-old-files option doesn't really fit into our + # package tracking strategy + print('--delete-old-files is not supported anymore', file=sys.stderr) + print('when do_package_tracking is enabled', file=sys.stderr) + sys.exit(1) + + if '://' in srpm: + print('trying to fetch', srpm) + import urlgrabber + urlgrabber.urlgrab(srpm) + srpm = os.path.basename(srpm) + + srpm = os.path.abspath(srpm) + if not os.path.isfile(srpm): + print('file \'%s\' does not exist' % srpm, file=sys.stderr) + sys.exit(1) + + if opts.project: + project_dir = opts.project + else: + project_dir = os.curdir + + if conf.config['do_package_tracking']: + project = Project(project_dir) + else: + project = store_read_project(project_dir) + + rpmq = rpmquery.RpmQuery.query(srpm) + title, pac, descr, url = rpmq.summary(), rpmq.name(), rpmq.description(), rpmq.url() + if url is None: + url = '' + + if opts.title: + title = opts.title + if opts.name: + pac = opts.name + if opts.description: + descr = opts.description + + # title and description can be empty + if not pac: + print('please specify a package name with the \'--name\' option. ' \ + 'The automatic detection failed', file=sys.stderr) + sys.exit(1) + + if conf.config['do_package_tracking']: + createPackageDir(os.path.join(project.dir, pac), project) + else: + if not os.path.exists(os.path.join(project_dir, pac)): + apiurl = store_read_apiurl(project_dir) + user = conf.get_apiurl_usr(apiurl) + data = meta_exists(metatype='pkg', + path_args=(quote_plus(project), quote_plus(pac)), + template_args=({ + 'name': pac, + 'user': user}), apiurl=apiurl) + if data: + data = ET.fromstring(''.join(data)) + data.find('title').text = ''.join(title) + data.find('description').text = ''.join(descr) + data.find('url').text = url + data = ET.tostring(data, encoding=ET_ENCODING) + else: + print('error - cannot get meta data', file=sys.stderr) + sys.exit(1) + edit_meta(metatype='pkg', + path_args=(quote_plus(project), quote_plus(pac)), + data = data, apiurl=apiurl) + Package.init_package(apiurl, project, pac, os.path.join(project_dir, pac)) + else: + print('error - local package already exists', file=sys.stderr) + sys.exit(1) + + unpack_srcrpm(srpm, os.path.join(project_dir, pac)) + p = Package(os.path.join(project_dir, pac)) + if len(p.filenamelist) == 0 and opts.commit: + print('Adding files to working copy...') + addFiles(glob.glob('%s/*' % os.path.join(project_dir, pac))) + if conf.config['do_package_tracking']: + project.commit((pac, )) + else: + p.update_datastructs() + p.commit() + elif opts.commit and opts.delete_old_files: + for filename in p.filenamelist: + p.delete_remote_source_file(filename) + p.update_local_filesmeta() + print('Adding files to working copy...') + addFiles(glob.glob('*')) + p.update_datastructs() + p.commit() + else: + print('No files were committed to the server. Please ' \ + 'commit them manually.') + print('Package \'%s\' only imported locally' % pac) + sys.exit(1) + + print('Package \'%s\' imported successfully' % pac) + + + @cmdln.option('-X', '-m', '--method', default='GET', metavar='HTTP_METHOD', + help='specify HTTP method to use (GET|PUT|DELETE|POST)') + @cmdln.option('-e', '--edit', default=None, action='store_true', + help='GET, edit and PUT the location') + @cmdln.option('-d', '--data', default=None, metavar='STRING', + help='specify string data for e.g. POST') + @cmdln.option('-T', '-f', '--file', default=None, metavar='FILE', + help='specify filename to upload, uses PUT mode by default') + @cmdln.option('-a', '--add-header', default=None, metavar='NAME STRING', + nargs=2, action='append', dest='headers', + help='add the specified header to the request') + def do_api(self, subcmd, opts, url): + """${cmd_name}: Issue an arbitrary request to the API + + Useful for testing. + + URL can be specified either partially (only the path component), or fully + with URL scheme and hostname ('http://...'). + + Note the global -A and -H options (see osc help). + + Examples: + osc api /source/home:user + osc api -X PUT -T /etc/fstab source/home:user/test5/myfstab + osc api -e /configuration + + ${cmd_usage} + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + + if not opts.method in ['GET', 'PUT', 'POST', 'DELETE']: + sys.exit('unknown method %s' % opts.method) + + # default is PUT when uploading files + if opts.file and opts.method == 'GET': + opts.method = 'PUT' + + if not url.startswith('http'): + if not url.startswith('/'): + url = '/' + url + url = apiurl + url + + if opts.headers: + opts.headers = dict(opts.headers) + + r = http_request(opts.method, + url, + data=opts.data, + file=opts.file, + headers=opts.headers) + out = r.read() + + if opts.edit: + text = edit_text(out) + r = http_request("PUT", + url, + data=text, + headers=opts.headers) + out = r.read() + + sys.stdout.write(out) + + + @cmdln.option('-b', '--bugowner-only', action='store_true', + help='Show only the bugowner') + @cmdln.option('-B', '--bugowner', action='store_true', + help='Show only the bugowner if defined, or maintainer otherwise') + @cmdln.option('-e', '--email', action='store_true', + help='show email addresses instead of user names') + @cmdln.option('--nodevelproject', action='store_true', + help='do not follow a defined devel project ' \ + '(primary project where a package is developed)') + @cmdln.option('-v', '--verbose', action='store_true', + help='show more information') + @cmdln.option('-D', '--devel-project', metavar='devel_project', + help='define the project where this package is primarily developed') + @cmdln.option('-a', '--add', metavar='user', + help='add a new person for given role ("maintainer" by default)') + @cmdln.option('-A', '--all', action='store_true', + help='list all found entries not just the first one') + @cmdln.option('-s', '--set-bugowner', metavar='user', + help='Set the bugowner to specified person (or group via group: prefix)') + @cmdln.option('-S', '--set-bugowner-request', metavar='user', + help='Set the bugowner to specified person via a request (or group via group: prefix)') + @cmdln.option('-U', '--user', metavar='USER', + help='All official maintained instances for the specified USER') + @cmdln.option('-G', '--group', metavar='GROUP', + help='All official maintained instances for the specified GROUP') + @cmdln.option('-d', '--delete', metavar='user', + help='delete a maintainer/bugowner (can be specified via --role)') + @cmdln.option('-r', '--role', metavar='role', action='append', default=[], + help='Specify user role') + @cmdln.option('-m', '--message', + help='Define message as commit entry or request description') + @cmdln.alias('bugowner') + def do_maintainer(self, subcmd, opts, *args): + """${cmd_name}: Show maintainers according to server side configuration + + # Search for official maintained sources in OBS instance + osc maintainer BINARY <options> + osc maintainer -U <user> <options> + osc maintainer -G <group> <options> + + # Lookup via containers + osc maintainer <options> + osc maintainer PRJ <options> + osc maintainer PRJ PKG <options> + + The tool looks up the default responsible person for a certain project or package. + When using with an OBS 2.4 (or later) server it is doing the lookup for + a given binary according to the server side configuration of default owners. + + The tool is also looking into devel packages and supports to fallback to the project + in case a package has no defined maintainer. + + Please use "osc meta pkg" in case you need to know the definition in a specific container. + + PRJ and PKG default to current working-copy path. + + ${cmd_usage} + ${cmd_option_list} + """ + def get_maintainer_data(apiurl, maintainer, verbose=False): + tags = ('email',) + if maintainer.startswith('group:'): + group = maintainer.replace('group:', '') + if verbose: + return [maintainer] + get_group_data(apiurl, group, 'title', *tags) + return get_group_data(apiurl, group, 'email') + if verbose: + tags = ('login', 'realname', 'email') + return get_user_data(apiurl, maintainer, *tags) + def setBugownerHelper(apiurl, project, package, bugowner): + try: + setBugowner(apiurl, project, package, bugowner) + except HTTPError as e: + if e.code != 403: + raise + print("No write permission in", project, end=' ') + if package: + print("/", package, end=' ') + print() + repl = raw_input('\nCreating a request instead? (y/n) ') + if repl.lower() == 'y': + opts.set_bugowner_request = bugowner + opts.set_bugowner = None + + binary = None + prj = None + pac = None + metaroot = None + searchresult = None + roles = [ 'bugowner', 'maintainer' ] + if len(opts.role): + roles = opts.role + elif opts.bugowner_only or opts.bugowner or subcmd == 'bugowner': + roles = [ 'bugowner' ] + + args = slash_split(args) + if opts.user or opts.group: + if len(args) != 0: + raise oscerr.WrongArgs('Either search for user or for packages.') + elif len(args) == 0: + try: + pac = store_read_package('.') + except oscerr.NoWorkingCopy: + pass + prj = store_read_project('.') + elif len(args) == 1: + # it is unclear if one argument is a binary or a project, try binary first for new OBS 2.4 + binary = prj = args[0] + elif len(args) == 2: + prj = args[0] + pac = args[1] + else: + raise oscerr.WrongArgs('Wrong number of arguments.') + + apiurl = self.get_api_url() + + # Try the OBS 2.4 way first. + if binary or opts.user or opts.group: + limit = None + if opts.all: + limit = 0 + filterroles = roles + if filterroles == [ 'bugowner', 'maintainer' ]: + # use server side configured default + filterroles = None + if binary: + searchresult = owner(apiurl, binary, "binary", usefilter=filterroles, devel=None, limit=limit) + if searchresult != None and len(searchresult) == 0: + # We talk to an OBS 2.4 or later understanding the call + if opts.set_bugowner or opts.set_bugowner_request: + # filtered search did not succeed, but maybe we want to set an owner initially? + searchresult = owner(apiurl, binary, "binary", usefilter="", devel=None, limit=-1) + if searchresult: + print("WARNING: the binary exists, but has no matching maintainership roles defined.") + print("Do you want to set it in the container where the binary appeared first?") + result = searchresult.find('owner') + print("This is: " + result.get('project'), end=' ') + if result.get('package'): + print (" / " + result.get('package')) + repl = raw_input('\nUse this container? (y/n) ') + if repl.lower() != 'y': + searchresult = None + else: + print("Empty search result, you may want to search with other or all roles via -r ''") + return + elif opts.user: + searchresult = owner(apiurl, opts.user, "user", usefilter=filterroles, devel=None) + elif opts.group: + searchresult = owner(apiurl, opts.group, "group", usefilter=filterroles, devel=None) + else: + raise oscerr.WrongArgs('osc bug, no valid search criteria') + + if opts.add: + if searchresult: + for result in searchresult.findall('owner'): + for role in roles: + addPerson(apiurl, result.get('project'), result.get('package'), opts.add, role) + else: + for role in roles: + addPerson(apiurl, prj, pac, opts.add, role) + elif opts.set_bugowner or opts.set_bugowner_request: + bugowner = opts.set_bugowner or opts.set_bugowner_request + requestactionsxml = "" + if searchresult: + for result in searchresult.findall('owner'): + if opts.set_bugowner: + setBugownerHelper(apiurl, result.get('project'), result.get('package'), opts.set_bugowner) + if opts.set_bugowner_request: + args = [bugowner, result.get('project')] + if result.get('package'): + args = args + [result.get('package')] + requestactionsxml += self._set_bugowner(args, opts) + + else: + if opts.set_bugowner: + setBugownerHelper(apiurl, prj, pac, opts.set_bugowner) + + if opts.set_bugowner_request: + args = [bugowner, prj] + if pac: + args = args + [pac] + requestactionsxml += self._set_bugowner(args, opts) + + if requestactionsxml != "": + if opts.message: + message = opts.message + else: + message = edit_message() + + import cgi + xml = """<request> %s <state name="new"/> <description>%s</description> </request> """ % \ + (requestactionsxml, cgi.escape(message or "")) + u = makeurl(apiurl, ['request'], query='cmd=create') + f = http_POST(u, data=xml) + + root = ET.parse(f).getroot() + print("Request ID:", root.get('id')) + + elif opts.delete: + if searchresult: + for result in searchresult.findall('owner'): + for role in roles: + delPerson(apiurl, result.get('project'), result.get('package'), opts.delete, role) + else: + for role in roles: + delPerson(apiurl, prj, pac, opts.delete, role) + elif opts.devel_project: + # XXX: does it really belong to this command? + setDevelProject(apiurl, prj, pac, opts.devel_project) + else: + if pac: + m = show_package_meta(apiurl, prj, pac) + metaroot = ET.fromstring(''.join(m)) + if not opts.nodevelproject: + while metaroot.findall('devel'): + d = metaroot.find('devel') + prj = d.get('project', prj) + pac = d.get('package', pac) + if opts.verbose: + print("Following to the development space: %s/%s" % (prj, pac)) + m = show_package_meta(apiurl, prj, pac) + metaroot = ET.fromstring(''.join(m)) + if not metaroot.findall('person') and not metaroot.findall('group'): + if opts.verbose: + print("No dedicated persons in package defined, showing the project persons.") + pac = None + m = show_project_meta(apiurl, prj) + metaroot = ET.fromstring(''.join(m)) + else: + # fallback to project lookup for old servers + if prj and not searchresult: + m = show_project_meta(apiurl, prj) + metaroot = ET.fromstring(''.join(m)) + + # extract the maintainers + projects = [] + # from owner search + if searchresult: + for result in searchresult.findall('owner'): + maintainers = {} + maintainers.setdefault("project", result.get('project')) + maintainers.setdefault("package", result.get('package')) + for person in result.findall('person'): + maintainers.setdefault(person.get('role'), []).append(person.get('name')) + for group in result.findall('group'): + maintainers.setdefault(group.get('role'), []).append("group:"+group.get('name')) + projects = projects + [maintainers] + # from meta data + if metaroot: + # we have just one result + maintainers = {} + for person in metaroot.findall('person'): + maintainers.setdefault(person.get('role'), []).append(person.get('userid')) + for group in metaroot.findall('group'): + maintainers.setdefault(group.get('role'), []).append("group:"+group.get('groupid')) + projects = [maintainers] + + # showing the maintainers + for maintainers in projects: + indent = "" + definingproject = maintainers.get("project") + if definingproject: + definingpackage = maintainers.get("package") + indent = " " + if definingpackage: + print("Defined in package: %s/%s " % (definingproject, definingpackage)) + else: + print("Defined in project: ", definingproject) + + if prj: + # not for user/group search + for role in roles: + if opts.bugowner and not len(maintainers.get(role, [])): + role = 'maintainer' + if pac: + print("%s%s of %s/%s : " %(indent, role, prj, pac)) + else: + print("%s%s of %s : " %(indent, role, prj)) + if opts.email: + emails = [] + for maintainer in maintainers.get(role, []): + user = get_maintainer_data(apiurl, maintainer, verbose=False) + if len(user): + emails.append(''.join(user)) + print(indent, end=' ') + print(', '.join(emails) or '-') + elif opts.verbose: + userdata = [] + for maintainer in maintainers.get(role, []): + user = get_maintainer_data(apiurl, maintainer, verbose=True) + userdata.append(user[0]) + if user[1] != '-': + userdata.append("%s <%s>"%(user[1], user[2])) + else: + userdata.append(user[2]) + for row in build_table(2, userdata, None, 3): + print(indent, end=' ') + print(row) + else: + print(indent, end=' ') + print(', '.join(maintainers.get(role, [])) or '-') + print() + + @cmdln.alias('who') + @cmdln.alias('user') + def do_whois(self, subcmd, opts, *usernames): + """${cmd_name}: Show fullname and email of a buildservice user + + ${cmd_usage} + ${cmd_option_list} + """ + apiurl = self.get_api_url() + if len(usernames) < 1: + if 'user' not in conf.config['api_host_options'][apiurl]: + raise oscerr.WrongArgs('your .oscrc does not have your user name.') + usernames = (conf.config['api_host_options'][apiurl]['user'],) + for name in usernames: + user = get_user_data(apiurl, name, 'login', 'realname', 'email') + if len(user) == 3: + print("%s: \"%s\" <%s>"%(user[0], user[1], user[2])) + + + @cmdln.option('-r', '--revision', metavar='rev', + help='print out the specified revision') + @cmdln.option('-e', '--expand', action='store_true', + help='force expansion of linked packages.') + @cmdln.option('-u', '--unexpand', action='store_true', + help='always work with unexpanded packages.') + @cmdln.option('-M', '--meta', action='store_true', + help='list meta data files') + @cmdln.alias('less') + def do_cat(self, subcmd, opts, *args): + """${cmd_name}: Output the content of a file to standard output + + Examples: + osc cat project package file + osc cat project/package/file + osc cat http://api.opensuse.org/build/.../_log + osc cat http://api.opensuse.org/source/../_link + + ${cmd_usage} + ${cmd_option_list} + """ + + if len(args) == 1 and (args[0].startswith('http://') or + args[0].startswith('https://')): + opts.method = 'GET' + opts.headers = None + opts.data = None + opts.file = None + return self.do_api('list', opts, *args) + + + + args = slash_split(args) + if len(args) != 3: + raise oscerr.WrongArgs('Wrong number of arguments.') + rev, dummy = parseRevisionOption(opts.revision) + apiurl = self.get_api_url() + + query = { } + if opts.meta: + query['meta'] = 1 + if opts.revision: + query['rev'] = opts.revision + if opts.expand: + query['rev'] = show_upstream_srcmd5(apiurl, args[0], args[1], expand=True, revision=opts.revision, meta=opts.meta) + u = makeurl(apiurl, ['source', args[0], args[1], args[2]], query=query) + try: + if subcmd == 'less': + f = http_GET(u) + run_pager(''.join(f.readlines())) + else: + for data in streamfile(u): + sys.stdout.write(data) + except HTTPError as e: + if e.code == 404 and not opts.expand and not opts.unexpand: + print('expanding link...', file=sys.stderr) + query['rev'] = show_upstream_srcmd5(apiurl, args[0], args[1], expand=True, revision=opts.revision) + u = makeurl(apiurl, ['source', args[0], args[1], args[2]], query=query) + if subcmd == "less": + f = http_GET(u) + run_pager(''.join(f.readlines())) + else: + for data in streamfile(u): + sys.stdout.write(data) + else: + e.osc_msg = 'If linked, try: cat -e' + raise e + + + # helper function to download a file from a specific revision + def download(self, name, md5, dir, destfile): + o = open(destfile, 'wb') + if md5 != '': + query = {'rev': dir['srcmd5']} + u = makeurl(dir['apiurl'], ['source', dir['project'], dir['package'], pathname2url(name)], query=query) + for buf in streamfile(u, http_GET, BUFSIZE): + o.write(buf) + o.close() + + + @cmdln.option('-d', '--destdir', default='repairlink', metavar='DIR', + help='destination directory') + def do_repairlink(self, subcmd, opts, *args): + """${cmd_name}: Repair a broken source link + + This command checks out a package with merged source changes. It uses + a 3-way merge to resolve file conflicts. After reviewing/repairing + the merge, use 'osc resolved ...' and 'osc ci' to re-create a + working source link. + + usage: + * For merging conflicting changes of a checkout package: + osc repairlink + + * Check out a package and merge changes: + osc repairlink PROJECT PACKAGE + + * Pull conflicting changes from one project into another one: + osc repairlink PROJECT PACKAGE INTO_PROJECT [INTO_PACKAGE] + + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + args = slash_split(args) + if len(args) >= 3 and len(args) <= 4: + prj = args[0] + package = target_package = args[1] + target_prj = args[2] + if len(args) == 4: + target_package = args[3] + elif len(args) == 2: + target_prj = prj = args[0] + target_package = package = args[1] + elif is_package_dir(os.getcwd()): + target_prj = prj = store_read_project(os.getcwd()) + target_package = package = store_read_package(os.getcwd()) + else: + raise oscerr.WrongArgs('Please specify project and package') + + # first try stored reference, then lastworking + query = { 'rev': 'latest' } + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + raise oscerr.APIError('package is not a source link') + if linkinfo.get('error') == None: + raise oscerr.APIError('source link is not broken') + workingrev = None + + if linkinfo.get('baserev'): + query = { 'rev': 'latest', 'linkrev': 'base' } + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo.get('error') == None: + workingrev = linkinfo.get('xsrcmd5') + + if workingrev == None: + query = { 'lastworking': 1 } + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + linkinfo = root.find('linkinfo') + if linkinfo == None: + raise oscerr.APIError('package is not a source link') + if linkinfo.get('error') == None: + raise oscerr.APIError('source link is not broken') + workingrev = linkinfo.get('lastworking') + if workingrev == None: + raise oscerr.APIError('source link never worked') + print("using last working link target") + else: + print("using link target of last commit") + + query = { 'expand': 1, 'emptylink': 1 } + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + meta = f.readlines() + root_new = ET.fromstring(''.join(meta)) + dir_new = { 'apiurl': apiurl, 'project': prj, 'package': package } + dir_new['srcmd5'] = root_new.get('srcmd5') + dir_new['entries'] = [[n.get('name'), n.get('md5')] for n in root_new.findall('entry')] + + query = { 'rev': workingrev } + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + root_oldpatched = ET.parse(f).getroot() + linkinfo_oldpatched = root_oldpatched.find('linkinfo') + if linkinfo_oldpatched == None: + raise oscerr.APIError('working rev is not a source link?') + if linkinfo_oldpatched.get('error') != None: + raise oscerr.APIError('working rev is not working?') + dir_oldpatched = { 'apiurl': apiurl, 'project': prj, 'package': package } + dir_oldpatched['srcmd5'] = root_oldpatched.get('srcmd5') + dir_oldpatched['entries'] = [[n.get('name'), n.get('md5')] for n in root_oldpatched.findall('entry')] + + query = {} + query['rev'] = linkinfo_oldpatched.get('srcmd5') + u = makeurl(apiurl, ['source', linkinfo_oldpatched.get('project'), linkinfo_oldpatched.get('package')], query=query) + f = http_GET(u) + root_old = ET.parse(f).getroot() + dir_old = { 'apiurl': apiurl } + dir_old['project'] = linkinfo_oldpatched.get('project') + dir_old['package'] = linkinfo_oldpatched.get('package') + dir_old['srcmd5'] = root_old.get('srcmd5') + dir_old['entries'] = [[n.get('name'), n.get('md5')] for n in root_old.findall('entry')] + + entries_old = dict(dir_old['entries']) + entries_oldpatched = dict(dir_oldpatched['entries']) + entries_new = dict(dir_new['entries']) + + entries = {} + entries.update(entries_old) + entries.update(entries_oldpatched) + entries.update(entries_new) + + destdir = opts.destdir + if os.path.isdir(destdir): + shutil.rmtree(destdir) + os.mkdir(destdir) + + Package.init_package(apiurl, target_prj, target_package, destdir) + store_write_string(destdir, '_files', ''.join(meta) + '\n') + store_write_string(destdir, '_linkrepair', '') + pac = Package(destdir) + + storedir = os.path.join(destdir, store) + + for name in sorted(entries.keys()): + md5_old = entries_old.get(name, '') + md5_new = entries_new.get(name, '') + md5_oldpatched = entries_oldpatched.get(name, '') + if md5_new != '': + self.download(name, md5_new, dir_new, os.path.join(storedir, name)) + if md5_old == md5_new: + if md5_oldpatched == '': + pac.put_on_deletelist(name) + continue + print(statfrmt(' ', name)) + self.download(name, md5_oldpatched, dir_oldpatched, os.path.join(destdir, name)) + continue + if md5_old == md5_oldpatched: + if md5_new == '': + continue + print(statfrmt('U', name)) + shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name)) + continue + if md5_new == md5_oldpatched: + if md5_new == '': + continue + print(statfrmt('G', name)) + shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name)) + continue + self.download(name, md5_oldpatched, dir_oldpatched, os.path.join(destdir, name + '.mine')) + if md5_new != '': + shutil.copy2(os.path.join(storedir, name), os.path.join(destdir, name + '.new')) + else: + self.download(name, md5_new, dir_new, os.path.join(destdir, name + '.new')) + self.download(name, md5_old, dir_old, os.path.join(destdir, name + '.old')) + + if binary_file(os.path.join(destdir, name + '.mine')) or \ + binary_file(os.path.join(destdir, name + '.old')) or \ + binary_file(os.path.join(destdir, name + '.new')): + shutil.copy2(os.path.join(destdir, name + '.new'), os.path.join(destdir, name)) + print(statfrmt('C', name)) + pac.put_on_conflictlist(name) + continue + + o = open(os.path.join(destdir, name), 'wb') + code = run_external('diff3', '-m', '-E', + '-L', '.mine', + os.path.join(destdir, name + '.mine'), + '-L', '.old', + os.path.join(destdir, name + '.old'), + '-L', '.new', + os.path.join(destdir, name + '.new'), + stdout=o) + if code == 0: + print(statfrmt('G', name)) + os.unlink(os.path.join(destdir, name + '.mine')) + os.unlink(os.path.join(destdir, name + '.old')) + os.unlink(os.path.join(destdir, name + '.new')) + elif code == 1: + print(statfrmt('C', name)) + pac.put_on_conflictlist(name) + else: + print(statfrmt('?', name)) + pac.put_on_conflictlist(name) + + pac.write_deletelist() + pac.write_conflictlist() + print() + print('Please change into the \'%s\' directory,' % destdir) + print('fix the conflicts (files marked with \'C\' above),') + print('run \'osc resolved ...\', and commit the changes.') + + + def do_pull(self, subcmd, opts, *args): + """${cmd_name}: merge the changes of the link target into your working copy. + + ${cmd_option_list} + """ + + if not is_package_dir('.'): + raise oscerr.NoWorkingCopy('Error: \'%s\' is not an osc working copy.' % os.path.abspath('.')) + p = Package('.') + # check if everything is committed + for filename in p.filenamelist: + state = p.status(filename) + if state != ' ' and state != 'S': + raise oscerr.WrongArgs('Please commit your local changes first!') + # check if we need to update + upstream_rev = p.latest_rev() + if not (p.isfrozen() or p.ispulled()): + raise oscerr.WrongArgs('osc pull makes only sense with a detached head, did you mean osc up?') + if p.rev != upstream_rev: + raise oscerr.WorkingCopyOutdated((p.absdir, p.rev, upstream_rev)) + elif not p.islink(): + raise oscerr.WrongArgs('osc pull only works on linked packages.') + elif not p.isexpanded(): + raise oscerr.WrongArgs('osc pull only works on expanded links.') + linkinfo = p.linkinfo + baserev = linkinfo.baserev + if baserev == None: + raise oscerr.WrongArgs('osc pull only works on links containing a base revision.') + + # get revisions we need + query = { 'expand': 1, 'emptylink': 1 } + u = makeurl(p.apiurl, ['source', p.prjname, p.name], query=query) + f = http_GET(u) + meta = f.readlines() + root_new = ET.fromstring(''.join(meta)) + linkinfo_new = root_new.find('linkinfo') + if linkinfo_new == None: + raise oscerr.APIError('link is not a really a link?') + if linkinfo_new.get('error') != None: + raise oscerr.APIError('link target is broken') + if linkinfo_new.get('srcmd5') == baserev: + print("Already up-to-date.") + p.unmark_frozen() + return + dir_new = { 'apiurl': p.apiurl, 'project': p.prjname, 'package': p.name } + dir_new['srcmd5'] = root_new.get('srcmd5') + dir_new['entries'] = [[n.get('name'), n.get('md5')] for n in root_new.findall('entry')] + + dir_oldpatched = { 'apiurl': p.apiurl, 'project': p.prjname, 'package': p.name, 'srcmd5': p.srcmd5 } + dir_oldpatched['entries'] = [[f.name, f.md5] for f in p.filelist] + + query = { 'rev': linkinfo.srcmd5 } + u = makeurl(p.apiurl, ['source', linkinfo.project, linkinfo.package], query=query) + f = http_GET(u) + root_old = ET.parse(f).getroot() + dir_old = { 'apiurl': p.apiurl, 'project': linkinfo.project, 'package': linkinfo.package, 'srcmd5': linkinfo.srcmd5 } + dir_old['entries'] = [[n.get('name'), n.get('md5')] for n in root_old.findall('entry')] + + # now do 3-way merge + entries_old = dict(dir_old['entries']) + entries_oldpatched = dict(dir_oldpatched['entries']) + entries_new = dict(dir_new['entries']) + entries = {} + entries.update(entries_old) + entries.update(entries_oldpatched) + entries.update(entries_new) + for name in sorted(entries.keys()): + if name.startswith('_service:') or name.startswith('_service_'): + continue + md5_old = entries_old.get(name, '') + md5_new = entries_new.get(name, '') + md5_oldpatched = entries_oldpatched.get(name, '') + if md5_old == md5_new or md5_oldpatched == md5_new: + continue + if md5_old == md5_oldpatched: + if md5_new == '': + print(statfrmt('D', name)) + p.put_on_deletelist(name) + os.unlink(name) + elif md5_old == '': + print(statfrmt('A', name)) + self.download(name, md5_new, dir_new, name) + p.put_on_addlist(name) + else: + print(statfrmt('U', name)) + self.download(name, md5_new, dir_new, name) + continue + # need diff3 to resolve issue + if md5_oldpatched == '': + open(name, 'w').write('') + os.rename(name, name + '.mine') + self.download(name, md5_new, dir_new, name + '.new') + self.download(name, md5_old, dir_old, name + '.old') + if binary_file(name + '.mine') or binary_file(name + '.old') or binary_file(name + '.new'): + shutil.copy2(name + '.new', name) + print(statfrmt('C', name)) + p.put_on_conflictlist(name) + continue + + o = open(name, 'wb') + code = run_external('diff3', '-m', '-E', + '-L', '.mine', name + '.mine', + '-L', '.old', name + '.old', + '-L', '.new', name + '.new', + stdout=o) + if code == 0: + print(statfrmt('G', name)) + os.unlink(name + '.mine') + os.unlink(name + '.old') + os.unlink(name + '.new') + elif code == 1: + print(statfrmt('C', name)) + p.put_on_conflictlist(name) + else: + print(statfrmt('?', name)) + p.put_on_conflictlist(name) + p.write_deletelist() + p.write_addlist() + p.write_conflictlist() + # store new linkrev + store_write_string(p.absdir, '_pulled', linkinfo_new.get('srcmd5') + '\n') + p.unmark_frozen() + print() + if len(p.in_conflict): + print('Please fix the conflicts (files marked with \'C\' above),') + print('run \'osc resolved ...\', and commit the changes') + print('to update the link information.') + else: + print('Please commit the changes to update the link information.') + + @cmdln.option('--create', action='store_true', default=False, + help='create new gpg signing key for this project') + @cmdln.option('--extend', action='store_true', default=False, + help='extend expiration date of the gpg public key for this project') + @cmdln.option('--delete', action='store_true', default=False, + help='delete the gpg signing key in this project') + @cmdln.option('--notraverse', action='store_true', default=False, + help='don\' traverse projects upwards to find key') + @cmdln.option('--sslcert', action='store_true', default=False, + help='fetch SSL certificate instead of GPG key') + def do_signkey(self, subcmd, opts, *args): + """${cmd_name}: Manage Project Signing Key + + osc signkey [--create|--delete|--extend] <PROJECT> + osc signkey [--notraverse] <PROJECT> + + This command is for managing gpg keys. It shows the public key + by default. There is no way to download or upload the private + part of a key by design. + + However you can create a new own key. You may want to consider + to sign the public key with your own existing key. + + If a project has no key, the key from upper level project will + be used (eg. when dropping "KDE:KDE4:Community" key, the one from + "KDE:KDE4" will be used). + + WARNING: THE OLD KEY WILL NOT BE RESTORABLE WHEN USING DELETE OR CREATE + + ${cmd_usage} + ${cmd_option_list} + """ + + apiurl = self.get_api_url() + f = None + + prj = None + if len(args) == 0: + cwd = os.getcwd() + if is_project_dir(cwd) or is_package_dir(cwd): + prj = store_read_project(cwd) + if len(args) == 1: + prj = args[0] + + if not prj: + raise oscerr.WrongArgs('Please specify just the project') + + if opts.create: + url = makeurl(apiurl, ['source', prj], query='cmd=createkey') + f = http_POST(url) + elif opts.extend: + url = makeurl(apiurl, ['source', prj], query='cmd=extendkey') + f = http_POST(url) + elif opts.delete: + url = makeurl(apiurl, ['source', prj, "_pubkey"]) + f = http_DELETE(url) + else: + while True: + try: + url = makeurl(apiurl, ['source', prj, '_pubkey']) + if opts.sslcert: + url = makeurl(apiurl, ['source', prj, '_project', '_sslcert'], 'meta=1') + f = http_GET(url) + break + except HTTPError as e: + l = prj.rsplit(':', 1) + # try key from parent project + if not opts.notraverse and len(l) > 1 and l[0] and l[1] and e.code == 404: + print('%s has no key, trying %s' % (prj, l[0])) + prj = l[0] + else: + raise + + while True: + buf = f.read(16384) + if not buf: + break + sys.stdout.write(buf) + + @cmdln.option('-m', '--message', + help='add MESSAGE to changes (do not open an editor)') + @cmdln.option('-F', '--file', metavar='FILE', + help='read changes message from FILE (do not open an editor)') + @cmdln.option('-e', '--just-edit', action='store_true', default=False, + help='just open changes (cannot be used with -m)') + def do_vc(self, subcmd, opts, *args): + """${cmd_name}: Edit the changes file + + osc vc [-m MESSAGE|-e] [filename[.changes]|path [file_with_comment]] + If no <filename> is given, exactly one *.changes or *.spec file has to + be in the cwd or in path. + + The email address used in .changes file is read from BuildService + instance, or should be defined in ~/.oscrc + [https://api.opensuse.org/] + user = login + pass = password + email = user@defined.email + + or can be specified via mailaddr environment variable. + + ${cmd_usage} + ${cmd_option_list} + """ + + from subprocess import Popen + if opts.message and opts.file: + raise oscerr.WrongOptions('\'--message\' and \'--file\' are mutually exclusive') + elif opts.message and opts.just_edit: + raise oscerr.WrongOptions('\'--message\' and \'--just-edit\' are mutually exclusive') + elif opts.file and opts.just_edit: + raise oscerr.WrongOptions('\'--file\' and \'--just-edit\' are mutually exclusive') + meego_style = False + if not args: + import glob, re + try: + fn_changelog = glob.glob('*.changes')[0] + fp = file(fn_changelog) + titleline = fp.readline() + fp.close() + if re.match('^\*\W+(.+\W+\d{1,2}\W+20\d{2})\W+(.+)\W+<(.+)>\W+(.+)$', titleline): + meego_style = True + except IndexError: + pass + + cmd_list = [conf.config['vc-cmd']] + if meego_style: + if not os.path.exists('/usr/bin/vc'): + print('Error: you need meego-packaging-tools for /usr/bin/vc command', file=sys.stderr) + return 1 + cmd_list = ['/usr/bin/vc'] + elif which(cmd_list[0]) is None: + print('Error: vc (\'%s\') command not found' % cmd_list[0], file=sys.stderr) + print('Install the build package from http://download.opensuse.org/repositories/openSUSE:/Tools/', file=sys.stderr) + return 1 + + # set user's email if no mailaddr exists + if 'mailaddr' not in os.environ: + + if len(args) and is_package_dir(args[0]): + apiurl = store_read_apiurl(args[0]) + else: + apiurl = self.get_api_url() + + user = conf.get_apiurl_usr(apiurl) + + data = get_user_data(apiurl, user, 'email') + if data: + os.environ['mailaddr'] = data[0] + else: + print('Try env mailaddr=...', file=sys.stderr) + + # mailaddr can be overrided by config one + if 'email' in conf.config['api_host_options'][apiurl]: + os.environ['mailaddr'] = conf.config['api_host_options'][apiurl]['email'] + + if meego_style: + if opts.message or opts.just_edit: + print('Warning: to edit MeeGo style changelog, opts will be ignored.', file=sys.stderr) + else: + if opts.message: + cmd_list.append("-m") + cmd_list.append(opts.message) + if opts.file: + if not os.path.isfile(opts.file): + raise oscerr.WrongOptions('\'%s\': is no file' % opts.file) + cmd_list.append("-m") + cmd_list.append(open(opts.file).read().strip()) + + if opts.just_edit: + cmd_list.append("-e") + + cmd_list.extend(args) + + vc = Popen(cmd_list) + vc.wait() + sys.exit(vc.returncode) + + @cmdln.option('-f', '--force', action='store_true', + help='forces removal of entire package and its files') + def do_mv(self, subcmd, opts, source, dest): + """${cmd_name}: Move SOURCE file to DEST and keep it under version control + + ${cmd_usage} + ${cmd_option_list} + """ + + if not os.path.isfile(source): + raise oscerr.WrongArgs("Source file '%s' does not exist or is not a file" % source) + if not opts.force and os.path.isfile(dest): + raise oscerr.WrongArgs("Dest file '%s' already exists" % dest) + if os.path.isdir(dest): + dest = os.path.join(dest, os.path.basename(source)) + src_pkg = findpacs([source]) + tgt_pkg = findpacs([dest]) + if not src_pkg: + raise oscerr.NoWorkingCopy("Error: \"%s\" is not located in an osc working copy." % os.path.abspath(source)) + if not tgt_pkg: + raise oscerr.NoWorkingCopy("Error: \"%s\" does not point to an osc working copy." % os.path.abspath(dest)) + + os.rename(source, dest) + try: + tgt_pkg[0].addfile(os.path.basename(dest)) + except oscerr.PackageFileConflict: + # file is already tracked + pass + src_pkg[0].delete_file(os.path.basename(source), force=opts.force) + + @cmdln.option('-d', '--delete', action='store_true', + help='delete option from config or reset option to the default)') + @cmdln.option('-s', '--stdin', action='store_true', + help='indicates that the config value should be read from stdin') + @cmdln.option('-p', '--prompt', action='store_true', + help='prompt for a value') + @cmdln.option('--no-echo', action='store_true', + help='prompt for a value but do not echo entered characters') + @cmdln.option('--dump', action='store_true', + help='dump the complete configuration (without \'pass\' and \'passx\' options)') + @cmdln.option('--dump-full', action='store_true', + help='dump the complete configuration (including \'pass\' and \'passx\' options)') + def do_config(self, subcmd, opts, *args): + """${cmd_name}: get/set a config option + + Examples: + osc config section option (get current value) + osc config section option value (set to value) + osc config section option --delete (delete option/reset to the default) + (section is either an apiurl or an alias or 'general') + osc config --dump (dump the complete configuration) + + ${cmd_usage} + ${cmd_option_list} + """ + if len(args) < 2 and not (opts.dump or opts.dump_full): + raise oscerr.WrongArgs('Too few arguments') + elif opts.dump or opts.dump_full: + cp = conf.get_configParser(conf.config['conffile']) + for sect in cp.sections(): + print('[%s]' % sect) + for opt in sorted(cp.options(sect)): + if sect == 'general' and opt in conf.api_host_options or \ + sect != 'general' and not opt in conf.api_host_options: + continue + if opt in ('pass', 'passx') and not opts.dump_full: + continue + val = str(cp.get(sect, opt, raw=True)) + # special handling for continuation lines + val = '\n '.join(val.split('\n')) + print('%s = %s' % (opt, val)) + print() + return + + section, opt, val = args[0], args[1], args[2:] + if len(val) and (opts.delete or opts.stdin or opts.prompt or opts.no_echo): + raise oscerr.WrongOptions('Sorry, \'--delete\' or \'--stdin\' or \'--prompt\' or \'--no-echo\' ' \ + 'and the specification of a value argument are mutually exclusive') + elif (opts.prompt or opts.no_echo) and opts.stdin: + raise oscerr.WrongOptions('Sorry, \'--prompt\' or \'--no-echo\' and \'--stdin\' are mutually exclusive') + elif opts.stdin: + # strip lines + val = [i.strip() for i in sys.stdin.readlines() if i.strip()] + if not len(val): + raise oscerr.WrongArgs('error: read empty value from stdin') + elif opts.no_echo or opts.prompt: + if opts.no_echo: + import getpass + inp = getpass.getpass('Value: ').strip() + else: + inp = raw_input('Value: ').strip() + if not inp: + raise oscerr.WrongArgs('error: no value was entered') + val = [inp] + opt, newval = conf.config_set_option(section, opt, ' '.join(val), delete=opts.delete, update=True) + if newval is None and opts.delete: + print('\'%s\': \'%s\' got removed' % (section, opt)) + elif newval is None: + print('\'%s\': \'%s\' is not set' % (section, opt)) + else: + if opts.no_echo: + # supress value + print('\'%s\': set \'%s\'' % (section, opt)) + elif opt == 'pass' and not conf.config['plaintext_passwd'] and newval == 'your_password': + opt, newval = conf.config_set_option(section, 'passx') + print('\'%s\': \'pass\' was rewritten to \'passx\': \'%s\'' % (section, newval)) + else: + print('\'%s\': \'%s\' is set to \'%s\'' % (section, opt, newval)) + + def do_revert(self, subcmd, opts, *files): + """${cmd_name}: Restore changed files or the entire working copy. + + Examples: + osc revert <modified file(s)> + ose revert . + Note: this only works for package working copies + + ${cmd_usage} + ${cmd_option_list} + """ + pacs = findpacs(files) + for p in pacs: + if not len(p.todo): + p.todo = p.filenamelist + p.to_be_added + for f in p.todo: + p.revert(f) + + @cmdln.option('--force-apiurl', action='store_true', + help='ask once for an apiurl and force this apiurl for all inconsistent projects/packages') + def do_repairwc(self, subcmd, opts, *args): + """${cmd_name}: try to repair an inconsistent working copy + + Examples: + osc repairwc <path> + + Note: if <path> is omitted it defaults to '.' (<path> can be + a project or package working copy) + + Warning: This command might delete some files in the storedir + (.osc). Please check the state of the wc afterwards (via 'osc status'). + + ${cmd_usage} + ${cmd_option_list} + """ + def get_apiurl(apiurls): + print('No apiurl is defined for this working copy.\n' \ + 'Please choose one from the following list (enter the number):') + for i in range(len(apiurls)): + print(' %d) %s' % (i, apiurls[i])) + num = raw_input('> ') + try: + num = int(num) + except ValueError: + raise oscerr.WrongArgs('\'%s\' is not a number. Aborting' % num) + if num < 0 or num >= len(apiurls): + raise oscerr.WrongArgs('number \'%s\' out of range. Aborting' % num) + return apiurls[num] + + args = parseargs(args) + pacs = [] + apiurls = list(conf.config['api_host_options'].keys()) + apiurl = '' + for i in args: + if is_project_dir(i): + try: + prj = Project(i, getPackageList=False) + except oscerr.WorkingCopyInconsistent as e: + if '_apiurl' in e.dirty_files and (not apiurl or not opts.force_apiurl): + apiurl = get_apiurl(apiurls) + prj = Project(i, getPackageList=False, wc_check=False) + prj.wc_repair(apiurl) + for p in prj.pacs_have: + if p in prj.pacs_broken: + continue + try: + Package(os.path.join(i, p)) + except oscerr.WorkingCopyInconsistent: + pacs.append(os.path.join(i, p)) + elif is_package_dir(i): + pacs.append(i) + else: + print('\'%s\' is neither a project working copy ' \ + 'nor a package working copy' % i, file=sys.stderr) + for pdir in pacs: + try: + p = Package(pdir) + except oscerr.WorkingCopyInconsistent as e: + if '_apiurl' in e.dirty_files and (not apiurl or not opts.force_apiurl): + apiurl = get_apiurl(apiurls) + p = Package(pdir, wc_check=False) + p.wc_repair(apiurl) + print('done. Please check the state of the wc (via \'osc status %s\').' % i) + else: + print('osc: working copy \'%s\' is not inconsistent' % i, file=sys.stderr) + + @cmdln.option('-n', '--dry-run', action='store_true', + help='print the results without actually removing a file') + def do_clean(self, subcmd, opts, *args): + """${cmd_name}: removes all untracked files from the package working copy + + Examples: + osc clean <path> + + Note: if <path> is omitted it defaults to '.' (<path> has to + be a package working copy) + + Warning: This command removes all files with status '?'. + + ${cmd_usage} + ${cmd_option_list} + """ + pacs = parseargs(args) + # do a sanity check first + for pac in pacs: + if not is_package_dir(pac): + raise oscerr.WrongArgs('\'%s\' is no package working copy' % pac) + for pdir in pacs: + p = Package(pdir) + pdir = getTransActPath(pdir) + for filename in (fname for st, fname in p.get_status() if st == '?'): + print('Removing: %s' % os.path.join(pdir, filename)) + if not opts.dry_run: + os.unlink(os.path.join(p.absdir, filename)) + + def _load_plugins(self): + plugin_dirs = [ + '/usr/lib/osc-plugins', + '/usr/local/lib/osc-plugins', + '/var/lib/osc-plugins', # Kept for backward compatibility + os.path.expanduser('~/.osc-plugins')] + for plugin_dir in plugin_dirs: + if not os.path.isdir(plugin_dir): + continue + for extfile in os.listdir(plugin_dir): + if not extfile.endswith('.py'): + continue + try: + modname = os.path.splitext(extfile)[0] + mod = imp.load_source(modname, os.path.join(plugin_dir, extfile)) + # restore the old exec semantic + mod.__dict__.update(globals()) + for name in dir(mod): + data = getattr(mod, name) + # Add all functions (which are defined in the imported module) + # to the class (filtering only methods which start with "do_" + # breaks the old behavior). + # Also add imported modules (needed for backward compatibility). + # New plugins should not use "self.<imported modname>.<something>" + # to refer to the imported module. Instead use + # "<imported modname>.<something>". + if (inspect.isfunction(data) and inspect.getmodule(data) == mod + or inspect.ismodule(data)): + setattr(self.__class__, name, data) + except (SyntaxError, NameError, ImportError) as e: + if (os.environ.get('OSC_PLUGIN_FAIL_IGNORE')): + print("%s: %s\n" % (os.path.join(plugin_dir, extfile), e), file=sys.stderr) + else: + import traceback + traceback.print_exc(file=sys.stderr) + print('\n%s: %s' % (os.path.join(plugin_dir, extfile), e), file=sys.stderr) + print("\n Try 'env OSC_PLUGIN_FAIL_IGNORE=1 osc ...'", file=sys.stderr) + sys.exit(1) + +# fini! +############################################################################### + +# vim: sw=4 et diff --git a/osc/conf.py b/osc/conf.py new file mode 100644 index 0000000..100259a --- /dev/null +++ b/osc/conf.py @@ -0,0 +1,1031 @@ +# Copyright (C) 2006-2009 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or version 3 (at your option). + +from __future__ import print_function + +"""Read osc configuration and store it in a dictionary + +This module reads and parses ~/.oscrc. The resulting configuration is stored +for later usage in a dictionary named 'config'. +The .oscrc is kept mode 0600, so that it is not publically readable. +This gives no real security for storing passwords. +If in doubt, use your favourite keyring. +Password is stored on ~/.oscrc as bz2 compressed and base64 encoded, so that is fairly +large and not to be recognized or remembered easily by an occasional spectator. + +If information is missing, it asks the user questions. + +After reading the config, urllib2 is initialized. + +The configuration dictionary could look like this: + +{'apisrv': 'https://api.opensuse.org/', + 'user': 'joe', + 'api_host_options': {'api.opensuse.org': {'user': 'joe', 'pass': 'secret'}, + 'apitest.opensuse.org': {'user': 'joe', 'pass': 'secret', + 'http_headers':(('Host','api.suse.de'), + ('User','faye'))}, + 'foo.opensuse.org': {'user': 'foo', 'pass': 'foo'}}, + 'build-cmd': '/usr/bin/build', + 'build-root': '/abuild/oscbuild-%(repo)s-%(arch)s', + 'packagecachedir': '/var/cache/osbuild', + 'su-wrapper': 'sudo', + } + +""" + +import bz2 +import base64 +import os +import re +import sys +import ssl + +try: + from http.cookiejar import LWPCookieJar, CookieJar + from http.client import HTTPConnection, HTTPResponse + from io import StringIO + from urllib.parse import urlsplit + from urllib.error import URLError + from urllib.request import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler + from urllib.request import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler +except ImportError: + #python 2.x + from cookielib import LWPCookieJar, CookieJar + from httplib import HTTPConnection, HTTPResponse + from StringIO import StringIO + from urlparse import urlsplit + from urllib2 import URLError, HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, ProxyHandler + from urllib2 import AbstractHTTPHandler, build_opener, proxy_bypass, HTTPSHandler + +from . import OscConfigParser +from osc import oscerr +from .oscsslexcp import NoSecureSSLError + +GENERIC_KEYRING = False +GNOME_KEYRING = False + +try: + import keyring + GENERIC_KEYRING = True +except: + try: + import gobject + gobject.set_application_name('osc') + import gnomekeyring + if os.environ['GNOME_DESKTOP_SESSION_ID']: + # otherwise gnome keyring bindings spit out errors, when you have + # it installed, but you are not under gnome + # (even though hundreds of gnome-keyring daemons got started in parallel) + # another option would be to support kwallet here + GNOME_KEYRING = gnomekeyring.is_available() + except: + pass + + +def _get_processors(): + """ + get number of processors (online) based on + SC_NPROCESSORS_ONLN (returns 1 if config name does not exist). + """ + try: + return os.sysconf('SC_NPROCESSORS_ONLN') + except ValueError as e: + return 1 + +DEFAULTS = {'apiurl': 'https://api.opensuse.org', + 'user': 'your_username', + 'pass': 'your_password', + 'passx': '', + 'packagecachedir': '/var/tmp/osbuild-packagecache', + 'su-wrapper': 'sudo', + + # build type settings + 'build-cmd': '/usr/bin/build', + 'build-type': '', # may be empty for chroot, kvm or xen + 'build-root': '/var/tmp/build-root/%(repo)s-%(arch)s', + 'build-uid': '', # use the default provided by build + 'build-device': '', # required for VM builds + 'build-memory': '', # required for VM builds + 'build-swap': '', # optional for VM builds + 'build-vmdisk-rootsize': '', # optional for VM builds + 'build-vmdisk-swapsize': '', # optional for VM builds + 'build-vmdisk-filesystem': '', # optional for VM builds + 'build-vm-user': '', # optional for VM builds + 'build-kernel': '', # optional for VM builds + 'build-initrd': '', # optional for VM builds + + 'build-jobs': _get_processors(), + 'builtin_signature_check': '1', # by default use builtin check for verify pkgs + 'icecream': '0', + + 'buildlog_strip_time': '0', # strips the build time from the build log + + 'debug': '0', + 'http_debug': '0', + 'http_full_debug': '0', + 'http_retries': '3', + 'verbose': '1', + 'traceback': '0', + 'post_mortem': '0', + 'use_keyring': '0', + 'gnome_keyring': '0', + 'cookiejar': '~/.osc_cookiejar', + # fallback for osc build option --no-verify + 'no_verify': '0', + # enable project tracking by default + 'do_package_tracking': '1', + # default for osc build + 'extra-pkgs': '', + # default repository + 'build_repository': 'openSUSE_Factory', + # default project for branch or bco + 'getpac_default_project': 'openSUSE:Factory', + # alternate filesystem layout: have multiple subdirs, where colons were. + 'checkout_no_colon': '0', + # change filesystem layout: avoid checkout from within a proj or package dir. + 'checkout_rooted': '0', + # local files to ignore with status, addremove, .... + 'exclude_glob': '.osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.vctmp.*', + # whether to keep passwords in plaintext. + 'plaintext_passwd': '1', + # limit the age of requests shown with 'osc req list'. + # this is a default only, can be overridden by 'osc req list -D NNN' + # Use 0 for unlimted. + 'request_list_days': 0, + # check for unversioned/removed files before commit + 'check_filelist': '1', + # check for pending requests after executing an action (e.g. checkout, update, commit) + 'check_for_request_on_action': '0', + # what to do with the source package if the submitrequest has been accepted + 'submitrequest_on_accept_action': '', + 'request_show_interactive': '0', + 'request_show_source_buildstatus': '0', + # if a review is accepted in interactive mode and a group + # was specified the review will be accepted for this group + 'review_inherit_group': '0', + 'submitrequest_accepted_template': '', + 'submitrequest_declined_template': '', + 'linkcontrol': '0', + 'include_request_from_project': '1', + 'local_service_run': '1', + + # Maintenance defaults to OBS instance defaults + 'maintained_attribute': 'OBS:Maintained', + 'maintenance_attribute': 'OBS:MaintenanceProject', + 'maintained_update_project_attribute': 'OBS:UpdateProject', + 'show_download_progress': '0', + # path to the vc script + 'vc-cmd': '/usr/lib/build/vc' +} + +# being global to this module, this dict can be accessed from outside +# it will hold the parsed configuration +config = DEFAULTS.copy() + +boolean_opts = ['debug', 'do_package_tracking', 'http_debug', 'post_mortem', 'traceback', 'check_filelist', 'plaintext_passwd', + 'checkout_no_colon', 'checkout_rooted', 'check_for_request_on_action', 'linkcontrol', 'show_download_progress', 'request_show_interactive', + 'request_show_source_buildstatus', 'review_inherit_group', 'use_keyring', 'gnome_keyring', 'no_verify', 'builtin_signature_check', + 'http_full_debug', 'include_request_from_project', 'local_service_run', 'buildlog_strip_time'] + +api_host_options = ['user', 'pass', 'passx', 'aliases', 'http_headers', 'email', 'sslcertck', 'cafile', 'capath', 'trusted_prj'] + +new_conf_template = """ +[general] + +# URL to access API server, e.g. %(apiurl)s +# you also need a section [%(apiurl)s] with the credentials +apiurl = %(apiurl)s + +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = %(packagecachedir)s + +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = %(su-wrapper)s + +# rootdir to setup the chroot environment +# can contain %%(repo)s, %%(arch)s, %%(project)s, %%(package)s and %%(apihost)s (apihost is the hostname +# extracted from currently used apiurl) for replacement, e.g. +# /srv/oscbuild/%%(repo)s-%%(arch)s or +# /srv/oscbuild/%%(repo)s-%%(arch)s-%%(project)s-%%(package)s +#build-root = %(build-root)s + +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N + +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = + +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root + +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap + +# build-kernel is the boot kernel used for VM builds +#build-kernel = /boot/vmlinuz + +# build-initrd is the boot initrd used for VM builds +#build-initrd = /boot/initrd + +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 + +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 + +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 + +# build-vmdisk-filesystem is the file system type of the disk-image used in a VM build +# values are ext3(default) ext4 xfs reiserfs btrfs +#build-vmdisk-filesystem = ext4 + +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = + +# strip leading build time information from the build log +# buildlog_strip_time = 1 + +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace + +# build platform is used if the platform argument is omitted to osc build +#build_repository = %(build_repository)s + +# default project for getpac or bco +#getpac_default_project = %(getpac_default_project)s + +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = %(checkout_no_colon)s + +# change filesystem layout: avoid checkout within a project or package dir. +#checkout_rooted = %(checkout_rooted)s + +# local files to ignore with status, addremove, .... +#exclude_glob = %(exclude_glob)s + +# keep passwords in plaintext. +# Set to 0 to obfuscate passwords. It's no real security, just +# prevents most people from remembering your password if they watch +# you editing this file. +#plaintext_passwd = %(plaintext_passwd)s + +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = %(request_list_days)s + +# show info useful for debugging +#debug = 1 + +# show HTTP traffic useful for debugging +#http_debug = 1 + +# number of retries on HTTP transfer +#http_retries = 3 + +# Skip signature verification of packages used for build. +#no_verify = 1 + +# jump into the debugger in case of errors +#post_mortem = 1 + +# print call traces in case of errors +#traceback = 1 + +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 + +# check for unversioned/removed files before commit +#check_filelist = 1 + +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 + +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate + +# template for an accepted submitrequest +#submitrequest_accepted_template = Hi %%(who)s,\\n +# thanks for working on:\\t%%(tgt_project)s/%%(tgt_package)s. +# SR %%(reqid)s has been accepted.\\n\\nYour maintainers + +# template for a declined submitrequest +#submitrequest_declined_template = Hi %%(who)s,\\n +# sorry your SR %%(reqid)s (request type: %%(type)s) for +# %%(tgt_project)s/%%(tgt_package)s has been declined because... + +#review requests interactively (default: off) +#request_show_review = 1 + +# if a review is accepted in interactive mode and a group +# was specified the review will be accepted for this group (default: off) +#review_inherit_group = 1 + +[%(apiurl)s] +user = %(user)s +pass = %(pass)s +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Plain text password +#pass = +# Force using of keyring for this API +#keyring = 1 +""" + + +account_not_configured_text = """ +Your user account / password are not configured yet. +You will be asked for them below, and they will be stored in +%s for future use. +""" + +config_incomplete_text = """ + +Your configuration file %s is not complete. +Make sure that it has a [general] section. +(You can copy&paste the below. Some commented defaults are shown.) + +""" + +config_missing_apiurl_text = """ +the apiurl \'%s\' does not exist in the config file. Please enter +your credentials for this apiurl. +""" + +cookiejar = None + + +def parse_apisrv_url(scheme, apisrv): + if apisrv.startswith('http://') or apisrv.startswith('https://'): + url = apisrv + elif scheme != None: + url = scheme + apisrv + else: + msg = 'invalid apiurl \'%s\' (specify the protocol (http:// or https://))' % apisrv + raise URLError(msg) + scheme, url, path = urlsplit(url)[0:3] + return scheme, url, path.rstrip('/') + + +def urljoin(scheme, apisrv, path=''): + return '://'.join([scheme, apisrv]) + path + + +def is_known_apiurl(url): + """returns true if url is a known apiurl""" + apiurl = urljoin(*parse_apisrv_url(None, url)) + return apiurl in config['api_host_options'] + + +def extract_known_apiurl(url): + """ + Return longest prefix of given url that is known apiurl, + None if there is no known apiurl that is prefix of given url. + """ + scheme, host, path = parse_apisrv_url(None, url) + p = path.split('/') + while p: + apiurl = urljoin(scheme, host, '/'.join(p)) + if apiurl in config['api_host_options']: + return apiurl + p.pop() + return None + + +def get_apiurl_api_host_options(apiurl): + """ + Returns all apihost specific options for the given apiurl, None if + no such specific optiosn exist. + """ + # FIXME: in A Better World (tm) there was a config object which + # knows this instead of having to extract it from a url where it + # had been mingled into before. But this works fine for now. + + apiurl = urljoin(*parse_apisrv_url(None, apiurl)) + if is_known_apiurl(apiurl): + return config['api_host_options'][apiurl] + raise oscerr.ConfigMissingApiurl('missing credentials for apiurl: \'%s\'' % apiurl, + '', apiurl) + + +def get_apiurl_usr(apiurl): + """ + returns the user for this host - if this host does not exist in the + internal api_host_options the default user is returned. + """ + # FIXME: maybe there should be defaults not just for the user but + # for all apihost specific options. The ConfigParser class + # actually even does this but for some reason we don't use it + # (yet?). + + try: + return get_apiurl_api_host_options(apiurl)['user'] + except KeyError: + print('no specific section found in config file for host of [\'%s\'] - using default user: \'%s\'' \ + % (apiurl, config['user']), file=sys.stderr) + return config['user'] + + +# workaround m2crypto issue: +# if multiple SSL.Context objects are created +# m2crypto only uses the last object which was created. +# So we need to build a new opener everytime we switch the +# apiurl (because different apiurls may have different +# cafile/capath locations) +def _build_opener(apiurl): + from osc.core import __version__ + global config + if 'last_opener' not in _build_opener.__dict__: + _build_opener.last_opener = (None, None) + if apiurl == _build_opener.last_opener[0]: + return _build_opener.last_opener[1] + + # respect no_proxy env variable + if proxy_bypass(apiurl): + # initialize with empty dict + proxyhandler = ProxyHandler({}) + else: + # read proxies from env + proxyhandler = ProxyHandler() + + # workaround for http://bugs.python.org/issue9639 + authhandler_class = HTTPBasicAuthHandler + if sys.version_info >= (2, 6, 6) and sys.version_info < (2, 7, 1) \ + and not 'reset_retry_count' in dir(HTTPBasicAuthHandler): + print('warning: your urllib2 version seems to be broken. ' \ + 'Using a workaround for http://bugs.python.org/issue9639', file=sys.stderr) + + class OscHTTPBasicAuthHandler(HTTPBasicAuthHandler): + def http_error_401(self, *args): + response = HTTPBasicAuthHandler.http_error_401(self, *args) + self.retried = 0 + return response + + def http_error_404(self, *args): + self.retried = 0 + return None + + authhandler_class = OscHTTPBasicAuthHandler + elif sys.version_info >= (2, 6, 6) and sys.version_info < (2, 7, 1): + class OscHTTPBasicAuthHandler(HTTPBasicAuthHandler): + def http_error_404(self, *args): + self.reset_retry_count() + return None + + authhandler_class = OscHTTPBasicAuthHandler + elif sys.version_info >= (2, 6, 5) and sys.version_info < (2, 6, 6): + # workaround for broken urllib2 in python 2.6.5: wrong credentials + # lead to an infinite recursion + class OscHTTPBasicAuthHandler(HTTPBasicAuthHandler): + def retry_http_basic_auth(self, host, req, realm): + # don't retry if auth failed + if req.get_header(self.auth_header, None) is not None: + return None + return HTTPBasicAuthHandler.retry_http_basic_auth(self, host, req, realm) + + authhandler_class = OscHTTPBasicAuthHandler + + options = config['api_host_options'][apiurl] + # with None as first argument, it will always use this username/password + # combination for urls for which arg2 (apisrv) is a super-url + authhandler = authhandler_class( \ + HTTPPasswordMgrWithDefaultRealm()) + authhandler.add_password(None, apiurl, options['user'], options['pass']) + + if options['sslcertck']: + try: + from . import oscssl + from M2Crypto import m2urllib2 + except ImportError as e: + print(e) + raise NoSecureSSLError('M2Crypto is needed to access %s in a secure way.\nPlease install python-m2crypto.' % apiurl) + + cafile = options.get('cafile', None) + capath = options.get('capath', None) + if not cafile and not capath: + for i in ['/etc/pki/tls/cert.pem', '/etc/ssl/certs']: + if os.path.isfile(i): + cafile = i + break + elif os.path.isdir(i): + capath = i + break + if not cafile and not capath: + raise oscerr.OscIOError(None, 'No CA certificates found') + ctx = oscssl.mySSLContext() + if ctx.load_verify_locations(capath=capath, cafile=cafile) != 1: + raise oscerr.OscIOError(None, 'No CA certificates found') + opener = m2urllib2.build_opener(ctx, oscssl.myHTTPSHandler(ssl_context=ctx, appname='osc'), HTTPCookieProcessor(cookiejar), authhandler, proxyhandler) + else: + handlers = [HTTPCookieProcessor(cookiejar), authhandler, proxyhandler] + try: + # disable ssl cert check in python >= 2.7.9 + ctx = ssl._create_unverified_context() + handlers.append(HTTPSHandler(context=ctx)) + except AttributeError: + pass + print("WARNING: SSL certificate checks disabled. Connection is insecure!\n", file=sys.stderr) + opener = build_opener(*handlers) + opener.addheaders = [('User-agent', 'osc/%s' % __version__)] + _build_opener.last_opener = (apiurl, opener) + return opener + + +def init_basicauth(config): + """initialize urllib2 with the credentials for Basic Authentication""" + + def filterhdrs(meth, ishdr, *hdrs): + # this is so ugly but httplib doesn't use + # a logger object or such + def new_method(self, *args, **kwargs): + # check if this is a recursive call (note: we do not + # have to care about thread safety) + is_rec_call = getattr(self, '_orig_stdout', None) is not None + try: + if not is_rec_call: + self._orig_stdout = sys.stdout + sys.stdout = StringIO() + meth(self, *args, **kwargs) + hdr = sys.stdout.getvalue() + finally: + # restore original stdout + if not is_rec_call: + sys.stdout = self._orig_stdout + del self._orig_stdout + for i in hdrs: + if ishdr: + hdr = re.sub(r'%s:[^\\r]*\\r\\n' % i, '', hdr) + else: + hdr = re.sub(i, '', hdr) + sys.stdout.write(hdr) + new_method.__name__ = meth.__name__ + return new_method + + if config['http_debug'] and not config['http_full_debug']: + HTTPConnection.send = filterhdrs(HTTPConnection.send, True, 'Cookie', 'Authorization') + HTTPResponse.begin = filterhdrs(HTTPResponse.begin, False, 'header: Set-Cookie.*\n') + + if sys.version_info < (2, 6): + # HTTPS proxy is not supported in old urllib2. It only leads to an error + # or, at best, a warning. + if 'https_proxy' in os.environ: + del os.environ['https_proxy'] + if 'HTTPS_PROXY' in os.environ: + del os.environ['HTTPS_PROXY'] + + if config['http_debug']: + # brute force + def urllib2_debug_init(self, debuglevel=0): + self._debuglevel = 1 + AbstractHTTPHandler.__init__ = urllib2_debug_init + + cookie_file = os.path.expanduser(config['cookiejar']) + global cookiejar + cookiejar = LWPCookieJar(cookie_file) + try: + cookiejar.load(ignore_discard=True) + except IOError: + try: + fd = os.open(cookie_file, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600) + os.close(fd) + except IOError: + # hmm is any good reason why we should catch the IOError? + #print 'Unable to create cookiejar file: \'%s\'. Using RAM-based cookies.' % cookie_file + cookiejar = CookieJar() + + +def get_configParser(conffile=None, force_read=False): + """ + Returns an ConfigParser() object. After its first invocation the + ConfigParser object is stored in a method attribute and this attribute + is returned unless you pass force_read=True. + """ + conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc') + conffile = os.path.expanduser(conffile) + if 'conffile' not in get_configParser.__dict__: + get_configParser.conffile = conffile + if force_read or 'cp' not in get_configParser.__dict__ or conffile != get_configParser.conffile: + get_configParser.cp = OscConfigParser.OscConfigParser(DEFAULTS) + get_configParser.cp.read(conffile) + get_configParser.conffile = conffile + return get_configParser.cp + + +def write_config(fname, cp): + """write new configfile in a safe way""" + if os.path.exists(fname) and not os.path.isfile(fname): + # only write to a regular file + return + with open(fname + '.new', 'w') as f: + cp.write(f, comments=True) + try: + os.rename(fname + '.new', fname) + os.chmod(fname, 0o600) + except: + if os.path.exists(fname + '.new'): + os.unlink(fname + '.new') + raise + + +def config_set_option(section, opt, val=None, delete=False, update=True, **kwargs): + """ + Sets a config option. If val is not specified the current/default value is + returned. If val is specified, opt is set to val and the new value is returned. + If an option was modified get_config is called with **kwargs unless update is set + to False (override_conffile defaults to config['conffile']). + If val is not specified and delete is True then the option is removed from the + config/reset to the default value. + """ + cp = get_configParser(config['conffile']) + # don't allow "internal" options + general_opts = [i for i in DEFAULTS.keys() if not i in ['user', 'pass', 'passx']] + if section != 'general': + section = config['apiurl_aliases'].get(section, section) + scheme, host, path = \ + parse_apisrv_url(config.get('scheme', 'https'), section) + section = urljoin(scheme, host, path) + + sections = {} + for url in cp.sections(): + if url == 'general': + sections[url] = url + else: + scheme, host, path = \ + parse_apisrv_url(config.get('scheme', 'https'), url) + apiurl = urljoin(scheme, host, path) + sections[apiurl] = url + + section = sections.get(section.rstrip('/'), section) + if not section in cp.sections(): + raise oscerr.ConfigError('unknown section \'%s\'' % section, config['conffile']) + if section == 'general' and not opt in general_opts or \ + section != 'general' and not opt in api_host_options: + raise oscerr.ConfigError('unknown config option \'%s\'' % opt, config['conffile']) + run = False + if val: + cp.set(section, opt, val) + write_config(config['conffile'], cp) + run = True + elif delete and cp.has_option(section, opt): + cp.remove_option(section, opt) + write_config(config['conffile'], cp) + run = True + if run and update: + kw = {'override_conffile': config['conffile'], + 'override_no_keyring': config['use_keyring'], + 'override_no_gnome_keyring': config['gnome_keyring']} + kw.update(kwargs) + get_config(**kw) + if cp.has_option(section, opt): + return (opt, cp.get(section, opt, raw=True)) + return (opt, None) + +def passx_decode(passx): + """decode the obfuscated password back to plain text password""" + return bz2.decompress(base64.b64decode(passx.encode("ascii"))).decode("ascii") + +def passx_encode(passwd): + """encode plain text password to obfuscated form""" + return base64.b64encode(bz2.compress(passwd.encode('ascii'))).decode("ascii") + +def write_initial_config(conffile, entries, custom_template=''): + """ + write osc's intial configuration file. entries is a dict which contains values + for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ). + custom_template is an optional configuration template. + """ + conf_template = custom_template or new_conf_template + config = DEFAULTS.copy() + config.update(entries) + # at this point use_keyring and gnome_keyring are str objects + if config['use_keyring'] == '1' and GENERIC_KEYRING: + protocol, host, path = \ + parse_apisrv_url(None, config['apiurl']) + keyring.set_password(host, config['user'], config['pass']) + config['pass'] = '' + config['passx'] = '' + elif config['gnome_keyring'] == '1' and GNOME_KEYRING: + protocol, host, path = \ + parse_apisrv_url(None, config['apiurl']) + gnomekeyring.set_network_password_sync( + user=config['user'], + password=config['pass'], + protocol=protocol, + server=host, + object=path) + config['user'] = '' + config['pass'] = '' + config['passx'] = '' + if not config['plaintext_passwd']: + config['pass'] = '' + else: + config['passx'] = passx_encode(config['pass']) + + sio = StringIO(conf_template.strip() % config) + cp = OscConfigParser.OscConfigParser(DEFAULTS) + cp.readfp(sio) + write_config(conffile, cp) + + +def add_section(filename, url, user, passwd): + """ + Add a section to config file for new api url. + """ + global config + cp = get_configParser(filename) + try: + cp.add_section(url) + except OscConfigParser.configparser.DuplicateSectionError: + # Section might have existed, but was empty + pass + if config['use_keyring'] and GENERIC_KEYRING: + protocol, host, path = parse_apisrv_url(None, url) + keyring.set_password(host, user, passwd) + cp.set(url, 'keyring', '1') + cp.set(url, 'user', user) + cp.remove_option(url, 'pass') + cp.remove_option(url, 'passx') + elif config['gnome_keyring'] and GNOME_KEYRING: + protocol, host, path = parse_apisrv_url(None, url) + gnomekeyring.set_network_password_sync( + user=user, + password=passwd, + protocol=protocol, + server=host, + object=path) + cp.set(url, 'keyring', '1') + cp.remove_option(url, 'pass') + cp.remove_option(url, 'passx') + else: + cp.set(url, 'user', user) + if not config['plaintext_passwd']: + cp.remove_option(url, 'pass') + cp.set(url, 'passx', passx_encode(passwd)) + else: + cp.remove_option(url, 'passx') + cp.set(url, 'pass', passwd) + write_config(filename, cp) + + +def get_config(override_conffile=None, + override_apiurl=None, + override_debug=None, + override_http_debug=None, + override_http_full_debug=None, + override_traceback=None, + override_post_mortem=None, + override_no_keyring=None, + override_no_gnome_keyring=None, + override_verbose=None): + """do the actual work (see module documentation)""" + global config + + conffile = override_conffile or os.environ.get('OSC_CONFIG', '~/.oscrc') + conffile = os.path.expanduser(conffile) + + if not os.path.exists(conffile): + raise oscerr.NoConfigfile(conffile, \ + account_not_configured_text % conffile) + + # okay, we made sure that .oscrc exists + + # make sure it is not world readable, it may contain a password. + os.chmod(conffile, 0o600) + + cp = get_configParser(conffile) + + if not cp.has_section('general'): + # FIXME: it might be sufficient to just assume defaults? + msg = config_incomplete_text % conffile + msg += new_conf_template % DEFAULTS + raise oscerr.ConfigError(msg, conffile) + + config = dict(cp.items('general', raw=1)) + config['conffile'] = conffile + + for i in boolean_opts: + try: + config[i] = cp.getboolean('general', i) + except ValueError as e: + raise oscerr.ConfigError('cannot parse \'%s\' setting: ' % i + str(e), conffile) + + config['packagecachedir'] = os.path.expanduser(config['packagecachedir']) + config['exclude_glob'] = config['exclude_glob'].split() + + re_clist = re.compile('[, ]+') + config['extra-pkgs'] = [i.strip() for i in re_clist.split(config['extra-pkgs'].strip()) if i] + + # collect the usernames, passwords and additional options for each api host + api_host_options = {} + + # Regexp to split extra http headers into a dictionary + # the text to be matched looks essentially looks this: + # "Attribute1: value1, Attribute2: value2, ..." + # there may be arbitray leading and intermitting whitespace. + # the following regexp does _not_ support quoted commas within the value. + http_header_regexp = re.compile(r"\s*(.*?)\s*:\s*(.*?)\s*(?:,\s*|\Z)") + + # override values which we were called with + # This needs to be done before processing API sections as it might be already used there + if override_no_keyring: + config['use_keyring'] = False + if override_no_gnome_keyring: + config['gnome_keyring'] = False + + aliases = {} + for url in [x for x in cp.sections() if x != 'general']: + # backward compatiblity + scheme, host, path = parse_apisrv_url(config.get('scheme', 'https'), url) + apiurl = urljoin(scheme, host, path) + user = None + password = None + if config['use_keyring'] and GENERIC_KEYRING: + try: + # Read from keyring lib if available + user = cp.get(url, 'user', raw=True) + password = str(keyring.get_password(host, user)) + except: + # Fallback to file based auth. + pass + elif config['gnome_keyring'] and GNOME_KEYRING: + # Read from gnome keyring if available + try: + gk_data = gnomekeyring.find_network_password_sync(protocol=scheme, server=host, object=path) + if not 'user' in gk_data[0]: + raise oscerr.ConfigError('no user found in keyring', conffile) + user = gk_data[0]['user'] + if 'password' in gk_data[0]: + password = str(gk_data[0]['password']) + else: + # this is most likely an error + print('warning: no password found in keyring', file=sys.stderr) + except gnomekeyring.NoMatchError: + # Fallback to file based auth. + pass + + if not user is None and len(user) == 0: + user = None + print('Warning: blank user in the keyring for the ' \ + 'apiurl %s.\nPlease fix your keyring entry.', file=sys.stderr) + + if user is not None and password is None: + err = ('no password defined for "%s".\nPlease fix your keyring ' + 'entry or gnome-keyring setup.\nAssuming an empty password.' + % url) + print(err, file=sys.stderr) + password = '' + + # Read credentials from config + if user is None: + #FIXME: this could actually be the ideal spot to take defaults + #from the general section. + user = cp.get(url, 'user', raw=True) # need to set raw to prevent '%' expansion + password = cp.get(url, 'pass', raw=True) # especially on password! + try: + passwordx = passx_decode(cp.get(url, 'passx', raw=True)) # especially on password! + except: + passwordx = '' + + if password == None or password == 'your_password': + password = '' + + if user is None or user == '': + raise oscerr.ConfigError('user is blank for %s, please delete or complete the "user=" entry in %s.' % (apiurl, config['conffile']), config['conffile']) + + if config['plaintext_passwd'] and passwordx or not config['plaintext_passwd'] and password: + if config['plaintext_passwd']: + if password != passwordx: + print('%s: rewriting from encoded pass to plain pass' % url, file=sys.stderr) + add_section(conffile, url, user, passwordx) + password = passwordx + else: + if password != passwordx: + print('%s: rewriting from plain pass to encoded pass' % url, file=sys.stderr) + add_section(conffile, url, user, password) + + if not config['plaintext_passwd']: + password = passwordx + + if cp.has_option(url, 'http_headers'): + http_headers = cp.get(url, 'http_headers') + http_headers = http_header_regexp.findall(http_headers) + else: + http_headers = [] + if cp.has_option(url, 'aliases'): + for i in cp.get(url, 'aliases').split(','): + key = i.strip() + if key == '': + continue + if key in aliases: + msg = 'duplicate alias entry: \'%s\' is already used for another apiurl' % key + raise oscerr.ConfigError(msg, conffile) + aliases[key] = url + + api_host_options[apiurl] = {'user': user, + 'pass': password, + 'http_headers': http_headers} + + optional = ('email', 'sslcertck', 'cafile', 'capath') + for key in optional: + if cp.has_option(url, key): + if key == 'sslcertck': + api_host_options[apiurl][key] = cp.getboolean(url, key) + else: + api_host_options[apiurl][key] = cp.get(url, key) + if cp.has_option(url, 'build-root', proper=True): + api_host_options[apiurl]['build-root'] = cp.get(url, 'build-root', raw=True) + + if not 'sslcertck' in api_host_options[apiurl]: + api_host_options[apiurl]['sslcertck'] = True + + if scheme == 'http': + api_host_options[apiurl]['sslcertck'] = False + + if cp.has_option(url, 'trusted_prj'): + api_host_options[apiurl]['trusted_prj'] = cp.get(url, 'trusted_prj').split(' ') + else: + api_host_options[apiurl]['trusted_prj'] = [] + + # add the auth data we collected to the config dict + config['api_host_options'] = api_host_options + config['apiurl_aliases'] = aliases + + apiurl = aliases.get(config['apiurl'], config['apiurl']) + config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl)) + # backward compatibility + if 'apisrv' in config: + apisrv = config['apisrv'].lstrip('http://') + apisrv = apisrv.lstrip('https://') + scheme = config.get('scheme', 'https') + config['apiurl'] = urljoin(scheme, apisrv) + if 'apisrc' in config or 'scheme' in config: + print('Warning: Use of the \'scheme\' or \'apisrv\' in ~/.oscrc is deprecated!\n' \ + 'Warning: See README for migration details.', file=sys.stderr) + if 'build_platform' in config: + print('Warning: Use of \'build_platform\' config option is deprecated! (use \'build_repository\' instead)', file=sys.stderr) + config['build_repository'] = config['build_platform'] + + config['verbose'] = int(config['verbose']) + # override values which we were called with + if override_verbose: + config['verbose'] = override_verbose + 1 + + if override_debug: + config['debug'] = override_debug + if override_http_debug: + config['http_debug'] = override_http_debug + if override_http_full_debug: + config['http_debug'] = override_http_full_debug or config['http_debug'] + config['http_full_debug'] = override_http_full_debug + if override_traceback: + config['traceback'] = override_traceback + if override_post_mortem: + config['post_mortem'] = override_post_mortem + if override_apiurl: + apiurl = aliases.get(override_apiurl, override_apiurl) + # check if apiurl is a valid url + config['apiurl'] = urljoin(*parse_apisrv_url(None, apiurl)) + + # XXX unless config['user'] goes away (and is replaced with a handy function, or + # config becomes an object, even better), set the global 'user' here as well, + # provided that there _are_ credentials for the chosen apiurl: + try: + config['user'] = get_apiurl_usr(config['apiurl']) + except oscerr.ConfigMissingApiurl as e: + e.msg = config_missing_apiurl_text % config['apiurl'] + e.file = conffile + raise e + + # finally, initialize urllib2 for to use the credentials for Basic Authentication + init_basicauth(config) + + +# vim: sw=4 et diff --git a/osc/core.py b/osc/core.py new file mode 100644 index 0000000..3ee2c56 --- /dev/null +++ b/osc/core.py @@ -0,0 +1,7281 @@ +# Copyright (C) 2006 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or version 3 (at your option). + +from __future__ import print_function + +__version__ = '0.154.0' + +# __store_version__ is to be incremented when the format of the working copy +# "store" changes in an incompatible way. Please add any needed migration +# functionality to check_store_version(). +__store_version__ = '1.0' + +import locale +import os +import os.path +import sys +import shutil +import subprocess +import re +import socket +import errno +import shlex + +try: + from urllib.parse import urlsplit, urlunsplit, urlparse, quote_plus, urlencode, unquote + from urllib.error import HTTPError + from urllib.request import pathname2url, install_opener, urlopen + from urllib.request import Request as URLRequest + from io import StringIO +except ImportError: + #python 2.x + from urlparse import urlsplit, urlunsplit, urlparse + from urllib import pathname2url, quote_plus, urlencode, unquote + from urllib2 import HTTPError, install_opener, urlopen + from urllib2 import Request as URLRequest + from cStringIO import StringIO + + +try: + from xml.etree import cElementTree as ET +except ImportError: + import cElementTree as ET + +from . import oscerr +from . import conf + +try: + # python 2.6 and python 2.7 + unicode + ET_ENCODING = "utf-8" + # python 2.6 does not have bytes and python 2.7 reimplements it as alias to + # str, but in incompatible way as it does not accept the same arguments + bytes = lambda x, *args: x +except: + #python3 does not have unicode, so lets reimplement it + #as void function as it already gets unicode strings + unicode = lambda x, *args: x + ET_ENCODING = "unicode" + +DISTURL_RE = re.compile(r"^(?P<bs>.*)://(?P<apiurl>.*?)/(?P<project>.*?)/(?P<repository>.*?)/(?P<revision>.*)-(?P<source>.*)$") +BUILDLOGURL_RE = re.compile(r"^(?P<apiurl>https?://.*?)/build/(?P<project>.*?)/(?P<repository>.*?)/(?P<arch>.*?)/(?P<package>.*?)/_log$") +BUFSIZE = 1024*1024 +store = '.osc' + +new_project_templ = """\ +<project name="%(name)s"> + + <title></title> <!-- Short title of NewProject --> + <description></description> + <!-- This is for a longer description of the purpose of the project --> + + <person role="maintainer" userid="%(user)s" /> + <person role="bugowner" userid="%(user)s" /> +<!-- remove this block to publish your packages on the mirrors --> + <publish> + <disable /> + </publish> + <build> + <enable /> + </build> + <debuginfo> + <enable /> + </debuginfo> + +<!-- remove this comment to enable one or more build targets + + <repository name="openSUSE_Factory"> + <path project="openSUSE:Factory" repository="snapshot" /> + <arch>x86_64</arch> + <arch>i586</arch> + </repository> + <repository name="openSUSE_13.2"> + <path project="openSUSE:13.2" repository="standard"/> + <arch>x86_64</arch> + <arch>i586</arch> + </repository> + <repository name="openSUSE_13.1"> + <path project="openSUSE:13.1" repository="standard"/> + <arch>x86_64</arch> + <arch>i586</arch> + </repository> + <repository name="Fedora_21"> + <path project="Fedora:21" repository="standard" /> + <arch>x86_64</arch> + <arch>i586</arch> + </repository> + <repository name="SLE_12"> + <path project="SUSE:SLE-12:GA" repository="standard" /> + <arch>x86_64</arch> + <arch>i586</arch> + </repository> +--> + +</project> +""" + +new_package_templ = """\ +<package name="%(name)s"> + + <title></title> <!-- Title of package --> + + <description></description> <!-- for long description --> + +<!-- following roles are inherited from the parent project + <person role="maintainer" userid="%(user)s"/> + <person role="bugowner" userid="%(user)s"/> +--> +<!-- + <url>PUT_UPSTREAM_URL_HERE</url> +--> + +<!-- + use one of the examples below to disable building of this package + on a certain architecture, in a certain repository, + or a combination thereof: + + <disable arch="x86_64"/> + <disable repository="SUSE_SLE-10"/> + <disable repository="SUSE_SLE-10" arch="x86_64"/> + + Possible sections where you can use the tags above: + <build> + </build> + <debuginfo> + </debuginfo> + <publish> + </publish> + <useforbuild> + </useforbuild> + + Please have a look at: + http://en.opensuse.org/Restricted_formats + Packages containing formats listed there are NOT allowed to + be packaged in the openSUSE Buildservice and will be deleted! + +--> + +</package> +""" + +new_attribute_templ = """\ +<attributes> + <attribute namespace="" name=""> + <value><value> + </attribute> +</attributes> +""" + +new_user_template = """\ +<person> + <login>%(user)s</login> + <email>PUT_EMAIL_ADDRESS_HERE</email> + <realname>PUT_REAL_NAME_HERE</realname> + <watchlist> + <project name="home:%(user)s"/> + </watchlist> +</person> +""" + +info_templ = """\ +Project name: %s +Package name: %s +Path: %s +API URL: %s +Source URL: %s +srcmd5: %s +Revision: %s +Link info: %s +""" + +new_pattern_template = """\ +<!-- See https://github.com/openSUSE/libzypp/tree/master/zypp/parser/yum/schema/patterns.rng --> + +<!-- +<pattern xmlns="http://novell.com/package/metadata/suse/pattern" + xmlns:rpm="http://linux.duke.edu/metadata/rpm"> + <name></name> + <summary></summary> + <description></description> + <uservisible/> + <category lang="en"></category> + <rpm:requires> + <rpm:entry name="must-have-package"/> + </rpm:requires> + <rpm:recommends> + <rpm:entry name="package"/> + </rpm:recommends> + <rpm:suggests> + <rpm:entry name="anotherpackage"/> + </rpm:suggests> +</pattern> +--> +""" + +buildstatus_symbols = {'succeeded': '.', + 'disabled': ' ', + 'expansion error': 'U', # obsolete with OBS 2.0 + 'unresolvable': 'U', + 'failed': 'F', + 'broken': 'B', + 'blocked': 'b', + 'building': '%', + 'finished': 'f', + 'scheduled': 's', + 'locked': 'L', + 'excluded': 'x', + 'dispatching': 'd', + 'signing': 'S', +} + + +# os.path.samefile is available only under Unix +def os_path_samefile(path1, path2): + try: + return os.path.samefile(path1, path2) + except: + return os.path.realpath(path1) == os.path.realpath(path2) + +class File: + """represent a file, including its metadata""" + def __init__(self, name, md5, size, mtime, skipped=False): + self.name = name + self.md5 = md5 + self.size = size + self.mtime = mtime + self.skipped = skipped + def __repr__(self): + return self.name + def __str__(self): + return self.name + + +class Serviceinfo: + """Source service content + """ + def __init__(self): + """creates an empty serviceinfo instance""" + self.services = [] + self.project = None + self.package = None + + def read(self, serviceinfo_node, append=False): + """read in the source services <services> element passed as + elementtree node. + """ + def error(msg, xml): + data = 'invalid service format:\n%s' % ET.tostring(xml, encoding=ET_ENCODING) + raise ValueError("%s\n\n%s" % (data, msg)) + + if serviceinfo_node is None: + return + if not append: + self.services = [] + services = serviceinfo_node.findall('service') + + for service in services: + name = service.get('name') + if len(name) < 3 or '/' in name: + error("invalid service name: %s" % name, service) + mode = service.get('mode', '') + data = { 'name' : name, 'mode' : mode } + command = [ name ] + for param in service.findall('param'): + option = param.get('name') + if option is None: + error("%s: a parameter requires a name" % name, service) + value = '' + if param.text: + value = param.text + command.append('--' + option) + # hmm is this reasonable or do we want to allow real + # options (e.g., "--force" (without an argument)) as well? + command.append(value) + data['command'] = command + self.services.append(data) + + def getProjectGlobalServices(self, apiurl, project, package): + # get all project wide services in one file, we don't store it yet + u = makeurl(apiurl, ['source', project, package], query='cmd=getprojectservices') + try: + f = http_POST(u) + root = ET.parse(f).getroot() + self.read(root, True) + self.project = project + self.package = package + except HTTPError as e: + if e.code != 403 and e.code != 400: + raise e + + def addVerifyFile(self, serviceinfo_node, filename): + import hashlib + + f = open(filename, 'r') + digest = hashlib.sha256(f.read()).hexdigest() + f.close() + + r = serviceinfo_node + s = ET.Element( "service", name="verify_file" ) + ET.SubElement(s, "param", name="file").text = filename + ET.SubElement(s, "param", name="verifier").text = "sha256" + ET.SubElement(s, "param", name="checksum").text = digest + + r.append( s ) + return r + + + def addDownloadUrl(self, serviceinfo_node, url_string): + url = urlparse( url_string ) + protocol = url.scheme + host = url.netloc + path = url.path + + r = serviceinfo_node + s = ET.Element( "service", name="download_url" ) + ET.SubElement(s, "param", name="protocol").text = protocol + ET.SubElement(s, "param", name="host").text = host + ET.SubElement(s, "param", name="path").text = path + + r.append( s ) + return r + + def addSetVersion(self, serviceinfo_node): + r = serviceinfo_node + s = ET.Element( "service", name="set_version", mode="buildtime" ) + r.append( s ) + return r + + def addGitUrl(self, serviceinfo_node, url_string): + r = serviceinfo_node + s = ET.Element( "service", name="obs_scm" ) + ET.SubElement(s, "param", name="url").text = url_string + ET.SubElement(s, "param", name="scm").text = "git" + r.append( s ) + return r + + def addTarUp(self, serviceinfo_node): + r = serviceinfo_node + s = ET.Element( "service", name="tar", mode="buildtime" ) + r.append( s ) + return r + + def addRecompressTar(self, serviceinfo_node): + r = serviceinfo_node + s = ET.Element( "service", name="recompress", mode="buildtime" ) + ET.SubElement(s, "param", name="file").text = "*.tar" + ET.SubElement(s, "param", name="compression").text = "xz" + r.append( s ) + return r + + def execute(self, dir, callmode = None, singleservice = None, verbose = None): + import tempfile + + # cleanup existing generated files + for filename in os.listdir(dir): + if filename.startswith('_service:') or filename.startswith('_service_'): + ent = os.path.join(dir, filename) + if os.path.isdir(ent): + shutil.rmtree(ent) + else: + os.unlink(ent) + + allservices = self.services or [] + if singleservice and not singleservice in allservices: + # set array to the manual specified singleservice, if it is not part of _service file + data = { 'name' : singleservice, 'command' : [ singleservice ], 'mode' : '' } + allservices = [data] + + # services can detect that they run via osc this way + os.putenv("OSC_VERSION", get_osc_version()) + + # set environment when using OBS 2.3 or later + if self.project != None: + os.putenv("OBS_SERVICE_PROJECT", self.project) + os.putenv("OBS_SERVICE_PACKAGE", self.package) + + # recreate files + ret = 0 + for service in allservices: + if singleservice and service['name'] != singleservice: + continue + if service['mode'] == "buildtime": + continue + if service['mode'] == "serveronly" and callmode != "disabled": + continue + if service['mode'] == "disabled" and callmode != "disabled": + continue + if service['mode'] != "disabled" and callmode == "disabled": + continue + if service['mode'] != "trylocal" and service['mode'] != "localonly" and callmode == "trylocal": + continue + temp_dir = None + try: + temp_dir = tempfile.mkdtemp(dir=dir, suffix='.%s.service' % service['name']) + cmd = service['command'] + if not os.path.exists("/usr/lib/obs/service/"+cmd[0]): + raise oscerr.PackageNotInstalled("obs-service-%s"%cmd[0]) + cmd[0] = "/usr/lib/obs/service/"+cmd[0] + cmd = cmd + [ "--outdir", temp_dir ] + if conf.config['verbose'] > 1 or verbose or conf.config['debug']: + print("Run source service:", ' '.join(cmd)) + r = run_external(*cmd) + + if r != 0: + print("Aborting: service call failed: ", ' '.join(cmd)) + # FIXME: addDownloadUrlService calls si.execute after + # updating _services. + return r + + if service['mode'] == "disabled" or service['mode'] == "trylocal" or service['mode'] == "localonly" or callmode == "local" or callmode == "trylocal": + for filename in os.listdir(temp_dir): + os.rename(os.path.join(temp_dir, filename), os.path.join(dir, filename)) + else: + name = service['name'] + for filename in os.listdir(temp_dir): + os.rename(os.path.join(temp_dir, filename), os.path.join(dir, "_service:"+name+":"+filename)) + finally: + if temp_dir is not None: + shutil.rmtree(temp_dir) + + return 0 + +class Linkinfo: + """linkinfo metadata (which is part of the xml representing a directory + """ + def __init__(self): + """creates an empty linkinfo instance""" + self.project = None + self.package = None + self.xsrcmd5 = None + self.lsrcmd5 = None + self.srcmd5 = None + self.error = None + self.rev = None + self.baserev = None + + def read(self, linkinfo_node): + """read in the linkinfo metadata from the <linkinfo> element passed as + elementtree node. + If the passed element is None, the method does nothing. + """ + if linkinfo_node == None: + return + self.project = linkinfo_node.get('project') + self.package = linkinfo_node.get('package') + self.xsrcmd5 = linkinfo_node.get('xsrcmd5') + self.lsrcmd5 = linkinfo_node.get('lsrcmd5') + self.srcmd5 = linkinfo_node.get('srcmd5') + self.error = linkinfo_node.get('error') + self.rev = linkinfo_node.get('rev') + self.baserev = linkinfo_node.get('baserev') + + def islink(self): + """returns True if the linkinfo is not empty, otherwise False""" + if self.xsrcmd5 or self.lsrcmd5: + return True + return False + + def isexpanded(self): + """returns True if the package is an expanded link""" + if self.lsrcmd5 and not self.xsrcmd5: + return True + return False + + def haserror(self): + """returns True if the link is in error state (could not be applied)""" + if self.error: + return True + return False + + def __str__(self): + """return an informatory string representation""" + if self.islink() and not self.isexpanded(): + return 'project %s, package %s, xsrcmd5 %s, rev %s' \ + % (self.project, self.package, self.xsrcmd5, self.rev) + elif self.islink() and self.isexpanded(): + if self.haserror(): + return 'broken link to project %s, package %s, srcmd5 %s, lsrcmd5 %s: %s' \ + % (self.project, self.package, self.srcmd5, self.lsrcmd5, self.error) + else: + return 'expanded link to project %s, package %s, srcmd5 %s, lsrcmd5 %s' \ + % (self.project, self.package, self.srcmd5, self.lsrcmd5) + else: + return 'None' + +class DirectoryServiceinfo: + def __init__(self): + self.code = None + self.xsrcmd5 = None + self.lsrcmd5 = None + self.error = '' + + def read(self, serviceinfo_node): + if serviceinfo_node is None: + return + self.code = serviceinfo_node.get('code') + self.xsrcmd5 = serviceinfo_node.get('xsrcmd5') + self.lsrcmd5 = serviceinfo_node.get('lsrcmd5') + self.error = serviceinfo_node.find('error') + if self.error: + self.error = self.error.text + + def isexpanded(self): + """ + Returns true, if the directory contains the "expanded"/generated service files + """ + return self.lsrcmd5 is not None and self.xsrcmd5 is None + + def haserror(self): + return self.error is not None + +# http://effbot.org/zone/element-lib.htm#prettyprint +def xmlindent(elem, level=0): + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + for e in elem: + xmlindent(e, level+1) + if not e.tail or not e.tail.strip(): + e.tail = i + " " + if not e.tail or not e.tail.strip(): + e.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + +class Project: + """ + Represent a checked out project directory, holding packages. + + :Attributes: + ``dir`` + The directory path containing the project. + + ``name`` + The name of the project. + + ``apiurl`` + The endpoint URL of the API server. + + ``pacs_available`` + List of names of packages available server-side. + This is only populated if ``getPackageList`` is set + to ``True`` in the constructor. + + ``pacs_have`` + List of names of packages which exist server-side + and exist in the local project working copy (if + 'do_package_tracking' is disabled). + If 'do_package_tracking' is enabled it represents the + list names of packages which are tracked in the project + working copy (that is it might contain packages which + exist on the server as well as packages which do not + exist on the server (for instance if the local package + was added or if the package was removed on the server-side)). + + ``pacs_excluded`` + List of names of packages in the local project directory + which are excluded by the `exclude_glob` configuration + variable. Only set if `do_package_tracking` is enabled. + + ``pacs_unvers`` + List of names of packages in the local project directory + which are not tracked. Only set if `do_package_tracking` + is enabled. + + ``pacs_broken`` + List of names of packages which are tracked but do not + exist in the local project working copy. Only set if + `do_package_tracking` is enabled. + + ``pacs_missing`` + List of names of packages which exist server-side but + are not expected to exist in the local project directory. + """ + + REQ_STOREFILES = ('_project', '_apiurl') + + def __init__(self, dir, getPackageList=True, progress_obj=None, wc_check=True): + """ + Constructor. + + :Parameters: + `dir` : str + The directory path containing the checked out project. + + `getPackageList` : bool + Set to `False` if you want to skip retrieval from the + server of the list of packages in the project . + + `wc_check` : bool + """ + import fnmatch + self.dir = dir + self.absdir = os.path.abspath(dir) + self.progress_obj = progress_obj + + self.name = store_read_project(self.dir) + self.apiurl = store_read_apiurl(self.dir, defaulturl=not wc_check) + + dirty_files = [] + if wc_check: + dirty_files = self.wc_check() + if dirty_files: + msg = 'Your working copy \'%s\' is in an inconsistent state.\n' \ + 'Please run \'osc repairwc %s\' and check the state\n' \ + 'of the working copy afterwards (via \'osc status %s\')' % (self.dir, self.dir, self.dir) + raise oscerr.WorkingCopyInconsistent(self.name, None, dirty_files, msg) + + if getPackageList: + self.pacs_available = meta_get_packagelist(self.apiurl, self.name) + else: + self.pacs_available = [] + + if conf.config['do_package_tracking']: + self.pac_root = self.read_packages().getroot() + self.pacs_have = [ pac.get('name') for pac in self.pac_root.findall('package') ] + self.pacs_excluded = [ i for i in os.listdir(self.dir) + for j in conf.config['exclude_glob'] + if fnmatch.fnmatch(i, j) ] + self.pacs_unvers = [ i for i in os.listdir(self.dir) if i not in self.pacs_have and i not in self.pacs_excluded ] + # store all broken packages (e.g. packages which where removed by a non-osc cmd) + # in the self.pacs_broken list + self.pacs_broken = [] + for p in self.pacs_have: + if not os.path.isdir(os.path.join(self.absdir, p)): + # all states will be replaced with the '!'-state + # (except it is already marked as deleted ('D'-state)) + self.pacs_broken.append(p) + else: + self.pacs_have = [ i for i in os.listdir(self.dir) if i in self.pacs_available ] + + self.pacs_missing = [ i for i in self.pacs_available if i not in self.pacs_have ] + + def wc_check(self): + global store + dirty_files = [] + req_storefiles = Project.REQ_STOREFILES + if conf.config['do_package_tracking']: + req_storefiles += ('_packages',) + for fname in req_storefiles: + if not os.path.exists(os.path.join(self.absdir, store, fname)): + dirty_files.append(fname) + return dirty_files + + def wc_repair(self, apiurl=None): + global store + if not os.path.exists(os.path.join(self.dir, store, '_apiurl')) or apiurl: + if apiurl is None: + msg = 'cannot repair wc: the \'_apiurl\' file is missing but ' \ + 'no \'apiurl\' was passed to wc_repair' + # hmm should we raise oscerr.WrongArgs? + raise oscerr.WorkingCopyInconsistent(self.prjname, self.name, [], msg) + # sanity check + conf.parse_apisrv_url(None, apiurl) + store_write_apiurl(self.dir, apiurl) + self.apiurl = store_read_apiurl(self.dir, defaulturl=False) + + def checkout_missing_pacs(self, expand_link=False): + for pac in self.pacs_missing: + + if conf.config['do_package_tracking'] and pac in self.pacs_unvers: + # pac is not under version control but a local file/dir exists + msg = 'can\'t add package \'%s\': Object already exists' % pac + raise oscerr.PackageExists(self.name, pac, msg) + else: + print('checking out new package %s' % pac) + checkout_package(self.apiurl, self.name, pac, \ + pathname=getTransActPath(os.path.join(self.dir, pac)), \ + prj_obj=self, prj_dir=self.dir, expand_link=expand_link, progress_obj=self.progress_obj) + + def status(self, pac): + exists = os.path.exists(os.path.join(self.absdir, pac)) + st = self.get_state(pac) + if st is None and exists: + return '?' + elif st is None: + raise oscerr.OscIOError(None, 'osc: \'%s\' is not under version control' % pac) + elif st in ('A', ' ') and not exists: + return '!' + elif st == 'D' and not exists: + return 'D' + else: + return st + + def get_status(self, *exclude_states): + res = [] + for pac in self.pacs_have: + st = self.status(pac) + if not st in exclude_states: + res.append((st, pac)) + if not '?' in exclude_states: + res.extend([('?', pac) for pac in self.pacs_unvers]) + return res + + def get_pacobj(self, pac, *pac_args, **pac_kwargs): + try: + st = self.status(pac) + if st in ('?', '!') or st == 'D' and not os.path.exists(os.path.join(self.dir, pac)): + return None + return Package(os.path.join(self.dir, pac), *pac_args, **pac_kwargs) + except oscerr.OscIOError: + return None + + def set_state(self, pac, state): + node = self.get_package_node(pac) + if node == None: + self.new_package_entry(pac, state) + else: + node.set('state', state) + + def get_package_node(self, pac): + for node in self.pac_root.findall('package'): + if pac == node.get('name'): + return node + return None + + def del_package_node(self, pac): + for node in self.pac_root.findall('package'): + if pac == node.get('name'): + self.pac_root.remove(node) + + def get_state(self, pac): + node = self.get_package_node(pac) + if node != None: + return node.get('state') + else: + return None + + def new_package_entry(self, name, state): + ET.SubElement(self.pac_root, 'package', name=name, state=state) + + def read_packages(self): + """ + Returns an ``xml.etree.cElementTree`` object representing the + parsed contents of the project's ``.osc/_packages`` XML file. + """ + global store + + packages_file = os.path.join(self.absdir, store, '_packages') + if os.path.isfile(packages_file) and os.path.getsize(packages_file): + try: + result = ET.parse(packages_file) + except: + msg = 'Cannot read package file \'%s\'. ' % packages_file + msg += 'You can try to remove it and then run osc repairwc.' + raise oscerr.OscIOError(None, msg) + return result + else: + # scan project for existing packages and migrate them + cur_pacs = [] + for data in os.listdir(self.dir): + pac_dir = os.path.join(self.absdir, data) + # we cannot use self.pacs_available because we cannot guarantee that the package list + # was fetched from the server + if data in meta_get_packagelist(self.apiurl, self.name) and is_package_dir(pac_dir) \ + and Package(pac_dir).name == data: + cur_pacs.append(ET.Element('package', name=data, state=' ')) + store_write_initial_packages(self.absdir, self.name, cur_pacs) + return ET.parse(os.path.join(self.absdir, store, '_packages')) + + def write_packages(self): + xmlindent(self.pac_root) + store_write_string(self.absdir, '_packages', ET.tostring(self.pac_root, encoding=ET_ENCODING)) + + def addPackage(self, pac): + import fnmatch + for i in conf.config['exclude_glob']: + if fnmatch.fnmatch(pac, i): + msg = 'invalid package name: \'%s\' (see \'exclude_glob\' config option)' % pac + raise oscerr.OscIOError(None, msg) + state = self.get_state(pac) + if state == None or state == 'D': + self.new_package_entry(pac, 'A') + self.write_packages() + # sometimes the new pac doesn't exist in the list because + # it would take too much time to update all data structs regularly + if pac in self.pacs_unvers: + self.pacs_unvers.remove(pac) + else: + raise oscerr.PackageExists(self.name, pac, 'package \'%s\' is already under version control' % pac) + + def delPackage(self, pac, force = False): + state = self.get_state(pac.name) + can_delete = True + if state == ' ' or state == 'D': + del_files = [] + for filename in pac.filenamelist + pac.filenamelist_unvers: + filestate = pac.status(filename) + if filestate == 'M' or filestate == 'C' or \ + filestate == 'A' or filestate == '?': + can_delete = False + else: + del_files.append(filename) + if can_delete or force: + for filename in del_files: + pac.delete_localfile(filename) + if pac.status(filename) != '?': + # this is not really necessary + pac.put_on_deletelist(filename) + print(statfrmt('D', getTransActPath(os.path.join(pac.dir, filename)))) + print(statfrmt('D', getTransActPath(os.path.join(pac.dir, os.pardir, pac.name)))) + pac.write_deletelist() + self.set_state(pac.name, 'D') + self.write_packages() + else: + print('package \'%s\' has local modifications (see osc st for details)' % pac.name) + elif state == 'A': + if force: + delete_dir(pac.absdir) + self.del_package_node(pac.name) + self.write_packages() + print(statfrmt('D', pac.name)) + else: + print('package \'%s\' has local modifications (see osc st for details)' % pac.name) + elif state == None: + print('package is not under version control') + else: + print('unsupported state') + + def update(self, pacs = (), expand_link=False, unexpand_link=False, service_files=False): + if len(pacs): + for pac in pacs: + Package(os.path.join(self.dir, pac), progress_obj=self.progress_obj).update() + else: + # we need to make sure that the _packages file will be written (even if an exception + # occurs) + try: + # update complete project + # packages which no longer exists upstream + upstream_del = [ pac for pac in self.pacs_have if not pac in self.pacs_available and self.get_state(pac) != 'A'] + sinfo_pacs = [pac for pac in self.pacs_have if self.get_state(pac) in (' ', 'D') and not pac in self.pacs_broken] + sinfos = get_project_sourceinfo(self.apiurl, self.name, True, *sinfo_pacs) + + for pac in upstream_del: + if self.status(pac) != '!': + p = Package(os.path.join(self.dir, pac)) + self.delPackage(p, force = True) + delete_storedir(p.storedir) + try: + os.rmdir(pac) + except: + pass + self.pac_root.remove(self.get_package_node(pac)) + self.pacs_have.remove(pac) + + for pac in self.pacs_have: + state = self.get_state(pac) + if pac in self.pacs_broken: + if self.get_state(pac) != 'A': + checkout_package(self.apiurl, self.name, pac, + pathname=getTransActPath(os.path.join(self.dir, pac)), prj_obj=self, + prj_dir=self.dir, expand_link=not unexpand_link, progress_obj=self.progress_obj) + elif state == ' ': + # do a simple update + p = Package(os.path.join(self.dir, pac), progress_obj=self.progress_obj) + rev = None + needs_update = True + if expand_link and p.islink() and not p.isexpanded(): + if p.haslinkerror(): + try: + rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev) + except: + rev = show_upstream_xsrcmd5(p.apiurl, p.prjname, p.name, revision=p.rev, linkrev="base") + p.mark_frozen() + else: + rev = p.linkinfo.xsrcmd5 + print('Expanding to rev', rev) + elif unexpand_link and p.islink() and p.isexpanded(): + rev = p.linkinfo.lsrcmd5 + print('Unexpanding to rev', rev) + elif p.islink() and p.isexpanded(): + needs_update = p.update_needed(sinfos[p.name]) + if needs_update: + rev = p.latest_rev() + elif p.hasserviceinfo() and p.serviceinfo.isexpanded() and not service_files: + # FIXME: currently, do_update does not propagate the --server-side-source-service-files + # option to this method. Consequence: an expanded service is always unexpanded during + # an update (TODO: discuss if this is a reasonable behavior (at least this the default + # behavior for a while)) + needs_update = True + else: + needs_update = p.update_needed(sinfos[p.name]) + print('Updating %s' % p.name) + if needs_update: + p.update(rev, service_files) + else: + print('At revision %s.' % p.rev) + if unexpand_link: + p.unmark_frozen() + elif state == 'D': + # pac exists (the non-existent pac case was handled in the first if block) + p = Package(os.path.join(self.dir, pac), progress_obj=self.progress_obj) + if p.update_needed(sinfos[p.name]): + p.update() + elif state == 'A' and pac in self.pacs_available: + # file/dir called pac already exists and is under version control + msg = 'can\'t add package \'%s\': Object already exists' % pac + raise oscerr.PackageExists(self.name, pac, msg) + elif state == 'A': + # do nothing + pass + else: + print('unexpected state.. package \'%s\'' % pac) + + self.checkout_missing_pacs(expand_link=not unexpand_link) + finally: + self.write_packages() + + def commit(self, pacs = (), msg = '', files = {}, verbose = False, skip_local_service_run = False, can_branch=False, force=False): + if len(pacs): + try: + for pac in pacs: + todo = [] + if pac in files: + todo = files[pac] + state = self.get_state(pac) + if state == 'A': + self.commitNewPackage(pac, msg, todo, verbose=verbose, skip_local_service_run=skip_local_service_run) + elif state == 'D': + self.commitDelPackage(pac) + elif state == ' ': + # display the correct dir when sending the changes + if os_path_samefile(os.path.join(self.dir, pac), os.getcwd()): + p = Package('.') + else: + p = Package(os.path.join(self.dir, pac)) + p.todo = todo + p.commit(msg, verbose=verbose, skip_local_service_run=skip_local_service_run, can_branch=can_branch, force=force) + elif pac in self.pacs_unvers and not is_package_dir(os.path.join(self.dir, pac)): + print('osc: \'%s\' is not under version control' % pac) + elif pac in self.pacs_broken: + print('osc: \'%s\' package not found' % pac) + elif state == None: + self.commitExtPackage(pac, msg, todo, verbose=verbose, skip_local_service_run=skip_local_service_run) + finally: + self.write_packages() + else: + # if we have packages marked as '!' we cannot commit + for pac in self.pacs_broken: + if self.get_state(pac) != 'D': + msg = 'commit failed: package \'%s\' is missing' % pac + raise oscerr.PackageMissing(self.name, pac, msg) + try: + for pac in self.pacs_have: + state = self.get_state(pac) + if state == ' ': + # do a simple commit + Package(os.path.join(self.dir, pac)).commit(msg, verbose=verbose, skip_local_service_run=skip_local_service_run) + elif state == 'D': + self.commitDelPackage(pac) + elif state == 'A': + self.commitNewPackage(pac, msg, verbose=verbose, skip_local_service_run=skip_local_service_run) + finally: + self.write_packages() + + def commitNewPackage(self, pac, msg = '', files = [], verbose = False, skip_local_service_run = False): + """creates and commits a new package if it does not exist on the server""" + if pac in self.pacs_available: + print('package \'%s\' already exists' % pac) + else: + user = conf.get_apiurl_usr(self.apiurl) + edit_meta(metatype='pkg', + path_args=(quote_plus(self.name), quote_plus(pac)), + template_args=({ + 'name': pac, + 'user': user}), + apiurl=self.apiurl) + # display the correct dir when sending the changes + olddir = os.getcwd() + if os_path_samefile(os.path.join(self.dir, pac), os.curdir): + os.chdir(os.pardir) + p = Package(pac) + else: + p = Package(os.path.join(self.dir, pac)) + p.todo = files + print(statfrmt('Sending', os.path.normpath(p.dir))) + p.commit(msg=msg, verbose=verbose, skip_local_service_run=skip_local_service_run) + self.set_state(pac, ' ') + os.chdir(olddir) + + def commitDelPackage(self, pac): + """deletes a package on the server and in the working copy""" + try: + # display the correct dir when sending the changes + if os_path_samefile(os.path.join(self.dir, pac), os.curdir): + pac_dir = pac + else: + pac_dir = os.path.join(self.dir, pac) + p = Package(os.path.join(self.dir, pac)) + #print statfrmt('Deleting', os.path.normpath(os.path.join(p.dir, os.pardir, pac))) + delete_storedir(p.storedir) + try: + os.rmdir(p.dir) + except: + pass + except OSError: + pac_dir = os.path.join(self.dir, pac) + #print statfrmt('Deleting', getTransActPath(os.path.join(self.dir, pac))) + print(statfrmt('Deleting', getTransActPath(pac_dir))) + delete_package(self.apiurl, self.name, pac) + self.del_package_node(pac) + + def commitExtPackage(self, pac, msg, files = [], verbose=False, skip_local_service_run=False): + """commits a package from an external project""" + if os_path_samefile(os.path.join(self.dir, pac), os.getcwd()): + pac_path = '.' + else: + pac_path = os.path.join(self.dir, pac) + + project = store_read_project(pac_path) + package = store_read_package(pac_path) + apiurl = store_read_apiurl(pac_path, defaulturl=False) + if not meta_exists(metatype='pkg', + path_args=(quote_plus(project), quote_plus(package)), + template_args=None, create_new=False, apiurl=apiurl): + user = conf.get_apiurl_usr(self.apiurl) + edit_meta(metatype='pkg', + path_args=(quote_plus(project), quote_plus(package)), + template_args=({'name': pac, 'user': user}), apiurl=apiurl) + p = Package(pac_path) + p.todo = files + p.commit(msg=msg, verbose=verbose, skip_local_service_run=skip_local_service_run) + + def __str__(self): + r = [] + r.append('*****************************************************') + r.append('Project %s (dir=%s, absdir=%s)' % (self.name, self.dir, self.absdir)) + r.append('have pacs:\n%s' % ', '.join(self.pacs_have)) + r.append('missing pacs:\n%s' % ', '.join(self.pacs_missing)) + r.append('*****************************************************') + return '\n'.join(r) + + @staticmethod + def init_project(apiurl, dir, project, package_tracking=True, getPackageList=True, progress_obj=None, wc_check=True): + global store + + if not os.path.exists(dir): + # use makedirs (checkout_no_colon config option might be enabled) + os.makedirs(dir) + elif not os.path.isdir(dir): + raise oscerr.OscIOError(None, 'error: \'%s\' is no directory' % dir) + if os.path.exists(os.path.join(dir, store)): + raise oscerr.OscIOError(None, 'error: \'%s\' is already an initialized osc working copy' % dir) + else: + os.mkdir(os.path.join(dir, store)) + + store_write_project(dir, project) + store_write_apiurl(dir, apiurl) + if package_tracking: + store_write_initial_packages(dir, project, []) + return Project(dir, getPackageList, progress_obj, wc_check) + + +class Package: + """represent a package (its directory) and read/keep/write its metadata""" + + # should _meta be a required file? + REQ_STOREFILES = ('_project', '_package', '_apiurl', '_files', '_osclib_version') + OPT_STOREFILES = ('_to_be_added', '_to_be_deleted', '_in_conflict', '_in_update', + '_in_commit', '_meta', '_meta_mode', '_frozenlink', '_pulled', '_linkrepair', + '_size_limit', '_commit_msg') + + def __init__(self, workingdir, progress_obj=None, size_limit=None, wc_check=True): + global store + + self.dir = workingdir + self.absdir = os.path.abspath(self.dir) + self.storedir = os.path.join(self.absdir, store) + self.progress_obj = progress_obj + self.size_limit = size_limit + if size_limit and size_limit == 0: + self.size_limit = None + + check_store_version(self.dir) + + self.prjname = store_read_project(self.dir) + self.name = store_read_package(self.dir) + self.apiurl = store_read_apiurl(self.dir, defaulturl=not wc_check) + + self.update_datastructs() + dirty_files = [] + if wc_check: + dirty_files = self.wc_check() + if dirty_files: + msg = 'Your working copy \'%s\' is in an inconsistent state.\n' \ + 'Please run \'osc repairwc %s\' (Note this might _remove_\n' \ + 'files from the .osc/ dir). Please check the state\n' \ + 'of the working copy afterwards (via \'osc status %s\')' % (self.dir, self.dir, self.dir) + raise oscerr.WorkingCopyInconsistent(self.prjname, self.name, dirty_files, msg) + + self.todo = [] + + def wc_check(self): + dirty_files = [] + for fname in self.filenamelist: + if not os.path.exists(os.path.join(self.storedir, fname)) and not fname in self.skipped: + dirty_files.append(fname) + for fname in Package.REQ_STOREFILES: + if not os.path.isfile(os.path.join(self.storedir, fname)): + dirty_files.append(fname) + for fname in os.listdir(self.storedir): + if fname in Package.REQ_STOREFILES or fname in Package.OPT_STOREFILES or \ + fname.startswith('_build'): + continue + elif fname in self.filenamelist and fname in self.skipped: + dirty_files.append(fname) + elif not fname in self.filenamelist: + dirty_files.append(fname) + for fname in self.to_be_deleted[:]: + if not fname in self.filenamelist: + dirty_files.append(fname) + for fname in self.in_conflict[:]: + if not fname in self.filenamelist: + dirty_files.append(fname) + return dirty_files + + def wc_repair(self, apiurl=None): + if not os.path.exists(os.path.join(self.storedir, '_apiurl')) or apiurl: + if apiurl is None: + msg = 'cannot repair wc: the \'_apiurl\' file is missing but ' \ + 'no \'apiurl\' was passed to wc_repair' + # hmm should we raise oscerr.WrongArgs? + raise oscerr.WorkingCopyInconsistent(self.prjname, self.name, [], msg) + # sanity check + conf.parse_apisrv_url(None, apiurl) + store_write_apiurl(self.dir, apiurl) + self.apiurl = store_read_apiurl(self.dir, defaulturl=False) + # all files which are present in the filelist have to exist in the storedir + for f in self.filelist: + # XXX: should we also check the md5? + if not os.path.exists(os.path.join(self.storedir, f.name)) and not f.name in self.skipped: + # if get_source_file fails we're screwed up... + get_source_file(self.apiurl, self.prjname, self.name, f.name, + targetfilename=os.path.join(self.storedir, f.name), revision=self.rev, + mtime=f.mtime) + for fname in os.listdir(self.storedir): + if fname in Package.REQ_STOREFILES or fname in Package.OPT_STOREFILES or \ + fname.startswith('_build'): + continue + elif not fname in self.filenamelist or fname in self.skipped: + # this file does not belong to the storedir so remove it + os.unlink(os.path.join(self.storedir, fname)) + for fname in self.to_be_deleted[:]: + if not fname in self.filenamelist: + self.to_be_deleted.remove(fname) + self.write_deletelist() + for fname in self.in_conflict[:]: + if not fname in self.filenamelist: + self.in_conflict.remove(fname) + self.write_conflictlist() + + def info(self): + source_url = makeurl(self.apiurl, ['source', self.prjname, self.name]) + r = info_templ % (self.prjname, self.name, self.absdir, self.apiurl, source_url, self.srcmd5, self.rev, self.linkinfo) + return r + + def addfile(self, n): + if not os.path.exists(os.path.join(self.absdir, n)): + raise oscerr.OscIOError(None, 'error: file \'%s\' does not exist' % n) + if n in self.to_be_deleted: + self.to_be_deleted.remove(n) +# self.delete_storefile(n) + self.write_deletelist() + elif n in self.filenamelist or n in self.to_be_added: + raise oscerr.PackageFileConflict(self.prjname, self.name, n, 'osc: warning: \'%s\' is already under version control' % n) +# shutil.copyfile(os.path.join(self.dir, n), os.path.join(self.storedir, n)) + if self.dir != '.': + pathname = os.path.join(self.dir, n) + else: + pathname = n + self.to_be_added.append(n) + self.write_addlist() + print(statfrmt('A', pathname)) + + def delete_file(self, n, force=False): + """deletes a file if possible and marks the file as deleted""" + state = '?' + try: + state = self.status(n) + except IOError as ioe: + if not force: + raise ioe + if state in ['?', 'A', 'M', 'R', 'C'] and not force: + return (False, state) + # special handling for skipped files: if file exists, simply delete it + if state == 'S': + exists = os.path.exists(os.path.join(self.dir, n)) + self.delete_localfile(n) + return (exists, 'S') + + self.delete_localfile(n) + was_added = n in self.to_be_added + if state in ('A', 'R') or state == '!' and was_added: + self.to_be_added.remove(n) + self.write_addlist() + elif state == 'C': + # don't remove "merge files" (*.r, *.mine...) + # that's why we don't use clear_from_conflictlist + self.in_conflict.remove(n) + self.write_conflictlist() + if not state in ('A', '?') and not (state == '!' and was_added): + self.put_on_deletelist(n) + self.write_deletelist() + return (True, state) + + def delete_storefile(self, n): + try: os.unlink(os.path.join(self.storedir, n)) + except: pass + + def delete_localfile(self, n): + try: os.unlink(os.path.join(self.dir, n)) + except: pass + + def put_on_deletelist(self, n): + if n not in self.to_be_deleted: + self.to_be_deleted.append(n) + + def put_on_conflictlist(self, n): + if n not in self.in_conflict: + self.in_conflict.append(n) + + def put_on_addlist(self, n): + if n not in self.to_be_added: + self.to_be_added.append(n) + + def clear_from_conflictlist(self, n): + """delete an entry from the file, and remove the file if it would be empty""" + if n in self.in_conflict: + + filename = os.path.join(self.dir, n) + storefilename = os.path.join(self.storedir, n) + myfilename = os.path.join(self.dir, n + '.mine') + if self.islinkrepair() or self.ispulled(): + upfilename = os.path.join(self.dir, n + '.new') + else: + upfilename = os.path.join(self.dir, n + '.r' + self.rev) + + try: + os.unlink(myfilename) + # the working copy may be updated, so the .r* ending may be obsolete... + # then we don't care + os.unlink(upfilename) + if self.islinkrepair() or self.ispulled(): + os.unlink(os.path.join(self.dir, n + '.old')) + except: + pass + + self.in_conflict.remove(n) + + self.write_conflictlist() + + # XXX: this isn't used at all + def write_meta_mode(self): + # XXX: the "elif" is somehow a contradiction (with current and the old implementation + # it's not possible to "leave" the metamode again) (except if you modify pac.meta + # which is really ugly:) ) + if self.meta: + store_write_string(self.absdir, '_meta_mode', '') + elif self.ismetamode(): + os.unlink(os.path.join(self.storedir, '_meta_mode')) + + def write_sizelimit(self): + if self.size_limit and self.size_limit <= 0: + try: + os.unlink(os.path.join(self.storedir, '_size_limit')) + except: + pass + else: + store_write_string(self.absdir, '_size_limit', str(self.size_limit) + '\n') + + def write_addlist(self): + self.__write_storelist('_to_be_added', self.to_be_added) + + def write_deletelist(self): + self.__write_storelist('_to_be_deleted', self.to_be_deleted) + + def delete_source_file(self, n): + """delete local a source file""" + self.delete_localfile(n) + self.delete_storefile(n) + + def delete_remote_source_file(self, n): + """delete a remote source file (e.g. from the server)""" + query = 'rev=upload' + u = makeurl(self.apiurl, ['source', self.prjname, self.name, pathname2url(n)], query=query) + http_DELETE(u) + + def put_source_file(self, n, tdir, copy_only=False): + query = 'rev=repository' + tfilename = os.path.join(tdir, n) + shutil.copyfile(os.path.join(self.dir, n), tfilename) + # escaping '+' in the URL path (note: not in the URL query string) is + # only a workaround for ruby on rails, which swallows it otherwise + if not copy_only: + u = makeurl(self.apiurl, ['source', self.prjname, self.name, pathname2url(n)], query=query) + http_PUT(u, file = tfilename) + if n in self.to_be_added: + self.to_be_added.remove(n) + + def __commit_update_store(self, tdir): + """move files from transaction directory into the store""" + for filename in os.listdir(tdir): + os.rename(os.path.join(tdir, filename), os.path.join(self.storedir, filename)) + + def __generate_commitlist(self, todo_send): + root = ET.Element('directory') + for i in sorted(todo_send.keys()): + ET.SubElement(root, 'entry', name=i, md5=todo_send[i]) + return root + + @staticmethod + def commit_filelist(apiurl, project, package, filelist, msg='', user=None, **query): + """send the commitlog and the local filelist to the server""" + if user is None: + user = conf.get_apiurl_usr(apiurl) + query.update({'cmd': 'commitfilelist', 'user': user, 'comment': msg}) + u = makeurl(apiurl, ['source', project, package], query=query) + f = http_POST(u, data=ET.tostring(filelist, encoding=ET_ENCODING)) + root = ET.parse(f).getroot() + return root + + @staticmethod + def commit_get_missing(filelist): + """returns list of missing files (filelist is the result of commit_filelist)""" + error = filelist.get('error') + if error is None: + return [] + elif error != 'missing': + raise oscerr.APIError('commit_get_missing_files: ' + 'unexpected \'error\' attr: \'%s\'' % error) + todo = [] + for n in filelist.findall('entry'): + name = n.get('name') + if name is None: + raise oscerr.APIError('missing \'name\' attribute:\n%s\n' + % ET.tostring(filelist, encoding=ET_ENCODING)) + todo.append(n.get('name')) + return todo + + def __send_commitlog(self, msg, local_filelist): + """send the commitlog and the local filelist to the server""" + query = {} + if self.islink() and self.isexpanded(): + query['keeplink'] = '1' + if conf.config['linkcontrol'] or self.isfrozen(): + query['linkrev'] = self.linkinfo.srcmd5 + if self.ispulled(): + query['repairlink'] = '1' + query['linkrev'] = self.get_pulled_srcmd5() + if self.islinkrepair(): + query['repairlink'] = '1' + return self.commit_filelist(self.apiurl, self.prjname, self.name, + local_filelist, msg, **query) + + def commit(self, msg='', verbose=False, skip_local_service_run=False, can_branch=False, force=False): + # commit only if the upstream revision is the same as the working copy's + upstream_rev = self.latest_rev() + if self.rev != upstream_rev: + raise oscerr.WorkingCopyOutdated((self.absdir, self.rev, upstream_rev)) + + if not skip_local_service_run: + r = self.run_source_services(mode="trylocal", verbose=verbose) + if r is not 0: + # FIXME: it is better to raise this in Serviceinfo.execute with more + # information (like which service/command failed) + raise oscerr.ServiceRuntimeError('A service failed with error: %d' % r) + + # check if it is a link, if so, branch the package + if self.is_link_to_different_project(): + if can_branch: + orgprj = self.get_local_origin_project() + print("Branching {} from {} to {}".format(self.name, orgprj, self.prjname)) + exists, targetprj, targetpkg, srcprj, srcpkg = branch_pkg( + self.apiurl, orgprj, self.name, target_project=self.prjname) + # update _meta and _files to sychronize the local package + # to the new branched one in OBS + self.update_local_pacmeta() + self.update_local_filesmeta() + else: + print("{} Not commited because is link to a different project".format(self.name)) + return 1 + + if not self.todo: + self.todo = [i for i in self.to_be_added if not i in self.filenamelist] + self.filenamelist + + pathn = getTransActPath(self.dir) + + todo_send = {} + todo_delete = [] + real_send = [] + for filename in self.filenamelist + [i for i in self.to_be_added if not i in self.filenamelist]: + if filename.startswith('_service:') or filename.startswith('_service_'): + continue + st = self.status(filename) + if st == 'C': + print('Please resolve all conflicts before committing using "osc resolved FILE"!') + return 1 + elif filename in self.todo: + if st in ('A', 'R', 'M'): + todo_send[filename] = dgst(os.path.join(self.absdir, filename)) + real_send.append(filename) + print(statfrmt('Sending', os.path.join(pathn, filename))) + elif st in (' ', '!', 'S'): + if st == '!' and filename in self.to_be_added: + print('file \'%s\' is marked as \'A\' but does not exist' % filename) + return 1 + f = self.findfilebyname(filename) + if f is None: + raise oscerr.PackageInternalError(self.prjname, self.name, + 'error: file \'%s\' with state \'%s\' is not known by meta' \ + % (filename, st)) + todo_send[filename] = f.md5 + elif st == 'D': + todo_delete.append(filename) + print(statfrmt('Deleting', os.path.join(pathn, filename))) + elif st in ('R', 'M', 'D', ' ', '!', 'S'): + # ignore missing new file (it's not part of the current commit) + if st == '!' and filename in self.to_be_added: + continue + f = self.findfilebyname(filename) + if f is None: + raise oscerr.PackageInternalError(self.prjname, self.name, + 'error: file \'%s\' with state \'%s\' is not known by meta' \ + % (filename, st)) + todo_send[filename] = f.md5 + + if not force and not real_send and not todo_delete and not self.islinkrepair() and not self.ispulled(): + print('nothing to do for package %s' % self.name) + return 1 + + print('Transmitting file data', end=' ') + filelist = self.__generate_commitlist(todo_send) + sfilelist = self.__send_commitlog(msg, filelist) + send = self.commit_get_missing(sfilelist) + real_send = [i for i in real_send if not i in send] + # abort after 3 tries + tries = 3 + tdir = None + try: + tdir = os.path.join(self.storedir, '_in_commit') + if os.path.isdir(tdir): + shutil.rmtree(tdir) + os.mkdir(tdir) + while len(send) and tries: + for filename in send[:]: + sys.stdout.write('.') + sys.stdout.flush() + self.put_source_file(filename, tdir) + send.remove(filename) + tries -= 1 + sfilelist = self.__send_commitlog(msg, filelist) + send = self.commit_get_missing(sfilelist) + if len(send): + raise oscerr.PackageInternalError(self.prjname, self.name, + 'server does not accept filelist:\n%s\nmissing:\n%s\n' \ + % (ET.tostring(filelist, encoding=ET_ENCODING), ET.tostring(sfilelist, encoding=ET_ENCODING))) + # these files already exist on the server + for filename in real_send: + self.put_source_file(filename, tdir, copy_only=True) + # update store with the committed files + self.__commit_update_store(tdir) + finally: + if tdir is not None and os.path.isdir(tdir): + shutil.rmtree(tdir) + self.rev = sfilelist.get('rev') + print() + print('Committed revision %s.' % self.rev) + + if self.ispulled(): + os.unlink(os.path.join(self.storedir, '_pulled')) + if self.islinkrepair(): + os.unlink(os.path.join(self.storedir, '_linkrepair')) + self.linkrepair = False + # XXX: mark package as invalid? + print('The source link has been repaired. This directory can now be removed.') + + if self.islink() and self.isexpanded(): + li = Linkinfo() + li.read(sfilelist.find('linkinfo')) + if li.xsrcmd5 is None: + raise oscerr.APIError('linkinfo has no xsrcmd5 attr:\n%s\n' % ET.tostring(sfilelist, encoding=ET_ENCODING)) + sfilelist = ET.fromstring(self.get_files_meta(revision=li.xsrcmd5)) + for i in sfilelist.findall('entry'): + if i.get('name') in self.skipped: + i.set('skipped', 'true') + store_write_string(self.absdir, '_files', ET.tostring(sfilelist, encoding=ET_ENCODING) + '\n') + for filename in todo_delete: + self.to_be_deleted.remove(filename) + self.delete_storefile(filename) + self.write_deletelist() + self.write_addlist() + self.update_datastructs() + + print_request_list(self.apiurl, self.prjname, self.name) + + # FIXME: add testcases for this codepath + sinfo = sfilelist.find('serviceinfo') + if sinfo is not None: + print('Waiting for server side source service run') + u = makeurl(self.apiurl, ['source', self.prjname, self.name]) + while sinfo is not None and sinfo.get('code') == 'running': + sys.stdout.write('.') + sys.stdout.flush() + # does it make sense to add some delay? + sfilelist = ET.fromstring(http_GET(u).read()) + # if sinfo is None another commit might have occured in the "meantime" + sinfo = sfilelist.find('serviceinfo') + print('') + rev = self.latest_rev() + self.update(rev=rev) + elif self.get_local_meta() is None: + # if this was a newly added package there is no _meta + # file + self.update_local_pacmeta() + + def __write_storelist(self, name, data): + if len(data) == 0: + try: + os.unlink(os.path.join(self.storedir, name)) + except: + pass + else: + store_write_string(self.absdir, name, '%s\n' % '\n'.join(data)) + + def write_conflictlist(self): + self.__write_storelist('_in_conflict', self.in_conflict) + + def updatefile(self, n, revision, mtime=None): + filename = os.path.join(self.dir, n) + storefilename = os.path.join(self.storedir, n) + origfile_tmp = os.path.join(self.storedir, '_in_update', '%s.copy' % n) + origfile = os.path.join(self.storedir, '_in_update', n) + if os.path.isfile(filename): + shutil.copyfile(filename, origfile_tmp) + os.rename(origfile_tmp, origfile) + else: + origfile = None + + get_source_file(self.apiurl, self.prjname, self.name, n, targetfilename=storefilename, + revision=revision, progress_obj=self.progress_obj, mtime=mtime, meta=self.meta) + + shutil.copyfile(storefilename, filename) + if mtime: + utime(filename, (-1, mtime)) + if not origfile is None: + os.unlink(origfile) + + def mergefile(self, n, revision, mtime=None): + filename = os.path.join(self.dir, n) + storefilename = os.path.join(self.storedir, n) + myfilename = os.path.join(self.dir, n + '.mine') + upfilename = os.path.join(self.dir, n + '.r' + self.rev) + origfile_tmp = os.path.join(self.storedir, '_in_update', '%s.copy' % n) + origfile = os.path.join(self.storedir, '_in_update', n) + shutil.copyfile(filename, origfile_tmp) + os.rename(origfile_tmp, origfile) + os.rename(filename, myfilename) + + get_source_file(self.apiurl, self.prjname, self.name, n, + revision=revision, targetfilename=upfilename, + progress_obj=self.progress_obj, mtime=mtime, meta=self.meta) + + if binary_file(myfilename) or binary_file(upfilename): + # don't try merging + shutil.copyfile(upfilename, filename) + shutil.copyfile(upfilename, storefilename) + os.unlink(origfile) + self.in_conflict.append(n) + self.write_conflictlist() + return 'C' + else: + # try merging + # diff3 OPTIONS... MINE OLDER YOURS + merge_cmd = 'diff3 -m -E %s %s %s > %s' % (myfilename, storefilename, upfilename, filename) + ret = run_external(merge_cmd, shell=True) + + # "An exit status of 0 means `diff3' was successful, 1 means some + # conflicts were found, and 2 means trouble." + if ret == 0: + # merge was successful... clean up + shutil.copyfile(upfilename, storefilename) + os.unlink(upfilename) + os.unlink(myfilename) + os.unlink(origfile) + return 'G' + elif ret == 1: + # unsuccessful merge + shutil.copyfile(upfilename, storefilename) + os.unlink(origfile) + self.in_conflict.append(n) + self.write_conflictlist() + return 'C' + else: + raise oscerr.ExtRuntimeError('diff3 failed with exit code: %s' % ret, merge_cmd) + + def update_local_filesmeta(self, revision=None): + """ + Update the local _files file in the store. + It is replaced with the version pulled from upstream. + """ + meta = self.get_files_meta(revision=revision) + store_write_string(self.absdir, '_files', meta + '\n') + + def get_files_meta(self, revision='latest', skip_service=True): + fm = show_files_meta(self.apiurl, self.prjname, self.name, revision=revision, meta=self.meta) + # look for "too large" files according to size limit and mark them + root = ET.fromstring(fm) + for e in root.findall('entry'): + size = e.get('size') + if size and self.size_limit and int(size) > self.size_limit \ + or skip_service and (e.get('name').startswith('_service:') or e.get('name').startswith('_service_')): + e.set('skipped', 'true') + return ET.tostring(root, encoding=ET_ENCODING) + + def get_local_meta(self): + """Get the local _meta file for the package.""" + meta = store_read_file(self.absdir, '_meta') + return meta + + def get_local_origin_project(self): + """Get the originproject from the _meta file.""" + # if the wc was checked out via some old osc version + # there might be no meta file: in this case we assume + # that the origin project is equal to the wc's project + meta = self.get_local_meta() + if meta is None: + return self.prjname + root = ET.fromstring(meta) + return root.get('project') + + def is_link_to_different_project(self): + """Check if the package is a link to a different project.""" + orgprj = self.get_local_origin_project() + return self.prjname != orgprj + + def update_datastructs(self): + """ + Update the internal data structures if the local _files + file has changed (e.g. update_local_filesmeta() has been + called). + """ + import fnmatch + files_tree = read_filemeta(self.dir) + files_tree_root = files_tree.getroot() + + self.rev = files_tree_root.get('rev') + self.srcmd5 = files_tree_root.get('srcmd5') + + self.linkinfo = Linkinfo() + self.linkinfo.read(files_tree_root.find('linkinfo')) + self.serviceinfo = DirectoryServiceinfo() + self.serviceinfo.read(files_tree_root.find('serviceinfo')) + + self.filenamelist = [] + self.filelist = [] + self.skipped = [] + for node in files_tree_root.findall('entry'): + try: + f = File(node.get('name'), + node.get('md5'), + int(node.get('size')), + int(node.get('mtime'))) + if node.get('skipped'): + self.skipped.append(f.name) + f.skipped = True + except: + # okay, a very old version of _files, which didn't contain any metadata yet... + f = File(node.get('name'), '', 0, 0) + self.filelist.append(f) + self.filenamelist.append(f.name) + + self.to_be_added = read_tobeadded(self.absdir) + self.to_be_deleted = read_tobedeleted(self.absdir) + self.in_conflict = read_inconflict(self.absdir) + self.linkrepair = os.path.isfile(os.path.join(self.storedir, '_linkrepair')) + self.size_limit = read_sizelimit(self.dir) + self.meta = self.ismetamode() + + # gather unversioned files, but ignore some stuff + self.excluded = [] + for i in os.listdir(self.dir): + for j in conf.config['exclude_glob']: + if fnmatch.fnmatch(i, j): + self.excluded.append(i) + break + self.filenamelist_unvers = [ i for i in os.listdir(self.dir) + if i not in self.excluded + if i not in self.filenamelist ] + + def islink(self): + """tells us if the package is a link (has 'linkinfo'). + A package with linkinfo is a package which links to another package. + Returns True if the package is a link, otherwise False.""" + return self.linkinfo.islink() + + def isexpanded(self): + """tells us if the package is a link which is expanded. + Returns True if the package is expanded, otherwise False.""" + return self.linkinfo.isexpanded() + + def islinkrepair(self): + """tells us if we are repairing a broken source link.""" + return self.linkrepair + + def ispulled(self): + """tells us if we have pulled a link.""" + return os.path.isfile(os.path.join(self.storedir, '_pulled')) + + def isfrozen(self): + """tells us if the link is frozen.""" + return os.path.isfile(os.path.join(self.storedir, '_frozenlink')) + + def ismetamode(self): + """tells us if the package is in meta mode""" + return os.path.isfile(os.path.join(self.storedir, '_meta_mode')) + + def get_pulled_srcmd5(self): + pulledrev = None + for line in open(os.path.join(self.storedir, '_pulled'), 'r'): + pulledrev = line.strip() + return pulledrev + + def haslinkerror(self): + """ + Returns True if the link is broken otherwise False. + If the package is not a link it returns False. + """ + return self.linkinfo.haserror() + + def linkerror(self): + """ + Returns an error message if the link is broken otherwise None. + If the package is not a link it returns None. + """ + return self.linkinfo.error + + def hasserviceinfo(self): + """ + Returns True, if this package contains services. + """ + return self.serviceinfo.lsrcmd5 is not None or self.serviceinfo.xsrcmd5 is not None + + def update_local_pacmeta(self): + """ + Update the local _meta file in the store. + It is replaced with the version pulled from upstream. + """ + meta = show_package_meta(self.apiurl, self.prjname, self.name) + if meta != "": + # is empty for _project for example + meta = ''.join(meta) + store_write_string(self.absdir, '_meta', meta + '\n') + + def findfilebyname(self, n): + for i in self.filelist: + if i.name == n: + return i + + def get_status(self, excluded=False, *exclude_states): + global store + todo = self.todo + if not todo: + todo = self.filenamelist + self.to_be_added + \ + [i for i in self.filenamelist_unvers if not os.path.isdir(os.path.join(self.absdir, i))] + if excluded: + todo.extend([i for i in self.excluded if i != store]) + todo = set(todo) + res = [] + for fname in sorted(todo): + st = self.status(fname) + if not st in exclude_states: + res.append((st, fname)) + return res + + def status(self, n): + """ + status can be: + + file storefile file present STATUS + exists exists in _files + + x - - 'A' and listed in _to_be_added + x x - 'R' and listed in _to_be_added + x x x ' ' if digest differs: 'M' + and if in conflicts file: 'C' + x - - '?' + - x x 'D' and listed in _to_be_deleted + x x x 'D' and listed in _to_be_deleted (e.g. if deleted file was modified) + x x x 'C' and listed in _in_conflict + x - x 'S' and listed in self.skipped + - - x 'S' and listed in self.skipped + - x x '!' + - - - NOT DEFINED + + """ + + known_by_meta = False + exists = False + exists_in_store = False + if n in self.filenamelist: + known_by_meta = True + if os.path.exists(os.path.join(self.absdir, n)): + exists = True + if os.path.exists(os.path.join(self.storedir, n)): + exists_in_store = True + + if n in self.to_be_deleted: + state = 'D' + elif n in self.in_conflict: + state = 'C' + elif n in self.skipped: + state = 'S' + elif n in self.to_be_added and exists and exists_in_store: + state = 'R' + elif n in self.to_be_added and exists: + state = 'A' + elif exists and exists_in_store and known_by_meta: + if dgst(os.path.join(self.absdir, n)) != self.findfilebyname(n).md5: + state = 'M' + else: + state = ' ' + elif n in self.to_be_added and not exists: + state = '!' + elif not exists and exists_in_store and known_by_meta and not n in self.to_be_deleted: + state = '!' + elif exists and not exists_in_store and not known_by_meta: + state = '?' + elif not exists_in_store and known_by_meta: + # XXX: this codepath shouldn't be reached (we restore the storefile + # in update_datastructs) + raise oscerr.PackageInternalError(self.prjname, self.name, + 'error: file \'%s\' is known by meta but no storefile exists.\n' + 'This might be caused by an old wc format. Please backup your current\n' + 'wc and checkout the package again. Afterwards copy all files (except the\n' + '.osc/ dir) into the new package wc.' % n) + elif os.path.islink(os.path.join(self.absdir, n)): + # dangling symlink, whose name is _not_ tracked: treat it + # as unversioned + state = '?' + else: + # this case shouldn't happen (except there was a typo in the filename etc.) + raise oscerr.OscIOError(None, 'osc: \'%s\' is not under version control' % n) + + return state + + def get_diff(self, revision=None, ignoreUnversioned=False): + import tempfile + diff_hdr = 'Index: %s\n' + diff_hdr += '===================================================================\n' + kept = [] + added = [] + deleted = [] + def diff_add_delete(fname, add, revision): + diff = [] + diff.append(diff_hdr % fname) + tmpfile = None + origname = fname + if add: + diff.append('--- %s\t(revision 0)\n' % fname) + rev = 'revision 0' + if revision and not fname in self.to_be_added: + rev = 'working copy' + diff.append('+++ %s\t(%s)\n' % (fname, rev)) + fname = os.path.join(self.absdir, fname) + else: + diff.append('--- %s\t(revision %s)\n' % (fname, revision or self.rev)) + diff.append('+++ %s\t(working copy)\n' % fname) + fname = os.path.join(self.storedir, fname) + + try: + if revision is not None and not add: + (fd, tmpfile) = tempfile.mkstemp(prefix='osc_diff') + get_source_file(self.apiurl, self.prjname, self.name, origname, tmpfile, revision) + fname = tmpfile + if binary_file(fname): + what = 'added' + if not add: + what = 'deleted' + diff = diff[:1] + diff.append('Binary file \'%s\' %s.\n' % (origname, what)) + return diff + tmpl = '+%s' + ltmpl = '@@ -0,0 +1,%d @@\n' + if not add: + tmpl = '-%s' + ltmpl = '@@ -1,%d +0,0 @@\n' + lines = [tmpl % i for i in open(fname, 'r').readlines()] + if len(lines): + diff.append(ltmpl % len(lines)) + if not lines[-1].endswith('\n'): + lines.append('\n\\ No newline at end of file\n') + diff.extend(lines) + finally: + if tmpfile is not None: + os.close(fd) + os.unlink(tmpfile) + return diff + + if revision is None: + todo = self.todo or [i for i in self.filenamelist if not i in self.to_be_added]+self.to_be_added + for fname in todo: + if fname in self.to_be_added and self.status(fname) == 'A': + added.append(fname) + elif fname in self.to_be_deleted: + deleted.append(fname) + elif fname in self.filenamelist: + kept.append(self.findfilebyname(fname)) + elif fname in self.to_be_added and self.status(fname) == '!': + raise oscerr.OscIOError(None, 'file \'%s\' is marked as \'A\' but does not exist\n'\ + '(either add the missing file or revert it)' % fname) + elif not ignoreUnversioned: + raise oscerr.OscIOError(None, 'file \'%s\' is not under version control' % fname) + else: + fm = self.get_files_meta(revision=revision) + root = ET.fromstring(fm) + rfiles = self.__get_files(root) + # swap added and deleted + kept, deleted, added, services = self.__get_rev_changes(rfiles) + added = [f.name for f in added] + added.extend([f for f in self.to_be_added if not f in kept]) + deleted = [f.name for f in deleted] + deleted.extend(self.to_be_deleted) + for f in added[:]: + if f in deleted: + added.remove(f) + deleted.remove(f) +# print kept, added, deleted + for f in kept: + state = self.status(f.name) + if state in ('S', '?', '!'): + continue + elif state == ' ' and revision is None: + continue + elif revision and self.findfilebyname(f.name).md5 == f.md5 and state != 'M': + continue + yield [diff_hdr % f.name] + if revision is None: + yield get_source_file_diff(self.absdir, f.name, self.rev) + else: + tmpfile = None + diff = [] + try: + (fd, tmpfile) = tempfile.mkstemp(prefix='osc_diff') + get_source_file(self.apiurl, self.prjname, self.name, f.name, tmpfile, revision) + diff = get_source_file_diff(self.absdir, f.name, revision, + os.path.basename(tmpfile), os.path.dirname(tmpfile), f.name) + finally: + if tmpfile is not None: + os.close(fd) + os.unlink(tmpfile) + yield diff + + for f in added: + yield diff_add_delete(f, True, revision) + for f in deleted: + yield diff_add_delete(f, False, revision) + + def merge(self, otherpac): + self.todo += otherpac.todo + + def __str__(self): + r = """ +name: %s +prjname: %s +workingdir: %s +localfilelist: %s +linkinfo: %s +rev: %s +'todo' files: %s +""" % (self.name, + self.prjname, + self.dir, + '\n '.join(self.filenamelist), + self.linkinfo, + self.rev, + self.todo) + + return r + + + def read_meta_from_spec(self, spec = None): + import glob + if spec: + specfile = spec + else: + # scan for spec files + speclist = glob.glob(os.path.join(self.dir, '*.spec')) + if len(speclist) == 1: + specfile = speclist[0] + elif len(speclist) > 1: + print('the following specfiles were found:') + for filename in speclist: + print(filename) + print('please specify one with --specfile') + sys.exit(1) + else: + print('no specfile was found - please specify one ' \ + 'with --specfile') + sys.exit(1) + + data = read_meta_from_spec(specfile, 'Summary', 'Url', '%description') + self.summary = data.get('Summary', '') + self.url = data.get('Url', '') + self.descr = data.get('%description', '') + + + def update_package_meta(self, force=False): + """ + for the updatepacmetafromspec subcommand + argument force supress the confirm question + """ + + m = ''.join(show_package_meta(self.apiurl, self.prjname, self.name)) + + root = ET.fromstring(m) + root.find('title').text = self.summary + root.find('description').text = ''.join(self.descr) + url = root.find('url') + if url == None: + url = ET.SubElement(root, 'url') + url.text = self.url + + u = makeurl(self.apiurl, ['source', self.prjname, self.name, '_meta']) + mf = metafile(u, ET.tostring(root, encoding=ET_ENCODING)) + + if not force: + print('*' * 36, 'old', '*' * 36) + print(m) + print('*' * 36, 'new', '*' * 36) + print(ET.tostring(root, encoding=ET_ENCODING)) + print('*' * 72) + repl = raw_input('Write? (y/N/e) ') + else: + repl = 'y' + + if repl == 'y': + mf.sync() + elif repl == 'e': + mf.edit() + + mf.discard() + + def mark_frozen(self): + store_write_string(self.absdir, '_frozenlink', '') + print() + print("The link in this package (\"%s\") is currently broken. Checking" % self.name) + print("out the last working version instead; please use 'osc pull'") + print("to merge the conflicts.") + print() + + def unmark_frozen(self): + if os.path.exists(os.path.join(self.storedir, '_frozenlink')): + os.unlink(os.path.join(self.storedir, '_frozenlink')) + + def latest_rev(self, include_service_files=False, expand=False): + # if expand is True the xsrcmd5 will be returned (even if the wc is unexpanded) + if self.islinkrepair(): + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, linkrepair=1, meta=self.meta, include_service_files=include_service_files) + elif self.islink() and (self.isexpanded() or expand): + if self.isfrozen() or self.ispulled(): + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, linkrev=self.linkinfo.srcmd5, meta=self.meta, include_service_files=include_service_files) + else: + try: + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, meta=self.meta, include_service_files=include_service_files) + except: + try: + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, linkrev=self.linkinfo.srcmd5, meta=self.meta, include_service_files=include_service_files) + except: + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, linkrev="base", meta=self.meta, include_service_files=include_service_files) + self.mark_frozen() + elif not self.islink() and expand: + upstream_rev = show_upstream_xsrcmd5(self.apiurl, self.prjname, self.name, meta=self.meta, include_service_files=include_service_files) + else: + upstream_rev = show_upstream_rev(self.apiurl, self.prjname, self.name, meta=self.meta, include_service_files=include_service_files) + return upstream_rev + + def __get_files(self, fmeta_root): + f = [] + if fmeta_root.get('rev') is None and len(fmeta_root.findall('entry')) > 0: + raise oscerr.APIError('missing rev attribute in _files:\n%s' % ''.join(ET.tostring(fmeta_root, encoding=ET_ENCODING))) + for i in fmeta_root.findall('entry'): + error = i.get('error') + if error is not None: + raise oscerr.APIError('broken files meta: %s' % error) + skipped = i.get('skipped') is not None + f.append(File(i.get('name'), i.get('md5'), + int(i.get('size')), int(i.get('mtime')), skipped)) + return f + + def __get_rev_changes(self, revfiles): + kept = [] + added = [] + deleted = [] + services = [] + revfilenames = [] + for f in revfiles: + revfilenames.append(f.name) + # treat skipped like deleted files + if f.skipped: + if f.name.startswith('_service:'): + services.append(f) + else: + deleted.append(f) + continue + # treat skipped like added files + # problem: this overwrites existing files during the update + # (because skipped files aren't in self.filenamelist_unvers) + if f.name in self.filenamelist and not f.name in self.skipped: + kept.append(f) + else: + added.append(f) + for f in self.filelist: + if not f.name in revfilenames: + deleted.append(f) + + return kept, added, deleted, services + + def update_needed(self, sinfo): + # this method might return a false-positive (that is a True is returned, + # even though no update is needed) (for details, see comments below) + if self.islink(): + if self.isexpanded(): + # check if both revs point to the same expanded sources + # Note: if the package contains a _service file, sinfo.srcmd5's lsrcmd5 + # points to the "expanded" services (xservicemd5) => chances + # for a false-positive are high, because osc usually works on the + # "unexpanded" services. + # Once the srcserver supports something like noservice=1, we can get rid of + # this false-positives (patch was already sent to the ml) (but this also + # requires some slight changes in osc) + return sinfo.get('srcmd5') != self.srcmd5 + elif self.hasserviceinfo(): + # check if we have expanded or unexpanded services + if self.serviceinfo.isexpanded(): + return sinfo.get('lsrcmd5') != self.srcmd5 + else: + # again, we might have a false-positive here, because + # a mismatch of the "xservicemd5"s does not neccessarily + # imply a change in the "unexpanded" services. + return sinfo.get('lsrcmd5') != self.serviceinfo.xsrcmd5 + # simple case: unexpanded sources and no services + # self.srcmd5 should also work + return sinfo.get('lsrcmd5') != self.linkinfo.lsrcmd5 + elif self.hasserviceinfo(): + if self.serviceinfo.isexpanded(): + return sinfo.get('srcmd5') != self.srcmd5 + else: + # cannot handle this case, because the sourceinfo does not contain + # information about the lservicemd5. Once the srcserver supports + # a noservice=1 query parameter, we can handle this case. + return True + return sinfo.get('srcmd5') != self.srcmd5 + + def update(self, rev = None, service_files = False, size_limit = None): + import tempfile + rfiles = [] + # size_limit is only temporary for this update + old_size_limit = self.size_limit + if not size_limit is None: + self.size_limit = int(size_limit) + if os.path.isfile(os.path.join(self.storedir, '_in_update', '_files')): + print('resuming broken update...') + root = ET.parse(os.path.join(self.storedir, '_in_update', '_files')).getroot() + rfiles = self.__get_files(root) + kept, added, deleted, services = self.__get_rev_changes(rfiles) + # check if we aborted in the middle of a file update + broken_file = os.listdir(os.path.join(self.storedir, '_in_update')) + broken_file.remove('_files') + if len(broken_file) == 1: + origfile = os.path.join(self.storedir, '_in_update', broken_file[0]) + wcfile = os.path.join(self.absdir, broken_file[0]) + origfile_md5 = dgst(origfile) + origfile_meta = self.findfilebyname(broken_file[0]) + if origfile.endswith('.copy'): + # ok it seems we aborted at some point during the copy process + # (copy process == copy wcfile to the _in_update dir). remove file+continue + os.unlink(origfile) + elif self.findfilebyname(broken_file[0]) is None: + # should we remove this file from _in_update? if we don't + # the user has no chance to continue without removing the file manually + raise oscerr.PackageInternalError(self.prjname, self.name, + '\'%s\' is not known by meta but exists in \'_in_update\' dir') + elif os.path.isfile(wcfile) and dgst(wcfile) != origfile_md5: + (fd, tmpfile) = tempfile.mkstemp(dir=self.absdir, prefix=broken_file[0]+'.') + os.close(fd) + os.rename(wcfile, tmpfile) + os.rename(origfile, wcfile) + print('warning: it seems you modified \'%s\' after the broken ' \ + 'update. Restored original file and saved modified version ' \ + 'to \'%s\'.' % (wcfile, tmpfile)) + elif not os.path.isfile(wcfile): + # this is strange... because it existed before the update. restore it + os.rename(origfile, wcfile) + else: + # everything seems to be ok + os.unlink(origfile) + elif len(broken_file) > 1: + raise oscerr.PackageInternalError(self.prjname, self.name, 'too many files in \'_in_update\' dir') + tmp = rfiles[:] + for f in tmp: + if os.path.exists(os.path.join(self.storedir, f.name)): + if dgst(os.path.join(self.storedir, f.name)) == f.md5: + if f in kept: + kept.remove(f) + elif f in added: + added.remove(f) + # this can't happen + elif f in deleted: + deleted.remove(f) + if not service_files: + services = [] + self.__update(kept, added, deleted, services, ET.tostring(root, encoding=ET_ENCODING), root.get('rev')) + os.unlink(os.path.join(self.storedir, '_in_update', '_files')) + os.rmdir(os.path.join(self.storedir, '_in_update')) + # ok everything is ok (hopefully)... + fm = self.get_files_meta(revision=rev) + root = ET.fromstring(fm) + rfiles = self.__get_files(root) + store_write_string(self.absdir, '_files', fm + '\n', subdir='_in_update') + kept, added, deleted, services = self.__get_rev_changes(rfiles) + if not service_files: + services = [] + self.__update(kept, added, deleted, services, fm, root.get('rev')) + os.unlink(os.path.join(self.storedir, '_in_update', '_files')) + if os.path.isdir(os.path.join(self.storedir, '_in_update')): + os.rmdir(os.path.join(self.storedir, '_in_update')) + self.size_limit = old_size_limit + + def __update(self, kept, added, deleted, services, fm, rev): + pathn = getTransActPath(self.dir) + # check for conflicts with existing files + for f in added: + if f.name in self.filenamelist_unvers: + raise oscerr.PackageFileConflict(self.prjname, self.name, f.name, + 'failed to add file \'%s\' file/dir with the same name already exists' % f.name) + # ok, the update can't fail due to existing files + for f in added: + self.updatefile(f.name, rev, f.mtime) + print(statfrmt('A', os.path.join(pathn, f.name))) + for f in deleted: + # if the storefile doesn't exist we're resuming an aborted update: + # the file was already deleted but we cannot know this + # OR we're processing a _service: file (simply keep the file) + if os.path.isfile(os.path.join(self.storedir, f.name)) and self.status(f.name) != 'M': +# if self.status(f.name) != 'M': + self.delete_localfile(f.name) + self.delete_storefile(f.name) + print(statfrmt('D', os.path.join(pathn, f.name))) + if f.name in self.to_be_deleted: + self.to_be_deleted.remove(f.name) + self.write_deletelist() + + for f in kept: + state = self.status(f.name) +# print f.name, state + if state == 'M' and self.findfilebyname(f.name).md5 == f.md5: + # remote file didn't change + pass + elif state == 'M': + # try to merge changes + merge_status = self.mergefile(f.name, rev, f.mtime) + print(statfrmt(merge_status, os.path.join(pathn, f.name))) + elif state == '!': + self.updatefile(f.name, rev, f.mtime) + print('Restored \'%s\'' % os.path.join(pathn, f.name)) + elif state == 'C': + get_source_file(self.apiurl, self.prjname, self.name, f.name, + targetfilename=os.path.join(self.storedir, f.name), revision=rev, + progress_obj=self.progress_obj, mtime=f.mtime, meta=self.meta) + print('skipping \'%s\' (this is due to conflicts)' % f.name) + elif state == 'D' and self.findfilebyname(f.name).md5 != f.md5: + # XXX: in the worst case we might end up with f.name being + # in _to_be_deleted and in _in_conflict... this needs to be checked + if os.path.exists(os.path.join(self.absdir, f.name)): + merge_status = self.mergefile(f.name, rev, f.mtime) + print(statfrmt(merge_status, os.path.join(pathn, f.name))) + if merge_status == 'C': + # state changes from delete to conflict + self.to_be_deleted.remove(f.name) + self.write_deletelist() + else: + # XXX: we cannot recover this case because we've no file + # to backup + self.updatefile(f.name, rev, f.mtime) + print(statfrmt('U', os.path.join(pathn, f.name))) + elif state == ' ' and self.findfilebyname(f.name).md5 != f.md5: + self.updatefile(f.name, rev, f.mtime) + print(statfrmt('U', os.path.join(pathn, f.name))) + + # checkout service files + for f in services: + get_source_file(self.apiurl, self.prjname, self.name, f.name, + targetfilename=os.path.join(self.absdir, f.name), revision=rev, + progress_obj=self.progress_obj, mtime=f.mtime, meta=self.meta) + print(statfrmt('A', os.path.join(pathn, f.name))) + store_write_string(self.absdir, '_files', fm + '\n') + if not self.meta: + self.update_local_pacmeta() + self.update_datastructs() + + print('At revision %s.' % self.rev) + + def run_source_services(self, mode=None, singleservice=None, verbose=None): + if self.name.startswith("_"): + return 0 + curdir = os.getcwd() + os.chdir(self.absdir) # e.g. /usr/lib/obs/service/verify_file fails if not inside the project dir. + si = Serviceinfo() + if os.path.exists('_service'): + if self.filenamelist.count('_service') or self.filenamelist_unvers.count('_service'): + try: + service = ET.parse(os.path.join(self.absdir, '_service')).getroot() + except ET.ParseError as v: + line, column = v.position + print('XML error in _service file on line %s, column %s' % (line, column)) + sys.exit(1) + si.read(service) + si.getProjectGlobalServices(self.apiurl, self.prjname, self.name) + r = si.execute(self.absdir, mode, singleservice, verbose) + os.chdir(curdir) + return r + + def revert(self, filename): + if not filename in self.filenamelist and not filename in self.to_be_added: + raise oscerr.OscIOError(None, 'file \'%s\' is not under version control' % filename) + elif filename in self.skipped: + raise oscerr.OscIOError(None, 'file \'%s\' is marked as skipped and cannot be reverted' % filename) + if filename in self.filenamelist and not os.path.exists(os.path.join(self.storedir, filename)): + raise oscerr.PackageInternalError('file \'%s\' is listed in filenamelist but no storefile exists' % filename) + state = self.status(filename) + if not (state == 'A' or state == '!' and filename in self.to_be_added): + shutil.copyfile(os.path.join(self.storedir, filename), os.path.join(self.absdir, filename)) + if state == 'D': + self.to_be_deleted.remove(filename) + self.write_deletelist() + elif state == 'C': + self.clear_from_conflictlist(filename) + elif state in ('A', 'R') or state == '!' and filename in self.to_be_added: + self.to_be_added.remove(filename) + self.write_addlist() + + @staticmethod + def init_package(apiurl, project, package, dir, size_limit=None, meta=False, progress_obj=None): + global store + + if not os.path.exists(dir): + os.mkdir(dir) + elif not os.path.isdir(dir): + raise oscerr.OscIOError(None, 'error: \'%s\' is no directory' % dir) + if os.path.exists(os.path.join(dir, store)): + raise oscerr.OscIOError(None, 'error: \'%s\' is already an initialized osc working copy' % dir) + else: + os.mkdir(os.path.join(dir, store)) + store_write_project(dir, project) + store_write_string(dir, '_package', package + '\n') + store_write_apiurl(dir, apiurl) + if meta: + store_write_string(dir, '_meta_mode', '') + if size_limit: + store_write_string(dir, '_size_limit', str(size_limit) + '\n') + store_write_string(dir, '_files', '<directory />' + '\n') + store_write_string(dir, '_osclib_version', __store_version__ + '\n') + return Package(dir, progress_obj=progress_obj, size_limit=size_limit) + + +class AbstractState: + """ + Base class which represents state-like objects (<review />, <state />). + """ + def __init__(self, tag): + self.__tag = tag + + def get_node_attrs(self): + """return attributes for the tag/element""" + raise NotImplementedError() + + def get_node_name(self): + """return tag/element name""" + return self.__tag + + def get_comment(self): + """return data from <comment /> tag""" + raise NotImplementedError() + + def get_description(self): + """return data from <description /> tag""" + raise NotImplementedError() + + def to_xml(self): + """serialize object to XML""" + root = ET.Element(self.get_node_name()) + for attr in self.get_node_attrs(): + val = getattr(self, attr) + if not val is None: + root.set(attr, val) + if self.get_description(): + ET.SubElement(root, 'description').text = self.get_description() + if self.get_comment(): + ET.SubElement(root, 'comment').text = self.get_comment() + return root + + def to_str(self): + """return "pretty" XML data""" + root = self.to_xml() + xmlindent(root) + return ET.tostring(root, encoding=ET_ENCODING) + + +class ReviewState(AbstractState): + """Represents the review state in a request""" + def __init__(self, review_node): + if not review_node.get('state'): + raise oscerr.APIError('invalid review node (state attr expected): %s' % \ + ET.tostring(review_node, encoding=ET_ENCODING)) + AbstractState.__init__(self, review_node.tag) + self.state = review_node.get('state') + self.by_user = review_node.get('by_user') + self.by_group = review_node.get('by_group') + self.by_project = review_node.get('by_project') + self.by_package = review_node.get('by_package') + self.who = review_node.get('who') + self.when = review_node.get('when') + self.comment = '' + if not review_node.find('comment') is None and \ + review_node.find('comment').text: + self.comment = review_node.find('comment').text.strip() + + def get_node_attrs(self): + return ('state', 'by_user', 'by_group', 'by_project', 'by_package', 'who', 'when') + + def get_comment(self): + return self.comment + + def get_description(self): + return None + + +class RequestHistory(AbstractState): + """Represents a history element of a request""" + re_name = re.compile(r'^Request (?:got )?([^\s]+)$') + + def __init__(self, history_node): + AbstractState.__init__(self, history_node.tag) + self.who = history_node.get('who') + self.when = history_node.get('when') + if not history_node.find('description') is None and \ + history_node.find('description').text: + # OBS 2.6 + self.description = history_node.find('description').text.strip() + else: + # OBS 2.5 and before + self.description = history_node.get('name') + self.comment = '' + if not history_node.find('comment') is None and \ + history_node.find('comment').text: + self.comment = history_node.find('comment').text.strip() + self.name = self._parse_name(history_node) + + def _parse_name(self, history_node): + name = history_node.get('name', None) + if name is not None: + # OBS 2.5 and before + return name + mo = self.re_name.search(self.description) + if mo is not None: + return mo.group(1) + return self.description + + def get_node_attrs(self): + return ('who', 'when') + + def get_description(self): + return self.description + + def get_comment(self): + return self.comment + + +class RequestState(AbstractState): + """Represents the state of a request""" + def __init__(self, state_node): + if not state_node.get('name'): + raise oscerr.APIError('invalid request state node (name attr expected): %s' % \ + ET.tostring(state_node, encoding=ET_ENCODING)) + AbstractState.__init__(self, state_node.tag) + self.name = state_node.get('name') + self.who = state_node.get('who') + self.when = state_node.get('when') + if state_node.find('description') is None: + # OBS 2.6 has it always, before it did not exist + self.description = state_node.get('description') + self.comment = '' + if not state_node.find('comment') is None and \ + state_node.find('comment').text: + self.comment = state_node.find('comment').text.strip() + + def get_node_attrs(self): + return ('name', 'who', 'when') + + def get_comment(self): + return self.comment + + def get_description(self): + return None + + +class Action: + """ + Represents a <action /> element of a Request. + This class is quite common so that it can be used for all different + action types. Note: instances only provide attributes for their specific + type. + Examples: + r = Action('set_bugowner', tgt_project='foo', person_name='buguser') + # available attributes: r.type (== 'set_bugowner'), r.tgt_project (== 'foo'), r.tgt_package (== None) + r.to_str() -> + <action type="set_bugowner"> + <target project="foo" /> + <person name="buguser" /> + </action> + ## + r = Action('delete', tgt_project='foo', tgt_package='bar') + # available attributes: r.type (== 'delete'), r.tgt_project (== 'foo'), r.tgt_package (=='bar') + r.to_str() -> + <action type="delete"> + <target package="bar" project="foo" /> + </action> + """ + + # allowed types + the corresponding (allowed) attributes + type_args = {'submit': ('src_project', 'src_package', 'src_rev', 'tgt_project', 'tgt_package', 'opt_sourceupdate', + 'acceptinfo_rev', 'acceptinfo_srcmd5', 'acceptinfo_xsrcmd5', 'acceptinfo_osrcmd5', + 'acceptinfo_oxsrcmd5', 'opt_updatelink', 'opt_makeoriginolder'), + 'add_role': ('tgt_project', 'tgt_package', 'person_name', 'person_role', 'group_name', 'group_role'), + 'set_bugowner': ('tgt_project', 'tgt_package', 'person_name', 'group_name'), + 'maintenance_release': ('src_project', 'src_package', 'src_rev', 'tgt_project', 'tgt_package', 'person_name', + 'acceptinfo_rev', 'acceptinfo_srcmd5', 'acceptinfo_xsrcmd5', 'acceptinfo_osrcmd5', + 'acceptinfo_oxsrcmd5', 'acceptinfo_oproject', 'acceptinfo_opackage'), + 'maintenance_incident': ('src_project', 'src_package', 'src_rev', 'tgt_project', 'tgt_package', 'tgt_releaseproject', 'person_name', 'opt_sourceupdate', 'opt_makeoriginolder', + 'acceptinfo_rev', 'acceptinfo_srcmd5', 'acceptinfo_xsrcmd5', 'acceptinfo_osrcmd5', + 'acceptinfo_oxsrcmd5'), + 'delete': ('tgt_project', 'tgt_package', 'tgt_repository'), + 'change_devel': ('src_project', 'src_package', 'tgt_project', 'tgt_package'), + 'group': ('grouped_id', )} + # attribute prefix to element name map (only needed for abbreviated attributes) + prefix_to_elm = {'src': 'source', 'tgt': 'target', 'opt': 'options'} + + def __init__(self, type, **kwargs): + if not type in Action.type_args.keys(): + raise oscerr.WrongArgs('invalid action type: \'%s\'' % type) + self.type = type + for i in kwargs.keys(): + if not i in Action.type_args[type]: + raise oscerr.WrongArgs('invalid argument: \'%s\'' % i) + # set all type specific attributes + for i in Action.type_args[type]: + setattr(self, i, kwargs.get(i)) + + def to_xml(self): + """ + Serialize object to XML. + The xml tag names and attributes are constructed from the instance's attributes. + Example: + self.group_name -> tag name is "group", attribute name is "name" + self.src_project -> tag name is "source" (translated via prefix_to_elm dict), + attribute name is "project" + Attributes prefixed with "opt_" need a special handling, the resulting xml should + look like this: opt_updatelink -> <options><updatelink>value</updatelink></options>. + Attributes which are "None" will be skipped. + """ + root = ET.Element('action', type=self.type) + for i in Action.type_args[self.type]: + prefix, attr = i.split('_', 1) + vals = getattr(self, i) + # single, plain elements are _not_ stored in a list + plain = False + if vals is None: + continue + elif not hasattr(vals, 'append'): + vals = [vals] + plain = True + for val in vals: + elm = root.find(Action.prefix_to_elm.get(prefix, prefix)) + if elm is None or not plain: + elm = ET.Element(Action.prefix_to_elm.get(prefix, prefix)) + root.append(elm) + if prefix == 'opt': + ET.SubElement(elm, attr).text = val + else: + elm.set(attr, val) + return root + + def to_str(self): + """return "pretty" XML data""" + root = self.to_xml() + xmlindent(root) + return ET.tostring(root, encoding=ET_ENCODING) + + @staticmethod + def from_xml(action_node): + """create action from XML""" + if action_node is None or \ + not action_node.get('type') in Action.type_args.keys() or \ + not action_node.tag in ('action', 'submit'): + raise oscerr.WrongArgs('invalid argument') + elm_to_prefix = dict([(i[1], i[0]) for i in Action.prefix_to_elm.items()]) + kwargs = {} + for node in action_node: + prefix = elm_to_prefix.get(node.tag, node.tag) + if prefix == 'opt': + data = [('opt_%s' % opt.tag, opt.text.strip()) for opt in node if opt.text] + else: + data = [('%s_%s' % (prefix, k), v) for k, v in node.items()] + # it would be easier to store everything in a list but in + # this case we would lose some "structure" (see to_xml) + for k, v in data: + if k in kwargs: + l = kwargs[k] + if not hasattr(l, 'append'): + l = [l] + kwargs[k] = l + l.append(v) + else: + kwargs[k] = v + return Action(action_node.get('type'), **kwargs) + + +class Request: + """Represents a request (<request />)""" + + def __init__(self): + self._init_attributes() + + def _init_attributes(self): + """initialize attributes with default values""" + self.reqid = None + self.title = '' + self.description = '' + self.priority = None + self.state = None + self.accept_at = None + self.actions = [] + self.statehistory = [] + self.reviews = [] + + def read(self, root): + """read in a request""" + self._init_attributes() + if not root.get('id'): + raise oscerr.APIError('invalid request: %s\n' % ET.tostring(root, encoding=ET_ENCODING)) + self.reqid = root.get('id') + if root.find('state') is None: + raise oscerr.APIError('invalid request (state expected): %s\n' % ET.tostring(root, encoding=ET_ENCODING)) + self.state = RequestState(root.find('state')) + action_nodes = root.findall('action') + if not action_nodes: + # check for old-style requests + for i in root.findall('submit'): + i.set('type', 'submit') + action_nodes.append(i) + for action in action_nodes: + self.actions.append(Action.from_xml(action)) + for review in root.findall('review'): + self.reviews.append(ReviewState(review)) + for history_element in root.findall('history'): + self.statehistory.append(RequestHistory(history_element)) + if not root.find('priority') is None and root.find('priority').text: + self.priority = root.find('priority').text.strip() + if not root.find('accept_at') is None and root.find('accept_at').text: + self.accept_at = root.find('accept_at').text.strip() + if not root.find('title') is None: + self.title = root.find('title').text.strip() + if not root.find('description') is None and root.find('description').text: + self.description = root.find('description').text.strip() + + def add_action(self, type, **kwargs): + """add a new action to the request""" + self.actions.append(Action(type, **kwargs)) + + def get_actions(self, *types): + """ + get all actions with a specific type + (if types is empty return all actions) + """ + if not types: + return self.actions + return [i for i in self.actions if i.type in types] + + def get_creator(self): + """return the creator of the request""" + if len(self.statehistory): + return self.statehistory[0].who + return self.state.who + + def to_xml(self): + """serialize object to XML""" + root = ET.Element('request') + if not self.reqid is None: + root.set('id', self.reqid) + for action in self.actions: + root.append(action.to_xml()) + if not self.state is None: + root.append(self.state.to_xml()) + for review in self.reviews: + root.append(review.to_xml()) + for hist in self.statehistory: + root.append(hist.to_xml()) + if self.title: + ET.SubElement(root, 'title').text = self.title + if self.description: + ET.SubElement(root, 'description').text = self.description + if self.accept_at: + ET.SubElement(root, 'accept_at').text = self.accept_at + if self.priority: + ET.SubElement(root, 'priority').text = self.priority + return root + + def to_str(self): + """return "pretty" XML data""" + root = self.to_xml() + xmlindent(root) + return ET.tostring(root, encoding=ET_ENCODING) + + def accept_at_in_hours(self, hours): + """set auto accept_at time""" + import datetime + + now = datetime.datetime.utcnow() + now = now + datetime.timedelta(hours=hours) + self.accept_at = now.isoformat() + + @staticmethod + def format_review(review, show_srcupdate=False): + """ + format a review depending on the reviewer's type. + A dict which contains the formatted str's is returned. + """ + + d = {'state': '%s:' % review.state} + if review.by_package: + d['by'] = '%s/%s' % (review.by_project, review.by_package) + d['type'] = 'Package' + elif review.by_project: + d['by'] = '%s' % review.by_project + d['type'] = 'Project' + elif review.by_group: + d['by'] = '%s' % review.by_group + d['type'] = 'Group' + else: + d['by'] = '%s' % review.by_user + d['type'] = 'User' + if review.who: + d['by'] += '(%s)' % review.who + return d + + def format_action(self, action, show_srcupdate=False): + """ + format an action depending on the action's type. + A dict which contains the formatted str's is returned. + """ + def prj_pkg_join(prj, pkg, repository=None): + if not pkg: + if not repository: + return prj or '' + return '%s(%s)' % (prj, repository) + return '%s/%s' % (prj, pkg) + + d = {'type': '%s:' % action.type} + if action.type == 'set_bugowner': + if action.person_name: + d['source'] = action.person_name + if action.group_name: + d['source'] = 'group:%s' % action.group_name + d['target'] = prj_pkg_join(action.tgt_project, action.tgt_package) + elif action.type == 'change_devel': + d['source'] = prj_pkg_join(action.tgt_project, action.tgt_package) + d['target'] = 'developed in %s' % prj_pkg_join(action.src_project, action.src_package) + elif action.type == 'maintenance_incident': + d['source'] = '%s ->' % action.src_project + if action.src_package: + d['source'] = '%s' % prj_pkg_join(action.src_project, action.src_package) + if action.src_rev: + d['source'] = d['source'] + '@%s' % action.src_rev + d['source'] = d['source'] + ' ->' + d['target'] = action.tgt_project + if action.tgt_releaseproject: + d['target'] += " (release in " + action.tgt_releaseproject + ")" + srcupdate = ' ' + if action.opt_sourceupdate and show_srcupdate: + srcupdate = '(%s)' % action.opt_sourceupdate + elif action.type == 'maintenance_release': + d['source'] = '%s' % prj_pkg_join(action.src_project, action.src_package) + if action.src_rev: + d['source'] = d['source'] + '@%s' % action.src_rev + d['source'] = d['source'] + ' ->' + d['target'] = prj_pkg_join(action.tgt_project, action.tgt_package) + elif action.type == 'submit': + d['source'] = '%s' % prj_pkg_join(action.src_project, action.src_package) + if action.src_rev: + d['source'] = d['source'] + '@%s' % action.src_rev + if action.opt_sourceupdate and show_srcupdate: + d['source'] = d['source'] + '(%s)' % action.opt_sourceupdate + d['source'] = d['source'] + ' ->' + tgt_package = action.tgt_package + if action.src_package == action.tgt_package: + tgt_package = '' + d['target'] = prj_pkg_join(action.tgt_project, tgt_package) + if action.opt_makeoriginolder: + d['target'] = d['target'] + ' ***make origin older***' + if action.opt_updatelink: + d['target'] = d['target'] + ' ***update link***' + elif action.type == 'add_role': + roles = [] + if action.person_name and action.person_role: + roles.append('person: %s as %s' % (action.person_name, action.person_role)) + if action.group_name and action.group_role: + roles.append('group: %s as %s' % (action.group_name, action.group_role)) + d['source'] = ', '.join(roles) + d['target'] = prj_pkg_join(action.tgt_project, action.tgt_package) + elif action.type == 'delete': + d['source'] = '' + d['target'] = prj_pkg_join(action.tgt_project, action.tgt_package, action.tgt_repository) + elif action.type == 'group': + l = action.grouped_id + if l is None: + # there may be no requests in a group action + l = '' + if not hasattr(l, 'append'): + l = [l] + d['source'] = ', '.join(l) + ' ->' + d['target'] = self.reqid + else: + raise oscerr.APIError('Unknown action type %s\n' % action.type) + return d + + def list_view(self): + """return "list view" format""" + import textwrap + lines = ['%6s State:%-10s By:%-12s When:%-19s' % (self.reqid, self.state.name, self.state.who, self.state.when)] + tmpl = ' %(type)-16s %(source)-50s %(target)s' + for action in self.actions: + lines.append(tmpl % self.format_action(action)) + tmpl = ' Review by %(type)-10s is %(state)-10s %(by)-50s' + for review in self.reviews: + lines.append(tmpl % Request.format_review(review)) + history = ['%s: %s' % (hist.description, hist.who) for hist in self.statehistory] + if history: + lines.append(' From: %s' % ' -> '.join(history)) + if self.description: + lines.append(textwrap.fill(self.description, width=80, initial_indent=' Descr: ', + subsequent_indent=' ')) + lines.append(textwrap.fill(self.state.comment, width=80, initial_indent=' Comment: ', + subsequent_indent=' ')) + return '\n'.join(lines) + + def __str__(self): + """return "detailed" format""" + lines = ['Request: #%s\n' % self.reqid] + if self.accept_at and self.state.name in [ 'new', 'review' ]: + lines.append(' *** This request will get automatically accepted after '+self.accept_at+' ! ***\n') + if self.priority in [ 'critical', 'important' ] and self.state.name in [ 'new', 'review' ]: + lines.append(' *** This request has classified as '+self.priority+' ! ***\n') + + for action in self.actions: + tmpl = ' %(type)-13s %(source)s %(target)s' + if action.type == 'delete': + # remove 1 whitespace because source is empty + tmpl = ' %(type)-12s %(source)s %(target)s' + lines.append(tmpl % self.format_action(action, show_srcupdate=True)) + lines.append('\n\nMessage:') + if self.description: + lines.append(self.description) + else: + lines.append('<no message>') + if self.state: + lines.append('\nState: %-10s %-12s %s' % (self.state.name, self.state.when, self.state.who)) + lines.append('Comment: %s' % (self.state.comment or '<no comment>')) + + indent = '\n ' + tmpl = '%(state)-10s %(by)-50s %(when)-12s %(who)-20s %(comment)s' + reviews = [] + for review in reversed(self.reviews): + d = {'state': review.state} + if review.by_user: + d['by'] = "User: " + review.by_user + if review.by_group: + d['by'] = "Group: " + review.by_group + if review.by_package: + d['by'] = "Package: " + review.by_project + "/" + review.by_package + elif review.by_project: + d['by'] = "Project: " + review.by_project + d['when'] = review.when or '' + d['who'] = review.who or '' + d['comment'] = '' + if review.comment: + d['comment'] = '\n ' + review.comment + reviews.append(tmpl % d) + if reviews: + lines.append('\nReview: %s' % indent.join(reviews)) + + tmpl = '%(when)-10s %(who)-12s %(desc)s' + histories = [] + for hist in reversed(self.statehistory): + d = {'when': hist.when, 'who': hist.who, 'desc': hist.description} + histories.append(tmpl % d) + if histories: + lines.append('\nHistory: %s' % indent.join(histories)) + + return '\n'.join(lines) + + def __cmp__(self, other): + return cmp(int(self.reqid), int(other.reqid)) + + def create(self, apiurl, addrevision=False): + """create a new request""" + query = {'cmd' : 'create' } + if addrevision: + query['addrevision'] = "1" + u = makeurl(apiurl, ['request'], query=query) + f = http_POST(u, data=self.to_str()) + root = ET.fromstring(f.read()) + self.read(root) + +def shorttime(t): + """format time as Apr 02 18:19 + or Apr 02 2005 + depending on whether it is in the current year + """ + import time + + if time.gmtime()[0] == time.gmtime(t)[0]: + # same year + return time.strftime('%b %d %H:%M', time.gmtime(t)) + else: + return time.strftime('%b %d %Y', time.gmtime(t)) + + +def is_project_dir(d): + global store + + return os.path.exists(os.path.join(d, store, '_project')) and not \ + os.path.exists(os.path.join(d, store, '_package')) + + +def is_package_dir(d): + global store + + return os.path.exists(os.path.join(d, store, '_project')) and \ + os.path.exists(os.path.join(d, store, '_package')) + +def parse_disturl(disturl): + """Parse a disturl, returns tuple (apiurl, project, source, repository, + revision), else raises an oscerr.WrongArgs exception + """ + + global DISTURL_RE + + m = DISTURL_RE.match(disturl) + if not m: + raise oscerr.WrongArgs("`%s' does not look like disturl" % disturl) + + apiurl = m.group('apiurl') + if apiurl.split('.')[0] != 'api': + apiurl = 'https://api.' + ".".join(apiurl.split('.')[1:]) + return (apiurl, m.group('project'), m.group('source'), m.group('repository'), m.group('revision')) + +def parse_buildlogurl(buildlogurl): + """Parse a build log url, returns a tuple (apiurl, project, package, + repository, arch), else raises oscerr.WrongArgs exception""" + + global BUILDLOGURL_RE + + m = BUILDLOGURL_RE.match(buildlogurl) + if not m: + raise oscerr.WrongArgs('\'%s\' does not look like url with a build log' % buildlogurl) + + return (m.group('apiurl'), m.group('project'), m.group('package'), m.group('repository'), m.group('arch')) + +def slash_split(l): + """Split command line arguments like 'foo/bar' into 'foo' 'bar'. + This is handy to allow copy/paste a project/package combination in this form. + + Trailing slashes are removed before the split, because the split would + otherwise give an additional empty string. + """ + r = [] + for i in l: + i = i.rstrip('/') + r += i.split('/') + return r + +def expand_proj_pack(args, idx=0, howmany=0): + """looks for occurance of '.' at the position idx. + If howmany is 2, both proj and pack are expanded together + using the current directory, or none of them if not possible. + If howmany is 0, proj is expanded if possible, then, if there + is no idx+1 element in args (or args[idx+1] == '.'), pack is also + expanded, if possible. + If howmany is 1, only proj is expanded if possible. + + If args[idx] does not exist, an implicit '.' is assumed. + If not enough elements up to idx exist, an error is raised. + + See also parseargs(args), slash_split(args), findpacs(args) + All these need unification, somehow. + """ + + # print args,idx,howmany + + if len(args) < idx: + raise oscerr.WrongArgs('not enough argument, expected at least %d' % idx) + + if len(args) == idx: + args += '.' + if args[idx+0] == '.': + if howmany == 0 and len(args) > idx+1: + if args[idx+1] == '.': + # we have two dots. + # remove one dot and make sure to expand both proj and pack + args.pop(idx+1) + howmany = 2 + else: + howmany = 1 + # print args,idx,howmany + + args[idx+0] = store_read_project('.') + if howmany == 0: + try: + package = store_read_package('.') + args.insert(idx+1, package) + except: + pass + elif howmany == 2: + package = store_read_package('.') + args.insert(idx+1, package) + return args + + +def findpacs(files, progress_obj=None): + """collect Package objects belonging to the given files + and make sure each Package is returned only once""" + pacs = [] + for f in files: + p = filedir_to_pac(f, progress_obj) + known = None + for i in pacs: + if i.name == p.name: + known = i + break + if known: + i.merge(p) + else: + pacs.append(p) + return pacs + + +def filedir_to_pac(f, progress_obj=None): + """Takes a working copy path, or a path to a file inside a working copy, + and returns a Package object instance + + If the argument was a filename, add it onto the "todo" list of the Package """ + + if os.path.isdir(f): + wd = f + p = Package(wd, progress_obj=progress_obj) + else: + wd = os.path.dirname(f) or os.curdir + p = Package(wd, progress_obj=progress_obj) + p.todo = [ os.path.basename(f) ] + return p + + +def read_filemeta(dir): + global store + + msg = '\'%s\' is not a valid working copy.' % dir + filesmeta = os.path.join(dir, store, '_files') + if not is_package_dir(dir): + raise oscerr.NoWorkingCopy(msg) + if not os.path.isfile(filesmeta): + raise oscerr.NoWorkingCopy('%s (%s does not exist)' % (msg, filesmeta)) + + try: + r = ET.parse(filesmeta) + except SyntaxError as e: + raise oscerr.NoWorkingCopy('%s\nWhen parsing .osc/_files, the following error was encountered:\n%s' % (msg, e)) + return r + +def store_readlist(dir, name): + global store + + r = [] + if os.path.exists(os.path.join(dir, store, name)): + r = [line.rstrip('\n') for line in open(os.path.join(dir, store, name), 'r')] + return r + +def read_tobeadded(dir): + return store_readlist(dir, '_to_be_added') + +def read_tobedeleted(dir): + return store_readlist(dir, '_to_be_deleted') + +def read_sizelimit(dir): + global store + + r = None + fname = os.path.join(dir, store, '_size_limit') + + if os.path.exists(fname): + r = open(fname).readline().strip() + + if r is None or not r.isdigit(): + return None + return int(r) + +def read_inconflict(dir): + return store_readlist(dir, '_in_conflict') + +def parseargs(list_of_args): + """Convenience method osc's commandline argument parsing. + + If called with an empty tuple (or list), return a list containing the current directory. + Otherwise, return a list of the arguments.""" + if list_of_args: + return list(list_of_args) + else: + return [os.curdir] + + +def statfrmt(statusletter, filename): + return '%s %s' % (statusletter, filename) + + +def pathjoin(a, *p): + """Join two or more pathname components, inserting '/' as needed. Cut leading ./""" + path = os.path.join(a, *p) + if path.startswith('./'): + path = path[2:] + return path + + +def makeurl(baseurl, l, query=[]): + """Given a list of path compoments, construct a complete URL. + + Optional parameters for a query string can be given as a list, as a + dictionary, or as an already assembled string. + In case of a dictionary, the parameters will be urlencoded by this + function. In case of a list not -- this is to be backwards compatible. + """ + + if conf.config['verbose'] > 1: + print('makeurl:', baseurl, l, query) + + if isinstance(query, type(list())): + query = '&'.join(query) + elif isinstance(query, type(dict())): + query = urlencode(query) + + scheme, netloc, path = urlsplit(baseurl)[0:3] + return urlunsplit((scheme, netloc, '/'.join([path] + list(l)), query, '')) + + +def http_request(method, url, headers={}, data=None, file=None): + """wrapper around urllib2.urlopen for error handling, + and to support additional (PUT, DELETE) methods""" + def create_memoryview(obj): + if sys.version_info < (2, 7, 99): + # obj might be a mmap and python 2.7's mmap does not + # behave like a bytearray (a bytearray in turn can be used + # to create the memoryview). For now simply return a buffer + return buffer(obj) + return memoryview(obj) + + filefd = None + + if conf.config['http_debug']: + print('\n\n--', method, url, file=sys.stderr) + + if method == 'POST' and not file and not data: + # adding data to an urllib2 request transforms it into a POST + data = '' + + req = URLRequest(url) + api_host_options = {} + apiurl = conf.extract_known_apiurl(url) + if apiurl is not None: + # ok no external request + install_opener(conf._build_opener(apiurl)) + api_host_options = conf.get_apiurl_api_host_options(apiurl) + for header, value in api_host_options['http_headers']: + req.add_header(header, value) + + req.get_method = lambda: method + + # POST requests are application/x-www-form-urlencoded per default + # but sending data requires an octet-stream type + if method == 'PUT' or (method == 'POST' and (data or file)): + req.add_header('Content-Type', 'application/octet-stream') + + if isinstance(headers, type({})): + for i in headers.keys(): + print(headers[i]) + req.add_header(i, headers[i]) + + if file and not data: + size = os.path.getsize(file) + if size < 1024*512: + data = open(file, 'rb').read() + else: + import mmap + filefd = open(file, 'rb') + try: + if sys.platform[:3] != 'win': + data = mmap.mmap(filefd.fileno(), os.path.getsize(file), mmap.MAP_SHARED, mmap.PROT_READ) + else: + data = mmap.mmap(filefd.fileno(), os.path.getsize(file)) + data = create_memoryview(data) + except EnvironmentError as e: + if e.errno == 19: + sys.exit('\n\n%s\nThe file \'%s\' could not be memory mapped. It is ' \ + '\non a filesystem which does not support this.' % (e, file)) + elif hasattr(e, 'winerror') and e.winerror == 5: + # falling back to the default io + data = open(file, 'rb').read() + else: + raise + + if conf.config['debug']: print(method, url, file=sys.stderr) + + try: + if isinstance(data, str): + data = bytes(data, "utf-8") + fd = urlopen(req, data=data) + + finally: + if hasattr(conf.cookiejar, 'save'): + conf.cookiejar.save(ignore_discard=True) + + if filefd: filefd.close() + + return fd + + +def http_GET(*args, **kwargs): return http_request('GET', *args, **kwargs) +def http_POST(*args, **kwargs): return http_request('POST', *args, **kwargs) +def http_PUT(*args, **kwargs): return http_request('PUT', *args, **kwargs) +def http_DELETE(*args, **kwargs): return http_request('DELETE', *args, **kwargs) + + +def check_store_version(dir): + global store + + versionfile = os.path.join(dir, store, '_osclib_version') + try: + v = open(versionfile).read().strip() + except: + v = '' + + if v == '': + msg = 'Error: "%s" is not an osc package working copy.' % os.path.abspath(dir) + if os.path.exists(os.path.join(dir, '.svn')): + msg = msg + '\nTry svn instead of osc.' + raise oscerr.NoWorkingCopy(msg) + + if v != __store_version__: + if v in ['0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '0.95', '0.96', '0.97', '0.98', '0.99']: + # version is fine, no migration needed + f = open(versionfile, 'w') + f.write(__store_version__ + '\n') + f.close() + return + msg = 'The osc metadata of your working copy "%s"' % dir + msg += '\nhas __store_version__ = %s, but it should be %s' % (v, __store_version__) + msg += '\nPlease do a fresh checkout or update your client. Sorry about the inconvenience.' + raise oscerr.WorkingCopyWrongVersion(msg) + + +def meta_get_packagelist(apiurl, prj, deleted=None, expand=False): + + query = {} + if deleted: + query['deleted'] = 1 + if expand: + query['expand'] = 1 + + u = makeurl(apiurl, ['source', prj], query) + f = http_GET(u) + root = ET.parse(f).getroot() + return [ node.get('name') for node in root.findall('entry') ] + + +def meta_get_filelist(apiurl, prj, package, verbose=False, expand=False, revision=None, meta=False, deleted=False): + """return a list of file names, + or a list File() instances if verbose=True""" + + query = {} + if deleted: + query['deleted'] = 1 + if expand: + query['expand'] = 1 + if meta: + query['meta'] = 1 + if revision: + query['rev'] = revision + else: + query['rev'] = 'latest' + + u = makeurl(apiurl, ['source', prj, package], query=query) + f = http_GET(u) + root = ET.parse(f).getroot() + + if not verbose: + return [ node.get('name') for node in root.findall('entry') ] + + else: + l = [] + # rev = int(root.get('rev')) # don't force int. also allow srcmd5 here. + rev = root.get('rev') + for node in root.findall('entry'): + f = File(node.get('name'), + node.get('md5'), + int(node.get('size')), + int(node.get('mtime'))) + f.rev = rev + l.append(f) + return l + + +def meta_get_project_list(apiurl, deleted=None): + query = {} + if deleted: + query['deleted'] = 1 + + u = makeurl(apiurl, ['source'], query) + f = http_GET(u) + root = ET.parse(f).getroot() + return sorted([ node.get('name') for node in root if node.get('name')]) + + +def show_project_meta(apiurl, prj): + url = makeurl(apiurl, ['source', prj, '_meta']) + f = http_GET(url) + return f.readlines() + + +def show_project_conf(apiurl, prj): + url = makeurl(apiurl, ['source', prj, '_config']) + f = http_GET(url) + return f.readlines() + + +def show_package_trigger_reason(apiurl, prj, pac, repo, arch): + url = makeurl(apiurl, ['build', prj, repo, arch, pac, '_reason']) + try: + f = http_GET(url) + return f.read() + except HTTPError as e: + e.osc_msg = 'Error getting trigger reason for project \'%s\' package \'%s\'' % (prj, pac) + raise + + +def show_package_meta(apiurl, prj, pac, meta=False): + query = {} + if meta: + query['meta'] = 1 + + # The fake packages _project has no _meta file + if pac.startswith('_project'): + return "" + + url = makeurl(apiurl, ['source', prj, pac, '_meta'], query) + try: + f = http_GET(url) + return f.readlines() + except HTTPError as e: + e.osc_msg = 'Error getting meta for project \'%s\' package \'%s\'' % (prj, pac) + raise + + +def show_attribute_meta(apiurl, prj, pac, subpac, attribute, with_defaults, with_project): + path = [] + path.append('source') + path.append(prj) + if pac: + path.append(pac) + if pac and subpac: + path.append(subpac) + path.append('_attribute') + if attribute: + path.append(attribute) + query = [] + if with_defaults: + query.append("with_default=1") + if with_project: + query.append("with_project=1") + url = makeurl(apiurl, path, query) + try: + f = http_GET(url) + return f.readlines() + except HTTPError as e: + e.osc_msg = 'Error getting meta for project \'%s\' package \'%s\'' % (prj, pac) + raise + + +def show_devel_project(apiurl, prj, pac): + m = show_package_meta(apiurl, prj, pac) + node = ET.fromstring(''.join(m)).find('devel') + if node is None: + return None, None + else: + return node.get('project'), node.get('package', None) + + +def set_devel_project(apiurl, prj, pac, devprj=None, devpac=None): + meta = show_package_meta(apiurl, prj, pac) + root = ET.fromstring(''.join(meta)) + node = root.find('devel') + if node is None: + if devprj is None: + return + node = ET.Element('devel') + root.append(node) + else: + if devprj is None: + root.remove(node) + else: + node.clear() + if devprj: + node.set('project', devprj) + if devpac: + node.set('package', devpac) + url = makeurl(apiurl, ['source', prj, pac, '_meta']) + mf = metafile(url, ET.tostring(root, encoding=ET_ENCODING)) + mf.sync() + + +def show_package_disabled_repos(apiurl, prj, pac): + m = show_package_meta(apiurl, prj, pac) + #FIXME: don't work if all repos of a project are disabled and only some are enabled since <disable/> is empty + try: + root = ET.fromstring(''.join(m)) + elm = root.find('build') + r = [ node.get('repository') for node in elm.findall('disable')] + return r + except: + return None + + +def show_pattern_metalist(apiurl, prj): + url = makeurl(apiurl, ['source', prj, '_pattern']) + try: + f = http_GET(url) + tree = ET.parse(f) + except HTTPError as e: + e.osc_msg = 'show_pattern_metalist: Error getting pattern list for project \'%s\'' % prj + raise + r = sorted([ node.get('name') for node in tree.getroot() ]) + return r + + +def show_pattern_meta(apiurl, prj, pattern): + url = makeurl(apiurl, ['source', prj, '_pattern', pattern]) + try: + f = http_GET(url) + return f.readlines() + except HTTPError as e: + e.osc_msg = 'show_pattern_meta: Error getting pattern \'%s\' for project \'%s\'' % (pattern, prj) + raise + +def show_configuration(apiurl): + u = makeurl(apiurl, ['public', 'configuration']) + f = http_GET(u) + return f.readlines() + + +class metafile: + """metafile that can be manipulated and is stored back after manipulation.""" + def __init__(self, url, input, change_is_required=False, file_ext='.xml'): + import tempfile + + self.url = url + self.change_is_required = change_is_required + (fd, self.filename) = tempfile.mkstemp(prefix = 'osc_metafile.', suffix = file_ext) + f = os.fdopen(fd, 'w') + f.write(''.join(input)) + f.close() + self.hash_orig = dgst(self.filename) + + def sync(self): + if self.change_is_required and self.hash_orig == dgst(self.filename): + print('File unchanged. Not saving.') + os.unlink(self.filename) + return + + print('Sending meta data...') + # don't do any exception handling... it's up to the caller what to do in case + # of an exception + http_PUT(self.url, file=self.filename) + os.unlink(self.filename) + print('Done.') + + def edit(self): + try: + while True: + run_editor(self.filename) + try: + self.sync() + break + except HTTPError as e: + error_help = "%d" % e.code + if e.hdrs.get('X-Opensuse-Errorcode'): + error_help = "%s (%d)" % (e.hdrs.get('X-Opensuse-Errorcode'), e.code) + + print('BuildService API error:', error_help, file=sys.stderr) + # examine the error - we can't raise an exception because we might want + # to try again + data = e.read() + if '<summary>' in data: + print(data.split('<summary>')[1].split('</summary>')[0], file=sys.stderr) + ri = raw_input('Try again? ([y/N]): ') + if ri not in ['y', 'Y']: + break + finally: + self.discard() + + def discard(self): + if os.path.exists(self.filename): + print('discarding %s' % self.filename) + os.unlink(self.filename) + +# different types of metadata +metatypes = { 'prj': { 'path': 'source/%s/_meta', + 'template': new_project_templ, + 'file_ext': '.xml' + }, + 'pkg': { 'path' : 'source/%s/%s/_meta', + 'template': new_package_templ, + 'file_ext': '.xml' + }, + 'attribute': { 'path' : 'source/%s/%s/_meta', + 'template': new_attribute_templ, + 'file_ext': '.xml' + }, + 'prjconf': { 'path': 'source/%s/_config', + 'template': '', + 'file_ext': '.txt' + }, + 'user': { 'path': 'person/%s', + 'template': new_user_template, + 'file_ext': '.xml' + }, + 'pattern': { 'path': 'source/%s/_pattern/%s', + 'template': new_pattern_template, + 'file_ext': '.xml' + }, + } + +def meta_exists(metatype, + path_args=None, + template_args=None, + create_new=True, + apiurl=None): + + global metatypes + + if not apiurl: + apiurl = conf.config['apiurl'] + url = make_meta_url(metatype, path_args, apiurl) + try: + data = http_GET(url).readlines() + except HTTPError as e: + if e.code == 404 and create_new: + data = metatypes[metatype]['template'] + if template_args: + data = StringIO(data % template_args).readlines() + else: + raise e + + return data + +def make_meta_url(metatype, path_args=None, apiurl=None, force=False, remove_linking_repositories=False): + global metatypes + + if not apiurl: + apiurl = conf.config['apiurl'] + if metatype not in metatypes.keys(): + raise AttributeError('make_meta_url(): Unknown meta type \'%s\'' % metatype) + path = metatypes[metatype]['path'] + + if path_args: + path = path % path_args + + query = {} + if force: + query = { 'force': '1' } + if remove_linking_repositories: + query['remove_linking_repositories'] = '1' + + return makeurl(apiurl, [path], query) + + +def edit_meta(metatype, + path_args=None, + data=None, + template_args=None, + edit=False, + force=False, + remove_linking_repositories=False, + change_is_required=False, + apiurl=None): + + global metatypes + + if not apiurl: + apiurl = conf.config['apiurl'] + if not data: + data = meta_exists(metatype, + path_args, + template_args, + create_new = metatype != 'prjconf', # prjconf always exists, 404 => unknown prj + apiurl=apiurl) + + if edit: + change_is_required = True + + if metatype == 'pkg': + # check if the package is a link to a different project + project, package = path_args + orgprj = ET.fromstring(''.join(data)).get('project') + if orgprj is not None and unquote(project) != orgprj: + print('The package is linked from a different project.') + print('If you want to edit the meta of the package create first a branch.') + print(' osc branch %s %s %s' % (orgprj, package, unquote(project))) + print(' osc meta pkg %s %s -e' % (unquote(project), package)) + return + + url = make_meta_url(metatype, path_args, apiurl, force, remove_linking_repositories) + f = metafile(url, data, change_is_required, metatypes[metatype]['file_ext']) + + if edit: + f.edit() + else: + f.sync() + + +def show_files_meta(apiurl, prj, pac, revision=None, expand=False, linkrev=None, linkrepair=False, meta=False): + query = {} + if revision: + query['rev'] = revision + else: + query['rev'] = 'latest' + if linkrev: + query['linkrev'] = linkrev + elif conf.config['linkcontrol']: + query['linkrev'] = 'base' + if meta: + query['meta'] = 1 + if expand: + query['expand'] = 1 + if linkrepair: + query['emptylink'] = 1 + f = http_GET(makeurl(apiurl, ['source', prj, pac], query=query)) + return f.read() + +def show_upstream_srcmd5(apiurl, prj, pac, expand=False, revision=None, meta=False, include_service_files=False): + m = show_files_meta(apiurl, prj, pac, expand=expand, revision=revision, meta=meta) + et = ET.fromstring(''.join(m)) + if include_service_files: + try: + sinfo = et.find('serviceinfo') + if sinfo != None and sinfo.get('xsrcmd5') and not sinfo.get('error'): + return sinfo.get('xsrcmd5') + except: + pass + return et.get('srcmd5') + + +def show_upstream_xsrcmd5(apiurl, prj, pac, revision=None, linkrev=None, linkrepair=False, meta=False, include_service_files=False): + m = show_files_meta(apiurl, prj, pac, revision=revision, linkrev=linkrev, linkrepair=linkrepair, meta=meta, expand=include_service_files) + et = ET.fromstring(''.join(m)) + if include_service_files: + return et.get('srcmd5') + + li_node = et.find('linkinfo') + if li_node is None: + return None + + li = Linkinfo() + li.read(li_node) + + if li.haserror(): + raise oscerr.LinkExpandError(prj, pac, li.error) + return li.xsrcmd5 + + +def show_project_sourceinfo(apiurl, project, nofilename, *packages): + query = ['view=info'] + if packages: + query.extend(['package=%s' % quote_plus(p) for p in packages]) + if nofilename: + query.append('nofilename=1') + f = http_GET(makeurl(apiurl, ['source', project], query=query)) + return f.read() + + +def get_project_sourceinfo(apiurl, project, nofilename, *packages): + try: + si = show_project_sourceinfo(apiurl, project, nofilename, *packages) + except HTTPError as e: + # old API servers (e.g. 2.3.5) do not know the 'nofilename' parameter, so retry without + if e.code == 400 and nofilename: + return get_project_sourceinfo(apiurl, project, False, *packages) + if e.code != 414: + raise + if len(packages) == 1: + raise oscerr.APIError('package name too long: %s' % packages[0]) + n = len(packages) / 2 + pkgs = packages[:n] + res = get_project_sourceinfo(apiurl, project, nofilename, *pkgs) + pkgs = packages[n:] + res.update(get_project_sourceinfo(apiurl, project, nofilename, *pkgs)) + return res + root = ET.fromstring(si) + res = {} + for sinfo in root.findall('sourceinfo'): + res[sinfo.get('package')] = sinfo + return res + + +def show_upstream_rev_vrev(apiurl, prj, pac, revision=None, expand=False, meta=False): + m = show_files_meta(apiurl, prj, pac, revision=revision, expand=expand, meta=meta) + et = ET.fromstring(''.join(m)) + return et.get('rev'), et.get('vrev') + +def show_upstream_rev(apiurl, prj, pac, revision=None, expand=False, linkrev=None, meta=False, include_service_files=False): + m = show_files_meta(apiurl, prj, pac, revision=revision, expand=expand, linkrev=linkrev, meta=meta) + et = ET.fromstring(''.join(m)) + if include_service_files: + try: + sinfo = et.find('serviceinfo') + if sinfo != None and sinfo.get('xsrcmd5') and not sinfo.get('error'): + return sinfo.get('xsrcmd5') + except: + pass + return et.get('rev') + + +def read_meta_from_spec(specfile, *args): + import codecs, re + """ + Read tags and sections from spec file. To read out + a tag the passed argument mustn't end with a colon. To + read out a section the passed argument must start with + a '%'. + This method returns a dictionary which contains the + requested data. + """ + + if not os.path.isfile(specfile): + raise oscerr.OscIOError(None, '\'%s\' is not a regular file' % specfile) + + try: + lines = codecs.open(specfile, 'r', locale.getpreferredencoding()).readlines() + except UnicodeDecodeError: + lines = open(specfile).readlines() + + tags = [] + sections = [] + spec_data = {} + + for itm in args: + if itm.startswith('%'): + sections.append(itm) + else: + tags.append(itm) + + tag_pat = '(?P<tag>^%s)\s*:\s*(?P<val>.*)' + for tag in tags: + m = re.compile(tag_pat % tag, re.I | re.M).search(''.join(lines)) + if m and m.group('val'): + spec_data[tag] = m.group('val').strip() + + section_pat = '^%s\s*?$' + for section in sections: + m = re.compile(section_pat % section, re.I | re.M).search(''.join(lines)) + if m: + start = lines.index(m.group()+'\n') + 1 + data = [] + for line in lines[start:]: + if line.startswith('%'): + break + data.append(line) + spec_data[section] = data + + return spec_data + +def get_default_editor(): + import platform + system = platform.system() + if system == 'Windows': + return 'notepad' + if system == 'Linux': + try: + # Python 2.6 + dist = platform.linux_distribution()[0] + except AttributeError: + dist = platform.dist()[0] + if dist == 'debian': + return 'editor' + elif dist == 'fedora': + return 'vi' + return 'vim' + return 'vi' + +def get_default_pager(): + import platform + system = platform.system() + if system == 'Windows': + return 'less' + if system == 'Linux': + try: + # Python 2.6 + dist = platform.linux_distribution()[0] + except AttributeError: + dist = platform.dist()[0] + if dist == 'debian': + return 'pager' + return 'less' + return 'more' + +def run_pager(message, tmp_suffix=''): + import tempfile, sys + + if not message: + return + + if not sys.stdout.isatty(): + print(message) + else: + tmpfile = tempfile.NamedTemporaryFile(suffix=tmp_suffix) + tmpfile.write(message) + tmpfile.flush() + pager = os.getenv('PAGER', default=get_default_pager()) + try: + run_external(pager, tmpfile.name) + finally: + tmpfile.close() + +def run_editor(filename): + cmd = _editor_command() + cmd.append(filename) + return run_external(cmd[0], *cmd[1:]) + +def _editor_command(): + editor = os.getenv('EDITOR', default=get_default_editor()) + try: + cmd = shlex.split(editor) + except SyntaxError: + cmd = editor.split() + return cmd + +def _edit_message_open_editor(filename, data, orig_mtime): + # FIXME: import modules globally + import tempfile + editor = _editor_command() + mtime = os.stat(filename).st_mtime + if mtime == orig_mtime: + # prepare file for editors + if editor[0] in ('vi', 'vim'): + with tempfile.NamedTemporaryFile() as f: + f.write(data) + f.flush() + editor.extend(['-c', ':r %s' % f.name, filename]) + run_external(editor[0], *editor[1:]) + else: + with open(filename, 'w') as f: + f.write(data) + orig_mtime = os.stat(filename).st_mtime + run_editor(filename) + else: + run_editor(filename) + return os.stat(filename).st_mtime != orig_mtime + +def edit_message(footer='', template='', templatelen=30): + delim = '--This line, and those below, will be ignored--\n' + data = '' + if template != '': + if not templatelen is None: + lines = template.splitlines() + data = '\n'.join(lines[:templatelen]) + if lines[templatelen:]: + footer = '%s\n\n%s' % ('\n'.join(lines[templatelen:]), footer) + data += '\n' + delim + '\n' + footer + return edit_text(data, delim, suffix='.diff', template=template) + +def edit_text(data='', delim=None, suffix='.txt', template=''): + import tempfile + try: + (fd, filename) = tempfile.mkstemp(prefix='osc-editor', suffix=suffix) + os.close(fd) + mtime = os.stat(filename).st_mtime + while True: + file_changed = _edit_message_open_editor(filename, data, mtime) + msg = open(filename).read() + if delim: + msg = msg.split(delim)[0].rstrip() + if msg and file_changed: + break + else: + reason = 'Log message not specified' + if template == msg: + reason = 'Default log message was not changed. Press \'c\' to continue.' + ri = raw_input('%s\na)bort, c)ontinue, e)dit: ' % reason) + if ri in 'aA': + raise oscerr.UserAbort() + elif ri in 'cC': + break + elif ri in 'eE': + pass + finally: + os.unlink(filename) + return msg + +def clone_request(apiurl, reqid, msg=None): + query = {'cmd': 'branch', 'request': reqid} + url = makeurl(apiurl, ['source'], query) + r = http_POST(url, data=msg) + root = ET.fromstring(r.read()) + project = None + for i in root.findall('data'): + if i.get('name') == 'targetproject': + project = i.text.strip() + if not project: + raise oscerr.APIError('invalid data from clone request:\n%s\n' % ET.tostring(root, encoding=ET_ENCODING)) + return project + +# create a maintenance release request +def create_release_request(apiurl, src_project, message=''): + import cgi + r = Request() + # api will complete the request + r.add_action('maintenance_release', src_project=src_project) + # XXX: clarify why we need the unicode(...) stuff + r.description = cgi.escape(unicode(message, 'utf8')) + r.create(apiurl) + return r + +# create a maintenance incident per request +def create_maintenance_request(apiurl, src_project, src_packages, tgt_project, tgt_releaseproject, opt_sourceupdate, message=''): + import cgi + r = Request() + if src_packages: + for p in src_packages: + r.add_action('maintenance_incident', src_project=src_project, src_package=p, tgt_project=tgt_project, tgt_releaseproject=tgt_releaseproject, opt_sourceupdate = opt_sourceupdate) + else: + r.add_action('maintenance_incident', src_project=src_project, tgt_project=tgt_project, tgt_releaseproject=tgt_releaseproject, opt_sourceupdate = opt_sourceupdate) + # XXX: clarify why we need the unicode(...) stuff + r.description = cgi.escape(unicode(message, 'utf8')) + r.create(apiurl, addrevision=True) + return r + +def create_submit_request(apiurl, + src_project, src_package=None, + dst_project=None, dst_package=None, + message="", orev=None, src_update=None, dst_updatelink=None): + + import cgi + options_block = "" + package = "" + if src_package: + package = """package="%s" """ % (src_package) + options_block = "<options>" + if src_update: + options_block += """<sourceupdate>%s</sourceupdate>""" % (src_update) + if dst_updatelink: + options_block += """<updatelink>true</updatelink>""" + options_block += "</options>" + + + # Yes, this kind of xml construction is horrible + targetxml = "" + if dst_project: + packagexml = "" + if dst_package: + packagexml = """package="%s" """ % ( dst_package ) + targetxml = """<target project="%s" %s /> """ % ( dst_project, packagexml ) + # XXX: keep the old template for now in order to work with old obs instances + xml = """\ +<request> + <action type="submit"> + <source project="%s" %s rev="%s"/> + %s + %s + </action> + <state name="new"/> + <description>%s</description> +</request> +""" % (src_project, + package, + orev or show_upstream_rev(apiurl, src_project, src_package), + targetxml, + options_block, + cgi.escape(message)) + + # Don't do cgi.escape(unicode(message, "utf8"))) above. + # Promoting the string to utf8, causes the post to explode with: + # uncaught exception: Fatal error: Start tag expected, '<' not found at :1. + # I guess, my original workaround was not that bad. + + u = makeurl(apiurl, ['request'], query='cmd=create') + r = None + try: + f = http_POST(u, data=xml) + root = ET.parse(f).getroot() + r = root.get('id') + except HTTPError as e: + if e.hdrs.get('X-Opensuse-Errorcode') == "submit_request_rejected": + print("WARNING:") + print("WARNING: Project does not accept submit request, request to open a NEW maintenance incident instead") + print("WARNING:") + xpath = 'maintenance/maintains/@project = \'%s\' and attribute/@name = \'%s\'' % (dst_project, conf.config['maintenance_attribute']) + res = search(apiurl, project_id=xpath) + root = res['project_id'] + project = root.find('project') + if project is None: + print("WARNING: This project is not maintained in the maintenance project specified by '%s', looking elsewhere" % conf.config['maintenance_attribute']) + xpath = 'maintenance/maintains/@project = \'%s\'' % dst_project + res = search(apiurl, project_id=xpath) + root = res['project_id'] + project = root.find('project') + if project is None: + raise oscerr.APIError("Server did not define a default maintenance project, can't submit.") + tproject = project.get('name') + r = create_maintenance_request(apiurl, src_project, [src_package], tproject, dst_project, src_update, message) + r = r.reqid + else: + raise + + return r + + +def get_request(apiurl, reqid): + u = makeurl(apiurl, ['request', reqid], {'withfullhistory': '1'}) + f = http_GET(u) + root = ET.parse(f).getroot() + + r = Request() + r.read(root) + return r + + +def change_review_state(apiurl, reqid, newstate, by_user='', by_group='', by_project='', by_package='', message='', supersed=None): + query = {'cmd': 'changereviewstate', 'newstate': newstate } + if by_user: + query['by_user'] = by_user + if by_group: + query['by_group'] = by_group + if by_project: + query['by_project'] = by_project + if by_package: + query['by_package'] = by_package + if supersed: + query['superseded_by'] = supersed + u = makeurl(apiurl, ['request', reqid], query=query) + f = http_POST(u, data=message) + root = ET.parse(f).getroot() + return root.get('code') + +def change_request_state(apiurl, reqid, newstate, message='', supersed=None, force=False): + query = {'cmd': 'changestate', 'newstate': newstate } + if supersed: + query['superseded_by'] = supersed + if force: + query['force'] = "1" + u = makeurl(apiurl, + ['request', reqid], query=query) + f = http_POST(u, data=message) + + root = ET.parse(f).getroot() + return root.get('code', 'unknown') + +def change_request_state_template(req, newstate): + if not len(req.actions): + return '' + action = req.actions[0] + tmpl_name = '%srequest_%s_template' % (action.type, newstate) + tmpl = conf.config.get(tmpl_name, '') + tmpl = tmpl.replace('\\t', '\t').replace('\\n', '\n') + data = {'reqid': req.reqid, 'type': action.type, 'who': req.get_creator()} + if req.actions[0].type == 'submit': + data.update({'src_project': action.src_project, + 'src_package': action.src_package, 'src_rev': action.src_rev, + 'dst_project': action.tgt_project, 'dst_package': action.tgt_package, + 'tgt_project': action.tgt_project, 'tgt_package': action.tgt_package}) + try: + return tmpl % data + except KeyError as e: + print('error: cannot interpolate \'%s\' in \'%s\'' % (e.args[0], tmpl_name), file=sys.stderr) + return '' + +def get_review_list(apiurl, project='', package='', byuser='', bygroup='', byproject='', bypackage='', states=()): + # this is so ugly... + def build_by(xpath, val): + if 'all' in states: + return xpath_join(xpath, 'review/%s' % val, op='and') + elif states: + s_xp = '' + for state in states: + s_xp = xpath_join(s_xp, '@state=\'%s\'' % state, inner=True) + val = val.strip('[').strip(']') + return xpath_join(xpath, 'review[%s and (%s)]' % (val, s_xp), op='and') + else: + # default case + return xpath_join(xpath, 'review[%s and @state=\'new\']' % val, op='and') + return '' + + xpath = '' + if states == (): + # default: requests which are still open and have reviews in "new" state + xpath = xpath_join('', 'state/@name=\'review\'', op='and') + xpath = xpath_join(xpath, 'review/@state=\'new\'', op='and') + if byuser: + xpath = build_by(xpath, '@by_user=\'%s\'' % byuser) + if bygroup: + xpath = build_by(xpath, '@by_group=\'%s\'' % bygroup) + if bypackage: + xpath = build_by(xpath, '@by_project=\'%s\' and @by_package=\'%s\'' % (byproject, bypackage)) + elif byproject: + xpath = build_by(xpath, '@by_project=\'%s\'' % byproject) + + # XXX: we cannot use the '|' in the xpath expression because it is not supported + # in the backend + todo = {} + if project: + todo['project'] = project + if package: + todo['package'] = package + for kind, val in todo.items(): + xpath_base = 'action/target/@%(kind)s=\'%(val)s\' or ' \ + 'submit/target/@%(kind)s=\'%(val)s\'' + + if conf.config['include_request_from_project']: + xpath_base = xpath_join(xpath_base, 'action/source/@%(kind)s=\'%(val)s\' or ' \ + 'submit/source/@%(kind)s=\'%(val)s\'', op='or', inner=True) + xpath = xpath_join(xpath, xpath_base % {'kind': kind, 'val': val}, op='and', nexpr_parentheses=True) + + if conf.config['verbose'] > 1: + print('[ %s ]' % xpath) + res = search(apiurl, request=xpath) + collection = res['request'] + requests = [] + for root in collection.findall('request'): + r = Request() + r.read(root) + requests.append(r) + return requests + +# this function uses the logic in the api which is faster and more exact then the xpath search +def get_request_collection(apiurl, role=None, req_who=None, req_states=('new', 'review', 'declined')): + + query={ "view" : "collection" } + if role: + query["roles"] = role + if req_who: + query["user"] = req_who + + query["states"] = ",".join(req_states) + + u = makeurl(apiurl, ['request'], query) + f = http_GET(u) + res = ET.parse(f).getroot() + + requests = [] + for root in res.findall('request'): + r = Request() + r.read(root) + requests.append(r) + return requests + +def get_exact_request_list(apiurl, src_project, dst_project, src_package=None, dst_package=None, req_who=None, req_state=('new', 'review', 'declined'), req_type=None): + xpath = '' + if not 'all' in req_state: + for state in req_state: + xpath = xpath_join(xpath, 'state/@name=\'%s\'' % state, op='or', inner=True) + xpath = '(%s)' % xpath + if req_who: + xpath = xpath_join(xpath, '(state/@who=\'%(who)s\' or history/@who=\'%(who)s\')' % {'who': req_who}, op='and') + + xpath += " and action[source/@project='%s'" % src_project + if src_package: + xpath += " and source/@package='%s'" % src_package + xpath += " and target/@project='%s'" % dst_project + if dst_package: + xpath += " and target/@package='%s'" % dst_package + xpath += "]" + if req_type: + xpath += " and action/@type=\'%s\'" % req_type + + if conf.config['verbose'] > 1: + print('[ %s ]' % xpath) + + res = search(apiurl, request=xpath) + collection = res['request'] + requests = [] + for root in collection.findall('request'): + r = Request() + r.read(root) + requests.append(r) + return requests + +def get_request_list(apiurl, project='', package='', req_who='', req_state=('new', 'review', 'declined'), req_type=None, exclude_target_projects=[]): + xpath = '' + if not 'all' in req_state: + for state in req_state: + xpath = xpath_join(xpath, 'state/@name=\'%s\'' % state, inner=True) + if req_who: + xpath = xpath_join(xpath, '(state/@who=\'%(who)s\' or history/@who=\'%(who)s\')' % {'who': req_who}, op='and') + + # XXX: we cannot use the '|' in the xpath expression because it is not supported + # in the backend + todo = {} + if project: + todo['project'] = project + if package: + todo['package'] = package + for kind, val in todo.items(): + xpath_base = 'action/target/@%(kind)s=\'%(val)s\' or ' \ + 'submit/target/@%(kind)s=\'%(val)s\'' + + if conf.config['include_request_from_project']: + xpath_base = xpath_join(xpath_base, 'action/source/@%(kind)s=\'%(val)s\' or ' \ + 'submit/source/@%(kind)s=\'%(val)s\'', op='or', inner=True) + xpath = xpath_join(xpath, xpath_base % {'kind': kind, 'val': val}, op='and', nexpr_parentheses=True) + + if req_type: + xpath = xpath_join(xpath, 'action/@type=\'%s\'' % req_type, op='and') + for i in exclude_target_projects: + xpath = xpath_join(xpath, '(not(action/target/@project=\'%(prj)s\' or ' \ + 'submit/target/@project=\'%(prj)s\'))' % {'prj': i}, op='and') + + if conf.config['verbose'] > 1: + print('[ %s ]' % xpath) + res = search(apiurl, request=xpath) + collection = res['request'] + requests = [] + for root in collection.findall('request'): + r = Request() + r.read(root) + requests.append(r) + return requests + +# old style search, this is to be removed +def get_user_projpkgs_request_list(apiurl, user, req_state=('new', 'review', ), req_type=None, exclude_projects=[], projpkgs={}): + """OBSOLETE: user involved request search is supported by OBS 2.2 server side in a better way + Return all running requests for all projects/packages where is user is involved""" + if not projpkgs: + res = get_user_projpkgs(apiurl, user, exclude_projects=exclude_projects) + projects = [] + for i in res['project_id'].findall('project'): + projpkgs[i.get('name')] = [] + projects.append(i.get('name')) + for i in res['package_id'].findall('package'): + if not i.get('project') in projects: + projpkgs.setdefault(i.get('project'), []).append(i.get('name')) + xpath = '' + for prj, pacs in projpkgs.items(): + if not len(pacs): + xpath = xpath_join(xpath, 'action/target/@project=\'%s\'' % prj, inner=True) + else: + xp = '' + for p in pacs: + xp = xpath_join(xp, 'action/target/@package=\'%s\'' % p, inner=True) + xp = xpath_join(xp, 'action/target/@project=\'%s\'' % prj, op='and') + xpath = xpath_join(xpath, xp, inner=True) + if req_type: + xpath = xpath_join(xpath, 'action/@type=\'%s\'' % req_type, op='and') + if not 'all' in req_state: + xp = '' + for state in req_state: + xp = xpath_join(xp, 'state/@name=\'%s\'' % state, inner=True) + xpath = xpath_join(xp, xpath, op='and', nexpr_parentheses=True) + res = search(apiurl, request=xpath) + result = [] + for root in res['request'].findall('request'): + r = Request() + r.read(root) + result.append(r) + return result + +def get_request_log(apiurl, reqid): + r = get_request(apiurl, reqid) + data = [] + frmt = '-' * 76 + '\n%s | %s | %s\n\n%s' + r.statehistory.reverse() + # the description of the request is used for the initial log entry + # otherwise its comment attribute would contain None + if len(r.statehistory) >= 1: + r.statehistory[-1].comment = r.description + else: + r.state.comment = r.description + for state in [ r.state ] + r.statehistory: + s = frmt % (state.name, state.who, state.when, str(state.comment)) + data.append(s) + return data + +def check_existing_requests(apiurl, src_project, src_package, dst_project, + dst_package): + reqs = get_exact_request_list(apiurl, src_project, dst_project, + src_package, dst_package, + req_type='submit', + req_state=['new', 'review', 'declined']) + repl = '' + if reqs: + print('There are already the following submit request: %s.' % \ + ', '.join([i.reqid for i in reqs])) + repl = raw_input('Supersede the old requests? (y/n/c) ') + if repl.lower() == 'c': + print('Aborting', file=sys.stderr) + raise oscerr.UserAbort() + return repl == 'y', reqs + +def check_existing_maintenance_requests(apiurl, src_project, src_packages, dst_project, + release_project): + reqs = [] + for src_package in src_packages: + reqs += get_exact_request_list(apiurl, src_project, dst_project, + src_package, None, + req_type='maintenance_incident', + req_state=['new', 'review', 'declined']) + repl = '' + if reqs: + print('There are already the following maintenance incident request: %s.' % \ + ', '.join([i.reqid for i in reqs])) + repl = raw_input('Supersede the old requests? (y/n/c) ') + if repl.lower() == 'c': + print('Aborting', file=sys.stderr) + raise oscerr.UserAbort() + return repl == 'y', reqs + +def get_group(apiurl, group): + u = makeurl(apiurl, ['group', quote_plus(group)]) + try: + f = http_GET(u) + return ''.join(f.readlines()) + except HTTPError: + print('group \'%s\' not found' % group) + return None + +def get_user_meta(apiurl, user): + u = makeurl(apiurl, ['person', quote_plus(user)]) + try: + f = http_GET(u) + return ''.join(f.readlines()) + except HTTPError: + print('user \'%s\' not found' % user) + return None + + +def _get_xml_data(meta, *tags): + data = [] + if meta != None: + root = ET.fromstring(meta) + for tag in tags: + elm = root.find(tag) + if elm is None or elm.text is None: + data.append('-') + else: + data.append(elm.text) + return data + + +def get_user_data(apiurl, user, *tags): + """get specified tags from the user meta""" + meta = get_user_meta(apiurl, user) + return _get_xml_data(meta, *tags) + + +def get_group_data(apiurl, group, *tags): + meta = get_group(apiurl, group) + return _get_xml_data(meta, *tags) + + +def download(url, filename, progress_obj = None, mtime = None): + import tempfile, shutil + global BUFSIZE + + o = None + try: + prefix = os.path.basename(filename) + path = os.path.dirname(filename) + (fd, tmpfile) = tempfile.mkstemp(dir=path, prefix = prefix, suffix = '.osctmp') + os.fchmod(fd, 0o644) + try: + o = os.fdopen(fd, 'wb') + for buf in streamfile(url, http_GET, BUFSIZE, progress_obj=progress_obj): + o.write(bytes(buf, "utf-8")) + o.close() + os.rename(tmpfile, filename) + except: + os.unlink(tmpfile) + raise + finally: + if o is not None: + o.close() + + if mtime: + utime(filename, (-1, mtime)) + +def get_source_file(apiurl, prj, package, filename, targetfilename=None, revision=None, progress_obj=None, mtime=None, meta=False): + targetfilename = targetfilename or filename + query = {} + if meta: + query['rev'] = 1 + if revision: + query['rev'] = revision + u = makeurl(apiurl, ['source', prj, package, pathname2url(filename.encode(locale.getpreferredencoding(), 'replace'))], query=query) + download(u, targetfilename, progress_obj, mtime) + +def get_binary_file(apiurl, prj, repo, arch, + filename, + package = None, + target_filename = None, + target_mtime = None, + progress_meter = False): + progress_obj = None + if progress_meter: + from .meter import TextMeter + progress_obj = TextMeter() + + target_filename = target_filename or filename + + where = package or '_repository' + u = makeurl(apiurl, ['build', prj, repo, arch, where, filename]) + download(u, target_filename, progress_obj, target_mtime) + +def dgst_from_string(str): + # Python 2.5 depracates the md5 modules + # Python 2.4 doesn't have hashlib yet + try: + import hashlib + md5_hash = hashlib.md5() + except ImportError: + import md5 + md5_hash = md5.new() + md5_hash.update(str) + return md5_hash.hexdigest() + +def dgst(file): + + #if not os.path.exists(file): + #return None + + global BUFSIZE + + try: + import hashlib + md5 = hashlib + except ImportError: + import md5 + md5 = md5 + s = md5.md5() + f = open(file, 'rb') + while True: + buf = f.read(BUFSIZE) + if not buf: break + s.update(buf) + return s.hexdigest() + f.close() + + +def binary(s): + """return true if a string is binary data using diff's heuristic""" + if s and bytes('\0', "utf-8") in s[:4096]: + return True + return False + + +def binary_file(fn): + """read 4096 bytes from a file named fn, and call binary() on the data""" + return binary(open(fn, 'rb').read(4096)) + + +def get_source_file_diff(dir, filename, rev, oldfilename = None, olddir = None, origfilename = None): + """ + This methods diffs oldfilename against filename (so filename will + be shown as the new file). + The variable origfilename is used if filename and oldfilename differ + in their names (for instance if a tempfile is used for filename etc.) + """ + + import difflib + + global store + + if not oldfilename: + oldfilename = filename + + if not olddir: + olddir = os.path.join(dir, store) + + if not origfilename: + origfilename = filename + + file1 = os.path.join(olddir, oldfilename) # old/stored original + file2 = os.path.join(dir, filename) # working copy + if binary_file(file1) or binary_file(file2): + return ['Binary file \'%s\' has changed.\n' % origfilename] + + f1 = f2 = None + try: + f1 = open(file1, 'rt') + s1 = f1.readlines() + f1.close() + + f2 = open(file2, 'rt') + s2 = f2.readlines() + f2.close() + finally: + if f1: + f1.close() + if f2: + f2.close() + + d = difflib.unified_diff(s1, s2, + fromfile = '%s\t(revision %s)' % (origfilename, rev), \ + tofile = '%s\t(working copy)' % origfilename) + d = list(d) + # python2.7's difflib slightly changed the format + # adapt old format to the new format + if len(d) > 1: + d[0] = d[0].replace(' \n', '\n') + d[1] = d[1].replace(' \n', '\n') + + # if file doesn't end with newline, we need to append one in the diff result + for i, line in enumerate(d): + if not line.endswith('\n'): + d[i] += '\n\\ No newline at end of file' + if i+1 != len(d): + d[i] += '\n' + return d + +def server_diff(apiurl, + old_project, old_package, old_revision, + new_project, new_package, new_revision, + unified=False, missingok=False, meta=False, expand=True, full=True): + query = {'cmd': 'diff'} + if expand: + query['expand'] = 1 + if old_project: + query['oproject'] = old_project + if old_package: + query['opackage'] = old_package + if old_revision: + query['orev'] = old_revision + if new_revision: + query['rev'] = new_revision + if unified: + query['unified'] = 1 + if missingok: + query['missingok'] = 1 + if meta: + query['meta'] = 1 + if full: + query['filelimit'] = 0 + query['tarlimit'] = 0 + + u = makeurl(apiurl, ['source', new_project, new_package], query=query) + + f = http_POST(u) + return f.read() + +def server_diff_noex(apiurl, + old_project, old_package, old_revision, + new_project, new_package, new_revision, + unified=False, missingok=False, meta=False, expand=True): + try: + return server_diff(apiurl, + old_project, old_package, old_revision, + new_project, new_package, new_revision, + unified, missingok, meta, expand) + except HTTPError as e: + msg = None + body = None + try: + body = e.read() + if not 'bad link' in body: + return '# diff failed: ' + body + except: + return '# diff failed with unknown error' + + if expand: + rdiff = "## diff on expanded link not possible, showing unexpanded version\n" + try: + rdiff += server_diff_noex(apiurl, + old_project, old_package, old_revision, + new_project, new_package, new_revision, + unified, missingok, meta, False) + except: + elm = ET.fromstring(body).find('summary') + summary = '' + if not elm is None: + summary = elm.text + return 'error: diffing failed: %s' % summary + return rdiff + + +def request_diff(apiurl, reqid): + u = makeurl(apiurl, ['request', reqid], query={'cmd': 'diff'} ) + + f = http_POST(u) + return f.read() + +def submit_action_diff(apiurl, action): + """diff a single submit action""" + # backward compatiblity: only a recent api/backend supports the missingok parameter + try: + return server_diff(apiurl, action.tgt_project, action.tgt_package, None, + action.src_project, action.src_package, action.src_rev, True, True) + except HTTPError as e: + if e.code == 400: + try: + return server_diff(apiurl, action.tgt_project, action.tgt_package, None, + action.src_project, action.src_package, action.src_rev, True, False) + except HTTPError as e: + if e.code != 404: + raise e + root = ET.fromstring(e.read()) + return 'error: \'%s\' does not exist' % root.find('summary').text + elif e.code == 404: + root = ET.fromstring(e.read()) + return 'error: \'%s\' does not exist' % root.find('summary').text + raise e + +def make_dir(apiurl, project, package, pathname=None, prj_dir=None, package_tracking=True, pkg_path=None): + """ + creates the plain directory structure for a package dir. + The 'apiurl' parameter is needed for the project dir initialization. + The 'project' and 'package' parameters specify the name of the + project and the package. The optional 'pathname' parameter is used + for printing out the message that a new dir was created (default: 'prj_dir/package'). + The optional 'prj_dir' parameter specifies the path to the project dir (default: 'project'). + If pkg_path is not None store the package's content in pkg_path (no project structure is created) + """ + prj_dir = prj_dir or project + + # FIXME: carefully test each patch component of prj_dir, + # if we have a .osc/_files entry at that level. + # -> if so, we have a package/project clash, + # and should rename this path component by appending '.proj' + # and give user a warning message, to discourage such clashes + + if pkg_path is None: + pathname = pathname or getTransActPath(os.path.join(prj_dir, package)) + pkg_path = os.path.join(prj_dir, package) + if is_package_dir(prj_dir): + # we want this to become a project directory, + # but it already is a package directory. + raise oscerr.OscIOError(None, 'checkout_package: package/project clash. Moving myself away not implemented') + + if not is_project_dir(prj_dir): + # this directory could exist as a parent direory for one of our earlier + # checked out sub-projects. in this case, we still need to initialize it. + print(statfrmt('A', prj_dir)) + Project.init_project(apiurl, prj_dir, project, package_tracking) + + if is_project_dir(os.path.join(prj_dir, package)): + # the thing exists, but is a project directory and not a package directory + # FIXME: this should be a warning message to discourage package/project clashes + raise oscerr.OscIOError(None, 'checkout_package: package/project clash. Moving project away not implemented') + else: + pathname = pkg_path + + if not os.path.exists(pkg_path): + print(statfrmt('A', pathname)) + os.mkdir(os.path.join(pkg_path)) +# os.mkdir(os.path.join(prj_dir, package, store)) + + return pkg_path + + +def checkout_package(apiurl, project, package, + revision=None, pathname=None, prj_obj=None, + expand_link=False, prj_dir=None, server_service_files = None, service_files=None, progress_obj=None, size_limit=None, meta=False, outdir=None): + try: + # the project we're in might be deleted. + # that'll throw an error then. + olddir = os.getcwd() + except: + olddir = os.environ.get("PWD") + + if not prj_dir: + prj_dir = olddir + else: + if sys.platform[:3] == 'win': + prj_dir = prj_dir[:2] + prj_dir[2:].replace(':', ';') + else: + if conf.config['checkout_no_colon']: + prj_dir = prj_dir.replace(':', '/') + + root_dots = '.' + if conf.config['checkout_rooted']: + if prj_dir[:1] == '/': + if conf.config['verbose'] > 1: + print("checkout_rooted ignored for %s" % prj_dir) + # ?? should we complain if not is_project_dir(prj_dir) ?? + else: + # if we are inside a project or package dir, ascend to parent + # directories, so that all projects are checked out relative to + # the same root. + if is_project_dir(".."): + # if we are in a package dir, goto parent. + # Hmm, with 'checkout_no_colon' in effect, we have directory levels that + # do not easily reveal the fact, that they are part of a project path. + # At least this test should find that the parent of 'home/username/branches' + # is a project (hack alert). Also goto parent in this case. + root_dots = "../" + elif is_project_dir("../.."): + # testing two levels is better than one. + # May happen in case of checkout_no_colon, or + # if project roots were previously inconsistent + root_dots = "../../" + if is_project_dir(root_dots): + if conf.config['checkout_no_colon']: + oldproj = store_read_project(root_dots) + n = len(oldproj.split(':')) + else: + n = 1 + root_dots = root_dots + "../" * n + + if root_dots != '.': + if conf.config['verbose']: + print("found root of %s at %s" % (oldproj, root_dots)) + prj_dir = root_dots + prj_dir + + if not pathname: + pathname = getTransActPath(os.path.join(prj_dir, package)) + + # before we create directories and stuff, check if the package actually + # exists + show_package_meta(apiurl, project, package, meta) + + isfrozen = False + if expand_link: + # try to read from the linkinfo + # if it is a link we use the xsrcmd5 as the revision to be + # checked out + try: + x = show_upstream_xsrcmd5(apiurl, project, package, revision=revision, meta=meta, include_service_files=server_service_files) + except: + x = show_upstream_xsrcmd5(apiurl, project, package, revision=revision, meta=meta, linkrev='base', include_service_files=server_service_files) + if x: + isfrozen = True + if x: + revision = x + directory = make_dir(apiurl, project, package, pathname, prj_dir, conf.config['do_package_tracking'], outdir) + p = Package.init_package(apiurl, project, package, directory, size_limit, meta, progress_obj) + if isfrozen: + p.mark_frozen() + # no project structure is wanted when outdir is used + if conf.config['do_package_tracking'] and outdir is None: + # check if we can re-use an existing project object + if prj_obj is None: + prj_obj = Project(prj_dir) + prj_obj.set_state(p.name, ' ') + prj_obj.write_packages() + p.update(revision, server_service_files, size_limit) + if service_files: + print('Running all source services local') + p.run_source_services() + +def replace_pkg_meta(pkgmeta, new_name, new_prj, keep_maintainers = False, + dst_userid = None, keep_develproject = False): + """ + update pkgmeta with new new_name and new_prj and set calling user as the + only maintainer (unless keep_maintainers is set). Additionally remove the + develproject entry (<devel />) unless keep_develproject is true. + """ + root = ET.fromstring(''.join(pkgmeta)) + root.set('name', new_name) + root.set('project', new_prj) + if not keep_maintainers: + for person in root.findall('person'): + root.remove(person) + if not keep_develproject: + for dp in root.findall('devel'): + root.remove(dp) + return ET.tostring(root, encoding=ET_ENCODING) + +def link_to_branch(apiurl, project, package): + """ + convert a package with a _link + project.diff to a branch + """ + + if '_link' in meta_get_filelist(apiurl, project, package): + u = makeurl(apiurl, ['source', project, package], 'cmd=linktobranch') + http_POST(u) + else: + raise oscerr.OscIOError(None, 'no _link file inside project \'%s\' package \'%s\'' % (project, package)) + +def link_pac(src_project, src_package, dst_project, dst_package, force, rev='', cicount='', disable_publish = False, missing_target = False, vrev=''): + """ + create a linked package + - "src" is the original package + - "dst" is the "link" package that we are creating here + """ + meta_change = False + dst_meta = '' + apiurl = conf.config['apiurl'] + try: + dst_meta = meta_exists(metatype='pkg', + path_args=(quote_plus(dst_project), quote_plus(dst_package)), + template_args=None, + create_new=False, apiurl=apiurl) + root = ET.fromstring(''.join(dst_meta)) + if root.get('project') != dst_project: + # The source comes from a different project via a project link, we need to create this instance + meta_change = True + except: + meta_change = True + + if meta_change: + if missing_target: + dst_meta = '<package name="%s"><title/><description/></package>' % dst_package + else: + src_meta = show_package_meta(apiurl, src_project, src_package) + dst_meta = replace_pkg_meta(src_meta, dst_package, dst_project) + + if disable_publish: + meta_change = True + root = ET.fromstring(''.join(dst_meta)) + elm = root.find('publish') + if not elm: + elm = ET.SubElement(root, 'publish') + elm.clear() + ET.SubElement(elm, 'disable') + dst_meta = ET.tostring(root, encoding=ET_ENCODING) + + if meta_change: + edit_meta('pkg', + path_args=(dst_project, dst_package), + data=dst_meta) + # create the _link file + # but first, make sure not to overwrite an existing one + if '_link' in meta_get_filelist(apiurl, dst_project, dst_package): + if force: + print('forced overwrite of existing _link file', file=sys.stderr) + else: + print(file=sys.stderr) + print('_link file already exists...! Aborting', file=sys.stderr) + sys.exit(1) + + if rev: + rev = ' rev="%s"' % rev + else: + rev = '' + + if vrev: + vrev = ' vrev="%s"' % vrev + else: + vrev = '' + + missingok = '' + if missing_target: + missingok = ' missingok="true"' + + if cicount: + cicount = ' cicount="%s"' % cicount + else: + cicount = '' + + print('Creating _link...', end=' ') + + project = '' + if src_project != dst_project: + project = 'project="%s"' % src_project + + link_template = """\ +<link %s package="%s"%s%s%s%s> +<patches> + <!-- <branch /> for a full copy, default case --> + <!-- <apply name="patch" /> apply a patch on the source directory --> + <!-- <topadd>%%define build_with_feature_x 1</topadd> add a line on the top (spec file only) --> + <!-- <add name="file.patch" /> add a patch to be applied after %%setup (spec file only) --> + <!-- <delete name="filename" /> delete a file --> +</patches> +</link> +""" % (project, src_package, missingok, rev, vrev, cicount) + + u = makeurl(apiurl, ['source', dst_project, dst_package, '_link']) + http_PUT(u, data=link_template) + print('Done.') + +def aggregate_pac(src_project, src_package, dst_project, dst_package, repo_map = {}, disable_publish = False, nosources = False): + """ + aggregate package + - "src" is the original package + - "dst" is the "aggregate" package that we are creating here + - "map" is a dictionary SRC => TARGET repository mappings + """ + meta_change = False + dst_meta = '' + apiurl = conf.config['apiurl'] + try: + dst_meta = meta_exists(metatype='pkg', + path_args=(quote_plus(dst_project), quote_plus(dst_package)), + template_args=None, + create_new=False, apiurl=apiurl) + root = ET.fromstring(''.join(dst_meta)) + if root.get('project') != dst_project: + # The source comes from a different project via a project link, we need to create this instance + meta_change = True + except: + meta_change = True + + if meta_change: + src_meta = show_package_meta(apiurl, src_project, src_package) + dst_meta = replace_pkg_meta(src_meta, dst_package, dst_project) + meta_change = True + + if disable_publish: + meta_change = True + root = ET.fromstring(''.join(dst_meta)) + elm = root.find('publish') + if not elm: + elm = ET.SubElement(root, 'publish') + elm.clear() + ET.SubElement(elm, 'disable') + dst_meta = ET.tostring(root, encoding=ET_ENCODING) + if meta_change: + edit_meta('pkg', + path_args=(dst_project, dst_package), + data=dst_meta) + + # create the _aggregate file + # but first, make sure not to overwrite an existing one + if '_aggregate' in meta_get_filelist(apiurl, dst_project, dst_package): + print(file=sys.stderr) + print('_aggregate file already exists...! Aborting', file=sys.stderr) + sys.exit(1) + + print('Creating _aggregate...', end=' ') + aggregate_template = """\ +<aggregatelist> + <aggregate project="%s"> +""" % (src_project) + + aggregate_template += """\ + <package>%s</package> +""" % ( src_package) + + if nosources: + aggregate_template += """\ + <nosources /> +""" + for src, tgt in repo_map.items(): + aggregate_template += """\ + <repository target="%s" source="%s" /> +""" % (tgt, src) + + aggregate_template += """\ + </aggregate> +</aggregatelist> +""" + + u = makeurl(apiurl, ['source', dst_project, dst_package, '_aggregate']) + http_PUT(u, data=aggregate_template) + print('Done.') + + +def attribute_branch_pkg(apiurl, attribute, maintained_update_project_attribute, package, targetproject, return_existing=False, force=False, noaccess=False, add_repositories=False, dryrun=False, nodevelproject=False, maintenance=False): + """ + Branch packages defined via attributes (via API call) + """ + query = { 'cmd': 'branch' } + query['attribute'] = attribute + if targetproject: + query['target_project'] = targetproject + if dryrun: + query['dryrun'] = "1" + if force: + query['force'] = "1" + if noaccess: + query['noaccess'] = "1" + if nodevelproject: + query['ignoredevel'] = '1' + if add_repositories: + query['add_repositories'] = "1" + if maintenance: + query['maintenance'] = "1" + if package: + query['package'] = package + if maintained_update_project_attribute: + query['update_project_attribute'] = maintained_update_project_attribute + + u = makeurl(apiurl, ['source'], query=query) + f = None + try: + f = http_POST(u) + except HTTPError as e: + msg = ''.join(e.readlines()) + msg = msg.split('<summary>')[1] + msg = msg.split('</summary>')[0] + raise oscerr.APIError(msg) + + r = None + + root = ET.fromstring(f.read()) + if dryrun: + return root + # TODO: change api here and return parsed XML as class + if conf.config['http_debug']: + print(ET.tostring(root, encoding=ET_ENCODING), file=sys.stderr) + for node in root.findall('data'): + r = node.get('name') + if r and r == 'targetproject': + return node.text + + return r + + +def branch_pkg(apiurl, src_project, src_package, nodevelproject=False, rev=None, linkrev=None, target_project=None, target_package=None, return_existing=False, msg='', force=False, noaccess=False, add_repositories=False, add_repositories_block=None, add_repositories_rebuild=None, extend_package_names=False, missingok=False, maintenance=False, newinstance=False): + """ + Branch a package (via API call) + """ + query = { 'cmd': 'branch' } + if nodevelproject: + query['ignoredevel'] = '1' + if force: + query['force'] = '1' + if noaccess: + query['noaccess'] = '1' + if add_repositories: + query['add_repositories'] = "1" + if add_repositories_block: + query['add_repositories_block'] = add_repositories_block + if add_repositories_rebuild: + query['add_repositories_rebuild'] = add_repositories_rebuild + if maintenance: + query['maintenance'] = "1" + if missingok: + query['missingok'] = "1" + if newinstance: + query['newinstance'] = "1" + if extend_package_names: + query['extend_package_names'] = "1" + if rev: + query['rev'] = rev + if linkrev: + query['linkrev'] = linkrev + if target_project: + query['target_project'] = target_project + if target_package: + query['target_package'] = target_package + if msg: + query['comment'] = msg + u = makeurl(apiurl, ['source', src_project, src_package], query=query) + try: + f = http_POST(u) + except HTTPError as e: + root = ET.fromstring(e.read()) + if missingok: + if root and root.get('code') == "not_missing": + raise oscerr.NotMissing("Package exists already via project link, but link will point to given project") + summary = root.find('summary') + if summary is None: + raise oscerr.APIError('unexpected response:\n%s' % ET.tostring(root, encoding=ET_ENCODING)) + if not return_existing: + raise oscerr.APIError('failed to branch: %s' % summary.text) + m = re.match(r"branch target package already exists: (\S+)/(\S+)", summary.text) + if not m: + e.msg += '\n' + summary.text + raise + return (True, m.group(1), m.group(2), None, None) + + root = ET.fromstring(f.read()) + if conf.config['http_debug']: + print(ET.tostring(root, encoding=ET_ENCODING), file=sys.stderr) + data = {} + for i in root.findall('data'): + data[i.get('name')] = i.text + return (False, data.get('targetproject', None), data.get('targetpackage', None), + data.get('sourceproject', None), data.get('sourcepackage', None)) + + +def copy_pac(src_apiurl, src_project, src_package, + dst_apiurl, dst_project, dst_package, + client_side_copy = False, + keep_maintainers = False, + keep_develproject = False, + expand = False, + revision = None, + comment = None, + force_meta_update = None, + keep_link = None): + """ + Create a copy of a package. + + Copying can be done by downloading the files from one package and commit + them into the other by uploading them (client-side copy) -- + or by the server, in a single api call. + """ + + if not (src_apiurl == dst_apiurl and src_project == dst_project \ + and src_package == dst_package): + src_meta = show_package_meta(src_apiurl, src_project, src_package) + dst_userid = conf.get_apiurl_usr(dst_apiurl) + src_meta = replace_pkg_meta(src_meta, dst_package, dst_project, keep_maintainers, + dst_userid, keep_develproject) + + url = make_meta_url('pkg', (quote_plus(dst_project),) + (quote_plus(dst_package),), dst_apiurl) + found = None + try: + found = http_GET(url).readlines() + except HTTPError as e: + pass + if force_meta_update or not found: + print('Sending meta data...') + u = makeurl(dst_apiurl, ['source', dst_project, dst_package, '_meta']) + http_PUT(u, data=src_meta) + + print('Copying files...') + if not client_side_copy: + query = {'cmd': 'copy', 'oproject': src_project, 'opackage': src_package } + if expand or keep_link: + query['expand'] = '1' + if keep_link: + query['keeplink'] = '1' + if revision: + query['orev'] = revision + if comment: + query['comment'] = comment + u = makeurl(dst_apiurl, ['source', dst_project, dst_package], query=query) + f = http_POST(u) + return f.read() + + else: + # copy one file after the other + import tempfile + query = {'rev': 'upload'} + xml = show_files_meta(src_apiurl, src_project, src_package, + expand=expand, revision=revision) + filelist = ET.fromstring(xml) + revision = filelist.get('srcmd5') + # filter out _service: files + for entry in filelist.findall('entry'): + # hmm the old code also checked for _service_ (but this is + # probably a relict from former times (if at all)) + if entry.get('name').startswith('_service:'): + filelist.remove(entry) + tfilelist = Package.commit_filelist(dst_apiurl, dst_project, + dst_package, filelist, msg=comment) + todo = Package.commit_get_missing(tfilelist) + for filename in todo: + print(' ', filename) + # hmm ideally, we would pass a file-like (that delegates to + # streamfile) to http_PUT... + with tempfile.NamedTemporaryFile(prefix='osc-copypac') as f: + get_source_file(src_apiurl, src_project, src_package, filename, + targetfilename=f.name, revision=revision) + path = ['source', dst_project, dst_package, pathname2url(filename)] + u = makeurl(dst_apiurl, path, query={'rev': 'repository'}) + http_PUT(u, file=f.name) + tfilelist = Package.commit_filelist(dst_apiurl, dst_project, dst_package, + filelist, msg=comment) + todo = Package.commit_get_missing(tfilelist) + if todo: + raise oscerr.APIError('failed to copy: %s' % ', '.join(todo)) + return 'Done.' + + +def unlock_package(apiurl, prj, pac, msg): + query = {'cmd': 'unlock', 'comment': msg} + u = makeurl(apiurl, ['source', prj, pac], query) + http_POST(u) + +def unlock_project(apiurl, prj, msg=None): + query = {'cmd': 'unlock', 'comment': msg} + u = makeurl(apiurl, ['source', prj], query) + http_POST(u) + + +def undelete_package(apiurl, prj, pac, msg=None): + query = {'cmd': 'undelete'} + if msg: + query['comment'] = msg + else: + query['comment'] = 'undeleted via osc' + u = makeurl(apiurl, ['source', prj, pac], query) + http_POST(u) + +def undelete_project(apiurl, prj, msg=None): + query = {'cmd': 'undelete'} + if msg: + query['comment'] = msg + else: + query['comment'] = 'undeleted via osc' + u = makeurl(apiurl, ['source', prj], query) + http_POST(u) + + +def delete_package(apiurl, prj, pac, force=False, msg=None): + query = {} + if force: + query['force'] = "1" + if msg: + query['comment'] = msg + u = makeurl(apiurl, ['source', prj, pac], query) + http_DELETE(u) + +def delete_project(apiurl, prj, force=False, msg=None): + query = {} + if force: + query['force'] = "1" + if msg: + query['comment'] = msg + u = makeurl(apiurl, ['source', prj], query) + http_DELETE(u) + +def delete_files(apiurl, prj, pac, files): + for filename in files: + u = makeurl(apiurl, ['source', prj, pac, filename], query={'comment': 'removed %s' % (filename, )}) + http_DELETE(u) + + +# old compat lib call +def get_platforms(apiurl): + return get_repositories(apiurl) + +def get_repositories(apiurl): + f = http_GET(makeurl(apiurl, ['platform'])) + tree = ET.parse(f) + r = sorted([ node.get('name') for node in tree.getroot() ]) + return r + + +def get_distibutions(apiurl, discon=False): + r = [] + + # FIXME: this is just a naming convention on api.opensuse.org, but not a general valid apparoach + if discon: + result_line_templ = '%(name)-25s %(project)s' + f = http_GET(makeurl(apiurl, ['build'])) + root = ET.fromstring(''.join(f)) + + for node in root.findall('entry'): + if node.get('name').startswith('DISCONTINUED:'): + rmap = {} + rmap['name'] = node.get('name').replace('DISCONTINUED:', '').replace(':', ' ') + rmap['project'] = node.get('name') + r.append (result_line_templ % rmap) + + r.insert(0, 'distribution project') + r.insert(1, '------------ -------') + + else: + result_line_templ = '%(name)-25s %(project)-25s %(repository)-25s %(reponame)s' + f = http_GET(makeurl(apiurl, ['distributions'])) + root = ET.fromstring(''.join(f)) + + for node in root.findall('distribution'): + rmap = {} + for node2 in node.findall('name'): + rmap['name'] = node2.text + for node3 in node.findall('project'): + rmap['project'] = node3.text + for node4 in node.findall('repository'): + rmap['repository'] = node4.text + for node5 in node.findall('reponame'): + rmap['reponame'] = node5.text + r.append(result_line_templ % rmap) + + r.insert(0, 'distribution project repository reponame') + r.insert(1, '------------ ------- ---------- --------') + + return r + + +# old compat lib call +def get_platforms_of_project(apiurl, prj): + return get_repositories_of_project(apiurl, prj) + +def get_repositories_of_project(apiurl, prj): + f = show_project_meta(apiurl, prj) + root = ET.fromstring(''.join(f)) + + r = [ node.get('name') for node in root.findall('repository')] + return r + + +class Repo: + repo_line_templ = '%-15s %-10s' + + def __init__(self, name, arch): + self.name = name + self.arch = arch + + def __str__(self): + return self.repo_line_templ % (self.name, self.arch) + + def __repr__(self): + return 'Repo(%s %s)' % (self.name, self.arch) + + @staticmethod + def fromfile(filename): + if not os.path.exists(filename): + return [] + repos = [] + lines = open(filename, 'r').readlines() + for line in lines: + data = line.split() + if len(data) == 2: + repos.append(Repo(data[0], data[1])) + elif len(data) == 1: + # only for backward compatibility + repos.append(Repo(data[0], '')) + return repos + + @staticmethod + def tofile(filename, repos): + with open(filename, 'w') as f: + for repo in repos: + f.write('%s %s\n' % (repo.name, repo.arch)) + +def get_repos_of_project(apiurl, prj): + f = show_project_meta(apiurl, prj) + root = ET.fromstring(''.join(f)) + + for node in root.findall('repository'): + for node2 in node.findall('arch'): + yield Repo(node.get('name'), node2.text) + +def get_binarylist(apiurl, prj, repo, arch, package=None, verbose=False): + what = package or '_repository' + u = makeurl(apiurl, ['build', prj, repo, arch, what]) + f = http_GET(u) + tree = ET.parse(f) + if not verbose: + return [ node.get('filename') for node in tree.findall('binary')] + else: + l = [] + for node in tree.findall('binary'): + f = File(node.get('filename'), + None, + int(node.get('size')), + int(node.get('mtime'))) + l.append(f) + return l + + +def get_binarylist_published(apiurl, prj, repo, arch): + u = makeurl(apiurl, ['published', prj, repo, arch]) + f = http_GET(u) + tree = ET.parse(f) + r = [ node.get('name') for node in tree.findall('entry')] + return r + + +def show_results_meta(apiurl, prj, package=None, lastbuild=None, repository=[], arch=[], oldstate=None): + query = {} + if package: + query['package'] = package + if oldstate: + query['oldstate'] = oldstate + if lastbuild: + query['lastbuild'] = 1 + u = makeurl(apiurl, ['build', prj, '_result'], query=query) + for repo in repository: + u = u + '&repository=%s' % repo + for a in arch: + u = u + '&arch=%s' % a + f = http_GET(u) + return f.readlines() + + +def show_prj_results_meta(apiurl, prj): + u = makeurl(apiurl, ['build', prj, '_result']) + f = http_GET(u) + return f.readlines() + + +def result_xml_to_dicts(xml): + # assumption: xml contains at most one status element (maybe we should + # generalize this to arbitrary status element) + root = ET.fromstring(xml) + r = [] + for node in root.findall('result'): + rmap = {} + rmap['project'] = rmap['prj'] = node.get('project') + rmap['repository'] = rmap['repo'] = rmap['rep'] = node.get('repository') + rmap['arch'] = node.get('arch') + rmap['state'] = node.get('state') + rmap['dirty'] = node.get('dirty') + rmap['repostate'] = node.get('code') + rmap['pkg'] = rmap['package'] = rmap['pac'] = '' + rmap['code'] = '' + rmap['details'] = '' + details = None + statusnode = node.find('status') + if statusnode is not None: + # the way currently use this function, there should be + # always a status element + rmap['pkg'] = rmap['package'] = rmap['pac'] = statusnode.get('package') + rmap['code'] = statusnode.get('code', '') + details = statusnode.find('details') + if details is not None: + rmap['details'] = details.text + rmap['dirty'] = rmap['dirty'] == 'true' + + r.append(rmap) + return r + + +def format_results(results, format): + """apply selected format on each dict in results and return it as a list of strings""" + return [format % r for r in results] + + +def get_results(apiurl, project, package, verbose=False, printJoin='', *args, **kwargs): + """returns list of/or prints a human readable status for the specified package""" + # hmm the function name is a bit too generic - something like + # get_package_results_human would be better, but this would break the existing + # api (unless we keep get_results around as well)... + result_line_templ = '%(rep)-20s %(arch)-10s %(status)s' + r = [] + for results in get_package_results(apiurl, project, package, **kwargs): + r = [] + for res in result_xml_to_dicts(results): + if '_oldstate' in res: + oldstate = res['_oldstate'] + continue + res['status'] = res['code'] + if verbose and res['details'] != '': + if res['code'] in ('unresolvable', 'expansion error'): + lines = res['details'].split(',') + res['status'] += ': ' + '\n '.join(lines) + else: + res['status'] += ': %s' % (res['details'], ) + if res['dirty']: + if verbose: + res['status'] = 'outdated (was: %s)' % res['status'] + else: + res['status'] += '*' + elif res['code'] in ('succeeded') and res['repostate'] != "published": + if verbose: + res['status'] += '(unpublished)' + else: + res['status'] += '*' + + r.append(result_line_templ % res) + + if printJoin: + print(printJoin.join(r)) + return r + + +def get_package_results(apiurl, project, package, wait=False, *args, **kwargs): + """generator that returns a the package results as an xml structure""" + xml = '' + waiting_states = ('blocked', 'scheduled', 'dispatching', 'building', + 'signing', 'finished') + while True: + waiting = False + try: + xml = ''.join(show_results_meta(apiurl, project, package, *args, **kwargs)) + except HTTPError as e: + # check for simple timeout error and fetch again + if e.code == 502 or e.code == 504: + # re-try result request + continue + raise + root = ET.fromstring(xml) + kwargs['oldstate'] = root.get('state') + for result in root.findall('result'): + if result.get('dirty') is not None: + waiting = True + break + elif result.get('code') in waiting_states: + waiting = True + break + elif (result.get('code') == 'succeeded' + and result.get('repostate') != 'published'): + waiting = True + break + + if not wait or not waiting: + break + else: + yield xml + yield xml + + +def get_prj_results(apiurl, prj, hide_legend=False, csv=False, status_filter=None, name_filter=None, arch=None, repo=None, vertical=None, show_excluded=None): + #print '----------------------------------------' + global buildstatus_symbols + + r = [] + + f = show_prj_results_meta(apiurl, prj) + root = ET.fromstring(''.join(f)) + + pacs = [] + # sequence of (repo,arch) tuples + targets = [] + # {package: {(repo,arch): status}} + status = {} + if root.find('result') == None: + return [] + for results in root.findall('result'): + for node in results: + pacs.append(node.get('package')) + pacs = sorted(list(set(pacs))) + for node in root.findall('result'): + # filter architecture and repository + if arch != None and node.get('arch') not in arch: + continue + if repo != None and node.get('repository') not in repo: + continue + if node.get('dirty') == "true": + state = "outdated" + else: + state = node.get('state') + tg = (node.get('repository'), node.get('arch'), state) + targets.append(tg) + for pacnode in node.findall('status'): + pac = pacnode.get('package') + if pac not in status: + status[pac] = {} + status[pac][tg] = pacnode.get('code') + targets.sort() + + # filter option + if status_filter or name_filter or not show_excluded: + + pacs_to_show = [] + targets_to_show = [] + + #filtering for Package Status + if status_filter: + if status_filter in buildstatus_symbols.values(): + # a list is needed because if status_filter == "U" + # we have to filter either an "expansion error" (obsolete) + # or an "unresolvable" state + filters = [] + for txt, sym in buildstatus_symbols.items(): + if sym == status_filter: + filters.append(txt) + for filt_txt in filters: + for pkg in status.keys(): + for repo in status[pkg].keys(): + if status[pkg][repo] == filt_txt: + if not name_filter: + pacs_to_show.append(pkg) + targets_to_show.append(repo) + elif name_filter in pkg: + pacs_to_show.append(pkg) + + #filtering for Package Name + elif name_filter: + for pkg in pacs: + if name_filter in pkg: + pacs_to_show.append(pkg) + + #filter non building states + elif not show_excluded: + enabled = {} + for pkg in status.keys(): + showpkg = False + for repo in status[pkg].keys(): + if status[pkg][repo] != "excluded": + enabled[repo] = 1 + showpkg = True + + if showpkg: + pacs_to_show.append(pkg) + + targets_to_show = enabled.keys() + + pacs = [ i for i in pacs if i in pacs_to_show ] + if len(targets_to_show): + targets = [ i for i in targets if i in targets_to_show ] + + # csv output + if csv: + # TODO: option to disable the table header + row = ['_'] + ['/'.join(tg) for tg in targets] + r.append(';'.join(row)) + for pac in pacs: + row = [pac] + [status[pac][tg] for tg in targets if tg in status[pac]] + r.append(';'.join(row)) + return r + + if not vertical: + # human readable output + max_pacs = 40 + for startpac in range(0, len(pacs), max_pacs): + offset = 0 + for pac in pacs[startpac:startpac+max_pacs]: + r.append(' |' * offset + ' ' + pac) + offset += 1 + + for tg in targets: + line = [] + line.append(' ') + for pac in pacs[startpac:startpac+max_pacs]: + st = '' + if pac not in status or tg not in status[pac]: + # for newly added packages, status may be missing + st = '?' + else: + try: + st = buildstatus_symbols[status[pac][tg]] + except: + print('osc: warn: unknown status \'%s\'...' % status[pac][tg]) + print('please edit osc/core.py, and extend the buildstatus_symbols dictionary.') + st = '?' + buildstatus_symbols[status[pac][tg]] = '?' + line.append(st) + line.append(' ') + line.append(' %s %s (%s)' % tg) + line = ''.join(line) + + r.append(line) + + r.append('') + else: + offset = 0 + for tg in targets: + r.append('| ' * offset + '%s %s (%s)'%tg ) + offset += 1 + + for pac in pacs: + line = [] + for tg in targets: + st = '' + if pac not in status or tg not in status[pac]: + # for newly added packages, status may be missing + st = '?' + else: + try: + st = buildstatus_symbols[status[pac][tg]] + except: + print('osc: warn: unknown status \'%s\'...' % status[pac][tg]) + print('please edit osc/core.py, and extend the buildstatus_symbols dictionary.') + st = '?' + buildstatus_symbols[status[pac][tg]] = '?' + line.append(st) + line.append(' '+pac) + r.append(' '.join(line)) + + line = [] + for i in range(0, len(targets)): + line.append(str(i%10)) + r.append(' '.join(line)) + + r.append('') + + if not hide_legend and len(pacs): + r.append(' Legend:') + legend = [] + for i, j in buildstatus_symbols.items(): + if i == "expansion error": + continue + legend.append('%3s %-20s' % (j, i)) + legend.append(' ? buildstatus not available (only new packages)') + + if vertical: + for i in range(0, len(targets)): + s = '%1d %s %s (%s)' % (i%10, targets[i][0], targets[i][1], targets[i][2]) + if i < len(legend): + legend[i] += s + else: + legend.append(' '*24 + s) + + r += legend + + return r + + +def streamfile(url, http_meth = http_GET, bufsize=8192, data=None, progress_obj=None, text=None): + """ + performs http_meth on url and read bufsize bytes from the response + until EOF is reached. After each read bufsize bytes are yielded to the + caller. A spezial usage is bufsize="line" to read line by line (text). + """ + cl = '' + retries = 0 + # Repeat requests until we get reasonable Content-Length header + # Server (or iChain) is corrupting data at some point, see bnc#656281 + while cl == '': + if retries >= int(conf.config['http_retries']): + raise oscerr.OscIOError(None, 'Content-Length is empty for %s, protocol violation' % url) + retries = retries + 1 + if retries > 1 and conf.config['http_debug']: + print('\n\nRetry %d --' % (retries - 1), url, file=sys.stderr) + f = http_meth.__call__(url, data = data) + cl = f.info().get('Content-Length') + + if cl is not None: + # sometimes the proxy adds the same header again + # which yields in value like '3495, 3495' + # use the first of these values (should be all the same) + cl = cl.split(',')[0] + cl = int(cl) + + if progress_obj: + basename = os.path.basename(urlsplit(url)[2]) + progress_obj.start(basename=basename, text=text, size=cl) + + if bufsize == "line": + bufsize = 8192 + xread = f.readline + else: + xread = f.read + + read = 0 + while True: + data = xread(bufsize) + if not len(data): + break + read += len(data) + if progress_obj: + progress_obj.update(read) + yield data + + if progress_obj: + progress_obj.end(read) + f.close() + + if not cl is None and read != cl: + raise oscerr.OscIOError(None, 'Content-Length is not matching file size for %s: %i vs %i file size' % (url, cl, read)) + + +def buildlog_strip_time(data): + """Strips the leading build time from the log""" + time_regex = re.compile('^\[[^\]]*\] ', re.M) + return time_regex.sub('', data) + + +def print_buildlog(apiurl, prj, package, repository, arch, offset=0, strip_time=False, last=False): + """prints out the buildlog on stdout""" + + # to protect us against control characters + import string + all_bytes = string.maketrans('', '') + remove_bytes = all_bytes[:8] + all_bytes[14:32] # accept tabs and newlines + + query = {'nostream' : '1', 'start' : '%s' % offset} + if last: + query['last'] = 1 + while True: + query['start'] = offset + start_offset = offset + u = makeurl(apiurl, ['build', prj, repository, arch, package, '_log'], query=query) + for data in streamfile(u, bufsize="line"): + offset += len(data) + if strip_time: + data = buildlog_strip_time(data) + sys.stdout.write(data.translate(all_bytes, remove_bytes)) + if start_offset == offset: + break + +def get_dependson(apiurl, project, repository, arch, packages=None, reverse=None): + query = [] + if packages: + for i in packages: + query.append('package=%s' % quote_plus(i)) + + if reverse: + query.append('view=revpkgnames') + else: + query.append('view=pkgnames') + + u = makeurl(apiurl, ['build', project, repository, arch, '_builddepinfo'], query=query) + f = http_GET(u) + return f.read() + +def get_buildinfo(apiurl, prj, package, repository, arch, specfile=None, addlist=None, debug=None): + query = [] + if addlist: + for i in addlist: + query.append('add=%s' % quote_plus(i)) + if debug: + query.append('debug=1') + + u = makeurl(apiurl, ['build', prj, repository, arch, package, '_buildinfo'], query=query) + + if specfile: + f = http_POST(u, data=specfile) + else: + f = http_GET(u) + return f.read() + + +def get_buildconfig(apiurl, prj, repository): + u = makeurl(apiurl, ['build', prj, repository, '_buildconfig']) + f = http_GET(u) + return f.read() + + +def get_source_rev(apiurl, project, package, revision=None): + # API supports ?deleted=1&meta=1&rev=4 + # but not rev=current,rev=latest,rev=top, or anything like this. + # CAUTION: We have to loop through all rev and find the highest one, if none given. + + if revision: + url = makeurl(apiurl, ['source', project, package, '_history'], {'rev': revision}) + else: + url = makeurl(apiurl, ['source', project, package, '_history']) + f = http_GET(url) + xml = ET.parse(f) + ent = None + for new in xml.findall('revision'): + # remember the newest one. + if not ent: + ent = new + elif ent.find('time').text < new.find('time').text: + ent = new + if not ent: + return { 'version': None, 'error': 'empty revisionlist: no such package?' } + e = {} + for k in ent.keys(): + e[k] = ent.get(k) + for k in list(ent): + e[k.tag] = k.text + return e + +def get_buildhistory(apiurl, prj, package, repository, arch, format = 'text', limit = None): + import time + query = {} + if limit != None and int(limit) > 0: + query['limit'] = int(limit) + u = makeurl(apiurl, ['build', prj, repository, arch, package, '_history'], query) + f = http_GET(u) + root = ET.parse(f).getroot() + + r = [] + for node in root.findall('entry'): + rev = node.get('rev') + srcmd5 = node.get('srcmd5') + versrel = node.get('versrel') + bcnt = int(node.get('bcnt')) + t = time.gmtime(int(node.get('time'))) + t = time.strftime('%Y-%m-%d %H:%M:%S', t) + + if format == 'csv': + r.append('%s|%s|%s|%s.%d' % (t, srcmd5, rev, versrel, bcnt)) + else: + bversrel='%s.%d' % (versrel, bcnt) + r.append('%s %s %s %s' % (t, srcmd5, bversrel.ljust(16)[:16], rev)) + + if format == 'text': + r.insert(0, 'time srcmd5 vers-rel.bcnt rev') + + return r + +def print_jobhistory(apiurl, prj, current_package, repository, arch, format = 'text', limit=20): + import time + query = {} + if current_package: + query['package'] = current_package + if limit != None and int(limit) > 0: + query['limit'] = int(limit) + u = makeurl(apiurl, ['build', prj, repository, arch, '_jobhistory'], query ) + f = http_GET(u) + root = ET.parse(f).getroot() + + if format == 'text': + print("time package reason code build time worker") + for node in root.findall('jobhist'): + package = node.get('package') + worker = node.get('workerid') + reason = node.get('reason') + if not reason: + reason = "unknown" + code = node.get('code') + st = int(node.get('starttime')) + et = int(node.get('endtime')) + endtime = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(et)) + waittm = et-st + if waittm > 24*60*60: + waitbuild = "%1dd %2dh %2dm %2ds" % (waittm / (24*60*60), (waittm / (60*60)) % 24, (waittm / 60) % 60, waittm % 60) + elif waittm > 60*60: + waitbuild = " %2dh %2dm %2ds" % (waittm / (60*60), (waittm / 60) % 60, waittm % 60) + else: + waitbuild = " %2dm %2ds" % (waittm / 60, waittm % 60) + + if format == 'csv': + print('%s|%s|%s|%s|%s|%s' % (endtime, package, reason, code, waitbuild, worker)) + else: + print('%s %-50s %-16s %-16s %-16s %-16s' % (endtime, package[0:49], reason[0:15], code[0:15], waitbuild, worker)) + + +def get_commitlog(apiurl, prj, package, revision, format = 'text', meta = False, deleted = False, revision_upper=None): + import time + + query = {} + if deleted: + query['deleted'] = 1 + if meta: + query['meta'] = 1 + + u = makeurl(apiurl, ['source', prj, package, '_history'], query) + f = http_GET(u) + root = ET.parse(f).getroot() + + r = [] + if format == 'xml': + r.append('<?xml version="1.0"?>') + r.append('<log>') + revisions = root.findall('revision') + revisions.reverse() + for node in revisions: + srcmd5 = node.find('srcmd5').text + try: + rev = int(node.get('rev')) + #vrev = int(node.get('vrev')) # what is the meaning of vrev? + try: + if revision is not None and revision_upper is not None: + if rev > int(revision_upper) or rev < int(revision): + continue + elif revision is not None and rev != int(revision): + continue + except ValueError: + if revision != srcmd5: + continue + except ValueError: + # this part should _never_ be reached but... + return [ 'an unexpected error occured - please file a bug' ] + version = node.find('version').text + user = node.find('user').text + try: + comment = node.find('comment').text.encode(locale.getpreferredencoding(), 'replace') + except: + comment = '<no message>' + try: + requestid = node.find('requestid').text.encode(locale.getpreferredencoding(), 'replace') + except: + requestid = "" + t = time.gmtime(int(node.find('time').text)) + t = time.strftime('%Y-%m-%d %H:%M:%S', t) + + if format == 'csv': + s = '%s|%s|%s|%s|%s|%s|%s' % (rev, user, t, srcmd5, version, + comment.replace('\\', '\\\\').replace('\n', '\\n').replace('|', '\\|'), requestid) + r.append(s) + elif format == 'xml': + r.append('<logentry') + r.append(' revision="%s" srcmd5="%s">' % (rev, srcmd5)) + r.append('<author>%s</author>' % user) + r.append('<date>%s</date>' % t) + r.append('<requestid>%s</requestid>' % requestid) + r.append('<msg>%s</msg>' % + comment.replace('&', '&').replace('<', '>').replace('>', '<')) + r.append('</logentry>') + else: + if requestid: + requestid = "rq" + requestid + s = '-' * 76 + \ + '\nr%s | %s | %s | %s | %s | %s\n' % (rev, user, t, srcmd5, version, requestid) + \ + '\n' + comment + r.append(s) + + if format not in ['csv', 'xml']: + r.append('-' * 76) + if format == 'xml': + r.append('</log>') + return r + + +def runservice(apiurl, prj, package): + u = makeurl(apiurl, ['source', prj, package], query={'cmd': 'runservice'}) + + try: + f = http_POST(u) + except HTTPError as e: + e.osc_msg = 'could not trigger service run for project \'%s\' package \'%s\'' % (prj, package) + raise + + root = ET.parse(f).getroot() + return root.get('code') + +def waitservice(apiurl, prj, package): + u = makeurl(apiurl, ['source', prj, package], query={'cmd': 'waitservice'}) + + try: + f = http_POST(u) + except HTTPError as e: + e.osc_msg = 'The service for project \'%s\' package \'%s\' failed' % (prj, package) + raise + + root = ET.parse(f).getroot() + return root.get('code') + +def mergeservice(apiurl, prj, package): + # first waiting that the service finishes and that it did not fail + waitservice(apiurl, prj, package) + + # real merge + u = makeurl(apiurl, ['source', prj, package], query={'cmd': 'mergeservice'}) + + try: + f = http_POST(u) + except HTTPError as e: + e.osc_msg = 'could not merge service files in project \'%s\' package \'%s\'' % (prj, package) + raise + + root = ET.parse(f).getroot() + return root.get('code') + + +def rebuild(apiurl, prj, package, repo, arch, code=None): + query = { 'cmd': 'rebuild' } + if package: + query['package'] = package + if repo: + query['repository'] = repo + if arch: + query['arch'] = arch + if code: + query['code'] = code + + u = makeurl(apiurl, ['build', prj], query=query) + try: + f = http_POST(u) + except HTTPError as e: + e.osc_msg = 'could not trigger rebuild for project \'%s\' package \'%s\'' % (prj, package) + raise + + root = ET.parse(f).getroot() + return root.get('code') + + +def store_read_project(dir): + global store + + try: + p = open(os.path.join(dir, store, '_project')).readlines()[0].strip() + except IOError: + msg = 'Error: \'%s\' is not an osc project dir or working copy' % os.path.abspath(dir) + if os.path.exists(os.path.join(dir, '.svn')): + msg += '\nTry svn instead of osc.' + raise oscerr.NoWorkingCopy(msg) + return p + + +def store_read_package(dir): + global store + + try: + p = open(os.path.join(dir, store, '_package')).readlines()[0].strip() + except IOError: + msg = 'Error: \'%s\' is not an osc package working copy' % os.path.abspath(dir) + if os.path.exists(os.path.join(dir, '.svn')): + msg += '\nTry svn instead of osc.' + raise oscerr.NoWorkingCopy(msg) + return p + +def store_read_apiurl(dir, defaulturl=True): + global store + + fname = os.path.join(dir, store, '_apiurl') + try: + url = open(fname).readlines()[0].strip() + # this is needed to get a proper apiurl + # (former osc versions may stored an apiurl with a trailing slash etc.) + apiurl = conf.urljoin(*conf.parse_apisrv_url(None, url)) + except: + if not defaulturl: + if is_project_dir(dir): + project = store_read_project(dir) + package = None + elif is_package_dir(dir): + project = store_read_project(dir) + package = None + else: + msg = 'Error: \'%s\' is not an osc package working copy' % os.path.abspath(dir) + raise oscerr.NoWorkingCopy(msg) + msg = 'Your working copy \'%s\' is in an inconsistent state.\n' \ + 'Please run \'osc repairwc %s\' (Note this might _remove_\n' \ + 'files from the .osc/ dir). Please check the state\n' \ + 'of the working copy afterwards (via \'osc status %s\')' % (dir, dir, dir) + raise oscerr.WorkingCopyInconsistent(project, package, ['_apiurl'], msg) + apiurl = conf.config['apiurl'] + return apiurl + +def store_write_string(dir, file, string, subdir=''): + global store + + if subdir and not os.path.isdir(os.path.join(dir, store, subdir)): + os.mkdir(os.path.join(dir, store, subdir)) + fname = os.path.join(dir, store, subdir, file) + try: + f = open(fname + '.new', 'w') + f.write(string) + f.close() + os.rename(fname + '.new', fname) + except: + if os.path.exists(fname + '.new'): + os.unlink(fname + '.new') + raise + +def store_write_project(dir, project): + store_write_string(dir, '_project', project + '\n') + +def store_write_apiurl(dir, apiurl): + store_write_string(dir, '_apiurl', apiurl + '\n') + +def store_unlink_file(dir, file): + global store + + try: os.unlink(os.path.join(dir, store, file)) + except: pass + +def store_read_file(dir, file): + global store + + try: + content = open(os.path.join(dir, store, file)).read() + return content + except: + return None + +def store_write_initial_packages(dir, project, subelements): + global store + + fname = os.path.join(dir, store, '_packages') + root = ET.Element('project', name=project) + for elem in subelements: + root.append(elem) + ET.ElementTree(root).write(fname) + +def get_osc_version(): + return __version__ + + +def abortbuild(apiurl, project, package=None, arch=None, repo=None): + return cmdbuild(apiurl, 'abortbuild', project, package, arch, repo) + +def restartbuild(apiurl, project, package=None, arch=None, repo=None): + return cmdbuild(apiurl, 'restartbuild', project, package, arch, repo) + +def wipebinaries(apiurl, project, package=None, arch=None, repo=None, code=None): + return cmdbuild(apiurl, 'wipe', project, package, arch, repo, code) + + +def cmdbuild(apiurl, cmd, project, package=None, arch=None, repo=None, code=None): + query = { 'cmd': cmd } + if package: + query['package'] = package + if arch: + query['arch'] = arch + if repo: + query['repository'] = repo + if code: + query['code'] = code + + u = makeurl(apiurl, ['build', project], query) + try: + f = http_POST(u) + except HTTPError as e: + e.osc_msg = '%s command failed for project %s' % (cmd, project) + if package: + e.osc_msg += ' package %s' % package + if arch: + e.osc_msg += ' arch %s' % arch + if repo: + e.osc_msg += ' repository %s' % repo + if code: + e.osc_msg += ' code=%s' % code + raise + + root = ET.parse(f).getroot() + return root.get('code') + + +def parseRevisionOption(string): + """ + returns a tuple which contains the revisions + """ + + if string: + if ':' in string: + splitted_rev = string.split(':') + try: + for i in splitted_rev: + int(i) + return splitted_rev + except ValueError: + print('your revision \'%s\' will be ignored' % string, file=sys.stderr) + return None, None + else: + if string.isdigit(): + return string, None + elif string.isalnum() and len(string) == 32: + # could be an md5sum + return string, None + else: + print('your revision \'%s\' will be ignored' % string, file=sys.stderr) + return None, None + else: + return None, None + +def checkRevision(prj, pac, revision, apiurl=None, meta=False): + """ + check if revision is valid revision, i.e. it is not + larger than the upstream revision id + """ + if len(revision) == 32: + # there isn't a way to check this kind of revision for validity + return True + if not apiurl: + apiurl = conf.config['apiurl'] + try: + if int(revision) > int(show_upstream_rev(apiurl, prj, pac, meta)) \ + or int(revision) <= 0: + return False + else: + return True + except (ValueError, TypeError): + return False + +def build_table(col_num, data = [], headline = [], width=1, csv = False): + """ + This method builds a simple table. + Example1: build_table(2, ['foo', 'bar', 'suse', 'osc'], ['col1', 'col2'], 2) + col1 col2 + foo bar + suse osc + """ + + longest_col = [] + for i in range(col_num): + longest_col.append(0) + if headline and not csv: + data[0:0] = headline + # find longest entry in each column + i = 0 + for itm in data: + if longest_col[i] < len(itm): + longest_col[i] = len(itm) + if i == col_num - 1: + i = 0 + else: + i += 1 + # calculate length for each column + for i, row in enumerate(longest_col): + longest_col[i] = row + width + # build rows + row = [] + table = [] + i = 0 + for itm in data: + if i % col_num == 0: + i = 0 + row = [] + table.append(row) + # there is no need to justify the entries of the last column + # or when generating csv + if i == col_num -1 or csv: + row.append(itm) + else: + row.append(itm.ljust(longest_col[i])) + i += 1 + if csv: + separator = '|' + else: + separator = '' + return [separator.join(row) for row in table] + +def xpath_join(expr, new_expr, op='or', inner=False, nexpr_parentheses=False): + """ + Join two xpath expressions. If inner is False expr will + be surrounded with parentheses (unless it's not already + surrounded). If nexpr_parentheses is True new_expr will be + surrounded with parentheses. + """ + if not expr: + return new_expr + elif not new_expr: + return expr + # NOTE: this is NO syntax check etc. (e.g. if a literal contains a '(' or ')' + # the check might fail and expr will be surrounded with parentheses or NOT) + parentheses = not inner + if not inner and expr.startswith('(') and expr.endswith(')'): + parentheses = False + braces = [i for i in expr if i == '(' or i == ')'] + closed = 0 + while len(braces): + if braces.pop() == ')': + closed += 1 + continue + else: + closed += -1 + while len(braces): + if braces.pop() == '(': + closed += -1 + else: + closed += 1 + if closed != 0: + parentheses = True + break + if parentheses: + expr = '(%s)' % expr + if nexpr_parentheses: + new_expr = '(%s)' % new_expr + return '%s %s %s' % (expr, op, new_expr) + +def search(apiurl, **kwargs): + """ + Perform a search request. The requests are constructed as follows: + kwargs = {'kind1' => xpath1, 'kind2' => xpath2, ..., 'kindN' => xpathN} + GET /search/kind1?match=xpath1 + ... + GET /search/kindN?match=xpathN + """ + res = {} + for urlpath, xpath in kwargs.items(): + path = [ 'search' ] + path += urlpath.split('_') # FIXME: take underscores as path seperators. I see no other way atm to fix OBS api calls and not breaking osc api + u = makeurl(apiurl, path, ['match=%s' % quote_plus(xpath)]) + f = http_GET(u) + res[urlpath] = ET.parse(f).getroot() + return res + +def owner(apiurl, binary, mode="binary", attribute=None, project=None, usefilter=None, devel=None, limit=None): + """ + Perform a binary package owner search. This is supported since OBS 2.4. + """ + # find default project, if not specified + query = { mode: binary } + if attribute: + query['attribute'] = attribute + if project: + query['project'] = project + if devel: + query['devel'] = devel + if limit != None: + query['limit'] = limit + if usefilter != None: + query['filter'] = ",".join(usefilter) + u = makeurl(apiurl, [ 'search', 'owner' ], query) + res = None + try: + f = http_GET(u) + res = ET.parse(f).getroot() + except HTTPError as e: + # old server not supporting this search + pass + return res + +def set_link_rev(apiurl, project, package, revision='', expand=False): + url = makeurl(apiurl, ['source', project, package, '_link']) + try: + f = http_GET(url) + root = ET.parse(f).getroot() + except HTTPError as e: + e.osc_msg = 'Unable to get _link file in package \'%s\' for project \'%s\'' % (package, project) + raise + revision = _set_link_rev(apiurl, project, package, root, revision, expand=expand) + l = ET.tostring(root, encoding=ET_ENCODING) + http_PUT(url, data=l) + return revision + +def _set_link_rev(apiurl, project, package, root, revision='', expand=False): + """ + Updates the rev attribute of the _link xml. If revision is set to None + the rev and vrev attributes are removed from the _link xml. + updates the rev attribute of the _link xml. If revision is the empty + string the latest rev of the link's source package is used (or the + xsrcmd5 if expand is True). If revision is neither None nor the empty + string the _link's rev attribute is set to this revision (or to the + xsrcmd5 if expand is True). + """ + src_project = root.get('project', project) + src_package = root.get('package', package) + vrev = None + if revision is None: + if 'rev' in root.keys(): + del root.attrib['rev'] + if 'vrev' in root.keys(): + del root.attrib['vrev'] + elif not revision or expand: + revision, vrev = show_upstream_rev_vrev(apiurl, src_project, src_package, revision=revision, expand=expand) + + if revision: + root.set('rev', revision) + # add vrev when revision is a srcmd5 + if vrev is not None and revision is not None and len(revision) >= 32: + root.set('vrev', vrev) + return revision + + +def delete_dir(dir): + # small security checks + if os.path.islink(dir): + raise oscerr.OscIOError(None, 'cannot remove linked dir') + elif os.path.abspath(dir) == '/': + raise oscerr.OscIOError(None, 'cannot remove \'/\'') + + for dirpath, dirnames, filenames in os.walk(dir, topdown=False): + for filename in filenames: + os.unlink(os.path.join(dirpath, filename)) + for dirname in dirnames: + os.rmdir(os.path.join(dirpath, dirname)) + os.rmdir(dir) + + +def delete_storedir(store_dir): + """ + This method deletes a store dir. + """ + head, tail = os.path.split(store_dir) + if tail == '.osc': + delete_dir(store_dir) + +def unpack_srcrpm(srpm, dir, *files): + """ + This method unpacks the passed srpm into the + passed dir. If arguments are passed to the \'files\' tuple + only this files will be unpacked. + """ + if not is_srcrpm(srpm): + print('error - \'%s\' is not a source rpm.' % srpm, file=sys.stderr) + sys.exit(1) + curdir = os.getcwd() + if os.path.isdir(dir): + os.chdir(dir) + cmd = 'rpm2cpio %s | cpio -i %s &> /dev/null' % (srpm, ' '.join(files)) + ret = run_external(cmd, shell=True) + if ret != 0: + print('error \'%s\' - cannot extract \'%s\'' % (ret, srpm), file=sys.stderr) + sys.exit(1) + os.chdir(curdir) + +def is_rpm(f): + """check if the named file is an RPM package""" + try: + h = open(f, 'rb').read(4) + except: + return False + + if h == '\xed\xab\xee\xdb': + return True + else: + return False + +def is_srcrpm(f): + """check if the named file is a source RPM""" + + if not is_rpm(f): + return False + + try: + h = open(f, 'rb').read(8) + except: + return False + + if h[7] == '\x01': + return True + else: + return False + +def addMaintainer(apiurl, prj, pac, user): + # for backward compatibility only + addPerson(apiurl, prj, pac, user) + +def addPerson(apiurl, prj, pac, user, role="maintainer"): + """ add a new person to a package or project """ + path = quote_plus(prj), + kind = 'prj' + if pac: + path = path + (quote_plus(pac),) + kind = 'pkg' + data = meta_exists(metatype=kind, + path_args=path, + template_args=None, + create_new=False) + + if data and get_user_meta(apiurl, user) != None: + root = ET.fromstring(''.join(data)) + found = False + for person in root.getiterator('person'): + if person.get('userid') == user and person.get('role') == role: + found = True + print("user already exists") + break + if not found: + # the xml has a fixed structure + root.insert(2, ET.Element('person', role=role, userid=user)) + print('user \'%s\' added to \'%s\'' % (user, pac or prj)) + edit_meta(metatype=kind, + path_args=path, + data=ET.tostring(root, encoding=ET_ENCODING)) + else: + print("osc: an error occured") + +def delMaintainer(apiurl, prj, pac, user): + # for backward compatibility only + delPerson(apiurl, prj, pac, user) + +def delPerson(apiurl, prj, pac, user, role="maintainer"): + """ delete a person from a package or project """ + path = quote_plus(prj), + kind = 'prj' + if pac: + path = path + (quote_plus(pac), ) + kind = 'pkg' + data = meta_exists(metatype=kind, + path_args=path, + template_args=None, + create_new=False) + if data and get_user_meta(apiurl, user) != None: + root = ET.fromstring(''.join(data)) + found = False + for person in root.getiterator('person'): + if person.get('userid') == user and person.get('role') == role: + root.remove(person) + found = True + print("user \'%s\' removed" % user) + if found: + edit_meta(metatype=kind, + path_args=path, + data=ET.tostring(root, encoding=ET_ENCODING)) + else: + print("user \'%s\' not found in \'%s\'" % (user, pac or prj)) + else: + print("an error occured") + +def setBugowner(apiurl, prj, pac, user=None, group=None): + """ delete all bugowners (user and group entries) and set one new one in a package or project """ + path = quote_plus(prj), + kind = 'prj' + if pac: + path = path + (quote_plus(pac), ) + kind = 'pkg' + data = meta_exists(metatype=kind, + path_args=path, + template_args=None, + create_new=False) + if user.startswith('group:'): + group=user.replace('group:', '') + user=None + if data: + root = ET.fromstring(''.join(data)) + for group_element in root.getiterator('group'): + if group_element.get('role') == "bugowner": + root.remove(group_element) + for person_element in root.getiterator('person'): + if person_element.get('role') == "bugowner": + root.remove(person_element) + if user: + root.insert(2, ET.Element('person', role='bugowner', userid=user)) + elif group: + root.insert(2, ET.Element('group', role='bugowner', groupid=group)) + else: + print("Neither user nor group is specified") + edit_meta(metatype=kind, + path_args=path, + data=ET.tostring(root, encoding=ET_ENCODING)) + +def setDevelProject(apiurl, prj, pac, dprj, dpkg=None): + """ set the <devel project="..."> element to package metadata""" + path = (quote_plus(prj),) + (quote_plus(pac),) + data = meta_exists(metatype='pkg', + path_args=path, + template_args=None, + create_new=False) + + if data and show_project_meta(apiurl, dprj) != None: + root = ET.fromstring(''.join(data)) + if not root.find('devel') != None: + ET.SubElement(root, 'devel') + elem = root.find('devel') + if dprj: + elem.set('project', dprj) + else: + if 'project' in elem.keys(): + del elem.attrib['project'] + if dpkg: + elem.set('package', dpkg) + else: + if 'package' in elem.keys(): + del elem.attrib['package'] + edit_meta(metatype='pkg', + path_args=path, + data=ET.tostring(root, encoding=ET_ENCODING)) + else: + print("osc: an error occured") + +def createPackageDir(pathname, prj_obj=None): + """ + create and initialize a new package dir in the given project. + prj_obj can be a Project() instance. + """ + prj_dir, pac_dir = getPrjPacPaths(pathname) + if is_project_dir(prj_dir): + global store + if not os.path.exists(pac_dir+store): + prj = prj_obj or Project(prj_dir, False) + Package.init_package(prj.apiurl, prj.name, pac_dir, pac_dir) + prj.addPackage(pac_dir) + print(statfrmt('A', os.path.normpath(pathname))) + else: + raise oscerr.OscIOError(None, 'file or directory \'%s\' already exists' % pathname) + else: + msg = '\'%s\' is not a working copy' % prj_dir + if os.path.exists(os.path.join(prj_dir, '.svn')): + msg += '\ntry svn instead of osc.' + raise oscerr.NoWorkingCopy(msg) + + +def stripETxml(node): + node.tail = None + if node.text != None: + node.text = node.text.replace(" ", "").replace("\n", "") + for child in node.getchildren(): + stripETxml(child) + +def addGitSource(url): + service_file = os.path.join(os.getcwd(), '_service') + addfile = False + if os.path.exists( service_file ): + services = ET.parse(os.path.join(os.getcwd(), '_service')).getroot() + else: + services = ET.fromstring("<services />") + addfile = True + stripETxml( services ) + si = Serviceinfo() + s = si.addGitUrl(services, url) + s = si.addTarUp(services) + s = si.addRecompressTar(services) + s = si.addSetVersion(services) + si.read(s) + + # for pretty output + xmlindent(s) + f = open(service_file, 'wb') + f.write(ET.tostring(s, encoding=ET_ENCODING)) + f.close() + if addfile: + addFiles( ['_service'] ) + +def addDownloadUrlService(url): + service_file = os.path.join(os.getcwd(), '_service') + addfile = False + if os.path.exists( service_file ): + services = ET.parse(os.path.join(os.getcwd(), '_service')).getroot() + else: + services = ET.fromstring("<services />") + addfile = True + stripETxml( services ) + si = Serviceinfo() + s = si.addDownloadUrl(services, url) + si.read(s) + + # for pretty output + xmlindent(s) + f = open(service_file, 'wb') + f.write(ET.tostring(s, encoding=ET_ENCODING)) + f.close() + if addfile: + addFiles( ['_service'] ) + + # download file + path = os.getcwd() + files = os.listdir(path) + si.execute(path) + newfiles = os.listdir(path) + + # add verify service for new files + for filename in files: + newfiles.remove(filename) + + for filename in newfiles: + if filename.startswith('_service:download_url:'): + s = si.addVerifyFile(services, filename) + + # for pretty output + xmlindent(s) + f = open(service_file, 'wb') + f.write(ET.tostring(s, encoding=ET_ENCODING)) + f.close() + + +def addFiles(filenames, prj_obj = None): + for filename in filenames: + if not os.path.exists(filename): + raise oscerr.OscIOError(None, 'file \'%s\' does not exist' % filename) + + # init a package dir if we have a normal dir in the "filenames"-list + # so that it will be find by findpacs() later + pacs = list(filenames) + for filename in filenames: + prj_dir, pac_dir = getPrjPacPaths(filename) + if not is_package_dir(filename) and os.path.isdir(filename) and is_project_dir(prj_dir) \ + and conf.config['do_package_tracking']: + prj_name = store_read_project(prj_dir) + prj_apiurl = store_read_apiurl(prj_dir, defaulturl=False) + Package.init_package(prj_apiurl, prj_name, pac_dir, filename) + elif is_package_dir(filename) and conf.config['do_package_tracking']: + raise oscerr.PackageExists(store_read_project(filename), store_read_package(filename), + 'osc: warning: \'%s\' is already under version control' % filename) + elif os.path.isdir(filename) and is_project_dir(prj_dir): + raise oscerr.WrongArgs('osc: cannot add a directory to a project unless ' \ + '\'do_package_tracking\' is enabled in the configuration file') + elif os.path.isdir(filename): + print('skipping directory \'%s\'' % filename) + pacs.remove(filename) + pacs = findpacs(pacs) + for pac in pacs: + if conf.config['do_package_tracking'] and not pac.todo: + prj = prj_obj or Project(os.path.dirname(pac.absdir), False) + if pac.name in prj.pacs_unvers: + prj.addPackage(pac.name) + print(statfrmt('A', getTransActPath(os.path.join(pac.dir, os.pardir, pac.name)))) + for filename in pac.filenamelist_unvers: + if os.path.isdir(os.path.join(pac.dir, filename)): + print('skipping directory \'%s\'' % os.path.join(pac.dir, filename)) + else: + pac.todo.append(filename) + elif pac.name in prj.pacs_have: + print('osc: warning: \'%s\' is already under version control' % pac.name) + for filename in pac.todo: + if filename in pac.skipped: + continue + if filename in pac.excluded: + print('osc: warning: \'%s\' is excluded from a working copy' % filename, file=sys.stderr) + continue + pac.addfile(filename) + +def getPrjPacPaths(path): + """ + returns the path for a project and a package + from path. This is needed if you try to add + or delete packages: + Examples: + osc add pac1/: prj_dir = CWD; + pac_dir = pac1 + osc add /path/to/pac1: + prj_dir = path/to; + pac_dir = pac1 + osc add /path/to/pac1/file + => this would be an invalid path + the caller has to validate the returned + path! + """ + # make sure we hddave a dir: osc add bar vs. osc add bar/; osc add /path/to/prj_dir/new_pack + # filename = os.path.join(tail, '') + prj_dir, pac_dir = os.path.split(os.path.normpath(path)) + if prj_dir == '': + prj_dir = os.getcwd() + return (prj_dir, pac_dir) + +def getTransActPath(pac_dir): + """ + returns the path for the commit and update operations/transactions. + Normally the "dir" attribute of a Package() object will be passed to + this method. + """ + if pac_dir != '.': + pathn = os.path.normpath(pac_dir) + else: + pathn = '' + return pathn + +def get_commit_message_template(pac): + """ + Read the difference in .changes file(s) and put them as a template to commit message. + """ + diff = [] + template = [] + + if pac.todo: + todo = pac.todo + else: + todo = pac.filenamelist + pac.filenamelist_unvers + + files = [i for i in todo if i.endswith('.changes') and pac.status(i) in ('A', 'M')] + + for filename in files: + if pac.status(filename) == 'M': + diff += get_source_file_diff(pac.absdir, filename, pac.rev) + elif pac.status(filename) == 'A': + f = open(os.path.join(pac.absdir, filename), 'r') + for line in f: + diff += '+' + line + f.close() + + if diff: + template = parse_diff_for_commit_message(''.join(diff)) + + return template + +def parse_diff_for_commit_message(diff, template = []): + date_re = re.compile(r'\+(Mon|Tue|Wed|Thu|Fri|Sat|Sun) ([A-Z][a-z]{2}) ( ?[0-9]|[0-3][0-9]) .*') + diff = diff.split('\n') + + # The first four lines contains a header of diff + for line in diff[3:]: + # this condition is magical, but it removes all unwanted lines from commit message + if not(line) or (line and line[0] != '+') or \ + date_re.match(line) or \ + line == '+' or line[0:3] == '+++': + continue + + if line == '+-------------------------------------------------------------------': + template.append('') + else: + template.append(line[1:]) + + return template + +def get_commit_msg(wc_dir, pacs): + template = store_read_file(wc_dir, '_commit_msg') + # open editor for commit message + # but first, produce status and diff to append to the template + footer = [] + lines = [] + for p in pacs: + states = sorted(p.get_status(False, ' ', '?'), lambda x, y: cmp(x[1], y[1])) + changed = [statfrmt(st, os.path.normpath(os.path.join(p.dir, filename))) for st, filename in states] + if changed: + footer += changed + footer.append('\nDiff for working copy: %s' % p.dir) + footer.extend([''.join(i) for i in p.get_diff(ignoreUnversioned=True)]) + lines.extend(get_commit_message_template(p)) + if template is None: + if lines and lines[0] == '': + del lines[0] + template = '\n'.join(lines) + msg = '' + # if footer is empty, there is nothing to commit, and no edit needed. + if footer: + msg = edit_message(footer='\n'.join(footer), template=template) + if msg: + store_write_string(wc_dir, '_commit_msg', msg + '\n') + else: + store_unlink_file(wc_dir, '_commit_msg') + return msg + +def print_request_list(apiurl, project, package = None, states = ('new', 'review', ), force = False): + """ + prints list of pending requests for the specified project/package if "check_for_request_on_action" + is enabled in the config or if "force" is set to True + """ + if not conf.config['check_for_request_on_action'] and not force: + return + requests = get_request_list(apiurl, project, package, req_state=states) + msg = 'Pending requests for %s: %s (%s)' + if package is None and len(requests): + print(msg % ('project', project, len(requests))) + elif len(requests): + print(msg % ('package', '/'.join([project, package]), len(requests))) + for r in requests: + print(r.list_view(), '\n') + +def request_interactive_review(apiurl, request, initial_cmd='', group=None, + ignore_reviews=False, source_buildstatus=False): + """review the request interactively""" + import tempfile, re + + tmpfile = None + + def safe_change_request_state(*args, **kwargs): + try: + change_request_state(*args, **kwargs) + return True + except HTTPError as e: + print('Server returned an error:', e, file=sys.stderr) + print('Try -f to force the state change', file=sys.stderr) + return False + + def print_request(request): + print(request) + + def print_source_buildstatus(src_actions, newline=False): + if newline: + print() + if not src_actions: + print('unable to get source buildstatus: no source actions defined') + for action in src_actions: + print('%s/%s:' % (action.src_project, action.src_package)) + print('\n'.join(get_results(apiurl, action.src_project, action.src_package))) + + print_request(request) + try: + prompt = '(a)ccept/(d)ecline/(r)evoke/c(l)one/(s)kip/(c)ancel > ' + editable_actions = request.get_actions('submit', 'maintenance_incident') + # actions which have sources + buildresults + src_actions = editable_actions + request.get_actions('maintenance_release') + if editable_actions: + prompt = 'd(i)ff/(a)ccept/(d)ecline/(r)evoke/(b)uildstatus/c(l)one/(e)dit/(s)kip/(c)ancel > ' + elif src_actions: + # no edit for maintenance release requests + prompt = 'd(i)ff/(a)ccept/(d)ecline/(r)evoke/(b)uildstatus/c(l)one/(s)kip/(c)ancel > ' + editprj = '' + orequest = None + if source_buildstatus: + print_source_buildstatus(src_actions, newline=True) + while True: + if initial_cmd: + repl = initial_cmd + initial_cmd = '' + else: + repl = raw_input(prompt).strip() + if repl == 'i' and src_actions: + if not orequest is None and tmpfile: + tmpfile.close() + tmpfile = None + if tmpfile is None: + tmpfile = tempfile.NamedTemporaryFile(suffix='.diff') + try: + diff = request_diff(apiurl, request.reqid) + tmpfile.write(diff) + except HTTPError as e: + if e.code != 400: + raise + # backward compatible diff for old apis + for action in src_actions: + diff = 'old: %s/%s\nnew: %s/%s\n' % (action.src_project, action.src_package, + action.tgt_project, action.tgt_package) + diff += submit_action_diff(apiurl, action) + diff += '\n\n' + tmpfile.write(diff) + tmpfile.flush() + run_editor(tmpfile.name) + print_request(request) + elif repl == 's': + print('skipping: #%s' % request.reqid, file=sys.stderr) + break + elif repl == 'c': + print('Aborting', file=sys.stderr) + raise oscerr.UserAbort() + elif repl == 'b' and src_actions: + print_source_buildstatus(src_actions) + elif repl == 'e' and editable_actions: + # this is only for editable actions + if not editprj: + editprj = clone_request(apiurl, request.reqid, 'osc editrequest') + orequest = request + request = edit_submitrequest(apiurl, editprj, orequest, request) + src_actions = editable_actions = request.get_actions('submit', 'maintenance_incident') + print_request(request) + prompt = 'd(i)ff/(a)ccept/(b)uildstatus/(e)dit/(s)kip/(c)ancel > ' + else: + state_map = {'a': 'accepted', 'd': 'declined', 'r': 'revoked'} + mo = re.search('^([adrl])(?:\s+(-f)?\s*-m\s+(.*))?$', repl) + if mo is None or orequest and mo.group(1) != 'a': + print('invalid choice: \'%s\'' % repl, file=sys.stderr) + continue + state = state_map.get(mo.group(1)) + force = mo.group(2) is not None + msg = mo.group(3) + footer = '' + msg_template = '' + if not (state is None or request.state is None): + footer = 'changing request from state \'%s\' to \'%s\'\n\n' \ + % (request.state.name, state) + msg_template = change_request_state_template(request, state) + footer += str(request) + if tmpfile is not None: + tmpfile.seek(0) + # the read bytes probably have a moderate size so the str won't be too large + footer += '\n\n' + tmpfile.read() + if msg is None: + try: + msg = edit_message(footer = footer, template=msg_template) + except oscerr.UserAbort: + # do not abort (show prompt again) + continue + else: + msg = msg.strip('\'').strip('"') + if not orequest is None: + request.create(apiurl) + if not safe_change_request_state(apiurl, request.reqid, 'accepted', msg, force=force): + # an error occured + continue + repl = raw_input('Supersede original request? (y|N) ') + if repl in ('y', 'Y'): + safe_change_request_state(apiurl, orequest.reqid, 'superseded', + 'superseded by %s' % request.reqid, request.reqid, force=force) + elif state is None: + clone_request(apiurl, request.reqid, msg) + else: + reviews = [r for r in request.reviews if r.state == 'new'] + if not reviews or ignore_reviews: + if safe_change_request_state(apiurl, request.reqid, state, msg, force=force): + break + else: + # an error occured + continue + group_reviews = [r for r in reviews if (r.by_group is not None + and r.by_group == group)] + if len(group_reviews) == 1 and conf.config['review_inherit_group']: + review = group_reviews[0] + else: + print('Please chose one of the following reviews:') + for i in range(len(reviews)): + fmt = Request.format_review(reviews[i]) + print('(%i)' % i, 'by %(type)-10s %(by)s' % fmt) + num = raw_input('> ') + try: + num = int(num) + except ValueError: + print('\'%s\' is not a number.' % num) + continue + if num < 0 or num >= len(reviews): + print('number \'%s\' out of range.' % num) + continue + review = reviews[num] + change_review_state(apiurl, request.reqid, state, by_user=review.by_user, + by_group=review.by_group, by_project=review.by_project, + by_package=review.by_package, message=msg) + break + finally: + if tmpfile is not None: + tmpfile.close() + +def edit_submitrequest(apiurl, project, orequest, new_request=None): + """edit a submit action from orequest/new_request""" + import tempfile, shutil + actions = orequest.get_actions('submit') + oactions = actions + if new_request is not None: + actions = new_request.get_actions('submit') + num = 0 + if len(actions) > 1: + print('Please chose one of the following submit actions:') + for i in range(len(actions)): + # it is safe to use orequest because currently the formatting + # of a submit action does not need instance specific data + fmt = orequest.format_action(actions[i]) + print('(%i)' % i, '%(source)s %(target)s' % fmt) + num = raw_input('> ') + try: + num = int(num) + except ValueError: + raise oscerr.WrongArgs('\'%s\' is not a number.' % num) + if num < 0 or num >= len(orequest.actions): + raise oscerr.WrongArgs('number \'%s\' out of range.' % num) + + # the api replaced ':' with '_' in prj and pkg names (clone request) + package = '%s.%s' % (oactions[num].src_package.replace(':', '_'), + oactions[num].src_project.replace(':', '_')) + tmpdir = None + cleanup = True + try: + tmpdir = tempfile.mkdtemp(prefix='osc_editsr') + p = Package.init_package(apiurl, project, package, tmpdir) + p.update() + shell = os.getenv('SHELL', default='/bin/sh') + olddir = os.getcwd() + os.chdir(tmpdir) + print('Checked out package \'%s\' to %s. Started a new shell (%s).\n' \ + 'Please fix the package and close the shell afterwards.' % (package, tmpdir, shell)) + run_external(shell) + # the pkg might have uncommitted changes... + cleanup = False + os.chdir(olddir) + # reread data + p = Package(tmpdir) + modified = p.get_status(False, ' ', '?', 'S') + if modified: + print('Your working copy has the following modifications:') + print('\n'.join([statfrmt(st, filename) for st, filename in modified])) + repl = raw_input('Do you want to commit the local changes first? (y|N) ') + if repl in ('y', 'Y'): + msg = get_commit_msg(p.absdir, [p]) + p.commit(msg=msg) + cleanup = True + finally: + if cleanup: + shutil.rmtree(tmpdir) + else: + print('Please remove the dir \'%s\' manually' % tmpdir) + r = Request() + for action in orequest.get_actions(): + new_action = Action.from_xml(action.to_xml()) + r.actions.append(new_action) + if new_action.type == 'submit': + new_action.src_package = '%s.%s' % (action.src_package.replace(':', '_'), + action.src_project.replace(':', '_')) + new_action.src_project = project + # do an implicit cleanup + new_action.opt_sourceupdate = 'cleanup' + return r + +def get_user_projpkgs(apiurl, user, role=None, exclude_projects=[], proj=True, pkg=True, maintained=False, metadata=False): + """Return all project/packages where user is involved.""" + xpath = 'person/@userid = \'%s\'' % user + excl_prj = '' + excl_pkg = '' + for i in exclude_projects: + excl_prj = xpath_join(excl_prj, 'not(@name = \'%s\')' % i, op='and') + excl_pkg = xpath_join(excl_pkg, 'not(@project = \'%s\')' % i, op='and') + role_filter_xpath = xpath + if role: + xpath = xpath_join(xpath, 'person/@role = \'%s\'' % role, inner=True, op='and') + xpath_pkg = xpath_join(xpath, excl_pkg, op='and') + xpath_prj = xpath_join(xpath, excl_prj, op='and') + + if maintained: + xpath_pkg = xpath_join(xpath_pkg, '(project/attribute/@name=\'%(attr)s\' or attribute/@name=\'%(attr)s\')' % {'attr': conf.config['maintained_attribute']}, op='and') + + what = {} + if pkg: + if metadata: + what['package'] = xpath_pkg + else: + what['package_id'] = xpath_pkg + if proj: + if metadata: + what['project'] = xpath_prj + else: + what['project_id'] = xpath_prj + try: + res = search(apiurl, **what) + except HTTPError as e: + if e.code != 400 or not role_filter_xpath: + raise e + # backward compatibility: local role filtering + what = dict([[kind, role_filter_xpath] for kind in what.keys()]) + if 'package' in what: + what['package'] = xpath_join(role_filter_xpath, excl_pkg, op='and') + if 'project' in what: + what['project'] = xpath_join(role_filter_xpath, excl_prj, op='and') + res = search(apiurl, **what) + filter_role(res, user, role) + return res + +def raw_input(*args): + try: + import builtins + func = builtins.input + except ImportError: + #python 2.7 + import __builtin__ + func = __builtin__.raw_input + + try: + return func(*args) + except EOFError: + # interpret ctrl-d as user abort + raise oscerr.UserAbort() + +def run_external(filename, *args, **kwargs): + """Executes the program filename via subprocess.call. + + *args are additional arguments which are passed to the + program filename. **kwargs specify additional arguments for + the subprocess.call function. + if no args are specified the plain filename is passed + to subprocess.call (this can be used to execute a shell + command). Otherwise [filename] + list(args) is passed + to the subprocess.call function. + + """ + # unless explicitly specified use shell=False + kwargs.setdefault('shell', False) + if args: + cmd = [filename] + list(args) + else: + cmd = filename + try: + return subprocess.call(cmd, **kwargs) + except OSError as e: + if e.errno != errno.ENOENT: + raise + raise oscerr.ExtRuntimeError(e.strerror, filename) + +# backward compatibility: local role filtering +def filter_role(meta, user, role): + """ + remove all project/package nodes if no person node exists + where @userid=user and @role=role + """ + for kind, root in meta.items(): + delete = [] + for node in root.findall(kind): + found = False + for p in node.findall('person'): + if p.get('userid') == user and p.get('role') == role: + found = True + break + if not found: + delete.append(node) + for node in delete: + root.remove(node) + +def find_default_project(apiurl=None, package=None): + """" + look though the list of conf.config['getpac_default_project'] + and find the first project where the given package exists in the build service. + """ + if not len(conf.config['getpac_default_project']): + return None + candidates = re.split('[, ]+', conf.config['getpac_default_project']) + if package is None or len(candidates) == 1: + return candidates[0] + + # search through the list, where package exists ... + for prj in candidates: + try: + # any fast query will do here. + show_package_meta(apiurl, prj, package) + return prj + except HTTPError: + pass + return None + +def utime(filename, arg, ignore_einval=True): + """wrapper around os.utime which ignore errno EINVAL by default""" + try: + # workaround for bnc#857610): if filename resides on a nfs share + # os.utime might raise EINVAL + os.utime(filename, arg) + except OSError as e: + if e.errno == errno.EINVAL and ignore_einval: + return + raise + +def which(name): + """Searches "name" in PATH.""" + name = os.path.expanduser(name) + if os.path.isabs(name): + if os.path.exists(name): + return name + return None + for directory in os.environ.get('PATH', '').split(':'): + path = os.path.join(directory, name) + if os.path.exists(path): + return path + return None + + +def get_comments(apiurl, kind, name): + url = makeurl(apiurl, ['comments', kind, name]) + f = http_GET(url) + return ET.parse(f).getroot() + + +def print_comments(apiurl, kind, name): + def print_rec(comments, indent=''): + for comment in comments: + print(indent, end='') + print('On', comment.get('when'), comment.get('who'), 'wrote:') + text = indent + comment.text.replace('\r\n',' \n') + print(('\n' + indent).join(text.split('\n'))) + print() + print_rec([c for c in root if c.get('parent') == comment.get('id')], indent + ' ') + + root = get_comments(apiurl, kind, name) + comments = [c for c in root if c.get('parent') is None] + if comments: + print('\nComments:') + print_rec(comments) + +# vim: sw=4 et diff --git a/osc/fetch.py b/osc/fetch.py new file mode 100644 index 0000000..21cc3d6 --- /dev/null +++ b/osc/fetch.py @@ -0,0 +1,435 @@ +# Copyright (C) 2006 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +from __future__ import print_function + +import sys, os + +try: + from urllib.parse import quote_plus + from urllib.request import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, HTTPError +except ImportError: + #python 2.x + from urllib import quote_plus + from urllib2 import HTTPBasicAuthHandler, HTTPCookieProcessor, HTTPPasswordMgrWithDefaultRealm, HTTPError + +from urlgrabber.grabber import URLGrabber, URLGrabError +from urlgrabber.mirror import MirrorGroup +from .core import makeurl, streamfile +from .util import packagequery, cpio +from . import conf +from . import oscerr +import tempfile +import re +try: + from .meter import TextMeter +except: + TextMeter = None + + +def join_url(self, base_url, rel_url): + """to override _join_url of MirrorGroup, because we want to + pass full URLs instead of base URL where relative_url is added later... + IOW, we make MirrorGroup ignore relative_url + """ + return base_url + + +class OscFileGrabber(URLGrabber): + def __init__(self, progress_obj=None): + # we cannot use super because we still have to support + # older urlgrabber versions where URLGrabber is an old-style class + URLGrabber.__init__(self) + self.progress_obj = progress_obj + + def urlgrab(self, url, filename, text=None, **kwargs): + if url.startswith('file://'): + f = url.replace('file://', '', 1) + if os.path.isfile(f): + return f + else: + raise URLGrabError(2, 'Local file \'%s\' does not exist' % f) + with file(filename, 'wb') as f: + try: + for i in streamfile(url, progress_obj=self.progress_obj, + text=text): + f.write(i) + except HTTPError as e: + exc = URLGrabError(14, str(e)) + exc.url = url + exc.exception = e + exc.code = e.code + raise exc + except IOError as e: + raise URLGrabError(4, str(e)) + return filename + + +class Fetcher: + def __init__(self, cachedir='/tmp', api_host_options={}, urllist=[], + http_debug=False, cookiejar=None, offline=False, enable_cpio=True): + # set up progress bar callback + if sys.stdout.isatty() and TextMeter: + self.progress_obj = TextMeter(fo=sys.stdout) + else: + self.progress_obj = None + + self.cachedir = cachedir + self.urllist = urllist + self.http_debug = http_debug + self.offline = offline + self.cpio = {} + self.enable_cpio = enable_cpio + + passmgr = HTTPPasswordMgrWithDefaultRealm() + for host in api_host_options: + passmgr.add_password(None, host, api_host_options[host]['user'], + api_host_options[host]['pass']) + openers = (HTTPBasicAuthHandler(passmgr), ) + if cookiejar: + openers += (HTTPCookieProcessor(cookiejar), ) + self.gr = OscFileGrabber(progress_obj=self.progress_obj) + + def failureReport(self, errobj): + """failure output for failovers from urlgrabber""" + if errobj.url.startswith('file://'): + return {} + print('%s/%s: attempting download from api, since not found at %s' + % (self.curpac.project, self.curpac, errobj.url.split('/')[2])) + return {} + + def __add_cpio(self, pac): + prpap = '%s/%s/%s/%s' % (pac.project, pac.repository, pac.repoarch, pac.repopackage) + self.cpio.setdefault(prpap, {})[pac.repofilename] = pac + + def __download_cpio_archive(self, apiurl, project, repo, arch, package, **pkgs): + if not pkgs: + return + query = ['binary=%s' % quote_plus(i) for i in pkgs] + query.append('view=cpio') + try: + url = makeurl(apiurl, ['build', project, repo, arch, package], query=query) + sys.stdout.write("preparing download ...\r") + sys.stdout.flush() + with tempfile.NamedTemporaryFile(prefix='osc_build_cpio') as tmparchive: + self.gr.urlgrab(url, filename=tmparchive.name, + text='fetching packages for \'%s\'' % project) + archive = cpio.CpioRead(tmparchive.name) + archive.read() + for hdr in archive: + # XXX: we won't have an .errors file because we're using + # getbinarylist instead of the public/... route + # (which is routed to getbinaries) + # getbinaries does not support kiwi builds + if hdr.filename == '.errors': + archive.copyin_file(hdr.filename) + raise oscerr.APIError('CPIO archive is incomplete ' + '(see .errors file)') + if package == '_repository': + n = re.sub(r'\.pkg\.tar\..z$', '.arch', hdr.filename) + pac = pkgs[n.rsplit('.', 1)[0]] + else: + # this is a kiwi product + pac = pkgs[hdr.filename] + + # Extract a single file from the cpio archive + try: + fd, tmpfile = tempfile.mkstemp(prefix='osc_build_file') + archive.copyin_file(hdr.filename, + os.path.dirname(tmpfile), + os.path.basename(tmpfile)) + self.move_package(tmpfile, pac.localdir, pac) + finally: + os.close(fd) + if os.path.exists(tmpfile): + os.unlink(tmpfile) + + for pac in pkgs.values(): + if not os.path.isfile(pac.fullfilename): + raise oscerr.APIError('failed to fetch file \'%s\': ' + 'missing in CPIO archive' % + pac.repofilename) + except URLGrabError as e: + if e.errno != 14 or e.code != 414: + raise + # query str was too large + keys = list(pkgs.keys()) + if len(keys) == 1: + raise oscerr.APIError('unable to fetch cpio archive: ' + 'server always returns code 414') + n = len(pkgs) / 2 + new_pkgs = dict([(k, pkgs[k]) for k in keys[:n]]) + self.__download_cpio_archive(apiurl, project, repo, arch, + package, **new_pkgs) + new_pkgs = dict([(k, pkgs[k]) for k in keys[n:]]) + self.__download_cpio_archive(apiurl, project, repo, arch, + package, **new_pkgs) + + def __fetch_cpio(self, apiurl): + for prpap, pkgs in self.cpio.items(): + project, repo, arch, package = prpap.split('/', 3) + self.__download_cpio_archive(apiurl, project, repo, arch, package, **pkgs) + + def fetch(self, pac, prefix=''): + # for use by the failure callback + self.curpac = pac + + MirrorGroup._join_url = join_url + mg = MirrorGroup(self.gr, pac.urllist, failure_callback=(self.failureReport, (), {})) + + if self.http_debug: + print('\nURLs to try for package \'%s\':' % pac, file=sys.stderr) + print('\n'.join(pac.urllist), file=sys.stderr) + print(file=sys.stderr) + + try: + with tempfile.NamedTemporaryFile(prefix='osc_build', + delete=False) as tmpfile: + mg.urlgrab(pac.filename, filename=tmpfile.name, + text='%s(%s) %s' % (prefix, pac.project, pac.filename)) + self.move_package(tmpfile.name, pac.localdir, pac) + except URLGrabError as e: + if self.enable_cpio and e.errno == 256: + self.__add_cpio(pac) + return + print() + print('Error:', e.strerror, file=sys.stderr) + print('Failed to retrieve %s from the following locations ' + '(in order):' % pac.filename, file=sys.stderr) + print('\n'.join(pac.urllist), file=sys.stderr) + sys.exit(1) + finally: + if os.path.exists(tmpfile.name): + os.unlink(tmpfile.name) + + def move_package(self, tmpfile, destdir, pac_obj=None): + import shutil + pkgq = packagequery.PackageQuery.query(tmpfile, extra_rpmtags=(1044, 1051, 1052)) + if pkgq: + canonname = pkgq.canonname() + else: + if pac_obj is None: + print('Unsupported file type: ', tmpfile, file=sys.stderr) + sys.exit(1) + canonname = pac_obj.binary + + fullfilename = os.path.join(destdir, canonname) + if pac_obj is not None: + pac_obj.canonname = canonname + pac_obj.fullfilename = fullfilename + shutil.move(tmpfile, fullfilename) + os.chmod(fullfilename, 0o644) + + def dirSetup(self, pac): + dir = os.path.join(self.cachedir, pac.localdir) + if not os.path.exists(dir): + try: + os.makedirs(dir, mode=0o755) + except OSError as e: + print('packagecachedir is not writable for you?', file=sys.stderr) + print(e, file=sys.stderr) + sys.exit(1) + + def run(self, buildinfo): + cached = 0 + all = len(buildinfo.deps) + for i in buildinfo.deps: + i.makeurls(self.cachedir, self.urllist) + if os.path.exists(i.fullfilename): + cached += 1 + if i.hdrmd5: + from .util import packagequery + hdrmd5 = packagequery.PackageQuery.queryhdrmd5(i.fullfilename) + if not hdrmd5 or hdrmd5 != i.hdrmd5: + os.unlink(i.fullfilename) + cached -= 1 + miss = 0 + needed = all - cached + if all: + miss = 100.0 * needed / all + print("%.1f%% cache miss. %d/%d dependencies cached.\n" % (miss, cached, all)) + done = 1 + for i in buildinfo.deps: + i.makeurls(self.cachedir, self.urllist) + if not os.path.exists(i.fullfilename): + if self.offline: + raise oscerr.OscIOError(None, + 'Missing \'%s\' in cache: ' + '--offline not possible.' % + i.fullfilename) + self.dirSetup(i) + if i.hdrmd5 and self.enable_cpio: + self.__add_cpio(i) + done += 1 + continue + try: + # if there isn't a progress bar, there is no output at all + if not self.progress_obj: + print('%d/%d (%s) %s' % (done, needed, i.project, i.filename)) + self.fetch(i) + if self.progress_obj: + print(" %d/%d\r" % (done, needed), end=' ') + sys.stdout.flush() + + except KeyboardInterrupt: + print('Cancelled by user (ctrl-c)') + print('Exiting.') + sys.exit(0) + done += 1 + + self.__fetch_cpio(buildinfo.apiurl) + + prjs = list(buildinfo.projects.keys()) + for i in prjs: + dest = "%s/%s" % (self.cachedir, i) + if not os.path.exists(dest): + os.makedirs(dest, mode=0o755) + dest += '/_pubkey' + + url = makeurl(buildinfo.apiurl, ['source', i, '_pubkey']) + try: + if self.offline and not os.path.exists(dest): + # may need to try parent + raise URLGrabError(2) + elif not self.offline: + OscFileGrabber().urlgrab(url, dest) + # not that many keys usually + if i not in buildinfo.prjkeys: + buildinfo.keys.append(dest) + buildinfo.prjkeys.append(i) + except KeyboardInterrupt: + print('Cancelled by user (ctrl-c)') + print('Exiting.') + if os.path.exists(dest): + os.unlink(dest) + sys.exit(0) + except URLGrabError as e: + # Not found is okay, let's go to the next project + if e.errno == 14 and e.code != 404: + print("Invalid answer from server", e, file=sys.stderr) + sys.exit(1) + + if self.http_debug: + print("can't fetch key for %s: %s" % (i, e.strerror), file=sys.stderr) + print("url: %s" % url, file=sys.stderr) + + if os.path.exists(dest): + os.unlink(dest) + + l = i.rsplit(':', 1) + # try key from parent project + if len(l) > 1 and l[1] and not l[0] in buildinfo.projects: + prjs.append(l[0]) + + +def verify_pacs_old(pac_list): + """Take a list of rpm filenames and run rpm -K on them. + + In case of failure, exit. + + Check all packages in one go, since this takes only 6 seconds on my Athlon 700 + instead of 20 when calling 'rpm -K' for each of them. + """ + import subprocess + + if not pac_list: + return + + # don't care about the return value because we check the + # output anyway, and rpm always writes to stdout. + + # save locale first (we rely on English rpm output here) + saved_LC_ALL = os.environ.get('LC_ALL') + os.environ['LC_ALL'] = 'en_EN' + + o = subprocess.Popen(['rpm', '-K'] + pac_list, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, close_fds=True).stdout + + # restore locale + if saved_LC_ALL: + os.environ['LC_ALL'] = saved_LC_ALL + else: + os.environ.pop('LC_ALL') + + for line in o.readlines(): + + if 'OK' not in line: + print() + print('The following package could not be verified:', file=sys.stderr) + print(line, file=sys.stderr) + sys.exit(1) + + if 'NOT OK' in line: + print() + print('The following package could not be verified:', file=sys.stderr) + print(line, file=sys.stderr) + + if 'MISSING KEYS' in line: + missing_key = line.split('#')[-1].split(')')[0] + + print(""" +- If the key (%(name)s) is missing, install it first. + For example, do the following: + osc signkey PROJECT > file + and, as root: + rpm --import %(dir)s/keyfile-%(name)s + + Then, just start the build again. + +- If you do not trust the packages, you should configure osc build for XEN or KVM + +- You may use --no-verify to skip the verification (which is a risk for your system). +""" % {'name': missing_key, + 'dir': os.path.expanduser('~')}, file=sys.stderr) + + else: + print(""" +- If the signature is wrong, you may try deleting the package manually + and re-run this program, so it is fetched again. +""", file=sys.stderr) + + sys.exit(1) + + +def verify_pacs(bi): + """Take a list of rpm filenames and verify their signatures. + + In case of failure, exit. + """ + + pac_list = [i.fullfilename for i in bi.deps] + if conf.config['builtin_signature_check'] is not True: + return verify_pacs_old(pac_list) + + if not pac_list: + return + + if not bi.keys: + raise oscerr.APIError("can't verify packages due to lack of GPG keys") + + print("using keys from", ', '.join(bi.prjkeys)) + + from . import checker + failed = False + checker = checker.Checker() + try: + checker.readkeys(bi.keys) + for pkg in pac_list: + try: + checker.check(pkg) + except Exception as e: + failed = True + print(pkg, ':', e) + except: + checker.cleanup() + raise + + if failed: + checker.cleanup() + sys.exit(1) + + checker.cleanup() + +# vim: sw=4 et diff --git a/osc/meter.py b/osc/meter.py new file mode 100644 index 0000000..f24b18d --- /dev/null +++ b/osc/meter.py @@ -0,0 +1,103 @@ +# 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., +# 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA + +# this is basically a copy of python-urlgrabber's TextMeter class, +# with support added for dynamical sizing according to screen size. +# it uses getScreenWidth() scrapped from smart. +# 2007-04-24, poeml + +from __future__ import print_function + +from urlgrabber.progress import BaseMeter, format_time, format_number +import sys, os + +def getScreenWidth(): + import termios, struct, fcntl + s = struct.pack('HHHH', 0, 0, 0, 0) + try: + x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) + except IOError: + return 80 + return struct.unpack('HHHH', x)[1] + + +class TextMeter(BaseMeter): + def __init__(self, fo=sys.stderr, hide_finished=False): + BaseMeter.__init__(self) + self.fo = fo + self.hide_finished = hide_finished + try: + width = int(os.environ['COLUMNS']) + except (KeyError, ValueError): + width = getScreenWidth() + + + #self.unsized_templ = '\r%-60.60s %5sB %s ' + self.unsized_templ = '\r%%-%s.%ss %%5sB %%s ' % (width *2/5, width*3/5) + #self.sized_templ = '\r%-45.45s %3i%% |%-15.15s| %5sB %8s ' + self.bar_length = width/5 + self.sized_templ = '\r%%-%s.%ss %%3i%%%% |%%-%s.%ss| %%5sB %%8s ' % (width*4/10, width*4/10, self.bar_length, self.bar_length) + + + def _do_start(self, *args, **kwargs): + BaseMeter._do_start(self, *args, **kwargs) + self._do_update(0) + + def _do_update(self, amount_read, now=None): + etime = self.re.elapsed_time() + fetime = format_time(etime) + fread = format_number(amount_read) + #self.size = None + if self.text is not None: + text = self.text + else: + text = self.basename + if self.size is None: + out = self.unsized_templ % \ + (text, fread, fetime) + else: + rtime = self.re.remaining_time() + frtime = format_time(rtime) + frac = self.re.fraction_read() + bar = '='*int(self.bar_length * frac) + + out = self.sized_templ % \ + (text, frac*100, bar, fread, frtime) + 'ETA ' + + self.fo.write(out) + self.fo.flush() + + def _do_end(self, amount_read, now=None): + total_time = format_time(self.re.elapsed_time()) + total_size = format_number(amount_read) + if self.text is not None: + text = self.text + else: + text = self.basename + if self.size is None: + out = self.unsized_templ % \ + (text, total_size, total_time) + else: + bar = '=' * self.bar_length + out = self.sized_templ % \ + (text, 100, bar, total_size, total_time) + ' ' + if self.hide_finished: + self.fo.write('\r'+ ' '*len(out) + '\r') + else: + self.fo.write(out + '\n') + self.fo.flush() + +# vim: sw=4 et diff --git a/osc/oscerr.py b/osc/oscerr.py new file mode 100644 index 0000000..559f80d --- /dev/null +++ b/osc/oscerr.py @@ -0,0 +1,156 @@ +# Copyright (C) 2008 Novell Inc. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + + + +class OscBaseError(Exception): + def __init__(self, args=()): + Exception.__init__(self) + self.args = args + def __str__(self): + return ''.join(self.args) + +class UserAbort(OscBaseError): + """Exception raised when the user requested abortion""" + +class ConfigError(OscBaseError): + """Exception raised when there is an error in the config file""" + def __init__(self, msg, fname): + OscBaseError.__init__(self) + self.msg = msg + self.file = fname + +class ConfigMissingApiurl(ConfigError): + """Exception raised when a apiurl does not exist in the config file""" + def __init__(self, msg, fname, url): + ConfigError.__init__(self, msg, fname) + self.url = url + +class APIError(OscBaseError): + """Exception raised when there is an error in the output from the API""" + def __init__(self, msg): + OscBaseError.__init__(self) + self.msg = msg + +class NoConfigfile(OscBaseError): + """Exception raised when osc's configfile cannot be found""" + def __init__(self, fname, msg): + OscBaseError.__init__(self) + self.file = fname + self.msg = msg + +class ExtRuntimeError(OscBaseError): + """Exception raised when there is a runtime error of an external tool""" + def __init__(self, msg, fname): + OscBaseError.__init__(self) + self.msg = msg + self.file = fname + +class ServiceRuntimeError(OscBaseError): + """Exception raised when the execution of a source service failed""" + def __init__(self, msg): + OscBaseError.__init__(self) + self.msg = msg + +class WrongArgs(OscBaseError): + """Exception raised by the cli for wrong arguments usage""" + +class WrongOptions(OscBaseError): + """Exception raised by the cli for wrong option usage""" + #def __str__(self): + # s = 'Sorry, wrong options.' + # if self.args: + # s += '\n' + self.args + # return s + +class NoWorkingCopy(OscBaseError): + """Exception raised when directory is neither a project dir nor a package dir""" + +class NotMissing(OscBaseError): + """Exception raised when link target should not exist, but it does""" + +class WorkingCopyWrongVersion(OscBaseError): + """Exception raised when working copy's .osc/_osclib_version doesn't match""" + +class WorkingCopyOutdated(OscBaseError): + """Exception raised when the working copy is outdated. + It takes a tuple with three arguments: path to wc, + revision that it has, revision that it should have. + """ + def __str__(self): + return ('Working copy \'%s\' is out of date (rev %s vs rev %s).\n' + 'Looks as if you need to update it first.' \ + % (self[0], self[1], self[2])) + +class PackageError(OscBaseError): + """Base class for all Package related exceptions""" + def __init__(self, prj, pac): + OscBaseError.__init__(self) + self.prj = prj + self.pac = pac + +class WorkingCopyInconsistent(PackageError): + """Exception raised when the working copy is in an inconsistent state""" + def __init__(self, prj, pac, dirty_files, msg): + PackageError.__init__(self, prj, pac) + self.dirty_files = dirty_files + self.msg = msg + +class LinkExpandError(PackageError): + """Exception raised when source link expansion fails""" + def __init__(self, prj, pac, msg): + PackageError.__init__(self, prj, pac) + self.msg = msg + +class OscIOError(OscBaseError): + def __init__(self, e, msg): + OscBaseError.__init__(self) + self.e = e + self.msg = msg + +class PackageNotInstalled(OscBaseError): + """ + Exception raised when a package is not installed on local system + """ + def __init__(self, pkg): + OscBaseError.__init__(self, pkg) + + def __str__(self): + return 'Package %s is required for this operation' % ''.join(self.args) + +class SignalInterrupt(Exception): + """Exception raised on SIGTERM and SIGHUP.""" + +class PackageExists(PackageError): + """ + Exception raised when a local object already exists + """ + def __init__(self, prj, pac, msg): + PackageError.__init__(self, prj, pac) + self.msg = msg + +class PackageMissing(PackageError): + """ + Exception raised when a local object doesn't exist + """ + def __init__(self, prj, pac, msg): + PackageError.__init__(self, prj, pac) + self.msg = msg + +class PackageFileConflict(PackageError): + """ + Exception raised when there's a file conflict. + Conflict doesn't mean an unsuccessfull merge in this context. + """ + def __init__(self, prj, pac, file, msg): + PackageError.__init__(self, prj, pac) + self.file = file + self.msg = msg + +class PackageInternalError(PackageError): + def __init__(self, prj, pac, msg): + PackageError.__init__(self, prj, pac) + self.msg = msg +# vim: sw=4 et diff --git a/osc/oscssl.py b/osc/oscssl.py new file mode 100644 index 0000000..62f50c4 --- /dev/null +++ b/osc/oscssl.py @@ -0,0 +1,372 @@ +# Copyright (C) 2009 Novell Inc. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +from __future__ import print_function + +import M2Crypto.httpslib +from M2Crypto.SSL.Checker import SSLVerificationError +from M2Crypto import m2, SSL +import M2Crypto.m2urllib2 +import socket +import sys + +try: + from urllib.parse import urlparse, splithost, splitport, splittype + from urllib.request import addinfourl + from http.client import HTTPSConnection +except ImportError: + #python 2.x + from urlparse import urlparse + from urllib import addinfourl, splithost, splitport, splittype + from httplib import HTTPSConnection + +from .core import raw_input + +class TrustedCertStore: + _tmptrusted = {} + + def __init__(self, host, port, app, cert): + + self.cert = cert + self.host = host + if self.host == None: + raise Exception("empty host") + if port: + self.host += "_%d" % port + import os + self.dir = os.path.expanduser('~/.config/%s/trusted-certs' % app) + self.file = self.dir + '/%s.pem' % self.host + + def is_known(self): + if self.host in self._tmptrusted: + return True + + import os + if os.path.exists(self.file): + return True + return False + + def is_trusted(self): + import os + if self.host in self._tmptrusted: + cert = self._tmptrusted[self.host] + else: + if not os.path.exists(self.file): + return False + from M2Crypto import X509 + cert = X509.load_cert(self.file) + if self.cert.as_pem() == cert.as_pem(): + return True + else: + return False + + def trust_tmp(self): + self._tmptrusted[self.host] = self.cert + + def trust_always(self): + self.trust_tmp() + from M2Crypto import X509 + import os + if not os.path.exists(self.dir): + os.makedirs(self.dir) + self.cert.save_pem(self.file) + + +# verify_cb is called for each error once +# we only collect the errors and return suceess +# connection will be aborted later if it needs to +def verify_cb(ctx, ok, store): + if not ctx.verrs: + ctx.verrs = ValidationErrors() + + try: + if not ok: + ctx.verrs.record(store.get_current_cert(), store.get_error(), store.get_error_depth()) + return 1 + + except Exception as e: + print(e, file=sys.stderr) + return 0 + +class FailCert: + def __init__(self, cert): + self.cert = cert + self.errs = [] + +class ValidationErrors: + + def __init__(self): + self.chain_ok = True + self.cert_ok = True + self.failures = {} + + def record(self, cert, err, depth): + #print "cert for %s, level %d fail(%d)" % ( cert.get_subject().commonName, depth, err ) + if depth == 0: + self.cert_ok = False + else: + self.chain_ok = False + + if not depth in self.failures: + self.failures[depth] = FailCert(cert) + else: + if self.failures[depth].cert.get_fingerprint() != cert.get_fingerprint(): + raise Exception("Certificate changed unexpectedly. This should not happen") + self.failures[depth].errs.append(err) + + def show(self, out): + for depth in self.failures.keys(): + cert = self.failures[depth].cert + print("*** certificate verify failed at depth %d" % depth, file=out) + print("Subject: ", cert.get_subject(), file=out) + print("Issuer: ", cert.get_issuer(), file=out) + print("Valid: ", cert.get_not_before(), "-", cert.get_not_after(), file=out) + print("Fingerprint(MD5): ", cert.get_fingerprint('md5'), file=out) + print("Fingerprint(SHA1): ", cert.get_fingerprint('sha1'), file=out) + + for err in self.failures[depth].errs: + reason = "Unknown" + try: + import M2Crypto.Err + reason = M2Crypto.Err.get_x509_verify_error(err) + except: + pass + print("Reason:", reason, file=out) + + # check if the encountered errors could be ignored + def could_ignore(self): + if not 0 in self.failures: + return True + + nonfatal_errors = [ + m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + m2.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, + m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, + m2.X509_V_ERR_CERT_UNTRUSTED, + m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, + + m2.X509_V_ERR_CERT_NOT_YET_VALID, + m2.X509_V_ERR_CERT_HAS_EXPIRED, + m2.X509_V_OK, + ] + + canignore = True + for err in self.failures[0].errs: + if not err in nonfatal_errors: + canignore = False + break + + return canignore + +class mySSLContext(SSL.Context): + + def __init__(self): + SSL.Context.__init__(self, 'sslv23') + self.set_options(m2.SSL_OP_NO_SSLv2 | m2.SSL_OP_NO_SSLv3) + self.set_cipher_list("ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH") + self.set_session_cache_mode(m2.SSL_SESS_CACHE_CLIENT) + self.verrs = None + #self.set_info_callback() # debug + self.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9, callback=lambda ok, store: verify_cb(self, ok, store)) + +class myHTTPSHandler(M2Crypto.m2urllib2.HTTPSHandler): + handler_order = 499 + saved_session = None + + def __init__(self, *args, **kwargs): + self.appname = kwargs.pop('appname', 'generic') + M2Crypto.m2urllib2.HTTPSHandler.__init__(self, *args, **kwargs) + + # copied from M2Crypto.m2urllib2.HTTPSHandler + # it's sole purpose is to use our myHTTPSHandler/myHTTPSProxyHandler class + # ideally the m2urllib2.HTTPSHandler.https_open() method would be split into + # "do_open()" and "https_open()" so that we just need to override + # the small "https_open()" method...) + def https_open(self, req): + host = req.get_host() + if not host: + raise M2Crypto.m2urllib2.URLError('no host given: ' + req.get_full_url()) + + # Our change: Check to see if we're using a proxy. + # Then create an appropriate ssl-aware connection. + full_url = req.get_full_url() + target_host = urlparse(full_url)[1] + + if (target_host != host): + h = myProxyHTTPSConnection(host = host, appname = self.appname, ssl_context = self.ctx) + # M2Crypto.ProxyHTTPSConnection.putrequest expects a fullurl + selector = full_url + else: + h = myHTTPSConnection(host = host, appname = self.appname, ssl_context = self.ctx) + selector = req.get_selector() + # End our change + h.set_debuglevel(self._debuglevel) + if self.saved_session: + h.set_session(self.saved_session) + + headers = dict(req.headers) + headers.update(req.unredirected_hdrs) + # We want to make an HTTP/1.1 request, but the addinfourl + # class isn't prepared to deal with a persistent connection. + # It will try to read all remaining data from the socket, + # which will block while the server waits for the next request. + # So make sure the connection gets closed after the (only) + # request. + headers["Connection"] = "close" + try: + h.request(req.get_method(), selector, req.data, headers) + s = h.get_session() + if s: + self.saved_session = s + r = h.getresponse() + except socket.error as err: # XXX what error? + err.filename = full_url + raise M2Crypto.m2urllib2.URLError(err) + + # Pick apart the HTTPResponse object to get the addinfourl + # object initialized properly. + + # Wrap the HTTPResponse object in socket's file object adapter + # for Windows. That adapter calls recv(), so delegate recv() + # to read(). This weird wrapping allows the returned object to + # have readline() and readlines() methods. + + # XXX It might be better to extract the read buffering code + # out of socket._fileobject() and into a base class. + + r.recv = r.read + fp = socket._fileobject(r) + + resp = addinfourl(fp, r.msg, req.get_full_url()) + resp.code = r.status + resp.msg = r.reason + return resp + +class myHTTPSConnection(M2Crypto.httpslib.HTTPSConnection): + def __init__(self, *args, **kwargs): + self.appname = kwargs.pop('appname', 'generic') + M2Crypto.httpslib.HTTPSConnection.__init__(self, *args, **kwargs) + + def connect(self, *args): + self.sock = SSL.Connection(self.ssl_ctx) + if self.session: + self.sock.set_session(self.session) + if hasattr(self.sock, 'set_tlsext_host_name'): + self.sock.set_tlsext_host_name(self.host) + self.sock.connect((self.host, self.port)) + verify_certificate(self) + + def getHost(self): + return self.host + + def getPort(self): + return self.port + +class myProxyHTTPSConnection(M2Crypto.httpslib.ProxyHTTPSConnection, HTTPSConnection): + def __init__(self, *args, **kwargs): + self.appname = kwargs.pop('appname', 'generic') + M2Crypto.httpslib.ProxyHTTPSConnection.__init__(self, *args, **kwargs) + + def _start_ssl(self): + M2Crypto.httpslib.ProxyHTTPSConnection._start_ssl(self) + verify_certificate(self) + + def endheaders(self, *args, **kwargs): + if self._proxy_auth is None: + self._proxy_auth = self._encode_auth() + HTTPSConnection.endheaders(self, *args, **kwargs) + + # broken in m2crypto: port needs to be an int + def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): + #putrequest is called before connect, so can interpret url and get + #real host/port to be used to make CONNECT request to proxy + proto, rest = splittype(url) + if proto is None: + raise ValueError("unknown URL type: %s" % url) + #get host + host, rest = splithost(rest) + #try to get port + host, port = splitport(host) + #if port is not defined try to get from proto + if port is None: + try: + port = self._ports[proto] + except KeyError: + raise ValueError("unknown protocol for: %s" % url) + self._real_host = host + self._real_port = int(port) + M2Crypto.httpslib.HTTPSConnection.putrequest(self, method, url, skip_host, skip_accept_encoding) + + def getHost(self): + return self._real_host + + def getPort(self): + return self._real_port + +def verify_certificate(connection): + ctx = connection.sock.ctx + verrs = ctx.verrs + ctx.verrs = None + cert = connection.sock.get_peer_cert() + if not cert: + connection.close() + raise SSLVerificationError("server did not present a certificate") + + # XXX: should be check if the certificate is known anyways? + # Maybe it changed to something valid. + if not connection.sock.verify_ok(): + + tc = TrustedCertStore(connection.getHost(), connection.getPort(), connection.appname, cert) + + if tc.is_known(): + + if tc.is_trusted(): # ok, same cert as the stored one + return + else: + print("WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!", file=sys.stderr) + print("IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!", file=sys.stderr) + print("offending certificate is at '%s'" % tc.file, file=sys.stderr) + raise SSLVerificationError("remote host identification has changed") + + # if http_debug is set we redirect sys.stdout to an StringIO + # instance in order to do some header filtering (see conf module) + # so we have to use the "original" stdout for printing + out = getattr(connection, '_orig_stdout', sys.stdout) + verrs.show(out) + + print(file=out) + + if not verrs.could_ignore(): + raise SSLVerificationError("Certificate validation error cannot be ignored") + + if not verrs.chain_ok: + print("A certificate in the chain failed verification", file=out) + if not verrs.cert_ok: + print("The server certificate failed verification", file=out) + + while True: + print(""" +Would you like to +0 - quit (default) +1 - continue anyways +2 - trust the server certificate permanently +9 - review the server certificate +""", file=out) + + print("Enter choice [0129]: ", end='', file=out) + r = raw_input() + if not r or r == '0': + connection.close() + raise SSLVerificationError("Untrusted Certificate") + elif r == '1': + tc.trust_tmp() + return + elif r == '2': + tc.trust_always() + return + elif r == '9': + print(cert.as_text(), file=out) + +# vim: sw=4 et diff --git a/osc/oscsslexcp.py b/osc/oscsslexcp.py new file mode 100644 index 0000000..8c6af82 --- /dev/null +++ b/osc/oscsslexcp.py @@ -0,0 +1,8 @@ +class NoSecureSSLError(Exception): + def __init__(self, msg): + Exception.__init__(self) + self.msg = msg + def __str__(self): + return self.msg + +# vim: sw=4 et diff --git a/osc/util/__init__.py b/osc/util/__init__.py new file mode 100644 index 0000000..74769f9 --- /dev/null +++ b/osc/util/__init__.py @@ -0,0 +1 @@ +__all__ = ['ar', 'cpio', 'debquery', 'packagequery', 'rpmquery', 'safewriter'] diff --git a/osc/util/ar.py b/osc/util/ar.py new file mode 100644 index 0000000..34337a1 --- /dev/null +++ b/osc/util/ar.py @@ -0,0 +1,214 @@ +# Copyright 2009 Marcus Huewe <suse-tux@gmx.de> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation; +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import print_function + +import os +import re +import sys +import stat + +#XXX: python 2.7 contains io.StringIO, which needs unicode instead of str +#therefor try to import old stuff before new one here +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +# workaround for python24 +if not hasattr(os, 'SEEK_SET'): + os.SEEK_SET = 0 + +class ArError(Exception): + """Base class for all ar related errors""" + def __init__(self, fn, msg): + Exception.__init__(self) + self.file = fn + self.msg = msg + + def __str__(self): + return 'ar error: %s' % self.msg + +class ArHdr: + """Represents an ar header entry""" + def __init__(self, fn, date, uid, gid, mode, size, fmag, off): + self.file = fn.strip() + self.date = date.strip() + self.uid = uid.strip() + self.gid = gid.strip() + self.mode = stat.S_IMODE(int(mode, 8)) + self.size = int(size) + self.fmag = fmag + # data section starts at off and ends at off + size + self.dataoff = int(off) + + def __str__(self): + return '%16s %d' % (self.file, self.size) + +class ArFile(StringIO): + """Represents a file which resides in the archive""" + def __init__(self, fn, uid, gid, mode, buf): + StringIO.__init__(self, buf) + self.name = fn + self.uid = uid + self.gid = gid + self.mode = mode + + def saveTo(self, dir = None): + """ + writes file to dir/filename if dir isn't specified the current + working dir is used. Additionally it tries to set the owner/group + and permissions. + """ + if not dir: + dir = os.getcwd() + fn = os.path.join(dir, self.name) + f = open(fn, 'wb') + f.write(self.getvalue()) + f.close() + os.chmod(fn, self.mode) + uid = self.uid + if uid != os.geteuid() or os.geteuid() != 0: + uid = -1 + gid = self.gid + if not gid in os.getgroups() or os.getegid() != 0: + gid = -1 + os.chown(fn, uid, gid) + + def __str__(self): + return '%s %s %s %s' % (self.name, self.uid, + self.gid, self.mode) + +class Ar: + """ + Represents an ar archive (only GNU format is supported). + Readonly access. + """ + hdr_len = 60 + hdr_pat = re.compile('^(.{16})(.{12})(.{6})(.{6})(.{8})(.{10})(.{2})', re.DOTALL) + + def __init__(self, fn = None, fh = None): + if fn == None and fh == None: + raise ArError('either \'fn\' or \'fh\' must be != None') + if fh != None: + self.__file = fh + self.__closefile = False + self.filename = fh.name + else: + # file object: will be closed in __del__() + self.__file = None + self.__closefile = True + self.filename = fn + self._init_datastructs() + + def __del__(self): + if self.__file and self.__closefile: + self.__file.close() + + def _init_datastructs(self): + self.hdrs = [] + self.ext_fnhdr = None + + def _appendHdr(self, hdr): + # GNU uses an internal '//' file to store very long filenames + if hdr.file.startswith('//'): + self.ext_fnhdr = hdr + else: + self.hdrs.append(hdr) + + def _fixupFilenames(self): + """ + support the GNU approach for very long filenames: + every filename which exceeds 16 bytes is stored in the data section of a special file ('//') + and the filename in the header of this long file specifies the offset in the special file's + data section. The end of such a filename is indicated with a trailing '/'. + Another special file is the '/' which contains the symbol lookup table. + """ + for h in self.hdrs: + if h.file == '/': + continue + # remove slashes which are appended by ar + h.file = h.file.rstrip('/') + if not h.file.startswith('/'): + continue + # handle long filename + off = int(h.file[1:len(h.file)]) + start = self.ext_fnhdr.dataoff + off + self.__file.seek(start, os.SEEK_SET) + # XXX: is it safe to read all the data in one chunk? I assume the '//' data section + # won't be too large + data = self.__file.read(self.ext_fnhdr.size) + end = data.find('/') + if end != -1: + h.file = data[0:end] + else: + raise ArError('//', 'invalid data section - trailing slash (off: %d)' % start) + + def _get_file(self, hdr): + self.__file.seek(hdr.dataoff, os.SEEK_SET) + return ArFile(hdr.file, hdr.uid, hdr.gid, hdr.mode, + self.__file.read(hdr.size)) + + def read(self): + """reads in the archive. It tries to use mmap due to performance reasons (in case of large files)""" + if not self.__file: + import mmap + self.__file = open(self.filename, 'rb') + try: + if sys.platform[:3] != 'win': + self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name), prot=mmap.PROT_READ) + else: + self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name)) + except EnvironmentError as e: + if e.errno == 19 or ( hasattr(e, 'winerror') and e.winerror == 5 ): + print('cannot use mmap to read the file, falling back to the default io', file=sys.stderr) + else: + raise e + else: + self.__file.seek(0, os.SEEK_SET) + self._init_datastructs() + data = self.__file.read(7) + if data != '!<arch>': + raise ArError(self.filename, 'no ar archive') + pos = 8 + while (len(data) != 0): + self.__file.seek(pos, os.SEEK_SET) + data = self.__file.read(self.hdr_len) + if not data: + break + pos += self.hdr_len + m = self.hdr_pat.search(data) + if not m: + raise ArError(self.filename, 'unexpected hdr entry') + args = m.groups() + (pos, ) + hdr = ArHdr(*args) + self._appendHdr(hdr) + # data blocks are 2 bytes aligned - if they end on an odd + # offset ARFMAG[0] will be used for padding (according to the current binutils code) + pos += hdr.size + (hdr.size & 1) + self._fixupFilenames() + + def get_file(self, fn): + for h in self.hdrs: + if h.file == fn: + return self._get_file(h) + return None + + def __iter__(self): + for h in self.hdrs: + if h.file == '/': + continue + yield self._get_file(h) + raise StopIteration() diff --git a/osc/util/archquery.py b/osc/util/archquery.py new file mode 100644 index 0000000..2af87de --- /dev/null +++ b/osc/util/archquery.py @@ -0,0 +1,187 @@ + +from __future__ import print_function + +import os.path +import re +import tarfile +from . import packagequery +import subprocess + +class ArchError(packagequery.PackageError): + pass + +class ArchQuery(packagequery.PackageQuery, packagequery.PackageQueryResult): + def __init__(self, fh): + self.__file = fh + self.__path = os.path.abspath(fh.name) + self.fields = {} + #self.magic = None + #self.pkgsuffix = 'pkg.tar.gz' + self.pkgsuffix = 'arch' + + def read(self, all_tags=True, self_provides=True, *extra_tags): + # all_tags and *extra_tags are currently ignored + f = open(self.__path, 'rb') + #self.magic = f.read(5) + #if self.magic == '\375\067zXZ': + # self.pkgsuffix = 'pkg.tar.xz' + fn = open('/dev/null', 'wb') + pipe = subprocess.Popen(['tar', '-O', '-xf', self.__path, '.PKGINFO'], stdout=subprocess.PIPE, stderr=fn).stdout + for line in pipe.readlines(): + line = line.rstrip().split(' = ', 2) + if len(line) == 2: + if not line[0] in self.fields: + self.fields[line[0]] = [] + self.fields[line[0]].append(line[1]) + if self_provides: + prv = '%s = %s' % (self.name(), self.fields['pkgver'][0]) + self.fields.setdefault('provides', []).append(prv) + return self + + def vercmp(self, archq): + res = cmp(int(self.epoch()), int(archq.epoch())) + if res != 0: + return res + res = ArchQuery.rpmvercmp(self.version(), archq.version()) + if res != None: + return res + res = ArchQuery.rpmvercmp(self.release(), archq.release()) + return res + + def name(self): + return self.fields['pkgname'][0] if 'pkgname' in self.fields else None + + def version(self): + pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None + if pkgver != None: + pkgver = re.sub(r'[0-9]+:', '', pkgver, 1) + pkgver = re.sub(r'-[^-]*$', '', pkgver) + return pkgver + + def release(self): + pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None + if pkgver != None: + m = re.search(r'-([^-])*$', pkgver) + if m: + return m.group(1) + return None + + def epoch(self): + pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None + if pkgver != None: + m = re.match(r'([0-9])+:', pkgver) + if m: + return m.group(1) + return None + + def arch(self): + return self.fields['arch'][0] if 'arch' in self.fields else None + + def description(self): + return self.fields['pkgdesc'][0] if 'pkgdesc' in self.fields else None + + def path(self): + return self.__path + + def provides(self): + return self.fields['provides'] if 'provides' in self.fields else [] + + def requires(self): + return self.fields['depend'] if 'depend' in self.fields else [] + + def conflicts(self): + return self.fields['conflict'] if 'conflict' in self.fields else [] + + def obsoletes(self): + return self.fields['replaces'] if 'replaces' in self.fields else [] + + def canonname(self): + pkgver = self.fields['pkgver'][0] if 'pkgver' in self.fields else None + return self.name() + '-' + pkgver + '-' + self.arch() + '.' + self.pkgsuffix + + def gettag(self, tag): + # implement me, if needed + return None + + @staticmethod + def query(filename, all_tags = False, *extra_tags): + f = open(filename, 'rb') + archq = ArchQuery(f) + archq.read(all_tags, *extra_tags) + f.close() + return archq + + @staticmethod + def rpmvercmp(ver1, ver2): + """ + implementation of RPM's version comparison algorithm + (as described in lib/rpmvercmp.c) + """ + if ver1 == ver2: + return 0 + res = 0 + while res == 0: + # remove all leading non alphanumeric chars + ver1 = re.sub('^[^a-zA-Z0-9]*', '', ver1) + ver2 = re.sub('^[^a-zA-Z0-9]*', '', ver2) + if not (len(ver1) and len(ver2)): + break + # check if we have a digits segment + mo1 = re.match('(\d+)', ver1) + mo2 = re.match('(\d+)', ver2) + numeric = True + if mo1 is None: + mo1 = re.match('([a-zA-Z]+)', ver1) + mo2 = re.match('([a-zA-Z]+)', ver2) + numeric = False + # check for different types: alpha and numeric + if mo2 is None: + if numeric: + return 1 + return -1 + seg1 = mo1.group(0) + ver1 = ver1[mo1.end(0):] + seg2 = mo2.group(1) + ver2 = ver2[mo2.end(1):] + if numeric: + # remove leading zeros + seg1 = re.sub('^0+', '', seg1) + seg2 = re.sub('^0+', '', seg2) + # longer digit segment wins - if both have the same length + # a simple ascii compare decides + res = len(seg1) - len(seg2) or cmp(seg1, seg2) + else: + res = cmp(seg1, seg2) + if res > 0: + return 1 + elif res < 0: + return -1 + return cmp(ver1, ver2) + + @staticmethod + def filename(name, epoch, version, release, arch): + if epoch: + if release: + return '%s-%s:%s-%s-%s.arch' % (name, epoch, version, release, arch) + else: + return '%s-%s:%s-%s.arch' % (name, epoch, version, arch) + if release: + return '%s-%s-%s-%s.arch' % (name, version, release, arch) + else: + return '%s-%s-%s.arch' % (name, version, arch) + + +if __name__ == '__main__': + import sys + try: + archq = ArchQuery.query(sys.argv[1]) + except ArchError as e: + print(e.msg) + sys.exit(2) + print(archq.name(), archq.version(), archq.release(), archq.arch()) + print(archq.canonname()) + print(archq.description()) + print('##########') + print('\n'.join(archq.provides())) + print('##########') + print('\n'.join(archq.requires())) diff --git a/osc/util/cpio.py b/osc/util/cpio.py new file mode 100644 index 0000000..cd3c406 --- /dev/null +++ b/osc/util/cpio.py @@ -0,0 +1,256 @@ +# Copyright 2009 Marcus Huewe <suse-tux@gmx.de> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation; +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +from __future__ import print_function + +import mmap +import os +import stat +import struct +import sys + +# workaround for python24 +if not hasattr(os, 'SEEK_SET'): + os.SEEK_SET = 0 + +# format implementation is based on src/copyin.c and src/util.c (see cpio sources) + +class CpioError(Exception): + """base class for all cpio related errors""" + def __init__(self, fn, msg): + Exception.__init__(self) + self.file = fn + self.msg = msg + def __str__(self): + return '%s: %s' % (self.file, self.msg) + +class CpioHdr: + """ + Represents a cpio header ("New" portable format and CRC format). + """ + def __init__(self, mgc, ino, mode, uid, gid, nlink, mtime, filesize, + dev_maj, dev_min, rdev_maj, rdev_min, namesize, checksum, + off = -1, filename = ''): + """ + All passed parameters are hexadecimal strings (not NUL terminated) except + off and filename. They will be converted into normal ints. + """ + self.ino = ino + self.mode = mode + self.uid = uid + self.gid = gid + self.nlink = nlink + self.mtime = mtime + # 0 indicates FIFO or dir + self.filesize = filesize + self.dev_maj = dev_maj + self.dev_min = dev_min + # only needed for special block/char files + self.rdev_maj = rdev_maj + self.rdev_min = rdev_min + # length of filename (inluding terminating NUL) + self.namesize = namesize + # != 0 indicates CRC format (which we do not support atm) + self.checksum = checksum + for k, v in self.__dict__.items(): + self.__dict__[k] = int(v, 16) + self.filename = filename + # data starts at dataoff and ends at dataoff+filesize + self.dataoff = off + + def __str__(self): + return "%s %s %s %s" % (self.filename, self.filesize, self.namesize, self.dataoff) + +class CpioRead: + """ + Represents a cpio archive. + Supported formats: + * ascii SVR4 no CRC also called "new_ascii" + """ + + # supported formats - use name -> mgc mapping to increase readabilty + sfmt = { + 'newascii': '070701', + } + + # header format + hdr_fmt = '6s8s8s8s8s8s8s8s8s8s8s8s8s8s' + hdr_len = 110 + + def __init__(self, filename): + self.filename = filename + self.format = -1 + self.__file = None + self._init_datastructs() + + def __del__(self): + if self.__file: + self.__file.close() + + def __iter__(self): + for h in self.hdrs: + yield h + + def _init_datastructs(self): + self.hdrs = [] + + def _calc_padding(self, off): + """ + skip some bytes after a header or a file. + based on 'static void tape_skip_padding()' in copyin.c. + """ + if self._is_format('newascii'): + return (4 - (off % 4)) % 4 + + def _is_format(self, type): + return self.format == self.sfmt[type] + + def _copyin_file(self, hdr, dest, fn): + """saves file to disk""" + # TODO: investigate links (e.g. symbolic links are working) + # check if we have a regular file + if not stat.S_ISREG(stat.S_IFMT(hdr.mode)): + msg = '\'%s\' is no regular file - only regular files are supported atm' % hdr.filename + raise NotImplementedError(msg) + fn = os.path.join(dest, fn) + f = open(fn, 'wb') + self.__file.seek(hdr.dataoff, os.SEEK_SET) + f.write(self.__file.read(hdr.filesize)) + f.close() + os.chmod(fn, hdr.mode) + uid = hdr.uid + if uid != os.geteuid() or os.geteuid() != 1: + uid = -1 + gid = hdr.gid + if not gid in os.getgroups() or os.getegid() != -1: + gid = -1 + os.chown(fn, uid, gid) + + def _get_hdr(self, fn): + for h in self.hdrs: + if h.filename == fn: + return h + return None + + def read(self): + if not self.__file: + self.__file = open(self.filename, 'rb') + try: + if sys.platform[:3] != 'win': + self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name), prot = mmap.PROT_READ) + else: + self.__file = mmap.mmap(self.__file.fileno(), os.path.getsize(self.__file.name)) + except EnvironmentError as e: + if e.errno == 19 or ( hasattr(e, 'winerror') and e.winerror == 5 ): + print('cannot use mmap to read the file, failing back to default', file=sys.stderr) + else: + raise e + else: + self.__file.seek(0, os.SEEK_SET) + self._init_datastructs() + data = self.__file.read(6) + self.format = data + if not self.format in self.sfmt.values(): + raise CpioError(self.filename, '\'%s\' is not a supported cpio format' % self.format) + pos = 0 + while (len(data) != 0): + self.__file.seek(pos, os.SEEK_SET) + data = self.__file.read(self.hdr_len) + if not data: + break + pos += self.hdr_len + data = struct.unpack(self.hdr_fmt, data) + hdr = CpioHdr(*data) + hdr.filename = self.__file.read(hdr.namesize - 1) + if hdr.filename == 'TRAILER!!!': + break + pos += hdr.namesize + if self._is_format('newascii'): + pos += self._calc_padding(hdr.namesize + 110) + hdr.dataoff = pos + self.hdrs.append(hdr) + pos += hdr.filesize + self._calc_padding(hdr.filesize) + + def copyin_file(self, filename, dest = None, new_fn = None): + """ + copies filename to dest. + If dest is None the file will be stored in $PWD/filename. If dest points + to a dir the file will be stored in dest/filename. In case new_fn is specified + the file will be stored as new_fn. + """ + hdr = self._get_hdr(filename) + if not hdr: + raise CpioError(filename, '\'%s\' does not exist in archive' % filename) + dest = dest or os.getcwd() + fn = new_fn or filename + self._copyin_file(hdr, dest, fn) + + def copyin(self, dest = None): + """ + extracts the cpio archive to dest. + If dest is None $PWD will be used. + """ + dest = dest or os.getcwd() + for h in self.hdrs: + self._copyin_file(h, dest, h.filename) + +class CpioWrite: + """cpio archive small files in memory, using new style portable header format""" + + def __init__(self): + self.cpio = '' + + def add(self, name=None, content=None, perms=0x1a4, type=0x8000): + namesize = len(name) + 1 + if namesize % 2: + name += '\0' + filesize = len(content) + mode = perms | type + + c = [] + c.append('070701') # magic + c.append('%08X' % 0) # inode + c.append('%08X' % mode) # mode + c.append('%08X' % 0) # uid + c.append('%08X' % 0) # gid + c.append('%08X' % 0) # nlink + c.append('%08X' % 0) # mtime + c.append('%08X' % filesize) + c.append('%08X' % 0) # major + c.append('%08X' % 0) # minor + c.append('%08X' % 0) # rmajor + c.append('%08X' % 0) # rminor + c.append('%08X' % namesize) + c.append('%08X' % 0) # checksum + + c.append(name + '\0') + c.append('\0' * (len(''.join(c)) % 4)) + + c.append(content) + + c = ''.join(c) + if len(c) % 4: + c += '\0' * (4 - len(c) % 4) + + self.cpio += c + + def add_padding(self): + if len(self.cpio) % 512: + self.cpio += '\0' * (512 - len(self.cpio) % 512) + + def get(self): + self.add('TRAILER!!!', '') + self.add_padding() + return ''.join(self.cpio) diff --git a/osc/util/debquery.py b/osc/util/debquery.py new file mode 100644 index 0000000..4b1a823 --- /dev/null +++ b/osc/util/debquery.py @@ -0,0 +1,191 @@ + +from __future__ import print_function + +from . import ar +import os.path +import re +import tarfile +from . import packagequery + +class DebError(packagequery.PackageError): + pass + +class DebQuery(packagequery.PackageQuery, packagequery.PackageQueryResult): + + default_tags = ('package', 'version', 'release', 'epoch', 'architecture', 'description', + 'provides', 'depends', 'pre_depends', 'conflicts', 'breaks') + + def __init__(self, fh): + self.__file = fh + self.__path = os.path.abspath(fh.name) + self.filename_suffix = 'deb' + self.fields = {} + + def read(self, all_tags=False, self_provides=True, *extra_tags): + arfile = ar.Ar(fh = self.__file) + arfile.read() + debbin = arfile.get_file('debian-binary') + if debbin is None: + raise DebError(self.__path, 'no debian binary') + if debbin.read() != '2.0\n': + raise DebError(self.__path, 'invalid debian binary format') + control = arfile.get_file('control.tar.gz') + if control is None: + raise DebError(self.__path, 'missing control.tar.gz') + # XXX: python2.4 relies on a name + tar = tarfile.open(name = 'control.tar.gz', fileobj = control) + try: + name = './control' + # workaround for python2.4's tarfile module + if 'control' in tar.getnames(): + name = 'control' + control = tar.extractfile(name) + except KeyError: + raise DebError(self.__path, 'missing \'control\' file in control.tar.gz') + self.__parse_control(control, all_tags, self_provides, *extra_tags) + return self + + def __parse_control(self, control, all_tags=False, self_provides=True, *extra_tags): + data = control.readline().strip() + while data: + field, val = re.split(':\s*', data.strip(), 1) + data = control.readline() + while data and re.match('\s+', data): + val += '\n' + data.strip() + data = control.readline().rstrip() + field = field.replace('-', '_').lower() + if field in self.default_tags + extra_tags or all_tags: + # a hyphen is not allowed in dict keys + self.fields[field] = val + versrel = self.fields['version'].rsplit('-', 1) + if len(versrel) == 2: + self.fields['version'] = versrel[0] + self.fields['release'] = versrel[1] + else: + self.fields['release'] = None + verep = self.fields['version'].split(':', 1) + if len(verep) == 2: + self.fields['epoch'] = verep[0] + self.fields['version'] = verep[1] + else: + self.fields['epoch'] = '0' + self.fields['provides'] = [ i.strip() for i in re.split(',\s*', self.fields.get('provides', '')) if i ] + self.fields['depends'] = [ i.strip() for i in re.split(',\s*', self.fields.get('depends', '')) if i ] + self.fields['pre_depends'] = [ i.strip() for i in re.split(',\s*', self.fields.get('pre_depends', '')) if i ] + self.fields['conflicts'] = [ i.strip() for i in re.split(',\s*', self.fields.get('conflicts', '')) if i ] + self.fields['breaks'] = [ i.strip() for i in re.split(',\s*', self.fields.get('breaks', '')) if i ] + if self_provides: + # add self provides entry + self.fields['provides'].append('%s (= %s)' % (self.name(), '-'.join(versrel))) + + def vercmp(self, debq): + res = cmp(int(self.epoch()), int(debq.epoch())) + if res != 0: + return res + res = DebQuery.debvercmp(self.version(), debq.version()) + if res != None: + return res + res = DebQuery.debvercmp(self.release(), debq.release()) + return res + + def name(self): + return self.fields['package'] + + def version(self): + return self.fields['version'] + + def release(self): + return self.fields['release'] + + def epoch(self): + return self.fields['epoch'] + + def arch(self): + return self.fields['architecture'] + + def description(self): + return self.fields['description'] + + def path(self): + return self.__path + + def provides(self): + return self.fields['provides'] + + def requires(self): + return self.fields['depends'] + self.fields['pre_depends'] + + def conflicts(self): + return self.fields['conflicts'] + self.fields['breaks'] + + def obsoletes(self): + return [] + + def gettag(self, num): + return self.fields.get(num, None) + + def canonname(self): + return DebQuery.filename(self.name(), self.epoch(), self.version(), self.release(), self.arch()) + + @staticmethod + def query(filename, all_tags = False, *extra_tags): + f = open(filename, 'rb') + debq = DebQuery(f) + debq.read(all_tags, *extra_tags) + f.close() + return debq + + @staticmethod + def debvercmp(ver1, ver2): + """ + implementation of dpkg's version comparison algorithm + """ + # 32 is arbitrary - it is needed for the "longer digit string wins" handling + # (found this nice approach in Build/Deb.pm (build package)) + ver1 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver1) + ver2 = re.sub('(\d+)', lambda m: (32 * '0' + m.group(1))[-32:], ver2) + vers = map(lambda x, y: (x or '', y or ''), ver1, ver2) + for v1, v2 in vers: + if v1 == v2: + continue + if (v1.isalpha() and v2.isalpha()) or (v1.isdigit() and v2.isdigit()): + res = cmp(v1, v2) + if res != 0: + return res + else: + if v1 == '~' or not v1: + return -1 + elif v2 == '~' or not v2: + return 1 + ord1 = ord(v1) + if not (v1.isalpha() or v1.isdigit()): + ord1 += 256 + ord2 = ord(v2) + if not (v2.isalpha() or v2.isdigit()): + ord2 += 256 + if ord1 > ord2: + return 1 + else: + return -1 + return 0 + + @staticmethod + def filename(name, epoch, version, release, arch): + if release: + return '%s_%s-%s_%s.deb' % (name, version, release, arch) + else: + return '%s_%s_%s.deb' % (name, version, arch) + +if __name__ == '__main__': + import sys + try: + debq = DebQuery.query(sys.argv[1]) + except DebError as e: + print(e.msg) + sys.exit(2) + print(debq.name(), debq.version(), debq.release(), debq.arch()) + print(debq.description()) + print('##########') + print('\n'.join(debq.provides())) + print('##########') + print('\n'.join(debq.requires())) diff --git a/osc/util/packagequery.py b/osc/util/packagequery.py new file mode 100644 index 0000000..950f222 --- /dev/null +++ b/osc/util/packagequery.py @@ -0,0 +1,164 @@ + +from __future__ import print_function + +class PackageError(Exception): + """base class for all package related errors""" + def __init__(self, fname, msg): + Exception.__init__(self) + self.fname = fname + self.msg = msg + +class PackageQueries(dict): + """Dict of package name keys and package query values. When assigning a + package query, to a name, the package is evaluated to see if it matches the + wanted architecture and if it has a greater version than the current value. + """ + + # map debian arches to common obs arches + architectureMap = {'i386': ['i586', 'i686'], 'amd64': ['x86_64'], 'ppc64el': ['ppc64le']} + + def __init__(self, wanted_architecture): + self.wanted_architecture = wanted_architecture + super(PackageQueries, self).__init__() + + def add(self, query): + """Adds package query to dict if it is of the correct architecture and + is newer (has a greater version) than the currently assigned package. + + @param a PackageQuery + """ + self.__setitem__(query.name(), query) + + def __setitem__(self, name, query): + if name != query.name(): + raise ValueError("key '%s' does not match " + "package query name '%s'" % (name, query.name())) + + architecture = query.arch() + + if (architecture in [self.wanted_architecture, 'noarch', 'all', 'any'] + or self.wanted_architecture in self.architectureMap.get(architecture, + [])): + current_query = self.get(name) + + # if current query does not exist or is older than this new query + if current_query is None or current_query.vercmp(query) <= 0: + super(PackageQueries, self).__setitem__(name, query) + +class PackageQuery: + """abstract base class for all package types""" + def read(self, all_tags = False, *extra_tags): + """Returns a PackageQueryResult instance""" + raise NotImplementedError + + # Hmmm... this should be a module function (inherting this stuff + # does not make much sense) (the same is true for the queryhdrmd5 method) + @staticmethod + def query(filename, all_tags=False, extra_rpmtags=(), extra_debtags=(), self_provides=True): + f = open(filename, 'rb') + magic = f.read(7) + f.seek(0) + extra_tags = () + pkgquery = None + if magic[:4] == '\xed\xab\xee\xdb': + from . import rpmquery + pkgquery = rpmquery.RpmQuery(f) + extra_tags = extra_rpmtags + elif magic == '!<arch>': + from . import debquery + pkgquery = debquery.DebQuery(f) + extra_tags = extra_debtags + elif magic[:5] == '<?xml': + f.close() + return None + elif magic[:5] == '\375\067zXZ' or magic[:2] == '\037\213': + from . import archquery + pkgquery = archquery.ArchQuery(f) + else: + raise PackageError(filename, 'unsupported package type. magic: \'%s\'' % magic) + pkgqueryresult = pkgquery.read(all_tags, self_provides, *extra_tags) + f.close() + return pkgqueryresult + + @staticmethod + def queryhdrmd5(filename): + f = open(filename, 'rb') + magic = f.read(7) + f.seek(0) + if magic[:4] == '\xed\xab\xee\xdb': + from . import rpmquery + f.close() + return rpmquery.RpmQuery.queryhdrmd5(filename) + return None + + +class PackageQueryResult: + """abstract base class that represents the result of a package query""" + def name(self): + raise NotImplementedError + + def version(self): + raise NotImplementedError + + def release(self): + raise NotImplementedError + + def epoch(self): + raise NotImplementedError + + def arch(self): + raise NotImplementedError + + def description(self): + raise NotImplementedError + + def path(self): + raise NotImplementedError + + def provides(self): + raise NotImplementedError + + def requires(self): + raise NotImplementedError + + def conflicts(self): + raise NotImplementedError + + def obsoletes(self): + raise NotImplementedError + + def gettag(self, tag): + raise NotImplementedError + + def vercmp(self, pkgquery): + raise NotImplementedError + + def canonname(self): + raise NotImplementedError + + def evr(self): + evr = self.version() + + if self.release(): + evr += "-" + self.release() + + epoch = self.epoch() + if epoch is not None and epoch != 0: + evr = epoch + ":" + evr + return evr + +if __name__ == '__main__': + import sys + try: + pkgq = PackageQuery.query(sys.argv[1]) + except PackageError as e: + print(e.msg) + sys.exit(2) + print(pkgq.name()) + print(pkgq.version()) + print(pkgq.release()) + print(pkgq.description()) + print('##########') + print('\n'.join(pkgq.provides())) + print('##########') + print('\n'.join(pkgq.requires())) diff --git a/osc/util/repodata.py b/osc/util/repodata.py new file mode 100644 index 0000000..7f952f5 --- /dev/null +++ b/osc/util/repodata.py @@ -0,0 +1,176 @@ +"""Module for reading repodata directory (created with createrepo) for package +information instead of scanning individual rpms.""" + +# standard modules +import gzip +import os.path + +# cElementTree can be standard or 3rd-party depending on python version +try: + from xml.etree import cElementTree as ET +except ImportError: + import cElementTree as ET + +# project modules +import osc.util.rpmquery +import osc.util.packagequery + +def namespace(name): + return "{http://linux.duke.edu/metadata/%s}" % name + +OPERATOR_BY_FLAGS = { + "EQ" : "=", + "LE" : "<=", + "GE" : ">=" +} + +def primaryPath(directory): + """Returns path to the primary repository data file. + + @param directory repository directory that contains the repodata subdirectory + @return str path to primary repository data file + @raise IOError if repomd.xml contains no primary location + """ + metaDataPath = os.path.join(directory, "repodata", "repomd.xml") + elementTree = ET.parse(metaDataPath) + root = elementTree.getroot() + + for dataElement in root: + if dataElement.get("type") == "primary": + locationElement = dataElement.find(namespace("repo") + "location") + # even though the repomd.xml file is under repodata, the location a + # attribute is relative to parent directory (directory). + primaryPath = os.path.join(directory, locationElement.get("href")) + break + else: + raise IOError("'%s' contains no primary location" % metaDataPath) + + return primaryPath + +def queries(directory): + """Returns a list of RepoDataQueries constructed from the repodata under + the directory. + + @param directory path to a repository directory (parent directory of + repodata directory) + @return list of RepoDataQueryResult instances + @raise IOError if repomd.xml contains no primary location + """ + path = primaryPath(directory) + + gunzippedPrimary = gzip.GzipFile(path) + elementTree = ET.parse(gunzippedPrimary) + root = elementTree.getroot() + + packageQueries = [] + for packageElement in root: + packageQuery = RepoDataQueryResult(directory, packageElement) + packageQueries.append(packageQuery) + + return packageQueries + +class RepoDataQueryResult(osc.util.packagequery.PackageQueryResult): + """PackageQueryResult that reads in data from the repodata directory files.""" + + def __init__(self, directory, element): + """Creates a RepoDataQueryResult from the a package Element under a metadata + Element in a primary.xml file. + + @param directory repository directory path. Used to convert relative + paths to full paths. + @param element package Element + """ + self.__directory = os.path.abspath(directory) + self.__element = element + + def __formatElement(self): + return self.__element.find(namespace("common") + "format") + + def __parseEntry(self, element): + entry = element.get("name") + flags = element.get("flags") + + if flags is not None: + version = element.get("ver") + operator = OPERATOR_BY_FLAGS[flags] + entry += " %s %s" % (operator, version) + + release = element.get("rel") + if release is not None: + entry += "-%s" % release + + return entry + + def __parseEntryCollection(self, collection): + formatElement = self.__formatElement() + collectionElement = formatElement.find(namespace("rpm") + collection) + + entries = [] + if collectionElement is not None: + for entryElement in collectionElement.findall(namespace("rpm") + + "entry"): + entry = self.__parseEntry(entryElement) + entries.append(entry) + + return entries + + def __versionElement(self): + return self.__element.find(namespace("common") + "version") + + def arch(self): + return self.__element.find(namespace("common") + "arch").text + + def description(self): + return self.__element.find(namespace("common") + "description").text + + def distribution(self): + return None + + def epoch(self): + return self.__versionElement().get("epoch") + + def name(self): + return self.__element.find(namespace("common") + "name").text + + def path(self): + locationElement = self.__element.find(namespace("common") + "location") + relativePath = locationElement.get("href") + absolutePath = os.path.join(self.__directory, relativePath) + + return absolutePath + + def provides(self): + return self.__parseEntryCollection("provides") + + def release(self): + return self.__versionElement().get("rel") + + def requires(self): + return self.__parseEntryCollection("requires") + + def conflicts(self): + return self.__parseEntryCollection('conflicts') + + def obsoletes(self): + return self.__parseEntryCollection('obsoletes') + + def canonname(self): + return osc.util.rpmquery.RpmQuery.filename(self.name(), None, + self.version(), self.release(), self.arch()) + + def gettag(self, tag): + # implement me, if needed + return None + + def vercmp(self, other): + res = osc.util.rpmquery.RpmQuery.rpmvercmp(str(self.epoch()), str(other.epoch())) + if res != 0: + return res + res = osc.util.rpmquery.RpmQuery.rpmvercmp(self.version(), other.version()) + if res != 0: + return res + res = osc.util.rpmquery.RpmQuery.rpmvercmp(self.release(), other.release()) + return res + + def version(self): + return self.__versionElement().get("ver") diff --git a/osc/util/rpmquery.py b/osc/util/rpmquery.py new file mode 100644 index 0000000..de05ff5 --- /dev/null +++ b/osc/util/rpmquery.py @@ -0,0 +1,366 @@ + +from __future__ import print_function + +import os +import re +import struct +from . import packagequery + +class RpmError(packagequery.PackageError): + pass + +class RpmHeaderError(RpmError): + pass + +class RpmHeader: + """corresponds more or less to the indexEntry_s struct""" + def __init__(self, offset, length): + self.offset = offset + # length of the data section (without length of indexEntries) + self.length = length + self.entries = [] + + def append(self, entry): + self.entries.append(entry) + + def gettag(self, tag): + for i in self.entries: + if i.tag == tag: + return i + return None + + def __iter__(self): + for i in self.entries: + yield i + + def __len__(self): + return len(self.entries) + +class RpmHeaderEntry: + """corresponds to the entryInfo_s struct (except the data attribute)""" + + # each element represents an int + ENTRY_SIZE = 16 + def __init__(self, tag, type, offset, count): + self.tag = tag + self.type = type + self.offset = offset + self.count = count + self.data = None + +class RpmQuery(packagequery.PackageQuery, packagequery.PackageQueryResult): + LEAD_SIZE = 96 + LEAD_MAGIC = 0xedabeedb + HEADER_MAGIC = 0x8eade801 + HEADERSIG_TYPE = 5 + + LESS = 1 << 1 + GREATER = 1 << 2 + EQUAL = 1 << 3 + + default_tags = (1000, 1001, 1002, 1003, 1004, 1022, 1005, 1020, + 1047, 1112, 1113, # provides + 1049, 1048, 1050, # requires + 1054, 1053, 1055, # conflicts + 1090, 1114, 1115 # obsoletes + ) + + def __init__(self, fh): + self.__file = fh + self.__path = os.path.abspath(fh.name) + self.filename_suffix = 'rpm' + self.header = None + + def read(self, all_tags=False, self_provides=True, *extra_tags, **extra_kw): + # self_provides is unused because a rpm always has a self provides + self.__read_lead() + data = self.__file.read(RpmHeaderEntry.ENTRY_SIZE) + hdrmgc, reserved, il, dl = struct.unpack('!I3i', data) + if self.HEADER_MAGIC != hdrmgc: + raise RpmHeaderError(self.__path, 'invalid headermagic \'%s\'' % hdrmgc) + # skip signature header for now + size = il * RpmHeaderEntry.ENTRY_SIZE + dl + # data is 8 byte aligned + pad = (size + 7) & ~7 + querysig = extra_kw.get('querysig') + if not querysig: + self.__file.read(pad) + data = self.__file.read(RpmHeaderEntry.ENTRY_SIZE) + hdrmgc, reserved, il, dl = struct.unpack('!I3i', data) + self.header = RpmHeader(pad, dl) + if self.HEADER_MAGIC != hdrmgc: + raise RpmHeaderError(self.__path, 'invalid headermagic \'%s\'' % hdrmgc) + data = self.__file.read(il * RpmHeaderEntry.ENTRY_SIZE) + while len(data) > 0: + ei = struct.unpack('!4i', data[:RpmHeaderEntry.ENTRY_SIZE]) + self.header.append(RpmHeaderEntry(*ei)) + data = data[RpmHeaderEntry.ENTRY_SIZE:] + data = self.__file.read(self.header.length) + for i in self.header: + if i.tag in self.default_tags + extra_tags or all_tags: + try: # this may fail for -debug* packages + self.__read_data(i, data) + except: pass + return self + + def __read_lead(self): + data = self.__file.read(self.LEAD_SIZE) + leadmgc, = struct.unpack('!I', data[:4]) + if leadmgc != self.LEAD_MAGIC: + raise RpmError(self.__path, 'invalid lead magic \'%s\'' % leadmgc) + sigtype, = struct.unpack('!h', data[78:80]) + if sigtype != self.HEADERSIG_TYPE: + raise RpmError(self.__path, 'invalid header signature \'%s\'' % sigtype) + + def __read_data(self, entry, data): + off = entry.offset + if entry.type == 2: + entry.data = struct.unpack('!%dc' % entry.count, data[off:off + 1 * entry.count]) + if entry.type == 3: + entry.data = struct.unpack('!%dh' % entry.count, data[off:off + 2 * entry.count]) + elif entry.type == 4: + entry.data = struct.unpack('!%di' % entry.count, data[off:off + 4 * entry.count]) + elif entry.type == 6: + entry.data = unpack_string(data[off:]) + elif entry.type == 7: + entry.data = data[off:off + entry.count] + elif entry.type == 8 or entry.type == 9: + cnt = entry.count + entry.data = [] + while cnt > 0: + cnt -= 1 + s = unpack_string(data[off:]) + # also skip '\0' + off += len(s) + 1 + entry.data.append(s) + if entry.type == 8: + return + lang = os.getenv('LANGUAGE') or os.getenv('LC_ALL') \ + or os.getenv('LC_MESSAGES') or os.getenv('LANG') + if lang is None: + entry.data = entry.data[0] + return + # get private i18n table + table = self.header.gettag(100) + # just care about the country code + lang = lang.split('_', 1)[0] + cnt = 0 + for i in table.data: + if cnt > len(entry.data) - 1: + break + if i == lang: + entry.data = entry.data[cnt] + return + cnt += 1 + entry.data = entry.data[0] + else: + raise RpmHeaderError(self.__path, 'unsupported tag type \'%d\' (tag: \'%s\'' % (entry.type, entry.tag)) + + def __reqprov(self, tag, flags, version): + pnames = self.header.gettag(tag) + if not pnames: + return [] + pnames = pnames.data + pflags = self.header.gettag(flags).data + pvers = self.header.gettag(version).data + if not (pnames and pflags and pvers): + raise RpmError(self.__path, 'cannot get provides/requires, tags are missing') + res = [] + for name, flags, ver in zip(pnames, pflags, pvers): + # RPMSENSE_SENSEMASK = 15 (see rpmlib.h) but ignore RPMSENSE_SERIAL (= 1 << 0) therefore use 14 + if flags & 14: + name += ' ' + if flags & self.GREATER: + name += '>' + elif flags & self.LESS: + name += '<' + if flags & self.EQUAL: + name += '=' + name += ' %s' % ver + res.append(name) + return res + + def vercmp(self, rpmq): + res = RpmQuery.rpmvercmp(str(self.epoch()), str(rpmq.epoch())) + if res != 0: + return res + res = RpmQuery.rpmvercmp(self.version(), rpmq.version()) + if res != 0: + return res + res = RpmQuery.rpmvercmp(self.release(), rpmq.release()) + return res + + # XXX: create dict for the tag => number mapping?! + def name(self): + return self.header.gettag(1000).data + + def version(self): + return self.header.gettag(1001).data + + def release(self): + return self.header.gettag(1002).data + + def epoch(self): + epoch = self.header.gettag(1003) + if epoch is None: + return 0 + return epoch.data[0] + + def arch(self): + return self.header.gettag(1022).data + + def summary(self): + return self.header.gettag(1004).data + + def description(self): + return self.header.gettag(1005).data + + def url(self): + entry = self.header.gettag(1020) + if entry is None: + return None + return entry.data + + def path(self): + return self.__path + + def provides(self): + return self.__reqprov(1047, 1112, 1113) + + def requires(self): + return self.__reqprov(1049, 1048, 1050) + + def conflicts(self): + return self.__reqprov(1054, 1053, 1055) + + def obsoletes(self): + return self.__reqprov(1090, 1114, 1115) + + def is_src(self): + # SOURCERPM = 1044 + return self.gettag(1044) is None + + def is_nosrc(self): + # NOSOURCE = 1051, NOPATCH = 1052 + return self.is_src() and \ + (self.gettag(1051) is not None or self.gettag(1052) is not None) + + def gettag(self, num): + return self.header.gettag(num) + + def canonname(self): + if self.is_nosrc(): + arch = 'nosrc' + elif self.is_src(): + arch = 'src' + else: + arch = self.arch() + return RpmQuery.filename(self.name(), None, self.version(), self.release(), arch) + + @staticmethod + def query(filename): + f = open(filename, 'rb') + rpmq = RpmQuery(f) + rpmq.read() + f.close() + return rpmq + + @staticmethod + def queryhdrmd5(filename): + f = open(filename, 'rb') + rpmq = RpmQuery(f) + rpmq.read(1004, querysig=True) + f.close() + entry = rpmq.gettag(1004) + if entry is None: + return None + return ''.join([ "%02x" % x for x in struct.unpack('16B', entry.data) ]) + + @staticmethod + def rpmvercmp(ver1, ver2): + """ + implementation of RPM's version comparison algorithm + (as described in lib/rpmvercmp.c) + """ + if ver1 == ver2: + return 0 + res = 0 + while res == 0: + # remove all leading non alphanumeric or tilde chars + ver1 = re.sub('^[^a-zA-Z0-9~]*', '', ver1) + ver2 = re.sub('^[^a-zA-Z0-9~]*', '', ver2) + if ver1.startswith('~') or ver2.startswith('~'): + if not ver1.startswith('~'): + return 1 + elif not ver2.startswith('~'): + return -1 + ver1 = ver1[1:] + ver2 = ver2[1:] + continue + + if not (len(ver1) and len(ver2)): + break + + # check if we have a digits segment + mo1 = re.match('(\d+)', ver1) + mo2 = re.match('(\d+)', ver2) + numeric = True + if mo1 is None: + mo1 = re.match('([a-zA-Z]+)', ver1) + mo2 = re.match('([a-zA-Z]+)', ver2) + numeric = False + # check for different types: alpha and numeric + if mo2 is None: + if numeric: + return 1 + return -1 + seg1 = mo1.group(0) + ver1 = ver1[mo1.end(0):] + seg2 = mo2.group(1) + ver2 = ver2[mo2.end(1):] + if numeric: + # remove leading zeros + seg1 = re.sub('^0+', '', seg1) + seg2 = re.sub('^0+', '', seg2) + # longer digit segment wins - if both have the same length + # a simple ascii compare decides + res = len(seg1) - len(seg2) or cmp(seg1, seg2) + else: + res = cmp(seg1, seg2) + if res > 0: + return 1 + elif res < 0: + return -1 + return cmp(ver1, ver2) + + @staticmethod + def filename(name, epoch, version, release, arch): + return '%s-%s-%s.%s.rpm' % (name, version, release, arch) + +def unpack_string(data): + """unpack a '\\0' terminated string from data""" + val = '' + for c in data: + c, = struct.unpack('!c', c) + if c == '\0': + break + else: + val += c + return val + +if __name__ == '__main__': + import sys + try: + rpmq = RpmQuery.query(sys.argv[1]) + except RpmError as e: + print(e.msg) + sys.exit(2) + print(rpmq.name(), rpmq.version(), rpmq.release(), rpmq.arch(), rpmq.url()) + print(rpmq.summary()) + print(rpmq.description()) + print('##########') + print('\n'.join(rpmq.provides())) + print('##########') + print('\n'.join(rpmq.requires())) + print('##########') + print(RpmQuery.queryhdrmd5(sys.argv[1])) diff --git a/osc/util/safewriter.py b/osc/util/safewriter.py new file mode 100644 index 0000000..079e3d8 --- /dev/null +++ b/osc/util/safewriter.py @@ -0,0 +1,29 @@ +# be careful when debugging this code: +# don't add print statements when setting sys.stdout = SafeWriter(sys.stdout)... +class SafeWriter: + """ + Safely write an (unicode) str. In case of an "UnicodeEncodeError" the + the str is encoded with the "encoding" encoding. + All getattr, setattr calls are passed through to the "writer" instance. + """ + def __init__(self, writer, encoding='unicode_escape'): + self.__dict__['writer'] = writer + self.__dict__['encoding'] = encoding + + def __get_writer(self): + return self.__dict__['writer'] + + def __get_encoding(self): + return self.__dict__['encoding'] + + def write(self, s): + try: + self.__get_writer().write(s) + except UnicodeEncodeError as e: + self.__get_writer().write(s.encode(self.__get_encoding())) + + def __getattr__(self, name): + return getattr(self.__get_writer(), name) + + def __setattr__(self, name, value): + setattr(self.__get_writer(), name, value) diff --git a/osc_expand_link.pl b/osc_expand_link.pl new file mode 100755 index 0000000..7a46c07 --- /dev/null +++ b/osc_expand_link.pl @@ -0,0 +1,491 @@ +#! /usr/bin/perl -w +# +# osc_expand_link.pl -- a tool to help osc build packages where an _link exists. +# (C) 2006 jw@suse.de, distribute under GPL v2. +# +# 2006-12-12, jw +# 2006-12-15, jw, v0.2 -- {files}{error} gets printed if present. +# 2008-03-25, jw, v0.3 -- go via api using iChains and ~/.oscrc +# 2008-03-26, jw, v0.4 -- added linked file retrieval and usage. +# 2009-10-21, jw, added obsolete warning, in favour of osc co -e + +use Data::Dumper; +use LWP::UserAgent; +use HTTP::Status; +use Digest::MD5; + +my $version = '0.4'; +my $verbose = 1; + +print "This $0 is obsolete. Please use instead: osc co -e\n"; +sleep 5; + +# curl buildservice:5352/source/home:jnweiger/vim +# curl 'buildservice:5352/source/home:jnweiger/vim?rev=d90bfab4301f758e0d82cf09aa263d37' +# curl 'buildservice:5352/source/home:jnweiger/vim/vim.spec?rev=d90bfab4301f758e0d82cf09aa263d37' + +my $cfg = { + apiurl => slurp_file(".osc/_apiurl", 1), + package => slurp_file(".osc/_package", 1), + project => slurp_file(".osc/_project", 1), + files => xml_slurp_file(".osc/_files", { container => 'directory', attr => 'merge' }), + link => xml_slurp_file(".osc/_link", { container => 'link', attr => 'merge' }), +}; + +{ + package CredUserAgent; + @ISA = qw(LWP::UserAgent); + + sub new + { + my $self = LWP::UserAgent::new(@_); + $self->agent("osc_expand_link.pl/$version"); + $self; + } + sub get_basic_credentials + { + my ($self, $realm, $uri) = @_; + my $netloc = $uri->host_port; + + unless ($self->{auth}) + { + print STDERR "Auth for $realm at $netloc\n"; + unless (open IN, "<", "$ENV{HOME}/.oscrc") + { + print STDERR "$ENV{HOME}/.oscrc: $!\n"; + return (undef, undef); + } + while (defined (my $line = <IN>)) + { + chomp $line; + $self->{auth}{pass} = $1 if $line =~ m{^pass\s*=\s*(\S+)}; + $self->{auth}{user} = $1 if $line =~ m{^user\s*=\s*(\S+)}; + } + close IN; + print STDERR "~/.oscrc: user=$self->{auth}{user}\n"; + } + return ($self->{auth}{user},$self->{auth}{pass}); + } +} + +my $ua = CredUserAgent->new (keep_alive => 1); + +sub cred_get +{ + my ($url) = @_; + my $r = $ua->get($url); + die "$url: " . $r->status_line . "\n" unless $r->is_success; + return $r->content; +} + +sub cred_getstore +{ + my ($url, $file) = @_; + my $r = $ua->get($url, ':content_file' => $file); + die "$url: " . $r->status_line . "\n" unless $r->is_success; + $r->code; +} + +$cfg->{apiurl} ||= 'https://api.opensuse.org'; +$cfg->{project} ||= '<Project>'; +$cfg->{package} ||= '<Package>'; + +chomp $cfg->{apiurl}; +chomp $cfg->{project}; +chomp $cfg->{package}; + +my $source = "$cfg->{apiurl}/source"; +my $url = "$source/$cfg->{project}/$cfg->{package}"; + +if (my $url = $ARGV[0]) + { + + die qq{osc_expand_link $version; + +Usage: + + osc co $cfg->{project} $cfg->{package} + cd $cfg->{project}/$cfg->{package} + $0 + +to resolve a _link. + +or + + $0 $cfg->{apiurl}/source/$cfg->{project}/$cfg->{package} + +to review internal buildservice data. + +or + $0 $cfg->{apiurl}/source/$cfg->{project}/$cfg->{package}/linked/\\*.spec + + cd $cfg->{project}/$cfg->{package} + $0 linked \\*.spec + +to retrieve the original specfile behind a link. + +} if $url =~ m{^-}; + + $url = "$url/$ARGV[1]" if $url eq 'linked' and $ARGV[1]; + if ($url =~ m{^(.*/)?linked/(.*)$}) + { + $url = (defined $1) ? $1 : "$cfg->{project}/$cfg->{package}"; + my $file = $2; + $url = "$source/$url" if $cfg->{apiurl} and $url !~ m{://}; + print STDERR "$url\n"; + my $dir = xml_parse(cred_get($url), 'merge'); + my $li = $dir->{directory}{linkinfo} || die "no linkinfo in $url\n"; + $url = "$source/$li->{project}/$li->{package}"; + mkdir("linked"); + + if ($file =~ m{\*}) + { + my $dir = xml_parse(cred_get($url), 'merge'); + $dir = $dir->{directory} if $dir->{directory}; + my @list = sort map { $_->{name} } @{$dir->{entry}}; + my $file_re = "\Q$file\E"; $file_re =~ s{\\\*}{\.\*}g; + my @match = grep { $_ =~ m{^$file_re$} } @list; + die "pattern $file not found in\n @list\n" unless @match; + $file = $match[0]; + } + $url .= "/$file"; + + print STDERR "$url -> linked/$file\n"; + my $r = cred_getstore($url, "linked/$file"); + print STDERR " Error: $r\n" if $r != RC_OK; + exit 0; + } + + $url = "$cfg->{project}/$cfg->{package}/$url" unless $url =~ m{/}; + $url = "$source/$url" if $cfg->{apiurl} and $url !~ m{://}; + print cred_get($url); + exit 0; + } + +warn "$cfg->{project}/$cfg->{package} error: $cfg->{files}{error}\n" if $cfg->{files}{error}; +die "$cfg->{project}/$cfg->{package} has no _link\n" unless $cfg->{link}; +die "$cfg->{project}/$cfg->{package} has no xsrcmd5\n" unless $cfg->{files}{xsrcmd5}; + +print STDERR "expanding link to $cfg->{link}{project}/$cfg->{link}{package}\n"; +if (my $p = $cfg->{link}{patches}) + { + $p = [ $p ] if ref $p ne 'ARRAY'; + my @p = map { "$_->{apply}{name}" } @$p; + print STDERR "applied patches: " . join(',', @p) . "\n"; + } + +my $dir = xml_parse(cred_get("$url?rev=$cfg->{files}{xsrcmd5}"), 'merge'); +$dir = $dir->{directory} if defined $dir->{directory}; +$dir->{entry} = [ $dir->{entry} ] if ref $dir->{entry} ne 'ARRAY'; +for my $file (@{$dir->{entry}}) + { + if (-f $file->{name}) + { + ## check the md5sum of the existing file and be happy. + $md5 = Digest::MD5->new; + open IN, "<", $file->{name} or die "md5sum($file->{name} failed: $!"; + $md5->addfile(*IN); + close IN; + if ($md5->hexdigest eq $file->{md5}) + { + print STDERR " - $file->{name} (md5 unchanged)\n"; + } + else + { + print STDERR "Modified: $file->{name}, please commit changes!\n"; + } + next; + } + print STDERR " get $file->{name}"; + # fixme: xsrcmd5 is obsolete. + # use <linkinfo project="openSUSE:Factory" package="avrdude" xsrcmd5="a39c2bd14c3ad5dbb82edd7909fcdfc4"> + my $response = cred_getstore("$url/$file->{name}?rev=$cfg->{files}{xsrcmd5}", $file->{name}); + print STDERR ($response == RC_OK) ? "\n" : " Error:$response\n"; + } +exit 0; +########################################################################## + +sub slurp_file +{ + my ($path, $silent) = @_; + open IN, "<", $path or ($silent ? return undef : die "slurp_file($path) failed: $!\n"); + my $body = join '', <IN>; + close IN; + return $body; +} + + +################################################################# +## xml parser imported from w3dcm.pl and somewhat expanded. +## 2006-12-15, jw +## +## xml_parse assumes correct container closing. +## Any </...> tag would closes an open <foo>. +## Thus xml_parse is not suitable for HTML. +## +sub xml_parse +{ + my ($text, $attr) = @_; + my %xml; + my @stack = (); + my $t = \%xml; + +#print "xml_parse: '$text'\n"; + my @tags = find_tags($text); + for my $i (0 .. $#tags) + { + my $tag = substr $text, $tags[$i]->{offset}, $tags[$i]->{tag_len}; + my $cdata = ''; + my $s = $tags[$i]->{offset} + $tags[$i]->{tag_len}; + if (defined $tags[$i+1]) + { + my $l = $tags[$i+1]->{offset} - $s; + $cdata = substr $text, $s, $l; + } + else + { + $cdata = substr $text, $s; + } + +# print "tag=$tag\n"; + my $name = $1 if $tag =~ s{<([\?/]?[\w:-]+)\s*}{}; + $tag =~ s{>\s*$}{}; + my $nest = ($tag =~ s{[\?/]$}{}) ? 0 : 1; + my $close = ($name =~ s{^/}{}) ? 1 : 0; +# print "name=$name, attr='$tag', $close, $nest, '$cdata'\n"; + + my $x = {}; + $x->{-cdata} .= $cdata if $nest; + xml_add_attr($x, $tag, $attr) unless $tag eq ''; + + if (!$close) + { + delete $t->{-cdata} if $t->{-cdata} and $t->{-cdata} =~ m{^\s*$}; + unless ($t->{$name}) + { + $t->{$name} = $x; + } + else + { + $t->{$name} = [ $t->{$name} ] unless ref $t->{$name} eq 'ARRAY'; + push @{$t->{$name}}, $x; + } + } + + + if ($close) + { + $t = pop @stack; + } + elsif ($nest) + { + push @stack, $t; + $t = $x; + } + } + + print "stack=", Data::Dumper::Dumper(\@stack) if $verbose > 2; + scalar_cdata($t); + return $t; +} + +## +## reads a file formatted by xml_make, and returns a hash. +## The toplevel container is removed from that hash, if specified. +## A wildcard '*' can be specified to remove any toplevel container. +## Otherwise the name of the container must match precisely. +## +sub xml_slurp_file +{ + my ($file, $opt) = @_; + unless (open IN, "<$file") + { + return undef unless $opt->{die}; + die "xml_slurp($opt->{container}): cannot read $file: $!\n"; + } + + my $xml = join '', <IN>; close IN; + $xml = xml_parse($xml, $opt->{attr}); + if (my $container = $opt->{container}) + { + die "xml_slurp($file, '$container') malformed file, should have only one toplevel node.\n" + unless scalar keys %$xml == 1; + $container = (keys %$xml)[0] if $container eq '' or $container eq '*'; + die "xml_slurp($file, '$container') toplevel tag missing or wrong.\n" unless $xml->{$container}; + $xml = $xml->{$container}; + } + return $xml; +} + +sub xml_escape +{ + my ($text) = @_; + + ## XML::Simple does just that: + $text =~ s{&}{&}g; + $text =~ s{<}{<}g; + $text =~ s{>}{>}g; + $text =~ s{"}{"}g; + return $text; +} + +sub xml_unescape +{ + my ($text) = @_; + + ## XX: Fimxe: we should handle some more escapes here... + ## and better do it in a single pass. + $text =~ s{&#([\d]{3});}{chr $1}eg; + $text =~ s{<}{<}g; + $text =~ s{>}{>}g; + $text =~ s{"}{"}g; + $text =~ s{&}{&}g; + + return $text; +} + +## +## find all hashes, that contain exactly one key named '-cdata' +## and replace these hashes with the value of that key. +## These values are scalar when created by xml_parse(), hence the name. +## +sub scalar_cdata +{ + my ($hash) = @_; + my $selftag = '.scalar_cdata_running'; + + return unless ref $hash eq 'HASH'; + return if $hash->{$selftag}; + $hash->{$selftag} = 1; + + for my $key (keys %$hash) + { + my $val = $hash->{$key}; + if (ref $val eq 'ARRAY') + { + for my $i (0..$#$val) + { + scalar_cdata($hash->{$key}[$i]); + } + } + elsif (ref $val eq 'HASH') + { + my @k = keys %$val; + if (scalar(@k) == 1 && ($k[0] eq '-cdata')) + { + $hash->{$key} = $hash->{$key}{-cdata}; + } + else + { + delete $hash->{$key}{-cdata} if exists $val->{-cdata} && $val->{-cdata} =~ m{^\s*$}; + scalar_cdata($hash->{$key}); + } + } + } + delete $hash->{$selftag}; +} + +## +## find_tags -- a brute force tag finder. +## This code is robust enough to parse the weirdest HTML. +## An Array containing hashes of { offset, name, tag_len } is returned. +## CDATA is skipped, but can be determined from gaps between tags. +## The name parser may chop names, so XML-style tag names are +## unreliable. +## +sub find_tags +{ + my ($text) = @_; + my $last = ''; + my @tags; + my $inquotes = 0; + my $incomment = 0; + + while ($text =~ m{(<!--|-->|"|>|<)(/?\w*)}g) + { + my ($offset, $what, $name) = (length $`, $1, $2); + + if ($inquotes) + { + $inquotes = 0 if $what eq '"'; + next; + } + + if ($incomment) + { + $incomment = 0 if $what eq '-->'; + next; + } + + if ($what eq '"') + { + $inquotes = 1; + next; + } + + if ($what eq '<!--') + { + $incomment = 1; + next; + } + + next if $what eq $last; # opening and closing angular brackets are polar. + + if ($what eq '>' and scalar @tags) + { + $tags[$#tags]{tag_len} = 1 + $offset - $tags[$#tags]{offset}; + } + + if ($what eq '<') + { + push @tags, {name => $name, offset => $offset }; + } + + $last = $what; + } + return @tags; +} + +## +## how = undef: defaults to '-attr plain' +## how = '-attr plain': add the attributes as one scalar value to hash-element -attr +## how = '-attr hash': add the attributes as a hash-ref to hash-element -attr +## how = 'merge': add the attributes as direct hash elements. (This is irreversible) +## +## attributes are either space-separated, or delimited with '' or "". +sub xml_add_attr +{ + my ($hash, $text, $how) = @_; + $how = 'plain' unless $how; + my $tag = '-attr'; $tag = $1 if $how =~ s{^\s*([\w_:-]+)\s+(.*)$}{$2}; + $how = lc $how; + + return $hash->{$tag} = $text if $how eq 'plain'; + + if ($how eq 'hash') + { + $hash = $hash->{$tag} = {}; + $how = 'merge'; + ## fallthrough + } + if ($how eq 'merge') + { + while ($text =~ m{([\w_:-]+)\s*=("[^"]*"|'[^']'|\S*)\s*}g) + { + my ($key, $val) = ($1, $2); + $val =~ s{^"(.*)"$}{$1} unless $val =~ s{^'(.*)'$}{$1}; + if (defined($hash->{$key})) + { + ## redefinition. promote to array and push. + $hash->{$key} = [ $hash->{$key} ] unless ref $hash->{$key}; + push @{$hash->{$key}}, $val; + } + else + { + $hash->{$key} = $val; + } + } + return $hash; + } + die "xml_expand_attr: unknown method '$how'\n"; +} diff --git a/osc_hotshot.py b/osc_hotshot.py new file mode 100755 index 0000000..54ac40f --- /dev/null +++ b/osc_hotshot.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +import hotshot, hotshot.stats +import tempfile +import os, sys + +from osc import commandline + + +if __name__ == '__main__': + + (fd, filename) = tempfile.mkstemp(prefix = 'osc_profiledata_', dir = '/dev/shm') + f = os.fdopen(fd) + + try: + + prof = hotshot.Profile(filename) + + prof.runcall(commandline.main) + print 'run complete. analyzing.' + prof.close() + + stats = hotshot.stats.load(filename) + stats.strip_dirs() + stats.sort_stats('time', 'calls') + stats.print_stats(20) + + del stats + + finally: + f.close() + os.unlink(filename) diff --git a/run_bandit.sh b/run_bandit.sh new file mode 100755 index 0000000..a743dee --- /dev/null +++ b/run_bandit.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# you can pass as argument "csv","json" or "txt" (default) +if [ "$1" != "" ];then + OUTPUT=$1 +else + OUTPUT="txt" +fi + +# check if bandit is installed +command -v bandit >/dev/null 2>&1 || { echo "bandit should be installed. get the package from https://build.opensuse.org/package/show/home:vpereirabr/python-bandit. Aborting." >&2; exit 1; } + +bandit -c /usr/etc/bandit/bandit.yaml -r osc -f $OUTPUT + +if [ "$OUTPUT" == "csv" ];then + cat bandit_results.csv + rm -f bandit_results.csv +fi diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..1aaca6b --- /dev/null +++ b/setup.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python + +from distutils.core import setup +import distutils.command.build +import distutils.command.install_data +import os.path +import osc.core +import sys +from osc import commandline +from osc import babysitter +# optional support for py2exe +try: + import py2exe + HAVE_PY2EXE = True +except: + HAVE_PY2EXE = False + + +class build_osc(distutils.command.build.build, object): + """ + Custom build command which generates man page. + """ + + def build_man_page(self): + """ + """ + import gzip + man_path = os.path.join('build', 'osc.1.gz') + distutils.log.info('generating %s' % man_path) + outfile = gzip.open(man_path, 'w') + osccli = commandline.Osc(stdout=outfile) + # FIXME: we cannot call the main method because osc expects an ~/.oscrc + # file (this would break builds in environments like the obs) + #osccli.main(argv = ['osc','man']) + osccli.optparser = osccli.get_optparser() + osccli.do_man(None) + outfile.close() + + def run(self): + super(build_osc, self).run() + self.build_man_page() + + +# Support for documentation (sphinx) +class build_docs(distutils.command.build.Command): + description = 'builds documentation using sphinx' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + # metadata contains information supplied in setup() + metadata = self.distribution.metadata + # package_dir may be None, in that case use the current directory. + src_dir = (self.distribution.package_dir or {'': ''})[''] + src_dir = os.path.join(os.getcwd(), src_dir) + import sphinx + sphinx.main(['runme', + '-D', 'version=%s' % metadata.get_version(), + os.path.join('docs',), os.path.join('build', 'docs')]) + + +addparams = {} +if HAVE_PY2EXE: + addparams['console'] = [{'script': 'osc-wrapper.py', 'dest_base': 'osc', 'icon_resources': [(1, 'osc.ico')]}] + addparams['zipfile'] = 'shared.lib' + addparams['options'] = {'py2exe': {'optimize': 0, 'compressed': True, 'packages': ['xml.etree', 'StringIO', 'gzip']}} + +data_files = [] +if sys.platform[:3] != 'win': + data_files.append((os.path.join('share', 'man', 'man1'), [os.path.join('build', 'osc.1.gz')])) + +setup(name='osc', + version = osc.core.__version__, + description = 'openSUSE commander', + long_description = 'Command-line client for the openSUSE Build Service, which allows to access repositories in the openSUSE Build Service in similar way as Subversion repositories.', + author = 'openSUSE project', + author_email = 'opensuse-buildservice@opensuse.org', + license = 'GPL', + platforms = ['Linux', 'Mac OSX', 'Windows XP/2000/NT', 'Windows 95/98/ME', 'FreeBSD'], + keywords = ['openSUSE', 'SUSE', 'RPM', 'build', 'buildservice'], + url = 'http://en.opensuse.org/openSUSE:OSC', + download_url = 'https://github.com/openSUSE/osc', + packages = ['osc', 'osc.util'], + scripts = ['osc_hotshot.py', 'osc-wrapper.py'], + data_files = data_files, + + # Override certain command classes with our own ones + cmdclass = { + 'build': build_osc, + 'build_docs' : build_docs, + }, + **addparams + ) diff --git a/tests/addfile_fixtures/oscrc b/tests/addfile_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/addfile_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/addfile_fixtures/osctest/.osc/_apiurl b/tests/addfile_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/addfile_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/addfile_fixtures/osctest/.osc/_packages b/tests/addfile_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/addfile_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/addfile_fixtures/osctest/.osc/_project b/tests/addfile_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/addfile_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_apiurl b/tests/addfile_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_files b/tests/addfile_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_osclib_version b/tests/addfile_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_package b/tests/addfile_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_project b/tests/addfile_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/addfile_fixtures/osctest/simple/.osc/_to_be_deleted b/tests/addfile_fixtures/osctest/simple/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/addfile_fixtures/osctest/simple/.osc/foo b/tests/addfile_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/addfile_fixtures/osctest/simple/.osc/merge b/tests/addfile_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/addfile_fixtures/osctest/simple/.osc/nochange b/tests/addfile_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/addfile_fixtures/osctest/simple/merge b/tests/addfile_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/addfile_fixtures/osctest/simple/nochange b/tests/addfile_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/addfile_fixtures/osctest/simple/toadd1 b/tests/addfile_fixtures/osctest/simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/addfile_fixtures/osctest/simple/toadd2 b/tests/addfile_fixtures/osctest/simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/addfile_fixtures/osctest/simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/commit_fixtures/oscrc b/tests/commit_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/commit_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/commit_fixtures/osctest/.osc/_apiurl b/tests/commit_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/.osc/_packages b/tests/commit_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/commit_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/commit_fixtures/osctest/.osc/_project b/tests/commit_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/commit_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/commit_fixtures/osctest/add/.osc/_apiurl b/tests/commit_fixtures/osctest/add/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/add/.osc/_files b/tests/commit_fixtures/osctest/add/.osc/_files new file mode 100644 index 0000000..6c3d53a --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_files @@ -0,0 +1,5 @@ +<directory name="add" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/osctest/add/.osc/_meta b/tests/commit_fixtures/osctest/add/.osc/_meta new file mode 100644 index 0000000..1b2b022 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_meta @@ -0,0 +1,4 @@ +<package name="add" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/add/.osc/_osclib_version b/tests/commit_fixtures/osctest/add/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/add/.osc/_package b/tests/commit_fixtures/osctest/add/.osc/_package new file mode 100644 index 0000000..76d4bb8 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_package @@ -0,0 +1 @@ +add diff --git a/tests/commit_fixtures/osctest/add/.osc/_project b/tests/commit_fixtures/osctest/add/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/add/.osc/_to_be_added b/tests/commit_fixtures/osctest/add/.osc/_to_be_added new file mode 100644 index 0000000..76d4bb8 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/_to_be_added @@ -0,0 +1 @@ +add diff --git a/tests/commit_fixtures/osctest/add/.osc/foo b/tests/commit_fixtures/osctest/add/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/add/.osc/merge b/tests/commit_fixtures/osctest/add/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/add/.osc/nochange b/tests/commit_fixtures/osctest/add/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/add/add b/tests/commit_fixtures/osctest/add/add new file mode 100644 index 0000000..b242c36 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/add @@ -0,0 +1 @@ +added file diff --git a/tests/commit_fixtures/osctest/add/exists b/tests/commit_fixtures/osctest/add/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/exists diff --git a/tests/commit_fixtures/osctest/add/foo b/tests/commit_fixtures/osctest/add/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/add/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/add/merge b/tests/commit_fixtures/osctest/add/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/add/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/add/nochange b/tests/commit_fixtures/osctest/add/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/add/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_apiurl b/tests/commit_fixtures/osctest/added_missing/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_files b/tests/commit_fixtures/osctest/added_missing/.osc/_files new file mode 100644 index 0000000..d474ef3 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_files @@ -0,0 +1,3 @@ +<directory name="added_missing" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" skipped="True" /> +</directory> diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_meta b/tests/commit_fixtures/osctest/added_missing/.osc/_meta new file mode 100644 index 0000000..b0a2f47 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_meta @@ -0,0 +1,4 @@ +<package name="added_missing" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_osclib_version b/tests/commit_fixtures/osctest/added_missing/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_package b/tests/commit_fixtures/osctest/added_missing/.osc/_package new file mode 100644 index 0000000..db0af96 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_package @@ -0,0 +1 @@ +added_missing diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_project b/tests/commit_fixtures/osctest/added_missing/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/added_missing/.osc/_to_be_added b/tests/commit_fixtures/osctest/added_missing/.osc/_to_be_added new file mode 100644 index 0000000..b03d55d --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/.osc/_to_be_added @@ -0,0 +1,2 @@ +add +bar diff --git a/tests/commit_fixtures/osctest/added_missing/bar b/tests/commit_fixtures/osctest/added_missing/bar new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/commit_fixtures/osctest/added_missing/bar @@ -0,0 +1 @@ +foobar diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_apiurl b/tests/commit_fixtures/osctest/allstates/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_files b/tests/commit_fixtures/osctest/allstates/.osc/_files new file mode 100644 index 0000000..02a576b --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_files @@ -0,0 +1,8 @@ +<directory name="allstates" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="676513fde5797c3785164942c97dfec1" mtime="1283506309" name="missing" size="8" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="100" skipped="true" /> +</directory> diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_meta b/tests/commit_fixtures/osctest/allstates/.osc/_meta new file mode 100644 index 0000000..510875e --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_meta @@ -0,0 +1,4 @@ +<package name="allstates" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_osclib_version b/tests/commit_fixtures/osctest/allstates/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_package b/tests/commit_fixtures/osctest/allstates/.osc/_package new file mode 100644 index 0000000..9d1ec82 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_package @@ -0,0 +1 @@ +allstates diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_project b/tests/commit_fixtures/osctest/allstates/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_to_be_added b/tests/commit_fixtures/osctest/allstates/.osc/_to_be_added new file mode 100644 index 0000000..cd0a2fe --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_to_be_added @@ -0,0 +1,2 @@ +add +missing diff --git a/tests/commit_fixtures/osctest/allstates/.osc/_to_be_deleted b/tests/commit_fixtures/osctest/allstates/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/commit_fixtures/osctest/allstates/.osc/foo b/tests/commit_fixtures/osctest/allstates/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/allstates/.osc/merge b/tests/commit_fixtures/osctest/allstates/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/allstates/.osc/missing b/tests/commit_fixtures/osctest/allstates/.osc/missing new file mode 100644 index 0000000..33e45d5 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/missing @@ -0,0 +1 @@ +missing diff --git a/tests/commit_fixtures/osctest/allstates/.osc/nochange b/tests/commit_fixtures/osctest/allstates/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/allstates/.osc/test b/tests/commit_fixtures/osctest/allstates/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/commit_fixtures/osctest/allstates/add b/tests/commit_fixtures/osctest/allstates/add new file mode 100644 index 0000000..b242c36 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/add @@ -0,0 +1 @@ +added file diff --git a/tests/commit_fixtures/osctest/allstates/exists b/tests/commit_fixtures/osctest/allstates/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/exists diff --git a/tests/commit_fixtures/osctest/allstates/missing b/tests/commit_fixtures/osctest/allstates/missing new file mode 100644 index 0000000..feae347 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/missing @@ -0,0 +1 @@ +replaced diff --git a/tests/commit_fixtures/osctest/allstates/nochange b/tests/commit_fixtures/osctest/allstates/nochange new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/nochange @@ -0,0 +1 @@ +This file did change. diff --git a/tests/commit_fixtures/osctest/allstates/test b/tests/commit_fixtures/osctest/allstates/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/commit_fixtures/osctest/allstates/test @@ -0,0 +1 @@ +test diff --git a/tests/commit_fixtures/osctest/branch/.osc/_apiurl b/tests/commit_fixtures/osctest/branch/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/branch/.osc/_files b/tests/commit_fixtures/osctest/branch/.osc/_files new file mode 100644 index 0000000..8cbc407 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_files @@ -0,0 +1,4 @@ +<directory name="unix" rev="9afa23b484de05e28364b18de7bb1432" srcmd5="9afa23b484de05e28364b18de7bb1432"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="cd21541fe2442d3d324a6d6103752913" package="unique" project="btest" srcmd5="b63634ab40861fdb8b44e5f4f459c621" /> + <entry md5="75d884cf1d235180faec5acb63063972" mtime="1283525196" name="simple" size="21" /> +</directory> diff --git a/tests/commit_fixtures/osctest/branch/.osc/_meta b/tests/commit_fixtures/osctest/branch/.osc/_meta new file mode 100644 index 0000000..6adc647 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_meta @@ -0,0 +1,5 @@ +<package name="branch" project="osctest"> + <title>Title example</title> + <description>Description example</description> + <person role="maintainer" userid="Admin"/> +</package> diff --git a/tests/commit_fixtures/osctest/branch/.osc/_osclib_version b/tests/commit_fixtures/osctest/branch/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/branch/.osc/_package b/tests/commit_fixtures/osctest/branch/.osc/_package new file mode 100644 index 0000000..80858c1 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_package @@ -0,0 +1 @@ +branch diff --git a/tests/commit_fixtures/osctest/branch/.osc/_project b/tests/commit_fixtures/osctest/branch/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/commit_fixtures/osctest/branch/.osc/simple b/tests/commit_fixtures/osctest/branch/.osc/simple new file mode 100644 index 0000000..f425d9a --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/.osc/simple @@ -0,0 +1 @@ +imple modified file. diff --git a/tests/commit_fixtures/osctest/branch/cfilesremote b/tests/commit_fixtures/osctest/branch/cfilesremote new file mode 100644 index 0000000..5fe6f29 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/cfilesremote @@ -0,0 +1,5 @@ +<directory name="branch" rev="5" srcmd5="1d4bbfa2655ab3982074226e16e1e5ff" vrev="5"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="1d4bbfa2655ab3982074226e16e1e5ff" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" xsrcmd5="87ea02aede261b0267aabaa97c756e7a" /> + <entry md5="542f96b49b64095104d8a9e9dd313a9c" mtime="1283521153" name="_link" size="130" /> + <entry md5="75da7f7167c22b2b02c6879366d78ad1" mtime="1283525027" name="simple" size="22" /> +</directory> diff --git a/tests/commit_fixtures/osctest/branch/files b/tests/commit_fixtures/osctest/branch/files new file mode 100644 index 0000000..1f37d41 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/files @@ -0,0 +1,4 @@ +<directory name="branch" rev="87ea02aede261b0267aabaa97c756e7a" srcmd5="87ea02aede261b0267aabaa97c756e7a"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="1d4bbfa2655ab3982074226e16e1e5ff" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" /> + <entry md5="75da7f7167c22b2b02c6879366d78ad1" mtime="1283525027" name="simple" size="22" /> +</directory> diff --git a/tests/commit_fixtures/osctest/branch/filesremote b/tests/commit_fixtures/osctest/branch/filesremote new file mode 100644 index 0000000..1bfc91b --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/filesremote @@ -0,0 +1,5 @@ +<directory name="branch" rev="6" srcmd5="cd21541fe2442d3d324a6d6103752913" vrev="6"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="cd21541fe2442d3d324a6d6103752913" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" xsrcmd5="9afa23b484de05e28364b18de7bb1432" /> + <entry md5="542f96b49b64095104d8a9e9dd313a9c" mtime="1283521153" name="_link" size="130" skipped="true" /> + <entry md5="75d884cf1d235180faec5acb63063972" mtime="1283525196" name="simple" size="21" /> +</directory> diff --git a/tests/commit_fixtures/osctest/branch/simple b/tests/commit_fixtures/osctest/branch/simple new file mode 100644 index 0000000..60627a5 --- /dev/null +++ b/tests/commit_fixtures/osctest/branch/simple @@ -0,0 +1 @@ +simple modified file. diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_apiurl b/tests/commit_fixtures/osctest/conflict/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_files b/tests/commit_fixtures/osctest/conflict/.osc/_files new file mode 100644 index 0000000..a67ff42 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_files @@ -0,0 +1,5 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282130148" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282130148" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_in_conflict b/tests/commit_fixtures/osctest/conflict/.osc/_in_conflict new file mode 100644 index 0000000..a00af07 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_in_conflict @@ -0,0 +1 @@ +merge diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_meta b/tests/commit_fixtures/osctest/conflict/.osc/_meta new file mode 100644 index 0000000..6f5b30d --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_meta @@ -0,0 +1,4 @@ +<package name="conflict" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_osclib_version b/tests/commit_fixtures/osctest/conflict/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_package b/tests/commit_fixtures/osctest/conflict/.osc/_package new file mode 100644 index 0000000..783a0ef --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_package @@ -0,0 +1 @@ +conflict
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/conflict/.osc/_project b/tests/commit_fixtures/osctest/conflict/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/conflict/.osc/foo b/tests/commit_fixtures/osctest/conflict/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/conflict/.osc/merge b/tests/commit_fixtures/osctest/conflict/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/conflict/.osc/nochange b/tests/commit_fixtures/osctest/conflict/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/conflict/foo b/tests/commit_fixtures/osctest/conflict/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/conflict/merge b/tests/commit_fixtures/osctest/conflict/merge new file mode 100644 index 0000000..f4ff164 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/merge @@ -0,0 +1,4 @@ +Is it possible +to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/conflict/nochange b/tests/commit_fixtures/osctest/conflict/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/conflict/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/delete/.osc/_apiurl b/tests/commit_fixtures/osctest/delete/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/delete/.osc/_files b/tests/commit_fixtures/osctest/delete/.osc/_files new file mode 100644 index 0000000..b8bf188 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_files @@ -0,0 +1,5 @@ +<directory name="delete" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/osctest/delete/.osc/_meta b/tests/commit_fixtures/osctest/delete/.osc/_meta new file mode 100644 index 0000000..3ff45d2 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_meta @@ -0,0 +1,4 @@ +<package name="delete" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/delete/.osc/_osclib_version b/tests/commit_fixtures/osctest/delete/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/delete/.osc/_package b/tests/commit_fixtures/osctest/delete/.osc/_package new file mode 100644 index 0000000..c8b1b42 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_package @@ -0,0 +1 @@ +delete diff --git a/tests/commit_fixtures/osctest/delete/.osc/_project b/tests/commit_fixtures/osctest/delete/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/delete/.osc/_to_be_deleted b/tests/commit_fixtures/osctest/delete/.osc/_to_be_deleted new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/_to_be_deleted @@ -0,0 +1 @@ +nochange diff --git a/tests/commit_fixtures/osctest/delete/.osc/foo b/tests/commit_fixtures/osctest/delete/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/delete/.osc/merge b/tests/commit_fixtures/osctest/delete/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/delete/.osc/nochange b/tests/commit_fixtures/osctest/delete/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/delete/exists b/tests/commit_fixtures/osctest/delete/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/exists diff --git a/tests/commit_fixtures/osctest/delete/foo b/tests/commit_fixtures/osctest/delete/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/delete/merge b/tests/commit_fixtures/osctest/delete/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/delete/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_apiurl b/tests/commit_fixtures/osctest/multiple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_files b/tests/commit_fixtures/osctest/multiple/.osc/_files new file mode 100644 index 0000000..9df02ca --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_files @@ -0,0 +1,6 @@ +<directory name="multiple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_meta b/tests/commit_fixtures/osctest/multiple/.osc/_meta new file mode 100644 index 0000000..e8fea3d --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_meta @@ -0,0 +1,4 @@ +<package name="multiple" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_osclib_version b/tests/commit_fixtures/osctest/multiple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_package b/tests/commit_fixtures/osctest/multiple/.osc/_package new file mode 100644 index 0000000..5c4139d --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_package @@ -0,0 +1 @@ +multiple diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_project b/tests/commit_fixtures/osctest/multiple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_to_be_added b/tests/commit_fixtures/osctest/multiple/.osc/_to_be_added new file mode 100644 index 0000000..91e4e3d --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_to_be_added @@ -0,0 +1,2 @@ +add +add2 diff --git a/tests/commit_fixtures/osctest/multiple/.osc/_to_be_deleted b/tests/commit_fixtures/osctest/multiple/.osc/_to_be_deleted new file mode 100644 index 0000000..cf978bc --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +foo +merge diff --git a/tests/commit_fixtures/osctest/multiple/.osc/foo b/tests/commit_fixtures/osctest/multiple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/multiple/.osc/merge b/tests/commit_fixtures/osctest/multiple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/multiple/.osc/nochange b/tests/commit_fixtures/osctest/multiple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/multiple/.osc/test b/tests/commit_fixtures/osctest/multiple/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/commit_fixtures/osctest/multiple/add b/tests/commit_fixtures/osctest/multiple/add new file mode 100644 index 0000000..b242c36 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/add @@ -0,0 +1 @@ +added file diff --git a/tests/commit_fixtures/osctest/multiple/add2 b/tests/commit_fixtures/osctest/multiple/add2 new file mode 100644 index 0000000..4755903 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/add2 @@ -0,0 +1 @@ +add2 diff --git a/tests/commit_fixtures/osctest/multiple/exists b/tests/commit_fixtures/osctest/multiple/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/exists diff --git a/tests/commit_fixtures/osctest/multiple/nochange b/tests/commit_fixtures/osctest/multiple/nochange new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/nochange @@ -0,0 +1 @@ +This file did change. diff --git a/tests/commit_fixtures/osctest/multiple/test b/tests/commit_fixtures/osctest/multiple/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/commit_fixtures/osctest/multiple/test @@ -0,0 +1 @@ +test diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_apiurl b/tests/commit_fixtures/osctest/nochanges/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_files b/tests/commit_fixtures/osctest/nochanges/.osc/_files new file mode 100644 index 0000000..fee086c --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_files @@ -0,0 +1,5 @@ +<directory name="nochanges" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" skipped="True" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_meta b/tests/commit_fixtures/osctest/nochanges/.osc/_meta new file mode 100644 index 0000000..c2daf5c --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_meta @@ -0,0 +1,4 @@ +<package name="nochanges" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_osclib_version b/tests/commit_fixtures/osctest/nochanges/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_package b/tests/commit_fixtures/osctest/nochanges/.osc/_package new file mode 100644 index 0000000..40b16a9 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_package @@ -0,0 +1 @@ +nochanges diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/_project b/tests/commit_fixtures/osctest/nochanges/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/merge b/tests/commit_fixtures/osctest/nochanges/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/nochanges/.osc/nochange b/tests/commit_fixtures/osctest/nochanges/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/nochanges/exists b/tests/commit_fixtures/osctest/nochanges/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/exists diff --git a/tests/commit_fixtures/osctest/nochanges/nochange b/tests/commit_fixtures/osctest/nochanges/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/nochanges/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/simple/.osc/_apiurl b/tests/commit_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/commit_fixtures/osctest/simple/.osc/_files b/tests/commit_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/osctest/simple/.osc/_meta b/tests/commit_fixtures/osctest/simple/.osc/_meta new file mode 100644 index 0000000..a40f92d --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_meta @@ -0,0 +1,4 @@ +<package name="simple" project="osctest"> + <title>Title example</title> + <description>Description example</description> +</package> diff --git a/tests/commit_fixtures/osctest/simple/.osc/_osclib_version b/tests/commit_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/commit_fixtures/osctest/simple/.osc/_package b/tests/commit_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/simple/.osc/_project b/tests/commit_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/commit_fixtures/osctest/simple/.osc/foo b/tests/commit_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/simple/.osc/merge b/tests/commit_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/simple/.osc/nochange b/tests/commit_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/commit_fixtures/osctest/simple/exists b/tests/commit_fixtures/osctest/simple/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/exists diff --git a/tests/commit_fixtures/osctest/simple/foo b/tests/commit_fixtures/osctest/simple/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/commit_fixtures/osctest/simple/merge b/tests/commit_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/commit_fixtures/osctest/simple/nochange b/tests/commit_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/commit_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/commit_fixtures/testAddedMissing_cfilesremote b/tests/commit_fixtures/testAddedMissing_cfilesremote new file mode 100644 index 0000000..a9c946e --- /dev/null +++ b/tests/commit_fixtures/testAddedMissing_cfilesremote @@ -0,0 +1,4 @@ +<directory name="added_missing" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="14758f1afd44c09b7992073ccf00b43d" mtime="1292622742" name="bar" size="7" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> +</directory> diff --git a/tests/commit_fixtures/testAddedMissing_filesremote b/tests/commit_fixtures/testAddedMissing_filesremote new file mode 100644 index 0000000..03c7d89 --- /dev/null +++ b/tests/commit_fixtures/testAddedMissing_filesremote @@ -0,0 +1,3 @@ +<directory name="added_missing" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> +</directory> diff --git a/tests/commit_fixtures/testAddedMissing_lfilelist b/tests/commit_fixtures/testAddedMissing_lfilelist new file mode 100644 index 0000000..c846f12 --- /dev/null +++ b/tests/commit_fixtures/testAddedMissing_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="14758f1afd44c09b7992073ccf00b43d" name="bar" /><entry md5="0d62ceea6020d75154078a20d8c9f9ba" name="foo" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testAddedMissing_missingfilelist b/tests/commit_fixtures/testAddedMissing_missingfilelist new file mode 100644 index 0000000..7070186 --- /dev/null +++ b/tests/commit_fixtures/testAddedMissing_missingfilelist @@ -0,0 +1,3 @@ +<directory error="missing" name="added_missing"> + <entry md5="14758f1afd44c09b7992073ccf00b43d" name="bar" /> +</directory> diff --git a/tests/commit_fixtures/testAddfile_cfilesremote b/tests/commit_fixtures/testAddfile_cfilesremote new file mode 100644 index 0000000..47538b0 --- /dev/null +++ b/tests/commit_fixtures/testAddfile_cfilesremote @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" mtime="1111111111" name="add" size="11" /> +</directory> diff --git a/tests/commit_fixtures/testAddfile_filesremote b/tests/commit_fixtures/testAddfile_filesremote new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/commit_fixtures/testAddfile_filesremote @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/testAddfile_lfilelist b/tests/commit_fixtures/testAddfile_lfilelist new file mode 100644 index 0000000..c0c079b --- /dev/null +++ b/tests/commit_fixtures/testAddfile_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /><entry md5="0d62ceea6020d75154078a20d8c9f9ba" name="foo" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /><entry md5="7efa70f68983fad1cf487f69dedf93e9" name="nochange" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testAddfile_missingfilelist b/tests/commit_fixtures/testAddfile_missingfilelist new file mode 100644 index 0000000..ac41f20 --- /dev/null +++ b/tests/commit_fixtures/testAddfile_missingfilelist @@ -0,0 +1,3 @@ +<directory error="missing" name="add"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /> +</directory> diff --git a/tests/commit_fixtures/testAllStates_cfilesremote b/tests/commit_fixtures/testAllStates_cfilesremote new file mode 100644 index 0000000..18e5fa2 --- /dev/null +++ b/tests/commit_fixtures/testAllStates_cfilesremote @@ -0,0 +1,8 @@ +<directory name="allstates" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" mtime="3333333333" name="add" size="11" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="d908d26cac8092d475f40a5179ca6347" mtime="4444444444" name="missing" size="9" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="2222222222" name="nochange" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="100" /> +</directory> diff --git a/tests/commit_fixtures/testAllStates_expfiles b/tests/commit_fixtures/testAllStates_expfiles new file mode 100644 index 0000000..692f070 --- /dev/null +++ b/tests/commit_fixtures/testAllStates_expfiles @@ -0,0 +1,8 @@ +<directory name="allstates" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" mtime="3333333333" name="add" size="11" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="d908d26cac8092d475f40a5179ca6347" mtime="4444444444" name="missing" size="9" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="2222222222" name="nochange" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="100" skipped="true" /> +</directory> diff --git a/tests/commit_fixtures/testAllStates_filesremote b/tests/commit_fixtures/testAllStates_filesremote new file mode 100644 index 0000000..995a585 --- /dev/null +++ b/tests/commit_fixtures/testAllStates_filesremote @@ -0,0 +1,8 @@ +<directory name="allstates" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="676513fde5797c3785164942c97dfec1" mtime="1283506309" name="missing" size="8" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="100" /> +</directory> diff --git a/tests/commit_fixtures/testAllStates_lfilelist b/tests/commit_fixtures/testAllStates_lfilelist new file mode 100644 index 0000000..c534c8c --- /dev/null +++ b/tests/commit_fixtures/testAllStates_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /><entry md5="d908d26cac8092d475f40a5179ca6347" name="missing" /><entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /><entry md5="ffffffffffffffffffffffffffffffff" name="skipped" /><entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" name="test" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testAllStates_missingfilelist b/tests/commit_fixtures/testAllStates_missingfilelist new file mode 100644 index 0000000..d7ee788 --- /dev/null +++ b/tests/commit_fixtures/testAllStates_missingfilelist @@ -0,0 +1,5 @@ +<directory error="missing" name="allstates"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /> + <entry md5="d908d26cac8092d475f40a5179ca6347" name="missing" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /> +</directory> diff --git a/tests/commit_fixtures/testConflictfile_filesremote b/tests/commit_fixtures/testConflictfile_filesremote new file mode 100644 index 0000000..a67ff42 --- /dev/null +++ b/tests/commit_fixtures/testConflictfile_filesremote @@ -0,0 +1,5 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282130148" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282130148" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testDeletefile_cfilesremote b/tests/commit_fixtures/testDeletefile_cfilesremote new file mode 100644 index 0000000..20ce708 --- /dev/null +++ b/tests/commit_fixtures/testDeletefile_cfilesremote @@ -0,0 +1,4 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> +</directory> diff --git a/tests/commit_fixtures/testDeletefile_filesremote b/tests/commit_fixtures/testDeletefile_filesremote new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/commit_fixtures/testDeletefile_filesremote @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/testDeletefile_lfilelist b/tests/commit_fixtures/testDeletefile_lfilelist new file mode 100644 index 0000000..bc0f1c6 --- /dev/null +++ b/tests/commit_fixtures/testDeletefile_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="0d62ceea6020d75154078a20d8c9f9ba" name="foo" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testExpand_cfilesremote b/tests/commit_fixtures/testExpand_cfilesremote new file mode 100644 index 0000000..475cff8 --- /dev/null +++ b/tests/commit_fixtures/testExpand_cfilesremote @@ -0,0 +1,5 @@ +<directory name="branch" rev="7" srcmd5="1d4bbfa2655ab3982074226e16e1e5ff" vrev="7"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="1d4bbfa2655ab3982074226e16e1e5ff" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" xsrcmd5="87ea02aede261b0267aabaa97c756e7a" /> + <entry md5="542f96b49b64095104d8a9e9dd313a9c" mtime="1283521153" name="_link" size="130" /> + <entry md5="75da7f7167c22b2b02c6879366d78ad1" mtime="1283525027" name="simple" size="22" /> +</directory> diff --git a/tests/commit_fixtures/testExpand_expandedfilesremote b/tests/commit_fixtures/testExpand_expandedfilesremote new file mode 100644 index 0000000..1f37d41 --- /dev/null +++ b/tests/commit_fixtures/testExpand_expandedfilesremote @@ -0,0 +1,4 @@ +<directory name="branch" rev="87ea02aede261b0267aabaa97c756e7a" srcmd5="87ea02aede261b0267aabaa97c756e7a"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="1d4bbfa2655ab3982074226e16e1e5ff" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" /> + <entry md5="75da7f7167c22b2b02c6879366d78ad1" mtime="1283525027" name="simple" size="22" /> +</directory> diff --git a/tests/commit_fixtures/testExpand_filesremote b/tests/commit_fixtures/testExpand_filesremote new file mode 100644 index 0000000..1bfc91b --- /dev/null +++ b/tests/commit_fixtures/testExpand_filesremote @@ -0,0 +1,5 @@ +<directory name="branch" rev="6" srcmd5="cd21541fe2442d3d324a6d6103752913" vrev="6"> + <linkinfo baserev="b63634ab40861fdb8b44e5f4f459c621" lsrcmd5="cd21541fe2442d3d324a6d6103752913" package="bar" project="foo" srcmd5="b63634ab40861fdb8b44e5f4f459c621" xsrcmd5="9afa23b484de05e28364b18de7bb1432" /> + <entry md5="542f96b49b64095104d8a9e9dd313a9c" mtime="1283521153" name="_link" size="130" skipped="true" /> + <entry md5="75d884cf1d235180faec5acb63063972" mtime="1283525196" name="simple" size="21" /> +</directory> diff --git a/tests/commit_fixtures/testExpand_lfilelist b/tests/commit_fixtures/testExpand_lfilelist new file mode 100644 index 0000000..d6d37b5 --- /dev/null +++ b/tests/commit_fixtures/testExpand_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="75da7f7167c22b2b02c6879366d78ad1" name="simple" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testExpand_missingfilelist b/tests/commit_fixtures/testExpand_missingfilelist new file mode 100644 index 0000000..f2d91e8 --- /dev/null +++ b/tests/commit_fixtures/testExpand_missingfilelist @@ -0,0 +1,3 @@ +<directory error="missing" name="branch"> + <entry md5="75da7f7167c22b2b02c6879366d78ad1" name="simple" /> +</directory> diff --git a/tests/commit_fixtures/testInterrupted_lfilelist b/tests/commit_fixtures/testInterrupted_lfilelist new file mode 100644 index 0000000..85e9db5 --- /dev/null +++ b/tests/commit_fixtures/testInterrupted_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="0d62ceea6020d75154078a20d8c9f9ba" name="foo" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /><entry md5="382588b92f5976de693f44c4d6df27b7" name="nochange" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testMultiple_cfilesremote b/tests/commit_fixtures/testMultiple_cfilesremote new file mode 100644 index 0000000..e98188d --- /dev/null +++ b/tests/commit_fixtures/testMultiple_cfilesremote @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" mtime="1111111111" name="add" size="11" /> + <entry md5="ea467af882b32a275fe62eb05aba6ee1" mtime="0000000000" name="add2" size="5" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="2222222222" name="nochange" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/commit_fixtures/testMultiple_filesremote b/tests/commit_fixtures/testMultiple_filesremote new file mode 100644 index 0000000..00e4458 --- /dev/null +++ b/tests/commit_fixtures/testMultiple_filesremote @@ -0,0 +1,6 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/commit_fixtures/testMultiple_lfilelist b/tests/commit_fixtures/testMultiple_lfilelist new file mode 100644 index 0000000..cd8c43c --- /dev/null +++ b/tests/commit_fixtures/testMultiple_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /><entry md5="ea467af882b32a275fe62eb05aba6ee1" name="add2" /><entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /><entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" name="test" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testMultiple_missingfilelist b/tests/commit_fixtures/testMultiple_missingfilelist new file mode 100644 index 0000000..e4f6314 --- /dev/null +++ b/tests/commit_fixtures/testMultiple_missingfilelist @@ -0,0 +1,5 @@ +<directory error="missing" name="add"> + <entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /> + <entry md5="ea467af882b32a275fe62eb05aba6ee1" name="add2" /> +</directory> diff --git a/tests/commit_fixtures/testNoChanges_filesremote b/tests/commit_fixtures/testNoChanges_filesremote new file mode 100644 index 0000000..a67ff42 --- /dev/null +++ b/tests/commit_fixtures/testNoChanges_filesremote @@ -0,0 +1,5 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282130148" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282130148" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testPartial_cfilesremote b/tests/commit_fixtures/testPartial_cfilesremote new file mode 100644 index 0000000..db75949 --- /dev/null +++ b/tests/commit_fixtures/testPartial_cfilesremote @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" mtime="1111111111" name="add" size="11" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="2222222222" name="nochange" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/commit_fixtures/testPartial_filesremote b/tests/commit_fixtures/testPartial_filesremote new file mode 100644 index 0000000..00e4458 --- /dev/null +++ b/tests/commit_fixtures/testPartial_filesremote @@ -0,0 +1,6 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/commit_fixtures/testPartial_lfilelist b/tests/commit_fixtures/testPartial_lfilelist new file mode 100644 index 0000000..6da9126 --- /dev/null +++ b/tests/commit_fixtures/testPartial_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /><entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /><entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" name="test" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testPartial_missingfilelist b/tests/commit_fixtures/testPartial_missingfilelist new file mode 100644 index 0000000..589642c --- /dev/null +++ b/tests/commit_fixtures/testPartial_missingfilelist @@ -0,0 +1,4 @@ +<directory error="missing" name="partial"> + <entry md5="b423d194c75e59ee4d8d2e07ba24323d" name="add" /> + <entry md5="2abd19de6a38ff2890af64f453df96b1" name="nochange" /> +</directory> diff --git a/tests/commit_fixtures/testSimple_cfilesremote b/tests/commit_fixtures/testSimple_cfilesremote new file mode 100644 index 0000000..bc155b4 --- /dev/null +++ b/tests/commit_fixtures/testSimple_cfilesremote @@ -0,0 +1,5 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="382588b92f5976de693f44c4d6df27b7" mtime="1282047303" name="nochange" size="41" /> +</directory> diff --git a/tests/commit_fixtures/testSimple_filesremote b/tests/commit_fixtures/testSimple_filesremote new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/commit_fixtures/testSimple_filesremote @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/commit_fixtures/testSimple_lfilelist b/tests/commit_fixtures/testSimple_lfilelist new file mode 100644 index 0000000..85e9db5 --- /dev/null +++ b/tests/commit_fixtures/testSimple_lfilelist @@ -0,0 +1 @@ +<directory><entry md5="0d62ceea6020d75154078a20d8c9f9ba" name="foo" /><entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" name="merge" /><entry md5="382588b92f5976de693f44c4d6df27b7" name="nochange" /></directory>
\ No newline at end of file diff --git a/tests/commit_fixtures/testSimple_missingfilelist b/tests/commit_fixtures/testSimple_missingfilelist new file mode 100644 index 0000000..1c6bc68 --- /dev/null +++ b/tests/commit_fixtures/testSimple_missingfilelist @@ -0,0 +1,3 @@ +<directory error="missing" name="simple"> + <entry md5="c4eaea5dcaff13418e38e7fea151dd49" name="nochange" /> +</directory> diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 0000000..0465792 --- /dev/null +++ b/tests/common.py @@ -0,0 +1,207 @@ +import unittest +import osc.core +import shutil +import tempfile +import os +import sys +from xml.etree import cElementTree as ET +EXPECTED_REQUESTS = [] + +if sys.version_info[0:2] in ((2, 6), (2, 7)): + bytes = lambda x, *args: x + +try: + #python 2.x + from cStringIO import StringIO + from urllib2 import HTTPHandler, addinfourl, build_opener + from urlparse import urlparse, parse_qs +except ImportError: + from io import StringIO + from urllib.request import HTTPHandler, addinfourl, build_opener + from urllib.parse import urlparse, parse_qs + +def urlcompare(url, *args): + """compare all components of url except query string - it is converted to + dict, therefor different ordering does not makes url's different, as well + as quoting of a query string""" + + components = urlparse(url) + query_args = parse_qs(components.query) + components = components._replace(query=None) + + if not args: + return False + + for url in args: + components2 = urlparse(url) + query_args2 = parse_qs(components2.query) + components2 = components2._replace(query=None) + + if components != components2 or \ + query_args != query_args2: + return False + + return True + +class RequestWrongOrder(Exception): + """raised if an unexpected request is issued to urllib2""" + def __init__(self, url, exp_url, method, exp_method): + Exception.__init__(self) + self.url = url + self.exp_url = exp_url + self.method = method + self.exp_method = exp_method + + def __str__(self): + return '%s, %s, %s, %s' % (self.url, self.exp_url, self.method, self.exp_method) + +class RequestDataMismatch(Exception): + """raised if POSTed or PUTed data doesn't match with the expected data""" + def __init__(self, url, got, exp): + self.url = url + self.got = got + self.exp = exp + + def __str__(self): + return '%s, %s, %s' % (self.url, self.got, self.exp) + +class MyHTTPHandler(HTTPHandler): + def __init__(self, exp_requests, fixtures_dir): + HTTPHandler.__init__(self) + self.__exp_requests = exp_requests + self.__fixtures_dir = fixtures_dir + + def http_open(self, req): + r = self.__exp_requests.pop(0) + if not urlcompare(req.get_full_url(), r[1]) or req.get_method() != r[0]: + raise RequestWrongOrder(req.get_full_url(), r[1], req.get_method(), r[0]) + if req.get_method() in ('GET', 'DELETE'): + return self.__mock_GET(r[1], **r[2]) + elif req.get_method() in ('PUT', 'POST'): + return self.__mock_PUT(req, **r[2]) + + def __mock_GET(self, fullurl, **kwargs): + return self.__get_response(fullurl, **kwargs) + + def __mock_PUT(self, req, **kwargs): + exp = kwargs.get('exp', None) + if exp is not None and 'expfile' in kwargs: + raise RuntimeError('either specify exp or expfile') + elif 'expfile' in kwargs: + exp = open(os.path.join(self.__fixtures_dir, kwargs['expfile']), 'r').read() + elif exp is None: + raise RuntimeError('exp or expfile required') + if exp is not None: + # use req.data instead of req.get_data() for python3 compatiblity + if req.data != bytes(exp, "utf-8"): + raise RequestDataMismatch(req.get_full_url(), repr(req.get_data()), repr(exp)) + return self.__get_response(req.get_full_url(), **kwargs) + + def __get_response(self, url, **kwargs): + f = None + if 'exception' in kwargs: + raise kwargs['exception'] + if 'text' not in kwargs and 'file' in kwargs: + f = StringIO(open(os.path.join(self.__fixtures_dir, kwargs['file']), 'r').read()) + elif 'text' in kwargs and 'file' not in kwargs: + f = StringIO(kwargs['text']) + else: + raise RuntimeError('either specify text or file') + resp = addinfourl(f, {}, url) + resp.code = kwargs.get('code', 200) + resp.msg = '' + return resp + +def urldecorator(method, fullurl, **kwargs): + def decorate(test_method): + def wrapped_test_method(*args): + addExpectedRequest(method, fullurl, **kwargs) + test_method(*args) + # "rename" method otherwise we cannot specify a TestCaseClass.testName + # cmdline arg when using unittest.main() + wrapped_test_method.__name__ = test_method.__name__ + return wrapped_test_method + return decorate + +def GET(fullurl, **kwargs): + return urldecorator('GET', fullurl, **kwargs) + +def PUT(fullurl, **kwargs): + return urldecorator('PUT', fullurl, **kwargs) + +def POST(fullurl, **kwargs): + return urldecorator('POST', fullurl, **kwargs) + +def DELETE(fullurl, **kwargs): + return urldecorator('DELETE', fullurl, **kwargs) + +def addExpectedRequest(method, url, **kwargs): + global EXPECTED_REQUESTS + EXPECTED_REQUESTS.append((method, url, kwargs)) + +class OscTestCase(unittest.TestCase): + def setUp(self, copytree=True): + oscrc = os.path.join(self._get_fixtures_dir(), 'oscrc') + osc.core.conf.get_config(override_conffile=oscrc, + override_no_keyring=True, override_no_gnome_keyring=True) + os.environ['OSC_CONFIG'] = oscrc + + self.tmpdir = tempfile.mkdtemp(prefix='osc_test') + if copytree: + shutil.copytree(os.path.join(self._get_fixtures_dir(), 'osctest'), os.path.join(self.tmpdir, 'osctest')) + global EXPECTED_REQUESTS + EXPECTED_REQUESTS = [] + osc.core.conf._build_opener = lambda u: build_opener(MyHTTPHandler(EXPECTED_REQUESTS, self._get_fixtures_dir())) + self.stdout = sys.stdout + sys.stdout = StringIO() + + def tearDown(self): + self.assertTrue(len(EXPECTED_REQUESTS) == 0) + sys.stdout = self.stdout + try: + shutil.rmtree(self.tmpdir) + except: + pass + + def _get_fixtures_dir(self): + raise NotImplementedError('subclasses should implement this method') + + def _change_to_pkg(self, name): + os.chdir(os.path.join(self.tmpdir, 'osctest', name)) + + def _check_list(self, fname, exp): + fname = os.path.join('.osc', fname) + self.assertTrue(os.path.exists(fname)) + self.assertEqual(open(fname, 'r').read(), exp) + + def _check_addlist(self, exp): + self._check_list('_to_be_added', exp) + + def _check_deletelist(self, exp): + self._check_list('_to_be_deleted', exp) + + def _check_conflictlist(self, exp): + self._check_list('_in_conflict', exp) + + def _check_status(self, p, fname, exp): + self.assertEqual(p.status(fname), exp) + + def _check_digests(self, fname, *skipfiles): + fname = os.path.join(self._get_fixtures_dir(), fname) + self.assertEqual(open(os.path.join('.osc', '_files'), 'r').read(), open(fname, 'r').read()) + root = ET.parse(fname).getroot() + for i in root.findall('entry'): + if i.get('name') in skipfiles: + continue + self.assertTrue(os.path.exists(os.path.join('.osc', i.get('name')))) + self.assertEqual(osc.core.dgst(os.path.join('.osc', i.get('name'))), i.get('md5')) + + def assertEqualMultiline(self, got, exp): + if (got + exp).find('\n') == -1: + self.assertEqual(got, exp) + else: + start_delim = "\n" + (" 8< ".join(["-----"] * 8)) + "\n" + end_delim = "\n" + (" >8 ".join(["-----"] * 8)) + "\n\n" + self.assertEqual(got, exp, + "got:" + start_delim + got + end_delim + + "expected:" + start_delim + exp + end_delim) diff --git a/tests/conf_fixtures/oscrc b/tests/conf_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/conf_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/deletefile_fixtures/oscrc b/tests/deletefile_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/deletefile_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/deletefile_fixtures/osctest/.osc/_apiurl b/tests/deletefile_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/.osc/_packages b/tests/deletefile_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/deletefile_fixtures/osctest/.osc/_project b/tests/deletefile_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_apiurl b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_files b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_osclib_version b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_package b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_project b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_added b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_deleted b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/foo b/tests/deletefile_fixtures/osctest/already_deleted/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/merge b/tests/deletefile_fixtures/osctest/already_deleted/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/already_deleted/.osc/nochange b/tests/deletefile_fixtures/osctest/already_deleted/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/deletefile_fixtures/osctest/already_deleted/merge b/tests/deletefile_fixtures/osctest/already_deleted/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/already_deleted/nochange b/tests/deletefile_fixtures/osctest/already_deleted/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/deletefile_fixtures/osctest/already_deleted/toadd1 b/tests/deletefile_fixtures/osctest/already_deleted/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/already_deleted/toadd2 b/tests/deletefile_fixtures/osctest/already_deleted/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/already_deleted/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_apiurl b/tests/deletefile_fixtures/osctest/conflict/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_files b/tests/deletefile_fixtures/osctest/conflict/.osc/_files new file mode 100644 index 0000000..6fc0c34 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_files @@ -0,0 +1,5 @@ +<directory name="conflict" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_in_conflict b/tests/deletefile_fixtures/osctest/conflict/.osc/_in_conflict new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_in_conflict @@ -0,0 +1 @@ +foo diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_osclib_version b/tests/deletefile_fixtures/osctest/conflict/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_package b/tests/deletefile_fixtures/osctest/conflict/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_project b/tests/deletefile_fixtures/osctest/conflict/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/_to_be_added b/tests/deletefile_fixtures/osctest/conflict/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/foo b/tests/deletefile_fixtures/osctest/conflict/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/merge b/tests/deletefile_fixtures/osctest/conflict/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/conflict/.osc/nochange b/tests/deletefile_fixtures/osctest/conflict/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/deletefile_fixtures/osctest/conflict/foo b/tests/deletefile_fixtures/osctest/conflict/foo new file mode 100644 index 0000000..ad9621d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/foo @@ -0,0 +1,5 @@ +<<<<<<< foo.mine +This is no test. +======= +This is a simple test. +>>>>>>> foo.r2 diff --git a/tests/deletefile_fixtures/osctest/conflict/foo.mine b/tests/deletefile_fixtures/osctest/conflict/foo.mine new file mode 100644 index 0000000..3543613 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/foo.mine @@ -0,0 +1 @@ +This is no test. diff --git a/tests/deletefile_fixtures/osctest/conflict/foo.r2 b/tests/deletefile_fixtures/osctest/conflict/foo.r2 new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/foo.r2 @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/conflict/merge b/tests/deletefile_fixtures/osctest/conflict/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/conflict/nochange b/tests/deletefile_fixtures/osctest/conflict/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/deletefile_fixtures/osctest/conflict/toadd1 b/tests/deletefile_fixtures/osctest/conflict/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/conflict/toadd2 b/tests/deletefile_fixtures/osctest/conflict/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/conflict/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_apiurl b/tests/deletefile_fixtures/osctest/delete/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_files b/tests/deletefile_fixtures/osctest/delete/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_osclib_version b/tests/deletefile_fixtures/osctest/delete/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_package b/tests/deletefile_fixtures/osctest/delete/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_project b/tests/deletefile_fixtures/osctest/delete/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_added b/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_deleted b/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/foo b/tests/deletefile_fixtures/osctest/delete/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/merge b/tests/deletefile_fixtures/osctest/delete/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/delete/.osc/nochange b/tests/deletefile_fixtures/osctest/delete/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/deletefile_fixtures/osctest/delete/merge b/tests/deletefile_fixtures/osctest/delete/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/delete/nochange b/tests/deletefile_fixtures/osctest/delete/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/deletefile_fixtures/osctest/delete/toadd2 b/tests/deletefile_fixtures/osctest/delete/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/delete/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_apiurl b/tests/deletefile_fixtures/osctest/replace/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_files b/tests/deletefile_fixtures/osctest/replace/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_osclib_version b/tests/deletefile_fixtures/osctest/replace/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_package b/tests/deletefile_fixtures/osctest/replace/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_project b/tests/deletefile_fixtures/osctest/replace/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/_to_be_added b/tests/deletefile_fixtures/osctest/replace/.osc/_to_be_added new file mode 100644 index 0000000..d530a9a --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/_to_be_added @@ -0,0 +1,2 @@ +toadd1 +merge diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/foo b/tests/deletefile_fixtures/osctest/replace/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/merge b/tests/deletefile_fixtures/osctest/replace/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/replace/.osc/nochange b/tests/deletefile_fixtures/osctest/replace/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/deletefile_fixtures/osctest/replace/foo b/tests/deletefile_fixtures/osctest/replace/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/replace/merge b/tests/deletefile_fixtures/osctest/replace/merge new file mode 100644 index 0000000..feae347 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/merge @@ -0,0 +1 @@ +replaced diff --git a/tests/deletefile_fixtures/osctest/replace/nochange b/tests/deletefile_fixtures/osctest/replace/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/deletefile_fixtures/osctest/replace/toadd1 b/tests/deletefile_fixtures/osctest/replace/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/replace/toadd2 b/tests/deletefile_fixtures/osctest/replace/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/replace/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_apiurl b/tests/deletefile_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_files b/tests/deletefile_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..eb2a44c --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,7 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" mtime="123456789" name="skipped" size="225" skipped="true" /> + <entry md5="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" mtime="012345678" name="skipped_exists" size="22" skipped="true" /> +</directory> diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_osclib_version b/tests/deletefile_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_package b/tests/deletefile_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_project b/tests/deletefile_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/_to_be_added b/tests/deletefile_fixtures/osctest/simple/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/foo b/tests/deletefile_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/merge b/tests/deletefile_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/simple/.osc/nochange b/tests/deletefile_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/deletefile_fixtures/osctest/simple/foo b/tests/deletefile_fixtures/osctest/simple/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/deletefile_fixtures/osctest/simple/merge b/tests/deletefile_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/deletefile_fixtures/osctest/simple/nochange b/tests/deletefile_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/deletefile_fixtures/osctest/simple/skipped_exists b/tests/deletefile_fixtures/osctest/simple/skipped_exists new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/skipped_exists @@ -0,0 +1 @@ +foobar diff --git a/tests/deletefile_fixtures/osctest/simple/toadd1 b/tests/deletefile_fixtures/osctest/simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/deletefile_fixtures/osctest/simple/toadd2 b/tests/deletefile_fixtures/osctest/simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/deletefile_fixtures/osctest/simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/oscrc b/tests/difffile_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/difffile_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/difffile_fixtures/osctest/.osc/_apiurl b/tests/difffile_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/.osc/_packages b/tests/difffile_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/difffile_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/difffile_fixtures/osctest/.osc/_project b/tests/difffile_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/difffile_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_apiurl b/tests/difffile_fixtures/osctest/binary/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_files b/tests/difffile_fixtures/osctest/binary/.osc/_files new file mode 100644 index 0000000..2c82894 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_files @@ -0,0 +1,4 @@ +<directory name="binary" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="8f618462e00017108b4146a29e074bdf" mtime="1111111111" name="binary" size="18" /> + <entry md5="ee813c93cb5730dce38976695634482f" mtime="1111111111" name="binary_deleted" size="26" /> +</directory> diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_osclib_version b/tests/difffile_fixtures/osctest/binary/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_package b/tests/difffile_fixtures/osctest/binary/.osc/_package new file mode 100644 index 0000000..a9128c2 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_package @@ -0,0 +1 @@ +binary diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_project b/tests/difffile_fixtures/osctest/binary/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_to_be_added b/tests/difffile_fixtures/osctest/binary/.osc/_to_be_added new file mode 100644 index 0000000..075a151 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_to_be_added @@ -0,0 +1 @@ +binary_added diff --git a/tests/difffile_fixtures/osctest/binary/.osc/_to_be_deleted b/tests/difffile_fixtures/osctest/binary/.osc/_to_be_deleted new file mode 100644 index 0000000..705639e --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/_to_be_deleted @@ -0,0 +1 @@ +binary_deleted diff --git a/tests/difffile_fixtures/osctest/binary/.osc/binary b/tests/difffile_fixtures/osctest/binary/.osc/binary Binary files differnew file mode 100644 index 0000000..727c366 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/binary diff --git a/tests/difffile_fixtures/osctest/binary/.osc/binary_deleted b/tests/difffile_fixtures/osctest/binary/.osc/binary_deleted Binary files differnew file mode 100644 index 0000000..17e35ec --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/.osc/binary_deleted diff --git a/tests/difffile_fixtures/osctest/binary/binary b/tests/difffile_fixtures/osctest/binary/binary Binary files differnew file mode 100644 index 0000000..5868978 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/binary diff --git a/tests/difffile_fixtures/osctest/binary/binary_added b/tests/difffile_fixtures/osctest/binary/binary_added Binary files differnew file mode 100644 index 0000000..188a937 --- /dev/null +++ b/tests/difffile_fixtures/osctest/binary/binary_added diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_apiurl b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_files b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_osclib_version b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_package b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_package new file mode 100644 index 0000000..5d09f91 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_package @@ -0,0 +1 @@ +remote_localdelete diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_project b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_to_be_deleted b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_to_be_deleted new file mode 100644 index 0000000..a00af07 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/_to_be_deleted @@ -0,0 +1 @@ +merge diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/foo b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/merge b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/.osc/nochange b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/foo b/tests/difffile_fixtures/osctest/remote_localdelete/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/nochange b/tests/difffile_fixtures/osctest/remote_localdelete/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_localdelete/toadd2 b/tests/difffile_fixtures/osctest/remote_localdelete/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localdelete/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_apiurl b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_files b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_files new file mode 100644 index 0000000..7c4480a --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="b1b642cdbacf9956104f8565e297ed00" mtime="1283246089" name="binary" size="27" /> +</directory> diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_osclib_version b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_package b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_package new file mode 100644 index 0000000..6c6c3a8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_package @@ -0,0 +1 @@ +remote_localmodified diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_project b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/binary b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/binary Binary files differnew file mode 100644 index 0000000..5868978 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/binary diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/foo b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/merge b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/.osc/nochange b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/binary b/tests/difffile_fixtures/osctest/remote_localmodified/binary Binary files differnew file mode 100644 index 0000000..ff2abf9 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/binary diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/foo b/tests/difffile_fixtures/osctest/remote_localmodified/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/merge b/tests/difffile_fixtures/osctest/remote_localmodified/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/nochange b/tests/difffile_fixtures/osctest/remote_localmodified/nochange new file mode 100644 index 0000000..a64acb7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/nochange @@ -0,0 +1,2 @@ +This file didn't change. +oh it does diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/toadd1 b/tests/difffile_fixtures/osctest/remote_localmodified/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/difffile_fixtures/osctest/remote_localmodified/toadd2 b/tests/difffile_fixtures/osctest/remote_localmodified/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_localmodified/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_apiurl b/tests/difffile_fixtures/osctest/remote_simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_files b/tests/difffile_fixtures/osctest/remote_simple/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_osclib_version b/tests/difffile_fixtures/osctest/remote_simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_package b/tests/difffile_fixtures/osctest/remote_simple/.osc/_package new file mode 100644 index 0000000..7894c46 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_package @@ -0,0 +1 @@ +remote_simple diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_project b/tests/difffile_fixtures/osctest/remote_simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/_to_be_added b/tests/difffile_fixtures/osctest/remote_simple/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/foo b/tests/difffile_fixtures/osctest/remote_simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/merge b/tests/difffile_fixtures/osctest/remote_simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_simple/.osc/nochange b/tests/difffile_fixtures/osctest/remote_simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_simple/binary b/tests/difffile_fixtures/osctest/remote_simple/binary Binary files differnew file mode 100644 index 0000000..5868978 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/binary diff --git a/tests/difffile_fixtures/osctest/remote_simple/foo b/tests/difffile_fixtures/osctest/remote_simple/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_simple/merge b/tests/difffile_fixtures/osctest/remote_simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_simple/nochange b/tests/difffile_fixtures/osctest/remote_simple/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_simple/toadd1 b/tests/difffile_fixtures/osctest/remote_simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/difffile_fixtures/osctest/remote_simple/toadd2 b/tests/difffile_fixtures/osctest/remote_simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_apiurl b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_files b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_osclib_version b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_package b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_package new file mode 100644 index 0000000..6a70072 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_package @@ -0,0 +1 @@ +remote_simple_noadd diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_project b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/foo b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/merge b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/nochange b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/foo b/tests/difffile_fixtures/osctest/remote_simple_noadd/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/merge b/tests/difffile_fixtures/osctest/remote_simple_noadd/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/nochange b/tests/difffile_fixtures/osctest/remote_simple_noadd/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/remote_simple_noadd/toadd2 b/tests/difffile_fixtures/osctest/remote_simple_noadd/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/difffile_fixtures/osctest/remote_simple_noadd/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_apiurl b/tests/difffile_fixtures/osctest/replaced/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_files b/tests/difffile_fixtures/osctest/replaced/.osc/_files new file mode 100644 index 0000000..c275f82 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_files @@ -0,0 +1,3 @@ +<directory name="replaced" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="81be947db54c2e225dc8eacce64d8a4a" mtime="1282731457" name="replaced" size="17" /> +</directory> diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_osclib_version b/tests/difffile_fixtures/osctest/replaced/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_package b/tests/difffile_fixtures/osctest/replaced/.osc/_package new file mode 100644 index 0000000..feae347 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_package @@ -0,0 +1 @@ +replaced diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_project b/tests/difffile_fixtures/osctest/replaced/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/_to_be_added b/tests/difffile_fixtures/osctest/replaced/.osc/_to_be_added new file mode 100644 index 0000000..feae347 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/_to_be_added @@ -0,0 +1 @@ +replaced diff --git a/tests/difffile_fixtures/osctest/replaced/.osc/replaced b/tests/difffile_fixtures/osctest/replaced/.osc/replaced new file mode 100644 index 0000000..7c3f1a8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/.osc/replaced @@ -0,0 +1 @@ +yet another file diff --git a/tests/difffile_fixtures/osctest/replaced/replaced b/tests/difffile_fixtures/osctest/replaced/replaced new file mode 100644 index 0000000..f479fb8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/replaced/replaced @@ -0,0 +1 @@ +foo replaced diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_apiurl b/tests/difffile_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_files b/tests/difffile_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..041f606 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,9 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="eb9c2bf0eb63f3a7bc0ea37ef18aeba5" mtime="1282730880" name="somefile" size="13" /> + <entry md5="81be947db54c2e225dc8eacce64d8a4a" mtime="1282731457" name="replaced" size="17" /> + <entry md5="676513fde5797c3785164942c97dfec1" mtime="1282731738" name="missing" size="8" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="12" skipped="true" /> +</directory> diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_in_conflict b/tests/difffile_fixtures/osctest/simple/.osc/_in_conflict new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_in_conflict @@ -0,0 +1 @@ +foo diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_osclib_version b/tests/difffile_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_package b/tests/difffile_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_project b/tests/difffile_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_to_be_added b/tests/difffile_fixtures/osctest/simple/.osc/_to_be_added new file mode 100644 index 0000000..1f4923c --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_to_be_added @@ -0,0 +1,3 @@ +toadd1 +replaced +addedmissing diff --git a/tests/difffile_fixtures/osctest/simple/.osc/_to_be_deleted b/tests/difffile_fixtures/osctest/simple/.osc/_to_be_deleted new file mode 100644 index 0000000..ebf038b --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/_to_be_deleted @@ -0,0 +1 @@ +somefile diff --git a/tests/difffile_fixtures/osctest/simple/.osc/foo b/tests/difffile_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/difffile_fixtures/osctest/simple/.osc/merge b/tests/difffile_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/simple/.osc/missing b/tests/difffile_fixtures/osctest/simple/.osc/missing new file mode 100644 index 0000000..33e45d5 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/missing @@ -0,0 +1 @@ +missing diff --git a/tests/difffile_fixtures/osctest/simple/.osc/nochange b/tests/difffile_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/difffile_fixtures/osctest/simple/.osc/replaced b/tests/difffile_fixtures/osctest/simple/.osc/replaced new file mode 100644 index 0000000..7c3f1a8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/replaced @@ -0,0 +1 @@ +yet another file diff --git a/tests/difffile_fixtures/osctest/simple/.osc/somefile b/tests/difffile_fixtures/osctest/simple/.osc/somefile new file mode 100644 index 0000000..2ef267e --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/.osc/somefile @@ -0,0 +1 @@ +some content diff --git a/tests/difffile_fixtures/osctest/simple/foo b/tests/difffile_fixtures/osctest/simple/foo new file mode 100644 index 0000000..ad9621d --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/foo @@ -0,0 +1,5 @@ +<<<<<<< foo.mine +This is no test. +======= +This is a simple test. +>>>>>>> foo.r2 diff --git a/tests/difffile_fixtures/osctest/simple/merge b/tests/difffile_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/difffile_fixtures/osctest/simple/nochange b/tests/difffile_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/difffile_fixtures/osctest/simple/replaced b/tests/difffile_fixtures/osctest/simple/replaced new file mode 100644 index 0000000..f479fb8 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/replaced @@ -0,0 +1 @@ +foo replaced diff --git a/tests/difffile_fixtures/osctest/simple/toadd1 b/tests/difffile_fixtures/osctest/simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/difffile_fixtures/osctest/simple/toadd2 b/tests/difffile_fixtures/osctest/simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/difffile_fixtures/osctest/simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/difffile_fixtures/testDiffRemoteDeletedLocalAdded_files b/tests/difffile_fixtures/testDiffRemoteDeletedLocalAdded_files new file mode 100644 index 0000000..4aec6af --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteDeletedLocalAdded_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_binary b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_binary Binary files differnew file mode 100644 index 0000000..188a937 --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_binary diff --git a/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_files b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_files new file mode 100644 index 0000000..5ac058d --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_files @@ -0,0 +1,7 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="136a96e1470ec7424bc8ae47612977db" mtime="1282914026" name="foobar" size="14" /> + <entry md5="9b55c93ffec5ef8850c84882de7ef6b5" mtime="1283242538" name="binary" size="7" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_foobar b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_foobar new file mode 100644 index 0000000..e7856a6 --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteExistingLocalNotExisting_foobar @@ -0,0 +1,2 @@ +foobar +barfoo diff --git a/tests/difffile_fixtures/testDiffRemoteMissingLocalDeleted_files b/tests/difffile_fixtures/testDiffRemoteMissingLocalDeleted_files new file mode 100644 index 0000000..3ceb10c --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteMissingLocalDeleted_files @@ -0,0 +1,4 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteMissingLocalExisting_files b/tests/difffile_fixtures/testDiffRemoteMissingLocalExisting_files new file mode 100644 index 0000000..204fdda --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteMissingLocalExisting_files @@ -0,0 +1,4 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteModified_files b/tests/difffile_fixtures/testDiffRemoteModified_files new file mode 100644 index 0000000..d0983af --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteModified_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="daafe513479072c5a942928d1850a939" mtime="1282908295" name="merge" size="35" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteModified_merge b/tests/difffile_fixtures/testDiffRemoteModified_merge new file mode 100644 index 0000000..6236bd0 --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteModified_merge @@ -0,0 +1,3 @@ +Is it +possible to +merge this file? diff --git a/tests/difffile_fixtures/testDiffRemoteNoChange_files b/tests/difffile_fixtures/testDiffRemoteNoChange_files new file mode 100644 index 0000000..4aec6af --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteNoChange_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_binary b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_binary Binary files differnew file mode 100644 index 0000000..5868978 --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_binary diff --git a/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_files b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_files new file mode 100644 index 0000000..054024f --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="3" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="3"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="b1b642cdbacf9956104f8565e297ed00" mtime="1283246089" name="binary" size="27" /> +</directory> diff --git a/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_nochange b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/difffile_fixtures/testDiffRemoteUnchangedLocalModified_nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/init_package_fixtures/oscrc b/tests/init_package_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/init_package_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/init_project_fixtures/oscrc b/tests/init_project_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/init_project_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/osc b/tests/osc new file mode 120000 index 0000000..b2faf3e --- /dev/null +++ b/tests/osc @@ -0,0 +1 @@ +../osc
\ No newline at end of file diff --git a/tests/prdiff_fixtures/common-two-diff b/tests/prdiff_fixtures/common-two-diff new file mode 100644 index 0000000..a06ddea --- /dev/null +++ b/tests/prdiff_fixtures/common-two-diff @@ -0,0 +1,10 @@ +Index: common-two +=================================================================== +--- common-two 2013-01-18 19:18:38.225983117 +0000 ++++ common-two 2013-01-18 19:19:27.882082325 +0000 +@@ -1,4 +1,5 @@ + line one + line two + line three ++an extra line + last line diff --git a/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_apiurl b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_packages b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_packages new file mode 100644 index 0000000..e1711ef --- /dev/null +++ b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_packages @@ -0,0 +1,4 @@ +<project name="home:user:branches:some:project"> + <package name="common-one" state=" " /> + <package name="common-two" state=" " /> +</project>
\ No newline at end of file diff --git a/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_project b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_project new file mode 100644 index 0000000..b83a395 --- /dev/null +++ b/tests/prdiff_fixtures/home:user:branches:some:project/.osc/_project @@ -0,0 +1 @@ +home:user:branches:some:project diff --git a/tests/prdiff_fixtures/home:user:branches:some:project/common-two b/tests/prdiff_fixtures/home:user:branches:some:project/common-two new file mode 100644 index 0000000..ade1e2d --- /dev/null +++ b/tests/prdiff_fixtures/home:user:branches:some:project/common-two @@ -0,0 +1,5 @@ +line one +line two +line three +an extra line +last line
\ No newline at end of file diff --git a/tests/prdiff_fixtures/home:user:branches:some:project/directory b/tests/prdiff_fixtures/home:user:branches:some:project/directory new file mode 100644 index 0000000..f29d454 --- /dev/null +++ b/tests/prdiff_fixtures/home:user:branches:some:project/directory @@ -0,0 +1,6 @@ +<directory count='4'> + <entry name="common-one"/> + <entry name="common-two"/> + <entry name="common-three"/> + <entry name="only-in-new"/> +</directory> diff --git a/tests/prdiff_fixtures/new:prj/common-two b/tests/prdiff_fixtures/new:prj/common-two new file mode 100644 index 0000000..ade1e2d --- /dev/null +++ b/tests/prdiff_fixtures/new:prj/common-two @@ -0,0 +1,5 @@ +line one +line two +line three +an extra line +last line
\ No newline at end of file diff --git a/tests/prdiff_fixtures/new:prj/directory b/tests/prdiff_fixtures/new:prj/directory new file mode 100644 index 0000000..f29d454 --- /dev/null +++ b/tests/prdiff_fixtures/new:prj/directory @@ -0,0 +1,6 @@ +<directory count='4'> + <entry name="common-one"/> + <entry name="common-two"/> + <entry name="common-three"/> + <entry name="only-in-new"/> +</directory> diff --git a/tests/prdiff_fixtures/no-requests b/tests/prdiff_fixtures/no-requests new file mode 100644 index 0000000..aef429f --- /dev/null +++ b/tests/prdiff_fixtures/no-requests @@ -0,0 +1,2 @@ +<collection matches="0"> +</collection> diff --git a/tests/prdiff_fixtures/old:prj/common-two b/tests/prdiff_fixtures/old:prj/common-two new file mode 100644 index 0000000..48365a3 --- /dev/null +++ b/tests/prdiff_fixtures/old:prj/common-two @@ -0,0 +1,4 @@ +line one +line two +line three +last line
\ No newline at end of file diff --git a/tests/prdiff_fixtures/old:prj/directory b/tests/prdiff_fixtures/old:prj/directory new file mode 100644 index 0000000..a9db4b7 --- /dev/null +++ b/tests/prdiff_fixtures/old:prj/directory @@ -0,0 +1,6 @@ +<directory count='4'> + <entry name="common-one"/> + <entry name="common-two"/> + <entry name="common-three"/> + <entry name="only-in-old"/> +</directory> diff --git a/tests/prdiff_fixtures/oscrc b/tests/prdiff_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/prdiff_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/prdiff_fixtures/osctest/.osc/_apiurl b/tests/prdiff_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/prdiff_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/prdiff_fixtures/osctest/.osc/_packages b/tests/prdiff_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..e1711ef --- /dev/null +++ b/tests/prdiff_fixtures/osctest/.osc/_packages @@ -0,0 +1,4 @@ +<project name="home:user:branches:some:project"> + <package name="common-one" state=" " /> + <package name="common-two" state=" " /> +</project>
\ No newline at end of file diff --git a/tests/prdiff_fixtures/osctest/.osc/_project b/tests/prdiff_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83a395 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +home:user:branches:some:project diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_apiurl b/tests/prdiff_fixtures/osctest/common-one/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_files b/tests/prdiff_fixtures/osctest/common-one/.osc/_files new file mode 100644 index 0000000..4b6dcca --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_files @@ -0,0 +1,4 @@ +<directory name="common-one" rev="f53d033d63c3d6e9a8e4493225976122" srcmd5="f53d033d63c3d6e9a8e4493225976122"> + <linkinfo baserev="896e6d6d675d03b6934946d03a976450" lsrcmd5="0cf460222270b58e2a9a3d695b1d945d" package="common-one" project="some:project" srcmd5="8c7ed3cf5ec0b4aa20ef159fd8c51b76" /> + <entry md5="1a4c23ccf2eb12403acbfa3258233a9d" mtime="1352816081" name="common-one.spec" size="3457" /> +</directory> diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_meta b/tests/prdiff_fixtures/osctest/common-one/.osc/_meta new file mode 100644 index 0000000..3804519 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_meta @@ -0,0 +1,10 @@ +<package name="common-one" project="home:user:branches:some:project"> + <title>blah</title> + <description>foo</description> + <debuginfo> + <enable repository="openSUSE_12.2"/> + <enable repository="openSUSE_Factory"/> + <enable repository="SLE_11_SP2"/> + </debuginfo> +</package> + diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_osclib_version b/tests/prdiff_fixtures/osctest/common-one/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_package b/tests/prdiff_fixtures/osctest/common-one/.osc/_package new file mode 100644 index 0000000..089880f --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_package @@ -0,0 +1 @@ +common-one
\ No newline at end of file diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/_project b/tests/prdiff_fixtures/osctest/common-one/.osc/_project new file mode 100644 index 0000000..b83a395 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/_project @@ -0,0 +1 @@ +home:user:branches:some:project diff --git a/tests/prdiff_fixtures/osctest/common-one/.osc/common-one.spec b/tests/prdiff_fixtures/osctest/common-one/.osc/common-one.spec new file mode 100644 index 0000000..99bed78 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/.osc/common-one.spec @@ -0,0 +1 @@ +contents are irrelevant diff --git a/tests/prdiff_fixtures/osctest/common-one/common-one.spec b/tests/prdiff_fixtures/osctest/common-one/common-one.spec new file mode 100644 index 0000000..99bed78 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-one/common-one.spec @@ -0,0 +1 @@ +contents are irrelevant diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_apiurl b/tests/prdiff_fixtures/osctest/common-two/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_files b/tests/prdiff_fixtures/osctest/common-two/.osc/_files new file mode 100644 index 0000000..63b65f2 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_files @@ -0,0 +1,4 @@ +<directory name="common-two" rev="f53d033d63c3d6e9a8e4493225976122" srcmd5="f53d033d63c3d6e9a8e4493225976122"> + <linkinfo baserev="896e6d6d675d03b6934946d03a976450" lsrcmd5="0cf460222270b58e2a9a3d695b1d945d" package="common-two" project="some:project" srcmd5="8c7ed3cf5ec0b4aa20ef159fd8c51b76" /> + <entry md5="1a4c23ccf2eb12403acbfa3258233a9d" mtime="1352816081" name="common-two.spec" size="3457" /> +</directory> diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_meta b/tests/prdiff_fixtures/osctest/common-two/.osc/_meta new file mode 100644 index 0000000..3d41ffd --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_meta @@ -0,0 +1,10 @@ +<package name="common-two" project="home:user:branches:some:project"> + <title>blah</title> + <description>foo</description> + <debuginfo> + <enable repository="openSUSE_12.2"/> + <enable repository="openSUSE_Factory"/> + <enable repository="SLE_11_SP2"/> + </debuginfo> +</package> + diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_osclib_version b/tests/prdiff_fixtures/osctest/common-two/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_package b/tests/prdiff_fixtures/osctest/common-two/.osc/_package new file mode 100644 index 0000000..2ff3828 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_package @@ -0,0 +1 @@ +common-two
\ No newline at end of file diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/_project b/tests/prdiff_fixtures/osctest/common-two/.osc/_project new file mode 100644 index 0000000..b83a395 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/_project @@ -0,0 +1 @@ +home:user:branches:some:project diff --git a/tests/prdiff_fixtures/osctest/common-two/.osc/common-two.spec b/tests/prdiff_fixtures/osctest/common-two/.osc/common-two.spec new file mode 100644 index 0000000..99bed78 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/.osc/common-two.spec @@ -0,0 +1 @@ +contents are irrelevant diff --git a/tests/prdiff_fixtures/osctest/common-two/common-two.spec b/tests/prdiff_fixtures/osctest/common-two/common-two.spec new file mode 100644 index 0000000..99bed78 --- /dev/null +++ b/tests/prdiff_fixtures/osctest/common-two/common-two.spec @@ -0,0 +1 @@ +contents are irrelevant diff --git a/tests/prdiff_fixtures/request b/tests/prdiff_fixtures/request new file mode 100644 index 0000000..2355cbb --- /dev/null +++ b/tests/prdiff_fixtures/request @@ -0,0 +1,16 @@ +<collection matches="1"> + <request id="148023"> + <action type="submit"> + <source project="home:user:branches:some:project" package="common-two" rev="7"/> + <target project="some:project" package="common-two"/> + <options> + <sourceupdate>update</sourceupdate> + </options> + </action> + <state name="new" who="user" when="2013-01-11T11:04:14"> + <comment/> + </state> + <description>- Fix it to work +- Improve support for something</description> + </request> +</collection> diff --git a/tests/prdiff_fixtures/some:project/.osc/_apiurl b/tests/prdiff_fixtures/some:project/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/prdiff_fixtures/some:project/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/prdiff_fixtures/some:project/.osc/_packages b/tests/prdiff_fixtures/some:project/.osc/_packages new file mode 100644 index 0000000..c4c8b11 --- /dev/null +++ b/tests/prdiff_fixtures/some:project/.osc/_packages @@ -0,0 +1,4 @@ +<project name="some:project"> + <package name="common-one" state=" " /> + <package name="common-two" state=" " /> +</project>
\ No newline at end of file diff --git a/tests/prdiff_fixtures/some:project/.osc/_project b/tests/prdiff_fixtures/some:project/.osc/_project new file mode 100644 index 0000000..f9a316e --- /dev/null +++ b/tests/prdiff_fixtures/some:project/.osc/_project @@ -0,0 +1 @@ +some:project diff --git a/tests/prdiff_fixtures/some:project/common-two b/tests/prdiff_fixtures/some:project/common-two new file mode 100644 index 0000000..ade1e2d --- /dev/null +++ b/tests/prdiff_fixtures/some:project/common-two @@ -0,0 +1,5 @@ +line one +line two +line three +an extra line +last line
\ No newline at end of file diff --git a/tests/prdiff_fixtures/some:project/directory b/tests/prdiff_fixtures/some:project/directory new file mode 100644 index 0000000..f29d454 --- /dev/null +++ b/tests/prdiff_fixtures/some:project/directory @@ -0,0 +1,6 @@ +<directory count='4'> + <entry name="common-one"/> + <entry name="common-two"/> + <entry name="common-three"/> + <entry name="only-in-new"/> +</directory> diff --git a/tests/project_package_status_fixtures/oscrc b/tests/project_package_status_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/project_package_status_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/project_package_status_fixtures/osctest/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/.osc/_packages b/tests/project_package_status_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..2b174fd --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/.osc/_packages @@ -0,0 +1,9 @@ +<project name="osctest"> + <package name="conflict" state=" " /> + <package name="simple" state=" " /> + <package name="added" state="A" /> + <package name="deleted" state="D" /> + <package name="missing" state="!" /> + <package name="added_deleted" state="A" /> + <package name="deleted_deleted" state="D" /> +</project> diff --git a/tests/project_package_status_fixtures/osctest/.osc/_project b/tests/project_package_status_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/added/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_files b/tests/project_package_status_fixtures/osctest/added/.osc/_files new file mode 100644 index 0000000..9814121 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_files @@ -0,0 +1 @@ +<directory /> diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_osclib_version b/tests/project_package_status_fixtures/osctest/added/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_package b/tests/project_package_status_fixtures/osctest/added/.osc/_package new file mode 100644 index 0000000..d5f7fc3 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_package @@ -0,0 +1 @@ +added diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_project b/tests/project_package_status_fixtures/osctest/added/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/project_package_status_fixtures/osctest/added/.osc/_to_be_added b/tests/project_package_status_fixtures/osctest/added/.osc/_to_be_added new file mode 100644 index 0000000..3e75765 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/.osc/_to_be_added @@ -0,0 +1 @@ +new diff --git a/tests/project_package_status_fixtures/osctest/added/exists b/tests/project_package_status_fixtures/osctest/added/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/exists diff --git a/tests/project_package_status_fixtures/osctest/added/new b/tests/project_package_status_fixtures/osctest/added/new new file mode 100644 index 0000000..3e75765 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/added/new @@ -0,0 +1 @@ +new diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/conflict/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_files b/tests/project_package_status_fixtures/osctest/conflict/.osc/_files new file mode 100644 index 0000000..141eaef --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_files @@ -0,0 +1,4 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="1282047303" name="conflict" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_in_conflict b/tests/project_package_status_fixtures/osctest/conflict/.osc/_in_conflict new file mode 100644 index 0000000..9b1719f --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_in_conflict @@ -0,0 +1 @@ +conflict diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_osclib_version b/tests/project_package_status_fixtures/osctest/conflict/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_package b/tests/project_package_status_fixtures/osctest/conflict/.osc/_package new file mode 100644 index 0000000..9b1719f --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_package @@ -0,0 +1 @@ +conflict diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/_project b/tests/project_package_status_fixtures/osctest/conflict/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/conflict b/tests/project_package_status_fixtures/osctest/conflict/.osc/conflict new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/conflict @@ -0,0 +1 @@ +This file did change. diff --git a/tests/project_package_status_fixtures/osctest/conflict/.osc/test b/tests/project_package_status_fixtures/osctest/conflict/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/conflict/conflict b/tests/project_package_status_fixtures/osctest/conflict/conflict new file mode 100644 index 0000000..e47c5a6 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/conflict @@ -0,0 +1 @@ +Inconflict diff --git a/tests/project_package_status_fixtures/osctest/conflict/exists b/tests/project_package_status_fixtures/osctest/conflict/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/exists diff --git a/tests/project_package_status_fixtures/osctest/conflict/test b/tests/project_package_status_fixtures/osctest/conflict/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/conflict/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/deleted/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_files b/tests/project_package_status_fixtures/osctest/deleted/.osc/_files new file mode 100644 index 0000000..af92bf7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_files @@ -0,0 +1,4 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="1282047303" name="modified" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_osclib_version b/tests/project_package_status_fixtures/osctest/deleted/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_package b/tests/project_package_status_fixtures/osctest/deleted/.osc/_package new file mode 100644 index 0000000..71779d2 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_package @@ -0,0 +1 @@ +deleted diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_project b/tests/project_package_status_fixtures/osctest/deleted/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/_to_be_deleted b/tests/project_package_status_fixtures/osctest/deleted/.osc/_to_be_deleted new file mode 100644 index 0000000..25fff4f --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +modified +test diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/modified b/tests/project_package_status_fixtures/osctest/deleted/.osc/modified new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/modified @@ -0,0 +1 @@ +This file did change. diff --git a/tests/project_package_status_fixtures/osctest/deleted/.osc/test b/tests/project_package_status_fixtures/osctest/deleted/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/deleted/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/excluded/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/_files b/tests/project_package_status_fixtures/osctest/excluded/.osc/_files new file mode 100644 index 0000000..af92bf7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/_files @@ -0,0 +1,4 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="2abd19de6a38ff2890af64f453df96b1" mtime="1282047303" name="modified" size="22" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> +</directory> diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/_osclib_version b/tests/project_package_status_fixtures/osctest/excluded/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/_package b/tests/project_package_status_fixtures/osctest/excluded/.osc/_package new file mode 100644 index 0000000..bbde3dc --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/_package @@ -0,0 +1 @@ +excluded diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/_project b/tests/project_package_status_fixtures/osctest/excluded/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/modified b/tests/project_package_status_fixtures/osctest/excluded/.osc/modified new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/modified @@ -0,0 +1 @@ +This file did change. diff --git a/tests/project_package_status_fixtures/osctest/excluded/.osc/test b/tests/project_package_status_fixtures/osctest/excluded/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/excluded/_linkerror b/tests/project_package_status_fixtures/osctest/excluded/_linkerror new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/_linkerror diff --git a/tests/project_package_status_fixtures/osctest/excluded/dir/file b/tests/project_package_status_fixtures/osctest/excluded/dir/file new file mode 100644 index 0000000..f73f309 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/dir/file @@ -0,0 +1 @@ +file diff --git a/tests/project_package_status_fixtures/osctest/excluded/exists b/tests/project_package_status_fixtures/osctest/excluded/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/exists diff --git a/tests/project_package_status_fixtures/osctest/excluded/foo.orig b/tests/project_package_status_fixtures/osctest/excluded/foo.orig new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/foo.orig diff --git a/tests/project_package_status_fixtures/osctest/excluded/modified b/tests/project_package_status_fixtures/osctest/excluded/modified new file mode 100644 index 0000000..2e09960 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/modified @@ -0,0 +1 @@ +modified diff --git a/tests/project_package_status_fixtures/osctest/excluded/test b/tests/project_package_status_fixtures/osctest/excluded/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/excluded/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_apiurl b/tests/project_package_status_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_files b/tests/project_package_status_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..01e60f4 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,8 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="676513fde5797c3785164942c97dfec1" mtime="1283506309" name="missing" size="8" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="d8e8fca2dc0f896fd7cb4cb0031ba249" mtime="1283505591" name="test" size="5" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="100" skipped="true" /> +</directory> diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_osclib_version b/tests/project_package_status_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_package b/tests/project_package_status_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..ab23474 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_project b/tests/project_package_status_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_added b/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_added new file mode 100644 index 0000000..f499143 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_added @@ -0,0 +1,3 @@ +add +missing +missing_added diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_deleted b/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/foo b/tests/project_package_status_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/merge b/tests/project_package_status_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/missing b/tests/project_package_status_fixtures/osctest/simple/.osc/missing new file mode 100644 index 0000000..33e45d5 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/missing @@ -0,0 +1 @@ +missing diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/nochange b/tests/project_package_status_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/project_package_status_fixtures/osctest/simple/.osc/test b/tests/project_package_status_fixtures/osctest/simple/.osc/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/.osc/test @@ -0,0 +1 @@ +test diff --git a/tests/project_package_status_fixtures/osctest/simple/add b/tests/project_package_status_fixtures/osctest/simple/add new file mode 100644 index 0000000..b242c36 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/add @@ -0,0 +1 @@ +added file diff --git a/tests/project_package_status_fixtures/osctest/simple/exists b/tests/project_package_status_fixtures/osctest/simple/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/exists diff --git a/tests/project_package_status_fixtures/osctest/simple/missing b/tests/project_package_status_fixtures/osctest/simple/missing new file mode 100644 index 0000000..feae347 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/missing @@ -0,0 +1 @@ +replaced diff --git a/tests/project_package_status_fixtures/osctest/simple/nochange b/tests/project_package_status_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..34d6872 --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/nochange @@ -0,0 +1 @@ +This file did change. diff --git a/tests/project_package_status_fixtures/osctest/simple/test b/tests/project_package_status_fixtures/osctest/simple/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/project_package_status_fixtures/osctest/simple/test @@ -0,0 +1 @@ +test diff --git a/tests/repairwc_fixtures/oscrc b/tests/repairwc_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/repairwc_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/repairwc_fixtures/osctest/.osc/_apiurl b/tests/repairwc_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/.osc/_packages b/tests/repairwc_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/repairwc_fixtures/osctest/.osc/_project b/tests/repairwc_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/repairwc_fixtures/osctest/_packages b/tests/repairwc_fixtures/osctest/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_apiurl b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildconfig_prj_arch b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildconfig_prj_arch new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildconfig_prj_arch diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildinfo_prj_arch.xml b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildinfo_prj_arch.xml new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_buildinfo_prj_arch.xml diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_files b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_files new file mode 100644 index 0000000..d8e2ba4 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_files @@ -0,0 +1,5 @@ +<directory name="buildfiles" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_in_conflict new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_in_conflict @@ -0,0 +1 @@ +nochange diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_package b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_package new file mode 100644 index 0000000..8c26334 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_package @@ -0,0 +1 @@ +buildfiles diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_project b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/foo b/tests/repairwc_fixtures/osctest/buildfiles/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/merge b/tests/repairwc_fixtures/osctest/buildfiles/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/buildfiles/.osc/nochange b/tests/repairwc_fixtures/osctest/buildfiles/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/buildfiles/foobar b/tests/repairwc_fixtures/osctest/buildfiles/foobar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/foobar diff --git a/tests/repairwc_fixtures/osctest/buildfiles/merge b/tests/repairwc_fixtures/osctest/buildfiles/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/buildfiles/nochange b/tests/repairwc_fixtures/osctest/buildfiles/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/buildfiles/toadd1 b/tests/repairwc_fixtures/osctest/buildfiles/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/buildfiles/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_apiurl b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_apiurl new file mode 100644 index 0000000..718a28b --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_apiurl @@ -0,0 +1 @@ +urlwithoutprotocolandtld diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_files b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_files new file mode 100644 index 0000000..15245ce --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_files @@ -0,0 +1 @@ +<directory name="invalid_apiurl" rev="1" vrev="1" srcmd5="2738234914de5cc154b1494b1e98d940" /> diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_meta b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_meta new file mode 100644 index 0000000..13c5146 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_meta @@ -0,0 +1,11 @@ +<package project="remote" name="foo"> + <title>Title of New Package</title> + <description> +LONG DESCRIPTION +GOES +HERE + </description> + <person userid="Admin" role="maintainer"/> + <person userid="Admin" role="bugowner"/> + <url>PUT_UPSTREAM_URL_HERE</url> +</package> diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_package b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_package new file mode 100644 index 0000000..2c2226b --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_package @@ -0,0 +1 @@ +invalid_apiurl diff --git a/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_project b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_project new file mode 100644 index 0000000..9c998f7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/invalid_apiurl/.osc/_project @@ -0,0 +1 @@ +remote diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_apiurl b/tests/repairwc_fixtures/osctest/multiple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_files b/tests/repairwc_fixtures/osctest/multiple/.osc/_files new file mode 100644 index 0000000..8a96986 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="multiple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/multiple/.osc/_in_conflict new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_in_conflict @@ -0,0 +1 @@ +nochange diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/multiple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_package b/tests/repairwc_fixtures/osctest/multiple/.osc/_package new file mode 100644 index 0000000..5c4139d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_package @@ -0,0 +1 @@ +multiple diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_project b/tests/repairwc_fixtures/osctest/multiple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_deleted new file mode 100644 index 0000000..300a93b --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +foo +nofilesentry diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/foo b/tests/repairwc_fixtures/osctest/multiple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/multiple/.osc/unknown_file b/tests/repairwc_fixtures/osctest/multiple/.osc/unknown_file new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/.osc/unknown_file diff --git a/tests/repairwc_fixtures/osctest/multiple/foobar b/tests/repairwc_fixtures/osctest/multiple/foobar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/foobar diff --git a/tests/repairwc_fixtures/osctest/multiple/merge b/tests/repairwc_fixtures/osctest/multiple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/multiple/nochange b/tests/repairwc_fixtures/osctest/multiple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/multiple/toadd1 b/tests/repairwc_fixtures/osctest/multiple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/multiple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_files b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_files new file mode 100644 index 0000000..e9158ba --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_files @@ -0,0 +1,5 @@ +<directory name="noapiurl" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_in_conflict new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_in_conflict @@ -0,0 +1 @@ +nochange diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_package b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_package new file mode 100644 index 0000000..14aa43c --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_package @@ -0,0 +1 @@ +noapiurl diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_project b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/foo b/tests/repairwc_fixtures/osctest/noapiurl/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/merge b/tests/repairwc_fixtures/osctest/noapiurl/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/noapiurl/.osc/nochange b/tests/repairwc_fixtures/osctest/noapiurl/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/noapiurl/foobar b/tests/repairwc_fixtures/osctest/noapiurl/foobar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/foobar diff --git a/tests/repairwc_fixtures/osctest/noapiurl/merge b/tests/repairwc_fixtures/osctest/noapiurl/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/noapiurl/nochange b/tests/repairwc_fixtures/osctest/noapiurl/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/noapiurl/toadd1 b/tests/repairwc_fixtures/osctest/noapiurl/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/noapiurl/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_files b/tests/repairwc_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_package b/tests/repairwc_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_project b/tests/repairwc_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/foo b/tests/repairwc_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/merge b/tests/repairwc_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple/.osc/nochange b/tests/repairwc_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple/merge b/tests/repairwc_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple/nochange b/tests/repairwc_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple/toadd1 b/tests/repairwc_fixtures/osctest/simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple/toadd2 b/tests/repairwc_fixtures/osctest/simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple1/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_files b/tests/repairwc_fixtures/osctest/simple1/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple1/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_package b/tests/repairwc_fixtures/osctest/simple1/.osc/_package new file mode 100644 index 0000000..e2464cd --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_package @@ -0,0 +1 @@ +simple1 diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_project b/tests/repairwc_fixtures/osctest/simple1/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple1/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/merge b/tests/repairwc_fixtures/osctest/simple1/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple1/.osc/nochange b/tests/repairwc_fixtures/osctest/simple1/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple1/merge b/tests/repairwc_fixtures/osctest/simple1/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple1/nochange b/tests/repairwc_fixtures/osctest/simple1/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple1/toadd1 b/tests/repairwc_fixtures/osctest/simple1/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple1/toadd2 b/tests/repairwc_fixtures/osctest/simple1/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple1/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple2/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_files b/tests/repairwc_fixtures/osctest/simple2/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple2/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_package b/tests/repairwc_fixtures/osctest/simple2/.osc/_package new file mode 100644 index 0000000..e268fff --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_package @@ -0,0 +1 @@ +simple2 diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_project b/tests/repairwc_fixtures/osctest/simple2/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple2/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/foo b/tests/repairwc_fixtures/osctest/simple2/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/merge b/tests/repairwc_fixtures/osctest/simple2/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/nochange b/tests/repairwc_fixtures/osctest/simple2/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple2/.osc/somefile b/tests/repairwc_fixtures/osctest/simple2/.osc/somefile new file mode 100644 index 0000000..ebf038b --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/.osc/somefile @@ -0,0 +1 @@ +somefile diff --git a/tests/repairwc_fixtures/osctest/simple2/merge b/tests/repairwc_fixtures/osctest/simple2/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple2/nochange b/tests/repairwc_fixtures/osctest/simple2/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple2/toadd1 b/tests/repairwc_fixtures/osctest/simple2/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple2/toadd2 b/tests/repairwc_fixtures/osctest/simple2/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple2/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple3/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_files b/tests/repairwc_fixtures/osctest/simple3/.osc/_files new file mode 100644 index 0000000..0479b49 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple3" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple3/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_package b/tests/repairwc_fixtures/osctest/simple3/.osc/_package new file mode 100644 index 0000000..3d7b9c9 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_package @@ -0,0 +1 @@ +simple3 diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_project b/tests/repairwc_fixtures/osctest/simple3/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_added new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_added @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/foo b/tests/repairwc_fixtures/osctest/simple3/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/merge b/tests/repairwc_fixtures/osctest/simple3/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/nochange b/tests/repairwc_fixtures/osctest/simple3/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple3/.osc/toadd1 b/tests/repairwc_fixtures/osctest/simple3/.osc/toadd1 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/.osc/toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple3/merge b/tests/repairwc_fixtures/osctest/simple3/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple3/nochange b/tests/repairwc_fixtures/osctest/simple3/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple3/toadd1 b/tests/repairwc_fixtures/osctest/simple3/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple3/toadd2 b/tests/repairwc_fixtures/osctest/simple3/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple3/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple4/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_files b/tests/repairwc_fixtures/osctest/simple4/.osc/_files new file mode 100644 index 0000000..9fa8a9f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_files @@ -0,0 +1,5 @@ +<directory name="working_nonempty" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple4/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_package b/tests/repairwc_fixtures/osctest/simple4/.osc/_package new file mode 100644 index 0000000..6ece159 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_package @@ -0,0 +1 @@ +working_nonempty diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_project b/tests/repairwc_fixtures/osctest/simple4/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple4/.osc/_to_be_deleted new file mode 100644 index 0000000..6db8a6f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +foo +remove diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/foo b/tests/repairwc_fixtures/osctest/simple4/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/merge b/tests/repairwc_fixtures/osctest/simple4/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple4/.osc/nochange b/tests/repairwc_fixtures/osctest/simple4/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple4/merge b/tests/repairwc_fixtures/osctest/simple4/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple4/nochange b/tests/repairwc_fixtures/osctest/simple4/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple4/toadd1 b/tests/repairwc_fixtures/osctest/simple4/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple4/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple5/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_files b/tests/repairwc_fixtures/osctest/simple5/.osc/_files new file mode 100644 index 0000000..9fa8a9f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_files @@ -0,0 +1,5 @@ +<directory name="working_nonempty" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/simple5/.osc/_in_conflict new file mode 100644 index 0000000..9b1719f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_in_conflict @@ -0,0 +1 @@ +conflict diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple5/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_package b/tests/repairwc_fixtures/osctest/simple5/.osc/_package new file mode 100644 index 0000000..6ece159 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_package @@ -0,0 +1 @@ +working_nonempty diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_project b/tests/repairwc_fixtures/osctest/simple5/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple5/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/foo b/tests/repairwc_fixtures/osctest/simple5/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/merge b/tests/repairwc_fixtures/osctest/simple5/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple5/.osc/nochange b/tests/repairwc_fixtures/osctest/simple5/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple5/merge b/tests/repairwc_fixtures/osctest/simple5/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple5/nochange b/tests/repairwc_fixtures/osctest/simple5/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple5/toadd1 b/tests/repairwc_fixtures/osctest/simple5/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple5/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple6/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_files b/tests/repairwc_fixtures/osctest/simple6/.osc/_files new file mode 100644 index 0000000..65eb184 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple6" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple6/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_package b/tests/repairwc_fixtures/osctest/simple6/.osc/_package new file mode 100644 index 0000000..29a2746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_package @@ -0,0 +1 @@ +simple6 diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_project b/tests/repairwc_fixtures/osctest/simple6/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/merge b/tests/repairwc_fixtures/osctest/simple6/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple6/.osc/nochange b/tests/repairwc_fixtures/osctest/simple6/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple6/merge b/tests/repairwc_fixtures/osctest/simple6/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple6/nochange b/tests/repairwc_fixtures/osctest/simple6/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple6/toadd1 b/tests/repairwc_fixtures/osctest/simple6/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple6/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple7/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_files b/tests/repairwc_fixtures/osctest/simple7/.osc/_files new file mode 100644 index 0000000..9ae3788 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_files @@ -0,0 +1,6 @@ +<directory name="simple7" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="42" skipped="true" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/simple7/.osc/_in_conflict new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_in_conflict @@ -0,0 +1 @@ +nochange diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple7/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_package b/tests/repairwc_fixtures/osctest/simple7/.osc/_package new file mode 100644 index 0000000..c0cec07 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_package @@ -0,0 +1 @@ +simple7 diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_project b/tests/repairwc_fixtures/osctest/simple7/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/foo b/tests/repairwc_fixtures/osctest/simple7/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/merge b/tests/repairwc_fixtures/osctest/simple7/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple7/.osc/nochange b/tests/repairwc_fixtures/osctest/simple7/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple7/foobar b/tests/repairwc_fixtures/osctest/simple7/foobar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/foobar diff --git a/tests/repairwc_fixtures/osctest/simple7/merge b/tests/repairwc_fixtures/osctest/simple7/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple7/nochange b/tests/repairwc_fixtures/osctest/simple7/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple7/toadd1 b/tests/repairwc_fixtures/osctest/simple7/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple7/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_apiurl b/tests/repairwc_fixtures/osctest/simple8/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_files b/tests/repairwc_fixtures/osctest/simple8/.osc/_files new file mode 100644 index 0000000..cd725ff --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_files @@ -0,0 +1,6 @@ +<directory name="simple8" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="42" skipped="true" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/simple8/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_package b/tests/repairwc_fixtures/osctest/simple8/.osc/_package new file mode 100644 index 0000000..fc76adf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_package @@ -0,0 +1 @@ +simple8 diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_project b/tests/repairwc_fixtures/osctest/simple8/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/foo b/tests/repairwc_fixtures/osctest/simple8/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/merge b/tests/repairwc_fixtures/osctest/simple8/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/nochange b/tests/repairwc_fixtures/osctest/simple8/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/simple8/.osc/skipped b/tests/repairwc_fixtures/osctest/simple8/.osc/skipped new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/.osc/skipped diff --git a/tests/repairwc_fixtures/osctest/simple8/merge b/tests/repairwc_fixtures/osctest/simple8/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/simple8/nochange b/tests/repairwc_fixtures/osctest/simple8/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/simple8/toadd1 b/tests/repairwc_fixtures/osctest/simple8/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/simple8/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/osctest/working_empty/.osc/_apiurl b/tests/repairwc_fixtures/osctest/working_empty/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_empty/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/working_empty/.osc/_files b/tests/repairwc_fixtures/osctest/working_empty/.osc/_files new file mode 100644 index 0000000..9814121 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_empty/.osc/_files @@ -0,0 +1 @@ +<directory /> diff --git a/tests/repairwc_fixtures/osctest/working_empty/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/working_empty/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_empty/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/working_empty/.osc/_package b/tests/repairwc_fixtures/osctest/working_empty/.osc/_package new file mode 100644 index 0000000..4b1dcd1 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_empty/.osc/_package @@ -0,0 +1 @@ +working_empty diff --git a/tests/repairwc_fixtures/osctest/working_empty/.osc/_project b/tests/repairwc_fixtures/osctest/working_empty/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_empty/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_apiurl b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_files b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_files new file mode 100644 index 0000000..9fa8a9f --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_files @@ -0,0 +1,5 @@ +<directory name="working_nonempty" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_in_conflict b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_in_conflict new file mode 100644 index 0000000..55aa746 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_in_conflict @@ -0,0 +1 @@ +nochange diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_osclib_version b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_package b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_package new file mode 100644 index 0000000..6ece159 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_package @@ -0,0 +1 @@ +working_nonempty diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_project b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_added b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_added new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_added @@ -0,0 +1 @@ +foobar diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_deleted b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_deleted new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/_to_be_deleted @@ -0,0 +1 @@ +foo diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/foo b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/merge b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/.osc/nochange b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/foobar b/tests/repairwc_fixtures/osctest/working_nonempty/foobar new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/foobar diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/merge b/tests/repairwc_fixtures/osctest/working_nonempty/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/nochange b/tests/repairwc_fixtures/osctest/working_nonempty/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/repairwc_fixtures/osctest/working_nonempty/toadd1 b/tests/repairwc_fixtures/osctest/working_nonempty/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/repairwc_fixtures/osctest/working_nonempty/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_apiurl b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_apiurl new file mode 100644 index 0000000..c2401e4 --- /dev/null +++ b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_apiurl @@ -0,0 +1 @@ +noschemeandnotld diff --git a/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_packages b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_packages new file mode 100644 index 0000000..9b61f30 --- /dev/null +++ b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_packages @@ -0,0 +1 @@ +<project name="prj_noapiurl" /> diff --git a/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_project b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_project new file mode 100644 index 0000000..d3dc1c2 --- /dev/null +++ b/tests/repairwc_fixtures/prj_invalidapiurl/.osc/_project @@ -0,0 +1 @@ +prj_invalidapiurl diff --git a/tests/repairwc_fixtures/prj_noapiurl/.osc/_packages b/tests/repairwc_fixtures/prj_noapiurl/.osc/_packages new file mode 100644 index 0000000..9b61f30 --- /dev/null +++ b/tests/repairwc_fixtures/prj_noapiurl/.osc/_packages @@ -0,0 +1 @@ +<project name="prj_noapiurl" /> diff --git a/tests/repairwc_fixtures/prj_noapiurl/.osc/_project b/tests/repairwc_fixtures/prj_noapiurl/.osc/_project new file mode 100644 index 0000000..08c78d8 --- /dev/null +++ b/tests/repairwc_fixtures/prj_noapiurl/.osc/_project @@ -0,0 +1 @@ +prj_noapiurl diff --git a/tests/request_fixtures/oscrc b/tests/request_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/request_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/request_fixtures/test_read_request1.xml b/tests/request_fixtures/test_read_request1.xml new file mode 100644 index 0000000..af69950 --- /dev/null +++ b/tests/request_fixtures/test_read_request1.xml @@ -0,0 +1,18 @@ +<request id="42"> + <action type="submit"> + <source package="bar" project="foo" rev="1" /> + <target package="bar" project="foobar" /> + </action> + <action type="delete"> + <target project="deleteme" /> + </action> + <state name="accepted" when="2010-12-27T01:36:29" who="user1" /> + <history when="2010-12-13T13:02:03" who="creator"> + <description>Create Request</description> + <comment>foobar</comment> + </history> + <title>title of the request</title> + <description>this is a +very long +description</description> +</request> diff --git a/tests/request_fixtures/test_read_request2.xml b/tests/request_fixtures/test_read_request2.xml new file mode 100644 index 0000000..2916955 --- /dev/null +++ b/tests/request_fixtures/test_read_request2.xml @@ -0,0 +1,21 @@ +<request id="123"> + <action type="submit"> + <source package="abc" project="xyz" /> + <options> + <sourceupdate>cleanup</sourceupdate> + <updatelink>1</updatelink> + </options> + </action> + <action type="add_role"> + <target project="home:foo" /> + <person name="bar" role="maintainer" /> + <group name="groupxyz" role="reader" /> + </action> + <state name="review" when="2010-12-27T01:36:29" who="abc" /> + <review by_group="group1" state="new" when="2010-12-28T00:11:22" who="abc"> + <comment>review start</comment> + </review> + <history when="2010-12-11T00:00:00" who="creator"> + <description>Created request</description> + </history> +</request> diff --git a/tests/request_fixtures/test_request_list_view1.xml b/tests/request_fixtures/test_request_list_view1.xml new file mode 100644 index 0000000..a0117b2 --- /dev/null +++ b/tests/request_fixtures/test_request_list_view1.xml @@ -0,0 +1,36 @@ +<request id="62"> + <action type="set_bugowner"> + <target project="foo" /> + <person name="buguser" /> + </action> + <action type="add_role"> + <target project="foobar" /> + <person name="xyz" role="maintainer" /> + <group name="group1" role="reader" /> + </action> + <action type="add_role"> + <target project="foo" package="bar" /> + <person name="abc" role="reviewer" /> + </action> + <action type="change_devel"> + <source project="devprj" package="devpkg" /> + <target project="foo" package="bar" /> + </action> + <action type="submit"> + <source project="srcprj" package="srcpackage" /> + <target project="tgtprj" package="tgtpackage" /> + </action> + <action type="submit"> + <source project="foo" package="bar" /> + <target project="baz" /> + </action> + <action type="delete"> + <target project="deleteme" /> + </action> + <action type="delete"> + <target project="foo" package="bar" /> + </action> + <state name="new" when="2010-12-29T14:57:25" who="Admin"> + <comment></comment> + </state> +</request> diff --git a/tests/request_fixtures/test_request_list_view2.xml b/tests/request_fixtures/test_request_list_view2.xml new file mode 100644 index 0000000..ae9213a --- /dev/null +++ b/tests/request_fixtures/test_request_list_view2.xml @@ -0,0 +1,18 @@ +<request id="21"> + <action type="set_bugowner"> + <target project="foo" /> + <person name="buguser" /> + </action> + <state name="accepted" when="2010-12-29T16:37:45" who="foobar" /> + <history when="2010-12-28T16:37:45" who="user" > + <description>Created Request</description> + </history> + <history when="2010-12-28T18:37:45" who="foobar" > + <description>Review Approved</description> + </history> + <description>This is +a simple request with a lot of ... ... text and other stuff. This request also contains a +description. This is useful to +describe the request. blabla +blabla</description> +</request> diff --git a/tests/request_fixtures/test_request_str1.xml b/tests/request_fixtures/test_request_str1.xml new file mode 100644 index 0000000..ee5aa9b --- /dev/null +++ b/tests/request_fixtures/test_request_str1.xml @@ -0,0 +1,30 @@ +<request id="123"> + <action type="submit"> + <source package="abc" project="xyz" /> + <target project="foo" /> + <options> + <sourceupdate>cleanup</sourceupdate> + <updatelink>1</updatelink> + </options> + </action> + <action type="add_role"> + <target project="home:foo" /> + <person name="bar" role="maintainer" /> + <group name="groupxyz" role="reader" /> + </action> + <state name="review" when="2010-12-27T01:36:29" who="abc"> + <comment>currently in review</comment> + </state> + <review by_group="group1" state="new" when="2010-12-28T00:11:22" who="abc"> + <comment>review start</comment> + </review> + <review by_group="group1" state="accepted" when="2010-12-29T00:11:22" who="abc"> + <comment>accepted</comment> + </review> + <history name="new" when="2010-12-11T00:00:00" who="creator" /> + <history name="revoked" when="2010-12-12T00:00:00" who="creator" /> + <description>just a samll description +in order to describe this +request - blablabla +test.</description> +</request> diff --git a/tests/revertfile_fixtures/oscrc b/tests/revertfile_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/revertfile_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/revertfile_fixtures/osctest/.osc/_apiurl b/tests/revertfile_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/revertfile_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/revertfile_fixtures/osctest/.osc/_packages b/tests/revertfile_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/revertfile_fixtures/osctest/.osc/_project b/tests/revertfile_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_apiurl b/tests/revertfile_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_files b/tests/revertfile_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..5dbd576 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,10 @@ +<directory name="conflict" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="eb9c2bf0eb63f3a7bc0ea37ef18aeba5" mtime="1282730880" name="somefile" size="13" /> + <entry md5="81be947db54c2e225dc8eacce64d8a4a" mtime="1282731457" name="replaced" size="17" /> + <entry md5="676513fde5797c3785164942c97dfec1" mtime="1282731738" name="missing" size="8" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="deleted" size="9" /> + <entry md5="ffffffffffffffffffffffffffffffff" mtime="1111111111" name="skipped" size="12" skipped="true" /> +</directory> diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_in_conflict b/tests/revertfile_fixtures/osctest/simple/.osc/_in_conflict new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_in_conflict @@ -0,0 +1 @@ +foo diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_osclib_version b/tests/revertfile_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_package b/tests/revertfile_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_project b/tests/revertfile_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_added b/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_added new file mode 100644 index 0000000..1f4923c --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_added @@ -0,0 +1,3 @@ +toadd1 +replaced +addedmissing diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_deleted b/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_deleted new file mode 100644 index 0000000..08f95e5 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +somefile +deleted diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/deleted b/tests/revertfile_fixtures/osctest/simple/.osc/deleted new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/deleted diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/foo b/tests/revertfile_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/merge b/tests/revertfile_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/missing b/tests/revertfile_fixtures/osctest/simple/.osc/missing new file mode 100644 index 0000000..33e45d5 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/missing @@ -0,0 +1 @@ +missing diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/nochange b/tests/revertfile_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/replaced b/tests/revertfile_fixtures/osctest/simple/.osc/replaced new file mode 100644 index 0000000..7c3f1a8 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/replaced @@ -0,0 +1 @@ +yet another file diff --git a/tests/revertfile_fixtures/osctest/simple/.osc/somefile b/tests/revertfile_fixtures/osctest/simple/.osc/somefile new file mode 100644 index 0000000..2ef267e --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/.osc/somefile @@ -0,0 +1 @@ +some content diff --git a/tests/revertfile_fixtures/osctest/simple/foo b/tests/revertfile_fixtures/osctest/simple/foo new file mode 100644 index 0000000..ad9621d --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/foo @@ -0,0 +1,5 @@ +<<<<<<< foo.mine +This is no test. +======= +This is a simple test. +>>>>>>> foo.r2 diff --git a/tests/revertfile_fixtures/osctest/simple/merge b/tests/revertfile_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/revertfile_fixtures/osctest/simple/nochange b/tests/revertfile_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/revertfile_fixtures/osctest/simple/replaced b/tests/revertfile_fixtures/osctest/simple/replaced new file mode 100644 index 0000000..f479fb8 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/replaced @@ -0,0 +1 @@ +foo replaced diff --git a/tests/revertfile_fixtures/osctest/simple/toadd1 b/tests/revertfile_fixtures/osctest/simple/toadd1 new file mode 100644 index 0000000..1592423 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/toadd1 @@ -0,0 +1 @@ +toadd1 diff --git a/tests/revertfile_fixtures/osctest/simple/toadd2 b/tests/revertfile_fixtures/osctest/simple/toadd2 new file mode 100644 index 0000000..6f1ab97 --- /dev/null +++ b/tests/revertfile_fixtures/osctest/simple/toadd2 @@ -0,0 +1 @@ +toadd2 diff --git a/tests/setlinkrev_fixtures/expandedsrc_filesremote b/tests/setlinkrev_fixtures/expandedsrc_filesremote new file mode 100644 index 0000000..ad4d2a6 --- /dev/null +++ b/tests/setlinkrev_fixtures/expandedsrc_filesremote @@ -0,0 +1,5 @@ +<directory name="srcpkg" rev="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" srcmd5="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" vrev="1"> + <linkinfo project="srcsrcprj" package="srcsrcpkg" srcmd5="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" baserev="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" xsrcmd5="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" lsrcmd5="cccccccccccccccccccccccccccccccc" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="_link" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> +</directory> diff --git a/tests/setlinkrev_fixtures/link_with_rev b/tests/setlinkrev_fixtures/link_with_rev new file mode 100644 index 0000000..877aa3f --- /dev/null +++ b/tests/setlinkrev_fixtures/link_with_rev @@ -0,0 +1 @@ +<link package="srcpkg" project="srcprj" rev="7" /> diff --git a/tests/setlinkrev_fixtures/md5_rev_link b/tests/setlinkrev_fixtures/md5_rev_link new file mode 100644 index 0000000..3725d01 --- /dev/null +++ b/tests/setlinkrev_fixtures/md5_rev_link @@ -0,0 +1 @@ +<link package="srcpkg" project="srcprj" rev="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" vrev="1" /> diff --git a/tests/setlinkrev_fixtures/noproject_link b/tests/setlinkrev_fixtures/noproject_link new file mode 100644 index 0000000..9f45c4f --- /dev/null +++ b/tests/setlinkrev_fixtures/noproject_link @@ -0,0 +1 @@ +<link package="srcpkg" /> diff --git a/tests/setlinkrev_fixtures/oscrc b/tests/setlinkrev_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/setlinkrev_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/setlinkrev_fixtures/rev_link b/tests/setlinkrev_fixtures/rev_link new file mode 100644 index 0000000..1c16c99 --- /dev/null +++ b/tests/setlinkrev_fixtures/rev_link @@ -0,0 +1 @@ +<link package="srcpkg" project="srcprj" rev="42" /> diff --git a/tests/setlinkrev_fixtures/simple_filesremote b/tests/setlinkrev_fixtures/simple_filesremote new file mode 100644 index 0000000..0e5319f --- /dev/null +++ b/tests/setlinkrev_fixtures/simple_filesremote @@ -0,0 +1,4 @@ +<directory name="srcpkg" rev="42" srcmd5="ffffffffffffffffffffffffffffffff" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> +</directory> diff --git a/tests/setlinkrev_fixtures/simple_link b/tests/setlinkrev_fixtures/simple_link new file mode 100644 index 0000000..20db65a --- /dev/null +++ b/tests/setlinkrev_fixtures/simple_link @@ -0,0 +1 @@ +<link package="srcpkg" project="srcprj" /> diff --git a/tests/suite.py b/tests/suite.py new file mode 100644 index 0000000..0373e24 --- /dev/null +++ b/tests/suite.py @@ -0,0 +1,48 @@ +import os.path +import sys +import unittest + +try: + import xmlrunner # JUnit like XML reporting + have_xmlrunner = True +except ImportError: + have_xmlrunner = False + +import test_update +import test_addfiles +import test_deletefiles +import test_revertfiles +import test_difffiles +import test_init_package +import test_init_project +import test_commit +import test_repairwc +import test_package_status +import test_project_status +import test_request +import test_setlinkrev +import test_prdiff +import test_conf + +suite = unittest.TestSuite() +suite.addTests(test_addfiles.suite()) +suite.addTests(test_deletefiles.suite()) +suite.addTests(test_revertfiles.suite()) +suite.addTests(test_update.suite()) +suite.addTests(test_difffiles.suite()) +suite.addTests(test_init_package.suite()) +suite.addTests(test_init_project.suite()) +suite.addTests(test_commit.suite()) +suite.addTests(test_repairwc.suite()) +suite.addTests(test_package_status.suite()) +suite.addTests(test_project_status.suite()) +suite.addTests(test_request.suite()) +suite.addTests(test_setlinkrev.suite()) +suite.addTests(test_prdiff.suite()) +suite.addTests(test_conf.suite()) + +if have_xmlrunner: + result = xmlrunner.XMLTestRunner(output=os.path.join(os.getcwd(), 'junit-xml-results')).run(suite) +else: + result = unittest.TextTestRunner(verbosity=1).run(suite) +sys.exit(not result.wasSuccessful()) diff --git a/tests/test_addfiles.py b/tests/test_addfiles.py new file mode 100644 index 0000000..129bc4d --- /dev/null +++ b/tests/test_addfiles.py @@ -0,0 +1,85 @@ +import osc.core +import osc.oscerr +import os +import sys +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'addfile_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestAddFiles) + +class TestAddFiles(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def testSimpleAdd(self): + """add one file ('toadd1') to the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.addfile('toadd1') + exp = 'A toadd1\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self._check_status(p, 'toadd1', 'A') + self._check_addlist('toadd1\n') + + def testSimpleMultipleAdd(self): + """add multiple files ('toadd1', 'toadd2') to the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.addfile('toadd1') + p.addfile('toadd2') + exp = 'A toadd1\nA toadd2\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd2'))) + self._check_status(p, 'toadd1', 'A') + self._check_status(p, 'toadd2', 'A') + self._check_addlist('toadd1\ntoadd2\n') + + def testAddVersionedFile(self): + """add a versioned file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self.assertRaises(osc.oscerr.PackageFileConflict, p.addfile, 'merge') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self._check_status(p, 'merge', ' ') + + def testAddUnversionedFileTwice(self): + """add the same file twice""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.addfile('toadd1') + self.assertRaises(osc.oscerr.PackageFileConflict, p.addfile, 'toadd1') + exp = 'A toadd1\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self._check_status(p, 'toadd1', 'A') + self._check_addlist('toadd1\n') + + def testReplace(self): + """replace a deleted file ('foo')""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + open('foo', 'w').write('replaced file\n') + p.addfile('foo') + exp = 'A foo\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertNotEqual(open(os.path.join('.osc', 'foo'), 'r').read(), 'replaced file\n') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_status(p, 'foo', 'R') + self._check_addlist('foo\n') + + def testAddNonExistentFile(self): + """add a non existent file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self.assertRaises(osc.oscerr.OscIOError, p.addfile, 'doesnotexist') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_commit.py b/tests/test_commit.py new file mode 100644 index 0000000..02baa85 --- /dev/null +++ b/tests/test_commit.py @@ -0,0 +1,317 @@ +import osc.core +import osc.oscerr +import os +import sys +from common import GET, PUT, POST, DELETE, OscTestCase +from xml.etree import cElementTree as ET +try: + from urllib.error import HTTPError +except ImportError: + #python 2.x + from urllib2 import HTTPError + +FIXTURES_DIR = os.path.join(os.getcwd(), 'commit_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestCommit) + +rev_dummy = '<revision rev="repository">\n <srcmd5>empty</srcmd5>\n</revision>' + +class TestCommit(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + @GET('http://localhost/source/osctest/simple?rev=latest', file='testSimple_filesremote') + @POST('http://localhost/source/osctest/simple?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/simple?comment=&cmd=commitfilelist&user=Admin', + file='testSimple_missingfilelist', expfile='testSimple_lfilelist') + @PUT('http://localhost/source/osctest/simple/nochange?rev=repository', + exp='This file didn\'t change but\nis modified.\n', text=rev_dummy) + @POST('http://localhost/source/osctest/simple?comment=&cmd=commitfilelist&user=Admin', + file='testSimple_cfilesremote', expfile='testSimple_lfilelist') + def test_simple(self): + """a simple commit (only one modified file)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.commit() + exp = 'Sending nochange\nTransmitting file data .\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testSimple_cfilesremote') + self.assertTrue(os.path.exists('nochange')) + self.assertEqual(open('nochange', 'r').read(), open(os.path.join('.osc', 'nochange'), 'r').read()) + self._check_status(p, 'nochange', ' ') + self._check_status(p, 'foo', ' ') + self._check_status(p, 'merge', ' ') + + @GET('http://localhost/source/osctest/add?rev=latest', file='testAddfile_filesremote') + @POST('http://localhost/source/osctest/add?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/add?comment=&cmd=commitfilelist&user=Admin', + file='testAddfile_missingfilelist', expfile='testAddfile_lfilelist') + @PUT('http://localhost/source/osctest/add/add?rev=repository', + exp='added file\n', text=rev_dummy) + @POST('http://localhost/source/osctest/add?comment=&cmd=commitfilelist&user=Admin', + file='testAddfile_cfilesremote', expfile='testAddfile_lfilelist') + def test_addfile(self): + """commit a new file""" + self._change_to_pkg('add') + p = osc.core.Package('.') + p.commit() + exp = 'Sending add\nTransmitting file data .\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testAddfile_cfilesremote') + self.assertTrue(os.path.exists('add')) + self.assertEqual(open('add', 'r').read(), open(os.path.join('.osc', 'add'), 'r').read()) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self._check_status(p, 'add', ' ') + self._check_status(p, 'foo', ' ') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'nochange', ' ') + + @GET('http://localhost/source/osctest/delete?rev=latest', file='testDeletefile_filesremote') + @POST('http://localhost/source/osctest/delete?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/delete?comment=&cmd=commitfilelist&user=Admin', + file='testDeletefile_cfilesremote', expfile='testDeletefile_lfilelist') + def test_deletefile(self): + """delete a file""" + self._change_to_pkg('delete') + p = osc.core.Package('.') + p.commit() + exp = 'Deleting nochange\nTransmitting file data \nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testDeletefile_cfilesremote') + self.assertFalse(os.path.exists('nochange')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'nochange'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_status(p, 'foo', ' ') + self._check_status(p, 'merge', ' ') + + @GET('http://localhost/source/osctest/conflict?rev=latest', file='testConflictfile_filesremote') + @POST('http://localhost/source/osctest/conflict?cmd=getprojectservices', + exp='', text='<services />') + def test_conflictfile(self): + """package has a file which is in conflict state""" + self._change_to_pkg('conflict') + ret = osc.core.Package('.').commit() + self.assertTrue(ret == 1) + exp = 'Please resolve all conflicts before committing using "osc resolved FILE"!\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testConflictfile_filesremote') + self._check_conflictlist('merge\n') + + @GET('http://localhost/source/osctest/nochanges?rev=latest', file='testNoChanges_filesremote') + @POST('http://localhost/source/osctest/nochanges?cmd=getprojectservices', + exp='', text='<services />') + def test_nochanges(self): + """package has no changes (which can be committed)""" + self._change_to_pkg('nochanges') + p = osc.core.Package('.') + ret = p.commit() + self.assertTrue(ret == 1) + exp = 'nothing to do for package nochanges\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_status(p, 'foo', 'S') + self._check_status(p, 'merge', '!') + self._check_status(p, 'nochange', ' ') + + @GET('http://localhost/source/osctest/multiple?rev=latest', file='testMultiple_filesremote') + @POST('http://localhost/source/osctest/multiple?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/multiple?comment=&cmd=commitfilelist&user=Admin', + file='testMultiple_missingfilelist', expfile='testMultiple_lfilelist') + @PUT('http://localhost/source/osctest/multiple/nochange?rev=repository', exp='This file did change.\n', text=rev_dummy) + @PUT('http://localhost/source/osctest/multiple/add?rev=repository', exp='added file\n', text=rev_dummy) + @PUT('http://localhost/source/osctest/multiple/add2?rev=repository', exp='add2\n', text=rev_dummy) + @POST('http://localhost/source/osctest/multiple?comment=&cmd=commitfilelist&user=Admin', + file='testMultiple_cfilesremote', expfile='testMultiple_lfilelist') + def test_multiple(self): + """a simple commit (only one modified file)""" + self._change_to_pkg('multiple') + p = osc.core.Package('.') + p.commit() + exp = 'Deleting foo\nDeleting merge\nSending nochange\n' \ + 'Sending add\nSending add2\nTransmitting file data ...\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testMultiple_cfilesremote') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertRaises(osc.oscerr.OscIOError, p.status, 'foo') + self.assertRaises(osc.oscerr.OscIOError, p.status, 'merge') + self._check_status(p, 'add', ' ') + self._check_status(p, 'add2', ' ') + self._check_status(p, 'nochange', ' ') + + @GET('http://localhost/source/osctest/multiple?rev=latest', file='testPartial_filesremote') + @POST('http://localhost/source/osctest/multiple?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/multiple?comment=&cmd=commitfilelist&user=Admin', + file='testPartial_missingfilelist', expfile='testPartial_lfilelist') + @PUT('http://localhost/source/osctest/multiple/add?rev=repository', exp='added file\n', text=rev_dummy) + @PUT('http://localhost/source/osctest/multiple/nochange?rev=repository', exp='This file did change.\n', text=rev_dummy) + @POST('http://localhost/source/osctest/multiple?comment=&cmd=commitfilelist&user=Admin', + file='testPartial_cfilesremote', expfile='testPartial_lfilelist') + def test_partial(self): + """commit only some files""" + self._change_to_pkg('multiple') + p = osc.core.Package('.') + p.todo = ['foo', 'add', 'nochange'] + p.commit() + exp = 'Deleting foo\nSending nochange\n' \ + 'Sending add\nTransmitting file data ..\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testPartial_cfilesremote') + self._check_addlist('add2\n') + self._check_deletelist('merge\n') + self._check_status(p, 'add2', 'A') + self._check_status(p, 'merge', 'D') + self._check_status(p, 'add', ' ') + self._check_status(p, 'nochange', ' ') + self.assertRaises(osc.oscerr.OscIOError, p.status, 'foo') + + @GET('http://localhost/source/osctest/simple?rev=latest', file='testSimple_filesremote') + @POST('http://localhost/source/osctest/simple?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/simple?comment=&cmd=commitfilelist&user=Admin', + file='testSimple_missingfilelist', expfile='testSimple_lfilelist') + @PUT('http://localhost/source/osctest/simple/nochange?rev=repository', exp='This file didn\'t change but\nis modified.\n', + exception=IOError('test exception'), text=rev_dummy) + def test_interrupt(self): + """interrupt a commit""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self.assertRaises(IOError, p.commit) + exp = 'Sending nochange\nTransmitting file data .' + self.assertTrue(sys.stdout.getvalue(), exp) + self._check_digests('testSimple_filesremote') + self.assertTrue(os.path.exists('nochange')) + self._check_status(p, 'nochange', 'M') + + @GET('http://localhost/source/osctest/allstates?rev=latest', file='testPartial_filesremote') + @POST('http://localhost/source/osctest/allstates?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/allstates?comment=&cmd=commitfilelist&user=Admin', + file='testAllStates_missingfilelist', expfile='testAllStates_lfilelist') + @PUT('http://localhost/source/osctest/allstates/add?rev=repository', exp='added file\n', text=rev_dummy) + @PUT('http://localhost/source/osctest/allstates/missing?rev=repository', exp='replaced\n', text=rev_dummy) + @PUT('http://localhost/source/osctest/allstates/nochange?rev=repository', exp='This file did change.\n', text=rev_dummy) + @POST('http://localhost/source/osctest/allstates?comment=&cmd=commitfilelist&user=Admin', + file='testAllStates_cfilesremote', expfile='testAllStates_lfilelist') + def test_allstates(self): + """commit all files (all states are available except 'C')""" + self._change_to_pkg('allstates') + p = osc.core.Package('.') + p.commit() + exp = 'Deleting foo\nSending missing\nSending nochange\n' \ + 'Sending add\nTransmitting file data ...\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testAllStates_expfiles', 'skipped') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self.assertFalse(os.path.exists('foo')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_status(p, 'add', ' ') + self._check_status(p, 'nochange', ' ') + self._check_status(p, 'merge', '!') + self._check_status(p, 'missing', ' ') + self._check_status(p, 'skipped', 'S') + self._check_status(p, 'test', ' ') + + @GET('http://localhost/source/osctest/add?rev=latest', file='testAddfile_filesremote') + @POST('http://localhost/source/osctest/add?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/add?comment=&cmd=commitfilelist&user=Admin', + file='testAddfile_cfilesremote', expfile='testAddfile_lfilelist') + def test_remoteexists(self): + """file 'add' should be committed but already exists on the server""" + self._change_to_pkg('add') + p = osc.core.Package('.') + p.commit() + exp = 'Sending add\nTransmitting file data \nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testAddfile_cfilesremote') + self.assertTrue(os.path.exists('add')) + self.assertEqual(open('add', 'r').read(), open(os.path.join('.osc', 'add'), 'r').read()) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self._check_status(p, 'add', ' ') + self._check_status(p, 'foo', ' ') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'nochange', ' ') + + @GET('http://localhost/source/osctest/branch?rev=latest', file='testExpand_filesremote') + @POST('http://localhost/source/osctest/branch?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/branch?comment=&cmd=commitfilelist&user=Admin&keeplink=1', + file='testExpand_missingfilelist', expfile='testExpand_lfilelist') + @PUT('http://localhost/source/osctest/branch/simple?rev=repository', exp='simple modified file.\n', text=rev_dummy) + @POST('http://localhost/source/osctest/branch?comment=&cmd=commitfilelist&user=Admin&keeplink=1', + file='testExpand_cfilesremote', expfile='testExpand_lfilelist') + @GET('http://localhost/source/osctest/branch?rev=87ea02aede261b0267aabaa97c756e7a', file='testExpand_expandedfilesremote') + def test_expand(self): + """commit an expanded package""" + self._change_to_pkg('branch') + p = osc.core.Package('.') + p.commit() + exp = 'Sending simple\nTransmitting file data .\nCommitted revision 7.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testExpand_expandedfilesremote') + self._check_status(p, 'simple', ' ') + + @GET('http://localhost/source/osctest/added_missing?rev=latest', file='testAddedMissing_filesremote') + @POST('http://localhost/source/osctest/added_missing?cmd=getprojectservices', + exp='', text='<services />') + def test_added_missing(self): + """commit an added file which is missing""" + self._change_to_pkg('added_missing') + p = osc.core.Package('.') + ret = p.commit() + self.assertTrue(ret == 1) + exp = 'file \'add\' is marked as \'A\' but does not exist\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_status(p, 'add', '!') + + @GET('http://localhost/source/osctest/added_missing?rev=latest', file='testAddedMissing_filesremote') + @POST('http://localhost/source/osctest/added_missing?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/added_missing?comment=&cmd=commitfilelist&user=Admin', + file='testAddedMissing_missingfilelist', expfile='testAddedMissing_lfilelist') + @PUT('http://localhost/source/osctest/added_missing/bar?rev=repository', exp='foobar\n', text=rev_dummy) + @POST('http://localhost/source/osctest/added_missing?comment=&cmd=commitfilelist&user=Admin', + file='testAddedMissing_cfilesremote', expfile='testAddedMissing_lfilelist') + def test_added_missing2(self): + """commit an added file, another added file missing (but it's not part of the commit)""" + self._change_to_pkg('added_missing') + p = osc.core.Package('.') + p.todo = ['bar'] + p.commit() + exp = 'Sending bar\nTransmitting file data .\nCommitted revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_status(p, 'add', '!') + self._check_status(p, 'bar', ' ') + + @GET('http://localhost/source/osctest/simple?rev=latest', file='testSimple_filesremote') + @POST('http://localhost/source/osctest/simple?cmd=getprojectservices', + exp='', text='<services />') + @POST('http://localhost/source/osctest/simple?comment=&cmd=commitfilelist&user=Admin', + file='testSimple_missingfilelist', expfile='testSimple_lfilelist') + @PUT('http://localhost/source/osctest/simple/nochange?rev=repository', + exp='This file didn\'t change but\nis modified.\n', text=rev_dummy) + @POST('http://localhost/source/osctest/simple?comment=&cmd=commitfilelist&user=Admin', + expfile='testSimple_lfilelist', text='an error occured', code=500) + def test_commitfilelist_error(self): + """commit modified file but when committing the filelist the server returns status 500 (see ticket #65)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self._check_status(p, 'nochange', 'M') + self.assertRaises(HTTPError, p.commit) + exp = 'Sending nochange\nTransmitting file data .' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_status(p, 'nochange', 'M') + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_conf.py b/tests/test_conf.py new file mode 100644 index 0000000..86fb3e3 --- /dev/null +++ b/tests/test_conf.py @@ -0,0 +1,32 @@ +from osc.conf import passx_encode, passx_decode +from common import OscTestCase + +import os + +FIXTURES_DIR = os.path.join(os.getcwd(), 'conf_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestConf) + +class TestConf(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def setUp(self): + return super(TestConf, self).setUp(copytree=False) + + def testPassxEncodeDecode(self): + + passwd = "J0e'sPassword!@#" + passx = passx_encode(passwd) + #base64.b64encode(passwd.encode('bz2')) + passx27 = "QlpoOTFBWSZTWaDg4dQAAAKfgCiAQABAEEAAJgCYgCAAMQAACEyYmTyei67AsYSDSaLuSKcKEhQcHDqA" + + self.assertEqual(passwd, passx_decode(passx)) + self.assertEqual(passwd, passx_decode(passx27)) + self.assertEqual(passx, passx27) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_deletefiles.py b/tests/test_deletefiles.py new file mode 100644 index 0000000..8b38c36 --- /dev/null +++ b/tests/test_deletefiles.py @@ -0,0 +1,207 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'deletefile_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestDeleteFiles) + +class TestDeleteFiles(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def testSimpleRemove(self): + """delete a file ('foo') from the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('foo') + self.__check_ret(ret, True, ' ') + self.assertFalse(os.path.exists('foo')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + + def testDeleteModified(self): + """delete modified file ('nochange') from the wc (without force)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('nochange') + self.__check_ret(ret, False, 'M') + self.assertTrue(os.path.exists('nochange')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'nochange'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_status(p, 'nochange', 'M') + + def testDeleteUnversioned(self): + """delete an unversioned file ('toadd2') from the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('toadd2') + self.__check_ret(ret, False, '?') + self.assertTrue(os.path.exists('toadd2')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_status(p, 'toadd2', '?') + + def testDeleteAdded(self): + """delete an added file ('toadd1') from the wc (without force)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('toadd1') + self.__check_ret(ret, False, 'A') + self.assertTrue(os.path.exists('toadd1')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_status(p, 'toadd1', 'A') + + def testDeleteReplaced(self): + """delete an added file ('merge') from the wc (without force)""" + self._change_to_pkg('replace') + p = osc.core.Package('.') + ret = p.delete_file('merge') + self.__check_ret(ret, False, 'R') + self.assertTrue(os.path.exists('merge')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_addlist('toadd1\nmerge\n') + self._check_status(p, 'merge', 'R') + + def testDeleteConflict(self): + """delete a file ('foo', state='C') from the wc (without force)""" + self._change_to_pkg('conflict') + p = osc.core.Package('.') + ret = p.delete_file('foo') + self.__check_ret(ret, False, 'C') + self.assertTrue(os.path.exists('foo')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self._check_conflictlist('foo\n') + self._check_status(p, 'foo', 'C') + + def testDeleteModifiedForce(self): + """force deletion modified file ('nochange') from wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('nochange', force=True) + self.__check_ret(ret, True, 'M') + self.assertFalse(os.path.exists('nochange')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'nochange'))) + self._check_deletelist('nochange\n') + self._check_status(p, 'nochange', 'D') + + def testDeleteUnversionedForce(self): + """delete an unversioned file ('toadd2') from the wc (with force)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('toadd2', force=True) + self.__check_ret(ret, True, '?') + self.assertFalse(os.path.exists('toadd2')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self.assertRaises(osc.oscerr.OscIOError, p.status, 'toadd2') + + def testDeleteAddedForce(self): + """delete an added file ('toadd1') from the wc (with force)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('toadd1', force=True) + self.__check_ret(ret, True, 'A') + self.assertFalse(os.path.exists('toadd1')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + self.assertRaises(osc.oscerr.OscIOError, p.status, 'toadd1') + + def testDeleteReplacedForce(self): + """delete an added file ('merge') from the wc (with force)""" + self._change_to_pkg('replace') + p = osc.core.Package('.') + ret = p.delete_file('merge', force=True) + self.__check_ret(ret, True, 'R') + self.assertFalse(os.path.exists('merge')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'merge'))) + self._check_deletelist('merge\n') + self._check_addlist('toadd1\n') + self._check_status(p, 'merge', 'D') + + def testDeleteConflictForce(self): + """delete a file ('foo', state='C') from the wc (with force)""" + self._change_to_pkg('conflict') + p = osc.core.Package('.') + ret = p.delete_file('foo', force=True) + self.__check_ret(ret, True, 'C') + self.assertFalse(os.path.exists('foo')) + self.assertTrue(os.path.exists('foo.r2')) + self.assertTrue(os.path.exists('foo.mine')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_deletelist('foo\n') + self.assertFalse(os.path.exists(os.path.join('.osc', '_in_conflict'))) + self._check_status(p, 'foo', 'D') + + def testDeleteMultiple(self): + """delete mutliple files from the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('foo') + self.__check_ret(ret, True, ' ') + ret = p.delete_file('merge') + self.__check_ret(ret, True, ' ') + self.assertFalse(os.path.exists('foo')) + self.assertFalse(os.path.exists('merge')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertTrue(os.path.exists(os.path.join('.osc', 'merge'))) + self._check_deletelist('foo\nmerge\n') + + def testDeleteAlreadyDeleted(self): + """delete already deleted file from the wc""" + self._change_to_pkg('already_deleted') + p = osc.core.Package('.') + ret = p.delete_file('foo') + self.__check_ret(ret, True, 'D') + self.assertFalse(os.path.exists('foo')) + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + + def testDeleteAddedMissing(self): + """ + delete a file which was added to the wc and is removed again + (via a non osc command). It's current state is '!' + """ + self._change_to_pkg('delete') + p = osc.core.Package('.') + ret = p.delete_file('toadd1') + self.__check_ret(ret, True, '!') + self.assertFalse(os.path.exists('toadd1')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self._check_deletelist('foo\n') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_added'))) + + def testDeleteSkippedLocalNotExistent(self): + """ + delete a skipped file: no local file with that name exists + """ + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('skipped') + self.__check_ret(ret, False, 'S') + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + + def testDeleteSkippedLocalExistent(self): + """ + delete a skipped file: a local file with that name exists and will be deleted + (for instance _service:* files have status 'S' but a local files might exist) + """ + self._change_to_pkg('simple') + p = osc.core.Package('.') + ret = p.delete_file('skipped_exists') + self.__check_ret(ret, True, 'S') + self.assertFalse(os.path.exists('skipped_exists')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_to_be_deleted'))) + + def __check_ret(self, ret, exp1, exp2): + self.assertTrue(len(ret) == 2) + self.assertTrue(ret[0] == exp1) + self.assertTrue(ret[1] == exp2) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_difffiles.py b/tests/test_difffiles.py new file mode 100644 index 0000000..43c8afe --- /dev/null +++ b/tests/test_difffiles.py @@ -0,0 +1,336 @@ +import osc.core +import osc.oscerr +import os +import re +from common import GET, OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'difffile_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestDiffFiles) + +class TestDiffFiles(OscTestCase): + diff_hdr = 'Index: %s\n===================================================================' + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def testDiffUnmodified(self): + """diff an unmodified file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['merge'] + self.__check_diff(p, '', None) + + def testDiffAdded(self): + """diff an added file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['toadd1'] + exp = """%s +--- toadd1\t(revision 0) ++++ toadd1\t(revision 0) +@@ -0,0 +1,1 @@ ++toadd1 +""" % (TestDiffFiles.diff_hdr % 'toadd1') + self.__check_diff(p, exp, None) + + def testDiffRemoved(self): + """diff a removed file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['somefile'] + exp = """%s +--- somefile\t(revision 2) ++++ somefile\t(working copy) +@@ -1,1 +0,0 @@ +-some content +""" % (TestDiffFiles.diff_hdr % 'somefile') + self.__check_diff(p, exp, None) + + def testDiffMissing(self): + """diff a missing file (missing files are ignored)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['missing'] + self.__check_diff(p, '', None) + + def testDiffReplaced(self): + """diff a replaced file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['replaced'] + exp = """%s +--- replaced\t(revision 2) ++++ replaced\t(working copy) +@@ -1,1 +1,1 @@ +-yet another file ++foo replaced +""" % (TestDiffFiles.diff_hdr % 'replaced') + self.__check_diff(p, exp, None) + + def testDiffSkipped(self): + """diff a skipped file (skipped files are ignored)""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['skipped'] + self.__check_diff(p, '', None) + + def testDiffConflict(self): + """diff a file which is in the conflict state""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['foo'] + exp = """%s +--- foo\t(revision 2) ++++ foo\t(working copy) +@@ -1,1 +1,5 @@ ++<<<<<<< foo.mine ++This is no test. ++======= + This is a simple test. ++>>>>>>> foo.r2 +""" % (TestDiffFiles.diff_hdr % 'foo') + self.__check_diff(p, exp, None) + + def testDiffModified(self): + """diff a modified file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['nochange'] + exp = """%s +--- nochange\t(revision 2) ++++ nochange\t(working copy) +@@ -1,1 +1,2 @@ +-This file didn't change. ++This file didn't change but ++is modified. +""" % (TestDiffFiles.diff_hdr % 'nochange') + self.__check_diff(p, exp, None) + + def testDiffUnversioned(self): + """diff an unversioned file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['toadd2'] + self.assertRaises(osc.oscerr.OscIOError, self.__check_diff, p, '', None) + + def testDiffAddedMissing(self): + """diff a file which has satus 'A' but the local file does not exist""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['addedmissing'] + self.assertRaises(osc.oscerr.OscIOError, self.__check_diff, p, '', None) + + def testDiffMultipleFiles(self): + """diff multiple files""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['nochange', 'somefile'] + exp = """%s +--- nochange\t(revision 2) ++++ nochange\t(working copy) +@@ -1,1 +1,2 @@ +-This file didn't change. ++This file didn't change but ++is modified. +%s +--- somefile\t(revision 2) ++++ somefile\t(working copy) +@@ -1,1 +0,0 @@ +-some content +""" % (TestDiffFiles.diff_hdr % 'nochange', TestDiffFiles.diff_hdr % 'somefile') + self.__check_diff(p, exp, None) + + def testDiffReplacedEmptyTodo(self): + """diff a complete package""" + self._change_to_pkg('replaced') + p = osc.core.Package('.') + exp = """%s +--- replaced\t(revision 2) ++++ replaced\t(working copy) +@@ -1,1 +1,1 @@ +-yet another file ++foo replaced +""" % (TestDiffFiles.diff_hdr % 'replaced') + self.__check_diff(p, exp, None) + + def testDiffBinaryAdded(self): + """diff an added binary file""" + self._change_to_pkg('binary') + p = osc.core.Package('.') + p.todo = ['binary_added'] + exp = """%s +Binary file 'binary_added' added. +""" % (TestDiffFiles.diff_hdr % 'binary_added') + self.__check_diff(p, exp, None) + + def testDiffBinaryDeleted(self): + """diff a deleted binary file""" + self._change_to_pkg('binary') + p = osc.core.Package('.') + p.todo = ['binary_deleted'] + exp = """%s +Binary file 'binary_deleted' deleted. +""" % (TestDiffFiles.diff_hdr % 'binary_deleted') + self.__check_diff(p, exp, None) + + def testDiffBinaryModified(self): + """diff a modified binary file""" + self._change_to_pkg('binary') + p = osc.core.Package('.') + p.todo = ['binary'] + exp = """%s +Binary file 'binary' has changed. +""" % (TestDiffFiles.diff_hdr % 'binary') + self.__check_diff(p, exp, None) + + # diff with revision + @GET('http://localhost/source/osctest/remote_simple_noadd?rev=3', file='testDiffRemoteNoChange_files') + def testDiffRemoteNoChange(self): + """diff against remote revision where no file changed""" + self._change_to_pkg('remote_simple_noadd') + p = osc.core.Package('.') + self.__check_diff(p, '', 3) + + @GET('http://localhost/source/osctest/remote_simple?rev=3', file='testDiffRemoteModified_files') + @GET('http://localhost/source/osctest/remote_simple/merge?rev=3', file='testDiffRemoteModified_merge') + def testDiffRemoteModified(self): + """diff against a remote revision with one modified file""" + self._change_to_pkg('remote_simple') + p = osc.core.Package('.') + exp = """%s +--- merge\t(revision 3) ++++ merge\t(working copy) +@@ -1,3 +1,4 @@ + Is it + possible to + merge this file? ++I hope so... +%s +--- toadd1\t(revision 0) ++++ toadd1\t(revision 0) +@@ -0,0 +1,1 @@ ++toadd1 +""" % (TestDiffFiles.diff_hdr % 'merge', TestDiffFiles.diff_hdr % 'toadd1') + self.__check_diff(p, exp, 3) + + @GET('http://localhost/source/osctest/remote_simple?rev=3', file='testDiffRemoteDeletedLocalAdded_files') + def testDiffRemoteNotExistingLocalAdded(self): + """ + a file which doesn't exist in a remote revision and + has status A in the wc + """ + self._change_to_pkg('remote_simple') + p = osc.core.Package('.') + exp = """%s +--- toadd1\t(revision 0) ++++ toadd1\t(revision 0) +@@ -0,0 +1,1 @@ ++toadd1 +""" % (TestDiffFiles.diff_hdr % 'toadd1') + self.__check_diff(p, exp, 3) + + @GET('http://localhost/source/osctest/remote_simple_noadd?rev=3', file='testDiffRemoteExistingLocalNotExisting_files') + @GET('http://localhost/source/osctest/remote_simple_noadd/foobar?rev=3', file='testDiffRemoteExistingLocalNotExisting_foobar') + @GET('http://localhost/source/osctest/remote_simple_noadd/binary?rev=3', file='testDiffRemoteExistingLocalNotExisting_binary') + def testDiffRemoteExistingLocalNotExisting(self): + """ + a file doesn't exist in the local wc but exists + in the remote revision + """ + self._change_to_pkg('remote_simple_noadd') + p = osc.core.Package('.') + exp = """%s +--- foobar\t(revision 3) ++++ foobar\t(working copy) +@@ -1,2 +0,0 @@ +-foobar +-barfoo +%s +Binary file 'binary' deleted. +""" % (TestDiffFiles.diff_hdr % 'foobar', TestDiffFiles.diff_hdr % 'binary') + self.__check_diff(p, exp, 3) + + @GET('http://localhost/source/osctest/remote_localmodified?rev=3', file='testDiffRemoteUnchangedLocalModified_files') + @GET('http://localhost/source/osctest/remote_localmodified/nochange?rev=3', file='testDiffRemoteUnchangedLocalModified_nochange') + @GET('http://localhost/source/osctest/remote_localmodified/binary?rev=3', file='testDiffRemoteUnchangedLocalModified_binary') + def testDiffRemoteUnchangedLocalModified(self): + """remote revision didn't change, local file is modified""" + self._change_to_pkg('remote_localmodified') + p = osc.core.Package('.') + exp = """%s +--- nochange\t(revision 3) ++++ nochange\t(working copy) +@@ -1,1 +1,2 @@ + This file didn't change. ++oh it does +%s +Binary file 'binary' has changed. +""" % (TestDiffFiles.diff_hdr % 'nochange', TestDiffFiles.diff_hdr % 'binary') + self.__check_diff(p, exp, 3) + + @GET('http://localhost/source/osctest/remote_simple_noadd?rev=3', file='testDiffRemoteMissingLocalExisting_files') + def testDiffRemoteMissingLocalExisting(self): + """ + remote revision misses a file which exists in the local wc (state ' ')""" + self._change_to_pkg('remote_simple_noadd') + p = osc.core.Package('.') + exp = """%s +--- foo\t(revision 0) ++++ foo\t(working copy) +@@ -0,0 +1,1 @@ ++This is a simple test. +""" % (TestDiffFiles.diff_hdr % 'foo') + self.__check_diff(p, exp, 3) + + @GET('http://localhost/source/osctest/remote_localdelete?rev=3', file='testDiffRemoteMissingLocalDeleted_files') + def testDiffRemoteMissingLocalDeleted(self): + """ + remote revision misses a file which is marked for + deletion in the local wc + """ + # empty diff is expected (svn does the same) + self._change_to_pkg('remote_localdelete') + p = osc.core.Package('.') + self.__check_diff(p, '', 3) + + def __check_diff(self, p, exp, revision=None): + got = '' + for i in p.get_diff(revision): + got += ''.join(i) + + # When a hunk header refers to a single line in the "from" + # file and/or the "to" file, e.g. + # + # @@ -37,37 +41,43 @@ + # @@ -37,39 +41,41 @@ + # @@ -37,37 +41,41 @@ + # + # some systems will avoid repeating the line number: + # + # @@ -37 +41,43 @@ + # @@ -37,39 +41 @@ + # @@ -37 +41 @@ + # + # so we need to canonise the output to avoid false negative + # test failures. + + # TODO: Package.get_diff should return a consistent format + # (regardless of the used python version) + def __canonise_diff(diff): + # we cannot use re.M because python 2.6's re.sub does + # not support a flags argument + diff = [re.sub('^@@ -(\d+) ', '@@ -\\1,\\1 ', line) + for line in diff.split('\n')] + diff = [re.sub('^(@@ -\d+,\d+) \+(\d+) ', '\\1 +\\2,\\2 ', line) + for line in diff] + return '\n'.join(diff) + + got = __canonise_diff(got) + exp = __canonise_diff(exp) + self.assertEqualMultiline(got, exp) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_init_package.py b/tests/test_init_package.py new file mode 100644 index 0000000..7347506 --- /dev/null +++ b/tests/test_init_package.py @@ -0,0 +1,88 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase +FIXTURES_DIR = os.path.join(os.getcwd(), 'init_package_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestInitPackage) + +class TestInitPackage(OscTestCase): + def _get_fixtures_dir(self): + # workaround for git because it doesn't allow empty dirs + if not os.path.exists(os.path.join(FIXTURES_DIR, 'osctest')): + os.mkdir(os.path.join(FIXTURES_DIR, 'osctest')) + return FIXTURES_DIR + + def tearDown(self): + if os.path.exists(os.path.join(FIXTURES_DIR, 'osctest')): + os.rmdir(os.path.join(FIXTURES_DIR, 'osctest')) + OscTestCase.tearDown(self) + + def test_simple(self): + """initialize a package dir""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + osc.core.Package.init_package('http://localhost', 'osctest', 'testpkg', pac_dir) + storedir = os.path.join(pac_dir, osc.core.store) + self.assertFalse(os.path.exists(os.path.join(storedir, '_meta_mode'))) + self.assertFalse(os.path.exists(os.path.join(storedir, '_size_limit'))) + self._check_list(os.path.join(storedir, '_project'), 'osctest\n') + self._check_list(os.path.join(storedir, '_package'), 'testpkg\n') + self._check_list(os.path.join(storedir, '_files'), '<directory />\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + + def test_size_limit(self): + """initialize a package dir with size_limit parameter""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + osc.core.Package.init_package('http://localhost', 'osctest', 'testpkg', pac_dir, size_limit=42) + storedir = os.path.join(pac_dir, osc.core.store) + self.assertFalse(os.path.exists(os.path.join(storedir, '_meta_mode'))) + self._check_list(os.path.join(storedir, '_size_limit'), '42\n') + self._check_list(os.path.join(storedir, '_project'), 'osctest\n') + self._check_list(os.path.join(storedir, '_package'), 'testpkg\n') + self._check_list(os.path.join(storedir, '_files'), '<directory />\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + + def test_meta_mode(self): + """initialize a package dir with meta paramter""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + osc.core.Package.init_package('http://localhost', 'osctest', 'testpkg', pac_dir, meta=True) + storedir = os.path.join(pac_dir, osc.core.store) + self.assertFalse(os.path.exists(os.path.join(storedir, '_size_limit'))) + self._check_list(os.path.join(storedir, '_meta_mode'), '') + self._check_list(os.path.join(storedir, '_project'), 'osctest\n') + self._check_list(os.path.join(storedir, '_package'), 'testpkg\n') + self._check_list(os.path.join(storedir, '_files'), '<directory />\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + + def test_dirExists(self): + """initialize a package dir (dir already exists)""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + os.mkdir(pac_dir) + osc.core.Package.init_package('http://localhost', 'osctest', 'testpkg', pac_dir) + storedir = os.path.join(pac_dir, osc.core.store) + self.assertFalse(os.path.exists(os.path.join(storedir, '_meta_mode'))) + self.assertFalse(os.path.exists(os.path.join(storedir, '_size_limit'))) + self._check_list(os.path.join(storedir, '_project'), 'osctest\n') + self._check_list(os.path.join(storedir, '_package'), 'testpkg\n') + self._check_list(os.path.join(storedir, '_files'), '<directory />\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + + def test_storedirExists(self): + """initialize a package dir (dir+storedir already exists)""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + os.mkdir(pac_dir) + os.mkdir(os.path.join(pac_dir, osc.core.store)) + self.assertRaises(osc.oscerr.OscIOError, osc.core.Package.init_package, 'http://localhost', 'osctest', 'testpkg', pac_dir) + + def test_dirIsFile(self): + """initialize a package dir (dir is a file)""" + pac_dir = os.path.join(self.tmpdir, 'testpkg') + os.mkdir(pac_dir) + open(os.path.join(pac_dir, osc.core.store), 'w').write('foo\n') + self.assertRaises(osc.oscerr.OscIOError, osc.core.Package.init_package, 'http://localhost', 'osctest', 'testpkg', pac_dir) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_init_project.py b/tests/test_init_project.py new file mode 100644 index 0000000..f4714dc --- /dev/null +++ b/tests/test_init_project.py @@ -0,0 +1,71 @@ +import osc.core +import osc.oscerr +import os +from common import GET, OscTestCase +FIXTURES_DIR = os.path.join(os.getcwd(), 'init_project_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestInitProject) + +class TestInitProject(OscTestCase): + def _get_fixtures_dir(self): + # workaround for git because it doesn't allow empty dirs + if not os.path.exists(os.path.join(FIXTURES_DIR, 'osctest')): + os.mkdir(os.path.join(FIXTURES_DIR, 'osctest')) + return FIXTURES_DIR + + def tearDown(self): + if os.path.exists(os.path.join(FIXTURES_DIR, 'osctest')): + os.rmdir(os.path.join(FIXTURES_DIR, 'osctest')) + OscTestCase.tearDown(self) + + def test_simple(self): + """initialize a project dir""" + prj_dir = os.path.join(self.tmpdir, 'testprj') + prj = osc.core.Project.init_project('http://localhost', prj_dir, 'testprj', getPackageList=False) + self.assertTrue(isinstance(prj, osc.core.Project)) + storedir = os.path.join(prj_dir, osc.core.store) + self._check_list(os.path.join(storedir, '_project'), 'testprj\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + self._check_list(os.path.join(storedir, '_packages'), '<project name="testprj" />') + + def test_dirExists(self): + """initialize a project dir but the dir already exists""" + prj_dir = os.path.join(self.tmpdir, 'testprj') + os.mkdir(prj_dir) + prj = osc.core.Project.init_project('http://localhost', prj_dir, 'testprj', getPackageList=False) + self.assertTrue(isinstance(prj, osc.core.Project)) + storedir = os.path.join(prj_dir, osc.core.store) + self._check_list(os.path.join(storedir, '_project'), 'testprj\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + self._check_list(os.path.join(storedir, '_packages'), '<project name="testprj" />') + + def test_storedirExists(self): + """initialize a project dir but the storedir already exists""" + prj_dir = os.path.join(self.tmpdir, 'testprj') + os.mkdir(prj_dir) + os.mkdir(os.path.join(prj_dir, osc.core.store)) + self.assertRaises(osc.oscerr.OscIOError, osc.core.Project.init_project, 'http://localhost', prj_dir, 'testprj') + + @GET('http://localhost/source/testprj', text='<directory count="0" />') + def test_no_package_tracking(self): + """initialize a project dir but disable package tracking; enable getPackageList=True; + disable wc_check (because we didn't disable the package tracking before the Project class + was imported therefore REQ_STOREFILES contains '_packages') + """ + import osc.conf + # disable package tracking + osc.conf.config['do_package_tracking'] = False + prj_dir = os.path.join(self.tmpdir, 'testprj') + os.mkdir(prj_dir) + prj = osc.core.Project.init_project('http://localhost', prj_dir, 'testprj', False, wc_check=False) + self.assertTrue(isinstance(prj, osc.core.Project)) + storedir = os.path.join(prj_dir, osc.core.store) + self._check_list(os.path.join(storedir, '_project'), 'testprj\n') + self._check_list(os.path.join(storedir, '_apiurl'), 'http://localhost\n') + self.assertFalse(os.path.exists(os.path.join(storedir, '_packages'))) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_package_status.py b/tests/test_package_status.py new file mode 100644 index 0000000..430adec --- /dev/null +++ b/tests/test_package_status.py @@ -0,0 +1,86 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'project_package_status_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestPackageStatus) + +class TestPackageStatus(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def test_allfiles(self): + """get the status of all files in the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + exp_st = [('A', 'add'), ('?', 'exists'), ('D', 'foo'), ('!', 'merge'), ('R', 'missing'), + ('!', 'missing_added'), ('M', 'nochange'), ('S', 'skipped'), (' ', 'test')] + st = p.get_status() + self.assertEqual(exp_st, st) + + def test_todo(self): + """ + get the status of some files in the wc. + """ + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['test', 'missing_added', 'foo'] + exp_st = [('D', 'foo'), ('!', 'missing_added')] + st = p.get_status(False, ' ') + self.assertEqual(exp_st, st) + + def test_todo_noexcl(self): + """ get the status of some files in the wc. """ + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['test', 'missing_added', 'foo'] + exp_st = [('D', 'foo'), ('!', 'missing_added'), (' ', 'test')] + st = p.get_status() + self.assertEqual(exp_st, st) + + def test_exclude_state(self): + """get the status of all files in the wc but exclude some states""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + exp_st = [('A', 'add'), ('?', 'exists'), ('D', 'foo')] + st = p.get_status(False, '!', 'S', ' ', 'M', 'R') + self.assertEqual(exp_st, st) + + def test_nonexistent(self): + """get the status of a non existent file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.todo = ['doesnotexist'] + self.assertRaises(osc.oscerr.OscIOError, p.get_status) + + def test_conflict(self): + """get status of the wc (one file in conflict state)""" + self._change_to_pkg('conflict') + p = osc.core.Package('.') + exp_st = [('C', 'conflict'), ('?', 'exists'), (' ', 'test')] + st = p.get_status() + self.assertEqual(exp_st, st) + + def test_excluded(self): + """get status of the wc (ignore excluded files); package has state ' '""" + self._change_to_pkg('excluded') + p = osc.core.Package('.') + exp_st = [('?', 'exists'), ('M', 'modified')] + st = p.get_status(False, ' ') + self.assertEqual(exp_st, st) + + def test_noexcluded(self): + """get status of the wc (include excluded files)""" + self._change_to_pkg('excluded') + p = osc.core.Package('.') + exp_st = [('?', '_linkerror'), ('?', 'exists'), ('?', 'foo.orig'), ('M', 'modified'), (' ', 'test')] + st = p.get_status(True) + self.assertEqual(exp_st, st) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_prdiff.py b/tests/test_prdiff.py new file mode 100644 index 0000000..cff6327 --- /dev/null +++ b/tests/test_prdiff.py @@ -0,0 +1,271 @@ +import osc.commandline +import osc.core +import osc.oscerr +import os +import re +import sys +from common import GET, POST, OscTestCase, addExpectedRequest, EXPECTED_REQUESTS + + +FIXTURES_DIR = os.path.join(os.getcwd(), 'prdiff_fixtures') +API_URL = 'http://localhost/' +UPSTREAM = 'some:project' +BRANCH = 'home:user:branches:' + UPSTREAM + +def rdiff_url(pkg, oldprj, newprj): + return API_URL + 'source/%s/%s?unified=1&opackage=%s&oproject=%s&cmd=diff&expand=1&tarlimit=0&filelimit=0' % \ + (newprj, pkg, pkg, oldprj.replace(':', '%3A')) + +def request_url(prj): + return API_URL + 'search/request?match=%%28state%%2F%%40name%%3D%%27new%%27+or+state%%2F%%40name%%3D%%27review%%27%%29+and+%%28action%%2Ftarget%%2F%%40project%%3D%%27%s%%27+or+submit%%2Ftarget%%2F%%40project%%3D%%27%s%%27+or+action%%2Fsource%%2F%%40project%%3D%%27%s%%27+or+submit%%2Fsource%%2F%%40project%%3D%%27%s%%27%%29' % \ + tuple([prj.replace(':', '%3A')] * 4) + +def GET_PROJECT_PACKAGES(*projects): + def decorator(test_method): + def wrapped_test_method(*args): + for project in projects: + addExpectedRequest('GET', API_URL + 'source/' + project, + file='%s/directory' % project) + test_method(*args) + # "rename" method otherwise we cannot specify a TestCaseClass.testName + # cmdline arg when using unittest.main() + wrapped_test_method.__name__ = test_method.__name__ + return wrapped_test_method + return decorator + +def POST_RDIFF(oldprj, newprj): + def decorator(test_method): + def wrapped_test_method(*args): + addExpectedRequest('POST', rdiff_url('common-one', oldprj, newprj), exp='', text='') + addExpectedRequest('POST', rdiff_url('common-two', oldprj, newprj), exp='', file='common-two-diff') + addExpectedRequest('POST', rdiff_url('common-three', oldprj, newprj), exp='', text='') + test_method(*args) + # "rename" method otherwise we cannot specify a TestCaseClass.testName + # cmdline arg when using unittest.main() + wrapped_test_method.__name__ = test_method.__name__ + return wrapped_test_method + return decorator + +def suite(): + import unittest + return unittest.makeSuite(TestProjectDiff) + +class TestProjectDiff(OscTestCase): + diff_hdr = 'Index: %s\n===================================================================' + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def _change_to_tmpdir(self, *args): + os.chdir(os.path.join(self.tmpdir, *args)) + + def _run_prdiff(self, *args): + """Runs osc prdiff, returning captured STDOUT as a string.""" + cli = osc.commandline.Osc() + argv = ['osc', '--no-keyring', '--no-gnome-keyring', 'prdiff'] + argv.extend(args) + cli.main(argv=argv) + return sys.stdout.getvalue() + + + def testPrdiffTooManyArgs(self): + def runner(): + self._run_prdiff('one', 'two', 'superfluous-arg') + self.assertRaises(osc.oscerr.WrongArgs, runner) + + + @GET_PROJECT_PACKAGES(UPSTREAM, BRANCH) + @POST_RDIFF(UPSTREAM, BRANCH) + @POST(rdiff_url('only-in-new', UPSTREAM, BRANCH), exp='', text='') + def testPrdiffZeroArgs(self): + exp = """identical: common-one +differs: common-two +identical: common-three +identical: only-in-new +""" + def runner(): + self._run_prdiff() + + os.chdir('/tmp') + self.assertRaises(osc.oscerr.WrongArgs, runner) + + self._change_to_tmpdir(FIXTURES_DIR, UPSTREAM) + self.assertRaises(osc.oscerr.WrongArgs, runner) + + self._change_to_tmpdir(FIXTURES_DIR, BRANCH) + out = self._run_prdiff() + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES(UPSTREAM, BRANCH) + @POST_RDIFF(UPSTREAM, BRANCH) + @POST(rdiff_url('only-in-new', UPSTREAM, BRANCH), exp='', text='') + def testPrdiffOneArg(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two +identical: common-three +identical: only-in-new +""" + out = self._run_prdiff('home:user:branches:some:project') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST_RDIFF('old:prj', 'new:prj') + def testPrdiffTwoArgs(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two +identical: common-three +""" + out = self._run_prdiff('old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST_RDIFF('old:prj', 'new:prj') + def testPrdiffOldOnly(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two +identical: common-three +old only: only-in-old +""" + out = self._run_prdiff('--show-not-in-new', 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST_RDIFF('old:prj', 'new:prj') + def testPrdiffNewOnly(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two +identical: common-three +new only: only-in-new +""" + out = self._run_prdiff('--show-not-in-old', 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST_RDIFF('old:prj', 'new:prj') + def testPrdiffDiffstat(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two + + common-two | 1 + + 1 file changed, 1 insertion(+) + +identical: common-three +""" + out = self._run_prdiff('--diffstat', 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST_RDIFF('old:prj', 'new:prj') + def testPrdiffUnified(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two + +Index: common-two +=================================================================== +--- common-two\t2013-01-18 19:18:38.225983117 +0000 ++++ common-two\t2013-01-18 19:19:27.882082325 +0000 +@@ -1,4 +1,5 @@ + line one + line two + line three ++an extra line + last line + +identical: common-three +""" + out = self._run_prdiff('--unified', 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST(rdiff_url('common-two', 'old:prj', 'new:prj'), exp='', file='common-two-diff') + @POST(rdiff_url('common-three', 'old:prj', 'new:prj'), exp='', text='') + def testPrdiffInclude(self): + self._change_to_tmpdir() + exp = """differs: common-two +identical: common-three +""" + out = self._run_prdiff('--include', 'common-t', + 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST(rdiff_url('common-two', 'old:prj', 'new:prj'), exp='', file='common-two-diff') + @POST(rdiff_url('common-three', 'old:prj', 'new:prj'), exp='', text='') + def testPrdiffExclude(self): + self._change_to_tmpdir() + exp = """differs: common-two +identical: common-three +""" + out = self._run_prdiff('--exclude', 'one', 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES('old:prj', 'new:prj') + @POST(rdiff_url('common-two', 'old:prj', 'new:prj'), exp='', file='common-two-diff') + def testPrdiffIncludeExclude(self): + self._change_to_tmpdir() + exp = """differs: common-two +""" + out = self._run_prdiff('--include', 'common-t', + '--exclude', 'three', + 'old:prj', 'new:prj') + self.assertEqualMultiline(out, exp) + + + @GET_PROJECT_PACKAGES(UPSTREAM, BRANCH) + @GET(request_url(UPSTREAM), exp='', file='request') + @POST(rdiff_url('common-one', UPSTREAM, BRANCH), exp='', text='') + @POST(rdiff_url('common-two', UPSTREAM, BRANCH), exp='', file='common-two-diff') + @POST(rdiff_url('common-three', UPSTREAM, BRANCH), exp='', file='common-two-diff') + @POST(rdiff_url('only-in-new', UPSTREAM, BRANCH), exp='', text='') + def testPrdiffRequestsMatching(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two + +148023 State:new By:user When:2013-01-11T11:04:14 + submit: home:user:branches:some:project/common-two@7 -> some:project + Descr: - Fix it to work - Improve support for something + +differs: common-three +identical: only-in-new +""" + out = self._run_prdiff('--requests', UPSTREAM, BRANCH) + self.assertEqualMultiline(out, exp) + + + # Reverse the direction of the diff. + @GET_PROJECT_PACKAGES(BRANCH, UPSTREAM) + @GET(request_url(BRANCH), exp='', file='no-requests') + @POST(rdiff_url('common-one', BRANCH, UPSTREAM), exp='', text='') + @POST(rdiff_url('common-two', BRANCH, UPSTREAM), exp='', file='common-two-diff') + @POST(rdiff_url('common-three', BRANCH, UPSTREAM), exp='', file='common-two-diff') + @POST(rdiff_url('only-in-new', BRANCH, UPSTREAM), exp='', text='') + def testPrdiffRequestsSwitched(self): + self._change_to_tmpdir() + exp = """identical: common-one +differs: common-two +differs: common-three +identical: only-in-new +""" + out = self._run_prdiff('--requests', BRANCH, UPSTREAM) + self.assertEqualMultiline(out, exp) + + + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_project_status.py b/tests/test_project_status.py new file mode 100644 index 0000000..3c8497f --- /dev/null +++ b/tests/test_project_status.py @@ -0,0 +1,161 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'project_package_status_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestProjectStatus) + +class TestProjectStatus(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def test_simple(self): + """get the status of a package with state ' '""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = ' ' + st = prj.status('simple') + self.assertEqual(exp_st, st) + + def test_added(self): + """get the status of an added package""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = 'A' + st = prj.status('added') + self.assertEqual(exp_st, st) + + def test_deleted(self): + """get the status of a deleted package""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = 'D' + st = prj.status('deleted') + self.assertEqual(exp_st, st) + + def test_added_deleted(self): + """ + get the status of a package which was added and deleted + afterwards (with a non osc command) + """ + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = '!' + st = prj.status('added_deleted') + self.assertEqual(exp_st, st) + + def test_missing(self): + """ + get the status of a package with state " " + which was removed by a non osc command + """ + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = '!' + st = prj.status('missing') + self.assertEqual(exp_st, st) + + def test_deleted_deleted(self): + """ + get the status of a package which was deleted (with an + osc command) and afterwards the package directory was + deleted with a non osc command + """ + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = 'D' + st = prj.status('deleted_deleted') + self.assertEqual(exp_st, st) + + def test_unversioned_exists(self): + """get the status of an unversioned package""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = '?' + st = prj.status('excluded') + self.assertEqual(exp_st, st) + + def test_unversioned_nonexistent(self): + """get the status of an unversioned, nonexistent package""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + self.assertRaises(osc.oscerr.OscIOError, prj.status, 'doesnotexist') + + def test_get_status(self): + """get the status of the complete project""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = [(' ', 'conflict'), (' ', 'simple'), ('A', 'added'), ('D', 'deleted'), + ('!', 'missing'), ('!', 'added_deleted'), ('D', 'deleted_deleted'), ('?', 'excluded')] + st = prj.get_status() + self.assertEqual(exp_st, st) + + def test_get_status_excl(self): + """get the status of the complete project (exclude some states)""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + exp_st = [('A', 'added'), ('!', 'missing'), ('!', 'added_deleted')] + st = prj.get_status('D', ' ', '?') + self.assertEqual(exp_st, st) + + def test_get_pacobj_simple(self): + """package exists""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('simple') + self.assertTrue(isinstance(p, osc.core.Package)) + self.assertEqual(p.name, 'simple') + + def test_get_pacobj_added(self): + """package has state 'A', also test pac_kwargs""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('added', progress_obj={}) + self.assertTrue(isinstance(p, osc.core.Package)) + self.assertEqual(p.name, 'added') + self.assertEqual(p.progress_obj, {}) + + def test_get_pacobj_deleted(self): + """package has state 'D' and exists, also test pac_args""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('deleted', {}) + self.assertTrue(isinstance(p, osc.core.Package)) + self.assertEqual(p.name, 'deleted') + self.assertEqual(p.progress_obj, {}) + + def test_get_pacobj_missing(self): + """package is missing""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('missing') + self.assertTrue(isinstance(p, type(None))) + + def test_get_pacobj_deleted_deleted(self): + """package has state 'D' and does not exist""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('deleted_deleted') + self.assertTrue(isinstance(p, type(None))) + + def test_get_pacobj_unversioned(self): + """package/dir has state '?'""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('excluded') + self.assertTrue(isinstance(p, type(None))) + + def test_get_pacobj_nonexistent(self): + """package/dir does not exist""" + self._change_to_pkg('.') + prj = osc.core.Project('.', getPackageList=False) + p = prj.get_pacobj('doesnotexist') + self.assertTrue(isinstance(p, type(None))) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_repairwc.py b/tests/test_repairwc.py new file mode 100644 index 0000000..f2b090b --- /dev/null +++ b/tests/test_repairwc.py @@ -0,0 +1,265 @@ +import osc.core +import osc.oscerr +import os +import sys +from common import GET, PUT, POST, DELETE, OscTestCase +from xml.etree import cElementTree as ET +FIXTURES_DIR = os.path.join(os.getcwd(), 'repairwc_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestRepairWC) + +class TestRepairWC(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def __assertNotRaises(self, exception, meth, *args, **kwargs): + try: + meth(*args, **kwargs) + except exception: + self.fail('%s raised' % exception.__name__) + + def test_working_empty(self): + """consistent, empty working copy""" + self._change_to_pkg('working_empty') + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_working_nonempty(self): + """ + consistent, non-empty working copy. One file is in conflict, + one file is marked for deletion and one file has state 'A' + """ + self._change_to_pkg('working_nonempty') + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_buildfiles(self): + """ + wc has a _buildconfig_prj_arch and a _buildinfo_prj_arch.xml in the storedir + """ + self._change_to_pkg('buildfiles') + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + @GET('http://localhost/source/osctest/simple1/foo?rev=1', text='This is a simple test.\n') + def test_simple1(self): + """a file is marked for deletion but storefile doesn't exist""" + self._change_to_pkg('simple1') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple2(self): + """a file "somefile" exists in the storedir which isn't tracked""" + self._change_to_pkg('simple2') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertFalse(os.path.exists(os.path.join('.osc', 'somefile'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple3(self): + """toadd1 has state 'A' and a file .osc/toadd1 exists""" + self._change_to_pkg('simple3') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertFalse(os.path.exists(os.path.join('.osc', 'toadd1'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_addlist('toadd1\n') + self._check_status(p, 'toadd1', 'A') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple4(self): + """a file is listed in _to_be_deleted but isn't present in _files""" + self._change_to_pkg('simple4') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple5(self): + """a file is listed in _in_conflict but isn't present in _files""" + self._change_to_pkg('simple5') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertFalse(os.path.exists(os.path.join('.osc', '_in_conflict'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + @GET('http://localhost/source/osctest/simple6/foo?rev=1', text='This is a simple test.\n') + def test_simple6(self): + """ + a file is listed in _to_be_deleted and is present + in _files but the storefile is missing + """ + self._change_to_pkg('simple6') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple7(self): + """files marked as skipped don't exist in the storedir""" + self._change_to_pkg('simple7') + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_simple8(self): + """ + a file is marked as skipped but the skipped file exists in the storedir + """ + self._change_to_pkg('simple8') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertFalse(os.path.exists(os.path.join('.osc', 'skipped'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'M') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'toadd1', '?') + self._check_status(p, 'skipped', 'S') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + @GET('http://localhost/source/osctest/multiple/merge?rev=1', text='Is it\npossible to\nmerge this file?I hope so...\n') + @GET('http://localhost/source/osctest/multiple/nochange?rev=1', text='This file didn\'t change.\n') + def test_multiple(self): + """ + a storefile is missing, a file is listed in _to_be_deleted + but is not present in _files, a file is listed in _in_conflict + but the storefile is missing and a file exists in the storedir + but is not present in _files + """ + self._change_to_pkg('multiple') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + p.wc_repair() + self.assertTrue(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'unknown_file'))) + self._check_deletelist('foo\n') + self._check_status(p, 'foo', 'D') + self._check_status(p, 'nochange', 'C') + self._check_status(p, 'merge', ' ') + self._check_status(p, 'foobar', 'A') + self._check_status(p, 'toadd1', '?') + # additional cleanup check + self.__assertNotRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + + def test_noapiurl(self): + """the package wc has no _apiurl file""" + self._change_to_pkg('noapiurl') + p = osc.core.Package('.', wc_check=False) + p.wc_repair('http://localhost') + self.assertTrue(os.path.exists(os.path.join('.osc', '_apiurl'))) + self.assertEqual(open(os.path.join('.osc', '_apiurl')).read(), 'http://localhost\n') + self.assertEqual(p.apiurl, 'http://localhost') + + def test_invalidapiurl(self): + """the package wc has an invalid apiurl file (invalid url format)""" + self._change_to_pkg('invalid_apiurl') + p = osc.core.Package('.', wc_check=False) + p.wc_repair('http://localhost') + self.assertTrue(os.path.exists(os.path.join('.osc', '_apiurl'))) + self.assertEqual(open(os.path.join('.osc', '_apiurl')).read(), 'http://localhost\n') + self.assertEqual(p.apiurl, 'http://localhost') + + def test_invalidapiurl_param(self): + """pass an invalid apiurl to wc_repair""" + try: + from urllib.error import URLError + except ImportError: + from urllib2 import URLError + self._change_to_pkg('invalid_apiurl') + p = osc.core.Package('.', wc_check=False) + self.assertRaises(URLError, p.wc_repair, 'http:/localhost') + self.assertRaises(URLError, p.wc_repair, 'invalid') + + def test_noapiurlNotExistingApiurl(self): + """the package wc has no _apiurl file and no apiurl is passed to repairwc""" + self._change_to_pkg('noapiurl') + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Package, '.') + p = osc.core.Package('.', wc_check=False) + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, p.wc_repair) + self.assertFalse(os.path.exists(os.path.join('.osc', '_apiurl'))) + + def test_project_noapiurl(self): + """the project wc has no _apiurl file""" + import shutil + prj_dir = os.path.join(self.tmpdir, 'prj_noapiurl') + shutil.copytree(os.path.join(self._get_fixtures_dir(), 'prj_noapiurl'), prj_dir) + storedir = os.path.join(prj_dir, osc.core.store) + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Project, prj_dir, getPackageList=False) + prj = osc.core.Project(prj_dir, wc_check=False, getPackageList=False) + prj.wc_repair('http://localhost') + self.assertTrue(os.path.exists(os.path.join(storedir, '_apiurl'))) + self.assertTrue(os.path.exists(os.path.join(storedir, '_apiurl'))) + self.assertEqual(open(os.path.join(storedir, '_apiurl'), 'r').read(), 'http://localhost\n') + + def test_project_invalidapiurl(self): + """the project wc has an invalid _apiurl file (invalid url format)""" + import shutil + prj_dir = os.path.join(self.tmpdir, 'prj_invalidapiurl') + shutil.copytree(os.path.join(self._get_fixtures_dir(), 'prj_invalidapiurl'), prj_dir) + storedir = os.path.join(prj_dir, osc.core.store) + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Project, prj_dir, getPackageList=False) + prj = osc.core.Project(prj_dir, wc_check=False, getPackageList=False) + prj.wc_repair('http://localhost') + self.assertTrue(os.path.exists(os.path.join(storedir, '_apiurl'))) + self.assertTrue(os.path.exists(os.path.join(storedir, '_apiurl'))) + self.assertEqual(open(os.path.join(storedir, '_apiurl'), 'r').read(), 'http://localhost\n') + + def test_project_invalidapiurl_param(self): + """pass an invalid apiurl to wc_repair""" + import shutil + try: + from urllib.error import URLError + except ImportError: + from urllib2 import URLError + prj_dir = os.path.join(self.tmpdir, 'prj_invalidapiurl') + shutil.copytree(os.path.join(self._get_fixtures_dir(), 'prj_invalidapiurl'), prj_dir) + storedir = os.path.join(prj_dir, osc.core.store) + self.assertRaises(osc.oscerr.WorkingCopyInconsistent, osc.core.Project, prj_dir, getPackageList=False) + prj = osc.core.Project(prj_dir, wc_check=False, getPackageList=False) + self.assertRaises(URLError, prj.wc_repair, 'http:/localhost') + self.assertRaises(URLError, prj.wc_repair, 'invalid') + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_request.py b/tests/test_request.py new file mode 100644 index 0000000..d7cdd5a --- /dev/null +++ b/tests/test_request.py @@ -0,0 +1,577 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'request_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestRequest) + +class TestRequest(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def setUp(self): + OscTestCase.setUp(self, copytree=False) + + def test_createsr(self): + """create a simple submitrequest""" + r = osc.core.Request() + r.add_action('submit', src_project='foo', src_package='bar', src_rev='42', + tgt_project='foobar', tgt_package='bar') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'foo') + self.assertEqual(r.actions[0].src_package, 'bar') + self.assertEqual(r.actions[0].src_rev, '42') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertTrue(r.actions[0].opt_sourceupdate is None) + self.assertTrue(r.actions[0].opt_updatelink is None) + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + self.assertRaises(AttributeError, getattr, r.actions[0], 'doesnotexist') + exp = """<request> + <action type="submit"> + <source package="bar" project="foo" rev="42" /> + <target package="bar" project="foobar" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_createsr_with_option(self): + """create a submitrequest with option""" + """create a simple submitrequest""" + r = osc.core.Request() + r.add_action('submit', src_project='foo', src_package='bar', + tgt_project='foobar', tgt_package='bar', opt_sourceupdate='cleanup', opt_updatelink='1') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'foo') + self.assertEqual(r.actions[0].src_package, 'bar') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertEqual(r.actions[0].opt_sourceupdate, 'cleanup') + self.assertEqual(r.actions[0].opt_updatelink, '1') + self.assertTrue(r.actions[0].src_rev is None) + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + self.assertRaises(AttributeError, getattr, r.actions[0], 'doesnotexist') + exp = """<request> + <action type="submit"> + <source package="bar" project="foo" /> + <target package="bar" project="foobar" /> + <options> + <sourceupdate>cleanup</sourceupdate> + <updatelink>1</updatelink> + </options> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_createsr_missing_tgt_package(self): + """create a submitrequest with missing target package""" + r = osc.core.Request() + r.add_action('submit', src_project='foo', src_package='bar', + tgt_project='foobar') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'foo') + self.assertEqual(r.actions[0].src_package, 'bar') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + self.assertTrue(r.actions[0].tgt_package is None) + self.assertRaises(AttributeError, getattr, r.actions[0], 'doesnotexist') + exp = """<request> + <action type="submit"> + <source package="bar" project="foo" /> + <target project="foobar" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_createsr_invalid_argument(self): + """create a submitrequest with invalid action argument""" + r = osc.core.Request() + self.assertRaises(osc.oscerr.WrongArgs, r.add_action, 'submit', src_project='foo', src_invalid='bar') + + def test_create_request_invalid_type(self): + """create a request with an invalid action type""" + r = osc.core.Request() + self.assertRaises(osc.oscerr.WrongArgs, r.add_action, 'invalid', foo='bar') + + def test_create_add_role_person(self): + """create an add_role request (person element)""" + r = osc.core.Request() + r.add_action('add_role', tgt_project='foo', tgt_package='bar', person_name='user', person_role='reader') + self.assertEqual(r.actions[0].type, 'add_role') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertEqual(r.actions[0].person_name, 'user') + self.assertEqual(r.actions[0].person_role, 'reader') + self.assertTrue(r.actions[0].group_name is None) + self.assertTrue(r.actions[0].group_role is None) + exp = """<request> + <action type="add_role"> + <target package="bar" project="foo" /> + <person name="user" role="reader" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_add_role_group(self): + """create an add_role request (group element)""" + r = osc.core.Request() + r.add_action('add_role', tgt_project='foo', tgt_package='bar', group_name='group', group_role='reviewer') + self.assertEqual(r.actions[0].type, 'add_role') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertEqual(r.actions[0].group_name, 'group') + self.assertEqual(r.actions[0].group_role, 'reviewer') + self.assertTrue(r.actions[0].person_name is None) + self.assertTrue(r.actions[0].person_role is None) + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="add_role"> + <target package="bar" project="foo" /> + <group name="group" role="reviewer" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_add_role_person_group(self): + """create an add_role request (person+group element)""" + r = osc.core.Request() + r.add_action('add_role', tgt_project='foo', tgt_package='bar', person_name='user', person_role='reader', + group_name='group', group_role='reviewer') + self.assertEqual(r.actions[0].type, 'add_role') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertEqual(r.actions[0].person_name, 'user') + self.assertEqual(r.actions[0].person_role, 'reader') + self.assertEqual(r.actions[0].group_name, 'group') + self.assertEqual(r.actions[0].group_role, 'reviewer') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="add_role"> + <target package="bar" project="foo" /> + <person name="user" role="reader" /> + <group name="group" role="reviewer" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_set_bugowner_project(self): + """create a set_bugowner request for a project""" + r = osc.core.Request() + r.add_action('set_bugowner', tgt_project='foobar', person_name='buguser') + self.assertEqual(r.actions[0].type, 'set_bugowner') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertEqual(r.actions[0].person_name, 'buguser') + self.assertTrue(r.actions[0].tgt_package is None) + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="set_bugowner"> + <target project="foobar" /> + <person name="buguser" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_set_bugowner_package(self): + """create a set_bugowner request for a package""" + r = osc.core.Request() + r.add_action('set_bugowner', tgt_project='foobar', tgt_package='baz', person_name='buguser') + self.assertEqual(r.actions[0].type, 'set_bugowner') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertEqual(r.actions[0].tgt_package, 'baz') + self.assertEqual(r.actions[0].person_name, 'buguser') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="set_bugowner"> + <target package="baz" project="foobar" /> + <person name="buguser" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_delete_project(self): + """create a delete request for a project""" + r = osc.core.Request() + r.add_action('delete', tgt_project='foo') + self.assertEqual(r.actions[0].type, 'delete') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertTrue(r.actions[0].tgt_package is None) + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="delete"> + <target project="foo" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_delete_package(self): + """create a delete request for a package""" + r = osc.core.Request() + r.add_action('delete', tgt_project='foo', tgt_package='deleteme') + self.assertEqual(r.actions[0].type, 'delete') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].tgt_package, 'deleteme') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="delete"> + <target package="deleteme" project="foo" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_create_change_devel(self): + """create a change devel request""" + r = osc.core.Request() + r.add_action('change_devel', src_project='foo', src_package='bar', tgt_project='devprj', tgt_package='devpkg') + self.assertEqual(r.actions[0].type, 'change_devel') + self.assertEqual(r.actions[0].src_project, 'foo') + self.assertEqual(r.actions[0].src_package, 'bar') + self.assertEqual(r.actions[0].tgt_project, 'devprj') + self.assertEqual(r.actions[0].tgt_package, 'devpkg') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + exp = """<request> + <action type="change_devel"> + <source package="bar" project="foo" /> + <target package="devpkg" project="devprj" /> + </action> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_action_from_xml1(self): + """create action from xml""" + from xml.etree import cElementTree as ET + xml = """<action type="add_role"> + <target package="bar" project="foo" /> + <person name="user" role="reader" /> + <group name="group" role="reviewer" /> +</action>""" + action = osc.core.Action.from_xml(ET.fromstring(xml)) + self.assertEqual(action.type, 'add_role') + self.assertEqual(action.tgt_project, 'foo') + self.assertEqual(action.tgt_package, 'bar') + self.assertEqual(action.person_name, 'user') + self.assertEqual(action.person_role, 'reader') + self.assertEqual(action.group_name, 'group') + self.assertEqual(action.group_role, 'reviewer') + self.assertEqual(xml, action.to_str()) + + def test_action_from_xml2(self): + """create action from xml""" + from xml.etree import cElementTree as ET + xml = """<action type="submit"> + <source package="bar" project="foo" /> + <target package="bar" project="foobar" /> + <options> + <sourceupdate>cleanup</sourceupdate> + <updatelink>1</updatelink> + </options> +</action>""" + action = osc.core.Action.from_xml(ET.fromstring(xml)) + self.assertEqual(action.type, 'submit') + self.assertEqual(action.src_project, 'foo') + self.assertEqual(action.src_package, 'bar') + self.assertEqual(action.tgt_project, 'foobar') + self.assertEqual(action.tgt_package, 'bar') + self.assertEqual(action.opt_sourceupdate, 'cleanup') + self.assertEqual(action.opt_updatelink, '1') + self.assertTrue(action.src_rev is None) + self.assertEqual(xml, action.to_str()) + + def test_action_from_xml3(self): + """create action from xml (with acceptinfo element)""" + from xml.etree import cElementTree as ET + xml = """<action type="submit"> + <source package="bar" project="testprj" /> + <target package="baz" project="foobar" /> + <acceptinfo rev="5" srcmd5="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" xsrcmd5="ffffffffffffffffffffffffffffffff" /> +</action>""" + action = osc.core.Action.from_xml(ET.fromstring(xml)) + self.assertEqual(action.type, 'submit') + self.assertEqual(action.src_project, 'testprj') + self.assertEqual(action.src_package, 'bar') + self.assertEqual(action.tgt_project, 'foobar') + self.assertEqual(action.tgt_package, 'baz') + self.assertTrue(action.opt_sourceupdate is None) + self.assertTrue(action.opt_updatelink is None) + self.assertTrue(action.src_rev is None) + self.assertEqual(action.acceptinfo_rev, '5') + self.assertEqual(action.acceptinfo_srcmd5, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') + self.assertEqual(action.acceptinfo_xsrcmd5, 'ffffffffffffffffffffffffffffffff') + self.assertTrue(action.acceptinfo_osrcmd5 is None) + self.assertTrue(action.acceptinfo_oxsrcmd5 is None) + self.assertEqual(xml, action.to_str()) + + def test_action_from_xml_unknown_type(self): + """try to create action from xml with unknown type""" + from xml.etree import cElementTree as ET + xml = '<action type="foo"><source package="bar" project="foo" /></action>' + self.assertRaises(osc.oscerr.WrongArgs, osc.core.Action.from_xml, ET.fromstring(xml)) + + def test_read_request1(self): + """read in a request""" + from xml.etree import cElementTree as ET + xml = open(os.path.join(self._get_fixtures_dir(), 'test_read_request1.xml'), 'r').read().strip() + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.reqid, '42') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'foo') + self.assertEqual(r.actions[0].src_package, 'bar') + self.assertEqual(r.actions[0].src_rev, '1') + self.assertEqual(r.actions[0].tgt_project, 'foobar') + self.assertEqual(r.actions[0].tgt_package, 'bar') + self.assertTrue(r.actions[0].opt_sourceupdate is None) + self.assertTrue(r.actions[0].opt_updatelink is None) + self.assertEqual(r.actions[1].type, 'delete') + self.assertEqual(r.actions[1].tgt_project, 'deleteme') + self.assertTrue(r.actions[1].tgt_package is None) + self.assertEqual(r.state.name, 'accepted') + self.assertEqual(r.state.when, '2010-12-27T01:36:29') + self.assertEqual(r.state.who, 'user1') + self.assertEqual(r.state.comment, '') + self.assertEqual(r.statehistory[0].when, '2010-12-13T13:02:03') + self.assertEqual(r.statehistory[0].who, 'creator') + self.assertEqual(r.statehistory[0].comment, 'foobar') + self.assertEqual(r.title, 'title of the request') + self.assertEqual(r.description, 'this is a\nvery long\ndescription') + self.assertTrue(len(r.statehistory) == 1) + self.assertTrue(len(r.reviews) == 0) + self.assertEqual(xml, r.to_str()) + + def test_read_request2(self): + """read in a request (with reviews)""" + from xml.etree import cElementTree as ET + xml = open(os.path.join(self._get_fixtures_dir(), 'test_read_request2.xml'), 'r').read().strip() + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.reqid, '123') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'xyz') + self.assertEqual(r.actions[0].src_package, 'abc') + self.assertTrue(r.actions[0].src_rev is None) + self.assertEqual(r.actions[0].opt_sourceupdate, 'cleanup') + self.assertEqual(r.actions[0].opt_updatelink, '1') + self.assertEqual(r.actions[1].type, 'add_role') + self.assertEqual(r.actions[1].tgt_project, 'home:foo') + self.assertEqual(r.actions[1].person_name, 'bar') + self.assertEqual(r.actions[1].person_role, 'maintainer') + self.assertEqual(r.actions[1].group_name, 'groupxyz') + self.assertEqual(r.actions[1].group_role, 'reader') + self.assertTrue(r.actions[1].tgt_package is None) + self.assertEqual(r.state.name, 'review') + self.assertEqual(r.state.when, '2010-12-27T01:36:29') + self.assertEqual(r.state.who, 'abc') + self.assertEqual(r.state.comment, '') + self.assertEqual(r.reviews[0].state, 'new') + self.assertEqual(r.reviews[0].by_group, 'group1') + self.assertEqual(r.reviews[0].when, '2010-12-28T00:11:22') + self.assertEqual(r.reviews[0].who, 'abc') + self.assertEqual(r.reviews[0].comment, 'review start') + self.assertTrue(r.reviews[0].by_user is None) + self.assertEqual(r.statehistory[0].when, '2010-12-11T00:00:00') + self.assertEqual(r.statehistory[0].who, 'creator') + self.assertEqual(r.statehistory[0].comment, '') + self.assertEqual(r.get_creator(), 'creator') + self.assertTrue(len(r.statehistory) == 1) + self.assertTrue(len(r.reviews) == 1) + self.assertEqual(xml, r.to_str()) + + def test_read_request3(self): + """read in a request (with an "empty" comment+description)""" + from xml.etree import cElementTree as ET + xml = """<request id="2"> + <action type="set_bugowner"> + <target project="foo" /> + <person name="buguser" /> + </action> + <state name="new" when="2010-12-28T12:36:29" who="xyz"> + <comment></comment> + </state> + <description></description> +</request>""" + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.reqid, '2') + self.assertEqual(r.actions[0].type, 'set_bugowner') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].person_name, 'buguser') + self.assertEqual(r.state.name, 'new') + self.assertEqual(r.state.when, '2010-12-28T12:36:29') + self.assertEqual(r.state.who, 'xyz') + self.assertEqual(r.state.comment, '') + self.assertEqual(r.description, '') + self.assertTrue(len(r.statehistory) == 0) + self.assertTrue(len(r.reviews) == 0) + self.assertEqual(r.get_creator(), 'xyz') + exp = """<request id="2"> + <action type="set_bugowner"> + <target project="foo" /> + <person name="buguser" /> + </action> + <state name="new" when="2010-12-28T12:36:29" who="xyz" /> +</request>""" + + self.assertEqual(exp, r.to_str()) + + def test_request_list_view1(self): + """test the list_view method""" + from xml.etree import cElementTree as ET + xml = open(os.path.join(self._get_fixtures_dir(), 'test_request_list_view1.xml'), 'r').read().strip() + exp = """\ + 62 State:new By:Admin When:2010-12-29T14:57:25 + set_bugowner: buguser foo + add_role: person: xyz as maintainer, group: group1 as reader foobar + add_role: person: abc as reviewer foo/bar + change_devel: foo/bar developed in devprj/devpkg + submit: srcprj/srcpackage -> tgtprj/tgtpackage + submit: foo/bar -> baz + delete: deleteme + delete: foo/bar\n""" + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(exp, r.list_view()) + + def test_request_list_view2(self): + """test the list_view method (with history elements and description)""" + from xml.etree import cElementTree as ET + xml = open(os.path.join(self._get_fixtures_dir(), 'test_request_list_view2.xml'), 'r').read().strip() + r = osc.core.Request() + r.read(ET.fromstring(xml)) + exp = """\ + 21 State:accepted By:foobar When:2010-12-29T16:37:45 + set_bugowner: buguser foo + From: Created Request: user -> Review Approved: foobar + Descr: This is a simple request with a lot of ... ... text and other + stuff. This request also contains a description. This is useful + to describe the request. blabla blabla\n""" + self.assertEqual(exp, r.list_view()) + + def test_request_str1(self): + from xml.etree import cElementTree as ET + """test the __str__ method""" + xml = open(os.path.join(self._get_fixtures_dir(), 'test_request_str1.xml'), 'r').read().strip() + r = osc.core.Request() + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.get_creator(), 'creator') + exp = """\ +Request: #123 + + submit: xyz/abc(cleanup) -> foo ***update link*** + add_role: person: bar as maintainer, group: groupxyz as reader home:foo + + +Message: +just a samll description +in order to describe this +request - blablabla +test. + +State: review 2010-12-27T01:36:29 abc +Comment: currently in review + +Review: accepted Group: group1 2010-12-29T00:11:22 abc + accepted + new Group: group1 2010-12-28T00:11:22 abc + review start + +History: 2010-12-12T00:00:00 creator revoked + 2010-12-11T00:00:00 creator new""" + self.assertEqual(exp, str(r)) + + def test_request_str2(self): + """test the __str__ method""" + from xml.etree import cElementTree as ET + xml = """\ +<request id="98765"> + <action type="change_devel"> + <source project="devprj" package="devpkg" /> + <target project="foo" package="bar" /> + </action> + <action type="delete"> + <target project="deleteme" /> + </action> + <state name="new" when="2010-12-29T00:11:22" who="creator" /> +</request>""" + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.get_creator(), 'creator') + exp = """\ +Request: #98765 + + change_devel: foo/bar developed in devprj/devpkg + delete: deleteme + + +Message: +<no message> + +State: new 2010-12-29T00:11:22 creator +Comment: <no comment>""" + self.assertEqual(exp, str(r)) + + def test_legacy_request(self): + """load old-style submitrequest""" + from xml.etree import cElementTree as ET + xml = """\ +<request id="1234" type="submit"> + <submit> + <source package="baz" project="foobar" /> + <target package="baz" project="foo" /> + </submit> + <state name="new" when="2010-12-30T02:11:22" who="olduser" /> +</request>""" + r = osc.core.Request() + r.read(ET.fromstring(xml)) + self.assertEqual(r.reqid, '1234') + self.assertEqual(r.actions[0].type, 'submit') + self.assertEqual(r.actions[0].src_project, 'foobar') + self.assertEqual(r.actions[0].src_package, 'baz') + self.assertEqual(r.actions[0].tgt_project, 'foo') + self.assertEqual(r.actions[0].tgt_package, 'baz') + self.assertTrue(r.actions[0].opt_sourceupdate is None) + self.assertTrue(r.actions[0].opt_updatelink is None) + self.assertEqual(r.state.name, 'new') + self.assertEqual(r.state.when, '2010-12-30T02:11:22') + self.assertEqual(r.state.who, 'olduser') + self.assertEqual(r.state.comment, '') + self.assertEqual(r.get_creator(), 'olduser') + exp = """\ +<request id="1234"> + <action type="submit"> + <source package="baz" project="foobar" /> + <target package="baz" project="foo" /> + </action> + <state name="new" when="2010-12-30T02:11:22" who="olduser" /> +</request>""" + self.assertEqual(exp, r.to_str()) + + def test_get_actions(self): + """test get_actions method""" + from xml.etree import cElementTree as ET + xml = open(os.path.join(self._get_fixtures_dir(), 'test_request_list_view1.xml'), 'r').read().strip() + r = osc.core.Request() + r.read(ET.fromstring(xml)) + sr_actions = r.get_actions('submit') + self.assertTrue(len(sr_actions) == 2) + for i in sr_actions: + self.assertEqual(i.type, 'submit') + self.assertTrue(len(r.get_actions('submit', 'delete', 'change_devel')) == 5) + self.assertTrue(len(r.get_actions()) == 8) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_revertfiles.py b/tests/test_revertfiles.py new file mode 100644 index 0000000..5a8c443 --- /dev/null +++ b/tests/test_revertfiles.py @@ -0,0 +1,97 @@ +import osc.core +import osc.oscerr +import os +from common import OscTestCase + +FIXTURES_DIR = os.path.join(os.getcwd(), 'revertfile_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestRevertFiles) + +class TestRevertFiles(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + def testRevertUnchanged(self): + """revert an unchanged file (state == ' ')""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self.assertRaises(osc.oscerr.OscIOError, p.revert, 'toadd2') + self._check_status(p, 'toadd2', '?') + + def testRevertModified(self): + """revert a modified file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('nochange') + self.__check_file('nochange') + self._check_status(p, 'nochange', ' ') + + def testRevertAdded(self): + """revert an added file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('toadd1') + self.assertTrue(os.path.exists('toadd1')) + self._check_addlist('replaced\naddedmissing\n') + self._check_status(p, 'toadd1', '?') + + def testRevertDeleted(self): + """revert a deleted file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('somefile') + self.__check_file('somefile') + self._check_deletelist('deleted\n') + self._check_status(p, 'somefile', ' ') + + def testRevertMissing(self): + """revert a missing (state == '!') file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('missing') + self.__check_file('missing') + self._check_status(p, 'missing', ' ') + + def testRevertMissingAdded(self): + """revert a missing file which was added to the wc""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('addedmissing') + self._check_addlist('toadd1\nreplaced\n') + self.assertRaises(osc.oscerr.OscIOError, p.status, 'addedmissing') + + def testRevertReplaced(self): + """revert a replaced (state == 'R') file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('replaced') + self.__check_file('replaced') + self._check_addlist('toadd1\naddedmissing\n') + self._check_status(p, 'replaced', ' ') + + def testRevertConflict(self): + """revert a file which is in the conflict state""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + p.revert('foo') + self.__check_file('foo') + self.assertFalse(os.path.exists(os.path.join('.osc', '_in_conflict'))) + self._check_status(p, 'foo', ' ') + + def testRevertSkipped(self): + """revert a skipped file""" + self._change_to_pkg('simple') + p = osc.core.Package('.') + self.assertRaises(osc.oscerr.OscIOError, p.revert, 'skipped') + + def __check_file(self, fname): + storefile = os.path.join('.osc', fname) + self.assertTrue(os.path.exists(fname)) + self.assertTrue(os.path.exists(storefile)) + self.assertEqual(open(fname, 'r').read(), open(storefile, 'r').read()) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_setlinkrev.py b/tests/test_setlinkrev.py new file mode 100644 index 0000000..2e91f54 --- /dev/null +++ b/tests/test_setlinkrev.py @@ -0,0 +1,93 @@ +import osc.core +import osc.oscerr +import os +from common import GET, PUT, OscTestCase +FIXTURES_DIR = os.path.join(os.getcwd(), 'setlinkrev_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestSetLinkRev) + +class TestSetLinkRev(OscTestCase): + def setUp(self): + OscTestCase.setUp(self, copytree=False) + + def _get_fixtures_dir(self): + return FIXTURES_DIR + + @GET('http://localhost/source/osctest/simple/_link', file='simple_link') + @GET('http://localhost/source/srcprj/srcpkg?rev=latest', file='simple_filesremote') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" rev="42" />', text='dummytext') + def test_simple1(self): + """a simple set_link_rev call without revision""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple') + + @GET('http://localhost/source/osctest/simple/_link', file='simple_link') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" rev="42" />', text='dummytext') + def test_simple2(self): + """a simple set_link_rev call with revision""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', '42') + + @GET('http://localhost/source/osctest/simple/_link', file='noproject_link') + @GET('http://localhost/source/osctest/srcpkg?rev=latest&expand=1', file='expandedsrc_filesremote') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" rev="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" vrev="1" />', text='dummytext') + def test_expandedsrc(self): + """expand src package""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', expand=True) + + @GET('http://localhost/source/osctest/simple/_link', file='link_with_rev') + @GET('http://localhost/source/srcprj/srcpkg?rev=latest', file='simple_filesremote') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" rev="42" />', text='dummytext') + def test_existingrev(self): + """link already has a rev attribute, update it to current version""" + # we could also avoid the superfluous PUT + osc.core.set_link_rev('http://localhost', 'osctest', 'simple') + + @GET('http://localhost/source/osctest/simple/_link', file='link_with_rev') + @GET('http://localhost/source/srcprj/srcpkg?rev=latest&expand=1', file='expandedsrc_filesremote') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" rev="eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" vrev="1" />', + text='dummytext') + def test_expandexistingrev(self): + """link already has a rev attribute, update it to current version""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', expand=True) + + @GET('http://localhost/source/osctest/simple/_link', file='simple_link') + @GET('http://localhost/source/srcprj/srcpkg?rev=latest&expand=1', text='conflict in file merge', code=400) + def test_linkerror(self): + """link is broken""" + try: + from urllib.error import HTTPError + except ImportError: + from urllib2 import HTTPError + # the backend returns status 400 if we try to expand a broken _link + self.assertRaises(HTTPError, osc.core.set_link_rev, 'http://localhost', 'osctest', 'simple', expand=True) + + @GET('http://localhost/source/osctest/simple/_link', file='rev_link') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" />', text='dummytext') + def test_deleterev(self): + """delete rev attribute from link xml""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', revision=None) + + @GET('http://localhost/source/osctest/simple/_link', file='md5_rev_link') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" />', text='dummytext') + def test_deleterev(self): + """delete rev and vrev attribute from link xml""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', revision=None) + + @GET('http://localhost/source/osctest/simple/_link', file='simple_link') + @PUT('http://localhost/source/osctest/simple/_link', + exp='<link package="srcpkg" project="srcprj" />', text='dummytext') + def test_deleterevnonexistent(self): + """delete non existent rev attribute from link xml""" + osc.core.set_link_rev('http://localhost', 'osctest', 'simple', revision=None) + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/test_update.py b/tests/test_update.py new file mode 100644 index 0000000..aaffcab --- /dev/null +++ b/tests/test_update.py @@ -0,0 +1,288 @@ +import osc.core +import osc.oscerr +import os +import sys +from common import GET, OscTestCase +FIXTURES_DIR = os.path.join(os.getcwd(), 'update_fixtures') + +def suite(): + import unittest + return unittest.makeSuite(TestUpdate) + +class TestUpdate(OscTestCase): + def _get_fixtures_dir(self): + return FIXTURES_DIR + + @GET('http://localhost/source/osctest/simple?rev=latest', file='testUpdateNoChanges_files') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateNoChanges(self): + """update without any changes (the wc is the most recent version)""" + self._change_to_pkg('simple') + osc.core.Package('.').update() + self.assertEqual(sys.stdout.getvalue(), 'At revision 1.\n') + + @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateNewFile_files') + @GET('http://localhost/source/osctest/simple/upstream_added?rev=2', file='testUpdateNewFile_upstream_added') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateNewFile(self): + """a new file was added to the remote package""" + self._change_to_pkg('simple') + osc.core.Package('.').update(rev=2) + exp = 'A upstream_added\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testUpdateNewFile_files') + + @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateNewFileLocalExists_files') + def testUpdateNewFileLocalExists(self): + """ + a new file was added to the remote package but the same (unversioned) + file exists locally + """ + self._change_to_pkg('simple') + self.assertRaises(osc.oscerr.PackageFileConflict, osc.core.Package('.').update, rev=2) + + @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateDeletedFile_files') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateDeletedFile(self): + """a file was deleted from the remote package""" + self._change_to_pkg('simple') + osc.core.Package('.').update(rev=2) + exp = 'D foo\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testUpdateDeletedFile_files') + self.assertFalse(os.path.exists('foo')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + + @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateUpstreamModifiedFile_files') + @GET('http://localhost/source/osctest/simple/foo?rev=2', file='testUpdateUpstreamModifiedFile_foo') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateUpstreamModifiedFile(self): + """a file was modified in the remote package (local file isn't modified)""" + + self._change_to_pkg('simple') + osc.core.Package('.').update(rev=2) + exp = 'U foo\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testUpdateUpstreamModifiedFile_files') + + @GET('http://localhost/source/osctest/conflict?rev=2', file='testUpdateConflict_files') + @GET('http://localhost/source/osctest/conflict/merge?rev=2', file='testUpdateConflict_merge') + @GET('http://localhost/source/osctest/conflict/_meta', file='meta.xml') + def testUpdateConflict(self): + """ + a file was modified in the remote package (local file is also modified + and a merge isn't possible) + """ + self._change_to_pkg('conflict') + osc.core.Package('.').update(rev=2) + exp = 'C merge\nAt revision 2.\n' + self._check_digests('testUpdateConflict_files') + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_conflictlist('merge\n') + + @GET('http://localhost/source/osctest/already_in_conflict?rev=2', file='testUpdateAlreadyInConflict_files') + @GET('http://localhost/source/osctest/already_in_conflict/merge?rev=2', file='testUpdateAlreadyInConflict_merge') + @GET('http://localhost/source/osctest/already_in_conflict/_meta', file='meta.xml') + def testUpdateAlreadyInConflict(self): + """ + a file was modified in the remote package (the local file is already in conflict) + """ + self._change_to_pkg('already_in_conflict') + osc.core.Package('.').update(rev=2) + exp = 'skipping \'merge\' (this is due to conflicts)\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_conflictlist('merge\n') + self._check_digests('testUpdateAlreadyInConflict_files') + + @GET('http://localhost/source/osctest/deleted?rev=2', file='testUpdateLocalDeletions_files') + @GET('http://localhost/source/osctest/deleted/foo?rev=2', file='testUpdateLocalDeletions_foo') + @GET('http://localhost/source/osctest/deleted/merge?rev=2', file='testUpdateLocalDeletions_merge') + @GET('http://localhost/source/osctest/deleted/_meta', file='meta.xml') + def testUpdateLocalDeletions(self): + """ + the files 'foo' and 'merge' were modified in the remote package + and marked for deletion in the local wc. Additionally the file + 'merge' was modified in the wc before deletion so the local file + still exists (and a merge with the remote file is not possible) + """ + self._change_to_pkg('deleted') + osc.core.Package('.').update(rev=2) + exp = 'U foo\nC merge\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_deletelist('foo\n') + self._check_conflictlist('merge\n') + self.assertEqual(open('foo', 'r').read(), open(os.path.join('.osc', 'foo'), 'r').read()) + self._check_digests('testUpdateLocalDeletions_files') + + @GET('http://localhost/source/osctest/restore?rev=latest', file='testUpdateRestore_files') + @GET('http://localhost/source/osctest/restore/foo?rev=1', file='testUpdateRestore_foo') + @GET('http://localhost/source/osctest/restore/_meta', file='meta.xml') + def testUpdateRestore(self): + """local file 'foo' was deleted with a non osc command and will be restored""" + self._change_to_pkg('restore') + osc.core.Package('.').update() + exp = 'Restored \'foo\'\nAt revision 1.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testUpdateRestore_files') + + @GET('http://localhost/source/osctest/limitsize?rev=latest', file='testUpdateLimitSizeNoChange_filesremote') + @GET('http://localhost/source/osctest/limitsize/_meta', file='meta.xml') + def testUpdateLimitSizeNoChange(self): + """ + a new file was added to the remote package but isn't checked out because + of the size constraint + """ + self._change_to_pkg('limitsize') + osc.core.Package('.').update(size_limit=50) + exp = 'D bigfile\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) + self.assertFalse(os.path.exists('bigfile')) + self._check_digests('testUpdateLimitSizeNoChange_files', 'bigfile') + + @GET('http://localhost/source/osctest/limitsize_local?rev=latest', file='testUpdateLocalLimitSizeNoChange_filesremote') + @GET('http://localhost/source/osctest/limitsize_local/_meta', file='meta.xml') + def testUpdateLocalLimitSizeNoChange(self): + """ + a new file was added to the remote package but isn't checked out because + of the local size constraint + """ + self._change_to_pkg('limitsize_local') + p = osc.core.Package('.') + p.update() + exp = 'D bigfile\nD merge\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) + self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertFalse(os.path.exists('bigfile')) + self._check_digests('testUpdateLocalLimitSizeNoChange_files', 'bigfile', 'merge') + self._check_status(p, 'bigfile', 'S') + self._check_status(p, 'merge', 'S') + + @GET('http://localhost/source/osctest/limitsize?rev=latest', file='testUpdateLimitSizeAddDelete_filesremote') + @GET('http://localhost/source/osctest/limitsize/exists?rev=2', file='testUpdateLimitSizeAddDelete_exists') + @GET('http://localhost/source/osctest/limitsize/_meta', file='meta.xml') + def testUpdateLimitSizeAddDelete(self): + """ + a new file (exists) was added to the remote package with + size < size_limit and one file (nochange) was deleted from the + remote package (local file 'nochange' is modified). Additionally + files which didn't change are removed the local wc due to the + size constraint. + """ + self._change_to_pkg('limitsize') + osc.core.Package('.').update(size_limit=10) + exp = 'A exists\nD bigfile\nD foo\nD merge\nD nochange\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', 'bigfile'))) + self.assertFalse(os.path.exists('bigfile')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'foo'))) + self.assertFalse(os.path.exists('foo')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'merge'))) + self.assertFalse(os.path.exists('merge')) + # exists because local version is modified + self.assertTrue(os.path.exists('nochange')) + + self._check_digests('testUpdateLimitSizeAddDelete_files', 'bigfile', 'foo', 'merge', 'nochange') + + @GET('http://localhost/source/osctest/services?rev=latest', file='testUpdateServiceFilesAddDelete_filesremote') + @GET('http://localhost/source/osctest/services/bigfile?rev=2', file='testUpdateServiceFilesAddDelete_bigfile') + @GET('http://localhost/source/osctest/services/_service%3Abar?rev=2', file='testUpdateServiceFilesAddDelete__service:bar') + @GET('http://localhost/source/osctest/services/_service%3Afoo?rev=2', file='testUpdateServiceFilesAddDelete__service:foo') + @GET('http://localhost/source/osctest/services/_meta', file='meta.xml') + def testUpdateAddDeleteServiceFiles(self): + """update package with _service:* files""" + self._change_to_pkg('services') + osc.core.Package('.').update(service_files=True) + exp = 'A bigfile\nD _service:exists\nA _service:bar\nA _service:foo\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', '_service:bar'))) + self.assertTrue(os.path.exists('_service:bar')) + self.assertEqual(open('_service:bar').read(), 'another service\n') + self.assertFalse(os.path.exists(os.path.join('.osc', '_service:foo'))) + self.assertTrue(os.path.exists('_service:foo')) + self.assertEqual(open('_service:foo').read(), 'small\n') + self.assertTrue(os.path.exists('_service:exists')) + self._check_digests('testUpdateServiceFilesAddDelete_files', '_service:foo', '_service:bar') + + @GET('http://localhost/source/osctest/services?rev=latest', file='testUpdateServiceFilesAddDelete_filesremote') + @GET('http://localhost/source/osctest/services/bigfile?rev=2', file='testUpdateServiceFilesAddDelete_bigfile') + @GET('http://localhost/source/osctest/services/_meta', file='meta.xml') + def testUpdateDisableAddDeleteServiceFiles(self): + """update package with _service:* files (with service_files=False)""" + self._change_to_pkg('services') + osc.core.Package('.').update() + exp = 'A bigfile\nD _service:exists\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', '_service:bar'))) + self.assertFalse(os.path.exists('_service:bar')) + self.assertFalse(os.path.exists(os.path.join('.osc', '_service:foo'))) + self.assertFalse(os.path.exists('_service:foo')) + self.assertTrue(os.path.exists('_service:exists')) + self._check_digests('testUpdateServiceFilesAddDelete_files', '_service:foo', '_service:bar') + + @GET('http://localhost/source/osctest/metamode?meta=1&rev=latest', file='testUpdateMetaMode_filesremote') + @GET('http://localhost/source/osctest/metamode/_meta?rev=1', file='testUpdateMetaMode__meta') + def testUpdateMetaMode(self): + """update package with metamode enabled""" + self._change_to_pkg('metamode') + p = osc.core.Package('.') + p.update() + exp = 'A _meta\nD foo\nD merge\nD nochange\nAt revision 1.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists('foo')) + self.assertFalse(os.path.exists('merge')) + self.assertFalse(os.path.exists('nochange')) + self._check_digests('testUpdateMetaMode_filesremote') + self._check_status(p, '_meta', ' ') + + @GET('http://localhost/source/osctest/new?rev=latest', file='testUpdateNew_filesremote') + @GET('http://localhost/source/osctest/new/_meta', file='meta.xml') + def testUpdateNew(self): + """update a new (empty) package. The package has no revision.""" + self._change_to_pkg('new') + p = osc.core.Package('.') + p.update() + exp = 'At revision None.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self._check_digests('testUpdateNew_filesremote') + + # tests to recover from an aborted/broken update + + @GET('http://localhost/source/osctest/simple/foo?rev=2', file='testUpdateResume_foo') + @GET('http://localhost/source/osctest/simple/merge?rev=2', file='testUpdateResume_merge') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + @GET('http://localhost/source/osctest/simple?rev=2', file='testUpdateResume_files') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateResume(self): + """resume an aborted update""" + self._change_to_pkg('resume') + osc.core.Package('.').update(rev=2) + exp = 'resuming broken update...\nU foo\nU merge\nAt revision 2.\nAt revision 2.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', '_in_update'))) + self._check_digests('testUpdateResume_files') + + @GET('http://localhost/source/osctest/simple/foo?rev=1', file='testUpdateResumeDeletedFile_foo') + @GET('http://localhost/source/osctest/simple/merge?rev=1', file='testUpdateResumeDeletedFile_merge') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + @GET('http://localhost/source/osctest/simple?rev=1', file='testUpdateResumeDeletedFile_files') + @GET('http://localhost/source/osctest/simple/_meta', file='meta.xml') + def testUpdateResumeDeletedFile(self): + """ + resume an aborted update (the file 'added' was already deleted in the first update + run). It's marked as deleted again (this is due to an expected issue with the update + code) + """ + self._change_to_pkg('resume_deleted') + osc.core.Package('.').update(rev=1) + exp = 'resuming broken update...\nD added\nU foo\nU merge\nAt revision 1.\nAt revision 1.\n' + self.assertEqual(sys.stdout.getvalue(), exp) + self.assertFalse(os.path.exists(os.path.join('.osc', '_in_update'))) + self.assertFalse(os.path.exists('added')) + self.assertFalse(os.path.exists(os.path.join('.osc', 'added'))) + self._check_digests('testUpdateResumeDeletedFile_files') + +if __name__ == '__main__': + import unittest + unittest.main() diff --git a/tests/update_fixtures/meta.xml b/tests/update_fixtures/meta.xml new file mode 100644 index 0000000..abd3720 --- /dev/null +++ b/tests/update_fixtures/meta.xml @@ -0,0 +1,8 @@ +<package project="osctest" name="simple"> + <title/> + <description> + + </description> + <person userid="Admin" role="maintainer"/> + <person userid="Admin" role="bugowner"/> +</package>
\ No newline at end of file diff --git a/tests/update_fixtures/oscrc b/tests/update_fixtures/oscrc new file mode 100644 index 0000000..a30e040 --- /dev/null +++ b/tests/update_fixtures/oscrc @@ -0,0 +1,103 @@ +[general] +# URL to access API server, e.g. https://api.opensuse.org +# you also need a section [https://api.opensuse.org] with the credentials +apiurl = http://localhost +# Downloaded packages are cached here. Must be writable by you. +#packagecachedir = /var/tmp/osbuild-packagecache +# Wrapper to call build as root (sudo, su -, ...) +#su-wrapper = su -c +# rootdir to setup the chroot environment +# can contain %(repo)s, %(arch)s, %(project)s and %(package)s for replacement, e.g. +# /srv/oscbuild/%(repo)s-%(arch)s or +# /srv/oscbuild/%(repo)s-%(arch)s-%(project)s-%(package)s +#build-root = /var/tmp/build-root +# compile with N jobs (default: "getconf _NPROCESSORS_ONLN") +#build-jobs = N +# build-type to use - values can be (depending on the capabilities of the 'build' script) +# empty - chroot build +# kvm - kvm VM build (needs build-device, build-swap, build-memory) +# xen - xen VM build (needs build-device, build-swap, build-memory) +# experimental: +# qemu - qemu VM build +# lxc - lxc build +#build-type = +# build-device is the disk-image file to use as root for VM builds +# e.g. /var/tmp/FILE.root +#build-device = /var/tmp/FILE.root +# build-swap is the disk-image to use as swap for VM builds +# e.g. /var/tmp/FILE.swap +#build-swap = /var/tmp/FILE.swap +# build-memory is the amount of memory used in the VM +# value in MB - e.g. 512 +#build-memory = 512 +# build-vmdisk-rootsize is the size of the disk-image used as root in a VM build +# values in MB - e.g. 4096 +#build-vmdisk-rootsize = 4096 +# build-vmdisk-swapsize is the size of the disk-image used as swap in a VM build +# values in MB - e.g. 1024 +#build-vmdisk-swapsize = 1024 +# Numeric uid:gid to assign to the "abuild" user in the build-root +# or "caller" to use the current users uid:gid +# This is convenient when sharing the buildroot with ordinary userids +# on the host. +# This should not be 0 +# build-uid = +# extra packages to install when building packages locally (osc build) +# this corresponds to osc build's -x option and can be overridden with that +# -x '' can also be given on the command line to override this setting, or +# you can have an empty setting here. +#extra-pkgs = vim gdb strace +# build platform is used if the platform argument is omitted to osc build +#build_repository = openSUSE_Factory +# default project for getpac or bco +#getpac_default_project = openSUSE:Factory +# alternate filesystem layout: have multiple subdirs, where colons were. +#checkout_no_colon = 0 +# local files to ignore with status, addremove, .... +#exclude_glob = .osc CVS .svn .* _linkerror *~ #*# *.orig *.bak *.changes.* +# keep passwords in plaintext. If you see this comment, your osc +# already uses the encrypted password, and only keeps them in plain text +# for backwards compatibility. Default will change to 0 in future releases. +# You can remove the plaintext password without harm, if you do not need +# backwards compatibility. +#plaintext_passwd = 1 +# limit the age of requests shown with 'osc req list'. +# this is a default only, can be overridden by 'osc req list -D NNN' +# Use 0 for unlimted. +#request_list_days = 0 +# show info useful for debugging +#debug = 1 +# show HTTP traffic useful for debugging +#http_debug = 1 +# Skip signature verification of packages used for build. +#no_verify = 1 +# jump into the debugger in case of errors +#post_mortem = 1 +# print call traces in case of errors +#traceback = 1 +# use KDE/Gnome/MacOS/Windows keyring for credentials if available +#use_keyring = 1 +# check for unversioned/removed files before commit +#check_filelist = 1 +# check for pending requests after executing an action (e.g. checkout, update, commit) +#check_for_request_on_action = 0 +# what to do with the source package if the submitrequest has been accepted. If +# nothing is specified the API default is used +#submitrequest_on_accept_action = cleanup|update|noupdate +#review requests interactively (default: off) +#request_show_review = 1 +# Directory with executables to validate sources, esp before committing +#source_validator_directory = /usr/lib/osc/source_validators + +[http://localhost] +user=Admin +pass=opensuse +# set aliases for this apiurl +# aliases = foo, bar +# email used in .changes, unless the one from osc meta prj <user> will be used +# email = +# additional headers to pass to a request, e.g. for special authentication +#http_headers = Host: foofoobar, +# User: mumblegack +# Force using of keyring for this API +#keyring = 1 diff --git a/tests/update_fixtures/osctest/.osc/_apiurl b/tests/update_fixtures/osctest/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/.osc/_packages b/tests/update_fixtures/osctest/.osc/_packages new file mode 100644 index 0000000..04e56f0 --- /dev/null +++ b/tests/update_fixtures/osctest/.osc/_packages @@ -0,0 +1 @@ +<project name="osctest" /> diff --git a/tests/update_fixtures/osctest/.osc/_project b/tests/update_fixtures/osctest/.osc/_project new file mode 100644 index 0000000..b83ffd3 --- /dev/null +++ b/tests/update_fixtures/osctest/.osc/_project @@ -0,0 +1 @@ +osctest diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_apiurl b/tests/update_fixtures/osctest/already_in_conflict/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_files b/tests/update_fixtures/osctest/already_in_conflict/.osc/_files new file mode 100644 index 0000000..2ad5954 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_files @@ -0,0 +1,5 @@ +<directory name="already_in_conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282133912" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282133912" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282133912" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_in_conflict b/tests/update_fixtures/osctest/already_in_conflict/.osc/_in_conflict new file mode 100644 index 0000000..a00af07 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_in_conflict @@ -0,0 +1 @@ +merge diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_meta b/tests/update_fixtures/osctest/already_in_conflict/.osc/_meta new file mode 100644 index 0000000..0150d60 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_meta @@ -0,0 +1,8 @@ +<package project="osctest" name="already_in_conflict"> + <title/> + <description> + + </description> + <person userid="Admin" role="maintainer"/> + <person userid="Admin" role="bugowner"/> +</package>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_osclib_version b/tests/update_fixtures/osctest/already_in_conflict/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_package b/tests/update_fixtures/osctest/already_in_conflict/.osc/_package new file mode 100644 index 0000000..c2cae8d --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_package @@ -0,0 +1 @@ +already_in_conflict
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/_project b/tests/update_fixtures/osctest/already_in_conflict/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/foo b/tests/update_fixtures/osctest/already_in_conflict/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/merge b/tests/update_fixtures/osctest/already_in_conflict/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/already_in_conflict/.osc/nochange b/tests/update_fixtures/osctest/already_in_conflict/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/already_in_conflict/foo b/tests/update_fixtures/osctest/already_in_conflict/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/already_in_conflict/merge b/tests/update_fixtures/osctest/already_in_conflict/merge new file mode 100644 index 0000000..7469d51 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/merge @@ -0,0 +1,2 @@ +Is it +I hope so... diff --git a/tests/update_fixtures/osctest/already_in_conflict/nochange b/tests/update_fixtures/osctest/already_in_conflict/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/already_in_conflict/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/conflict/.osc/_apiurl b/tests/update_fixtures/osctest/conflict/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/conflict/.osc/_files b/tests/update_fixtures/osctest/conflict/.osc/_files new file mode 100644 index 0000000..a67ff42 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/_files @@ -0,0 +1,5 @@ +<directory name="conflict" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282130148" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282130148" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/conflict/.osc/_osclib_version b/tests/update_fixtures/osctest/conflict/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/conflict/.osc/_package b/tests/update_fixtures/osctest/conflict/.osc/_package new file mode 100644 index 0000000..783a0ef --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/_package @@ -0,0 +1 @@ +conflict
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/conflict/.osc/_project b/tests/update_fixtures/osctest/conflict/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/conflict/.osc/foo b/tests/update_fixtures/osctest/conflict/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/conflict/.osc/merge b/tests/update_fixtures/osctest/conflict/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/conflict/.osc/nochange b/tests/update_fixtures/osctest/conflict/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/conflict/foo b/tests/update_fixtures/osctest/conflict/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/conflict/merge b/tests/update_fixtures/osctest/conflict/merge new file mode 100644 index 0000000..f4ff164 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/merge @@ -0,0 +1,4 @@ +Is it possible +to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/conflict/nochange b/tests/update_fixtures/osctest/conflict/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/conflict/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/deleted/.osc/_apiurl b/tests/update_fixtures/osctest/deleted/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/deleted/.osc/_files b/tests/update_fixtures/osctest/deleted/.osc/_files new file mode 100644 index 0000000..d9a5451 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_files @@ -0,0 +1,5 @@ +<directory name="deleted" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282134731" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282134731" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282134731" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/deleted/.osc/_osclib_version b/tests/update_fixtures/osctest/deleted/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/deleted/.osc/_package b/tests/update_fixtures/osctest/deleted/.osc/_package new file mode 100644 index 0000000..3c22137 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_package @@ -0,0 +1 @@ +deleted
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/deleted/.osc/_project b/tests/update_fixtures/osctest/deleted/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/deleted/.osc/_to_be_deleted b/tests/update_fixtures/osctest/deleted/.osc/_to_be_deleted new file mode 100644 index 0000000..fa7a1f7 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/_to_be_deleted @@ -0,0 +1,2 @@ +merge +foo diff --git a/tests/update_fixtures/osctest/deleted/.osc/foo b/tests/update_fixtures/osctest/deleted/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/deleted/.osc/merge b/tests/update_fixtures/osctest/deleted/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/deleted/.osc/nochange b/tests/update_fixtures/osctest/deleted/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/deleted/merge b/tests/update_fixtures/osctest/deleted/merge new file mode 100644 index 0000000..c229519 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/merge @@ -0,0 +1,3 @@ +Is it possible to, +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/deleted/nochange b/tests/update_fixtures/osctest/deleted/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/deleted/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/limitsize/.osc/_apiurl b/tests/update_fixtures/osctest/limitsize/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/limitsize/.osc/_files b/tests/update_fixtures/osctest/limitsize/.osc/_files new file mode 100644 index 0000000..77d67af --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/_files @@ -0,0 +1,5 @@ +<directory name="limitsize" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/limitsize/.osc/_osclib_version b/tests/update_fixtures/osctest/limitsize/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/limitsize/.osc/_package b/tests/update_fixtures/osctest/limitsize/.osc/_package new file mode 100644 index 0000000..edc7cc1 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/_package @@ -0,0 +1 @@ +limitsize diff --git a/tests/update_fixtures/osctest/limitsize/.osc/_project b/tests/update_fixtures/osctest/limitsize/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/limitsize/.osc/foo b/tests/update_fixtures/osctest/limitsize/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/limitsize/.osc/merge b/tests/update_fixtures/osctest/limitsize/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/limitsize/.osc/nochange b/tests/update_fixtures/osctest/limitsize/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/limitsize/foo b/tests/update_fixtures/osctest/limitsize/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/limitsize/merge b/tests/update_fixtures/osctest/limitsize/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/limitsize/nochange b/tests/update_fixtures/osctest/limitsize/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_apiurl b/tests/update_fixtures/osctest/limitsize_local/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_files b/tests/update_fixtures/osctest/limitsize_local/.osc/_files new file mode 100644 index 0000000..77d67af --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_files @@ -0,0 +1,5 @@ +<directory name="limitsize" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_osclib_version b/tests/update_fixtures/osctest/limitsize_local/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_package b/tests/update_fixtures/osctest/limitsize_local/.osc/_package new file mode 100644 index 0000000..64a5ed3 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_package @@ -0,0 +1 @@ +limitsize_local diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_project b/tests/update_fixtures/osctest/limitsize_local/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/_size_limit b/tests/update_fixtures/osctest/limitsize_local/.osc/_size_limit new file mode 100644 index 0000000..64bb6b7 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/_size_limit @@ -0,0 +1 @@ +30 diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/foo b/tests/update_fixtures/osctest/limitsize_local/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/merge b/tests/update_fixtures/osctest/limitsize_local/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/limitsize_local/.osc/nochange b/tests/update_fixtures/osctest/limitsize_local/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/limitsize_local/foo b/tests/update_fixtures/osctest/limitsize_local/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/limitsize_local/merge b/tests/update_fixtures/osctest/limitsize_local/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/limitsize_local/nochange b/tests/update_fixtures/osctest/limitsize_local/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/update_fixtures/osctest/limitsize_local/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/update_fixtures/osctest/metamode/.osc/_apiurl b/tests/update_fixtures/osctest/metamode/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/metamode/.osc/_files b/tests/update_fixtures/osctest/metamode/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/metamode/.osc/_meta_mode b/tests/update_fixtures/osctest/metamode/.osc/_meta_mode new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_meta_mode diff --git a/tests/update_fixtures/osctest/metamode/.osc/_osclib_version b/tests/update_fixtures/osctest/metamode/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/metamode/.osc/_package b/tests/update_fixtures/osctest/metamode/.osc/_package new file mode 100644 index 0000000..862084f --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_package @@ -0,0 +1 @@ +metamode diff --git a/tests/update_fixtures/osctest/metamode/.osc/_project b/tests/update_fixtures/osctest/metamode/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/metamode/.osc/foo b/tests/update_fixtures/osctest/metamode/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/metamode/.osc/merge b/tests/update_fixtures/osctest/metamode/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/metamode/.osc/nochange b/tests/update_fixtures/osctest/metamode/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/metamode/foo b/tests/update_fixtures/osctest/metamode/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/metamode/merge b/tests/update_fixtures/osctest/metamode/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/metamode/nochange b/tests/update_fixtures/osctest/metamode/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/metamode/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/new/.osc/_apiurl b/tests/update_fixtures/osctest/new/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/new/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/new/.osc/_files b/tests/update_fixtures/osctest/new/.osc/_files new file mode 100644 index 0000000..d915967 --- /dev/null +++ b/tests/update_fixtures/osctest/new/.osc/_files @@ -0,0 +1 @@ +<directory name="new" /> diff --git a/tests/update_fixtures/osctest/new/.osc/_osclib_version b/tests/update_fixtures/osctest/new/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/new/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/new/.osc/_package b/tests/update_fixtures/osctest/new/.osc/_package new file mode 100644 index 0000000..3e75765 --- /dev/null +++ b/tests/update_fixtures/osctest/new/.osc/_package @@ -0,0 +1 @@ +new diff --git a/tests/update_fixtures/osctest/new/.osc/_project b/tests/update_fixtures/osctest/new/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/new/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/restore/.osc/_apiurl b/tests/update_fixtures/osctest/restore/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/restore/.osc/_files b/tests/update_fixtures/osctest/restore/.osc/_files new file mode 100644 index 0000000..a6b0cc6 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/_files @@ -0,0 +1,5 @@ +<directory name="restore" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/restore/.osc/_osclib_version b/tests/update_fixtures/osctest/restore/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/restore/.osc/_package b/tests/update_fixtures/osctest/restore/.osc/_package new file mode 100644 index 0000000..a9db91d --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/_package @@ -0,0 +1 @@ +restore diff --git a/tests/update_fixtures/osctest/restore/.osc/_project b/tests/update_fixtures/osctest/restore/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/restore/.osc/foo b/tests/update_fixtures/osctest/restore/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/restore/.osc/merge b/tests/update_fixtures/osctest/restore/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/restore/.osc/nochange b/tests/update_fixtures/osctest/restore/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/restore/exists b/tests/update_fixtures/osctest/restore/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/exists diff --git a/tests/update_fixtures/osctest/restore/merge b/tests/update_fixtures/osctest/restore/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/restore/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/restore/nochange b/tests/update_fixtures/osctest/restore/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/restore/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/resume/.osc/_apiurl b/tests/update_fixtures/osctest/resume/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/resume/.osc/_files b/tests/update_fixtures/osctest/resume/.osc/_files new file mode 100644 index 0000000..e4f249e --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="ff22941336956098ae9a564289d1bf1b" mtime="1282137256" name="added" size="15" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/resume/.osc/_in_update/_files b/tests/update_fixtures/osctest/resume/.osc/_in_update/_files new file mode 100644 index 0000000..0b0a0c8 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_in_update/_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="3ac41c59a5ed169d5ffef4d824700f7d" vrev="2"> + <entry md5="ff22941336956098ae9a564289d1bf1b" mtime="1282137256" name="added" size="15" /> + <entry md5="14758f1afd44c09b7992073ccf00b43d" mtime="1282137220" name="foo" size="7" /> + <entry md5="256d8f76ba7a0a231fb46a84866f25d8" mtime="1282137238" name="merge" size="20" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/resume/.osc/_in_update/foo b/tests/update_fixtures/osctest/resume/.osc/_in_update/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_in_update/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/resume/.osc/_meta b/tests/update_fixtures/osctest/resume/.osc/_meta new file mode 100644 index 0000000..abd3720 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_meta @@ -0,0 +1,8 @@ +<package project="osctest" name="simple"> + <title/> + <description> + + </description> + <person userid="Admin" role="maintainer"/> + <person userid="Admin" role="bugowner"/> +</package>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume/.osc/_osclib_version b/tests/update_fixtures/osctest/resume/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/resume/.osc/_package b/tests/update_fixtures/osctest/resume/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume/.osc/_project b/tests/update_fixtures/osctest/resume/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume/.osc/added b/tests/update_fixtures/osctest/resume/.osc/added new file mode 100644 index 0000000..0527e6b --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/added @@ -0,0 +1 @@ +This is a test diff --git a/tests/update_fixtures/osctest/resume/.osc/foo b/tests/update_fixtures/osctest/resume/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/resume/.osc/merge b/tests/update_fixtures/osctest/resume/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/resume/.osc/nochange b/tests/update_fixtures/osctest/resume/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/resume/added b/tests/update_fixtures/osctest/resume/added new file mode 100644 index 0000000..0527e6b --- /dev/null +++ b/tests/update_fixtures/osctest/resume/added @@ -0,0 +1 @@ +This is a test diff --git a/tests/update_fixtures/osctest/resume/exists b/tests/update_fixtures/osctest/resume/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/exists diff --git a/tests/update_fixtures/osctest/resume/foo b/tests/update_fixtures/osctest/resume/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/resume/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/resume/merge b/tests/update_fixtures/osctest/resume/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/resume/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/resume/nochange b/tests/update_fixtures/osctest/resume/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/resume/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_apiurl b/tests/update_fixtures/osctest/resume_deleted/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_files b/tests/update_fixtures/osctest/resume_deleted/.osc/_files new file mode 100644 index 0000000..5796136 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="1" srcmd5="3ac41c59a5ed169d5ffef4d824700f7d" vrev="1"> + <entry md5="d41d8cd98f00b204e9800998ecf8427e" mtime="1282137256" name="added" size="15" /> + <entry md5="14758f1afd44c09b7992073ccf00b43d" mtime="1282137220" name="foo" size="7" /> + <entry md5="256d8f76ba7a0a231fb46a84866f25d8" mtime="1282137238" name="merge" size="20" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/_files b/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/foo b/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/foo new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_in_update/foo @@ -0,0 +1 @@ +foobar diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_meta b/tests/update_fixtures/osctest/resume_deleted/.osc/_meta new file mode 100644 index 0000000..abd3720 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_meta @@ -0,0 +1,8 @@ +<package project="osctest" name="simple"> + <title/> + <description> + + </description> + <person userid="Admin" role="maintainer"/> + <person userid="Admin" role="bugowner"/> +</package>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_osclib_version b/tests/update_fixtures/osctest/resume_deleted/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_package b/tests/update_fixtures/osctest/resume_deleted/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/_project b/tests/update_fixtures/osctest/resume_deleted/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/added b/tests/update_fixtures/osctest/resume_deleted/.osc/added new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/added diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/foo b/tests/update_fixtures/osctest/resume_deleted/.osc/foo new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/foo @@ -0,0 +1 @@ +foobar diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/merge b/tests/update_fixtures/osctest/resume_deleted/.osc/merge new file mode 100644 index 0000000..2563d89 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/merge @@ -0,0 +1,5 @@ +xxx +xxx +yyy +zzz +zzz diff --git a/tests/update_fixtures/osctest/resume_deleted/.osc/nochange b/tests/update_fixtures/osctest/resume_deleted/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/resume_deleted/added b/tests/update_fixtures/osctest/resume_deleted/added new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/added diff --git a/tests/update_fixtures/osctest/resume_deleted/exists b/tests/update_fixtures/osctest/resume_deleted/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/exists diff --git a/tests/update_fixtures/osctest/resume_deleted/f b/tests/update_fixtures/osctest/resume_deleted/f new file mode 100644 index 0000000..0527e6b --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/f @@ -0,0 +1 @@ +This is a test diff --git a/tests/update_fixtures/osctest/resume_deleted/foo b/tests/update_fixtures/osctest/resume_deleted/foo new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/foo @@ -0,0 +1 @@ +foobar diff --git a/tests/update_fixtures/osctest/resume_deleted/merge b/tests/update_fixtures/osctest/resume_deleted/merge new file mode 100644 index 0000000..2563d89 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/merge @@ -0,0 +1,5 @@ +xxx +xxx +yyy +zzz +zzz diff --git a/tests/update_fixtures/osctest/resume_deleted/nochange b/tests/update_fixtures/osctest/resume_deleted/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/resume_deleted/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/services/.osc/_apiurl b/tests/update_fixtures/osctest/services/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/services/.osc/_files b/tests/update_fixtures/osctest/services/.osc/_files new file mode 100644 index 0000000..9209ca9 --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/_files @@ -0,0 +1,5 @@ +<directory name="foo" rev="1" srcmd5="b9f060f4b3640e58a1d44abc25ffb9bd" vrev="1"> + <entry md5="7b1458c733a187d4f3807665ddd02cca" mtime="1282565027" name="_service:exists" size="20" skipped="true" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282320303" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282320303" name="merge" size="48" /> +</directory> diff --git a/tests/update_fixtures/osctest/services/.osc/_osclib_version b/tests/update_fixtures/osctest/services/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/services/.osc/_package b/tests/update_fixtures/osctest/services/.osc/_package new file mode 100644 index 0000000..f7a48f2 --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/_package @@ -0,0 +1 @@ +services diff --git a/tests/update_fixtures/osctest/services/.osc/_project b/tests/update_fixtures/osctest/services/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/services/.osc/foo b/tests/update_fixtures/osctest/services/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/services/.osc/merge b/tests/update_fixtures/osctest/services/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/services/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/services/_service:exists b/tests/update_fixtures/osctest/services/_service:exists new file mode 100644 index 0000000..85e1c2f --- /dev/null +++ b/tests/update_fixtures/osctest/services/_service:exists @@ -0,0 +1,2 @@ +another service +foo diff --git a/tests/update_fixtures/osctest/services/foo b/tests/update_fixtures/osctest/services/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/services/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/services/merge b/tests/update_fixtures/osctest/services/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/services/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/simple/.osc/_apiurl b/tests/update_fixtures/osctest/simple/.osc/_apiurl new file mode 100644 index 0000000..0afeace --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/_apiurl @@ -0,0 +1 @@ +http://localhost diff --git a/tests/update_fixtures/osctest/simple/.osc/_files b/tests/update_fixtures/osctest/simple/.osc/_files new file mode 100644 index 0000000..f0dac1f --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory>
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/simple/.osc/_osclib_version b/tests/update_fixtures/osctest/simple/.osc/_osclib_version new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/_osclib_version @@ -0,0 +1 @@ +1.0 diff --git a/tests/update_fixtures/osctest/simple/.osc/_package b/tests/update_fixtures/osctest/simple/.osc/_package new file mode 100644 index 0000000..8fd3246 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/_package @@ -0,0 +1 @@ +simple
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/simple/.osc/_project b/tests/update_fixtures/osctest/simple/.osc/_project new file mode 100644 index 0000000..cea3bc8 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/_project @@ -0,0 +1 @@ +osctest
\ No newline at end of file diff --git a/tests/update_fixtures/osctest/simple/.osc/foo b/tests/update_fixtures/osctest/simple/.osc/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/simple/.osc/merge b/tests/update_fixtures/osctest/simple/.osc/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/simple/.osc/nochange b/tests/update_fixtures/osctest/simple/.osc/nochange new file mode 100644 index 0000000..0569b03 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/.osc/nochange @@ -0,0 +1 @@ +This file didn't change. diff --git a/tests/update_fixtures/osctest/simple/exists b/tests/update_fixtures/osctest/simple/exists new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/exists diff --git a/tests/update_fixtures/osctest/simple/foo b/tests/update_fixtures/osctest/simple/foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/osctest/simple/foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/osctest/simple/merge b/tests/update_fixtures/osctest/simple/merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/osctest/simple/merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/osctest/simple/nochange b/tests/update_fixtures/osctest/simple/nochange new file mode 100644 index 0000000..3a48a29 --- /dev/null +++ b/tests/update_fixtures/osctest/simple/nochange @@ -0,0 +1,2 @@ +This file didn't change but +is modified. diff --git a/tests/update_fixtures/testUpdateAlreadyInConflict_files b/tests/update_fixtures/testUpdateAlreadyInConflict_files new file mode 100644 index 0000000..96b9752 --- /dev/null +++ b/tests/update_fixtures/testUpdateAlreadyInConflict_files @@ -0,0 +1,5 @@ +<directory name="already_in_conflict" rev="2" srcmd5="686b725018c89978678e15daa666ff85" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282133912" name="foo" size="23" /> + <entry md5="14758f1afd44c09b7992073ccf00b43d" mtime="1282134056" name="merge" size="7" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282133912" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateAlreadyInConflict_merge b/tests/update_fixtures/testUpdateAlreadyInConflict_merge new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/update_fixtures/testUpdateAlreadyInConflict_merge @@ -0,0 +1 @@ +foobar diff --git a/tests/update_fixtures/testUpdateConflict_files b/tests/update_fixtures/testUpdateConflict_files new file mode 100644 index 0000000..93cd6a2 --- /dev/null +++ b/tests/update_fixtures/testUpdateConflict_files @@ -0,0 +1,5 @@ +<directory name="conflict" rev="2" srcmd5="6463d0bd161765e9a2b7186606c72ca1" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282130148" name="foo" size="23" /> + <entry md5="89fcd308c6e6919c472e56ec82ace945" mtime="1282130545" name="merge" size="46" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282130148" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateConflict_merge b/tests/update_fixtures/testUpdateConflict_merge new file mode 100644 index 0000000..f9f1e5a --- /dev/null +++ b/tests/update_fixtures/testUpdateConflict_merge @@ -0,0 +1,4 @@ +Is +it possible to +merge this file? +We'll see. diff --git a/tests/update_fixtures/testUpdateDeletedFile_files b/tests/update_fixtures/testUpdateDeletedFile_files new file mode 100644 index 0000000..9a8cc25 --- /dev/null +++ b/tests/update_fixtures/testUpdateDeletedFile_files @@ -0,0 +1,4 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLimitSizeAddDelete_exists b/tests/update_fixtures/testUpdateLimitSizeAddDelete_exists new file mode 100644 index 0000000..ac79041 --- /dev/null +++ b/tests/update_fixtures/testUpdateLimitSizeAddDelete_exists @@ -0,0 +1 @@ +small diff --git a/tests/update_fixtures/testUpdateLimitSizeAddDelete_files b/tests/update_fixtures/testUpdateLimitSizeAddDelete_files new file mode 100644 index 0000000..a06a209 --- /dev/null +++ b/tests/update_fixtures/testUpdateLimitSizeAddDelete_files @@ -0,0 +1,6 @@ +<directory name="foo" rev="2" srcmd5="018a80019e08143e7ae324c778873d62" vrev="2"> + <entry md5="ed955c917012307d982b7cdd5799ff1a" mtime="1282320398" name="bigfile" size="69" skipped="true" /> + <entry md5="d15dbfcb847653913855e21370d83af1" mtime="1282553634" name="exists" size="6" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282320303" name="foo" size="23" skipped="true" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282320303" name="merge" size="48" skipped="true" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLimitSizeAddDelete_filesremote b/tests/update_fixtures/testUpdateLimitSizeAddDelete_filesremote new file mode 100644 index 0000000..329f100 --- /dev/null +++ b/tests/update_fixtures/testUpdateLimitSizeAddDelete_filesremote @@ -0,0 +1,6 @@ +<directory name="foo" rev="2" vrev="2" srcmd5="018a80019e08143e7ae324c778873d62"> + <entry name="bigfile" md5="ed955c917012307d982b7cdd5799ff1a" size="69" mtime="1282320398" /> + <entry name="exists" md5="d15dbfcb847653913855e21370d83af1" size="6" mtime="1282553634" /> + <entry name="foo" md5="0d62ceea6020d75154078a20d8c9f9ba" size="23" mtime="1282320303" /> + <entry name="merge" md5="17b9e9e1a032ed44e7a584dc6303ffa8" size="48" mtime="1282320303" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLimitSizeNoChange_files b/tests/update_fixtures/testUpdateLimitSizeNoChange_files new file mode 100644 index 0000000..1745544 --- /dev/null +++ b/tests/update_fixtures/testUpdateLimitSizeNoChange_files @@ -0,0 +1,6 @@ +<directory name="limitsize" rev="2" srcmd5="e51a3133d3d3eb2a48e06efb79e2d503" vrev="2"> + <entry md5="ed955c917012307d982b7cdd5799ff1a" mtime="1282320398" name="bigfile" size="69" skipped="true" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282320303" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282320303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLimitSizeNoChange_filesremote b/tests/update_fixtures/testUpdateLimitSizeNoChange_filesremote new file mode 100644 index 0000000..6a3ced8 --- /dev/null +++ b/tests/update_fixtures/testUpdateLimitSizeNoChange_filesremote @@ -0,0 +1,6 @@ +<directory name="limitsize" rev="2" vrev="2" srcmd5="e51a3133d3d3eb2a48e06efb79e2d503"> + <entry name="bigfile" md5="ed955c917012307d982b7cdd5799ff1a" size="69" mtime="1282320398" /> + <entry name="foo" md5="0d62ceea6020d75154078a20d8c9f9ba" size="23" mtime="1282320303" /> + <entry name="merge" md5="17b9e9e1a032ed44e7a584dc6303ffa8" size="48" mtime="1282320303" /> + <entry name="nochange" md5="7efa70f68983fad1cf487f69dedf93e9" size="25" mtime="1282047303" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLocalDeletions_files b/tests/update_fixtures/testUpdateLocalDeletions_files new file mode 100644 index 0000000..d1b7f80 --- /dev/null +++ b/tests/update_fixtures/testUpdateLocalDeletions_files @@ -0,0 +1,5 @@ +<directory name="deleted" rev="2" srcmd5="0e717058d371ab9029336418c8c883bd" vrev="2"> + <entry md5="2bb5f888a0063a0931c12f35851953e4" mtime="1282135005" name="foo" size="37" /> + <entry md5="426e11f11438365322f102c02b0a33f0" mtime="1282134896" name="merge" size="50" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282134731" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLocalDeletions_foo b/tests/update_fixtures/testUpdateLocalDeletions_foo new file mode 100644 index 0000000..0319af9 --- /dev/null +++ b/tests/update_fixtures/testUpdateLocalDeletions_foo @@ -0,0 +1,2 @@ +This is a simple test. +And an update diff --git a/tests/update_fixtures/testUpdateLocalDeletions_merge b/tests/update_fixtures/testUpdateLocalDeletions_merge new file mode 100644 index 0000000..df2934d --- /dev/null +++ b/tests/update_fixtures/testUpdateLocalDeletions_merge @@ -0,0 +1,4 @@ +Is +it possible to +merge this file? +We'll see. Foo diff --git a/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_files b/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_files new file mode 100644 index 0000000..f03a9b5 --- /dev/null +++ b/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_files @@ -0,0 +1,6 @@ +<directory name="limitsize_local" rev="2" srcmd5="e51a3133d3d3eb2a48e06efb79e2d503" vrev="2"> + <entry md5="ed955c917012307d982b7cdd5799ff1a" mtime="1282320398" name="bigfile" size="69" skipped="true" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282320303" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282320303" name="merge" size="48" skipped="true" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_filesremote b/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_filesremote new file mode 100644 index 0000000..4ffd780 --- /dev/null +++ b/tests/update_fixtures/testUpdateLocalLimitSizeNoChange_filesremote @@ -0,0 +1,6 @@ +<directory name="limitsize_local" rev="2" vrev="2" srcmd5="e51a3133d3d3eb2a48e06efb79e2d503"> + <entry name="bigfile" md5="ed955c917012307d982b7cdd5799ff1a" size="69" mtime="1282320398" /> + <entry name="foo" md5="0d62ceea6020d75154078a20d8c9f9ba" size="23" mtime="1282320303" /> + <entry name="merge" md5="17b9e9e1a032ed44e7a584dc6303ffa8" size="48" mtime="1282320303" /> + <entry name="nochange" md5="7efa70f68983fad1cf487f69dedf93e9" size="25" mtime="1282047303" /> +</directory> diff --git a/tests/update_fixtures/testUpdateMetaMode__meta b/tests/update_fixtures/testUpdateMetaMode__meta new file mode 100644 index 0000000..2c2c701 --- /dev/null +++ b/tests/update_fixtures/testUpdateMetaMode__meta @@ -0,0 +1,4 @@ +<package project="osctest" name="metamode"> + <title>foo</title> + <description /> +</package> diff --git a/tests/update_fixtures/testUpdateMetaMode_filesremote b/tests/update_fixtures/testUpdateMetaMode_filesremote new file mode 100644 index 0000000..faca442 --- /dev/null +++ b/tests/update_fixtures/testUpdateMetaMode_filesremote @@ -0,0 +1,3 @@ +<directory name="metamode" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="b995ef5586bdb37154bdeac0bda18c51" mtime="1283265642" name="_meta" size="95" /> +</directory> diff --git a/tests/update_fixtures/testUpdateNewFileLocalExists_exists b/tests/update_fixtures/testUpdateNewFileLocalExists_exists new file mode 100644 index 0000000..9ca11f8 --- /dev/null +++ b/tests/update_fixtures/testUpdateNewFileLocalExists_exists @@ -0,0 +1 @@ +exists diff --git a/tests/update_fixtures/testUpdateNewFileLocalExists_files b/tests/update_fixtures/testUpdateNewFileLocalExists_files new file mode 100644 index 0000000..543b47e --- /dev/null +++ b/tests/update_fixtures/testUpdateNewFileLocalExists_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="28fe7af7e9985507cf51196fc67015b7" vrev="2"> + <entry md5="7ba6ca74b292aaa5d46bc407ac5be166" mtime="1282060455" name="exists" size="7" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateNewFile_files b/tests/update_fixtures/testUpdateNewFile_files new file mode 100644 index 0000000..f852ed2 --- /dev/null +++ b/tests/update_fixtures/testUpdateNewFile_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="9247f30cd5694f5301965a0f20a2ed16" vrev="2"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282054323" name="upstream_added" size="23" /> +</directory> diff --git a/tests/update_fixtures/testUpdateNewFile_upstream_added b/tests/update_fixtures/testUpdateNewFile_upstream_added new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/testUpdateNewFile_upstream_added @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/testUpdateNew_filesremote b/tests/update_fixtures/testUpdateNew_filesremote new file mode 100644 index 0000000..432daa9 --- /dev/null +++ b/tests/update_fixtures/testUpdateNew_filesremote @@ -0,0 +1,2 @@ +<directory name="new"> +</directory> diff --git a/tests/update_fixtures/testUpdateNoChanges_files b/tests/update_fixtures/testUpdateNoChanges_files new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/update_fixtures/testUpdateNoChanges_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateRestore_files b/tests/update_fixtures/testUpdateRestore_files new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/update_fixtures/testUpdateRestore_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateRestore_foo b/tests/update_fixtures/testUpdateRestore_foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/testUpdateRestore_foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/testUpdateResumeDeletedFile_files b/tests/update_fixtures/testUpdateResumeDeletedFile_files new file mode 100644 index 0000000..d2e3da5 --- /dev/null +++ b/tests/update_fixtures/testUpdateResumeDeletedFile_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="1" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="1"> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282047302" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateResumeDeletedFile_foo b/tests/update_fixtures/testUpdateResumeDeletedFile_foo new file mode 100644 index 0000000..3bb34cf --- /dev/null +++ b/tests/update_fixtures/testUpdateResumeDeletedFile_foo @@ -0,0 +1 @@ +This is a simple test. diff --git a/tests/update_fixtures/testUpdateResumeDeletedFile_merge b/tests/update_fixtures/testUpdateResumeDeletedFile_merge new file mode 100644 index 0000000..0b4685d --- /dev/null +++ b/tests/update_fixtures/testUpdateResumeDeletedFile_merge @@ -0,0 +1,4 @@ +Is it +possible to +merge this file? +I hope so... diff --git a/tests/update_fixtures/testUpdateResume_files b/tests/update_fixtures/testUpdateResume_files new file mode 100644 index 0000000..0b0a0c8 --- /dev/null +++ b/tests/update_fixtures/testUpdateResume_files @@ -0,0 +1,6 @@ +<directory name="simple" rev="2" srcmd5="3ac41c59a5ed169d5ffef4d824700f7d" vrev="2"> + <entry md5="ff22941336956098ae9a564289d1bf1b" mtime="1282137256" name="added" size="15" /> + <entry md5="14758f1afd44c09b7992073ccf00b43d" mtime="1282137220" name="foo" size="7" /> + <entry md5="256d8f76ba7a0a231fb46a84866f25d8" mtime="1282137238" name="merge" size="20" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateResume_foo b/tests/update_fixtures/testUpdateResume_foo new file mode 100644 index 0000000..323fae0 --- /dev/null +++ b/tests/update_fixtures/testUpdateResume_foo @@ -0,0 +1 @@ +foobar diff --git a/tests/update_fixtures/testUpdateResume_merge b/tests/update_fixtures/testUpdateResume_merge new file mode 100644 index 0000000..2563d89 --- /dev/null +++ b/tests/update_fixtures/testUpdateResume_merge @@ -0,0 +1,5 @@ +xxx +xxx +yyy +zzz +zzz diff --git a/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:bar b/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:bar new file mode 100644 index 0000000..5fe9f1f --- /dev/null +++ b/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:bar @@ -0,0 +1 @@ +another service diff --git a/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:foo b/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:foo new file mode 100644 index 0000000..ac79041 --- /dev/null +++ b/tests/update_fixtures/testUpdateServiceFilesAddDelete__service:foo @@ -0,0 +1 @@ +small diff --git a/tests/update_fixtures/testUpdateServiceFilesAddDelete_bigfile b/tests/update_fixtures/testUpdateServiceFilesAddDelete_bigfile new file mode 100644 index 0000000..8b7b0f9 --- /dev/null +++ b/tests/update_fixtures/testUpdateServiceFilesAddDelete_bigfile @@ -0,0 +1,5 @@ +This is a file +with a lot of +text. Foo foo +bar bar bar. +foobarfoobar diff --git a/tests/update_fixtures/testUpdateServiceFilesAddDelete_files b/tests/update_fixtures/testUpdateServiceFilesAddDelete_files new file mode 100644 index 0000000..35e0945 --- /dev/null +++ b/tests/update_fixtures/testUpdateServiceFilesAddDelete_files @@ -0,0 +1,7 @@ +<directory name="foo" rev="2" srcmd5="1c5d541a029694c43d5341cabcb4f40f" vrev="2"> + <entry md5="a0106bad78c9070662d5cde42ee35f23" mtime="1282564656" name="_service:bar" size="16" skipped="true" /> + <entry md5="d15dbfcb847653913855e21370d83af1" mtime="1282561867" name="_service:foo" size="6" skipped="true" /> + <entry md5="ed955c917012307d982b7cdd5799ff1a" mtime="1282320398" name="bigfile" size="69" /> + <entry md5="0d62ceea6020d75154078a20d8c9f9ba" mtime="1282320303" name="foo" size="23" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282320303" name="merge" size="48" /> +</directory> diff --git a/tests/update_fixtures/testUpdateServiceFilesAddDelete_filesremote b/tests/update_fixtures/testUpdateServiceFilesAddDelete_filesremote new file mode 100644 index 0000000..8f4e3ae --- /dev/null +++ b/tests/update_fixtures/testUpdateServiceFilesAddDelete_filesremote @@ -0,0 +1,7 @@ +<directory name="foo" rev="2" vrev="2" srcmd5="1c5d541a029694c43d5341cabcb4f40f"> + <entry name="_service:bar" md5="a0106bad78c9070662d5cde42ee35f23" size="16" mtime="1282564656" /> + <entry name="_service:foo" md5="d15dbfcb847653913855e21370d83af1" size="6" mtime="1282561867" /> + <entry name="bigfile" md5="ed955c917012307d982b7cdd5799ff1a" size="69" mtime="1282320398" /> + <entry name="foo" md5="0d62ceea6020d75154078a20d8c9f9ba" size="23" mtime="1282320303" /> + <entry name="merge" md5="17b9e9e1a032ed44e7a584dc6303ffa8" size="48" mtime="1282320303" /> +</directory> diff --git a/tests/update_fixtures/testUpdateUpstreamModifiedFile_files b/tests/update_fixtures/testUpdateUpstreamModifiedFile_files new file mode 100644 index 0000000..c605722 --- /dev/null +++ b/tests/update_fixtures/testUpdateUpstreamModifiedFile_files @@ -0,0 +1,5 @@ +<directory name="simple" rev="2" srcmd5="2df1eacfe03a3bec2112529e7f4dc39a" vrev="2"> + <entry md5="bb3a1efda68dff80ec3a2fb599b97ad8" mtime="1282058167" name="foo" size="39" /> + <entry md5="17b9e9e1a032ed44e7a584dc6303ffa8" mtime="1282047303" name="merge" size="48" /> + <entry md5="7efa70f68983fad1cf487f69dedf93e9" mtime="1282047303" name="nochange" size="25" /> +</directory> diff --git a/tests/update_fixtures/testUpdateUpstreamModifiedFile_foo b/tests/update_fixtures/testUpdateUpstreamModifiedFile_foo new file mode 100644 index 0000000..4083ca8 --- /dev/null +++ b/tests/update_fixtures/testUpdateUpstreamModifiedFile_foo @@ -0,0 +1,3 @@ +<added> +This is a simple test. +<added> |