bzrtools/.be/0000755000000000000000000000000012264646316011540 5ustar 00000000000000bzrtools/.the_kraken/0000755000000000000000000000000012264646316013265 5ustar 00000000000000bzrtools/AUTHORS0000644000000000000000000000010612264646316012141 0ustar 00000000000000Aaron Bentley Alexander Belchenko Robert Collins Wouter van Heyst bzrtools/COPYING0000644000000000000000000004311112264646316012127 0ustar 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, 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. Copyright (C) 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., 59 Temple Place, Suite 330, 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. , 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. bzrtools/CREDITS0000644000000000000000000000027612264646316012121 0ustar 00000000000000The following folks have contributed to bzrtools David Allouche Aaron Bentley Alexander Belchenko Jeff Bailey Robert Collins Michael Ellerman Eirik Nygaard John A. Meinel Daniel Silverstone bzrtools/INSTALL0000644000000000000000000000124312264646316012125 0ustar 00000000000000How to install bzrtools: All the tools are plugins, so you can install them in your plugins directory. IT IS IMPORTANT TO MOVE/COPY THE BZRTOOLS DIRECTORY, NOT ITS CONTENTS. $ mv ~/bzrtools ~/.bazaar/plugins/ You may have to create the plugins directory first. Alternatively, you can install them at the system-wide plugin location: $ sudo ./setup.py install You can test whether they are successfully installed by doing "bzr zap -h" Optional Dependencies: rsync - used by rspush diff - used by shelve and unshelve patch - used by shelve, unshelve and patch Getting If you want to test your install: /path/to/bzrtools/test.py [/path/to/bzr] bzrtools/NEWS0000644000000000000000000002724712264646316011607 0ustar 00000000000000August 19 2013 * Fix zap command compatibility with bzr 2.6 * Compatibility fixes for bzr 2.6 in graph, import, shelf testing. * RELEASE: bzrtools 2.6.0 January 19 2012 * Fix compatibility with final beta before bzr 2.5 * Remove rspush command due to deprecation of Branch.revision_history * RELEASE: bzrtools 2.5.0 September 24 2011 * Fix compatibility with bzr.dev (Jelmer Vernoij) * Remove unused imports (Jelmer Vernoij) * Fix bug #263065 (Andi Albrecht) * Rename 'branches' to 'list-branches', keep 'branches' as alias if not built in. * RELEASE: bzrtools 2.4.1 July 13 2011 * Fix compatibility with bzr 2.4 export API * RELEASE: bzrtools 2.4.0 June 27 2011 * import supports .tar.xz and tar.lzma files (Jelmer). * deprecated_graph functionality migrated from bzr (Jelmer). May 9 2011 * Support non-ascii tarballs. April 27 2011 * DiffWriter provides writelines method (Jelmer). April 26 2011 * Stop using deprecated failIfExists/failUnlessExists in test suite. * Stop using deprecated tree_files. * bzr shell works on systems without readline support. (Martin [gz]) * setup.py doesn't execute on import (Robert Collins) April 12 2011 * zap --store uses pipeline to store uncommitted changes. February 14 2011 * bzr shell logs exceptions quietly (Martin [gz]). February 1 2011 * Fix Python 2.7 incompatibility * RELEASE: bzrtools 2.3.1 January 25 2011 * RELEASE: bzrtools 2.3.0 May 12 2010 * Remove unneeded imports May 11 2010 * Fix tests of 'bzr zap' April 6 2010 * RELEASE: bzrtools 2.2.0 February 4 2010 * RELEASE: bzrtools 2.1.0 November 30 2009 * (Gordon Tyler) shell now accepts --directory November 12 2009 * shell now run qbzr commands in a subprocess. November 11 2009 * (Max Bowsher) fetch-ghosts now works again. And has tests. November 10 2009 * (Max Bowsher) Improved version checking October 20 2009 * conflict-diff no longer requires a single file to be specified. Multiple files or no files can be specified. October 19 2009 * RELEASE: bzrtools 2.1.0b1 September 26 2009 * Fix bug #435695: deprecation warning completing command names * Fix bug #431341: (John Arbash Meinel)) tab completion fails due non-ascii chars in string.whitespace. * (James Westby) patch accepts -p as a short name for --strip. * (Benoît Pierre) ^C does not abort the shell command * (Benoît Pierre) line parsing errors do not abort the shell command * RELEASE: bzrtools 2.0.1 September 3 2009 * RELEASE: bzrtools 2.0.0 August 20 2009 * RELEASE: bzrtools 1.18.0 July 13 2009 * RELEASE: bzrtools 1.17.0 July 10 2009 * `create-mirror` mirrors child_submit_to June 16 2009 * `shelve1` is no longer aliased to `shelve`, `unshelved1` is no longer aliased to `unshelve`. June 12 2009 * RELEASE: bzrtools 1.16.0 June 10 2009 * Implement create-mirror command May 27 2009 * Fix fetch-ghosts command for bzr 1.15 * RELEASE: bzrtools 1.15.0 April 28 2009 * bzrtools officially upgraded to rich roots. April 10 2009 * Fix import command for Python 2.6 * RELEASE: bzrtools 1.14.0 March 11 2009 * `bzr patch` handles URLs with trailing slashes. * RELEASE: bzrtools 1.13.0 * Remove clean-tree (now in bzr core) * New conflict-diff command February 10 2009 * RELEASE: bzrtools 1.12.0 January 12 2009 * RELEASE: bzrtools 1.11.0 January 7 2009 * Rename shelf command to shelf1, to match others & reduce confusion. November 28 2008 * RELEASE: bzrtools 1.10.0 Novemeber 23 2008 * Support auto-detecting or disabling color in cdiff (Benoît Pierre) * Support auto-detecting color in shelf UI November 11 2008 * Support colorizing in shelf UI November 5 2008 * RELEASE: bzrtools 1.9.1 November 4 2008 * Restore runtime ignore for .shelf files November 3 2008 * RELEASE: bzrtools 1.9.0 October 22 2008 * Use lazy command registration (changes bzr rocks 0.496 => 0.461). October 17 2008 * Rename shelve and unshelve to shelve1 and unshelve1, with aliases to 'shelve' and 'unshelve'. This allows bzr core to supply 'shelve' and 'unshelve'. October 7 2008 * RELEASE: bzrtools 1.8.0 * Fix NotStandalone class to be stringable (#277652) September 11 2008 * RELEASE: bzrtools 1.7.0 July 10 2008 * colordiff can now use /etc/colordiffrc June 5 2008 * RELEASE: bzrtools 1.6.0 June 3 2008 * Fix bug in when TERM environment variable is unset May 29 2008 * Spin off baz-import into a separate project * Remove show-paths May 12 2008 * Mark show-paths as obsolete * Update to avoid deprecated APIs * RELEASE: bzrtools 1.5.0 May 11 2008 * Test fixes from Alexander Belchenko * Merge heads plugin * Update heads plugin to use undeprecated APIs April 26 2008 * Updates to syntax highlighting from Marius Kruger April 17 2008 * Use Graph API (instead of old revision_graph code) for graph-ancestry * RELEASE: bzrtools 1.4.0 April 10 2008 * Add escaping to HTML graph-ancestry output * Nicer error when patch fails March 20 2008 * cbranch can now create parent directories for branches as needed. March 18 2008 * RELEASE: bzrtools 1.3.0 Feb 29 2008 * Add link-tree command Feb 25 2008 * Add hard-link support to cbranch Feb 13 2008 * RELEASE: bzrtools 1.2.0 Feb 12 2008 * Handle missing patch program Jan 10 2008 * Support branch6 formats (dirstate-tags, pack*) in rspush * RELEASE: bzrtools 1.1.0 Dec 21 2007 * "branches" and "multi-pull" updated to new find_* APIs. * new "trees" command. Dec 20 2007 * cbranch is much faster, and supports --files-from. Dec 1 2007 * RELEASE: bzrtools 1.0.0 * Use dotted revnos in graph-ancestry Nov 23 2007 * Remove switch (now in bzr itself) Nov 6 2007 * Handle command.options glitch * RELEASE: bzrtools 0.92.1 Nov 1 2007 * Update rspush to work only in standalone trees and fix lock error * RELEASE: bzrtools 0.92.0 September 11 2007 * RELEASE: bzrtools 0.91.0 September 4 2007 * switch works even when source branch is read-only August 15 2007 * Allow zap --force to delete a checkout with uncommitted changes August 14 2007 * Hide show-paths command, in favour of bzr info * BzrTools does not do out-of-date checks unless a bzrtools command is run * shelf doesn't emit reject message in test suite * branches now works with Apache (and possibly other servers) again * branches now dies quickly & cleanly if it can't connect * multi-pull reuses connections where possible * RELEASE: bzrtools 0.90.0 * remove branch-mark July 10 2007 * RELEASE: bzrtools 0.18.0 June 15 2007 * graph-ancestry supports new graph API, indicates both possible merge bases, indicates non-base LCAs, defaults max-distance to 100, defaults branch to cwd. June 12 2007 * RELEASE: bzrtools 0.17.0 * RELEASE: bzrtools 0.17.1 June 10 2007 * Whitespace cleanups from Charlie Shepherd June 9 2007 * Version warning is suppressed for dev version of the next release. E.g. bzrtools 0.17.0 will not complain when used with Bazaar 0.18.0 dev. May 8 2007 * RELEASE: bzrtools 0.16.1 April 27 2007 * RELEASE: bzrtools 0.16.0 March 26 2007 * Get shelf tests passing with dirstate (again!) * RELEASE: bzrtools 0.15.4 March 26 2007 * Add branch parameter to show-paths March 16 2007 * RELEASE: bzrtools 0.15.3 March 12 2007 * Hack around hunk headers showing as long lines. March 9 2007 * Handle broken python tar implementations in upstream_import * RELEASE: bzrtools 0.15.2 March 7 2007 * RELEASE: bzrtools 0.15.1 * RELEASE: bzrtools 0.15.0 Feb 2 2007 * Colordiff warns on long lines Jan 17 2007 * RELEASE: bzrtools 0.14.0 Jan 16 2007 * 'colordiff' now optionally checks for whitespace violations Jan 8 2007 * 'import' now imports directories. Jan 4 2007 * 'cbranch' now supports multilevel paths. So your repo hierarchy can match your working directory hierarchy exactly. New config option must be used: "cbranch_target". Appendpath policy should be used for this. Dec 28 2006 * 'import' command now honours execute bit in tarfiles Dec 21 2006 * 'graph-ancestry' shows branch nick if applicable Dec 19 2006 * 'patch' works over sftp (and, in theory, all transports) Dec 13 2006 * 'branch-history' tolerates commit ids with no email Dec 12 2006 * Add zip support to 'import' command Dec 11 2006 * 'patch' fixed to work properly with http URLs and all other transports Dec 5 2006 * 'rspush' supports dedicated rsync servers (i.e. site:: syntax) (Andrew Tridgell) Dec 4 2006 * 'shelf' handles pipe errors better when invoking patch Nov 27 2006 * RELEASE: bzrtools 0.13.0 Nov 22 2006 * Add encoding flag for 'baz-import' * Fix deprecated API use in 'switch' * Add show-paths command from Alexander Belchenko Oct 25 2006 * RELEASE: bzrtools 0.12.0 * Update 'import' command for unique roots changes Oct 24 2006 * Fix parent-setting in 'cbranch'. Oct 15 2006 * Update for unique roots changes Sep 25 2006 * RELEASE: bzrtools 0.11.0 * Remove Shove * Clean up test suite Aug 28 2006 * Shove is now deprecated * Reduce interactive slowdown by late-loading PyBaz * baz-import speedup: remove useless merge_inner call Aug 15 2006 * Check bzrlib version * RELEASE: bzrtools 0.9.1 Aug 11 2006 * RELEASE: bzrtools 0.9.0 Aug 6 2006 * Add --no-color option to shelve/unshelve July 13 2006 * clean-tree no longer treats --detritus or --ignored as including --unknowns July 11 2006 * Shelf colorizing June 14 2006 * Add 'shove' command, to move changes to a different tree June 3 2006 * clean-tree tweaks May 30 2006 * test suite updates May 18 2006 * Add 'import' command, to import tarballs May 11 2006 * RELEASE: bzrtools 0.8.1 * Fixed test case failure May 9 2006 * RELEASE: bzrtools 0.8 May 1 2006 * Renamed push to rspush (Robert Collins/Aaron Bentley) Apr 11 2006 * New Switch command (David Allouche/Canonical) Mar 22 2006 * New Zap command Mar 18 2006 * Updates to Shelf command Mar 10 2006 * New baz-import algorithm, with respository support Jan 31 2006 * RELEASE: bzrtools 0.7 * Improved shell completion * bzr push can omit working tree * Documentation updates Dec 13 2005 * New test.py for standalone (kinda) testing * New branch-history command * New "fix" command (done automatically in fetch-ghosts) Nov 8 2005 * Various API updates * Added force-reweave-inventory from Daniel Silverstone * Decorated push from Robert Collins * Improved shell completion * Improved import when first ancestor is in an unregisered archive Oct 28 2005 * Added tests for several commands * Made push auto_disable when native push present * Merged Michael Ellerman's shelf v2 plugin * New "shell" command, derived from Fai * Got pull working with URLs Oct 19 2005 * Added setup.py * disabled annotate in favor of bzr annotate * Added clean-tree --detrius * API sync with bzr Oct 14 2005 * Default-ignore shelf files * Win32 compatability fixes (Alexander Belchenko) * Conflict handling now in bzr itself * Fetch-missing renamed to fetch-ghosts * Annotate includes changes since last commit, uses sha1 instead of text_id Sept 29 2005 * better errors for bad push locations (Eirik Nygaard) * prevented push from overwriting any non-empty directory that is not an ancestor branch. (Remote revision-history must be a subset of local.) * added --overwrite option to push Sept 22 2005 * Significant reworking of graph-ancestry * Fetch-missing uses 'parent' instead of 'x-pull' (bzr changed) * Updated to match bzr 0.0.8 API changes * Updated to handle new bzr diff output Sept 13 2005 * documented clean-tree, conflicts, resolve, graph-ancestry, patch * obsoleted all the executibles; bzrtools only works as plugins now June 15, 2005 * Added annotate * Added Michael Ellerman's shelf/unshelf * Implemented python user interface June 8, 2005 baz2bzr * much better error messages for common mistakes * more thorough unit testing * works better with recent merge changes * Debian packaging (Jeff Bailey) June 7, 2005 * baz2bzr supports updating a previous import (John Meinel) * baz2bzr has proper commandline parsing (John Meinel) * baz2bzr can skip symlinks when importing * bzr-pull is obsolete, now that that bzr has a native pull command bzrtools/NEWS.Shelf0000644000000000000000000000351312264646316012635 0ustar 00000000000000NEWS Shelf version 0.9-beta (WIP): * You can now unshelve patches in arbitrary order. This works best if the shelved patches don't depend on each other, otherwise you'll find you have trouble unshelving them (as patch(1) won't apply them cleanly). To work around this you can use the --force option to unshelve. Handle with care. * The patch option to 'show' is now optional. If ommitted the top most patch will be displayed. Shelf version 0.8 (michael@ellerman.id.au-20060508134216-454ada48469c6daf): * Shelves are stored in -p0 format to match bzr's native diff format. Crazy -p1 format diffs are also supported. * Patches are backed up when you unshelve them. They're stored in xx~ where xx is the id of the patch. They'll be overwritten the next time you shelve to patch xx. * Support for bzr version 0.7 and 0.8, w/thanks to marienz on #bzr. * Shelves are now stored in .shelf. The bzr API for storing things under .bzr is not rich enough at the moment to do all the things the shelf code wants. * There is a new command 'shelf', which has a bunch of subcommands for operating on the shelf. The current subcommands are: list (ls) List the patches on the current shelf. delete (del) Delete a patch from the current shelf. switch Switch to the named shelf, create it if necessary. show Show the contents of the specified patch. * The question when shelving is now "Shelve this change", rather than "Keep this change", and the y/n choices have been reversed to suit obviously. This seems like a more logical choice of wording. * Unshelve now prompts as well, allowing you to selectively unshelve some changes. * Shelve and unshelve no longer print the diffstat automatically. If you want a diffstat then get the diffstat plugin! bzrtools/PACKAGERS0000644000000000000000000000354712264646316012330 0ustar 00000000000000Notes to packagers ################## Hi there, Thank you for packaging Bzrtools. Here are some notes. Bazaar version ============== Bzrtools' major and minor version number should be the same as Bazaar's. Newer versions of Bzrtools may use APIs that are not present in older Bazaar versions. To be on the safe side, Bzrtools disables itself in this situation. Old versions of Bzrtools may use APIs that are deprecated in newer versions of Bazaar. The deprecation period is one release, typically one month. So technically, slightly old versions of Bzrtools should work with newer versions of Bazaar. But even so, they will emit unpleasant deprecation warnings. In order to make things easier on maintainers, I've adopted the policy of releasing Bzrtools around the same time as the Bazaar release candidate. This gives you about a week of lead time before the final Bazaar release. Bzrtools releases are announced the bazaar@canonical.com and bazaar-announce@canonical.com mailing lists. Other dependencies ================== Certain bzrtools use or can use tools from the following packages: - rsync (rspush) - diff (shelve/unshelve) - patch (shelve/unshelve) - graphviz (graph-ancestry) - librsvg-bin (for antialiasing in graph-ancestry) These can be treated as recommended rather than hard dependencies, because many bzrtools commands will function properly without them. Additionally, the test suite uses testresources: http://www.robertcollins.net/unittest/testresources/ Testing ======= Note that Medusa is not required for the Bzrtools test suite. Medusa is used strictly by Bazaar. ``bzr selftest bzrtools`` will suggest installing Medusa because ``bzr selftest`` always suggests installing Medusa, if it is not already installed. If you prefer, you can run the ``test.py`` script provided. It runs the same tests as ``bzr selftest bzrtools``. bzrtools/README0000644000000000000000000000442712264646316011763 0ustar 00000000000000BZR TOOLS This is is a set of plugins for Bazaar. Please report bugs at: https://bugs.launchpad.net/bzrtools/ BRANCHES List all the branches present at, or underneath, a location. BRANCH-HISTORY Show a history of the branch, separated by committer and branch nick. CBRANCH Create a branch in a repository and a checkout elsewhere, in one command. CDIFF cdiff mimics bzr diff, but displays the diff in colour. cdiff knows how to read a ~/.colordiffrc file, in the same format as for colordiff(1). # example ~/.colordiffrc plain=darkwhite newtext=darkblue oldtext=darkred diffstuff=darkgreen FETCH-GHOSTS Scan this branch for missing ancestors (aka "ghosts), and attempt to retrieve them from a specified branch. GRAPH-ANCESTRY Use dot (from Graphviz) to produce graphics of a tree's ancestry. IMPORT Import a tarball into a bzr branch, or update to a fresh tarball. PATCH Use patch to apply a patch to this tree. MULTI-PULL Pull all branches and checkouts at or underneath the current location. RSPUSH Uses rsync to copy a branch to a remote location. Remembers the last location used. Will not push if - there are unknown files - there are uncommitted changes in the working tree. These safeguards are intended to prevent dirty trees from being uploaded. SHELL An interactive shell with bzr commands available as single commands, and nice shell completion. SHELVE/UNSHELVE/SHELF Shelve allows you to temporarily put changes you've made "on the shelf", ie. out of the way, until a later time when you can bring them back from the shelf with the 'unshelve' command. You can put multiple items on the shelf, each time you run unshelve the most recently shelved changes will be reinstated. If filenames are specified, only the changes to those files will be shelved, other files will be left untouched. If a revision is specified, changes since that revision will be shelved. If you specifiy "--pick" you'll be prompted for each hunk of the diff as to whether you want to shelve it or not. Press "?" at the prompt for help. SHOW-PATHS List the locations used by a branch or checkout. More Info ========= http://bazaar.canonical.com/BzrShelveExample SWITCH Change the branch associated with a lightweight checkout, and update the tree while retaining local changes. ZAP Safely remove undesired checkouts. bzrtools/README.Shelf0000644000000000000000000000247112264646316013020 0ustar 00000000000000Shelf for Bzr ============= This is the shelf plugin for bzr. Temporarily set aside some changes from the current tree. Shelve allows you to temporarily put changes you've made "on the shelf", ie. out of the way, until a later time when you can bring them back from the shelf with the 'unshelve' command. Shelve is intended to help separate several sets of text changes that have been inappropriately mingled. If you just want to get rid of all changes (text and otherwise) and you don't need to restore them later, use revert. By default shelve asks you what you want to shelve, press '?' at the prompt to get help. To shelve everything run shelve --all. If filenames are specified, only the changes to those files will be shelved, other files will be left untouched. If a revision is specified, changes since that revision will be shelved. You can put multiple items on the shelf. Normally each time you run unshelve the most recently shelved changes will be reinstated. However, you can also unshelve changes in a different order by explicitly specifiying which changes to unshelve. This works best when the changes don't depend on each other. --all Shelve all changes without prompting --message ARG, -m --revision ARG, -r More Info ========= http://bazaar.canonical.com/BzrShelveExample bzrtools/TODO0000644000000000000000000000056512264646316011572 0ustar 00000000000000baz-import should recurse across archives when possible. * This fails if the cache fools ancestry-graph. Not sure hot to address. unreachable history * import as a pending merge ? or as a direct parent ? symlink<->file transitions message vs log body ?! other headers - i.e. cscvsid ? parent and committed against are different, record that! version-last option bzrtools/TODO.Shelf0000644000000000000000000000111512264646316012622 0ustar 00000000000000FEATURES * Shelving of committed changes. Three modes (config option?), either uncommit the committed changes (changing history) or apply the inverse diff (messy, but not so scary), or prompt. * shelf commands that operate on the shelved patches should assume the top most (highest numbered) patch if no argument is given, eg. show/del. * shelf del should create backups. INTERNAL CRUD * Interact with bzr at a lower level, ie. not by parsing diffs. Will probably require changes to bzr core, or can we just use a delta object directly? * Write tests for --pick !!! bzrtools/TODO.heads0000644000000000000000000000074712264646316012657 0ustar 00000000000000Yet not implemented: - Write blackbox tests for 'heads' command - 'heads' command could use LOCATION argument for remote operations - Implement various sorting schemes (by nick) -- req. Wouter van Heyst - Resurrect heads without live branches - req. Eric Bagfors Implemented: - Implement various sorting schemes (by date [IMPLEMENTED]) -- req. Wouter van Heyst - Show only heads without live branches [IMPLEMENTED] - req. Aaron Bentley - Show tips of all live branches [IMPLEMENTED] bzrtools/__init__.py0000644000000000000000000000745612264646316013221 0ustar 00000000000000# Copyright (C) 2008 Aaron Bentley. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """\ Various useful commands for working with bzr. """ from bzrlib import ignores, option from bzrlib.commands import ( builtin_command_names, plugin_cmds, ) from version import version_info, __version__ ignores.add_runtime_ignores(['./.shelf']) commands = { 'cmd_branch_history': [], 'cmd_cbranch': [], 'cmd_cdiff': [], 'cmd_conflict_diff': [], 'cmd_create_mirror': [], 'cmd_fetch_ghosts': ['fetch-missing'], 'cmd_graph_ancestry': [], 'cmd_import': [], 'cmd_link_tree': [], 'cmd_multi_pull': [], 'cmd_patch': [], 'cmd_rspush': [], 'cmd_shelf1': [], 'cmd_shell': [], 'cmd_shelve1': [], 'cmd_trees': [], 'cmd_unshelve1': [], 'cmd_zap': [], } for cmd_name, aliases in commands.items(): plugin_cmds.register_lazy(cmd_name, aliases, 'bzrlib.plugins.bzrtools.command_classes') list_branches_aliases = (['branches'] if 'branches' not in builtin_command_names() else []) plugin_cmds.register_lazy('cmd_list_branches', list_branches_aliases, 'bzrlib.plugins.bzrtools.command_classes') plugin_cmds.register_lazy('cmd_heads', [], 'bzrlib.plugins.bzrtools.heads') option.diff_writer_registry.register_lazy( 'auto-color', 'bzrlib.plugins.bzrtools.colordiff', 'auto_diff_writer', 'Colorized diffs, if supported', ) option.diff_writer_registry.register_lazy( 'color', 'bzrlib.plugins.bzrtools.colordiff', 'DiffWriter', 'Colorized diffs', ) option.diff_writer_registry.default_key = 'auto-color' def test_suite(): from bzrlib.tests.TestUtil import TestLoader import tests from doctest import DocTestSuite, ELLIPSIS from unittest import TestSuite import bzrtools import tests.test_dotgraph import tests.test_cbranch import tests.test_conflict_diff from bzrlib.plugins.bzrtools.tests import test_fetch_ghosts import tests.test_graph import tests.test_link_tree import tests.test_patch import tests.test_mirror import tests.upstream_import import zap import tests.blackbox import tests.shelf_tests result = TestSuite() result.addTest(DocTestSuite(bzrtools, optionflags=ELLIPSIS)) result.addTest(tests.test_suite()) result.addTest(TestLoader().loadTestsFromModule(tests.shelf_tests)) result.addTest(tests.blackbox.test_suite()) result.addTest(TestLoader().loadTestsFromModule(tests.upstream_import)) result.addTest(zap.test_suite()) result.addTest(TestLoader().loadTestsFromModule(tests.test_dotgraph)) result.addTest(TestLoader().loadTestsFromModule(test_fetch_ghosts)) result.addTest(TestLoader().loadTestsFromModule(tests.test_graph)) result.addTest(TestLoader().loadTestsFromModule(tests.test_link_tree)) result.addTest(TestLoader().loadTestsFromModule(tests.test_patch)) result.addTest(TestLoader().loadTestsFromModule(tests.test_cbranch)) result.addTest(TestLoader().loadTestsFromModule(tests.test_conflict_diff)) result.addTest(TestLoader().loadTestsFromModule(tests.test_mirror)) return result bzrtools/branches.py0000644000000000000000000000042512264646316013234 0ustar 00000000000000from bzrlib.transport import get_transport from bzrtools import list_branches def branches(location=None): if location is None: location = '.' t = get_transport(location) for branch in list_branches(t): print branch.base[len(t.base):].rstrip('/') bzrtools/branchhistory.py0000644000000000000000000000434012264646316014326 0ustar 00000000000000from bzrlib import errors from bzrlib.branch import Branch from bzrlib.config import extract_email_address from bzrtools import short_committer def branch_history(branch): """Print history of a branch""" b = Branch.open_containing(branch)[0] descriptor = None start = None b.repository.lock_read() try: for revno, revision in iter_revisiondata(b): new_descriptor = (revision.committer, revision.properties.get('branch-nick')) if descriptor is None: descriptor = new_descriptor if start is None: start = revno if branch_change(descriptor, new_descriptor): print_info(descriptor, start, revno - 1) start = revno descriptor = new_descriptor print_info(descriptor, start, revno) finally: b.repository.unlock() def branch_change(old_descriptor, new_descriptor): try: old_email = extract_email_address(old_descriptor[0]) except errors.NoEmailInUsername: old_email = None try: new_email = extract_email_address(new_descriptor[0]) except errors.NoEmailInUsername: new_email = None if old_descriptor == new_descriptor: return False elif None not in (old_descriptor[1], new_descriptor[1]) and \ old_descriptor[1] != new_descriptor[1]: return True elif short_committer(old_descriptor[0]) ==\ short_committer(new_descriptor[0]): return False elif old_descriptor[0].strip(' ') == new_email: return False elif new_descriptor[0].strip(' ') == old_email: return False else: return True def iter_revisiondata(branch): """Iterate through revno, Revision pairs in the revision history""" for no, revision_id in enumerate(branch.revision_history()): yield no+1, branch.repository.get_revision(revision_id) def print_info(descriptor, start, end): """Print revision history""" descriptor_string = descriptor[0] if descriptor[1] is not None: descriptor_string += " / "+ descriptor[1] print descriptor_string if start != end: print " %d .. %d" % (start, end) else: print " %d" % start bzrtools/bzrtools.py0000644000000000000000000000721212264646316013326 0ustar 00000000000000# Copyright (C) 2005-2009, 2011-2013 Aaron Bentley # Copyright (C) 2007 John Arbash Meinel # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from contextlib import contextmanager import re from bzrlib import urlutils from bzrlib.errors import ( BzrCommandError, NotBranchError, NoSuchFile, ) from bzrlib.bzrdir import BzrDir from bzrlib.transport import get_transport @contextmanager def read_locked(lockable): """Read-lock a tree, branch or repository in this context.""" lockable.lock_read() try: yield lockable finally: lockable.unlock() def short_committer(committer): new_committer = re.sub('<.*>', '', committer).strip(' ') if len(new_committer) < 2: return committer return new_committer def apache_ls(t): """Screen-scrape Apache listings""" apache_dir = '[dir]'\ ' ]*)\/"[^>]*>', flags=re.I) for line in lines: match = expr.search(line) if match is None: continue url = match.group(1) if url.startswith('http://') or url.startswith('/') or '../' in url: continue if '?' in url: continue yield url.rstrip('/') def list_branches(t): def is_inside(branch): return bool(branch.base.startswith(t.base)) if t.base.startswith('http://'): def evaluate(bzrdir): try: branch = bzrdir.open_branch() if is_inside(branch): return True, branch else: return True, None except NotBranchError: return True, None return [b for b in BzrDir.find_bzrdirs(t, list_current=apache_ls, evaluate=evaluate) if b is not None] elif not t.listable(): raise BzrCommandError("Can't list this type of location.") return [b for b in BzrDir.find_branches(t) if is_inside(b)] def evaluate_branch_tree(bzrdir): try: tree, branch = bzrdir._get_tree_branch() except NotBranchError: return True, None else: return True, (branch, tree) def iter_branch_tree(t, lister=None): return (x for x in BzrDir.find_bzrdirs(t, evaluate=evaluate_branch_tree, list_current=lister) if x is not None) def open_from_url(location): location = urlutils.normalize_url(location) dirname, basename = urlutils.split(location) if location.endswith('/') and not basename.endswith('/'): basename += '/' return get_transport(dirname).get(basename) def run_tests(): import doctest result = doctest.testmod() if result[1] > 0: if result[0] == 0: print "All tests passed" else: print "No tests to run" if __name__ == "__main__": run_tests() bzrtools/cbranch.py0000644000000000000000000000630612264646316013053 0ustar 00000000000000# Copyright (C) 2006, 2008 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from bzrlib import ui from bzrlib.bzrdir import BzrDir from bzrlib.config import LocationConfig from bzrlib.errors import BzrCommandError, NoSuchFile from bzrlib.osutils import pathjoin, basename, abspath from bzrlib.transport import get_transport from bzrlib.workingtree import WorkingTree from bzrlib.urlutils import derive_to_location def cbranch(from_location, to_location=None, revision=None, lightweight=False, files_from=None, hardlink=False): if to_location is None: to_location = derive_to_location(from_location) config = LocationConfig(abspath(to_location)) b_loc = config.get_user_option("cbranch_target") if b_loc is None: b_root = config.get_user_option("cbranch_root") if b_root is None: raise BzrCommandError("Can't find cbranch_target in" " locations.conf") b_loc = pathjoin(b_root, basename(to_location)) accelerator_tree, old_branch = BzrDir.open_tree_or_branch(from_location) if files_from is not None: accelerator_tree = WorkingTree.open(files_from) if revision is None or len(revision) == 0: revision_id = old_branch.last_revision() elif len(revision) == 1: revision_id = revision[0].in_history(old_branch)[1] else: raise BzrCommandError('At most one revision may be supplied.') b_transport = get_transport(b_loc) ensure_base_recursive(b_transport.clone('..')) pb = ui.ui_factory.nested_progress_bar() try: pb.update('Creating branch', 0, 2) new_branch = old_branch.bzrdir.sprout(b_loc, revision_id, accelerator_tree=accelerator_tree).open_branch() pb.update('Creating checkout', 1, 2) new_branch.create_checkout(to_location, lightweight=lightweight, accelerator_tree=accelerator_tree, hardlink=hardlink) finally: pb.finished() def ensure_base_recursive(transport): """Ensure that the transport base and any its parents exist""" pending_transports = [transport] while len(pending_transports) > 0: transport = pending_transports.pop() try: transport.ensure_base() except NoSuchFile, e: pending_transports.append(transport) parent = transport.clone('..') if parent.base == transport.base: raise e pending_transports.append(parent) bzrtools/check-release.py0000755000000000000000000000136312264646316014147 0ustar 00000000000000#!/usr/bin/env python import os import re from subprocess import call, PIPE import sys sys.path.insert(0, '/home/abentley/bzrplugins') sys.path.insert(0, os.path.dirname(os.path.realpath('/home/abentley/bin/bzr'))) import bzrtools print "bzrtools version: %s" % bzrtools.__version__ def minigrep(pattern, filename): setup = open(filename, 'rb') for line in setup: match = re.search(pattern, line) if match is not None: return match newsmatch = minigrep('RELEASE: bzrtools %s' % (bzrtools.__version__), 'NEWS') if newsmatch is None: print "NEWS entry missing" sys.exit(1) else: print "NEWS entry found" if call(['bzr', 'diff'], stdout=PIPE) != 0: print "Please commit before releasing" sys.exit(1) bzrtools/colordiff.py0000644000000000000000000001774012264646316013426 0ustar 00000000000000# Copyright (C) 2006 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import re import sys from os.path import expanduser from bzrlib import patiencediff, trace from bzrlib.commands import get_cmd_object from bzrlib.patches import (hunk_from_header, InsertLine, RemoveLine, ContextLine, Hunk, HunkLine) import terminal class LineParser(object): def parse_line(self, line): if line.startswith("@"): return hunk_from_header(line) elif line.startswith("+"): return InsertLine(line[1:]) elif line.startswith("-"): return RemoveLine(line[1:]) elif line.startswith(" "): return ContextLine(line[1:]) else: return line class DiffWriter(object): def __init__(self, target, check_style=False, color='always'): self.target = target self.lp = LineParser() self.chunks = [] from terminal import has_ansi_colors if 'always' == color or ('auto' == color and has_ansi_colors()): self.colors = { 'metaline': 'darkyellow', 'plain': 'darkwhite', 'newtext': 'darkblue', 'oldtext': 'darkred', 'diffstuff': 'darkgreen', 'trailingspace': 'yellow', 'leadingtabs': 'magenta', 'longline': 'cyan', } self._read_colordiffrc('/etc/colordiffrc') self._read_colordiffrc(expanduser('~/.colordiffrc')) else: self.colors = { 'metaline': None, 'plain': None, 'newtext': None, 'oldtext': None, 'diffstuff': None, 'trailingspace': None, 'leadingtabs': None, 'longline': None, } self.added_leading_tabs = 0 self.added_trailing_whitespace = 0 self.spurious_whitespace = 0 self.long_lines = 0 self.max_line_len = 79 self._new_lines = [] self._old_lines = [] self.check_style = check_style def _read_colordiffrc(self, path): try: f = open(path, 'r') except IOError: return for line in f.readlines(): try: key, val = line.split('=') except ValueError: continue key = key.strip() val = val.strip() tmp = val if val.startswith('dark'): tmp = val[4:] if tmp not in terminal.colors: continue self.colors[key] = val def colorstring(self, type, item, bad_ws_match): color = self.colors[type] if color is not None: if self.check_style and bad_ws_match: #highlight were needed item.contents = ''.join(terminal.colorstring(txt, color, bcol) for txt, bcol in ( (bad_ws_match.group(1).expandtabs(), self.colors['leadingtabs']), (bad_ws_match.group(2)[0:self.max_line_len], None), (bad_ws_match.group(2)[self.max_line_len:], self.colors['longline']), (bad_ws_match.group(3), self.colors['trailingspace']) )) + bad_ws_match.group(4) string = terminal.colorstring(str(item), color) else: string = str(item) self.target.write(string) def write(self, text): newstuff = text.split('\n') for newchunk in newstuff[:-1]: self._writeline(''.join(self.chunks + [newchunk, '\n'])) self.chunks = [] self.chunks = [newstuff[-1]] def writelines(self, lines): for line in lines: self.write(line) def _writeline(self, line): item = self.lp.parse_line(line) bad_ws_match = None if isinstance(item, Hunk): line_class = 'diffstuff' self._analyse_old_new() elif isinstance(item, HunkLine): bad_ws_match = re.match(r'^([\t]*)(.*?)([\t ]*)(\r?\n)$', item.contents) has_leading_tabs = bool(bad_ws_match.group(1)) has_trailing_whitespace = bool(bad_ws_match.group(3)) if isinstance(item, InsertLine): if has_leading_tabs: self.added_leading_tabs += 1 if has_trailing_whitespace: self.added_trailing_whitespace += 1 if (len(bad_ws_match.group(2)) > self.max_line_len and not item.contents.startswith('++ ')): self.long_lines += 1 line_class = 'newtext' self._new_lines.append(item) elif isinstance(item, RemoveLine): line_class = 'oldtext' self._old_lines.append(item) else: line_class = 'plain' elif isinstance(item, basestring) and item.startswith('==='): line_class = 'metaline' self._analyse_old_new() else: line_class = 'plain' self._analyse_old_new() self.colorstring(line_class, item, bad_ws_match) def flush(self): self.target.flush() @staticmethod def _matched_lines(old, new): matcher = patiencediff.PatienceSequenceMatcher(None, old, new) matched_lines = sum (n for i, j, n in matcher.get_matching_blocks()) return matched_lines def _analyse_old_new(self): if (self._old_lines, self._new_lines) == ([], []): return if not self.check_style: return old = [l.contents for l in self._old_lines] new = [l.contents for l in self._new_lines] ws_matched = self._matched_lines(old, new) old = [l.rstrip() for l in old] new = [l.rstrip() for l in new] no_ws_matched = self._matched_lines(old, new) assert no_ws_matched >= ws_matched if no_ws_matched > ws_matched: self.spurious_whitespace += no_ws_matched - ws_matched self.target.write('^ Spurious whitespace change above.\n') self._old_lines, self._new_lines = ([], []) def auto_diff_writer(output): return DiffWriter(output, color='auto') def colordiff(color, check_style, *args, **kwargs): real_stdout = sys.stdout dw = DiffWriter(real_stdout, check_style, color) sys.stdout = dw try: get_cmd_object('diff').run(*args, **kwargs) finally: sys.stdout = real_stdout if check_style: if dw.added_leading_tabs > 0: trace.warning('%d new line(s) have leading tabs.' % dw.added_leading_tabs) if dw.added_trailing_whitespace > 0: trace.warning('%d new line(s) have trailing whitespace.' % dw.added_trailing_whitespace) if dw.long_lines > 0: trace.warning('%d new line(s) exceed(s) %d columns.' % (dw.long_lines, dw.max_line_len)) if dw.spurious_whitespace > 0: trace.warning('%d line(s) have spurious whitespace changes' % dw.spurious_whitespace) bzrtools/command.py0000644000000000000000000000736012264646316013072 0ustar 00000000000000# Copyright (C) 2007, 2009, 2010, 2011 Aaron Bentley. # Copyright (C) 2009 Max Bowsher. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import bzrlib from bzrlib import commands from version import version_info, __version__ _testing = False # True if we are currently testing commands via the test suite. def _stop_testing(): """Set the _testing flag to indicate we are no longer testing.""" global _testing _testing = False class BzrToolsCommand(commands.Command): def run_argv_aliases(self, argv, alias_argv=None): result = check_bzrlib_version(version_info[:2]) if result is not None: return result commands.Command.run_argv_aliases(self, argv, alias_argv) TOO_OLD = 'too_old' COMPATIBLE = 'compatible' MAYBE_TOO_NEW = 'maybe_too_new' TOO_NEW = 'too_new' def check_version_compatibility(bzrlib_version, min_version, max_version): """Check whether a bzrlib version is compatible with desired version. If the bzrlib_version is not less than min_version and not greater than max_version, it is considered COMPATIBLE. If the version exceeds max_version by 1 and is not a 'candidate' or 'final' version, it is considered MAYBE_TOO_NEW. Other values greater than max_version are considered TOO_NEW, and values lower than min_version are considered TOO_OLD. """ bzrlib_version = bzrlib.version_info[:2] if bzrlib_version < min_version: return TOO_OLD if bzrlib_version <= max_version: return COMPATIBLE max_plus = (max_version[0], max_version[1] + 1) if bzrlib_version == max_plus: if bzrlib.version_info[3] not in ('final', 'candidate'): return COMPATIBLE return MAYBE_TOO_NEW return TOO_NEW def check_bzrlib_version(desired): """Check that bzrlib is compatible. If version is < bzrtools version, assume incompatible. If version == bzrtools version, assume completely compatible If version == bzrtools version + 1, assume compatible, with deprecations Otherwise, assume incompatible. """ global _testing if _testing: return compatibility = check_version_compatibility(bzrlib.version_info, desired, desired) if compatibility == COMPATIBLE: return try: from bzrlib.trace import warning except ImportError: # get the message out any way we can from warnings import warn as warning if compatibility == TOO_OLD: warning('Bazaar version %s is too old to be used with' ' plugin "Bzrtools" %s.' % ( bzrlib.__version__, __version__)) # Not using BzrNewError, because it may not exist. return 3 else: warning('Plugin "Bzrtools" is not up to date with installed Bazaar' ' version %s.\n' 'There should be a newer version of Bzrtools available, e.g.' ' %i.%i.' % (bzrlib.__version__, bzrlib.version_info[0], bzrlib.version_info[1])) if compatibility == TOO_NEW: return 3 bzrtools/command_classes.py0000644000000000000000000005534412264646316014614 0ustar 00000000000000# Copyright (C) 2005, 2006, 2007, 2011 Aaron Bentley # Copyright (C) 2005, 2006, 2011 Canonical Limited. # Copyright (C) 2006 Michael Ellerman. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import bzrlib from bzrlib.lazy_import import lazy_import lazy_import(globals(), """ from bzrlib import help, urlutils from bzrlib.plugins.bzrtools import shelf """) from bzrlib.plugins import bzrtools from command import BzrToolsCommand from errors import CommandError from patchsource import BzrPatchSource import bzrlib.commands from bzrlib.branch import Branch from bzrlib.commands import get_cmd_object from bzrlib.errors import BzrCommandError from bzrlib.option import Option, RegistryOption class cmd_graph_ancestry(BzrToolsCommand): """Produce ancestry graphs using dot. Output format is detected according to file extension. Some of the more common output formats are html, png, gif, svg, ps. An extension of '.dot' will cause a dot graph file to be produced. HTML output has mouseovers that show the commit message. Branches are labeled r?, where ? is the revno. If they have no revno, with the last 5 characters of their revision identifier are used instead. The value starting with d is "(maximum) distance from the null revision". If --merge-branch is specified, the two branches are compared and a merge base is selected. Legend: white normal revision yellow THIS history red OTHER history orange COMMON history blue COMMON non-history ancestor green Merge base (COMMON ancestor farthest from the null revision) dotted Ghost revision (missing from branch storage) Ancestry is usually collapsed by skipping revisions with a single parent and descendant. The number of skipped revisions is shown on the arrow. This feature can be disabled with --no-collapse. By default, revisions are ordered by distance from root, but they can be clustered instead using --cluster. If available, rsvg is used to antialias PNG and JPEG output, but this can be disabled with --no-antialias. """ takes_args = ['file', 'merge_branch?'] takes_options = [Option('no-collapse', help="Do not skip simple nodes."), Option('no-antialias', help="Do not use rsvg to produce antialiased output."), Option('merge-branch', type=str, help="Use this branch to calcuate a merge base."), Option('cluster', help="Use clustered output."), Option('max-distance', help="Show no nodes farther than this.", type=int), Option('directory', help='Source branch to use (default is current' ' directory).', short_name='d', type=unicode), ] def run(self, file, merge_branch=None, no_collapse=False, no_antialias=False, cluster=False, max_distance=100, directory='.'): if max_distance == -1: max_distance = None import graph if cluster: ranking = "cluster" else: ranking = "forced" graph.write_ancestry_file(directory, file, not no_collapse, not no_antialias, merge_branch, ranking, max_distance=max_distance) class cmd_fetch_ghosts(BzrToolsCommand): """Attempt to retrieve ghosts from another branch. If the other branch is not supplied, the last-pulled branch is used. """ aliases = ['fetch-missing'] takes_args = ['branch?'] takes_options = [Option('no-fix', help="Skip additional synchonization.")] def run(self, branch=None, no_fix=False): from fetch_ghosts import fetch_ghosts fetch_ghosts(branch, do_reconcile=not no_fix) strip_help="""Strip the smallest prefix containing num leading slashes from \ each file name found in the patch file.""" class cmd_patch(BzrToolsCommand): """Apply a named patch to the current tree. """ takes_args = ['filename?'] takes_options = [Option('strip', type=int, short_name='p', help=strip_help), Option('silent', help='Suppress chatter.')] def run(self, filename=None, strip=None, silent=False): from patch import patch from bzrlib.workingtree import WorkingTree wt = WorkingTree.open_containing('.')[0] if strip is None: strip = 0 return patch(wt, filename, strip, silent) class cmd_shelve1(BzrToolsCommand): """Temporarily set aside some changes from the current tree. Shelve allows you to temporarily put changes you've made "on the shelf", ie. out of the way, until a later time when you can bring them back from the shelf with the 'unshelve1' command. Shelve is intended to help separate several sets of text changes that have been inappropriately mingled. If you just want to get rid of all changes (text and otherwise) and you don't need to restore them later, use revert. If you want to shelve all text changes at once, use shelve1 --all. By default shelve1 asks you what you want to shelve, press '?' at the prompt to get help. To shelve everything run shelve1 --all. If filenames are specified, only the changes to those files will be shelved, other files will be left untouched. If a revision is specified, changes since that revision will be shelved. You can put multiple items on the shelf. Normally each time you run unshelve1 the most recently shelved changes will be reinstated. However, you can also unshelve changes in a different order by explicitly specifiying which changes to unshelve1. This works best when the changes don't depend on each other. While you have patches on the shelf you can view and manipulate them with the 'shelf1' command. Run 'bzr shelf1 -h' for more info. """ takes_args = ['file*'] takes_options = [Option('message', help='A message to associate with the shelved changes.', short_name='m', type=unicode), 'revision', Option('all', help='Shelve all changes without prompting.'), Option('no-color', help='Never display changes in color.')] def run(self, all=False, file_list=None, message=None, revision=None, no_color=False): if revision is not None and revision: if len(revision) == 1: revision = revision[0] else: raise CommandError("shelve only accepts a single revision " "parameter.") source = BzrPatchSource(revision, file_list) s = shelf.Shelf(source.base) s.shelve(source, all, message, no_color) return 0 # The following classes are only used as subcommands for 'shelf1', they're # not to be registered directly with bzr. class cmd_shelf_list(bzrlib.commands.Command): """List the patches on the current shelf.""" aliases = ['list', 'ls'] def run(self): self.shelf.list() class cmd_shelf_delete(bzrlib.commands.Command): """Delete the patch from the current shelf.""" aliases = ['delete', 'del'] takes_args = ['patch'] def run(self, patch): self.shelf.delete(patch) class cmd_shelf_switch(bzrlib.commands.Command): """Switch to the other shelf, create it if necessary.""" aliases = ['switch'] takes_args = ['othershelf'] def run(self, othershelf): s = shelf.Shelf(self.shelf.base, othershelf) s.make_default() class cmd_shelf_show(bzrlib.commands.Command): """Show the contents of the specified or topmost patch.""" aliases = ['show', 'cat', 'display'] takes_args = ['patch?'] def run(self, patch=None): self.shelf.display(patch) class cmd_shelf_upgrade(bzrlib.commands.Command): """Upgrade old format shelves.""" aliases = ['upgrade'] def run(self): self.shelf.upgrade() class cmd_shelf1(BzrToolsCommand): """Perform various operations on your shelved patches. See also shelve1.""" takes_args = ['subcommand', 'args*'] subcommands = [cmd_shelf_list, cmd_shelf_delete, cmd_shelf_switch, cmd_shelf_show, cmd_shelf_upgrade] def run(self, subcommand, args_list): import sys if args_list is None: args_list = [] cmd = self._get_cmd_object(subcommand) source = BzrPatchSource() s = shelf.Shelf(source.base) cmd.shelf = s if args_list is None: args_list = [] return cmd.run_argv_aliases(args_list) def _get_cmd_object(self, cmd_name): for cmd_class in self.subcommands: for alias in cmd_class.aliases: if alias == cmd_name: return cmd_class() raise CommandError("Unknown shelf subcommand '%s'" % cmd_name) def help(self): text = ["%s\n\nSubcommands:\n" % self.__doc__] for cmd_class in self.subcommands: text.extend(self.sub_help(cmd_class) + ['\n']) return ''.join(text) def sub_help(self, cmd_class): text = [] cmd_obj = cmd_class() indent = 2 * ' ' usage = cmd_obj._usage() usage = usage.replace('bzr shelf-', '') text.append('%s%s\n' % (indent, usage)) text.append('%s%s\n' % (2 * indent, cmd_class.__doc__)) # Somewhat copied from bzrlib.help.help_on_command_options option_help = [] for option_name, option in sorted(cmd_obj.options().items()): if option_name == 'help': continue option_help.append('%s--%s' % (3 * indent, option_name)) if option.type is not None: option_help.append(' %s' % option.argname.upper()) if option.short_name(): option_help.append(', -%s' % option.short_name()) option_help.append('%s%s\n' % (2 * indent, option.help)) if len(option_help) > 0: text.append('%soptions:\n' % (2 * indent)) text.extend(option_help) return text class cmd_unshelve1(BzrToolsCommand): """Restore shelved changes. By default the most recently shelved changes are restored. However if you specify a patch by name those changes will be restored instead. See 'shelve1' for more information. """ takes_options = [ Option('all', help='Unshelve all changes without prompting.'), Option('force', help='Force unshelving even if errors occur.'), Option('no-color', help='Never display changes in color.') ] takes_args = ['patch?'] def run(self, patch=None, all=False, force=False, no_color=False): source = BzrPatchSource() s = shelf.Shelf(source.base) s.unshelve(source, patch, all, force, no_color) return 0 class cmd_shell(BzrToolsCommand): """Begin an interactive shell tailored for bzr. Bzr commands can be used without typing bzr first, and will be run natively when possible. Tab completion is tailored for bzr. The shell prompt shows the branch nick, revno, and path. If it encounters any moderately complicated shell command, it will punt to the system shell. Example: $ bzr shell bzr bzrtools:287/> status modified: __init__.py bzr bzrtools:287/> status --[TAB][TAB] --all --help --revision --show-ids bzr bzrtools:287/> status -- """ takes_options = [ Option('directory', help='Branch in which to start the shell, ' 'rather than the one containing the working directory.', short_name='d', type=unicode, ), ] def run(self, directory=None): import shell return shell.run_shell(directory) class cmd_branch_history(BzrToolsCommand): """\ Display the development history of a branch. Each different committer or branch nick is considered a different line of development. Committers are treated as the same if they have the same name, or if they have the same email address. """ takes_args = ["branch?"] def run(self, branch=None): from branchhistory import branch_history return branch_history(branch) class cmd_zap(BzrToolsCommand): """\ Remove a lightweight checkout, if it can be done safely. This command will remove a lightweight checkout without losing data. That means it only removes lightweight checkouts, and only if they have no uncommitted changes. If --branch is specified, the branch will be deleted too, but only if the the branch has no new commits (relative to its parent). If bzr-pipeline is also installed, the --store option will store changes in the branch before deleting the tree. To restore the changes, do:: bzr checkout --lightweight $BRANCH $CHECKOUT bzr switch-pipe -d $CHECKOUT `bzr nick -d $CHECKOUT` """ takes_options = [Option("branch", help="Remove associated branch from" " repository."), RegistryOption('change_policy', 'How to handle changed files', lazy_registry = ('bzrlib.plugins.bzrtools.zap', 'change_policy_registry'), value_switches=True, enum_switch=False)] takes_args = ["checkout"] def run(self, checkout, branch=False, change_policy=None): from zap import ( change_policy_registry, StoreChanges, zap, ) if change_policy is None: change_policy = change_policy_registry.get() if change_policy is StoreChanges: try: import bzrlib.plugins.pipeline except ImportError: raise BzrCommandError('--store requires bzr-pipeline.') return zap(checkout, remove_branch=branch, policy=change_policy) class cmd_cbranch(BzrToolsCommand): """ Create a new checkout, associated with a new repository branch. When you cbranch, bzr looks up a target location in locations.conf, and creates the branch there. In your locations.conf, add the following lines: [/working_directory_root] cbranch_target = /branch_root cbranch_target:policy = appendpath This will mean that if you run "bzr cbranch foo/bar foo/baz" in the working directory root, the branch will be created in "/branch_root/foo/baz" NOTE: cbranch also supports "cbranch_root", but that behaviour is deprecated. """ takes_options = [Option("lightweight", help="Create a lightweight checkout."), 'revision', Option('files-from', type=unicode, help='Accelerate checkout using files from this' ' tree.'), Option('hardlink', help='Hard-link files from source/files-from tree' ' where posible.')] takes_args = ["source", "target?"] def run(self, source, target=None, lightweight=False, revision=None, files_from=None, hardlink=False): from cbranch import cbranch return cbranch(source, target, lightweight=lightweight, revision=revision, files_from=files_from, hardlink=hardlink) class cmd_list_branches(BzrToolsCommand): """Scan a location for branches""" @property def aliases(self): from bzrlib import commands return commands.plugin_cmds.get_info('list-branches').aliases takes_args = ["location?"] def run(self, location=None): from branches import branches return branches(location) class cmd_trees(BzrToolsCommand): """Scan a location for trees""" takes_args = ['location?'] def run(self, location='.'): from bzrlib.workingtree import WorkingTree from bzrlib.transport import get_transport t = get_transport(location) for tree in WorkingTree.find_trees(location): self.outf.write('%s\n' % t.relpath( tree.bzrdir.root_transport.base)) class cmd_multi_pull(BzrToolsCommand): """Pull all the branches under a location, e.g. a repository. Both branches present in the directory and the branches of checkouts are pulled. """ takes_args = ["location?"] def run(self, location=None): from bzrlib.transport import get_transport from bzrtools import iter_branch_tree if location is None: location = '.' t = get_transport(location) possible_transports = [] if not t.listable(): print "Can't list this type of location." return 3 for branch, wt in iter_branch_tree(t): if wt is None: pullable = branch else: pullable = wt parent = branch.get_parent() if parent is None: continue if wt is not None: base = wt.basedir else: base = branch.base if base.startswith(t.base): relpath = base[len(t.base):].rstrip('/') else: relpath = base print "Pulling %s from %s" % (relpath, parent) try: branch_t = get_transport(parent, possible_transports) pullable.pull(Branch.open_from_transport(branch_t)) except Exception, e: print e class cmd_import(BzrToolsCommand): """Import sources from a directory, tarball or zip file This command will import a directory, tarball or zip file into a bzr tree, replacing any versioned files already present. If a directory is specified, it is used as the target. If the directory does not exist, or is not versioned, it is created. Tarballs may be gzip or bzip2 compressed. This is autodetected. If the tarball or zip has a single root directory, that directory is stripped when extracting the tarball. This is not done for directories. """ takes_args = ['source', 'tree?'] def run(self, source, tree=None): from upstream_import import do_import do_import(source, tree) class cmd_cdiff(BzrToolsCommand): """A color version of bzr's diff""" takes_args = property(lambda x: get_cmd_object('diff').takes_args) takes_options = list(get_cmd_object('diff').takes_options) + [ RegistryOption.from_kwargs('color', 'Color mode to use.', title='Color Mode', value_switches=False, enum_switch=True, never='Never colorize output.', auto='Only colorize output if terminal supports it and STDOUT is a' ' TTY.', always='Always colorize output (default).'), Option('check-style', help='Warn if trailing whitespace or spurious changes have been' ' added.')] def run(self, color='always', check_style=False, *args, **kwargs): from colordiff import colordiff colordiff(color, check_style, *args, **kwargs) class cmd_conflict_diff(BzrToolsCommand): """Compare a conflicted file against BASE.""" encoding_type = 'exact' takes_args = ['file*'] takes_options = [ RegistryOption.from_kwargs('direction', 'Direction of comparison.', value_switches=True, enum_switch=False, other='Compare OTHER against common base.', this='Compare THIS against common base.')] def run(self, file_list, direction='other'): from bzrlib.plugins.bzrtools.colordiff import DiffWriter from conflict_diff import ConflictDiffer dw = DiffWriter(self.outf, check_style=False, color='auto') ConflictDiffer().run(dw, file_list, direction) class cmd_rspush(BzrToolsCommand): """Upload this branch to another location using rsync. If no location is specified, the last-used location will be used. To prevent dirty trees from being uploaded, rspush will error out if there are unknown files or local changes. It will also error out if the upstream directory is non-empty and not an earlier version of the branch. """ takes_args = ['location?'] takes_options = [Option('overwrite', help='Ignore differences between' ' branches and overwrite unconditionally.'), Option('no-tree', help='Do not push the working tree,' ' just the .bzr.')] def run(self, location=None, overwrite=False, no_tree=False): from bzrlib import workingtree import bzrtools cur_branch = workingtree.WorkingTree.open_containing(".")[0] bzrtools.rspush(cur_branch, location, overwrite=overwrite, working_tree=not no_tree) class cmd_link_tree(BzrToolsCommand): """Hardlink matching files to another tree. Only files with identical content and execute bit will be linked. """ takes_args = ['location'] def run(self, location): from bzrlib import workingtree from bzrlib.plugins.bzrtools.link_tree import link_tree target_tree = workingtree.WorkingTree.open_containing(".")[0] source_tree = workingtree.WorkingTree.open(location) target_tree.lock_write() try: source_tree.lock_read() try: link_tree(target_tree, source_tree) finally: source_tree.unlock() finally: target_tree.unlock() class cmd_create_mirror(BzrToolsCommand): """Create a mirror of another branch. This is similar to `bzr branch`, but copies more settings, including the submit branch and nickname. It sets the public branch and parent of the target to the source location. """ takes_args = ['source', 'target'] def run(self, source, target): source_branch = Branch.open(source) from bzrlib.plugins.bzrtools.mirror import create_mirror create_mirror(source_branch, target, []) bzrtools/conflict_diff.py0000644000000000000000000000616212264646316014244 0ustar 00000000000000# Copyright (C) 2009 Aaron Bentley # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import errno from bzrlib.conflicts import TextConflict from bzrlib.diff import internal_diff from bzrlib.workingtree import WorkingTree from bzrlib.plugins.bzrtools import errors class ConflictDiffer(object): def __init__(self): self._old_tree = None self._tree = None def run(self, output, files=None, direction='other'): if files is None: self._tree = WorkingTree.open_containing('.')[0] files = [self._tree.abspath(c.path) for c in self._tree.conflicts() if isinstance(c, TextConflict)] for filename in files: self.conflict_diff(output, filename, direction) def conflict_diff(self, output, filename, direction): """Perform a diff for a file with conflicts.""" old_path = filename + '.BASE' old_lines = self.get_old_lines(filename, old_path) new_path_extension = { 'other': '.OTHER', 'this': '.THIS'}[direction] new_path = filename + new_path_extension newlines = open(new_path).readlines() internal_diff(old_path, old_lines, new_path, newlines, output) def get_old_tree(self, tree, base_path): if self._old_tree is not None: return self._old_tree graph = tree.branch.repository.get_graph() parent_ids = tree.get_parent_ids() if len(parent_ids) < 2: raise errors.NoConflictFiles(base_path) lca = graph.find_unique_lca(*parent_ids) return tree.branch.repository.revision_tree(lca) def get_old_lines(self, filename, base_path): """"Return the lines from before the conflicting changes were made.""" try: old_lines = open(base_path).readlines() except IOError, e: if e.errno != errno.ENOENT: raise return self.get_base_tree_lines(filename, base_path) return old_lines def get_base_tree_lines(self, filename, base_path): if self._tree is None: tree, path = WorkingTree.open_containing(filename) else: tree = self._tree path = tree.relpath(filename) tree.lock_read() try: file_id = tree.path2id(path) old_tree = self.get_old_tree(tree, base_path) return old_tree.get_file_lines(file_id) finally: tree.unlock() bzrtools/dotgraph.py0000644000000000000000000002124612264646316013263 0ustar 00000000000000# Copyright (C) 2004, 2005 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from subprocess import Popen, PIPE import os.path import errno import tempfile import shutil RSVG_OUTPUT_TYPES = ('png', 'jpg') DOT_OUTPUT_TYPES = ('svg', 'svgz', 'gif', 'jpg', 'ps', 'fig', 'mif', 'png', 'cmapx') class NoDot(Exception): def __init__(self): Exception.__init__(self, "Can't find dot!") class NoRsvg(Exception): def __init__(self): Exception.__init__(self, "Can't find rsvg!") class Node(object): def __init__(self, name, color=None, label=None, rev_id=None, cluster=None, node_style=None, date=None, message=None): self.name = name self.color = color self.label = label self.committer = None self.rev_id = rev_id if node_style is None: self.node_style = [] self.cluster = cluster self.rank = None self.date = date self.message = message self.href = None @staticmethod def get_attribute(name, value): if value is None: return '' value = value.replace("\\", "\\\\") value = value.replace('"', '\\"') value = value.replace('\n', '\\n') return '%s="%s"' % (name, value) def define(self): attributes = [] style = [] if self.color is not None: attributes.append('fillcolor="%s"' % self.color) style.append('filled') style.extend(self.node_style) if len(style) > 0: attributes.append('style="%s"' % ",".join(style)) label = self.label if label is not None: attributes.append('label="%s"' % label) attributes.append('shape="box"') tooltip = None if self.message is not None: tooltip = self.message attributes.append(self.get_attribute('tooltip', tooltip)) if self.href is not None: attributes.append('href="%s"' % self.href) elif tooltip: attributes.append('href="#"') if len(attributes) > 0: return '%s[%s]' % (self.name, " ".join(attributes)) def __str__(self): return self.name class Edge(object): def __init__(self, start, end, label=None): object.__init__(self) self.start = start self.end = end self.label = label def dot(self, do_weight=False): attributes = [] if self.label is not None: attributes.append(('label', self.label)) if do_weight: weight = '0' if self.start.cluster == self.end.cluster: weight = '1' elif self.start.rank is None: weight = '1' elif self.end.rank is None: weight = '1' attributes.append(('weight', weight)) if len(attributes) > 0: atlist = [] for key, value in attributes: atlist.append("%s=\"%s\"" % (key, value)) pq = ' '.join(atlist) op = "[%s]" % pq else: op = "" return "%s->%s%s;" % (self.start.name, self.end.name, op) def make_edge(relation): if hasattr(relation, 'start') and hasattr(relation, 'end'): return relation return Edge(relation[0], relation[1]) def dot_output(relations, ranking="forced"): defined = {} yield "digraph G\n" yield "{\n" clusters = set() edges = [make_edge(f) for f in relations] def rel_appropriate(start, end, cluster): if cluster is None: return (start.cluster is None and end.cluster is None) or \ start.cluster != end.cluster else: return start.cluster==cluster and end.cluster==cluster for edge in edges: if edge.start.cluster is not None: clusters.add(edge.start.cluster) if edge.end.cluster is not None: clusters.add(edge.end.cluster) clusters = list(clusters) clusters.append(None) for index, cluster in enumerate(clusters): if cluster is not None and ranking == "cluster": yield "subgraph cluster_%s\n" % index yield "{\n" yield ' label="%s"\n' % cluster for edge in edges: if edge.start.name not in defined and edge.start.cluster == cluster: defined[edge.start.name] = edge.start my_def = edge.start.define() if my_def is not None: yield " %s\n" % my_def if edge.end.name not in defined and edge.end.cluster == cluster: defined[edge.end.name] = edge.end my_def = edge.end.define() if my_def is not None: yield " %s;\n" % my_def if rel_appropriate(edge.start, edge.end, cluster): yield " %s\n" % edge.dot(do_weight=ranking=="forced") if cluster is not None and ranking == "cluster": yield "}\n" if ranking == "forced": ranks = {} for node in defined.itervalues(): if node.rank not in ranks: ranks[node.rank] = set() ranks[node.rank].add(node.name) sorted_ranks = [n for n in ranks.iteritems()] sorted_ranks.sort() last_rank = None for rank, nodes in sorted_ranks: if rank is None: continue yield 'rank%d[style="invis"];\n' % rank if last_rank is not None: yield 'rank%d -> rank%d[style="invis"];\n' % (last_rank, rank) last_rank = rank for rank, nodes in ranks.iteritems(): if rank is None: continue node_text = "; ".join('"%s"' % n for n in nodes) yield ' {rank = same; "rank%d"; %s}\n' % (rank, node_text) yield "}\n" def invoke_dot_aa(input, out_file, file_type='png'): """\ Produce antialiased Dot output, invoking rsvg on an intermediate file. rsvg only supports png, jpeg and .ico files.""" tempdir = tempfile.mkdtemp() try: temp_file = os.path.join(tempdir, 'temp.svg') invoke_dot(input, temp_file, 'svg') cmdline = ['rsvg', temp_file, out_file] try: rsvg_proc = Popen(cmdline) except OSError, e: if e.errno == errno.ENOENT: raise NoRsvg() status = rsvg_proc.wait() finally: shutil.rmtree(tempdir) return status def invoke_dot(input, out_file=None, file_type='svg', antialias=None, fontname="Helvetica", fontsize=11): cmdline = ['dot', '-T%s' % file_type, '-Nfontname=%s' % fontname, '-Efontname=%s' % fontname, '-Nfontsize=%d' % fontsize, '-Efontsize=%d' % fontsize] if out_file is not None: cmdline.extend(('-o', out_file)) try: dot_proc = Popen(cmdline, stdin=PIPE) except OSError, e: if e.errno == errno.ENOENT: raise NoDot() else: raise for line in input: dot_proc.stdin.write(line.encode('utf-8')) dot_proc.stdin.close() return dot_proc.wait() def invoke_dot_html(input, out_file): """\ Produce an html file, which uses a .png file, and a cmap to provide annotated revisions. """ tempdir = tempfile.mkdtemp() try: temp_dot = os.path.join(tempdir, 'temp.dot') status = invoke_dot(input, temp_dot, file_type='dot') dot = open(temp_dot) temp_file = os.path.join(tempdir, 'temp.cmapx') status = invoke_dot(dot, temp_file, 'cmapx') png_file = '.'.join(out_file.split('.')[:-1] + ['png']) dot.seek(0) status = invoke_dot(dot, png_file, 'png') png_relative = png_file.split('/')[-1] html = open(out_file, 'wb') w = html.write w('\n') w('\n') w('' % png_relative) w(open(temp_file).read()) w('\n') finally: shutil.rmtree(tempdir) return status bzrtools/errors.py0000644000000000000000000000540212264646316012763 0ustar 00000000000000# Copyright (C) 2005 Aaron Bentley, 2006 Michael Ellerman # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os try: from bzrlib.errors import BzrCommandError as CommandError from bzrlib.errors import BzrError except ImportError: class CommandError(Exception): pass class PatchFailed(BzrError): _fmt = """Patch application failed""" class PatchInvokeError(BzrError): _fmt = """Error invoking patch: %(errstr)s%(stderr)s""" internal_error = False def __init__(self, e, stderr=''): self.exception = e self.errstr = os.strerror(e.errno) self.stderr = '\n' + stderr class NoColor(Exception): """Color not available on this terminal.""" class NoBzrtoolsColor(Exception): """Bzrtools is required for color display""" class NotCheckout(CommandError): """Specified path is not a checkout.""" def __init__(self, path): CommandError.__init__(self, "%s is not a checkout" % path) class UncommittedCheckout(CommandError): """This checkout contains uncommitted changes""" def __init__(self): CommandError.__init__(self, "This checkout has uncommitted changes") class ParentMissingRevisions(CommandError): """The parent branch is missing revisions.""" def __init__(self, parent): CommandError.__init__(self, "The parent branch %s is missing revisions from this branch." % parent) self.parent = parent class NoParent(CommandError): def __init__(self): CommandError.__init__(self, "There is no parent, so deleting the" " branch could destroy data.") class ChangedBinaryFiles(BzrError): _fmt = 'Changes involve binary files.' class NoConflictFiles(CommandError): _fmt = '%(base_name)s does not exist and there are no pending merges.' def __init__(self, base_name): CommandError.__init__(self, base_name=base_name) class NotArchiveType(BzrError): _fmt = '%(path)s is not an archive.' def __init__(self, path): BzrError.__init__(self) self.path = path bzrtools/fetch_ghosts.py0000644000000000000000000000727012264646316014134 0ustar 00000000000000# Copyright (C) 2005 by Aaron Bentley # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from bzrlib.branch import Branch from bzrlib.trace import note from bzrlib.errors import NoSuchRevision, BzrCommandError class GhostFetcher(object): @classmethod def from_cmdline(klass, other): this_branch = Branch.open_containing('.')[0] if other is None: other = this_branch.get_parent() if other is None: raise BzrCommandError('No branch specified and no location' ' saved.') else: note("Using saved location %s.", other) other_branch = Branch.open_containing(other)[0] return klass(this_branch, other_branch) def __init__(self, this_branch, other_branch): self.this_branch = this_branch self.other_branch = other_branch def run(self): lock_other = self.this_branch.base != self.other_branch.base self.this_branch.lock_write() try: if lock_other: self.other_branch.lock_read() try: return self._run_locked() finally: if lock_other: self.other_branch.unlock() finally: self.this_branch.unlock() def iter_ghosts(self): """Find all ancestors that aren't stored in this branch.""" seen = set() lines = [self.this_branch.last_revision()] if lines[0] is None: return while len(lines) > 0: new_lines = [] for line in lines: if line in seen: continue seen.add(line) try: revision = self.this_branch.repository.get_revision(line) new_lines.extend(revision.parent_ids) except NoSuchRevision: yield line lines = new_lines def _run_locked(self): installed = [] failed = [] if self.this_branch.last_revision() is None: print "No revisions in branch." return # Because iter_ghosts tests for existence after our last fetch # is complete, it won't falsely report an ancestor as a ghost. # Yay iterators! ghosts = self.iter_ghosts() for revision in ghosts: try: self.this_branch.fetch(self.other_branch, revision) installed.append(revision) except NoSuchRevision: failed.append(revision) return installed, failed def fetch_ghosts(branch, do_reconcile): """Install ghosts from copies in another branch.""" installed, failed = GhostFetcher.from_cmdline(branch).run() if len(installed) > 0: print "Installed:" for rev in installed: print rev if len(failed) > 0: print "Still missing:" for rev in failed: print rev if do_reconcile and len(installed) > 0: from bzrlib.builtins import cmd_reconcile cmd_reconcile().run(".") bzrtools/graph.py0000644000000000000000000003555712264646316012566 0ustar 00000000000000# Copyright (C) 2005, 2008 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import time from bzrlib.branch import Branch from bzrlib.errors import BzrCommandError, NoSuchRevision from bzrlib.revision import NULL_REVISION from bzrtools import short_committer from dotgraph import ( dot_output, DOT_OUTPUT_TYPES, Edge, invoke_dot, invoke_dot_aa, invoke_dot_html, Node, NoDot, NoRsvg, RSVG_OUTPUT_TYPES, ) def max_distance(node, ancestors, distances, root_descendants): """Calculate the max distance to an ancestor. Return None if not all possible ancestors have known distances""" best = None if node in distances: best = distances[node] for ancestor in ancestors[node]: # skip ancestors we will never traverse: if root_descendants is not None and ancestor not in root_descendants: continue # An ancestor which is not listed in ancestors will never be in # distances, so we pretend it never existed. if ancestor not in ancestors: continue if ancestor not in distances: return None if best is None or distances[ancestor]+1 > best: best = distances[ancestor] + 1 return best def node_distances(graph, ancestors, start, root_descendants=None): """Produce a list of nodes, sorted by distance from a start node. This is an algorithm devised by Aaron Bentley, because applying Dijkstra backwards seemed too complicated. For each node, we walk its descendants. If all the descendant's ancestors have a max-distance-to-start, (excluding ones that can never reach start), we calculate their max-distance-to-start, and schedule their descendants. So when a node's last parent acquires a distance, it will acquire a distance on the next iteration. Once we know the max distances for all nodes, we can return a list sorted by distance, farthest first. """ distances = {start: 0} lines = set([start]) while len(lines) > 0: new_lines = set() for line in lines: line_descendants = graph[line] for descendant in line_descendants: distance = max_distance(descendant, ancestors, distances, root_descendants) if distance is None: continue distances[descendant] = distance new_lines.add(descendant) lines = new_lines return distances def nodes_by_distance(distances): """Return a list of nodes sorted by distance""" def by_distance(n): return distances[n],n node_list = distances.keys() node_list.sort(key=by_distance, reverse=True) return node_list def select_farthest(distances, common): """Return the farthest common node, or None if no node qualifies.""" node_list = nodes_by_distance(distances) for node in node_list: if node in common: return node mail_map = {'aaron.bentley@utoronto.ca' : 'Aaron Bentley', 'abentley@panoramicfeedback.com': 'Aaron Bentley', 'abentley@lappy' : 'Aaron Bentley', 'john@arbash-meinel.com' : 'John Arbash Meinel', 'mbp@sourcefrog.net' : 'Martin Pool', 'robertc@robertcollins.net' : 'Robert Collins', } committer_alias = {'abentley': 'Aaron Bentley'} def can_skip(rev_id, descendants, ancestors): if rev_id not in descendants: return False elif rev_id not in ancestors: return False elif len(ancestors[rev_id]) != 1: return False elif len(descendants[list(ancestors[rev_id])[0]]) != 1: return False elif len(descendants[rev_id]) != 1: return False else: return True def compact_ancestors(descendants, ancestors, exceptions=()): new_ancestors={} skip = set() for me, my_parents in ancestors.iteritems(): if me in skip: continue new_ancestors[me] = {} for parent in my_parents: new_parent = parent distance = 0 while can_skip(new_parent, descendants, ancestors): if new_parent in exceptions: break skip.add(new_parent) if new_parent in new_ancestors: del new_ancestors[new_parent] new_parent = list(ancestors[new_parent])[0] distance += 1 new_ancestors[me][new_parent] = distance return new_ancestors def get_rev_info(rev_id, source): """Return the committer, message, nick and date of a revision.""" committer = None message = None date = None nick = None if rev_id == 'null:': return None, 'Null Revision', None, None try: rev = source.get_revision(rev_id) except NoSuchRevision: try: committer = '-'.join(rev_id.split('-')[:-2]).strip(' ') if committer == '': return None, None, None, None except ValueError: return None, None, None, None else: committer = short_committer(rev.committer) if rev.message is not None: message = rev.message.split('\n')[0] gmtime = time.gmtime(rev.timestamp + (rev.timezone or 0)) date = time.strftime('%Y/%m/%d', gmtime) nick = rev.properties.get('branch-nick') if '@' in committer: try: committer = mail_map[committer] except KeyError: pass try: committer = committer_alias[committer] except KeyError: pass return committer, message, nick, date class Grapher(object): def __init__(self, branch, other_branch=None): object.__init__(self) self.branch = branch self.other_branch = other_branch if other_branch is not None: other_repo = other_branch.repository revision_b = self.other_branch.last_revision() else: other_repo = None revision_b = None self.graph = self.branch.repository.get_graph(other_repo) revision_a = self.branch.last_revision() self.scan_graph(revision_a, revision_b) self.n_history = list(self.graph.iter_lefthand_ancestry(revision_a)) self.n_history.reverse() self.n_revnos = branch.get_revision_id_to_revno_map() self.distances = node_distances(self.descendants, self.ancestors, self.root) if other_branch is not None: self.base = select_farthest(self.distances, self.common) self.m_history = self.graph.iter_lefthand_ancestry(revision_b) self.m_history = list(self.m_history) self.m_history.reverse() self.m_revnos = other_branch.get_revision_id_to_revno_map() self.new_base = self.graph.find_unique_lca(revision_a, revision_b) self.lcas = self.graph.find_lca(revision_a, revision_b) else: self.base = None self.new_base = None self.lcas = set() self.m_history = [] self.m_revnos = {} def scan_graph(self, revision_a, revision_b): a_ancestors = dict(self.graph.iter_ancestry([revision_a])) self.ancestors = a_ancestors self.root = NULL_REVISION if revision_b is not None: b_ancestors = dict(self.graph.iter_ancestry([revision_b])) self.common = set(a_ancestors.keys()) self.common.intersection_update(b_ancestors) self.ancestors.update(b_ancestors) else: self.common = [] revision_b = None self.descendants = {} ghosts = set() for revision, parents in self.ancestors.iteritems(): self.descendants.setdefault(revision, []) if parents is None: ghosts.add(revision) parents = [NULL_REVISION] for parent in parents: self.descendants.setdefault(parent, []).append(revision) for ghost in ghosts: self.ancestors[ghost] = [NULL_REVISION] @staticmethod def _get_revno_str(prefix, revno_map, revision_id): try: revno = revno_map[revision_id] except KeyError: return None return '%s%s' % (prefix, '.'.join(str(n) for n in revno)) def dot_node(self, node, num): try: n_rev = self.n_history.index(node) + 1 except ValueError: n_rev = None try: m_rev = self.m_history.index(node) + 1 except ValueError: m_rev = None if (n_rev, m_rev) == (None, None): name = self._get_revno_str('r', self.n_revnos, node) if name is None: name = self._get_revno_str('R', self.m_revnos, node) if name is None: name = node[-5:] cluster = None elif n_rev == m_rev: name = "rR%d" % n_rev else: namelist = [] for prefix, revno in (('r', n_rev), ('R', m_rev)): if revno is not None: namelist.append("%s%d" % (prefix, revno)) name = ' '.join(namelist) if None not in (n_rev, m_rev): cluster = "common_history" color = "#ff9900" elif (None, None) == (n_rev, m_rev): cluster = None if node in self.common: color = "#6699ff" else: color = "white" elif n_rev is not None: cluster = "my_history" color = "#ffff00" else: assert m_rev is not None cluster = "other_history" color = "#ff0000" if node in self.lcas: color = "#9933cc" if node == self.base: color = "#669933" if node == self.new_base: color = "#33ff33" if node == self.new_base: color = '#33cc99' label = [name] committer, message, nick, date = get_rev_info(node, self.branch.repository) if committer is not None: label.append(committer) if nick is not None: label.append(nick) if date is not None: label.append(date) if node in self.distances: rank = self.distances[node] label.append('d%d' % self.distances[node]) else: rank = None d_node = Node("n%d" % num, color=color, label="\\n".join(label), rev_id=node, cluster=cluster, message=message, date=date) d_node.rank = rank if node not in self.ancestors: d_node.node_style.append('dotted') return d_node def get_relations(self, collapse=False, max_distance=None): dot_nodes = {} node_relations = [] num = 0 if collapse: exceptions = self.lcas.union([self.base, self.new_base]) visible_ancestors = compact_ancestors(self.descendants, self.ancestors, exceptions) else: visible_ancestors = {} for revision, parents in self.ancestors.iteritems(): visible_ancestors[revision] = dict((p, 0) for p in parents) if max_distance is not None: min_distance = max(self.distances.values()) - max_distance visible_ancestors = dict((n, p) for n, p in visible_ancestors.iteritems() if self.distances[n] >= min_distance) for node, parents in visible_ancestors.iteritems(): if node not in dot_nodes: dot_nodes[node] = self.dot_node(node, num) num += 1 for parent, skipped in parents.iteritems(): if parent not in dot_nodes: dot_nodes[parent] = self.dot_node(parent, num) num += 1 edge = Edge(dot_nodes[parent], dot_nodes[node]) if skipped != 0: edge.label = "%d" % skipped node_relations.append(edge) return node_relations def write_ancestry_file(branch, filename, collapse=True, antialias=True, merge_branch=None, ranking="forced", max_distance=None): b = Branch.open_containing(branch)[0] if merge_branch is not None: m = Branch.open_containing(merge_branch)[0] else: m = None b.lock_write() try: if m is not None: m.lock_read() try: grapher = Grapher(b, m) relations = grapher.get_relations(collapse, max_distance) finally: if m is not None: m.unlock() finally: b.unlock() ext = filename.split('.')[-1] output = dot_output(relations, ranking) done = False if ext not in RSVG_OUTPUT_TYPES: antialias = False if antialias: output = list(output) try: invoke_dot_aa(output, filename, ext) done = True except NoDot, e: raise BzrCommandError("Can't find 'dot'. Please ensure Graphviz"\ " is installed correctly.") except NoRsvg, e: print "Not antialiasing because rsvg (from librsvg-bin) is not"\ " installed." antialias = False if ext in DOT_OUTPUT_TYPES and not antialias and not done: try: invoke_dot(output, filename, ext) done = True except NoDot, e: raise BzrCommandError("Can't find 'dot'. Please ensure Graphviz"\ " is installed correctly.") elif ext == 'dot' and not done: my_file = file(filename, 'wb') for fragment in output: my_file.write(fragment.encode('utf-8')) elif ext == 'html': try: invoke_dot_html(output, filename) except NoDot, e: raise BzrCommandError("Can't find 'dot'. Please ensure Graphviz"\ " is installed correctly.") elif not done: print "Unknown file extension: %s" % ext bzrtools/heads.py0000644000000000000000000001625712264646316012545 0ustar 00000000000000# Written by Alexander Belchenko # based on Robert Collins code # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Show all 'heads' in a repository""" import time import bzrlib from bzrlib.commands import Command, display_command from bzrlib import errors from bzrlib.option import Option from bzrlib.urlutils import unescape_for_display class cmd_heads(Command): """Show all revisions in a repository not having descendants. """ takes_options = [Option('by-date', help='Sort heads by date (descending).'), Option('all', help='Show all heads (dead and alive).'), Option('dead-only', help='Show only dead heads.'), Option('tips', help='Show tips of all branches.'), Option('debug-time', help='Enable debug print of operations times.'), ] encoding_type = "replace" takes_args = ['location?'] @display_command def run(self, by_date=False, all=False, dead_only=False, tips=False, debug_time=False, location='.'): import bzrlib.branch from bzrlib.osutils import format_date import bzrlib.repository self._init_elapsed_time(debug_time) to_file = self.outf branch = None try: branch = bzrlib.branch.Branch.open_containing(location)[0] repo = branch.repository except errors.NotBranchError: try: repo = bzrlib.repository.Repository.open(location) except errors.NotBranchError: print >>to_file, \ ("You need to run this command " "either from the root of a shared repository,\n" "or from a branch.") return 3 repo.lock_read() try: possible_heads = set(repo.all_revision_ids()) g = repo.get_graph().get_parent_map(possible_heads) not_heads = set() for parents in g.values(): not_heads.update(set(parents)) self.heads = possible_heads.difference(not_heads) # TODO: use different sorting schemes instead of alphabetical sort self.heads = list(self.heads) self._print_elapsed_time('get heads:') ## mark heads as dead or alive # mark all heads as dead self.head_mark = {} self.tips = {} for head in self.heads: self.head_mark[head] = 'dead' # give the list of live branches in repository or current branch # tip self._iter_branches_update_marks(repo, self.outf.encoding) self._print_elapsed_time('make head marks:') if tips: heads_tips = set(self.heads) heads_tips.update(set(self.tips.keys())) self.heads = list(heads_tips) head_revisions = dict(zip(self.heads, repo.get_revisions(self.heads))) # sorting by date if by_date: dates = {} for head in self.heads: rev = head_revisions[head] timestamp = rev.timestamp dates[timestamp] = head keys = dates.keys() keys.sort() keys.reverse() self.heads = [] for k in keys: self.heads.append(dates[k]) self._print_elapsed_time('sort by date:') # show time indent = ' '*2 show_timezone = 'original' for head in self.heads: mark = self.head_mark[head] if not all: if dead_only: if mark != 'dead': continue else: if mark != 'alive': if not tips or mark != 'tip': continue # tips if mark in ('alive', 'tip'): t = self.tips[head] if len(t) > 1: print >>to_file, 'TIP of branches:', print >>to_file, '[', ', '.join(t), ']' else: print >>to_file, 'TIP of branch:', t[0] if mark in ('alive', 'dead'): print >>to_file, 'HEAD:', print >>to_file, "revision-id:", head, if mark == 'dead': print >>to_file, '(dead)' else: print >>to_file rev = head_revisions[head] # borrowed from LongLogFormatter print >>to_file, indent+'committer:', rev.committer try: print >>to_file, indent+'branch nick: %s' % \ rev.properties['branch-nick'] except KeyError: pass date_str = format_date(rev.timestamp, rev.timezone or 0, show_timezone) print >>to_file, indent+'timestamp: %s' % date_str print >>to_file, indent+'message:' if not rev.message: print >>to_file, indent+' (no message)' else: message = rev.message.rstrip('\r\n') for l in message.split('\n'): print >>to_file, indent+' ' + l self._print_elapsed_time('print head:') print if not self.heads: print >>to_file, 'No heads found' return 1 finally: repo.unlock() def _iter_branches_update_marks(self, repo, encoding): for b in repo.find_branches(using=True): last_revid = b.last_revision() if last_revid in self.heads: self.head_mark[last_revid] = 'alive' else: self.head_mark[last_revid] = 'tip' escaped = unescape_for_display(b.base, encoding) self.tips.setdefault(last_revid, []).append(escaped) def _init_elapsed_time(self, debug_time=False): self.debug_time = debug_time if debug_time: self._time = time.time() def _print_elapsed_time(self, msg): if self.debug_time: last_time = time.time() print msg, last_time - self._time self._time = last_time #/class cmd_heads bzrtools/hunk_selector.py0000644000000000000000000001654312264646316014324 0ustar 00000000000000import sys from userinteractor import UserInteractor, UserOption from errors import NoColor, NoBzrtoolsColor import copy class HunkSelector: strings = {} def __init__(self, patches, color=None): if color is True or color is None: try: from colordiff import DiffWriter from terminal import has_ansi_colors if has_ansi_colors(): self.diff_stream = DiffWriter(sys.stdout, check_style=False) else: if color is True: raise NoColor() self.diff_stream = sys.stdout except ImportError: if color is True: raise NoBzrtoolsColor() self.diff_stream = sys.stdout else: self.diff_stream = sys.stdout self.standard_options = [ UserOption('y', self._selected, self.strings['select_desc'], default=True), UserOption('n', self._unselected, self.strings['unselect_desc']), UserOption('d', UserInteractor.FINISH, 'done, skip to the end.'), UserOption('i', self._invert, 'invert the current selection status of all hunks.'), UserOption('s', self._status, 'show selection status of all hunks.'), UserOption('q', UserInteractor.QUIT, 'quit') ] self.end_options = [ UserOption('y', UserInteractor.FINISH, self.strings['finish_desc'], default=True), UserOption('r', UserInteractor.RESTART, 'restart the hunk selection loop.'), UserOption('s', self._status, 'show selection status of all hunks.'), UserOption('i', self._invert, 'invert the current selection status of all hunks.'), UserOption('q', UserInteractor.QUIT, 'quit') ] self.patches = patches self.total_hunks = 0 self.interactor = UserInteractor() self.interactor.set_item_callback(self._hunk_callback) self.interactor.set_start_callback(self._start_callback) self.interactor.set_end_callback(self._end_callback) for patch in patches: for hunk in patch.hunks: # everything's selected by default hunk.selected = True self.total_hunks += 1 # we need a back pointer in the callbacks hunk.patch = patch self.interactor.add_item(hunk) # Called at the start of the main loop def _start_callback(self): self.last_printed = -1 self.interactor.set_prompt(self.strings['prompt']) self.interactor.set_options(self.standard_options) # Called at the end of the item loop, return False to indicate that the # interaction isn't finished and the confirmation prompt should be displayed def _end_callback(self): self._status() self.interactor.set_prompt(self.strings['end_prompt']) self.interactor.set_options(self.end_options) return False # Called once for each hunk def _hunk_callback(self, hunk, count): if self.last_printed != count: self.diff_stream.write(str(hunk.patch.get_header())) self.diff_stream.write(str(hunk)) self.last_printed = count if hunk.selected: self.interactor.get_option('y').default = True self.interactor.get_option('n').default = False else: self.interactor.get_option('y').default = False self.interactor.get_option('n').default = True # The user chooses to (un)shelve a hunk def _selected(self, hunk): hunk.selected = True return True # The user chooses to keep a hunk def _unselected(self, hunk): hunk.selected = False return True # The user chooses to invert the selection def _invert(self, hunk): for patch in self.patches: for hunk in patch.hunks: if hunk.__dict__.has_key('selected'): hunk.selected = not hunk.selected else: hunk.selected = True self._status() return False # The user wants to see the status def _status(self, hunk=None): print '\nStatus:' for patch in self.patches: print ' %s' % patch.oldname selected = 0 unselected = 0 for hunk in patch.hunks: if hunk.selected: selected += 1 else: unselected += 1 print ' ', self.strings['status_selected'] % selected print ' ', self.strings['status_unselected'] % unselected print # Tell the interactor we're not done with this item return False def select(self): if self.total_hunks == 0 or not self.interactor.interact(): # False from interact means they chose to quit return ([], []) # Go through each patch and collect all selected/unselected hunks for patch in self.patches: patch.selected = [] patch.unselected = [] for hunk in patch.hunks: if hunk.selected: patch.selected.append(hunk) else: patch.unselected.append(hunk) # Now build two lists, one of selected patches the other unselected selected_patches = [] unselected_patches = [] for patch in self.patches: if len(patch.selected): tmp = copy.copy(patch) tmp.hunks = tmp.selected del tmp.selected del tmp.unselected selected_patches.append(tmp) if len(patch.unselected): tmp = copy.copy(patch) tmp.hunks = tmp.unselected del tmp.selected del tmp.unselected unselected_patches.append(tmp) return (selected_patches, unselected_patches) class ShelveHunkSelector(HunkSelector): def __init__(self, patches, color=None): self.strings = {} self.strings['status_selected'] = '%d hunks to be shelved' self.strings['status_unselected'] = '%d hunks to be kept' self.strings['select_desc'] = 'shelve this change.' self.strings['unselect_desc'] = 'keep this change in your tree.' self.strings['finish_desc'] = 'shelve selected changes.' self.strings['prompt'] = 'Shelve this change? (%(count)d of %(total)d)' self.strings['end_prompt'] = 'Shelve these changes?' HunkSelector.__init__(self, patches, color) class UnshelveHunkSelector(HunkSelector): def __init__(self, patches, color=None): self.strings = {} self.strings['status_selected'] = '%d hunks to be unshelved' self.strings['status_unselected'] = '%d hunks left on shelf' self.strings['select_desc'] = 'unshelve this change.' self.strings['unselect_desc'] = 'leave this change on the shelf.' self.strings['finish_desc'] = 'unshelve selected changes.' self.strings['prompt'] = 'Unshelve this change? ' \ '(%(count)d of %(total)d)' self.strings['end_prompt'] = 'Unshelve these changes?' HunkSelector.__init__(self, patches, color) bzrtools/link_tree.py0000644000000000000000000000273712264646316013433 0ustar 00000000000000# Copyright (C) 2008 Aaron Bentley # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from bzrlib.transform import TreeTransform def link_tree(target_tree, source_tree): tt = TreeTransform(target_tree) try: for (file_id, paths, changed_content, versioned, parent, name, kind, executable) in target_tree.iter_changes(source_tree, include_unchanged=True): if changed_content: continue if kind != ('file', 'file'): continue if executable[0] != executable[1]: continue trans_id = tt.trans_id_tree_file_id(file_id) tt.delete_contents(trans_id) tt.create_hardlink(source_tree.id2abspath(file_id), trans_id) tt.apply() finally: tt.finalize() bzrtools/mirror.py0000644000000000000000000000146612264646316012767 0ustar 00000000000000from bzrlib.transport import get_transport def create_mirror(source_branch, target_location, possible_transports): possible_transports.append(source_branch.bzrdir.root_transport) t = get_transport(target_location, possible_transports) mirror = source_branch.bzrdir.clone_on_transport(t).open_branch() mirror.set_public_branch(source_branch.base) mirror.set_parent(source_branch.base) if source_branch.get_config().has_explicit_nickname(): mirror.nick = source_branch.nick mirror.set_submit_branch(source_branch.get_submit_branch()) source_config = source_branch.get_config() child_submit_to = source_config.get_user_option('child_submit_to') if child_submit_to is not None: mirror.get_config().set_user_option('child_submit_to', child_submit_to) return mirror bzrtools/patch.py0000644000000000000000000000473712264646316012560 0ustar 00000000000000# Copyright (C) 2005, 2008 Aaron Bentley, 2006 Michael Ellerman # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys import subprocess import bzrlib.add from bzrlib.plugins.bzrtools.bzrtools import open_from_url from errors import PatchFailed, PatchInvokeError def patch(tree, location, strip, quiet=False): """Apply a patch to a branch, using patch(1). URLs may be used.""" my_file = None if location is None: my_file = sys.stdin else: my_file = open_from_url(location) patches = [my_file.read()] return run_patch(tree.basedir, patches, strip, quiet=quiet) def run_patch(directory, patches, strip=0, reverse=False, dry_run=False, quiet=False, _patch_cmd='patch', target_file=None): args = [_patch_cmd, '-d', directory, '-s', '-p%d' % strip, '-f'] if quiet: args.append('--quiet') if sys.platform == "win32": args.append('--binary') if reverse: args.append('-R') if dry_run: if sys.platform.startswith('freebsd'): args.append('--check') else: args.append('--dry-run') stderr = subprocess.PIPE else: stderr = None if target_file is not None: args.append(target_file) try: process = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=stderr) except OSError, e: raise PatchInvokeError(e) try: for patch in patches: process.stdin.write(str(patch)) process.stdin.close() except IOError, e: raise PatchInvokeError(e, process.stderr.read()) result = process.wait() if not dry_run: sys.stdout.write(process.stdout.read()) if result != 0: raise PatchFailed() return result bzrtools/patches/0000755000000000000000000000000012264646316012523 5ustar 00000000000000bzrtools/patchsource.py0000644000000000000000000000372512264646316013775 0ustar 00000000000000import re from bzrlib import ( patches, workingtree, ) from bzrlib.plugins.bzrtools import errors class PatchSource(object): def __iter__(self): def iterator(obj): for p in obj.read(): yield p return iterator(self) def readlines(self): raise NotImplementedError() def readpatches(self): return patches.parse_patches(self.readlines()) class FilePatchSource(PatchSource): def __init__(self, filename): self.filename = filename PatchSource.__init__(self) def readlines(self): f = open(self.filename, 'r') return f.readlines() class BzrPatchSource(PatchSource): def __init__(self, revision=None, file_list=None): open_containing_paths = workingtree.WorkingTree.open_containing_paths self.tree, self.file_list = open_containing_paths(file_list) self.base = self.tree.basedir self.revision = revision # Hacks to cope with v0.7 and v0.8 of bzr if self.revision is None: if hasattr(self.tree, 'basis_tree'): self.old_tree = self.tree.basis_tree() else: self.old_tree = self.tree.branch.basis_tree() else: revision_id = self.revision.in_store(self.tree.branch).rev_id if hasattr(self.tree.branch, 'repository'): self.old_tree = self.tree.branch.repository.revision_tree(revision_id) else: self.old_tree = self.tree.branch.revision_tree(revision_id) PatchSource.__init__(self) def readlines(self): from bzrlib.diff import show_diff_trees from StringIO import StringIO f = StringIO() show_diff_trees(self.old_tree, self.tree, f, self.file_list, old_label='', new_label='') if re.search('Binary files .* differ', f.getvalue()): raise errors.ChangedBinaryFiles() f.seek(0) return f.readlines() bzrtools/progress.py0000644000000000000000000000302412264646316013311 0ustar 00000000000000# Copyright (C) 2005 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import sys class Progress(object): def __init__(self, units, current, total=None): self.units = units self.current = current self.total = total def _get_percent(self): if self.total is None: return None return 100.0 * self.current / self.total percent = property(_get_percent) def __str__(self): if self.total is not None: return "%i of %i %s %.1f%%" % (self.current, self.total, self.units, self.percent) else: return "%i %s" (self.current, self.units) def show_progress(pi, prog): pi.update(prog.units, prog.current, prog.total) def clear_progress_bar(): sys.stderr.write('\r%s\r' % (' '*79)) bzrtools/rspush.py0000644000000000000000000000342212264646316012773 0ustar 00000000000000# Copyright (C) 2005 by Aaron Bentley # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from bzrlib.option import Option from bzrlib.workingtree import WorkingTree import bzrtools from command import BzrToolsCommand class cmd_rspush(BzrToolsCommand): """Upload this branch to another location using rsync. If no location is specified, the last-used location will be used. To prevent dirty trees from being uploaded, rspush will error out if there are unknown files or local changes. It will also error out if the upstream directory is non-empty and not an earlier version of the branch. """ takes_args = ['location?'] takes_options = [Option('overwrite', help='Ignore differences between' ' branches and overwrite unconditionally'), Option('no-tree', help='Do not push the working tree,' ' just the .bzr.')] def run(self, location=None, overwrite=False, no_tree=False): cur_branch = WorkingTree.open_containing(".")[0] bzrtools.rspush(cur_branch, location, overwrite=overwrite, working_tree=not no_tree) bzrtools/setup.py0000755000000000000000000000105712264646316012614 0ustar 00000000000000#!/usr/bin/env python from distutils.core import setup import version if __name__ == '__main__': setup(name="BzrTools", version=version.__version__, description="Handy utilities for working with Bazaar (bzr).", author="Aaron Bentley", author_email="aaron@aaronbentley.com", license = "GNU GPL v2", url="http://bazaar-vcs.org/BzrTools", packages=['bzrlib.plugins.bzrtools', 'bzrlib.plugins.bzrtools.tests'], package_dir={'bzrlib.plugins.bzrtools': '.', }) bzrtools/shelf.py0000644000000000000000000002230012264646316012544 0ustar 00000000000000import os import sys from datetime import datetime from errors import CommandError, PatchFailed from hunk_selector import ShelveHunkSelector, UnshelveHunkSelector from patch import run_patch from patchsource import FilePatchSource from bzrlib.osutils import rename class Shelf(object): MESSAGE_PREFIX = "# Shelved patch: " _paths = { 'base' : '.shelf', 'shelves' : '.shelf/shelves', 'current-shelf' : '.shelf/current-shelf', } def __init__(self, base, name=None): self.base = base self.__setup() if name is None: current = os.path.join(self.base, self._paths['current-shelf']) name = open(current).read().strip() assert '\n' not in name self.name = name self.dir = os.path.join(self.base, self._paths['shelves'], name) if not os.path.isdir(self.dir): os.mkdir(self.dir) def __setup(self): # Create required directories etc. for dir in [self._paths['base'], self._paths['shelves']]: dir = os.path.join(self.base, dir) if not os.path.isdir(dir): os.mkdir(dir) current = os.path.join(self.base, self._paths['current-shelf']) if not os.path.exists(current): f = open(current, 'w') f.write('default') f.close() def make_default(self): f = open(os.path.join(self.base, self._paths['current-shelf']), 'w') f.write(self.name) f.close() self.log("Default shelf is now '%s'\n" % self.name) def log(self, msg): sys.stderr.write(msg) def delete(self, patch): path = self.__path_from_user(patch) rename(path, '%s~' % path) def display(self, patch=None): if patch is None: path = self.last_patch() if path is None: raise CommandError("No patches on shelf.") else: path = self.__path_from_user(patch) sys.stdout.write(open(path).read()) def list(self): indexes = self.__list() self.log("Patches on shelf '%s':" % self.name) if len(indexes) == 0: self.log(' None\n') return self.log('\n') for index in indexes: msg = self.get_patch_message(self.__path(index)) if msg is None: msg = "No message saved with patch." self.log(' %.2d: %s\n' % (index, msg)) def __path_from_user(self, patch_id): try: patch_index = int(patch_id) except (TypeError, ValueError): raise CommandError("Invalid patch name '%s'" % patch_id) path = self.__path(patch_index) if not os.path.exists(path): raise CommandError("Patch '%s' doesn't exist on shelf %s!" % \ (patch_id, self.name)) return path def __path(self, index): return os.path.join(self.dir, '%.2d' % index) def next_patch(self): indexes = self.__list() if len(indexes) == 0: next = 0 else: next = indexes[-1] + 1 return self.__path(next) def __list(self): patches = os.listdir(self.dir) indexes = [] for f in patches: if f.endswith('~'): continue # ignore backup files try: indexes.append(int(f)) except ValueError: self.log("Warning: Ignoring junk file '%s' on shelf.\n" % f) indexes.sort() return indexes def last_patch(self): indexes = self.__list() if len(indexes) == 0: return None return self.__path(indexes[-1]) def get_patch_message(self, patch_path): patch = open(patch_path, 'r').read() if not patch.startswith(self.MESSAGE_PREFIX): return None return patch[len(self.MESSAGE_PREFIX):patch.index('\n')] def unshelve(self, patch_source, patch_name=None, all=False, force=False, no_color=False): self._check_upgrade() if no_color is False: color = None else: color = False if patch_name is None: patch_path = self.last_patch() else: patch_path = self.__path_from_user(patch_name) if patch_path is None: raise CommandError("No patch found on shelf %s" % self.name) patches = FilePatchSource(patch_path).readpatches() if all: to_unshelve = patches to_remain = [] else: hs = UnshelveHunkSelector(patches, color) to_unshelve, to_remain = hs.select() if len(to_unshelve) == 0: raise CommandError('Nothing to unshelve') message = self.get_patch_message(patch_path) if message is None: message = "No message saved with patch." self.log('Unshelving from %s/%s: "%s"\n' % \ (self.name, os.path.basename(patch_path), message)) try: self._run_patch(to_unshelve, dry_run=True) self._run_patch(to_unshelve) except PatchFailed: try: self._run_patch(to_unshelve, strip=1, dry_run=True) self._run_patch(to_unshelve, strip=1) except PatchFailed: if force: self.log('Warning: Unshelving failed, forcing as ' \ 'requested. Shelf will not be modified.\n') try: self._run_patch(to_unshelve) except PatchFailed: pass return raise CommandError("Your shelved patch no " \ "longer applies cleanly to the working tree!") # Backup the shelved patch rename(patch_path, '%s~' % patch_path) if len(to_remain) > 0: f = open(patch_path, 'w') for patch in to_remain: f.write(str(patch)) f.close() def shelve(self, patch_source, all=False, message=None, no_color=False): self._check_upgrade() if no_color is False: color = None else: color = False patches = patch_source.readpatches() if all: to_shelve = patches else: to_shelve = ShelveHunkSelector(patches, color).select()[0] if len(to_shelve) == 0: raise CommandError('Nothing to shelve') if message is None: timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') message = "Changes shelved on %s" % timestamp patch_path = self.next_patch() self.log('Shelving to %s/%s: "%s"\n' % \ (self.name, os.path.basename(patch_path), message)) f = open(patch_path, 'a') assert '\n' not in message f.write("%s%s\n" % (self.MESSAGE_PREFIX, message)) for patch in to_shelve: f.write(str(patch)) f.flush() os.fsync(f.fileno()) f.close() try: self._run_patch(to_shelve, reverse=True, dry_run=True) self._run_patch(to_shelve, reverse=True) except PatchFailed: try: self._run_patch(to_shelve, reverse=True, strip=1, dry_run=True) self._run_patch(to_shelve, reverse=True, strip=1) except PatchFailed: raise CommandError("Failed removing shelved changes from the" "working tree!") def _run_patch(self, patches, strip=0, reverse=False, dry_run=False): run_patch(self.base, patches, strip, reverse, dry_run) def _check_upgrade(self): if len(self._list_old_shelves()) > 0: raise CommandError("Old format shelves found, either upgrade " \ "or remove them!") def _list_old_shelves(self): import glob stem = os.path.join(self.base, '.bzr-shelf') patches = glob.glob(stem) patches.extend(glob.glob(stem + '-*[!~]')) if len(patches) == 0: return [] def patch_index(name): if name == stem: return 0 return int(name[len(stem) + 1:]) # patches might not be sorted in the right order patch_ids = [] for patch in patches: if patch == stem: patch_ids.append(0) else: patch_ids.append(int(patch[len(stem) + 1:])) patch_ids.sort() patches = [] for id in patch_ids: if id == 0: patches.append(stem) else: patches.append('%s-%s' % (stem, id)) return patches def upgrade(self): patches = self._list_old_shelves() if len(patches) == 0: self.log('No old-style shelves found to upgrade.\n') return for patch in patches: old_file = open(patch, 'r') new_path = self.next_patch() new_file = open(new_path, 'w') new_file.write(old_file.read()) old_file.close() new_file.close() self.log('Copied %s to %s/%s\n' % (os.path.basename(patch), self.name, os.path.basename(new_path))) rename(patch, patch + '~') bzrtools/shell.py0000644000000000000000000002440512264646316012562 0ustar 00000000000000# Copyright (C) 2004, 2005 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import cmd from itertools import chain import os try: import readline except ImportError: _has_readline = False else: _has_readline = True import shlex import stat import string import sys from bzrlib import osutils, trace from bzrlib.branch import Branch from bzrlib.config import config_dir, ensure_config_dir_exists from bzrlib.commands import get_cmd_object, all_command_names, get_alias from bzrlib.errors import BzrError from bzrlib.workingtree import WorkingTree import terminal SHELL_BLACKLIST = set(['rm', 'ls']) COMPLETION_BLACKLIST = set(['shell']) class BlackListedCommand(BzrError): def __init__(self, command): BzrError.__init__(self, "The command %s is blacklisted for shell use" % command) class CompletionContext(object): def __init__(self, text, command=None, prev_opt=None, arg_pos=None): self.text = text self.command = command self.prev_opt = prev_opt self.arg_pos = None def get_completions(self): try: return self.get_completions_or_raise() except Exception, e: print e, type(e) return [] def get_option_completions(self): try: command_obj = get_cmd_object(self.command) except BzrError: return [] opts = [o+" " for o in iter_opt_completions(command_obj)] return list(filter_completions(opts, self.text)) def get_completions_or_raise(self): if self.command is None: if '/' in self.text: iter = iter_executables(self.text) else: iter = (c+" " for c in iter_command_names() if c not in COMPLETION_BLACKLIST) return list(filter_completions(iter, self.text)) if self.prev_opt is None: completions = self.get_option_completions() if self.command == "cd": iter = iter_dir_completions(self.text) completions.extend(list(filter_completions(iter, self.text))) else: iter = iter_file_completions(self.text) completions.extend(filter_completions(iter, self.text)) return completions class PromptCmd(cmd.Cmd): def __init__(self): cmd.Cmd.__init__(self) self.prompt = "bzr> " try: self.tree = WorkingTree.open_containing('.')[0] except: self.tree = None self.set_title() self.set_prompt() self.identchars += '-' ensure_config_dir_exists() self.history_file = osutils.pathjoin(config_dir(), 'shell-history') whitespace = ''.join(c for c in string.whitespace if c < chr(127)) if _has_readline: readline.set_completer_delims(whitespace) if os.access(self.history_file, os.R_OK) and \ os.path.isfile(self.history_file): readline.read_history_file(self.history_file) self.cwd = os.getcwd() def write_history(self): if _has_readline: readline.write_history_file(self.history_file) def do_quit(self, args): self.write_history() raise StopIteration def do_exit(self, args): self.do_quit(args) def do_EOF(self, args): print self.do_quit(args) def postcmd(self, line, bar): self.set_title() self.set_prompt() def set_prompt(self): if self.tree is not None: try: prompt_data = (self.tree.branch.nick, self.tree.branch.revno(), self.tree.relpath('.')) prompt = " %s:%d/%s" % prompt_data except: prompt = "" else: prompt = "" self.prompt = "bzr%s> " % prompt def set_title(self, command=None): try: b = Branch.open_containing('.')[0] version = "%s:%d" % (b.nick, b.revno()) except: version = "[no version]" if command is None: command = "" sys.stdout.write(terminal.term_title("bzr %s %s" % (command, version))) def do_cd(self, line): if line == "": line = "~" line = os.path.expanduser(line) if os.path.isabs(line): newcwd = line else: newcwd = self.cwd+'/'+line newcwd = os.path.normpath(newcwd) try: os.chdir(newcwd) self.cwd = newcwd except Exception, e: print e try: self.tree = WorkingTree.open_containing(".")[0] except: self.tree = None def do_help(self, line): self.default("help "+line) def default(self, line): try: args = shlex.split(line) except ValueError, e: print 'Parse error:', e return alias_args = get_alias(args[0]) if alias_args is not None: args[0] = alias_args.pop(0) commandname = args.pop(0) for char in ('|', '<', '>'): commandname = commandname.split(char)[0] if commandname[-1] in ('|', '<', '>'): commandname = commandname[:-1] try: if commandname in SHELL_BLACKLIST: raise BlackListedCommand(commandname) cmd_obj = get_cmd_object(commandname) except (BlackListedCommand, BzrError): return os.system(line) try: is_qbzr = cmd_obj.__module__.startswith('bzrlib.plugins.qbzr.') if too_complicated(line) or is_qbzr: return os.system("bzr "+line) else: return (cmd_obj.run_argv_aliases(args, alias_args) or 0) except BzrError, e: trace.log_exception_quietly() print e except KeyboardInterrupt, e: print "Interrupted" except Exception, e: trace.log_exception_quietly() print "Unhandled error:\n%s" % (e) def completenames(self, text, line, begidx, endidx): return CompletionContext(text).get_completions() def completedefault(self, text, line, begidx, endidx): """Perform completion for native commands. :param text: The text to complete :type text: str :param line: The entire line to complete :type line: str :param begidx: The start of the text in the line :type begidx: int :param endidx: The end of the text in the line :type endidx: int """ (cmd, args, foo) = self.parseline(line) if cmd == "bzr": cmd = None return CompletionContext(text, command=cmd).get_completions() def run_shell(directory=None): try: if not directory is None: os.chdir(directory) prompt = PromptCmd() while True: try: try: prompt.cmdloop() except KeyboardInterrupt: print finally: prompt.write_history() except StopIteration: pass def iter_opt_completions(command_obj): for option_name, option in command_obj.options().items(): yield "--" + option_name short_name = option.short_name() if short_name: yield "-" + short_name def iter_file_completions(arg, only_dirs = False): """Generate an iterator that iterates through filename completions. :param arg: The filename fragment to match :type arg: str :param only_dirs: If true, match only directories :type only_dirs: bool """ cwd = os.getcwd() if cwd != "/": extras = [".", ".."] else: extras = [] (dir, file) = os.path.split(arg) if dir != "": listingdir = os.path.expanduser(dir) else: listingdir = cwd for file in chain(os.listdir(listingdir), extras): if dir != "": userfile = dir+'/'+file else: userfile = file if userfile.startswith(arg): if os.path.isdir(listingdir+'/'+file): userfile+='/' yield userfile elif not only_dirs: yield userfile + ' ' def iter_dir_completions(arg): """Generate an iterator that iterates through directory name completions. :param arg: The directory name fragment to match :type arg: str """ return iter_file_completions(arg, True) def iter_command_names(hidden=False): for real_cmd_name in all_command_names(): cmd_obj = get_cmd_object(real_cmd_name) if not hidden and cmd_obj.hidden: continue for name in [real_cmd_name] + cmd_obj.aliases: # Don't complete on aliases that are prefixes of the canonical name if name == real_cmd_name or not real_cmd_name.startswith(name): yield name def iter_executables(path): dirname, partial = os.path.split(path) for filename in os.listdir(dirname): if not filename.startswith(partial): continue fullpath = os.path.join(dirname, filename) mode=os.lstat(fullpath)[stat.ST_MODE] if stat.S_ISREG(mode) and 0111 & mode: yield fullpath + ' ' def filter_completions(iter, arg): return (c for c in iter if c.startswith(arg)) def iter_munged_completions(iter, arg, text): for completion in iter: completion = str(completion) if completion.startswith(arg): yield completion[len(arg)-len(text):]+" " def too_complicated(line): for char in '|<>*?': if char in line: return True return False bzrtools/terminal.py0000644000000000000000000000456212264646316013270 0ustar 00000000000000# Copyright (C) 2004 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import sys __docformat__ = "restructuredtext" __doc__ = "Terminal control functionality" def has_ansi_colors(): # XXX The whole color handling should be rewritten to use terminfo # XXX before we get there, checking for setaf capability should do. # XXX See terminfo(5) for all the gory details. if sys.platform == "win32": return False if not sys.stdout.isatty(): return False import curses try: curses.setupterm() except curses.error: return False return bool(curses.tigetstr('setaf')) colors = { "black": "0", "red": "1", "green": "2", "yellow": "3", "blue": "4", "magenta": "5", "cyan": "6", "white": "7" } def colorstring(text, fgcolor=None, bgcolor=None): """ Returns a string using ANSI control codes to set the text color. :param text: The text to set the color for. :type text: string :param fgcolor: The foreground color to use :type fgcolor: string :param bgcolor: The background color to use :type bgcolor: string """ code = [] if fgcolor: if fgcolor.startswith('dark'): code.append('0') fgcolor = fgcolor[4:] else: code.append('1') code.append('3' + colors[fgcolor]) if bgcolor: code.append('4' + colors[bgcolor]) return "".join(("\033[", ';'.join(code), "m", text, "\033[0m")) def term_title(title): term = os.environ.get('TERM', '') if term.startswith('xterm') or term == 'dtterm': return "\033]0;%s\007" % title return str() # arch-tag: a79b9993-146e-4a51-8bae-a13791703ddd bzrtools/test.py0000755000000000000000000000163712264646316012437 0ustar 00000000000000#!/usr/bin/env python USAGE = """Just run test.py. Any supplied arguments are treated as PYTHONPATH prefixes.""" import sys import os.path import unittest import tempfile import shutil path_prefix = [] if len(sys.argv) > 1: if sys.argv[1] in ("-h", "--help", ""): print USAGE sys.exit(0) path_prefix = sys.argv[1:] path_prefix.append(os.path.join(os.path.dirname(__file__), "..")) sys.path = [os.path.realpath(p) for p in path_prefix] + sys.path try: from bzrlib.plugins import bzrtools except ImportError, e: if len(sys.argv) == 1 and "bzrlib" in str(e): print "You can specify the path to bzrlib as the first argument" raise suite = bzrtools.test_suite() runner = unittest.TextTestRunner(verbosity=0) tempdir = tempfile.mkdtemp() try: os.chdir(tempdir) result = runner.run(suite) finally: shutil.rmtree(tempdir) sys.exit({True: 0, False: 3}[result.wasSuccessful()]) bzrtools/tests/0000755000000000000000000000000012264646316012236 5ustar 00000000000000bzrtools/upstream_import.py0000644000000000000000000002447112264646316014710 0ustar 00000000000000"""Import upstream source into a branch""" import errno import os import re from StringIO import StringIO import stat import tarfile import zipfile from bzrlib import generate_ids from bzrlib.bzrdir import BzrDir from bzrlib.errors import NoSuchFile, BzrCommandError, NotBranchError from bzrlib.osutils import (pathjoin, isdir, file_iterator, basename, file_kind, splitpath) from bzrlib.trace import warning from bzrlib.transform import TreeTransform, resolve_conflicts, cook_conflicts from bzrlib.workingtree import WorkingTree from bzrlib.plugins.bzrtools.bzrtools import open_from_url from bzrlib.plugins.bzrtools import errors class ZipFileWrapper(object): def __init__(self, fileobj, mode): self.zipfile = zipfile.ZipFile(fileobj, mode) def getmembers(self): for info in self.zipfile.infolist(): yield ZipInfoWrapper(self.zipfile, info) def extractfile(self, infowrapper): return StringIO(self.zipfile.read(infowrapper.name)) def add(self, filename): if isdir(filename): self.zipfile.writestr(filename+'/', '') else: self.zipfile.write(filename) def close(self): self.zipfile.close() class ZipInfoWrapper(object): def __init__(self, zipfile, info): self.info = info self.type = None self.name = info.filename self.zipfile = zipfile self.mode = 0666 def isdir(self): # Really? Eeeew! return bool(self.name.endswith('/')) def isreg(self): # Really? Eeeew! return not self.isdir() class DirWrapper(object): def __init__(self, fileobj, mode='r'): assert mode == 'r', mode self.root = os.path.realpath(fileobj.read()) def __repr__(self): return 'DirWrapper(%r)' % self.root def getmembers(self, subdir=None): if subdir is not None: mydir = pathjoin(self.root, subdir) else: mydir = self.root for child in os.listdir(mydir): if subdir is not None: child = pathjoin(subdir, child) fi = FileInfo(self.root, child) yield fi if fi.isdir(): for v in self.getmembers(child): yield v def extractfile(self, member): return open(member.fullpath) class FileInfo(object): def __init__(self, root, filepath): self.fullpath = pathjoin(root, filepath) self.root = root if filepath != '': self.name = pathjoin(basename(root), filepath) else: print 'root %r' % root self.name = basename(root) self.type = None stat = os.lstat(self.fullpath) self.mode = stat.st_mode if self.isdir(): self.name += '/' def __repr__(self): return 'FileInfo(%r)' % self.name def isreg(self): return stat.S_ISREG(self.mode) def isdir(self): return stat.S_ISDIR(self.mode) def issym(self): if stat.S_ISLNK(self.mode): self.linkname = os.readlink(self.fullpath) return True else: return False def top_path(path): """Return the top directory given in a path.""" components = splitpath(path) if len(components) > 0: return components[0] else: return '' def common_directory(names): """Determine a single directory prefix from a list of names""" possible_prefix = None for name in names: name_top = top_path(name) if name_top == '': return None if possible_prefix is None: possible_prefix = name_top else: if name_top != possible_prefix: return None return possible_prefix def do_directory(tt, trans_id, tree, relative_path, path): if isdir(path) and tree.path2id(relative_path) is not None: tt.cancel_deletion(trans_id) else: tt.create_directory(trans_id) def add_implied_parents(implied_parents, path): """Update the set of implied parents from a path""" parent = os.path.dirname(path) if parent in implied_parents: return implied_parents.add(parent) add_implied_parents(implied_parents, parent) def names_of_files(tar_file): for member in tar_file.getmembers(): if member.type != "g": yield member.name def should_ignore(relative_path): return top_path(relative_path) == '.bzr' def import_tar(tree, tar_input): """Replace the contents of a working directory with tarfile contents. The tarfile may be a gzipped stream. File ids will be updated. """ tar_file = tarfile.open('lala', 'r', tar_input) import_archive(tree, tar_file) def import_zip(tree, zip_input): zip_file = ZipFileWrapper(zip_input, 'r') import_archive(tree, zip_file) def import_dir(tree, dir_input): dir_file = DirWrapper(dir_input) import_archive(tree, dir_file) def import_archive(tree, archive_file): tt = TreeTransform(tree) try: import_archive_to_transform(tree, archive_file, tt) tt.apply() finally: tt.finalize() def import_archive_to_transform(tree, archive_file, tt): prefix = common_directory(names_of_files(archive_file)) removed = set() for path, entry in tree.iter_entries_by_dir(): if entry.parent_id is None: continue trans_id = tt.trans_id_tree_path(path) tt.delete_contents(trans_id) removed.add(path) added = set() implied_parents = set() seen = set() for member in archive_file.getmembers(): if member.type == 'g': # type 'g' is a header continue # Inverse functionality in bzr uses utf-8. We could also # interpret relative to fs encoding, which would match native # behaviour better. relative_path = member.name.decode('utf-8') if prefix is not None: relative_path = relative_path[len(prefix)+1:] relative_path = relative_path.rstrip('/') if relative_path == '': continue if should_ignore(relative_path): continue add_implied_parents(implied_parents, relative_path) trans_id = tt.trans_id_tree_path(relative_path) added.add(relative_path.rstrip('/')) path = tree.abspath(relative_path) if member.name in seen: if tt.final_kind(trans_id) == 'file': tt.set_executability(None, trans_id) tt.cancel_creation(trans_id) seen.add(member.name) if member.isreg(): tt.create_file(file_iterator(archive_file.extractfile(member)), trans_id) executable = (member.mode & 0111) != 0 tt.set_executability(executable, trans_id) elif member.isdir(): do_directory(tt, trans_id, tree, relative_path, path) elif member.issym(): tt.create_symlink(member.linkname, trans_id) else: continue if tt.tree_file_id(trans_id) is None: name = basename(member.name.rstrip('/')) file_id = generate_ids.gen_file_id(name) tt.version_file(file_id, trans_id) for relative_path in implied_parents.difference(added): if relative_path == "": continue trans_id = tt.trans_id_tree_path(relative_path) path = tree.abspath(relative_path) do_directory(tt, trans_id, tree, relative_path, path) if tt.tree_file_id(trans_id) is None: tt.version_file(trans_id, trans_id) added.add(relative_path) for path in removed.difference(added): tt.unversion_file(tt.trans_id_tree_path(path)) for conflict in cook_conflicts(resolve_conflicts(tt), tt): warning(conflict) def do_import(source, tree_directory=None): """Implementation of import command. Intended for UI only""" if tree_directory is not None: try: tree = WorkingTree.open(tree_directory) except NotBranchError: if not os.path.exists(tree_directory): os.mkdir(tree_directory) branch = BzrDir.create_branch_convenience(tree_directory) tree = branch.bzrdir.open_workingtree() else: tree = WorkingTree.open_containing('.')[0] tree.lock_write() try: if tree.changes_from(tree.basis_tree()).has_changed(): raise BzrCommandError("Working tree has uncommitted changes.") try: archive, external_compressor = get_archive_type(source) except errors.NotArchiveType: if file_kind(source) == 'directory': s = StringIO(source) s.seek(0) import_dir(tree, s) else: raise BzrCommandError('Unhandled import source') else: if archive == 'zip': import_zip(tree, open_from_url(source)) elif archive == 'tar': try: tar_input = open_from_url(source) if external_compressor == 'bz2': import bz2 tar_input = StringIO(bz2.decompress(tar_input.read())) elif external_compressor == 'lzma': import lzma tar_input = StringIO(lzma.decompress(tar_input.read())) except IOError, e: if e.errno == errno.ENOENT: raise NoSuchFile(source) try: import_tar(tree, tar_input) finally: tar_input.close() finally: tree.unlock() def get_archive_type(path): """Return the type of archive and compressor indicated by path name. Only external compressors are returned, so zip files are only ('zip', None). .tgz is treated as ('tar', 'gz') and '.tar.xz' is treated as ('tar', 'lzma'). """ matches = re.match(r'.*\.(zip|tgz|tar(.(gz|bz2|lzma|xz))?)$', path) if not matches: raise errors.NotArchiveType(path) external_compressor = None if matches.group(3) is not None: archive = 'tar' external_compressor = matches.group(3) if external_compressor == 'xz': external_compressor = 'lzma' elif matches.group(1) == 'tgz': return 'tar', 'gz' else: archive = matches.group(1) return archive, external_compressor bzrtools/userinteractor.py0000644000000000000000000000674312264646316014531 0ustar 00000000000000import sys from bzrlib.osutils import getchar class UserOption: def __init__(self, char, action, help, default=False): self.char = char self.action = action self.default = default self.help = help class UserInteractor(object): # Special actions RESTART = 0 QUIT = 1 FINISH = 2 def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) self.__total_items = len(self.items) def set_items(self, item_list): self.items = item_list self.__total_items = len(self.items) def set_item_callback(self, cb): self.item_callback = cb def set_start_callback(self, cb): self.start_callback = cb def set_end_callback(self, cb): self.end_callback = cb def set_prompt(self, prompt): self.prompt = prompt def set_options(self, opt_list): self._options = [] self._option_dict = {} for option in opt_list: self.add_option(option) def add_option(self, option): self._options.append(option) self._option_dict[option.char] = option def get_option(self, char): return self._option_dict[char] def __do_action(self, action, item): if type(action) is int: if action == self.QUIT: self.__quit = True self.__finished = True elif action == self.RESTART: self.__restart = True self.__finished = False elif action == self.FINISH: self.__finished = True return True else: return action(item) def __select_loop(self): i = 0 self.start_callback() while i < len(self.items): item = self.items[i] self.item_callback(item, i + 1) if self.__ask_once(item, i + 1): i += 1 if self.__quit or self.__finished: break def interact(self): self.__quit = False self.__finished = False while not self.__quit and not self.__finished: self.__restart = False self.__select_loop() if self.__quit: break if self.end_callback(): break self.__finished = False self.__ask_once(None, self.__total_items) while not self.__finished and not self.__restart: self.__ask_once(None, -1) return not self.__quit def __ask_once(self, item, count): args = {'count': count, 'total' : self.__total_items} while True: sys.stdout.write(self.prompt % args) sys.stdout.write(' [') for opt in self._options: if opt.default: default = opt sys.stdout.write(opt.char) sys.stdout.write('?] (%s): ' % default.char) response = getchar() # default, which we see as newline, is 'n' if response in ['\n', '\r', '\r\n']: response = default.char print response # because echo is off for opt in self._options: if opt.char == response: return self.__do_action(opt.action, item) self.__show_help() return False # keep pychecker happy def __show_help(self): for opt in self._options: print ' %s - %s' % (opt.char, opt.help) bzrtools/version.py0000644000000000000000000000546412264646316013144 0ustar 00000000000000# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd # Copyright (C) 2005, 2006, 2007, 2008, 2009 Aaron Bentley # # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA def _format_version_tuple(version_info): """Turn a version number 2, 3 or 5-tuple into a short string. This format matches and the typical presentation used in Python output. This also checks that the version is reasonable: the sub-release must be zero for final releases. >>> print _format_version_tuple((1, 0, 0, 'final', 0)) 1.0.0 >>> print _format_version_tuple((1, 2, 0, 'dev', 0)) 1.2.0dev >>> print bzrlib._format_version_tuple((1, 2, 0, 'dev', 1)) 1.2.0dev1 >>> print _format_version_tuple((1, 1, 1, 'candidate', 2)) 1.1.1rc2 >>> print bzrlib._format_version_tuple((2, 1, 0, 'beta', 1)) 2.1.0b1 >>> print _format_version_tuple((1, 4, 0)) 1.4.0 >>> print _format_version_tuple((1, 4)) 1.4 >>> print bzrlib._format_version_tuple((2, 1, 0, 'final', 1)) Traceback (most recent call last): ... ValueError: version_info (2, 1, 0, 'final', 1) not valid >>> print _format_version_tuple((1, 4, 0, 'wibble', 0)) Traceback (most recent call last): ... ValueError: version_info (1, 4, 0, 'wibble', 0) not valid """ if len(version_info) == 2: main_version = '%d.%d' % version_info[:2] else: main_version = '%d.%d.%d' % version_info[:3] if len(version_info) <= 3: return main_version release_type = version_info[3] sub = version_info[4] # check they're consistent if release_type == 'final' and sub == 0: sub_string = '' elif release_type == 'dev' and sub == 0: sub_string = 'dev' elif release_type == 'dev': sub_string = 'dev' + str(sub) elif release_type in ('alpha', 'beta'): sub_string = release_type[0] + str(sub) elif release_type == 'candidate': sub_string = 'rc' + str(sub) else: raise ValueError("version_info %r not valid" % (version_info,)) return main_version + sub_string version_info = (2, 6, 0) __version__ = _format_version_tuple(version_info) bzrtools/zap.py0000644000000000000000000002003112264646316012234 0ustar 00000000000000# Copyright (C) 2006-2007, 2010-2011 Aaron Bentley # Copyright (C) 2013 Aaron Bentley # Copyright (C) 2007 Charlie Shepherd # Copyright (C) 2011 Canonical Ltd. # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from shutil import rmtree from bzrlib import ( bzrdir, revision as _mod_revision, ) from bzrlib.branch import Branch from bzrlib.errors import BzrCommandError, NoWorkingTree, NotBranchError from bzrlib import registry from bzrlib.workingtree import WorkingTree from errors import (NotCheckout, UncommittedCheckout, ParentMissingRevisions, NoParent) from bzrlib.plugins.bzrtools.bzrtools import read_locked class AllowChanged(object): @classmethod def check_changed(klass, wt, remove_branch): pass class CheckChanged(object): @classmethod def check_changed(klass, wt, remove_branch): delta = wt.changes_from(wt.basis_tree(), want_unchanged=False) if delta.has_changed(): klass.handle_changed(wt, remove_branch) class HaltOnChange(CheckChanged): @staticmethod def handle_changed(wt, remove_branch): raise UncommittedCheckout() class StoreChanges(CheckChanged): @staticmethod def handle_changed(wt, remove_branch): from bzrlib.plugins.pipeline.pipeline import PipeManager if remove_branch: raise BzrCommandError('Cannot store changes in deleted branch.') PipeManager.from_checkout(wt).store_uncommitted() change_policy_registry = registry.Registry() change_policy_registry.register('force', AllowChanged, 'Delete tree even if contents are modified.') change_policy_registry.register('store', StoreChanges, 'Store changes in branch. (Requires' ' bzr-pipeline.)') change_policy_registry.register('check', HaltOnChange, 'Stop if tree contents are modified.') change_policy_registry.default_key = 'check' def zap(path, remove_branch=False, policy=HaltOnChange): try: wt = bzrdir.BzrDir.open(path).open_workingtree(path, recommend_upgrade=False) except (NoWorkingTree, NotBranchError): raise NotCheckout(path) tree_base = wt.bzrdir.transport.base branch = wt.branch branch_base = branch.bzrdir.transport.base if tree_base == branch_base: raise NotCheckout(path) policy.check_changed(wt, remove_branch) if remove_branch: parent_loc = branch.get_parent() if parent_loc is None: raise NoParent() with read_locked(Branch.open(parent_loc)) as parent: last_revision = _mod_revision.ensure_null(parent.last_revision()) if last_revision != _mod_revision.NULL_REVISION: graph = parent.repository.get_graph() heads = graph.heads([last_revision, branch.last_revision()]) if branch.last_revision() in heads: raise ParentMissingRevisions(branch.get_parent()) rmtree(path) if remove_branch: t = branch.bzrdir.transport while t.base != branch_base: t = t.clone('..') t = t.clone('..') t.delete_tree('') def test_suite(): import os from unittest import makeSuite from bzrlib.tests import TestCaseWithTransport class PipelinePluginFeature: @staticmethod def available(): try: import bzrlib.plugins.pipeline except ImportError: return False else: return True class TestZap(TestCaseWithTransport): def make_checkout(self): wt = bzrdir.BzrDir.create_standalone_workingtree('source') return wt.branch.create_checkout('checkout', lightweight=True) def make_checkout2(self): wt = self.make_checkout() wt2 = wt.branch.bzrdir.sprout('source2').open_workingtree() return wt2.branch.create_checkout('checkout2', lightweight=True) def test_is_checkout(self): self.assertRaises(NotCheckout, zap, '.') wt = bzrdir.BzrDir.create_standalone_workingtree('.') self.assertRaises(NotCheckout, zap, '.') def test_zap_works(self): self.make_checkout() self.assertIs(True, os.path.exists('checkout')) zap('checkout') self.assertIs(False, os.path.exists('checkout')) self.assertIs(True, os.path.exists('source')) def test_zap_branch(self): self.make_checkout2() base = WorkingTree.open('checkout').branch.base self.assertIs(True, os.path.exists('checkout')) self.assertRaises(NoParent, zap, 'checkout', remove_branch=True) self.assertIs(True, os.path.exists('checkout')) self.assertIs(True, os.path.exists('source')) zap('checkout2', remove_branch=True) self.assertIs(False, os.path.exists('checkout2')) self.assertIs(False, os.path.exists('source2')) def test_checks_modified(self): checkout = self.make_checkout() os.mkdir('checkout/foo') checkout.add('foo') self.assertRaises(UncommittedCheckout, zap, 'checkout') checkout.commit('commit changes to branch') zap('checkout') def make_modified_checkout(self): checkout = self.make_checkout() os.mkdir('checkout/foo') checkout.add('foo') return checkout def test_allow_modified(self): self.make_modified_checkout() self.assertRaises(UncommittedCheckout, zap, 'checkout') zap('checkout', policy=AllowChanged) def test_store(self): self.requireFeature(PipelinePluginFeature) checkout = self.make_modified_checkout() zap('checkout', policy=StoreChanges) self.assertIn('stored-transform', checkout.branch.bzrdir.transport.list_dir('branch')) def test_store_remove_branch(self): self.requireFeature(PipelinePluginFeature) checkout = self.make_modified_checkout() branch = self.make_branch('branch') checkout.branch.set_parent(branch.base) e = self.assertRaises(BzrCommandError, zap, 'checkout', policy=StoreChanges, remove_branch=True) self.assertEqual('Cannot store changes in deleted branch.', str(e)) def test_store_remove_branch_unmodified(self): self.requireFeature(PipelinePluginFeature) checkout = self.make_checkout() branch = self.make_branch('branch') checkout.branch.set_parent(branch.base) zap('checkout', policy=StoreChanges, remove_branch=True) def test_zap_branch_with_unique_revision(self): parent = self.make_branch_and_tree('parent') parent.commit('foo') new_branch = self.make_branch('new') new_branch.set_parent(parent.branch.base) checkout = new_branch.create_checkout('checkout', lightweight=True) checkout.commit('unique') self.assertRaises(ParentMissingRevisions, zap, 'checkout', remove_branch=True) return makeSuite(TestZap) bzrtools/.be/bugs/0000755000000000000000000000000012264646316012500 5ustar 00000000000000bzrtools/.be/settings0000644000000000000000000000002312264646316013316 0ustar 00000000000000 rcs_name=bzr bzrtools/.be/version0000644000000000000000000000003112264646316013142 0ustar 00000000000000Bugs Everywhere Tree 1 0 bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/0000755000000000000000000000000012264646316016642 5ustar 00000000000000bzrtools/.be/bugs/0fc1fb5e-c545-453c-999d-5fc5ef852ca5/0000755000000000000000000000000012264646316017524 5ustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/0000755000000000000000000000000012264646316017220 5ustar 00000000000000bzrtools/.be/bugs/1e6d3356-5668-47f3-b451-5285bcc0ec3b/0000755000000000000000000000000012264646316017206 5ustar 00000000000000bzrtools/.be/bugs/4ddc9130-fe72-4d0c-9848-157ad6f2c14d/0000755000000000000000000000000012264646316017347 5ustar 00000000000000bzrtools/.be/bugs/55bd43ce-953b-4233-83d2-2034ea63a805/0000755000000000000000000000000012264646316017110 5ustar 00000000000000bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/0000755000000000000000000000000012264646316017300 5ustar 00000000000000bzrtools/.be/bugs/7c3b4f3e-33c5-4112-b8df-c209ec6cc8b6/0000755000000000000000000000000012264646316017500 5ustar 00000000000000bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/0000755000000000000000000000000012264646316017047 5ustar 00000000000000bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/0000755000000000000000000000000012264646316017434 5ustar 00000000000000bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/0000755000000000000000000000000012264646316017125 5ustar 00000000000000bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/0000755000000000000000000000000012264646316017365 5ustar 00000000000000bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/0000755000000000000000000000000012264646316017533 5ustar 00000000000000bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/0000755000000000000000000000000012264646316017400 5ustar 00000000000000bzrtools/.be/bugs/bad9a00f-50f5-4782-9da6-722fb0a58c93/0000755000000000000000000000000012264646316017343 5ustar 00000000000000bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/0000755000000000000000000000000012264646316017662 5ustar 00000000000000bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/0000755000000000000000000000000012264646316017355 5ustar 00000000000000bzrtools/.be/bugs/c59acbbf-25f4-450a-9a8f-7a6a45b3985e/0000755000000000000000000000000012264646316017515 5ustar 00000000000000bzrtools/.be/bugs/da0f16ce-1ee0-4f9e-bf1a-9a78c4a7e765/0000755000000000000000000000000012264646316017651 5ustar 00000000000000bzrtools/.be/bugs/dbd10145-51f8-4bda-96d9-06b5d3ef80b8/0000755000000000000000000000000012264646316017424 5ustar 00000000000000bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/0000755000000000000000000000000012264646316017467 5ustar 00000000000000bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/0000755000000000000000000000000012264646316020467 5ustar 00000000000000bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/values0000644000000000000000000000023712264646316020066 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=baz2bzr should use correct filesystem time=Thu, 26 May 2005 13:13:06 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c52/bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c520000755000000000000000000000000012264646316025115 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c52/bodybzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c520000644000000000000000000000025112264646316025115 0ustar 00000000000000If tmp is on a separate filesystem, baz2bzr won't be able to rename files. Option 1 (preferred): use the same filesystem Option 2: use shutils.move instead of os.rename ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c52/valuesbzrtools/.be/bugs/07f6e226-092a-4e69-8891-307903604c93/comments/a78766ca-05bb-4875-b716-7f22ec430c520000644000000000000000000000007712264646316025123 0ustar 00000000000000 Date=Thu, 26 May 2005 13:14:35 +0000 From=abentley bzrtools/.be/bugs/0fc1fb5e-c545-453c-999d-5fc5ef852ca5/values0000644000000000000000000000023112264646316020742 0ustar 00000000000000 creator=abentley severity=minor status=open summary=Shelf interacts poorly with Emacs time=Fri, 18 May 2007 09:18:34 +0000 bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/0000755000000000000000000000000012264646316021045 5ustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/values0000644000000000000000000000023212264646316020437 0ustar 00000000000000 creator=abentley severity=minor status=closed summary="import" fails on simple archive time=Fri, 02 Feb 2007 05:22:57 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c86/bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c860000755000000000000000000000000012264646316025726 5ustar 00000000000000././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce/bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce0000755000000000000000000000000012264646316026022 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c86/bodybzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c860000644000000000000000000000700512264646316025732 0ustar 00000000000000$mkdir upstream-1.0.1 $touch upstream-1.0.1/README $tar czf upstream-1.0.1.tar.gz upstream-1.0.1 $mkdir my-branch $cd my-branch $bzr init $bzr import ../upstream-1.0.1.tar.gz bzr: ERROR: bzrlib.errors.PathNotChild: Path '/' is not a child of path u'/home/user/my-branch' Traceback (most recent call last): File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 650, in run_bzr_catch_errors return run_bzr(argv) File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 612, in run_bzr ret = run(*run_argv) File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 304, in run_argv_aliases return self.run(**all_cmd_args) File "/usr/local/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/__init__.py", line 545, in run do_import(source, tree) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/upstream_import.py", line 272, in do_import import_tar(tree, tar_input) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/upstream_import.py", line 165, in import_tar import_archive(tree, tar_file) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/upstream_import.py", line 201, in import_archive trans_id = tt.trans_id_tree_path(relative_path) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/transform.py", line 240, in trans_id_tree_path path = self.canonical_path(path) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/transform.py", line 234, in canonical_path relpath = self._tree.relpath(abs) File "/usr/local/lib/python2.5/site-packages/bzrlib/workingtree.py", line 449, in relpath return osutils.relpath(self.basedir, path) File "/usr/local/lib/python2.5/site-packages/bzrlib/osutils.py", line 822, in relpath raise errors.PathNotChild(rp, base) PathNotChild: Path '/' is not a child of path u'/home/user/my-branch' bzr 0.15.0dev0 on python 2.5.0.final.0 (linux2) arguments: ['/usr/local/bin/bzr', 'import', '../upstream-1.0.1.tar.gz'] ** please send this report to bazaar@lists.ubuntu.com /usr/local/lib/python2.5/site-packages/bzrlib/lockable_files.py:110: UserWarning: file group LockableFiles() was not explicitly unlocked warn("file group %r was not explicitly unlocked" % self) /usr/local/lib/python2.5/site-packages/bzrlib/lockable_files.py:110: UserWarning: file group LockableFiles() was not explicitly unlocked warn("file group %r was not explicitly unlocked" % self) /usr/local/lib/python2.5/site-packages/bzrlib/lockable_files.py:110: UserWarning: file group LockableFiles() was not explicitly unlocked warn("file group %r was not explicitly unlocked" % self) I think this is because TarFile.getmembers() contains "upstream-1.0.1//" (two slashes). A quick fix that works for me is: --- upstream_import.py 2007-01-08 17:08:42 +0000 +++ upstream_import.py 2007-01-29 12:03:54 +0000 @@ -193,7 +193,7 @@ continue relative_path = member.name if prefix is not None: - relative_path = relative_path[len(prefix)+1:] + relative_path = relative_path[len(prefix):].lstrip('/') if relative_path == '': continue add_implied_parents(implied_parents, relative_path) bye, matthias ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c86/valuesbzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/a68c4dc0-c796-4747-b7a1-60bee9fb2c860000644000000000000000000000013512264646316025727 0ustar 00000000000000 Content-type=text/plain Date=Fri, 02 Feb 2007 05:23:51 +0000 From=abentley ././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce/bodybzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce0000644000000000000000000000002112264646316026015 0ustar 00000000000000Can't reproduce. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce/valuesbzrtools/.be/bugs/1804f828-abfe-4433-b287-7560ff9ea354/comments/aa6140d3-b4a7-46c3-abae-4c7b8a3523ce0000644000000000000000000000022412264646316026022 0ustar 00000000000000 Content-type=text/plain Date=Fri, 02 Feb 2007 06:00:36 +0000 From=abentley In-reply-to=a68c4dc0-c796-4747-b7a1-60bee9fb2c86 bzrtools/.be/bugs/1e6d3356-5668-47f3-b451-5285bcc0ec3b/values0000644000000000000000000000026112264646316020427 0ustar 00000000000000 assigned=abentley creator=root severity=minor status=closed summary=shell produces deprecation warnings time=Mon, 13 Mar 2006 00:12:18 +0000 bzrtools/.be/bugs/4ddc9130-fe72-4d0c-9848-157ad6f2c14d/values0000644000000000000000000000026312264646316020572 0ustar 00000000000000 assigned=abentley creator=abentley severity=minor status=closed summary=push overwrites diverged branches time=Tue, 20 Sep 2005 17:08:01 +0000 bzrtools/.be/bugs/55bd43ce-953b-4233-83d2-2034ea63a805/values0000644000000000000000000000030112264646316020324 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=shelf tracebacks when "shelf show" invoked with no patches on the shelf time=Mon, 17 Dec 2007 13:11:13 +0000 bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/0000755000000000000000000000000012264646316021125 5ustar 00000000000000bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/values0000644000000000000000000000024112264646316020517 0ustar 00000000000000 creator=abentley severity=minor status=open summary=rspush throws on missing parent directory time=Fri, 02 Feb 2007 05:11:27 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b/bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b0000755000000000000000000000000012264646316026004 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b/bodybzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b0000644000000000000000000000335712264646316026016 0ustar 00000000000000Hi, I tried to upload a branch to my webserver, where home/user/dir/ does not exist. It's obvious why bzr failed, but I hereby send the report as requested. Perhaps it needs a better error message. greetings, matthias $ bzr rspush host:dir/subdir Pushing to host:dir/subdir/ building file list ... done rsync: mkdir "/home/user/dir/subdir" failed: No such file or directory (2) rsync error: error in file IO (code 11) at main.c(529) [receiver=2.6.9] rsync: connection unexpectedly closed (8 bytes received so far) [sender] rsync error: error in rsync protocol data stream (code 12) at io.c(454) [sender=2.6.9] bzr: ERROR: bzrlib.plugins.bzrtools.bzrtools.RsyncStreamIO: Error in rsync protocol data stream. Traceback (most recent call last): File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 650, in run_bzr_catch_errors return run_bzr(argv) File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 612, in run_bzr ret = run(*run_argv) File "/usr/local/lib/python2.5/site-packages/bzrlib/commands.py", line 304, in run_argv_aliases return self.run(**all_cmd_args) File "/usr/local/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/__init__.py", line 655, in run working_tree=not no_tree) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/bzrtools.py", line 292, in rspush excludes=final_exclusions) File "/usr/local/stow/bazaar-dev/lib/python2.5/site-packages/bzrlib/plugins/bzrtools/bzrtools.py", line 160, in rsync raise RsyncStreamIO() RsyncStreamIO: Error in rsync protocol data stream. bzr 0.15.0dev0 on python 2.5.0.final.0 (linux2) arguments: ['/usr/local/bin/bzr', 'rspush', 'host:dir/subdir'] ** please send this report to bazaar-ng@lists.ubuntu.com ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b/valuesbzrtools/.be/bugs/5be3aa64-4585-49e4-8479-172c67afcc1b/comments/298847ed-8d6a-416f-a3aa-4fd6d3f3417b0000644000000000000000000000013512264646316026005 0ustar 00000000000000 Content-type=text/plain Date=Fri, 02 Feb 2007 05:11:55 +0000 From=abentley bzrtools/.be/bugs/7c3b4f3e-33c5-4112-b8df-c209ec6cc8b6/values0000644000000000000000000000027612264646316020727 0ustar 00000000000000 assigned=abentley creator=abentley severity=minor status=closed summary=push overwrites existing non-bzr directories time=Tue, 20 Sep 2005 17:07:14 +0000 bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/0000755000000000000000000000000012264646316020674 5ustar 00000000000000bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/values0000644000000000000000000000023612264646316020272 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=cdiff erroneously reports long lines time=Mon, 12 Feb 2007 22:35:21 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c/bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c0000755000000000000000000000000012264646316025450 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c/bodybzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c0000644000000000000000000000037212264646316025454 0ustar 00000000000000The patch parser (which isn't quite the same as the one used to parse bundles) misinterprets filename lines as portions of a diff hunk. This means that if those filename lines exceed 79 characters, they are erroneously reported as long source lines ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c/valuesbzrtools/.be/bugs/83031098-bd41-4159-852d-eec95740ab19/comments/5aa94e9e-8a61-4bb1-aa74-b50428b8324c0000644000000000000000000000013512264646316025451 0ustar 00000000000000 Content-type=text/plain Date=Mon, 12 Feb 2007 22:38:28 +0000 From=abentley bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/0000755000000000000000000000000012264646316021261 5ustar 00000000000000bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/values0000644000000000000000000000023612264646316020657 0ustar 00000000000000 creator=abentley severity=minor status=open summary=fetch-ghosts should be more aggressive time=Fri, 31 Mar 2006 15:44:44 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b043/bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b0430000755000000000000000000000000012264646316025656 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b043/bodybzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b0430000644000000000000000000000024712264646316025663 0ustar 00000000000000Fetch-ghosts only works on ghosts in the ancestry of the branch's last-revision. It should pull any available ghosts, i.e. it should scan the repository for ghosts. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b043/valuesbzrtools/.be/bugs/9312b4cf-379e-490b-bfb5-dfb81357ee25/comments/906938f4-be68-4d65-9b75-def07743b0430000644000000000000000000000007712264646316025664 0ustar 00000000000000 Date=Fri, 31 Mar 2006 15:47:26 +0000 From=abentley bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/0000755000000000000000000000000012264646316020752 5ustar 00000000000000bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/values0000644000000000000000000000025212264646316020346 0ustar 00000000000000 creator=Jelmer Vernooij severity=minor status=open summary=graph-ancestry should escape commit message time=Fri, 16 Mar 2007 19:23:23 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab/bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab0000755000000000000000000000000012264646316025552 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab/bodybzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab0000644000000000000000000000070512264646316025556 0ustar 00000000000000graph-ancestry needs to escape backslashes in commit messages to prevent dot syntax errors: ganieda:~/bzr/bzr.dev% bzr graph-ancestry . foo.dot ganieda:~/bzr/bzr.dev% dot -Tpng foo.dot -o foo.png Error: foo.dot:5095: syntax error near line 5095 context: n2311[fillcolor="white" style="filled" label="62e3e\nAaron Bentley\nwin32fixes\n2006/03/27\nd2202" shape="box" tooltip="Used osutils getcwd instead of replacing \"\\" with >>> \ <<< "/\"" href="#"] ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab/valuesbzrtools/.be/bugs/945dc3f8-75e1-4312-b56d-9ecb02310783/comments/5c42de60-2f51-48a0-9756-dcef5f6955ab0000644000000000000000000000013512264646316025553 0ustar 00000000000000 Content-type=text/plain Date=Fri, 16 Mar 2007 19:27:36 +0000 From=abentley bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/0000755000000000000000000000000012264646316021212 5ustar 00000000000000bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/values0000644000000000000000000000021512264646316020605 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=baz-import fails because cat-log uses a Revision, not a string ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad891786713/bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad8917867130000755000000000000000000000000012264646316025574 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad891786713/bodybzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad8917867130000644000000000000000000000623712264646316025606 0ustar 00000000000000From Robey Pointer... This exception: [ 7114] Tue 22:37:07.305 ERROR: exceptions.TypeError: args must be a sequence of strings, but was ('cat-log', '--dir', '/home/robey/code/ testing/baz2bzr-618h1D/rd', bzrlib.plugins.bzrtools- baz2bzr.pybaz.Revision('robey@lag.net--2003-public/secsh--dev--1.0-- base-0')) at /home/robey/code/bzrtools/pybaz/backends/forkexec.py line 254 in __init__ Traceback (most recent call last): File "/home/robey/code/bzr.dev/bzrlib/commands.py", line 557, in run_bzr_catch_errors return run_bzr(argv) File "/home/robey/code/bzr.dev/bzrlib/commands.py", line 520, in run_bzr ret = cmd_obj.run_argv(argv) File "/home/robey/code/bzr.dev/bzrlib/commands.py", line 233, in run_argv return self.run(**all_cmd_args) File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/baz_import.py", line 713, in run reuse_history_from=reuse_history_list) File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/baz_import.py", line 398, in import_version max_count=max_count): File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/baz_import.py", line 545, in iter_import_version tree, baz_inv, log = get_revision(revdir, revision) File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/baz_import.py", line 643, in get_revision log = get_log(tree, revision) File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/baz_import.py", line 638, in get_log assert str(log.revision) == str(revision), (log.revision, revision) File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/external/pybaz/ _patchlog.py", line 171, in _get_revision assert self.__revision == self['Archive']+'/'+self['Revision'] File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/external/pybaz/ _patchlog.py", line 150, in __getitem__ return self._parse()[header] File "/home/robey/.bazaar/plugins/bzrtools-baz2bzr/external/pybaz/ _patchlog.py", line 133, in _parse s = _backend().text_cmd( File "/home/robey/code/bzrtools/pybaz/backends/commandline.py", line 141, in text_cmd status, text = self._get_spawner().status_text_cmd(args, chdir, (0,)) File "/home/robey/code/bzrtools/pybaz/backends/commandline.py", line 240, in status_text_cmd return self._guess().status_text_cmd(*args, **kwargs) File "/home/robey/code/bzrtools/pybaz/backends/forkexec.py", line 62, in status_text_cmd logger=self._logger) File "/home/robey/code/bzrtools/pybaz/backends/forkexec.py", line 107, in exec_safe_status_stdout proc = ChildProcess(program, args, expected, chdir, logger) File "/home/robey/code/bzrtools/pybaz/backends/forkexec.py", line 254, in __init__ raise TypeError( is fixed by this tiny patch to pybaz: --- orig/pybaz/_patchlog.py +++ mod/pybaz/_patchlog.py @@ -99,7 +99,7 @@ if not p.has_archive() or not p.has_patchlevel: raise errors.NamespaceError(revision, 'fully-qualified revision') - self.__revision = revision + self.__revision = str(revision) if tree is None: self.__tree = None else: I'm sure that's fixing the symptom and not the bug, but it was sufficient for me to migrate paramiko, so I thought I'd share it. robey ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad891786713/valuesbzrtools/.be/bugs/aa3f38e8-8544-498f-893e-dec21b1b8f13/comments/e53c2ce1-5b28-4e48-8453-bad8917867130000644000000000000000000000007712264646316025602 0ustar 00000000000000 Date=Sat, 03 Dec 2005 23:53:27 +0000 From=abentley bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0000755000000000000000000000000012264646316021360 5ustar 00000000000000bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/values0000644000000000000000000000024112264646316020752 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=bzr branches opens too many connections time=Mon, 30 Jul 2007 14:35:37 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf96499/bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf964990000755000000000000000000000000012264646316026050 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf96499/bodybzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf964990000644000000000000000000001244212264646316026055 0ustar 00000000000000Hello Aaron. Here's yet another bug report we received at Debian's BTS. :) Thanks. ----- Forwarded message from Loïc Minier ----- From: Loïc Minier To: Debian Bug Tracking System Date: Mon, 30 Jul 2007 12:27:11 +0200 Subject: Bug#435240: multi-pull is slow because it doesn't reuse connections Package: bzrtools Version: 0.18.0-1 Severity: wishlist Tags: patch Hi, multi-pull is relatively slow because it opens a connection for each branch (especially ssh connections are slow). I'm attaching a patch to reuse the connection for all branches with the same URL base. I hope it's not so ugly that you poke yourself an eye out. The old behavior isn't available anymore with the patch, but perhaps this is a problem for some transports? I have no idea. Bye, -- System Information: Debian Release: lenny/sid APT prefers unstable APT policy: (500, 'unstable'), (1, 'experimental') Architecture: i386 (i686) Kernel: Linux 2.6.22-1-686 (SMP w/2 CPU cores) Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash Versions of packages bzrtools depends on: ii bzr 0.18-1 Bazaar, the next-generation distri ii patch 2.5.9-4 Apply a diff file to an original ii python 2.4.4-6 An interactive high-level object-o ii python-central 0.5.14 register and build utility for Pyt Versions of packages bzrtools recommends: ii graphviz 2.12-3 rich set of graph drawing tools ii rsync 2.6.9-3 fast remote file copy program (lik -- no debconf information -- Loïc Minier --- bzrtools-0.18.0/debian/changelog +++ bzrtools-0.18.0/debian/changelog @@ -1,3 +1,11 @@ +bzrtools (0.18.0-1.1) UNRELEASED; urgency=low + + * Non-maintainer upload. + * Group branches by URL base and reuse one transport per URL base to avoid + reopening a connection for each pull in multi-pull. + + -- Loic Minier Mon, 30 Jul 2007 12:06:43 +0200 + bzrtools (0.18.0-1) unstable; urgency=low [ Arnaud Fontaine ] --- bzrtools-0.18.0.orig/__init__.py +++ bzrtools-0.18.0/__init__.py @@ -503,27 +503,72 @@ if not t.listable(): print "Can't list this type of location." return 3 - for branch, wt in iter_branch_tree(t): - if wt is None: - pullable = branch - else: - pullable = wt - parent = branch.get_parent() - if parent is None: - continue - if wt is not None: - base = wt.basedir - else: - base = branch.base - if base.startswith(t.base): - relpath = base[len(t.base):].rstrip('/') - else: - relpath = base - print "Pulling %s from %s" % (relpath, parent) - try: - pullable.pull(Branch.open(parent)) - except Exception, e: - print e + print "Grouping branches by URL" + by_urlbase = pullable_infos_by_urlbase(t) + for urlbase in by_urlbase: + print "Processing branches for %s/" % urlbase + urlbase_transport = get_transport(urlbase) + for pi in by_urlbase[urlbase]: + pullable = pi.get_pullable() + relpath = get_relpath(t.base, pi.get_base()) + parent = pi.get_parent() + from bzrtools import bzrdir_from_transport + pull_transport = urlbase_transport.clone(get_relpath(urlbase, parent)) + bzrdir = bzrdir_from_transport(pull_transport) + pull_branch = bzrdir.open_branch() + print "Pulling %s from %s" % (relpath, parent) + try: + pullable.pull(pull_branch) + except Exception, e: + print e + + +def get_relpath(base, path): + if path.startswith(base): + return path[len(base):].rstrip('/') + else: + return path + + +class PullableInfo: + def __init__(self, branch, wt): + self.branch = branch + self.wt = wt + + def get_pullable(self): + if self.wt is None: + return self.branch + return self.wt + + def get_parent(self): + return self.branch.get_parent() + + def get_base(self): + if self.wt is not None: + return self.wt.basedir + return self.branch.base + + def get_urlbase(self): + import re + # always matches at least the empty string + urlbase_pattern = re.compile("^(([^:]*://)?([^/]*))") + return urlbase_pattern.match(self.get_parent()).groups()[0] + + +def pullable_infos_by_urlbase(t): + pullables_by_urlbase = {} + from bzrtools import iter_branch_tree + for branch, wt in iter_branch_tree(t): + parent = branch.get_parent() + if parent is None: + continue + pullable_info = PullableInfo(branch, wt) + urlbase = pullable_info.get_urlbase() + try: + pullables_by_urlbase[urlbase] += (pullable_info, ) + except KeyError: + pullables_by_urlbase[urlbase] = (pullable_info, ) + return pullables_by_urlbase class cmd_branch_mark(bzrlib.commands.Command): ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf96499/valuesbzrtools/.be/bugs/b394a88a-3ecc-4058-8e29-4ebdbf96c95f/comments/0978c6a8-194e-4437-b3fc-2c99adf964990000644000000000000000000000013512264646316026051 0ustar 00000000000000 Content-type=text/plain Date=Mon, 30 Jul 2007 14:38:04 +0000 From=abentley bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/0000755000000000000000000000000012264646316021225 5ustar 00000000000000bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/values0000644000000000000000000000024612264646316020624 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=bzr branches not robust against redirections time=Mon, 30 Jul 2007 14:19:38 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c/bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c0000755000000000000000000000000012264646316025735 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c/bodybzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c0000644000000000000000000000556712264646316025754 0ustar 00000000000000Hello Aaron. I can confirm the attached bug report, that `bzr branches` does not seem to work with http:// URLs. I digged a bit in the source, and as far as I can see the problem is that the t.get('.') line in apache_ls() raises a RedirectRequested exception, which is silently swallowed in iter_bzrdirs(). I don't know much about bzr transport, but I tried to fix the issue with do_catching_redirections, but it didn't seem to work, see the attached script. What happens is that the trailing slash gets lost somewhere: get_transport('http://bzr.debian.org/bzr/pkg-maemo').get('.') raises a RedirectRequested exception whose .target is http://bzr.debian.org/bzr/pkg-maemo/ (one trailing slash added); BUT: get_transport('http://bzr.debian.org/bzr/pkg-maemo/').get('.') raises again the very same exception!! (same .source and .target); AND: get_transport('http://bzr.debian.org/bzr/pkg-maemo//').get('.') works correctly. So two issues here, as I understand it: 1. `bzr branch` is not robust against redirections 2. transport code seems to be silently ignoring one trailing slash in URLs Please let me know if I can be of any help. ------------------------8<--------------------------- #! /usr/bin/python import sys from bzrlib.errors import RedirectRequested, TooManyRedirections from bzrlib.transport import get_transport, do_catching_redirections url = sys.argv[1:] and sys.argv[1] or 'http://bzr.debian.org/bzr/pkg-maemo' transport = get_transport(url) try: transport.get('.') except RedirectRequested: print "A RedirectRequested was raised, trying now with do_catching_redirections." else: print "No RedirectRequested was raised, URL not good for this test." sys.exit(1) try: lines = do_catching_redirections(lambda t: t.get('.'), transport, lambda t, e, notice: get_transport(e.target)) except TooManyRedirections: print "We got TooManyRedirections, that's bad." else: print "do_catching_redirections works nicely." ------------------------>8--------------------------- ----- Forwarded message from Loïc Minier ----- From: Loïc Minier To: Debian Bug Tracking System Date: Sun, 29 Jul 2007 12:12:10 +0200 Subject: Bug#435110: "bzr branches" doesn't work with http:// URLs Package: bzrtools Version: 0.18.0-1 Severity: normal Hi, bzr branches works with: bzr+ssh://bzr.debian.org/bzr/pkg-maemo bzr+ssh://bzr.debian.org/bzr/pkg-maemo/hildon-desktop sftp://bzr.debian.org/bzr/pkg-maemo ~/bzr/debian/pkg-maemo but fails with: http://bzr.debian.org/bzr/pkg-maemo http://bzr.debian.org/bzr/pkg-maemo/hildon-desktop while bzr ls works on: http://bzr.debian.org/bzr/pkg-maemo/hildon-desktop/ubuntu I see there's a special case for http:// in the code; could this be the issue? Or is this because iterating over HTTP requires listing the .bzr directory? Bye, ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c/valuesbzrtools/.be/bugs/b47ef4d6-6a96-44ad-9694-6dd2c855d9c3/comments/4946aca8-392b-4d95-ae22-65c1a38d842c0000644000000000000000000000013512264646316025736 0ustar 00000000000000 Content-type=text/plain Date=Mon, 30 Jul 2007 14:25:22 +0000 From=abentley bzrtools/.be/bugs/bad9a00f-50f5-4782-9da6-722fb0a58c93/values0000644000000000000000000000025312264646316020565 0ustar 00000000000000 creator=abentley severity=minor status=open summary=Supplying past convesions doesn't work as expected? time=Thu, 23 Mar 2006 16:38:13 +0000 bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/0000755000000000000000000000000012264646316021507 5ustar 00000000000000bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/values0000644000000000000000000000023112264646316021100 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=patch requires '--check' on bsd time=Tue, 13 Mar 2007 12:05:59 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c/bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c0000755000000000000000000000000012264646316026323 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c/bodybzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c0000644000000000000000000000072412264646316026330 0ustar 00000000000000There was an old bug in the LP bug tracker for bzrtools, and I've been trying to clean it out, since it isn't supposed to be used. Specifically it seems the freeBSD system prefers '--check' as a flag, rather than '--dry-run'. The bug is here: https://bugs.beta.launchpad.net/bzrtools/+bug/84256 The proposed fix is: - args.append('--dry-run') + if sys.platform.startswith('freebsd'): + args.append('--check') + else: + args.append('--dry-run') John =:-> ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c/valuesbzrtools/.be/bugs/be87c595-f555-4bce-a12a-9eeefc7349ab/comments/3448c47d-def5-4d03-9b8c-f0968e0c787c0000644000000000000000000000013512264646316026324 0ustar 00000000000000 Content-type=text/plain Date=Tue, 13 Mar 2007 12:06:39 +0000 From=abentley bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/0000755000000000000000000000000012264646316021202 5ustar 00000000000000bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/values0000644000000000000000000000023112264646316020573 0ustar 00000000000000 creator=abentley severity=minor status=open summary=Shelf doesn't handle binary files time=Fri, 16 Mar 2007 19:36:40 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce/bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce0000755000000000000000000000000012264646316025775 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce/bodybzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce0000644000000000000000000000025012264646316025774 0ustar 00000000000000Originally reported by Kiko. It seems as though the nicest thing to do is to create udiffs for binaries. Patch doesn't seem to care whether its input is a binary file ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce/valuesbzrtools/.be/bugs/c4664fbc-3b6a-48a6-bc09-9e96fa250592/comments/f752631b-5590-4b6f-86d9-57aadca441ce0000644000000000000000000000013512264646316025776 0ustar 00000000000000 Content-type=text/plain Date=Fri, 16 Mar 2007 19:38:27 +0000 From=abentley bzrtools/.be/bugs/c59acbbf-25f4-450a-9a8f-7a6a45b3985e/values0000644000000000000000000000024612264646316020741 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=Deprecated EmptyTree used all over the place time=Mon, 31 Jul 2006 17:42:26 +0000 bzrtools/.be/bugs/da0f16ce-1ee0-4f9e-bf1a-9a78c4a7e765/values0000644000000000000000000000026112264646316021072 0ustar 00000000000000 assigned=abentley creator=root severity=minor status=closed summary=shelf produces deprecation warnings time=Mon, 13 Mar 2006 00:12:30 +0000 bzrtools/.be/bugs/dbd10145-51f8-4bda-96d9-06b5d3ef80b8/values0000644000000000000000000000024312264646316020645 0ustar 00000000000000 creator=abentley severity=minor status=closed summary=No way to force an encoding for Arch logs time=Thu, 23 Mar 2006 16:42:53 +0000 bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/0000755000000000000000000000000012264646316021314 5ustar 00000000000000bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/values0000644000000000000000000000026412264646316020713 0ustar 00000000000000 creator=abentley severity=minor status=open summary=fetch-ghosts should always invoke reconcile on the repo root time=Fri, 31 Mar 2006 15:48:55 +0000 ././@LongLink0000000000000000000000000000014600000000000011216 Lustar 00000000000000bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc3/bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc30000755000000000000000000000000012264646316025610 5ustar 00000000000000././@LongLink0000000000000000000000000000015200000000000011213 Lustar 00000000000000bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc3/bodybzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc30000644000000000000000000000017612264646316025616 0ustar 00000000000000The repo root may not contain the branch, but reconcile uses the repo root, not branch. Fix so reconcile is called properly. ././@LongLink0000000000000000000000000000015400000000000011215 Lustar 00000000000000bzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc3/valuesbzrtools/.be/bugs/fdc2b62a-9622-4f08-ac78-bd2603a4e0af/comments/f13e3358-1a65-4a45-9472-781548bc3fc30000644000000000000000000000007712264646316025616 0ustar 00000000000000 Date=Fri, 31 Mar 2006 15:52:54 +0000 From=abentley bzrtools/.the_kraken/config0000644000000000000000000000013012264646316014447 0ustar 00000000000000testing = false series = stable check_script = ./check-release.py edit_changelog = true bzrtools/tests/__init__.py0000644000000000000000000000011412264646316014343 0ustar 00000000000000def test_suite(): from unittest import TestSuite return TestSuite() bzrtools/tests/blackbox.py0000644000000000000000000002130412264646316014375 0ustar 00000000000000import os import os.path from unittest import makeSuite from bzrlib import branch, osutils, workingtree from bzrlib.config import LocationConfig from bzrlib.transport import get_transport from bzrlib.tests import ( TestCaseWithTransport, ) from bzrlib.plugins.bzrtools import command try: from bzrlib.tests.features import ( HardlinkFeature, ModuleAvailableFeature, ) except ImportError: # bzr < 2.5 from bzrlib.tests import ( HardlinkFeature, ModuleAvailableFeature, ) LzmaFeature = ModuleAvailableFeature("lzma") class TestBzrTools(TestCaseWithTransport): def setUp(self): TestCaseWithTransport.setUp(self) command._testing = True self.addCleanup(command._stop_testing) @staticmethod def touch(filename): file(filename, 'wb').write('') def test_shelve(self): self.run_bzr('init') self.run_bzr('commit -m uc --unchanged') self.run_bzr('shelve1 -r 1 -m foo --all', retcode=3) file('foo', 'wb').write('foo') self.run_bzr('add foo') self.run_bzr('commit -m foo') self.run_bzr('shelve1 -r 1 -m foo --all', retcode=0) def test_fetch_ghosts(self): self.run_bzr('init') self.run_bzr('fetch-ghosts .') def test_fetch_ghosts_with_saved(self): wt = self.make_branch_and_tree('.') wt.branch.set_parent('.') self.run_bzr('fetch-ghosts') def test_patch(self): self.run_bzr('init') file('myfile', 'wb').write('hello') self.run_bzr('add') self.run_bzr('commit -m hello') file('myfile', 'wb').write('goodbye') file('mypatch', 'wb').write(self.run_bzr('diff', retcode=1)[0]) self.run_bzr('revert') assert file('myfile', 'rb').read() == 'hello' self.run_bzr('patch --silent mypatch') assert file('myfile', 'rb').read() == 'goodbye' def test_branch_history(self): self.run_bzr('init') file('myfile', 'wb').write('hello') self.run_bzr('add') self.run_bzr('commit -m hello') self.run_bzr('branch-history') def test_branch_history(self): self.run_bzr('init') file('myfile', 'wb').write('hello') self.run_bzr('add') self.run_bzr('commit -m hello') self.run_bzr('graph-ancestry . graph.dot') self.run_bzr('branch . my_branch') self.run_bzr('graph-ancestry . graph.dot --merge-branch my_branch') def test_fetch_ghosts(self): self.run_bzr('init') file('myfile', 'wb').write('hello') self.run_bzr('add') self.run_bzr('commit -m hello') self.run_bzr('branch . my_branch') self.run_bzr('fetch-ghosts my_branch') def test_zap(self): self.run_bzr('init source') self.run_bzr('checkout --lightweight source checkout') self.run_bzr('zap checkout') self.assertIs(False, os.path.exists('checkout')) self.assertIs(True, os.path.exists('source')) def test_zap_modified(self): tree = self.make_branch_and_tree('source') checkout = tree.branch.create_checkout('checkout', lightweight=True) self.build_tree(['checkout/file']) checkout.add('file') self.run_bzr_error(('This checkout has uncommitted changes',), 'zap checkout') self.assertPathExists('checkout') self.run_bzr('zap checkout --force') self.assertPathDoesNotExist('checkout') self.assertPathExists('source') def test_zap_branch(self): self.run_bzr('init source') self.run_bzr('checkout --lightweight source checkout') self.run_bzr('zap --branch checkout', retcode=3) self.assertIs(True, os.path.exists('checkout')) self.assertIs(True, os.path.exists('source')) self.run_bzr('branch source source2') self.run_bzr('checkout --lightweight source2 checkout2') self.run_bzr('zap --branch checkout2') self.assertIs(False, os.path.exists('checkout2')) self.assertIs(False, os.path.exists('source2')) def test_list_branches(self): self.run_bzr('init source') self.run_bzr('init source/subsource') self.run_bzr('checkout --lightweight source checkout') self.run_bzr('init checkout/subcheckout') self.run_bzr('init checkout/.bzr/subcheckout') out = self.run_bzr('list-branches')[0] lines = out.split('\n') self.assertIs(True, 'source' in lines) self.assertIs(True, 'source/subsource' in lines) self.assertIs(True, 'checkout/subcheckout' in lines) self.assertIs(True, 'checkout' not in lines) def test_import_upstream(self): self.run_bzr('init source') os.mkdir('source/src') f = file('source/src/myfile', 'wb') f.write('hello?') f.close() os.chdir('source') self.run_bzr('add') self.run_bzr('commit -m hello') self.run_bzr('export ../source-0.1.tar.gz') self.run_bzr('export ../source-0.1.tar.bz2') self.run_bzr('export ../source-0.1') self.run_bzr('init ../import') os.chdir('../import') self.run_bzr('import ../source-0.1.tar.gz') self.assertPathExists('src/myfile') result = self.run_bzr('import ../source-0.1.tar.gz', retcode=3)[1] self.assertContainsRe(result, 'Working tree has uncommitted changes') self.run_bzr('commit -m commit') self.run_bzr('import ../source-0.1.tar.gz') os.chdir('..') self.run_bzr('init import2') self.run_bzr('import source-0.1.tar.gz import2') self.assertPathExists('import2/src/myfile') self.run_bzr('import source-0.1.tar.gz import3') self.assertPathExists('import3/src/myfile') self.run_bzr('import source-0.1.tar.bz2 import4') self.assertPathExists('import4/src/myfile') self.run_bzr('import source-0.1 import5') self.assertPathExists('import5/src/myfile') def test_import_upstream_lzma(self): self.requireFeature(LzmaFeature) self.run_bzr('init source') os.mkdir('source/src') f = file('source/src/myfile', 'wb') f.write('hello?') f.close() os.chdir('source') self.run_bzr('add') self.run_bzr('commit -m hello') self.run_bzr('export ../source-0.1.tar.lzma') self.run_bzr('export ../source-0.1.tar.xz') os.chdir('..') self.run_bzr('import source-0.1.tar.lzma import1') self.assertPathExists('import1/src/myfile') self.run_bzr('import source-0.1.tar.xz import2') self.assertPathExists('import2/src/myfile') def test_cbranch(self): source = self.make_branch_and_tree('source') config = LocationConfig(osutils.abspath('target')) config.set_user_option('cbranch_target', 'target_branch') self.run_bzr('cbranch source target') checkout = workingtree.WorkingTree.open('target') self.assertEqual(checkout.branch.base, get_transport('target').base) self.assertEqual(checkout.branch.get_master_branch().base, get_transport('target_branch').base) self.assertEqual(checkout.branch.get_master_branch().get_parent(), get_transport('source').base) def test_cbranch_hardlink(self): self.requireFeature(HardlinkFeature) # Later formats don't support hardlinks. Boo! source = self.make_branch_and_tree('source', format='1.9') self.build_tree(['source/file']) source.add('file') source.commit('added file') config = LocationConfig(osutils.abspath('target')) config.set_user_option('cbranch_target', 'target_branch') self.run_bzr('cbranch source target --lightweight') checkout = workingtree.WorkingTree.open('target') self.assertNotEqual(os.lstat(checkout.abspath('file')).st_ino, os.lstat(source.abspath('file')).st_ino) config = LocationConfig(osutils.abspath('target2')) config.set_user_option('cbranch_target', 'target_branch2') self.run_bzr('cbranch source target2 --lightweight --hardlink') checkout2 = workingtree.WorkingTree.open('target2') self.assertEqual(os.lstat(checkout2.abspath('file')).st_ino, os.lstat(source.abspath('file')).st_ino) def test_create_mirror(self): source = self.make_branch_and_tree('source') source.commit('message') self.run_bzr('create-mirror source target') target = branch.Branch.open('target') self.assertEqual(source.last_revision(), target.last_revision()) self.assertEqual(source.branch.base, target.get_public_branch()) def test_suite(): return makeSuite(TestBzrTools) bzrtools/tests/shelf_tests.py0000644000000000000000000005533512264646316015146 0ustar 00000000000000import os.path from bzrlib.diff import _patch_header_date import bzrlib.tests from bzrlib.plugins.bzrtools.hunk_selector import ( ShelveHunkSelector, UnshelveHunkSelector, ) from bzrlib.plugins.bzrtools.errors import NoColor from bzrlib.plugins.bzrtools import command from bzrlib.plugins.bzrtools.command_classes import cmd_shelf1 class ShelfTests(bzrlib.tests.TestCaseWithTransport): ORIGINAL = '\n\nhello test world\n\n' MODIFIED = '\n\ngoodbye test world\n\n' DIFF_HEADER = "=== modified file '%(filename)s'\n" DIFF_1 = """--- %(filename)s\t%(old_date)s +++ %(filename)s\t%(new_date)s @@ -1,4 +1,4 @@ -hello test world +goodbye test world """ DIFF_2 = """--- test_file\t%(old_date)s +++ test_file\t%(new_date)s @@ -1,4 +1,4 @@ -goodbye test world +hello test world """ def setUp(self): bzrlib.tests.TestCaseWithTransport.setUp(self) command._testing = True self.addCleanup(command._stop_testing) def _check_diff(self, diff=DIFF_1, filename='test_file'): old_tree = self.tree.basis_tree() old_tree.lock_read() self.tree.lock_read() try: old_date = _patch_header_date(old_tree, old_tree.path2id(filename), filename) new_date = _patch_header_date(self.tree, self.tree.path2id(filename), filename) finally: self.tree.unlock() old_tree.unlock() keys = { 'filename' : filename , 'old_date': old_date, 'new_date': new_date} hdr = self.DIFF_HEADER % keys diff = diff % keys self.assertEqual(self.run_bzr('diff', retcode=1)[0], hdr + diff + '\n') def _check_shelf(self, idx, diff=DIFF_1, filename='test_file', new_date=None): old_tree = self.tree.basis_tree() old_tree.lock_read() try: old_date = _patch_header_date(old_tree, old_tree.path2id(filename), filename) finally: old_tree.unlock() diff = diff % { 'filename' : filename, 'old_date': old_date, 'new_date': new_date} shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default/' + idx)).read() shelf = shelf[shelf.index('\n') + 1:] # skip the message self.assertEqual(shelf, diff) def test_shelf(self): self.__test_loop(1) def test_shelf_multi(self): self.__test_loop(10) def __test_loop(self, count): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() for counter in range(count): # Modify the test file # write in binary mode because on win32 line-endings should be # LF f = file('test_file', 'wb') f.write(self.MODIFIED) f.close() self._check_diff() self.tree.lock_write() try: new_date = _patch_header_date(self.tree, self.tree.path2id('test_file'), 'test_file') finally: self.tree.unlock() # Shelve the changes self.run_bzr('shelve1 --all', retcode=0) # Make sure there is no diff anymore self.assertEqual(self.run_bzr('diff', retcode=0)[0], '') # Make sure the file is actually back the way it was self.assertEqual(file('test_file').read(), self.ORIGINAL) self._check_shelf('00', new_date=new_date) # Unshelve self.run_bzr('unshelve1 --all', retcode=0) self._check_diff() # Check the shelved patch was backed up self._check_shelf('00~', new_date=new_date) # Make sure the file is back the way it should be self.assertEqual(file('test_file').read(), self.MODIFIED) def test_shelf_nothing_to_shelve(self): import os.path self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # Shelve the changes self.run_bzr('shelve1 --all', retcode=3) if os.path.exists(os.path.join(self.tree.branch.base, '.shelf/shelves/default/00')): self.fail("Shelf exists, but it shouldn't") def __create_and_add_test_file(self, filename='test_file'): # write in binary mode because on win32 line-endings should be LF f = open(filename, 'wb') f.write(self.ORIGINAL) f.close() self.tree.add(self.tree.relpath(os.path.join(os.getcwd(), filename))) self.tree.commit(message='add %s' % filename) def test_shelf_with_revision(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # Modify the test file and commit it self.build_tree_contents([('test_file', self.MODIFIED)]) self.tree.commit(message='update test_file') # Shelve the changes self.run_bzr('shelve1 --all -r 1', retcode=0) self._check_diff(self.DIFF_2) # Make sure the file is the way it should be self.assertEqual(file('test_file').read(), self.ORIGINAL) # Unshelve self.run_bzr('unshelve1 --all', retcode=0) # Make sure the file is back the way it should be self.assertEqual(file('test_file').read(), self.MODIFIED) def test_shelf_with_two_revisions(self): self.tree = self.make_branch_and_tree('.') stdout, stderr = self.run_bzr('shelve1 --all -r 1..2', retcode=None) self.assertEqual(stderr.split('\n')[0], 'bzr: ERROR: shelve only accepts a single revision parameter.') def test_shelf_show_basic(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() self.__test_show(self.tree, '00') def __test_show(self, tree, patch): # Modify the test file self.build_tree_contents([('test_file', 'patch %s\n' % patch)]) # Shelve the changes self.run_bzr('shelve1 --all', retcode=0) # Make sure there is no diff anymore self.assertEqual(self.run_bzr('diff', retcode=0)[0], '') # Check the shelf is right shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default', patch)).read() self.assertTrue('patch %s' % patch in shelf) # Check the shown output is right shown = self.run_bzr('shelf1 show %s' % patch, retcode=0)[0] self.assertEqual(shown, shelf) def test_shelf_show_multi(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() self.__test_show(self.tree, '00') self.__test_show(self.tree, '01') self.__test_show(self.tree, '02') # Now check we can show a previously shelved patch shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default/00')).read() self.assertTrue('patch 00' in shelf) # Check the shown output is right shown = self.run_bzr('shelf1 show 00', retcode=0)[0] self.assertEqual(shown, shelf) def test_shelf_show_unspecified(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() self.__test_show(self.tree, '00') self.__test_show(self.tree, '01') self.__test_show(self.tree, '02') # Check that not specifying at patch gets us the most recent shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default/02')).read() self.assertTrue('patch 02' in shelf) # Check the shown output is right shown = self.run_bzr('shelf1 show', retcode=0)[0] self.assertEqual(shown, shelf) def test_shelf_show_with_no_patch(self): self.tree = self.make_branch_and_tree('.') stderr = self.run_bzr('shelf1 show 00', retcode=None)[1] self.assertTrue("Patch '00' doesn't exist on shelf default!" in stderr) def test_shelf_unshelve_failure(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # Modify the test file file('test_file', 'w').write(self.MODIFIED) # Shelve the changes self.run_bzr('shelve1 --all', retcode=0) # Write an unapplyable patch into the shelf shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default/00'), 'w') shelf.write(self.DIFF_2) shelf.close() # Unshelve, should fail self.run_bzr('unshelve1 --all', retcode=3) # Make sure the patch is still there, eventhough it's broken shelf = open(os.path.join(self.tree.basedir, '.shelf/shelves/default/00')).read() self.assertEqual(shelf, self.DIFF_2) # Working tree should be unchanged diff = self.run_bzr('diff', retcode=0)[0] self.assertEqual(diff, '') def test_shelf_unshelve_failure_two_hunks(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() self.__create_and_add_test_file(filename='test_file2') # Modify the test files file('test_file', 'w').write(self.MODIFIED) file('test_file2', 'w').write(self.MODIFIED) # Shelve the changes self.run_bzr('shelve1 --all', retcode=0) # Put the changes to test_file back, the shelved patch won't apply now file('test_file', 'w').write(self.MODIFIED) self.tree.commit(message='screw up test_file') # Unshelve, should fail self.run_bzr('unshelve1 --all', retcode=3) # Working tree should be unchanged diff = self.run_bzr('diff', retcode=0)[0] self.assertEqual(diff, '') # Force should succeed and modify test_file2, but leave shelf self.run_bzr('unshelve1 --force --all', retcode=0) self.assertEqual(open('test_file2').read(), self.MODIFIED) self.assertTrue(os.path.exists('.shelf/shelves/default/00')) def test_shelf_after_unshelve(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() self.__create_and_add_test_file(filename='test_file2') # Modify the test files file('test_file', 'w').write(self.MODIFIED) file('test_file2', 'w').write(self.MODIFIED) # Shelve the changes self.run_bzr('shelve1 --all test_file', retcode=0) self.run_bzr('shelve1 --all test_file2', retcode=0) # Unshelve self.run_bzr('unshelve1 --all', retcode=0) # We should now have 00 and 01~ self.assertTrue(os.path.exists('.shelf/shelves/default/00')) self.assertTrue(os.path.exists('.shelf/shelves/default/01~')) # Check ls works lines = self.run_bzr('shelf1 ls', retcode=0)[0].split('\n') for line in lines: self.assertFalse(line.startswith(' 01')) # Unshelve, if unshelve is confused by the backup it will fail self.run_bzr('unshelve1 --all', retcode=0) def test_shelf_delete(self): self.tree = self.make_branch_and_tree('.') self.tree.lock_write() try: self.__create_and_add_test_file() self.__create_and_add_test_file(filename='test_file2') # Modify the test files # write in binary mode because on win32 line-endings should be LF f = file('test_file', 'wb') f.write(self.MODIFIED) f.close() f = file('test_file2', 'wb') f.write(self.MODIFIED) f.close() new_date = _patch_header_date(self.tree, self.tree.path2id('test_file'), 'test_file') finally: self.tree.unlock() # Shelve the changes self.run_bzr('shelve1 --all test_file', retcode=0) self.run_bzr('shelve1 --all test_file2', retcode=0) self._check_shelf('00', new_date=new_date) # Delete 00 self.run_bzr('shelf1 delete 00', retcode=0) # We should now have 01 but not 00, but we should have 00~ self.assertFalse(os.path.exists('.shelf/shelves/default/00')) self.assertTrue(os.path.exists('.shelf/shelves/default/00~')) self.assertTrue(os.path.exists('.shelf/shelves/default/01')) # Check the backup is right self._check_shelf('00~', new_date=new_date) # Check ls works lines = self.run_bzr('shelf1 ls', retcode=0)[0].split('\n') for line in lines: self.assertFalse(line.startswith(' 00')) # Unshelve should unshelve 01 self.run_bzr('unshelve1 --all', retcode=0) self.assertEqual(file('test_file2').read(), self.MODIFIED) def test_shelf_gaps(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) # Now delete 00, leaving 01, next shelve should go into 02 self.run_bzr('shelf1 delete 0', retcode=0) self.assertFalse(os.path.exists('.shelf/shelves/default/00')) self.assertFalse(os.path.exists('.shelf/shelves/default/02')) file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) self.assertFalse(os.path.exists('.shelf/shelves/default/00')) self.assertTrue(os.path.exists('.shelf/shelves/default/02')) def test_shelf_upgrade(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # Modify then shelve, so we're not upgrading to 00, just for kicks file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) open('.bzr-shelf', 'w').write('First old shelf') open('.bzr-shelf-1', 'w').write('Second old shelf') open('.bzr-shelf-3', 'w').write('Fourth old shelf') # shelve and unshelve should bitch and do nothing file('test_file', 'w').write('blah blah blah') self.run_bzr('shelve1 --all', retcode=3) self.assertFalse(os.path.exists('.shelf/shelves/default/01')) self.assertEqual(file('test_file').read(), 'blah blah blah') self.run_bzr('unshelve1 --all', retcode=3) self.assertTrue(os.path.exists('.shelf/shelves/default/00')) # Upgrade, make sure it worked self.run_bzr('shelf1 upgrade', retcode=0) self.assertEqual(open('.shelf/shelves/default/01').read(), 'First old shelf') self.assertEqual(open('.shelf/shelves/default/02').read(), 'Second old shelf') self.assertEqual(open('.shelf/shelves/default/03').read(), 'Fourth old shelf') # Check the old shelves got backed up right self.assertTrue(os.path.exists('.bzr-shelf~')) self.assertTrue(os.path.exists('.bzr-shelf-1~')) self.assertTrue(os.path.exists('.bzr-shelf-3~')) self.assertFalse(os.path.exists('.bzr-shelf')) self.assertFalse(os.path.exists('.bzr-shelf-1')) self.assertFalse(os.path.exists('.bzr-shelf-3')) # Shelve should work now self.run_bzr('shelve1 --all', retcode=0) def test_shelf_p1_patch(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # Run a benign shelf command to setup .shelf for us self.run_bzr('shelf1 ls', retcode=0) old_tree = self.tree.basis_tree() old_tree.lock_read() self.tree.lock_read() try: old_date = _patch_header_date(old_tree, old_tree.path2id('test_file'), 'test_file') new_date = _patch_header_date(self.tree, self.tree.path2id('test_file'), 'test_file') finally: old_tree.unlock() self.tree.unlock() # Fake a -p0 shelved patch diff = self.DIFF_1 % { 'filename' : 'test_file', 'old_date': old_date, 'new_date' : new_date} diff = diff.replace('--- ', '--- a/') diff = diff.replace('+++ ', '+++ b/') open('.shelf/shelves/default/00', 'w').write(diff) # This should work self.run_bzr('unshelve1 --all', retcode=0) self._check_diff() def test_shelf_shelve_in_subdir(self): self.tree = self.make_branch_and_tree('.') subdir = 'subdir' os.mkdir(subdir) self.tree.add(subdir) os.chdir(subdir) self.__create_and_add_test_file() # Modify the test file # write in binary mode because on win32 line-endings should be LF f = file('test_file', 'wb') f.write(self.MODIFIED) f.close() # Shelve the changes self.run_bzr('shelve1 --all', retcode=0) # Working tree should be unchanged diff = self.run_bzr('diff', retcode=0)[0] self.assertEqual(diff, '') # Unshelve, should succeed self.run_bzr('unshelve1 --all', retcode=0) self._check_diff(filename='subdir/test_file') # Make sure relative filenames work ok self.run_bzr('shelve1 test_file --all', retcode=0) def test_shelf_shelf_bogus_subcommand(self): self.tree = self.make_branch_and_tree('.') self.run_bzr('shelf1 foo', retcode=3) # <- retcode == 3 def test_shelf_OOO_unshelve(self): self.tree = self.make_branch_and_tree('.') for i in range(1, 5): self.__create_and_add_test_file(filename='test_file%d' % i) # Modify the test files for i in range(1, 5): file('test_file%d' % i, 'w').write(self.MODIFIED) # Shelve the changes for i in range(1, 5): self.run_bzr(['shelve1', '--all', 'test_file%d' % i], retcode=0) # Check shelving worked for i in range(1, 5): self.assertEqual(file('test_file%d' % i).read(), self.ORIGINAL) # We should now have 00-03 for i in range(0, 4): self.assertTrue(os.path.exists('.shelf/shelves/default/0%d' % i)) # Unshelve 00 self.run_bzr('unshelve1 --all 00', retcode=0) self.assertEqual(file('test_file1').read(), self.MODIFIED) # Check ls works lines = self.run_bzr('shelf1 ls', retcode=0)[0].split('\n') for line in lines: self.assertFalse(line.startswith(' 00')) # Check we can reshelve once we've unshelved out of order, should be 04 self.assertFalse(os.path.exists('.shelf/shelves/default/04')) self.run_bzr('shelve1 --all') self.assertTrue(os.path.exists('.shelf/shelves/default/04')) # Check ls works text = self.run_bzr('shelf1 ls', retcode=0)[0] for line in text.split('\n'): self.assertFalse(line.startswith(' 00')) # We now have 01,02,03,04 # Unshelve 02 self.run_bzr('unshelve1 --all 02', retcode=0) self.assertEqual(file('test_file3').read(), self.MODIFIED) # Unshelve the default, this is the reshelved 00, hence modifies file 1 self.run_bzr('unshelve1 --all', retcode=0) self.assertEqual(file('test_file1').read(), self.MODIFIED) def test_shelf_switch_basic(self): self.tree = self.make_branch_and_tree('.') self.__create_and_add_test_file() # This should go to "default" file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) # Switch to "other" self.run_bzr('shelf1 switch other', retcode=0) file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) # Check it worked self.assertTrue(os.path.exists('.shelf/shelves/default/00')) self.assertFalse(os.path.exists('.shelf/shelves/default/01')) self.assertTrue(os.path.exists('.shelf/shelves/other/00')) # Switch back self.run_bzr('shelf1 switch default', retcode=0) file('test_file', 'w').write(self.MODIFIED) self.run_bzr('shelve1 --all test_file', retcode=0) # Check that worked self.assertTrue(os.path.exists('.shelf/shelves/default/01')) self.assertFalse(os.path.exists('.shelf/shelves/other/01')) def test_shelf_binary(self): self.tree = self.make_branch_and_tree('.') self.build_tree_contents([('file', '\x00')]) self.tree.add('file') self.run_bzr_error(['Changes involve binary files.'], 'shelve1 --all') def test_shelf_bad_patch_arg(self): self.tree = self.make_branch_and_tree('.') # Check the bad arg handling stdout, error = self.run_bzr('unshelve1 01', retcode=3) self.assertTrue("Patch '01' doesn't exist on shelf" in error) stdout, error = self.run_bzr('unshelve1 foo', retcode=3) self.assertTrue("Invalid patch name 'foo'" in error) # Hex and is cracky, so it shouldn't work stdout, error = self.run_bzr(['unshelve1', '0x00'], retcode=3) self.assertTrue("Invalid patch name '0x00'" in error) def test_color_hunk_selector(self): """Make sure NoColor is raised iff terminal.has_ansi_colors is False""" hs = ShelveHunkSelector([]) hs = UnshelveHunkSelector([]) try: from bzrlib.plugins.bzrtools import terminal except ImportError: from bzrtools import terminal old_has_ansi_colors = terminal.has_ansi_colors terminal.has_ansi_colors = lambda: False try: self.assertRaises(NoColor, ShelveHunkSelector, [], True) self.assertRaises(NoColor, UnshelveHunkSelector, [], True) terminal.has_ansi_colors = lambda: True hs = ShelveHunkSelector([], color=True) hs = UnshelveHunkSelector([], color=True) finally: terminal.has_ansi_colors = old_has_ansi_colors def test_no_color(self): """Ensure --no-color switch can be passed""" self.tree = self.make_branch_and_tree('.') subdir = 'subdir' os.mkdir(subdir) self.tree.add(subdir) os.chdir(subdir) self.__create_and_add_test_file() # Modify the test file # write in binary mode because on win32 line-endings should be LF f = file('test_file', 'wb') f.write(self.MODIFIED) f.close() stdout, error = self.run_bzr('shelve1 --all --no-color') stdout, error = self.run_bzr('unshelve1 --all --no-color') def test_shelf_help(self): self.assertContainsRe(cmd_shelf1().help(), 'list\n.*List the patches on the current shelf') def test_show_empty_shelf(self): self.tree = self.make_branch_and_tree('.') self.run_bzr_error(('No patches on shelf.',), 'shelf1 show') bzrtools/tests/test_cbranch.py0000644000000000000000000000126212264646316015250 0ustar 00000000000000from unittest import makeSuite from bzrlib.config import LocationConfig from bzrlib import osutils from bzrlib.tests import TestCaseWithTransport from bzrlib.plugins.bzrtools.cbranch import cbranch class TestCBranch(TestCaseWithTransport): def test_cbranch_creates_containing_dirs(self): source = self.make_branch_and_tree('source') config = LocationConfig(osutils.abspath('target')) config.set_user_option('cbranch_target', 'directory/subdirectory/target_branch') cbranch('source', 'target') self.assertPathExists('directory/subdirectory/target_branch') def test_suite(): return makeSuite(TestBzrTools) bzrtools/tests/test_conflict_diff.py0000644000000000000000000000535012264646316016443 0ustar 00000000000000# Copyright (C) 2009 Aaron Bentley # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from cStringIO import StringIO import os from bzrlib.tests import TestCaseWithTransport from bzrlib.plugins.bzrtools import errors from bzrlib.plugins.bzrtools.conflict_diff import ( ConflictDiffer ) class TestConflictDiff(TestCaseWithTransport): def test_conflict_diff_this(self): self.build_tree_contents( [('foo.THIS', 'this\n'), ('foo.BASE', 'base\n')]) s = StringIO() ConflictDiffer().conflict_diff(s, 'foo', 'this') self.assertEqual('--- foo.BASE\n+++ foo.THIS\n' '@@ -1,1 +1,1 @@\n' '-base\n+this\n\n', s.getvalue()) def test_no_conflict(self): tree = self.make_branch_and_tree('.') self.build_tree_contents([('foo', 'base\n')]) tree.add('foo') e = self.assertRaises(errors.NoConflictFiles, ConflictDiffer().get_old_lines, 'foo', 'foo.BASE') self.assertEqual('foo.BASE does not exist and there are no pending' ' merges.', str(e)) def test_get_old_lines(self): self.build_tree_contents([('foo.BASE', 'base\n')]) old_lines = ConflictDiffer().get_old_lines('foo', 'foo.BASE') self.assertEqual(['base\n'], old_lines) def test_get_old_lines_no_base(self): tree = self.make_branch_and_tree('tree') self.build_tree_contents([('tree/foo', 'base\n')]) tree.add('foo') tree.commit('added foo') other = tree.bzrdir.sprout('other').open_workingtree() self.build_tree_contents([('other/foo', 'other\n')]) other.commit('Changed foo text') self.build_tree_contents([('tree/foo', 'this\n')]) tree.commit('Changed foo text') tree.merge_from_branch(other.branch) os.unlink('tree/foo.BASE') old_lines = ConflictDiffer().get_old_lines('tree/foo', 'tree/foo.BASE') self.assertEqual(['base\n'], old_lines) bzrtools/tests/test_dotgraph.py0000644000000000000000000000047712264646316015467 0ustar 00000000000000from unittest import TestCase from bzrlib.plugins.bzrtools import dotgraph class TestNode(TestCase): def test_define_escapes_backslash(self): node = dotgraph.Node('AB', message=r'Slash\me') self.assertEqual(r'AB[shape="box" tooltip="Slash\\me" href="#"]', node.define()) bzrtools/tests/test_fetch_ghosts.py0000644000000000000000000000170712264646316016334 0ustar 00000000000000from bzrlib.plugins.bzrtools.fetch_ghosts import GhostFetcher from bzrlib.tests import TestCaseWithTransport class TestFetchGhosts(TestCaseWithTransport): def prepare_with_ghosts(self): tree = self.make_branch_and_tree('.') tree.commit('rev1', rev_id='rev1-id') tree.set_parent_ids(['rev1-id', 'ghost-id']) tree.commit('rev2') return tree def test_fetch_ghosts_failure(self): tree = self.prepare_with_ghosts() branch = self.make_branch('branch') GhostFetcher(tree.branch, branch).run() self.assertFalse(tree.branch.repository.has_revision('ghost-id')) def test_fetch_ghosts_success(self): tree = self.prepare_with_ghosts() ghost_tree = self.make_branch_and_tree('ghost_tree') ghost_tree.commit('ghost', rev_id='ghost-id') GhostFetcher(tree.branch, ghost_tree.branch).run() self.assertTrue(tree.branch.repository.has_revision('ghost-id')) bzrtools/tests/test_graph.py0000644000000000000000000000577112264646316014762 0ustar 00000000000000# Copyright (C) 2005, 2006 Canonical Ltd # # 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 Street, Fifth Floor, Boston, MA 02110-1301 USA from bzrlib.tests import ( TestCase, TestCaseWithMemoryTransport, ) from bzrlib.plugins.bzrtools.graph import ( get_rev_info, node_distances, nodes_by_distance, ) class TestBase(TestCase): def edge_add(self, *args): for start, end in zip(args[:-1], args[1:]): if start not in self.graph: self.graph[start] = {} if end not in self.graph: self.graph[end] = {} self.graph[start][end] = 1 def setUp(self): TestCase.setUp(self) self.graph = {} self.edge_add('A', 'B', 'C', 'D') self.edge_add('A', 'E', 'F', 'C') self.edge_add('A', 'G', 'H', 'I', 'B') self.edge_add('A', 'J', 'K', 'L', 'M', 'N') self.edge_add('O', 'N') def node_descendants(self): descendants = {'A':set()} for node in self.graph: for ancestor in self.graph[node]: if ancestor not in descendants: descendants[ancestor] = set() descendants[ancestor].add(node) return descendants def test_distances(self): descendants = self.node_descendants() distances = node_distances(self.graph, descendants, 'A') nodes = nodes_by_distance(distances) self.assertEqual(nodes[0], 'D') self.assert_(nodes[1] in ('N', 'C')) self.assert_(nodes[2] in ('N', 'C')) self.assert_(nodes[3] in ('B', 'M')) self.assert_(nodes[4] in ('B', 'M')) #Ensure we don't shortcut through B when there's only a difference of # 1 in distance self.graph = {} self.edge_add('A', 'B', 'C') self.edge_add('A', 'D', 'E', 'C') descendants = self.node_descendants() distances = node_distances(self.graph, descendants, 'A') self.assertEqual(distances['C'], 3) class TestGetRevInfo(TestCaseWithMemoryTransport): def test_revision_not_present(self): repo = self.make_repository('foo') revision = 'aaron@aaronbentley.com-20110925012351-s3q6441rx3t' committer, message, nick, date = get_rev_info(revision, repo) self.assertEqual('aaron@aaronbentley.com', committer) self.assertIs(None, message) self.assertIs(None, nick) self.assertIs(None, date) bzrtools/tests/test_link_tree.py0000644000000000000000000000646712264646316015640 0ustar 00000000000000# Copyright (C) 2008 Aaron Bentley # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os from bzrlib.transform import TreeTransform from bzrlib.tests import TestCaseWithTransport try: from bzrlib.tests.features import HardlinkFeature except ImportError: # bzr < 2.5 from bzrlib.tests import HardlinkFeature from bzrlib.plugins.bzrtools import command from bzrlib.plugins.bzrtools.link_tree import link_tree class TestLinkTreeBase(object): _test_needs_features = [HardlinkFeature] def setUp(self): TestCaseWithTransport.setUp(self) self.parent_tree = self.make_branch_and_tree('parent') self.parent_tree.lock_write() self.addCleanup(self.parent_tree.unlock) self.build_tree_contents([('parent/foo', 'bar')]) self.parent_tree.add('foo', 'foo-id') self.parent_tree.commit('added foo') child_bzrdir = self.parent_tree.bzrdir.sprout('child') self.child_tree = child_bzrdir.open_workingtree() def hardlinked(self): parent_stat = os.lstat(self.parent_tree.abspath('foo')) child_stat = os.lstat(self.child_tree.abspath('foo')) return parent_stat.st_ino == child_stat.st_ino class TestLinkTree(TestLinkTreeBase, TestCaseWithTransport): def test_link_fails_if_modified(self): """If the file to be linked has modified text, don't link.""" self.build_tree_contents([('child/foo', 'baz')]) link_tree(self.child_tree, self.parent_tree) self.assertFalse(self.hardlinked()) def test_link_fails_if_execute_bit_changed(self): """If the file to be linked has modified execute bit, don't link.""" tt = TreeTransform(self.child_tree) try: trans_id = tt.trans_id_tree_file_id('foo-id') tt.set_executability(True, trans_id) tt.apply() finally: tt.finalize() link_tree(self.child_tree, self.parent_tree) self.assertFalse(self.hardlinked()) def test_link_succeeds_if_unmodified(self): """If the file to be linked is unmodified, link""" link_tree(self.child_tree, self.parent_tree) self.assertTrue(self.hardlinked()) class TestLinkTreeCommand(TestLinkTreeBase, TestCaseWithTransport): def test_link_tree(self): """Ensure the command works as intended""" command._testing = True try: os.chdir('child') self.parent_tree.unlock() self.run_bzr('link-tree ../parent') self.assertTrue(self.hardlinked()) # want teh addCleanup to work properly self.parent_tree.lock_write() finally: command._testing = False bzrtools/tests/test_mirror.py0000644000000000000000000000425712264646316015171 0ustar 00000000000000from bzrlib import revision, transform from bzrlib.tests import TestCaseWithTransport from bzrlib.plugins.bzrtools.mirror import create_mirror class TestMirror(TestCaseWithTransport): def commit_transform(self, branch, tt): base_revno, base_revision = branch.last_revision_info() if base_revision == revision.NULL_REVISION: parents = [] else: parents = [base_revision] preview = tt.get_preview_tree() preview.set_parent_ids(parents) changes = tt.iter_changes() builder = branch.get_commit_builder(parents) try: list(builder.record_iter_changes(preview, base_revision, changes)) builder.finish_inventory() revision_id = builder.commit('message') except: builder.abort() raise branch.set_last_revision_info(base_revno + 1, revision_id) def make_transform(self, branch): base = branch.basis_tree() tt = transform.TransformPreview(base) self.addCleanup(tt.finalize) root = tt.new_directory('', transform.ROOT_PARENT, 'root-id') tt._new_root = root return tt def test_create_mirror(self): source = self.make_branch('source') source.lock_write() self.addCleanup(source.unlock) source.nick = 'my special nick' source.set_submit_branch(':submit:') source.get_config().set_user_option('child_submit_to', 'a@b.com') self.commit_transform(source, self.make_transform(source)) mirror = create_mirror(source, 'target', []) self.assertEqual(source.last_revision(), mirror.last_revision()) self.assertEqual(source.base, mirror.get_public_branch()) self.assertEqual(source.base, mirror.get_parent()) self.assertEqual('my special nick', mirror.nick) self.assertEqual(':submit:', mirror.get_submit_branch()) self.assertEqual('a@b.com', mirror.get_config().get_user_option( 'child_submit_to')) def test_nonexplict_nick(self): source = self.make_branch('source') mirror = create_mirror(source, 'target', []) self.assertEqual('target', mirror.nick) bzrtools/tests/test_patch.py0000644000000000000000000000216312264646316014750 0ustar 00000000000000# Copyright (C) 2008 Aaron Bentley # # # 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from bzrlib.tests import TestCaseInTempDir from bzrlib.plugins.bzrtools import errors from bzrlib.plugins.bzrtools.patch import run_patch class TestPatch(TestCaseInTempDir): def test_missing_patch(self): self.assertRaises(errors.PatchInvokeError, run_patch, '.', [], _patch_cmd='/unlikely/to/exist') bzrtools/tests/upstream_import.py0000644000000000000000000002377512264646316016060 0ustar 00000000000000import os from StringIO import StringIO from shutil import rmtree, copy2, copytree import tarfile import tempfile from unittest import makeSuite from bzrlib import ( osutils, revision as _mod_revision, transform ) from bzrlib.bzrdir import BzrDir from bzrlib.export import export from bzrlib.plugins.bzrtools import errors from bzrlib.plugins.bzrtools.upstream_import import ( common_directory, get_archive_type, import_archive, import_tar, import_zip, import_dir, top_path, ZipFileWrapper, ) from bzrlib.tests import ( TestCaseInTempDir, TestCaseWithTransport, ) try: from bzrlib.tests.features import UnicodeFilenameFeature except ImportError: # bzr < 2.5 from bzrlib.tests import UnicodeFilenameFeature def import_tar_broken(tree, tar_input): """ Import a tarfile with names that that end in //, e.g. Feisty Python 2.5 """ tar_file = tarfile.open('lala', 'r', tar_input) for member in tar_file.members: if member.name.endswith('/'): member.name += '/' import_archive(tree, tar_file) class DirFileWriter(object): def __init__(self, fileobj, mode): # We may be asked to 'append'. If so, fileobj already has a path. # So we copy the existing tree, and overwrite afterward. fileobj.seek(0) existing = fileobj.read() fileobj.seek(0) path = tempfile.mkdtemp(dir=os.getcwd()) if existing != '': # copytree requires the directory not to exist os.rmdir(path) copytree(existing, path) fileobj.write(path) self.root = path def add(self, path): target_path = os.path.join(self.root, path) parent = osutils.dirname(target_path) if not os.path.exists(parent): os.makedirs(parent) kind = osutils.file_kind(path) if kind == 'file': copy2(path, target_path) if kind == 'directory': os.mkdir(target_path) def close(self): pass class TestImport(TestCaseInTempDir): def make_tar(self, mode='w'): def maker(fileobj): return tarfile.open('project-0.1.tar', mode, fileobj) return self.make_archive(maker) def make_archive(self, maker, subdir=True): result = StringIO() archive_file = maker(result) try: os.mkdir('project-0.1') if subdir: prefix='project-0.1/' archive_file.add('project-0.1') else: prefix='' os.chdir('project-0.1') os.mkdir(prefix + 'junk') archive_file.add(prefix + 'junk') f = file(prefix + 'README', 'wb') f.write('What?') f.close() archive_file.add(prefix + 'README') f = file(prefix + 'FEEDME', 'wb') f.write('Hungry!!') f.close() archive_file.add(prefix + 'FEEDME') archive_file.close() finally: if not subdir: os.chdir('..') rmtree('project-0.1') result.seek(0) return result def make_archive2(self, builder, subdir): result = StringIO() archive_file = builder(result) os.mkdir('project-0.2') try: if subdir: prefix='project-0.2/' archive_file.add('project-0.2') else: prefix='' os.chdir('project-0.2') os.mkdir(prefix + 'junk') archive_file.add(prefix + 'junk') f = file(prefix + 'README', 'wb') f.write('Now?') f.close() archive_file.add(prefix + 'README') f = file(prefix + 'README', 'wb') f.write('Wow?') f.close() # Add a second entry for README with different contents. archive_file.add(prefix + 'README') archive_file.close() finally: if not subdir: os.chdir('..') result.seek(0) return result def make_messed_tar(self): result = StringIO() tar_file = tarfile.open('project-0.1.tar', 'w', result) os.mkdir('project-0.1') tar_file.add('project-0.1') os.mkdir('project-0.2') tar_file.add('project-0.2') f = file('project-0.1/README', 'wb') f.write('What?') f.close() tar_file.add('project-0.1/README') tar_file.close() rmtree('project-0.1') result.seek(0) return result def make_zip(self): def maker(fileobj): return ZipFileWrapper(fileobj, 'w') return self.make_archive(maker) def make_tar_with_bzrdir(self): result = StringIO() tar_file = tarfile.open('tar-with-bzrdir.tar', 'w', result) os.mkdir('toplevel-dir') tar_file.add('toplevel-dir') os.mkdir('toplevel-dir/.bzr') tar_file.add('toplevel-dir/.bzr') tar_file.close() rmtree('toplevel-dir') result.seek(0) return result def test_top_path(self): self.assertEqual(top_path('ab/b/c'), 'ab') self.assertEqual(top_path('etc'), 'etc') self.assertEqual(top_path('project-0.1'), 'project-0.1') def test_common_directory(self): self.assertEqual(common_directory(['ab/c/d', 'ab/c/e']), 'ab') self.assertIs(common_directory(['ab/c/d', 'ac/c/e']), None) self.assertEqual('FEEDME', common_directory(['FEEDME'])) def test_untar(self): def builder(fileobj, mode='w'): return tarfile.open('project-0.1.tar', mode, fileobj) self.archive_test(builder, import_tar) def test_broken_tar(self): def builder(fileobj, mode='w'): return tarfile.open('project-0.1.tar', mode, fileobj) self.archive_test(builder, import_tar_broken, subdir=True) def test_unzip(self): def builder(fileobj, mode='w'): return ZipFileWrapper(fileobj, mode) self.archive_test(builder, import_zip) def test_copydir_nosub(self): def builder(fileobj, mode='w'): return DirFileWriter(fileobj, mode) # It would be bogus to test with the result in a subdirectory, # because for directories, the input root is always the output root. self.archive_test(builder, import_dir) def archive_test(self, builder, importer, subdir=False): archive_file = self.make_archive(builder, subdir) tree = BzrDir.create_standalone_workingtree('tree') tree.lock_write() try: importer(tree, archive_file) self.assertTrue(tree.path2id('README') is not None) self.assertTrue(tree.path2id('FEEDME') is not None) self.assertTrue(os.path.isfile(tree.abspath('README'))) self.assertEqual(tree.stored_kind(tree.path2id('README')), 'file') self.assertEqual(tree.stored_kind(tree.path2id('FEEDME')), 'file') f = file(tree.abspath('junk/food'), 'wb') f.write('I like food\n') f.close() archive_file = self.make_archive2(builder, subdir) importer(tree, archive_file) self.assertTrue(tree.path2id('README') is not None) # Ensure the second version of the file is used. self.assertEqual(tree.get_file_text(tree.path2id('README')), 'Wow?') self.assertTrue(not os.path.exists(tree.abspath('FEEDME'))) finally: tree.unlock() def test_untar2(self): tar_file = self.make_messed_tar() tree = BzrDir.create_standalone_workingtree('tree') import_tar(tree, tar_file) self.assertTrue(tree.path2id('project-0.1/README') is not None) def test_untar_gzip(self): tar_file = self.make_tar(mode='w:gz') tree = BzrDir.create_standalone_workingtree('tree') import_tar(tree, tar_file) self.assertTrue(tree.path2id('README') is not None) def test_no_crash_with_bzrdir(self): tar_file = self.make_tar_with_bzrdir() tree = BzrDir.create_standalone_workingtree('tree') import_tar(tree, tar_file) # So long as it did not crash, that should be ok def test_get_archive_type(self): self.assertEqual(('tar', None), get_archive_type('foo.tar')) self.assertEqual(('zip', None), get_archive_type('foo.zip')) self.assertRaises(errors.NotArchiveType, get_archive_type, 'foo.gif') self.assertEqual(('tar', 'gz'), get_archive_type('foo.tar.gz')) self.assertRaises(errors.NotArchiveType, get_archive_type, 'foo.zip.gz') self.assertEqual(('tar', 'gz'), get_archive_type('foo.tgz')) self.assertEqual(('tar', 'lzma'), get_archive_type('foo.tar.lzma')) self.assertEqual(('tar', 'lzma'), get_archive_type('foo.tar.xz')) self.assertEqual(('tar', 'bz2'), get_archive_type('foo.tar.bz2')) class TestWithStuff(TestCaseWithTransport): def transform_to_tar(self, tt): stream = StringIO() export(tt.get_preview_tree(), root='', fileobj=stream, format='tar', dest=None) return stream def get_empty_tt(self): b = self.make_repository('foo') null_tree = b.revision_tree(_mod_revision.NULL_REVISION) tt = transform.TransformPreview(null_tree) root = tt.new_directory('', transform.ROOT_PARENT, 'tree-root') tt.fixup_new_roots() self.addCleanup(tt.finalize) return tt def test_nonascii_paths(self): self.requireFeature(UnicodeFilenameFeature) tt = self.get_empty_tt() encoded_file = tt.new_file( u'\u1234file', tt.root, 'contents', 'new-file') encoded_file = tt.new_file( 'other', tt.root, 'contents', 'other-file') tarfile = self.transform_to_tar(tt) tarfile.seek(0) tree = self.make_branch_and_tree('bar') import_tar(tree, tarfile) self.assertPathExists(u'bar/\u1234file') def test_suite(): return makeSuite(TestImport)