loggerhead-1.19~bzr511/.testr.conf0000644000000000000000000000032313731072134015123 0ustar 00000000000000[DEFAULT] test_command=BRZ_PLUGINS_AT=loggerhead@`pwd` BRZ_PLUGIN_PATH=-site:-user ${BRZ:-brz} selftest -s bp.loggerhead. --subunit2 $IDOPTION $LISTOPT test_id_option=--load-list $IDFILE test_list_option=--list loggerhead-1.19~bzr511/.travis.yml0000644000000000000000000000130513731072134015147 0ustar 00000000000000language: python addons: apt: update: true sudo: false cache: pip git: depth: false matrix: include: - python: 2.7 env: TAL_VERSION=4.3 - python: 3.5 env: TAL_VERSION=5.2 - python: 3.6 env: TAL_VERSION=5.2 script: - python -m coverage run -p -m unittest loggerhead.tests.test_suite install: - sudo apt install subunit adduser libjs-jquery - travis_retry pip install -U setuptools - travis_retry pip install -U pip coverage codecov flake8 testtools configobj cython testscenarios six docutils python-subunit dulwich bzr+lp:brz pygments paste http://www.owlfish.com/software/simpleTAL/downloads/SimpleTAL-$TAL_VERSION.tar.gz bleach after_success: - codecov loggerhead-1.19~bzr511/COPYING.txt0000644000000000000000000004310313731072134014711 0ustar 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 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 Lesser 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser General Public License instead of this License. loggerhead-1.19~bzr511/HACKING.rst0000644000000000000000000000334013731072134014635 0ustar 00000000000000Loggerhead ========== Overview -------- This document attempts to give some hints for people that are wanting to work on Loggerhead. Testing ------- You can run the loggerhead test suite as a bzr plugin. To run just the loggerhead tests:: bzr selftest -s bp.loggerhead Load Testing ------------ As a web service, Loggerhead will often be hit by multiple requests. We want to make sure that loggerhead can scale with many requests, without performing poorly or crashing under the load. There is a command ``bzr load-test-loggerhead`` that can be run to stress loggerhead. A script is given, describing what requests to make, against what URLs, and for what level of parallel activity. Load Testing Multiple Instances ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ One way that Launchpad provides both high availability and performance scaling is by running multiple instances of loggerhead, serving the same content. A proxy is then used to load balance the requests. This also allows us to shut down one instance for upgrading, without interupting service (requests are just routed to the other instance). However, multiple processes poses an even greater risk that caches will conflict. As such, it is useful to test that changes don't introduce coherency issues at load. ``bzr load-test-loggerhead`` can be configured with a script that will make requests against multiple loggerhead instances concurrently. To run multiple instances, it is often sufficient to just spawn multiple servers on different ports. For example:: $ bzr serve --http --port=8080 & $ bzr serve --http --port=8081 & There is a simple example script already in the source tree:: $ bzr load-test-loggerhead load_test_scripts/multiple_instances.script .. vim: ft=rst tw=78 loggerhead-1.19~bzr511/MANIFEST.in0000644000000000000000000000045113731072134014575 0ustar 00000000000000include COPYING.txt include HACKING include NEWS include README.txt include apache-loggerhead.conf include breezy.conf include loggerhead.conf.example include loggerheadd include Makefile recursive-include docs * recursive-include loggerhead/static * recursive-include loggerhead/tests/ *.py *.pt loggerhead-1.19~bzr511/Makefile0000644000000000000000000000022513731072134014476 0ustar 00000000000000 PYTHON ?= python3 dist: $(PYTHON) ./setup.py sdist clean: rm -rf dist/ check: BRZ_PLUGINS_AT=loggerhead@$$(pwd) brz selftest -s bp.loggerhead loggerhead-1.19~bzr511/NEWS0000644000000000000000000004627313731072134013552 0ustar 00000000000000What's changed in loggerhead? ============================= 1.20.0 ---- - Port to Breezy (https://www.breezy-vcs.org/). (Jelmer Vernooij) - Port to Python 3. (Jelmer Vernooij) - Serve-branches has been renamed to 'loggerhead-serve'. (Jelmer Vernooij) - Fixed weird icon in file lists (e.g. revision summaries) when a file or directory with a blank name was listed. (Cruz Bishop, #387337). - Add directory icons for directories in file lists (e.g. revision summaries). Previously they were using the file icon. (Cruz Bishop, #1053340). - Format files under one kilobyte as "x bytes". (Cruz Bishop, #990217) - Make the number of lines of context in embedded diffs on revision pages configurable via a ``context`` query argument. (Paul Nixon) - Add ``loggerhead/middleware`` and a few other files to the sdist. (Toshio Kuratomi) - Widen the line number boxes just a little bit. (Cruz Bishop, #310255) - Use breaking word-wrap on the entire information box. (Cruz Bishop, #258368) - Add some more minor UI changes to do with rounded corners. (Cruz Bishop) - Set line-height on
 elements in file views, fixing misaligned
      line numbers in some browsers. (Colin Watson)

    - Drop dependency on simplejson in favour of the standard library's json
      module in Python >= 2.6. (Colin Watson)

1.18.2 [12Sep2012]
------------------

    - Add ``bzr load-test-loggerhead`` as a way to make sure loggerhead can
      handle concurrent requests, etc. Scripts can be written that spawn
      multiple threads, and issue concurrent requests.
      (John Arbash Meinel)

    - HEAD requests should not return body content. This is done by adding
      another wsgi middleware that strips the body when the REQUEST_METHOD is
      HEAD. Note that you have to add the middleware into your pipeline, and
      it does not decrease the actual work done.
      (John Arbash Meinel, #716201)

    - If we get a HEAD request, there is no reason to expand the template, we
      shouldn't be returning body content anyway.
      (John Arbash Meinel, #716201, #716217)

    - Merge the pqm changes back into trunk, after trunk was reverted to an old
      revision. (John Arbash Meinel, #716152)

    - Redirect ``/files/file.txt`` to ``/view/file.txt`` and ``/view/dir`` to
      ``/files/dir``. (Jasper St. Pierre, #569358)

    - Remove ``start-loggerhead`` and ``stop-loggerhead`` which were already
      deprecated. (John Arbash Meinel)

    - Show "Author(s)" as separate from "Committer". And label them
      correctly. (John Arbash Meinel, #733015)

    - The json module is no longer claimed to be supported as alternative for 
      simplejson. (Jelmer Vernooij, #586611)

    - Viewing the ``/changes`` page only iterates enough of the history to show
      the actual revisions displayed, rather than walking the whole mainline.
      Improves performance on projects with long histories like emacs.
      (John Arbash Meinel)

    - Fix support for displaying foreign revision ids.
      (Jelmer Vernooij, #736026)

    - Add hook 'controller' to BranchWSGIApp, allowing other bzr plugins
      to provide custom branch-specific controllers. (Jelmer Vernooij, #585822)

    - Add privacy notification code to loggerhead, allowing branches to be
      marked as private and a notification ribbon to be displayed via
      javascript on the view pages. (JC Sackett, #823471)

    - Add a script and documentation for running under mod_wsgi.
      (Stuart Colville, Toshio Kuratomi)

    - Make tz calculations consistent and use UTC in the UI everywhere we show
      a precise timestamp. (Robert Collins, #594591)

    - Avoid crashing when viewing, annotating or downloading a
      non-existent file or revision.
      (William Grant, #728209, #929275)

    - Fix diff and view page styling to be more compact and more easily
      copyable. (William Grant, #743295)
    
    - Some small UI changes; extra border at the bottom of file diffs and
      rounded corners in UI. (Cruz Bishop)

    - Updated the Free Software Foundation address in headers. (Toshio
      Kuratomi)

    - Updated css to handle wrapping of long comments.
      (Francesco Del Degan, #276768)

    - Updated formatting of file sizes in file listings. (Cruz Bishop)

    - Added revision number with release info in the page footer, when
      available. (Cruz Bishop, #392668).


1.18.1 [24Mar2011]
------------------

    - Fix escaping of filenames in revision views.
      (William Grant, #740142)

    - Add missing import to loggerhead.trace, allowing start-loggerhead
      to run when a log.roll config option is set.
      (Max Kanat-Alexander, #673999)


1.18 [10Nov2010]
----------------

    - Syntax highlighting is no longer applied for files greater than 512K,
      reducing codebrowse.launchpad.net overloading.
      (Max Kanat-Alexander, #513044)

    - Documentation added in the docs directory. README simplified
      accordingly. (Tres Seaver).

    - Show svn/git/hg revision ids in loggerhead revision view.
      (Jelmer Vernooij)

    - Fix .bzr/smart access to branches in shared repos. (You also need
      a version of bzr with bug #348308 fixed.) (Andrew Bennetts)

    - Support FastCGI, SCGI and AJP using flup. (Denis Martinez)

    - Repository.get_revision_inventory() was removed in bzr 2.2; use
      Repository.get_inventory() instead. (Matt Nordhoff, #528194)

    - Ignore readonly+ prefix when checking if Loggerhead is serving a
      local location. (Reported by Tres Seaver.) (Matt Nordhoff)

    - Set Cache-Control and Expires headers on static pages.
      (John Arbash Meinel)

    - Generate relative links where possible (everywhere but HTTP
      redirects and feed IDs). (Michael Hudson, Matt Nordhoff)

    - Fix bad redirect when visiting "/download" or "/download/".
      (Matt Nordhoff, #247992)

1.17 [20Aug2009]
----------------

    - Add bug links in revision informations (Alexandre Garnier, #314052)

    - Make sure that binary files aren't annotated. (Martin Albisetti,
      #258848)

    - Loggerhead now serves bzr branches over HTTP and exposes the URL
      to branch them. Addresses bug #240577. (Jonathan Lange)

    - Leading blank lines in commit messages no longer result in an
      empty summary. (Colin Watson)

    - Added optional syntax highlighting to annotate view using
      python-pygments.  Partially addresses bug #306631. (Peter Bui)

    - Convert newlines in commit messages to HTML line breaks for
      annotate and changelog views.  Addresses bug #273688. (Peter
      Bui)

    - serve-branches now errors if run behind a proxy without
      paste.deploy installed. (Michael Hudson)

    - Loggerhead should now handle file and directory names that need
      URL escaping without crashing.

    - The start-loggerhead script properly sets the wsgi.url_scheme
      from the server.webpath option. (neror, #260547)

    - The revision page defaults to unified style again, and can
      convert to a side-by-side view using JavaScript. (Michael Hudson)

    - Clean up and improve performance of the annotate view. (Michael
      Hudson)

    - Finish converting JavaScript from MooTools to YUI 3. (Michael
      Hudson)

    - Improve compatibility with IE 6. (Michael Hudson)

    - Leading blank lines in commit messages no longer result in an
      empty summary. (Colin Watson)

    - Clip long lines in side-by-side diff view. (Michael Hudson,
      #334837)

    - The user-confusing "next" and "previous" links now read "older"
      and "newer" respectively. (Michael Hudson, #297930)

    - The annotate view now contains line number anchors. (Michael
      Hudson)

    - Fix inventory pages using "//" in links. (Michael Hudson, #329668)

    - Fix problems viewing files and directories containing spaces and
      other funny characters. (Peter Bui)

    - Changelog messages are now displayed with newlines preserved.
      (Peter Bui, #273688)

    - Offer a link to see the full file diffs for a file path. (Michael
      Hudson, #333797)

    - Fix annotate error caused by Pygments stripping trailing
      whitespace. (Michael Hudson, #338762)

    - Loggerhead can be installed as a Bazaar plugin and run by
      'bzr serve --http'. (Martin Pool)

    - Load parts of the changelog and revision pages via XMLHttpRequest
      to improve performance. This adds a dependency on simplejson or
      json. Partially addresses bug #253950. (Michael Hudson)

    - Various improvements to the animation JavaScript. (Michael Hudson)

    - Fix HTML content of source files being displayed unescaped when
      Pygments was unavailable. (Michael Hudson, #344970)

    - Fix serve-branches's path argument. (Michael Hudson, #353230)

    - serve-branches now has an option, --use-cdn, to load YUI from
      Yahoo!'s CDN. (Matt Nordhoff)

    - Fix certain race conditions for loading bzr-search. (Robert
      Collins, #334250)

    - Fix errors when using serve-branches --log-folder or --user-dirs.
      (It was calling config.get_option() incorrectly.) (Matt Nordhoff,
      bug #361238)

    - Move some caching from RAM to the disk, and other caching and
      memory usage improvements. (Michael Hudson)

    - Add a --cache-dir option to serve-branches to choose where to
      place the SQL cache, and only create one temporary SQL dir per
      process. (Matt Nordhoff, #358322)

    - Replace homebrew memory profiling code with Dozer. (Paul Hummer)

    - Use the branch's public_branch as the default suggested URL to
      branch from (Matt Nordhoff, #369767)

    - Fix a file descriptor leak (Matt Nordhoff, #370845)

    - Use transport API internally, so it is possible to specify a remote
      URL to serve-branches. (Jelmer Vernooij, #371787)

    - Fix internal server errors when using start-loggerhead. (Matt
      Nordhoff, #375948)

    - Fix annotating non-UTF-8 files when Pygments is disabled. (Matt
      Nordhoff, #376957)

    - Fix 'bzr serve --http' errors. (Matt Nordhoff, #377551)

    - Added the option to hide branches by setting http_serve = False
      in locations.conf (Martin Albisetti)

    - Fix serving branches over HTTP. (Matt Nordhoff, Jelmer Vernooij,
      #380026)

    - Install loggerhead as a bzr plugin by default (Jelmer Vernooij)

    - Fix logging 404 Not Found responses (Matt Nordhoff, #381029)

    - Bumped minimunm bzrlib version to 1.13 (Martin Albisetti)

    - Make sure the Atom feeds (nearly) validate. (Matt Nordhoff, #247162)

    - Support serving branches over HTTP using the smart server protocol.
      (Jelmer Vernooij, #306853)

    - Serving branch data was broken when --allow-writes was *not*
      passed. (Michael Hudson, #388730)

    - http_serve config values are interpreted more forgivingly.
      (Michael Hudson)

    - When specifying a remote url to serve-branches, do not share
      connections between threads. (Michael Hudson, #390972)

    - http_serve values from locations.conf are now applied to
      non-branch .bzr data (e.g shared repositories). (Michael Hudson)

    - tags are now displayed. (Cris Boylan, Alexandre Garnier, Michael
      Hudson, #246739)

    - Display Loggerhead's version number at the bottom of the page, and
      add a  generator tag also including the version numbers of
      its dependencies. (Matt Nordhoff, #370155)


1.10 [22Dec2008]
----------------

    - Add startup deamon script for Linux (Marius Kruger)

    - Switch navigation from file_ids to paths. Fixes bugs #260363,
      #269365 and #128926. (Martin Albisetti)

    - Fix bug #258710 ("the /files page explodes in an empty branch").
      Also minor improvements to the /files and /changes pages.
      (Marius Kruger)

    - Added --port, --host and --prefix options to serve-branches
      script. (Martin Albisetti)

    - Fixed broken template for project browsing with start-loggerhead
      (Martin Albisetti)

    - Added --reload options to restart the application when a python
      file change. (Guillermo Gonzalez)

    - Added error handling middleware. (Guillermo Gonzalez)

    - Fix bug #243415 ("Tracebacks go to console but not log
      file"). Also minor improvements to logging in serve-branches and
      start-loggerhead. (Guillermo Gonzalez)

1.6 [15Aug2008]
----------------

    - Download a diffs between revisions. (Martin Albisetti)

    - Modified templates to make loggerhead's theme easier to
      change. (Paul Hummer)

    - Default sqlite interface is now sqlite3. (Robert Collins)

    - New ninja theme sponsored by Canonical (Martin Albisetti)

    - Added COPYING file and clarified copyright headers (John Arbash Meinel)

    - Remove the .py extension requiered by the Debian Policy.
      (Jelmer Vernooij)

    - New startup script serve-branches will serve Loggerhead without
      the need of configuration, and allow you to browse through directories
      and branches. (Michael Hudson)

    - Loggerhead is no longer a TurboGears application, but rather a
      WSGI application built using Paste (see http://wsgi.org/ and
      http://pythonpaste.org/ for more about WSGI and Paste).

    - URLs now use revision numbers instead of revision ids (Martin Albisetti)

    - The scripts no longer insist on Python 2.4 -- loggerhead works
      fine with 2.5.

    - Bazaar as of version 1.5 has improved in performance enough that
      the revision cache no longer gave any noticeable benefit, so it
      was removed (the files-changed cache is still useful).

    - The templates were rewritten in Zope's TAL markup, powered by
      the simpleTAL library -- improving both the performance and
      memory consumption of rendering by a factor of around 3 for
      large pages over the old Kid templates.

    - Loggerhead's poorly performing text index was disabled. bzr-search
      is now used if the plugin is installed and a text index is present
      on the branch being viewed. (Martin Albisetti, Robert Collins).

    - Loggerhead no longer depends on bzrlib functions deprecated in
      Bazaar 1.5 and removed in 1.6 (Martin Albisetti).

    - The daemonization code was made more regular, fixing bugs
      #139161 ("Starting loggerhead process may not close its stdin
      and stdout properly") and #211526 ("Codebrowse log directory has
      unnecessarily permissive permissions")

    - Some confusion about what the 'file_id' query argument means was
      cleared up: filter_file_id now means "filter revisions to those
      that affect this file" in all views and file_id means "examine
      this file/directory" in the annotate and inventory views.

    - Dates are present more compactly.

    - The loggerhead.conf file can specify which network interface to
      bind to (Mattias Eriksson)

1.2.1  [06mar2008]
------------------

    - The changelog view was out of order when not using the revision
      cache.

1.2  [04mar2008]

    - Michael Hudson  has mostly taken
      over the maintenance of loggerhead.

    - loggerhead now has a simple test suite -- use 'nosetests' in the
      loggerhead directory to run it.

    - The rendering performance of pages that display large amounts of
      text was improved by a factor of 4 or so.

    - loggerhead no longer caches the text of the diffs between
      revisions.  It rarely helped and wasted a lot of disk space.

    - The layout of most pages was made more "tight" to waste less
      screen real estate, and some other UI improvements (Kent
      Gibson).

    - Much dead code was removed.

    - Loggerhead now computes the files changed between revisions only
      when it needs to know this.  This is a fairly expensive
      operation in Bazaar, and doing it less massively improves
      performance and memory usage in some situations.

    - Loggerhead now takes a read lock on the branch being viewed for
      the duration of each request, which improves performance
      significantly.

    - Loggerhead no longer uses the corruption-prone Berkely DB-backed
      shelve module for its caches, and rather (ab)uses a sqlite
      database instead.

    - The creation of History objects is much faster for large
      branches (Aaron Bentley).

    - Allow the construction of URLs using revnos and file paths as
      well as revids and fileids (bug #98826):

      - For changes pages, append the newest revno to display to the
        URL, like http://.../changes/

      - For annotate pages, append the revno to display to the URL,
        followed by the path, like http://.../annotate//

      - For file listing and revision pages append the revno to
        display to the URL, like http://.../files/

      Loggerhead still generates URLs using revision and file ids for
      the moment.

    - Many bugs were fixed:

      - Loggerhead does not escape special characters when embedding a
        revision ID in a URL (bug #88286)

      - Improved robustness in the face of ghosts.

      - Don't crash on displaying a commit with an empty message (bug
        #86247)

      - codebrowse fails with infinite redirections (James Henstridge,
        bug #89854)

      - Loggerhead fails to browse revisions that change binary files
        (James Henstridge, bug #91686)

      - Loggerhead atom feeds expose internal hostname (James
        Henstridge, bug #93585)

      - loggerhead don't like page break character (0x0C) in text
        files (bug #113313)

      - codebrowse source listings don't contain line number anchors
        (bug #98826)

      - only serve up unescaped user content with "Content-Disposition:
        attachment"

      - viewing the file listing consumes a lot memory (bug #116869)

      - loggerhead can't handle empty branches (bug #119228)

      - upgrading the format of a branch behind loggerhead could make
        it fail for that branch (bug #118673)

      - Error parsing non-ascii content (bug #117799)

      - Loggerhead failed on whitespace-only commit messages.

      - Links to diffs from within a revision do not work (bug
        #119422)

      - UTF-8 patches served as ISO-8859-1, fixed by served bundles as
        application/octet-stream (bug #121336)

      - TurboGears was turning query arguments into unicode, and bzr
        stopped accepting unicode revids (bug #175228)


1.1.1  [24jan2007]
------------------

    - fix broken inventory page (oops!)

    - fix a few rendering problems with kid and safari


1.1  [20jan2007]
----------------

    - new feature to compare two revisions to each other

    - inserted text in diffs is now blue instead of green

    - fixed to start and stop as a daemon now (use "-f" to run in the
      foreground), and all config is in loggerhead.conf (no need to mess with
      dev.cfg)

    - renamed show/hide javascript buttons to expand/collapse, and made them
      much faster

    - added an atom-feed link to each branch on the browse page [elliot
      murphy]

    - auto-publish feature for multiple branches under a single folder (see
      loggerhead.conf.example)

    - added the ability to share cache files per-project instead of just
      per-branch

    - added side-by-side diff display for the revision page (which is the
      default), with a button to switch between side-by-side and unified diff
      format

    - made caching use file locking, and close cleanly on shutdown

    - miscellaneous speed and page-size improvements


1.0  [23dec2006]
----------------

    - initial release

loggerhead-1.19~bzr511/README.rst0000644000000000000000000000112313731072134014523 0ustar  00000000000000Loggerhead
==========

Overview
--------

Loggerhead is a web viewer for Breezy. Its lets users do stuff like:

* navigate through branch history
* view the files in a given revision
* annotate files (i.e. find out the origin of each line of a file)
* perform searches.


Documentation
-------------

See docs/index.rst for installation instructions and basic
documentation.


Licensing
---------

This software is licensed under the GPL Version 2 or later.
Please see the file COPYING.txt for the licence details.

 (C) Copyright Canonical Limited 2006-10
 (C) Copyright Breezy Developers 2018
loggerhead-1.19~bzr511/__init__.py0000644000000000000000000001070313731072134015151 0ustar  00000000000000# Copyright 2009, 2010, 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335  USA


# This file allows loggerhead to be treated as a plugin for bzr.
#
# XXX: Because loggerhead already contains a loggerhead directory, much of the
# code is going to appear loaded at breezy.plugins.loggerhead.loggerhead.
# This seems like the easiest thing, because breezy wants the top-level plugin
# directory to be the module, but when it's used as a library people expect
# the source directory to contain a directory called loggerhead.  -- mbp
# 20090123

"""Loggerhead web viewer for Bazaar branches.

This provides a new option "--http" to the "bzr serve" command, that
starts a web server to browse the contents of a branch.
"""

import sys

version_info = (1, 20, 0) # Keep in sync with loggerhead/__init__.py

import breezy
from breezy import commands

from breezy.transport import transport_server_registry

DEFAULT_HOST = '0.0.0.0'
DEFAULT_PORT = 8080
HELP = ('Loggerhead, a web-based code viewer and server. (default port: %d)' %
        (DEFAULT_PORT,))


def serve_http(transport, host=None, port=None, inet=None, client_timeout=None):
    # TODO: if we supported inet to pass requests in and respond to them,
    #       then it would be easier to test the full stack, but it probably
    #       means routing around paste.httpserver.serve which probably
    #       isn't testing the full stack
    from paste.httpexceptions import HTTPExceptionHandler
    from paste.httpserver import serve

    try:
        from .loggerhead.apps.http_head import HeadMiddleware
        from .loggerhead.apps.transport import BranchesFromTransportRoot
        from .loggerhead.config import LoggerheadConfig
        from .loggerhead.main import setup_logging
    except ImportError:
        from loggerhead.apps.http_head import HeadMiddleware
        from loggerhead.apps.transport import BranchesFromTransportRoot
        from loggerhead.config import LoggerheadConfig
        from loggerhead.main import setup_logging

    if host is None:
        host = DEFAULT_HOST
    if port is None:
        port = DEFAULT_PORT
    argv = ['--host', host, '--port', str(port), '--', transport.base]
    if not transport.is_readonly():
        argv.insert(0, '--allow-writes')
    config = LoggerheadConfig(argv)
    setup_logging(config, init_logging=False, log_file=sys.stderr)
    app = BranchesFromTransportRoot(transport.base, config)
    # Bug #758618, HeadMiddleware seems to break HTTPExceptionHandler from
    # actually sending appropriate return codes to the client. Since nobody
    # desperately needs HeadMiddleware right now, just ignoring it.
    # app = HeadMiddleware(app)
    app = HTTPExceptionHandler(app)
    serve(app, host=host, port=port)

transport_server_registry.register('http', serve_http, help=HELP)

class cmd_load_test_loggerhead(commands.Command):
    """Run a load test against a live loggerhead instance.

    Pass in the name of a script file to run. See loggerhead/load_test.py
    for a description of the file format.
    """

    hidden = True
    takes_args = ["filename"]

    def run(self, filename):
        try:
            from .loggerhead.loggerhead import load_test
        except ImportError:
            from loggerhead.loggerhead import load_test
        script = load_test.run_script(filename)
        for thread_id in sorted(script._threads):
            worker = script._threads[thread_id][0]
            for url, success, time in worker.stats:
                self.outf.write(' %5.3fs %s %s\n'
                                % (time, str(success)[0], url))

commands.register_command(cmd_load_test_loggerhead)

def load_tests(loader, basic_tests, pattern):
    try:
        from .loggerhead.tests import test_suite
    except ImportError:
        from loggerhead.tests import test_suite
    basic_tests.addTest(test_suite())
    return basic_tests
loggerhead-1.19~bzr511/apache-loggerhead.conf0000644000000000000000000000107013731072134017224 0ustar  00000000000000### If you receive MemoryError tracebacks setting up loggerhead under mod_wsgi,
### read /usr/share/doc/loggerhead*/README for help
Alias /bzr/static /usr/share/loggerhead/static
RewriteEngine On
RewriteRule ^/bzr$ /bzr/ [R]

WSGIDaemonProcess loggerhead user=apache group=apache maximum-requests=1000 display-name=loggerhead processes=4 threads=1
WSGISocketPrefix run/wsgi
WSGIRestrictStdout On
WSGIRestrictSignal Off

WSGIScriptAlias /bzr /usr/bin/loggerhead.wsgi


    WSGIProcessGroup loggerhead
    Order deny,allow
    Allow from all


loggerhead-1.19~bzr511/breezy.conf0000644000000000000000000000043413731072134015207 0ustar  00000000000000# directory to serve bzr branches from
# Non-bzr directories under this path will also be visible in loggerhead
#http_root_dir = '/var/www/bzr'

# The url prefix for the bzr branches.
http_user_prefix = '/bzr'

# Directory to put cache files in
http_sql_dir = '/var/cache/loggerhead'
loggerhead-1.19~bzr511/byov.conf0000644000000000000000000000405013731072134014664 0ustar  00000000000000# Use lxd containers by default
vm.class = lxd
# Start with an up to date system by default
vm.update = True
# External sources dependencies, packages are not recent enough
dulwich.clone = (git clone git://jelmer.uk/dulwich ../dulwich.git)
dulwich.install = (cd ../dulwich.git && ./setup.py install --user)
dulwich3.install = (cd ../dulwich.git && python3 ./setup.py install --user)
simpletal3.install = pip3 install http://www.owlfish.com/software/simpleTAL/downloads/SimpleTAL-5.2.tar.gz

[loggerhead]
vm.release = xenial
brz.build_deps = gcc, debhelper, python, python-all-dev, python3-all-dev, python-configobj, python3-configobj, python-docutils, python3-docutils, python-paramiko, python3-paramiko, python-subunit, python3-subunit, python-testtools, python3-testtools, subunit, cython, cython3, python-fastimport, python-dulwich, python-six, python3-six
loggerhead.build_deps = python-setuptools, python3-setuptools, libjs-jquery, python-docutils, python3-docutils, python-pygments, python3-pygments, python-paste, python3-paste, python-pastedeploy, python3-pastedeploy, python-simpletal, python-bleach, python3-bleach
loggerhead.test_deps = python3-fixtures, python-fixtures
vm.packages = {brz.build_deps}, {loggerhead.build_deps}, {loggerhead.test_deps}, bzr, python-junitxml, python3-pip
brz.branch = (bzr branch lp:brz ../brz-trunk)
brz.make = (cd ../brz-trunk && make)
byoci.setup.command = ({dulwich.clone} && {dulwich.install} && {brz.branch} && {brz.make})
byoci.tests.command = bash -o pipefail -c "bzr log -l2 && (BRZ_PLUGINS_AT=loggerhead@`pwd` BRZ_PLUGIN_PATH=-site:-user python2 ../brz-trunk/brz selftest -v --parallel=fork --subunit2 | subunit2junitxml -o ../results.xml -f | subunit2pyunit)"
[loggerhead-py3]
byoci.setup.command = ({dulwich.clone} && {dulwich3.install} && {brz.branch} && {brz.make} && {simpletal3.install})
byoci.tests.command = bash -o pipefail -c "bzr log -l2 && (BRZ_PLUGINS_AT=loggerhead@`pwd` BRZ_PLUGIN_PATH=-site:-user python3 ../brz-trunk/brz selftest -v --parallel=fork --subunit2 | subunit2junitxml -o ../results.xml -f | subunit2pyunit)"
loggerhead-1.19~bzr511/docs/0000755000000000000000000000000013731072134013767 5ustar  00000000000000loggerhead-1.19~bzr511/load_test_scripts/0000755000000000000000000000000013731072134016564 5ustar  00000000000000loggerhead-1.19~bzr511/loggerhead/0000755000000000000000000000000013731072134015140 5ustar  00000000000000loggerhead-1.19~bzr511/loggerhead-serve0000755000000000000000000000165213731072134016214 0ustar  00000000000000#!/usr/bin/env python3
#
# Copyright (C) 2008, 2009 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, Suite 500, Boston, MA 02110-1335  USA

"""Search for branches underneath a directory and serve them all."""

import sys

from loggerhead.main import main


if __name__ == "__main__":
    main(sys.argv[1:])
loggerhead-1.19~bzr511/loggerhead-serve.10000644000000000000000000000231013731072134016340 0ustar  00000000000000.TH LOGGERHEAD "1" "August 2008" "loggerhead 1.6" "User Commands"
.SH NAME
loggerhead \- run a loggerhead server
.SH SYNOPSIS
.B loggerhead-serve
[\fIoptions\fR] \fI\fR
.SH DESCRIPTION
Loggerhead is a web viewer for projects in Breezy. It can be used to navigate 
a branch history, annotate files, view patches, perform searches, etc. It's 
heavily based on bazaar-webserve, which is itself based on hgweb for Mercurial.
.PP
The loggerhead-serve command runs a standalone loggerhead server in the foreground.
.SH OPTIONS
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.TP
\fB\-\-user\-dirs\fR
Serve user directories as ~user.
.TP
\fB\-\-trunk\-dir\fR=\fIDIR\fR
The directory that contains the trunk branches.
.TP
\fB\-\-port\fR
Port Loggerhead should listen on (defaults to 8080).
.TP
\fB\-\-host\fR
Host Loggerhead should listen on.
.TP
\fB\-\-prefix\fR
Specify host prefix.
.TP
\fB\-\-reload\fR
Restarts the application when changing python files. Only used for development purposes.
.TP
\fB\-\-log\-folder\fR=\fILOG_FOLDER\fR
The directory to place log files
.TP
\fB\-\-version\fR
Print the software version and exit.
.SH "LICENSE"
GNU GPLv2 or later.
.SH "SEE ALSO"
https://launchpad.net/loggerhead
loggerhead-1.19~bzr511/loggerhead.wsgi0000644000000000000000000000416713731072134016043 0ustar  00000000000000#!/usr/bin/python3 -tt
# 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, Suite 500, Boston, MA 02110-1335  USA


import sys
import os
import pwd
sys.path.insert(0, os.path.dirname(__file__))

from paste.httpexceptions import HTTPExceptionHandler
from loggerhead.apps.transport import BranchesFromTransportRoot
from loggerhead.apps.error import ErrorHandlerApp
from loggerhead.config import LoggerheadConfig
from breezy import config as bzrconfig
from breezy.sixish import PY3
from paste.deploy.config import PrefixMiddleware
from breezy.plugin import load_plugins

class NotConfiguredError(Exception):
    pass


load_plugins()
config = LoggerheadConfig()
prefix = config.get_option('user_prefix') or ''
# Note we could use LoggerheadConfig here if it didn't fail when a
# config option is not also a commandline option
root_dir = os.getenv('LOGGERHEAD_ROOT_DIR')
if not root_dir:
    root_dir = bzrconfig.GlobalConfig().get_user_option('http_root_dir')
if not root_dir:
    raise NotConfiguredError('You must set LOGGERHEAD_ROOT_DIR or have '
            'a ~/.config/breezy/breezy.conf file for'
            ' %(user)s with http_root_dir set to the base directory you want'
            ' to serve bazaar repositories from' %
            {'user': pwd.getpwuid(os.geteuid()).pw_name})
if not PY3:
    prefix = prefix.encode('utf-8', 'ignore')
    root_dir = root_dir.encode('utf-8', 'ignore')
app = BranchesFromTransportRoot(root_dir, config)
app = PrefixMiddleware(app, prefix=prefix)
app = HTTPExceptionHandler(app)
application = ErrorHandlerApp(app)
loggerhead-1.19~bzr511/loggerheadd0000755000000000000000000000465013731072134015237 0ustar  00000000000000#!/bin/sh
### BEGIN INIT INFO
# Required-Start:       $local_fs $remote_fs $network
# Default-Start:        3 5
# Default-Stop:         0 1 2 6
# Short-Description:    Loggerhead
# Description:          Manage Loggerhead (a web viewer for projects in bazaar)
### END INIT INFO


#
# Configure this please:
# (Please stop loggerhead before changing the configuration, otherwise this
#   script might not be able to kill loggerhead)
#

LHUSER=loggerhead

if [ `whoami` = "$LHUSER" ]; then
    SUDO=""
else
    SUDO="sudo -H -u $LHUSER"
fi

# If loggerhead-serve is not in your path, you will need to specify the full path:
SERVE_BRANCHES_CMD=loggerhead-serve

LOG_FOLDER=/var/log/loggerhead
LOG_FILE=$LOG_FOLDER/loggerheadd.log
URL_PREFIX=/loggerhead
PORT=8080

#please specify the base directory to serve:
BZRROOT=/bzrroot

# You can add additional options to loggerhead-serve here:
START_CMD="$SERVE_BRANCHES_CMD --prefix=$URL_PREFIX --log-folder=$LOG_FOLDER --port=$PORT $BZRROOT"


#
# main part
#

loggerhead_process(){
    $SUDO pgrep -fl "$START_CMD"
}

loggerhead_status(){
    proccess=`loggerhead_process`
    #echo "$proccess"
    listening=`netstat -nl |grep -e ":$PORT "`
    #echo "$listening"
    if [ -z "$proccess" ]; then
        echo "Loggerhead is *not* running."
    else
        echo "Loggerhead is running."
        if [ -z "$listening" ]; then
            echo "This server is *not* listening on port $PORT."
        else
            echo "This server is listening on port $PORT."
        fi
    fi
}

start_loggerhead(){
    echo "Starting loggerhead.   (See $LOG_FOLDER for details.)"

    # make sure the log folder is created
    if [ ! -d $LOG_FOLDER ]
    then
        $SUDO mkdir -p $LOG_FOLDER
    fi
    echo "" > $LOG_FILE
    $SUDO python3 $START_CMD > $LOG_FILE 2>&1 &

    #wait a little while of some logging to appear
    log=""
    for i in $(seq 1 3 30); do
        log=`cat $LOG_FILE`
        if [ -n "$log" ]; then
            break
        fi
        sleep 0.3
    done
    tail $LOG_FILE
    loggerhead_status
}

stop_loggerhead(){
    echo "Stopping loggerhead."
    $SUDO pkill -f "$START_CMD"
    loggerhead_status
}

case "$1" in
    start)
        start_loggerhead
    ;;
    stop)
        stop_loggerhead
    ;;
    status)
        loggerhead_status
    ;;
    restart)
        stop_loggerhead
        start_loggerhead
    ;;
    *)
        echo "Usage: loggerheadd { start | stop | status | restart }"
        exit 1
esac
loggerhead-1.19~bzr511/requirements.txt0000644000000000000000000000045613731072134016330 0ustar  00000000000000Paste >= 1.6
dulwich == 0.19.16; python_version < "3"
dulwich; python_version >= "3"
testtools
breezy
bleach
https://www.owlfish.com/software/simpleTAL/downloads/SimpleTAL-4.3.tar.gz; python_version < "3"
https://www.owlfish.com/software/simpleTAL/downloads/SimpleTAL-5.2.tar.gz; python_version >= "3"
loggerhead-1.19~bzr511/setup.py0000755000000000000000000000434213731072134014557 0ustar  00000000000000#!/usr/bin/env python3
#
# Copyright (C) 2008  Canonical Ltd.
#                     (Authored by Martin Albisetti )
# 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

"""Loggerhead is a web viewer for projects in bazaar"""

from setuptools import setup

import loggerhead


with open("README.rst") as readme:
    long_description = readme.read()


setup(
    name = "loggerhead",
    version = loggerhead.__version__,
    description = "Loggerhead is a web viewer for projects in bazaar",
    long_description=long_description,
    long_description_content_type="text/x-rst",
    license = "GNU GPL v2 or later",
    maintainer = "Michael Hudson",
    maintainer_email = "michael.hudson@canonical.com",
    scripts = [
        "loggerhead-serve",
        ],
    packages = ["loggerhead",
                "loggerhead/apps",
                "loggerhead/controllers",
                "loggerhead/middleware",
                "loggerhead/templates",
                "breezy.plugins.loggerhead"],
    package_dir={'breezy.plugins.loggerhead':'.'},
    package_data = {"loggerhead": ["templates/*.pt",
                                   "static/css/*.css",
                                   "static/javascript/*.js",
                                   "static/images/*"]},
    data_files = [
        ('share/man/man1', ['loggerhead-serve.1']),
        ('share/doc/loggerhead', ['apache-loggerhead.conf',
                                  'loggerheadd',
                                  'breezy.conf']),
        ],
    install_requires=['paste', 'bleach'],
    testsuite='loggerhead.tests.test_suite',
    )
loggerhead-1.19~bzr511/tox.ini0000644000000000000000000000031413731072134014350 0ustar  00000000000000[tox]
envlist = py27,py35,py36,py37,py38
skipsdist=True

[testenv]
deps = -rrequirements.txt
commands = brz selftest -v breezy.plugins.loggerhead --strict
setenv = BRZ_PLUGINS_AT = loggerhead@{toxinidir}
loggerhead-1.19~bzr511/docs/Makefile0000644000000000000000000000607613731072134015440 0ustar  00000000000000# Makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
PAPER         =
BUILDDIR      = _build

# Internal variables.
PAPEROPT_a4     = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest

help:
	@echo "Please use \`make ' where  is one of"
	@echo "  html      to make standalone HTML files"
	@echo "  dirhtml   to make HTML files named index.html in directories"
	@echo "  pickle    to make pickle files"
	@echo "  json      to make JSON files"
	@echo "  htmlhelp  to make HTML files and a HTML help project"
	@echo "  qthelp    to make HTML files and a qthelp project"
	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
	@echo "  changes   to make an overview of all changed/added/deprecated items"
	@echo "  linkcheck to check all external links for integrity"
	@echo "  doctest   to run all doctests embedded in the documentation (if enabled)"

clean:
	-rm -rf $(BUILDDIR)/*

html:
	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

dirhtml:
	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
	@echo
	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."

pickle:
	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
	@echo
	@echo "Build finished; now you can process the pickle files."

json:
	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
	@echo
	@echo "Build finished; now you can process the JSON files."

htmlhelp:
	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
	@echo
	@echo "Build finished; now you can run HTML Help Workshop with the" \
	      ".hhp project file in $(BUILDDIR)/htmlhelp."

qthelp:
	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
	@echo
	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/loggerhead.qhcp"
	@echo "To view the help file:"
	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/loggerhead.qhc"

latex:
	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
	@echo
	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
	      "run these through (pdf)latex."

changes:
	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
	@echo
	@echo "The overview file is in $(BUILDDIR)/changes."

linkcheck:
	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
	@echo
	@echo "Link check complete; look for any errors in the above output " \
	      "or in $(BUILDDIR)/linkcheck/output.txt."

doctest:
	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
	@echo "Testing of doctests in the sources finished, look at the " \
	      "results in $(BUILDDIR)/doctest/output.txt."
loggerhead-1.19~bzr511/docs/_build/0000755000000000000000000000000013731072134015225 5ustar  00000000000000loggerhead-1.19~bzr511/docs/_static/0000755000000000000000000000000013731072134015415 5ustar  00000000000000loggerhead-1.19~bzr511/docs/_templates/0000755000000000000000000000000013731072134016124 5ustar  00000000000000loggerhead-1.19~bzr511/docs/conf.py0000644000000000000000000001441513731072134015273 0ustar  00000000000000# -*- coding: utf-8 -*-
#
# loggerhead documentation build configuration file, created by
# sphinx-quickstart on Tue Mar 23 10:49:50 2010.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys, os

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.append(os.path.abspath('.'))

# -- General configuration -----------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = []

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'Loggerhead'
copyright = u'2010, Loggerhead team (https://launchpad.net/~loggerhead-team)'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.17'
# The full version, including alpha/beta/rc tags.
release = '1.17'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of documents that shouldn't be included in the build.
#unused_docs = []

# List of directories, relative to source directory, that shouldn't be searched
# for source files.
exclude_trees = ['_build']

# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []


# -- Options for HTML output ---------------------------------------------------

# The theme to use for HTML and HTML Help pages.  Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'default'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# " v documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_use_modindex = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a  tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = ''

# Output file base name for HTML help builder.
htmlhelp_basename = 'loggerheaddoc'


# -- Options for LaTeX output --------------------------------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
  ('index', 'loggerhead.tex', u'Loggerhead Documentation',
   u'Loggerhead team (https://launchpad.net/\\textasciitilde{}loggerhead-team)', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# Additional stuff for the LaTeX preamble.
#latex_preamble = ''

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_use_modindex = True
loggerhead-1.19~bzr511/docs/index.rst0000644000000000000000000001667013731072134015642 0ustar  00000000000000Loggerhead:  A web viewer for ``bzr`` branches
==============================================

Loggerhead is a web viewer for projects in Breezy. It can be used to navigate
a branch history, annotate files, view patches, perform searches, etc.
Loggerhead is heavily based on `bazaar-webserve
`_, which was, in turn, loosely
based on `hgweb `_.


Getting Started
---------------

Loggerhead depends on the following Python libraries.:

- SimpleTAL for templating.

- Paste for the server. (You need version 1.2 or newer of Paste).

- Paste Deploy  (optional, needed when proxying through Apache).

- flup (optional, needed to use FastCGI, SCGI or AJP).


Installing Dependencies Using Ubuntu Packages
#############################################

.. code-block:: sh

   $ sudo apt-get install python-simpletal
   $ sudo apt-get install python-paste
   $ sudo apt-get install python-pastedeploy
   $ sudo apt-get install python-flup

Installing Dependencies Using :command:`easy_install`
#####################################################

.. code-block:: sh

   $ easy_install \
     -f http://www.owlfish.com/software/simpleTAL/py2compatible/download.html \
     SimpleTAL
   $ easy_install Paste
   $ easy_install PasteDeploy
   $ easy_install flup


Running the Standalone Loggerhead Server
----------------------------------------

After installing all the dependencies, you should be able to run
:command:`loggerhead-serve` with the branch you want to serve on the
command line:

.. code-block:: sh

    ./loggerhead-serve ~/path/to/branch

By default, the script listens on port 8080, so head to
http://localhost:8080/ in your browser to see the branch.

You can also pass a directory that contains branches to the script,
and it will serve a very simple directory listing at other pages.

You may update the Bazaar branches being viewed at any time.
Loggerhead will notice and refresh, and Bazaar uses its own branch
locking to prevent corruption.

See :doc:`loggerhead-serve` for all command line options.

Running Loggerhead as a Daemon
------------------------------

To run Loggerhead as a linux daemon:

1) Copy the ``loggerheadd`` scipt to ``/etc/init.d``

.. code-block:: sh

   $ sudo cp ./loggerheadd /etc/init.d

2) Edit the file to configure where your Loggerhead is installed, and which
   loggerhead-serve options you would like.

.. code-block:: sh

   $ sudo vim /etc/init.d/loggerheadd

3) Register the service

.. code-block:: sh

   # on upstart based systems like Ubuntu run: 
   $ sudo update-rc.d loggerheadd defaults

   # on Sysvinit based systems like Centos or SuSE run:
   $ sudo chkconfig --add loggerheadd


Using Loggerhead as a Breezy Plugin
-----------------------------------

This branch contains experimental support for using Loggerhead as a Breezy
plugin.  To use it, place the top-level Loggerhead directory (the one
containing COPYING.txt) at ``~/.config/breezy/plugins/loggerhead``.  E.g.:

.. code-block:: sh

   $ bzr branch lp:loggerhead ~/.config/breezy/plugins/loggerhead
   $ cd ~/myproject
   $ bzr serve --http


Using a Config File
-------------------

To hide branches from being displayed, add to ``~/.config/breezy/locations.conf``,
under the branch's section:

.. code-block:: ini

    [/path/to/branch]
    http_serve = False

More configuration options to come soon.


Serving Loggerhead behind Apache
--------------------------------

If you want to view Breezy branches from your existing Apache
installation, you'll need to configure Apache to proxy certain
requests to Loggerhead.  Adding lines like this to your Apache
configuration is one way to do this:

.. code-block:: apache

    
        ProxyPass http://127.0.0.1:8080/branches/
        ProxyPassReverse http://127.0.0.1:8080/branches/
    

If Paste Deploy is installed, the :command:`loggerhead-serve` script can be
run behind a proxy at the root of a site, but if you're running it at
some path into the site, you'll need to specify it using
``--prefix=/some_path``.

Serving Loggerhead with mod_wsgi
--------------------------------

A second method for using Loggerhead with apache is to have apache itself
execute Loggerhead via mod_wsgi.  You need to add configuration for apache and
for breezy to make this work.  Example config files are in the Loggerhead doc
directory as apache-loggerhead.conf and breezy.conf.  You can copy them into
place and use them as a starting point following these directions:

1) Install mod_wsgi.  On Ubuntu and other Debian derived distros::

    sudo apt-get install libapache2-mod-wsgi

   On Fedora-derived distros::

    su -c yum install mod_wsgi

2) Copy the breezy.conf file where apache will find it (May be done for you if
   you installed Loggerhead from a distribution package)::

    # install -d -o apache -g apache -m 0755 /etc/loggerhead
    # cp -p /usr/share/doc/loggerhead*/breezy.conf /etc/loggerhead/
    # mkdir -p /var/www/.config
    # ln -s /etc/loggerhead /var/www/.config/breezy

3) Create the cache directory (May be done for you if you installed Loggerhead
   from a distribution package)::

    # install -d -o apache -g apache -m 0700 /var/cache/loggerhead/

4) Edit /etc/loggerhead/breezy.conf.  You need to set http_root_dir to the filesystem
   path that you will find your bzr branches under.  Note that normal
   directories under that path will also be visible in Loggerhead.

5) Install the apache conf file::

     # cp -p /usr/share/doc/loggerhead*/apache-loggerhead.conf /etc/httpd/conf.d/loggerhead.conf

6) Edit /etc/httpd/conf.d/loggerhead.conf to point to the url you desire to
   serve Loggerhead on.  This should match with the setting for
   http_user_prefix in breezy.conf

7) Restart apache and you should be able to start browsing

.. note:: If you have SELinux enabled on your system you may need to allow
   apache to execute files in temporary directories.  You will get a
   MemoryError traceback from python if this is the case.  This is because of
   the way that python ctypes interacts with libffi.  To rectify this, you may
   have to do several things, such as mounting tmpdirs so programs can be
   executed on them and setting this SELinux boolean::

       setsebool httpd_tmp_exec on

   This bug has information about how python and/or Linux distros might solve
   this issue permanently and links to bugs which diagnose the root cause.
   https://bugzilla.redhat.com/show_bug.cgi?id=582009

Search
------

Search is currently supported by using the bzr-search plugin (available
at: https://launchpad.net/bzr-search ).

You need to have the plugin installed and each branch indexed to allow
searching on branches.

Command-Line Reference
----------------------

.. toctree::
   :maxdepth: 2

   loggerhead-serve


Support
-------

Discussion should take place on the bazaar-dev mailing list at
mailto:bazaar@lists.canonical.com.  You can join the list at
.  You don't need to
subscribe to post, but your first post will be held briefly for manual
moderation.

Bugs, support questions and merge proposals are tracked on Launchpad, e.g:

    https://bugs.launchpad.net/loggerhead


Hacking
-------

To run Loggerhead tests, you will need to install the package ``python-nose``,
and run its :command:`nosetests` script in the Loggerhead directory:

.. code-block:: sh

    nosetests


License
-------

GNU GPLv2 or later.

See Also
--------

https://launchpad.net/loggerhead

Index
=====

- :ref:`genindex`
loggerhead-1.19~bzr511/docs/loggerhead-serve.rst0000644000000000000000000000604113731072134017745 0ustar  00000000000000:command:`loggerhead-serve`
=========================

The :command:`loggerhead-serve` script runs a standalone Loggerhead server in
the foreground.

.. program:: loggerhead-serve

Usage
-----

.. code-block:: sh

   loggerhead-serve [OPTIONS] 

Options
-------

.. cmdoption:: --user-dirs

    Serve user directories as ``~user`` (requires ``--trunk-dir``).

    If both options are set, then for requests where the CGI ``PATH_INFO``
    starts with "/~", serve branches under the  directory.

.. cmdoption:: --trunk-dir=DIR

    The directory that contains the trunk branches (requires ``--user-dirs``).

    If both options are set, then for requests where the CGI ``PATH_INFO``
    does not start with "/~", serve branches under DIR.

.. cmdoption:: --port

    Listen on the given port.
    
    Defaults to 8080.

.. cmdoption:: --host

    Listen on the interface corresponding to the given IP. 
    
    Defaults to listening on all interfaces, i.e., "0.0.0.0".

.. cmdoption:: --protocol

    Serve the application using the specified protocol.
    
    Can be one of: "http", "scgi", "fcgi", "ajp" (defaults to "http").

.. cmdoption:: --prefix

    Set the supplied value as the CGI ``SCRIPT_NAME`` for the application.

    This option is intended for use when serving Loggerhead behind a
    reverse proxy, with Loggerhead being "mounted" at a directory below
    the root.  E.g., if the reverse proxy translates requests for
    ``http://example.com/loggerhead`` onto the standalone Loggerhead process,
    that process should be run with ``--prefix=/loggerhead``.

.. cmdoption:: --log-folder=LOG_FOLDER

    The directory in which to place Loggerhead's log files.
    
    Defaults to the current directory.

.. cmdoption:: --cache-dir=SQL_CACHE_DIR

    The directory in which to place the SQL cache.

    Defaults to the current directory.

.. cmdoption:: --use-cdn
   
    Serve jQuery javascript libraries from Googles CDN.

.. cmdoption:: --allow-writes
   
    Allow writing to the Breezy server.
    
    Setting this option keeps Loggerhead from adding a 'readonly+' prefix
    to the base URL of the branch.  The only effect of suppressing this prefix
    is to make visible the display of instructions for checking out the
    'public_branch' URL for the branch being browsed.

.. cmdoption:: -h, --help

    Print the help message and exit

.. cmdoption:: --version

    Print the software version and exit.

Debugging Options
-----------------

The following options are only useful when developing / debugging Loggerhead
itself.

.. cmdoption:: --profile
   
    Generate per-request callgrind profile data.
    
    Data for each request is written to a file ``%d-stats.callgrind``,
    where ``%d`` is replaced by the sequence number of the request.

.. cmdoption:: --memory-profile

    Profile the memory usage using the `Dozer
    `_ middleware.

.. cmdoption:: --reload

    Restart the application when any of its python file change.
    
    This option should only used for development purposes.
loggerhead-1.19~bzr511/docs/make.bat0000644000000000000000000000600713731072134015377 0ustar  00000000000000@ECHO OFF

REM Command file for Sphinx documentation

set SPHINXBUILD=sphinx-build
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
if NOT "%PAPER%" == "" (
	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
)

if "%1" == "" goto help

if "%1" == "help" (
	:help
	echo.Please use `make ^` where ^ is one of
	echo.  html      to make standalone HTML files
	echo.  dirhtml   to make HTML files named index.html in directories
	echo.  pickle    to make pickle files
	echo.  json      to make JSON files
	echo.  htmlhelp  to make HTML files and a HTML help project
	echo.  qthelp    to make HTML files and a qthelp project
	echo.  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter
	echo.  changes   to make an overview over all changed/added/deprecated items
	echo.  linkcheck to check all external links for integrity
	echo.  doctest   to run all doctests embedded in the documentation if enabled
	goto end
)

if "%1" == "clean" (
	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
	del /q /s %BUILDDIR%\*
	goto end
)

if "%1" == "html" (
	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
	goto end
)

if "%1" == "dirhtml" (
	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
	echo.
	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
	goto end
)

if "%1" == "pickle" (
	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
	echo.
	echo.Build finished; now you can process the pickle files.
	goto end
)

if "%1" == "json" (
	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
	echo.
	echo.Build finished; now you can process the JSON files.
	goto end
)

if "%1" == "htmlhelp" (
	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
	echo.
	echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
	goto end
)

if "%1" == "qthelp" (
	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
	echo.
	echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\loggerhead.qhcp
	echo.To view the help file:
	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\loggerhead.ghc
	goto end
)

if "%1" == "latex" (
	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
	echo.
	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
	goto end
)

if "%1" == "changes" (
	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
	echo.
	echo.The overview file is in %BUILDDIR%/changes.
	goto end
)

if "%1" == "linkcheck" (
	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
	echo.
	echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
	goto end
)

if "%1" == "doctest" (
	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
	echo.
	echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
	goto end
)

:end
loggerhead-1.19~bzr511/load_test_scripts/multiple_instances.script0000644000000000000000000000156413731072134023722 0ustar  00000000000000{
    "comment": "Connect to multiple loggerhead instances and make requests on each. One should be on :8080, one should be on :8081. Multiple threads will place requests on each.",
    "parameters": {"base_url": "http://localhost"},
    "requests": [
        {"thread": "1", "relpath": ":8080/changes"},
        {"thread": "2", "relpath": ":8080/files"},
        {"thread": "3", "relpath": ":8081/files"},
        {"thread": "4", "relpath": ":8081/changes"},
        {"thread": "1", "relpath": ":8080/changes"},
        {"thread": "2", "relpath": ":8080/files"},
        {"thread": "3", "relpath": ":8081/files"},
        {"thread": "4", "relpath": ":8081/changes"},
        {"thread": "1", "relpath": ":8080/changes"},
        {"thread": "2", "relpath": ":8080/files"},
        {"thread": "3", "relpath": ":8081/files"},
        {"thread": "4", "relpath": ":8081/changes"}
    ]
}

loggerhead-1.19~bzr511/load_test_scripts/simple.script0000644000000000000000000000044313731072134021304 0ustar  00000000000000{
    "comment": "A fairly trivial load test script. It just loads the main two pages from a loggerhead install running directly on a branch.",
    "parameters": {"base_url": "http://localhost:8080"},
    "requests": [
        {"relpath": "/changes"},
        {"relpath": "/files"}
    ]
}

loggerhead-1.19~bzr511/loggerhead/__init__.py0000644000000000000000000000227013731072134017252 0ustar  00000000000000#
# Copyright (C) 2008, 2009 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, Suite 500, Boston, MA 02110-1335  USA

"""A simple container to turn this into a python package.

We also check the versions of some dependencies.
"""

import pkg_resources

__version__ = '1.20.0'  # Keep in sync with ../__init__.py.
__revision__ = None
required_breezy = (3, 0)

pkg_resources.get_distribution('Paste>=1.6')
try:
    pkg_resources.get_distribution('PasteDeploy>=1.3')
except pkg_resources.DistributionNotFound:
    # No paste.deploy is OK, but an old paste.deploy is bad.
    pass
loggerhead-1.19~bzr511/loggerhead/apps/0000755000000000000000000000000013731072134016103 5ustar  00000000000000loggerhead-1.19~bzr511/loggerhead/changecache.py0000644000000000000000000001226113731072134017725 0ustar  00000000000000#
# Copyright (C) 2006  Robey Pointer 
#
# 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, Suite 500, Boston, MA 02110-1335  USA
#

"""
a cache for chewed-up 'file change' data structures, which are basically just
a different way of storing a revision delta.  the cache improves lookup times
10x over bazaar's xml revision structure, though, so currently still worth
doing.

once a revision is committed in bazaar, it never changes, so once we have
cached a change, it's good forever.
"""

try:
    import cPickle as pickle
except ImportError: # Python >= 3
    import pickle
import marshal
import os
import tempfile
import zlib

try:
    from sqlite3 import dbapi2
except ImportError:
    from pysqlite2 import dbapi2

# We take an optimistic approach to concurrency here: we might do work twice
# in the case of races, but not crash or corrupt data.

def safe_init_db(filename, init_sql):
    # To avoid races around creating the database, we create the db in
    # a temporary file and rename it into the ultimate location.
    fd, temp_path = tempfile.mkstemp(dir=os.path.dirname(filename))
    os.close(fd)
    con = dbapi2.connect(temp_path)
    cur = con.cursor()
    cur.execute(init_sql)
    con.commit()
    con.close()
    os.rename(temp_path, filename)

class FakeShelf(object):

    def __init__(self, filename):
        create_table = not os.path.exists(filename)
        if create_table:
            safe_init_db(
                filename, "create table RevisionData "
                "(revid binary primary key, data binary)")
        self.connection = dbapi2.connect(filename)
        self.cursor = self.connection.cursor()

    def _create_table(self, filename):
        con = dbapi2.connect(filename)
        cur = con.cursor()
        cur.execute(
            "create table RevisionData "
            "(revid binary primary key, data binary)")
        con.commit()
        con.close()

    def _serialize(self, obj):
        return dbapi2.Binary(pickle.dumps(obj, protocol=2))

    def _unserialize(self, data):
        return pickle.loads(str(data))

    def get(self, revid):
        self.cursor.execute(
            "select data from revisiondata where revid = ?", (revid, ))
        filechange = self.cursor.fetchone()
        if filechange is None:
            return None
        else:
            return self._unserialize(filechange[0])

    def add(self, revid, object):
        try:
            self.cursor.execute(
                "insert into revisiondata (revid, data) values (?, ?)",
                (revid, self._serialize(object)))
            self.connection.commit()
        except dbapi2.IntegrityError:
            # If another thread or process attempted to set the same key, we
            # assume it set it to the same value and carry on with our day.
            pass


class RevInfoDiskCache(object):
    """Like `RevInfoMemoryCache` but backed in a sqlite DB."""

    def __init__(self, cache_path):
        if not os.path.exists(cache_path):
            os.mkdir(cache_path)
        filename = os.path.join(cache_path, 'revinfo.sql')
        create_table = not os.path.exists(filename)
        if create_table:
            safe_init_db(
                filename, "create table Data "
                "(key binary primary key, revid binary, data binary)")
        self.connection = dbapi2.connect(filename)
        self.cursor = self.connection.cursor()

    def get(self, key, revid):
        if not isinstance(key, bytes):
            raise TypeError(key)
        if not isinstance(revid, bytes):
            raise TypeError(revid)
        self.cursor.execute(
            "select revid, data from data where key = ?", (dbapi2.Binary(key),))
        row = self.cursor.fetchone()
        if row is None:
            return None
        elif str(row[0]) != revid:
            return None
        else:
            return marshal.loads(zlib.decompress(row[1]))

    def set(self, key, revid, data):
        if not isinstance(key, bytes):
            raise TypeError(key)
        if not isinstance(revid, bytes):
            raise TypeError(revid)
        try:
            self.cursor.execute(
                'delete from data where key = ?', (dbapi2.Binary(key), ))
            blob = zlib.compress(marshal.dumps(data))
            self.cursor.execute(
                "insert into data (key, revid, data) values (?, ?, ?)",
                list(map(dbapi2.Binary, [key, revid, blob])))
            self.connection.commit()
        except dbapi2.IntegrityError:
            # If another thread or process attempted to set the same key, we
            # don't care too much -- it's only a cache after all!
            pass
loggerhead-1.19~bzr511/loggerhead/config.py0000644000000000000000000001270113731072134016760 0ustar  00000000000000#
# Copyright (C) 2008, 2009 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.
#
"""Configuration tools for Loggerhead."""

from optparse import OptionParser
import sys
import tempfile

from breezy import config

_temporary_sql_dir = None

def _get_temporary_sql_dir():
    global _temporary_sql_dir
    if _temporary_sql_dir is None:
        _temporary_sql_dir = tempfile.mkdtemp(prefix='loggerhead-cache-')
    return _temporary_sql_dir

def command_line_parser():
    parser = OptionParser("%prog [options] ")
    parser.set_defaults(
        user_dirs=False,
        show_version=False,
        log_folder=None,
        use_cdn=False,
        sql_dir=None,
        allow_writes=False,
        export_tarballs=True,
        )
    parser.add_option("--user-dirs", action="store_true",
                      help="Serve user directories as ~user.")
    parser.add_option("--trunk-dir", metavar="DIR",
                      help="The directory that contains the trunk branches.")
    parser.add_option("--port", dest="user_port",
                      help=("Port Loggerhead should listen on "
                            "(defaults to 8080)."))
    parser.add_option("--host", dest="user_host",
                      help="Host Loggerhead should listen on.")
    parser.add_option("--protocol", dest="protocol",
                      help=("Protocol to use: http, scgi, fcgi, ajp"
                           "(defaults to http)."))
    parser.add_option("--log-level", default=None, action='callback',
                      callback=_optparse_level_to_int_level,
                      type="string",
                      help="Set the verbosity of logging. Can either"
                           " be set to a numeric or string"
                           " (eg, 10=debug, 30=warning)")
    parser.add_option("--memory-profile", action="store_true",
                      help="Profile the memory usage using Dozer.")
    parser.add_option("--prefix", dest="user_prefix",
                      help="Specify host prefix.")
    parser.add_option("--profile", action="store_true",
                      help="Generate callgrind profile data to "
                        "%d-stats.callgrind on each request.")
    parser.add_option("--reload", action="store_true",
                      help="Restarts the application when changing python"
                           " files. Only used for development purposes.")
    parser.add_option("--log-folder",
                      help="The directory to place log files in.")
    parser.add_option("--version", action="store_true", dest="show_version",
                      help="Print the software version and exit")
    parser.add_option("--use-cdn", action="store_true", dest="use_cdn",
                      help="Serve jQuery from Google's CDN")
    parser.add_option("--cache-dir", dest="sql_dir",
                      help="The directory to place the SQL cache in")
    parser.add_option("--allow-writes", action="store_true",
                      help="Allow writing to the Bazaar server.")
    parser.add_option("--export-tarballs", action="store_true",
                      help="Allow exporting revisions to tarballs.")
    return parser


_log_levels = {
    'debug': 10,
    'info': 20,
    'warning': 30,
    'error': 40,
    'critical': 50,
}

def _optparse_level_to_int_level(option, opt_str, value, parser):
    parser.values.log_level = _level_to_int_level(value)


def _level_to_int_level(value):
    """Convert a string level to an integer value."""
    if value is None:
        return None
    try:
        return int(value)
    except ValueError:
        pass
    return _log_levels[value.lower()]


class LoggerheadConfig(object):
    """A configuration object."""

    def __init__(self, argv=None):
        if argv is None:
            argv = sys.argv[1:]
        self._parser = command_line_parser()
        self._options, self._args = self._parser.parse_args(argv)

        sql_dir = self.get_option('sql_dir')
        if sql_dir is None:
            sql_dir = _get_temporary_sql_dir()
        self.SQL_DIR = sql_dir

    def get_option(self, option):
        """Get the value for the config option, either
        from ~/.config/breezy/breezy.conf or from the command line.
        All loggerhead-specific settings start with 'http_'
        """
        global_config = config.GlobalConfig().get_user_option('http_'+option)
        cmd_config = getattr(self._options, option)
        if global_config is not None and (
            cmd_config is None or cmd_config is False):
            return global_config
        else:
            return cmd_config

    def get_log_level(self):
        opt = self.get_option('log_level')
        return _level_to_int_level(opt)

    def get_arg(self, index):
        """Get an arg from the arg list."""
        return self._args[index]

    def print_help(self):
        """Wrapper around OptionParser.print_help."""
        return self._parser.print_help()

    @property
    def arg_count(self):
        """Return the number of args from the option parser."""
        return len(self._args)
loggerhead-1.19~bzr511/loggerhead/controllers/0000755000000000000000000000000013731072134017506 5ustar  00000000000000loggerhead-1.19~bzr511/loggerhead/daemon.py0000644000000000000000000000332313731072134016756 0ustar  00000000000000# daemon code from ASPN
#

import os


def daemonize(pidfile, home):
    """
    Detach this process from the controlling terminal and run it in the
    background as a daemon.
    """

    WORKDIR = "/"
    REDIRECT_TO = getattr(os, 'devnull', '/dev/null')
    MAXFD = 1024

    try:
        pid = os.fork()
    except OSError as e:
        raise Exception("%s [%d]" % (e.strerror, e.errno))

    if pid == 0:      # The first child.
        os.setsid()

        try:
            pid = os.fork()     # Fork a second child.
        except OSError as e:
            raise Exception("%s [%d]" % (e.strerror, e.errno))

        if pid == 0:  # The second child.
            os.chdir(WORKDIR)
        else:
            os._exit(0) # Exit parent (the first child) of the second child.
    else:
        os._exit(0)     # Exit parent of the first child.

    import resource            # Resource usage information.
    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
    if (maxfd == resource.RLIM_INFINITY):
        maxfd = MAXFD

    fd = os.open(REDIRECT_TO, os.O_RDONLY)
    os.dup2(fd, 0)
    fd = os.open(REDIRECT_TO, os.O_WRONLY)
    os.dup2(fd, 1)
    os.dup2(fd, 2)

    # Iterate through and close all other file descriptors.
    for fd in range(3, maxfd):
        try:
            os.close(fd)
        except OSError:        # ERROR, fd wasn't open to begin with (ignored)
            pass

    f = open(pidfile, 'w')
    f.write('%d\n' % (os.getpid(),))
    f.close()


def is_running(pidfile):
    try:
        f = open(pidfile, 'r')
    except IOError:
        return False
    pid = int(f.readline())
    f.close()
    try:
        os.kill(pid, 0)
    except OSError:
        # no such process
        return False
    return True
loggerhead-1.19~bzr511/loggerhead/highlight.py0000644000000000000000000000405113731072134017461 0ustar  00000000000000#
# Copyright (C) 2009  Peter Bui 
#
# 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, Suite 500, Boston, MA 02110-1335  USA
#

import breezy.osutils
try:
    from html import escape
except ImportError:
    from cgi import escape

from pygments import highlight as _highlight_func
from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
from pygments.formatters import HtmlFormatter
from pygments.util import ClassNotFound

DEFAULT_PYGMENT_STYLE = 'colorful'

# Trying to highlight very large files using pygments was killing
# loggerhead on launchpad.net, because pygments isn't very fast.
# So we only highlight files if they're 512K or smaller.
MAX_HIGHLIGHT_SIZE = 512000;

def highlight(path, text, encoding, style=DEFAULT_PYGMENT_STYLE):
    """
    Returns a list of highlighted (i.e. HTML formatted) strings.
    """

    if len(text) > MAX_HIGHLIGHT_SIZE:
        return map(escape,  breezy.osutils.split_lines(text))

    formatter = HtmlFormatter(style=style, nowrap=True, classprefix='pyg-')

    try:
        lexer = guess_lexer_for_filename(path, text[:1024], encoding=encoding)
    except (ClassNotFound, ValueError):
        try:
            lexer = guess_lexer(text[:1024], encoding=encoding)
        except (ClassNotFound, ValueError):
            lexer = TextLexer(encoding=encoding)

    hl_lines = _highlight_func(text, lexer, formatter)
    hl_lines = breezy.osutils.split_lines(hl_lines)

    return hl_lines
loggerhead-1.19~bzr511/loggerhead/history.py0000644000000000000000000007667613731072134017241 0ustar  00000000000000# Copyright (C) 2006-2011 Canonical Ltd.
#                     (Authored by Martin Albisetti )
# Copyright (C) 2006  Robey Pointer 
# Copyright (C) 2006  Goffredo Baroncelli 
# Copyright (C) 2005  Jake Edge 
# Copyright (C) 2005  Matt Mackall 
#
# 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, Suite 500, Boston, MA 02110-1335  USA
#

#
# This file (and many of the web templates) contains work based on the
# "bazaar-webserve" project by Goffredo Baroncelli, which is in turn based
# on "hgweb" by Jake Edge and Matt Mackall.
#


import bisect
import datetime
import logging
import re
import textwrap
import threading

from breezy import version_info as breezy_version
from breezy import tag
import breezy.branch
import breezy.delta
import breezy.errors
import breezy.foreign
import breezy.osutils
import breezy.revision

from . import search
from . import util
from .wholehistory import compute_whole_history_data


def is_branch(folder):
    try:
        breezy.branch.Branch.open(folder)
        return True
    except breezy.errors.NotBranchError:
        return False


def clean_message(message):
    """Clean up a commit message and return it and a short (1-line) version.

    Commit messages that are long single lines are reflowed using the textwrap
    module (Robey, the original author of this code, apparently favored this
    style of message).
    """
    message = message.lstrip().splitlines()

    if len(message) == 1:
        message = textwrap.wrap(message[0])

    if len(message) == 0:
        # We can end up where when (a) the commit message was empty or (b)
        # when the message consisted entirely of whitespace, in which case
        # textwrap.wrap() returns an empty list.
        return [''], ''

    # Make short form of commit message.
    short_message = message[0]
    if len(short_message) > 60:
        short_message = short_message[:60] + '...'

    return message, short_message


def rich_filename(path, kind):
    if kind == 'directory':
        path += '/'
    if kind == 'symlink':
        path += '@'
    return path


class _RevListToTimestamps(object):
    """This takes a list of revisions, and allows you to bisect by date"""

    __slots__ = ['revid_list', 'repository']

    def __init__(self, revid_list, repository):
        self.revid_list = revid_list
        self.repository = repository

    def __getitem__(self, index):
        """Get the date of the index'd item"""
        return datetime.datetime.fromtimestamp(self.repository.get_revision(
                   self.revid_list[index]).timestamp)

    def __len__(self):
        return len(self.revid_list)


class FileChangeReporter(object):

    def __init__(self, old_tree, new_tree):
        self.added = []
        self.modified = []
        self.renamed = []
        self.removed = []
        self.text_changes = []
        self.old_tree = old_tree
        self.new_tree = new_tree

    def revid(self, tree, path):
        if path is None:
            return breezy.revision.NULL_REVISION
        try:
            return tree.get_file_revision(path)
        except breezy.errors.NoSuchFile:
            return breezy.revision.NULL_REVISION

    if breezy_version >= (3, 1):
        def report(self, paths, versioned, renamed, copied, modified,
                   exe_change, kind):
            return self._report(
                    paths, versioned, renamed, copied,
                    modified, exe_change, kind)
    else:
        def report(self, file_id, paths, versioned, renamed, modified,
                   exe_change, kind):
            return self._report(
                    paths, versioned, renamed, None,
                    modified, exe_change, kind)

    def _report(self, paths, versioned, renamed, copied, modified,
                exe_change, kind):
        if modified not in ('unchanged', 'kind changed'):
            if versioned == 'removed':
                filename = rich_filename(paths[0], kind[0])
            else:
                filename = rich_filename(paths[1], kind[1])
            self.text_changes.append(util.Container(
                filename=filename,
                old_revision=self.revid(self.old_tree, paths[0]),
                new_revision=self.revid(self.new_tree, paths[1])))
        if versioned == 'added':
            self.added.append(util.Container(
                filename=rich_filename(paths[1], kind), kind=kind[1]))
        elif versioned == 'removed':
            self.removed.append(util.Container(
                filename=rich_filename(paths[0], kind), kind=kind[0]))
        elif renamed:
            self.renamed.append(util.Container(
                old_filename=rich_filename(paths[0], kind[0]),
                new_filename=rich_filename(paths[1], kind[1]),
                text_modified=modified == 'modified', exe_change=exe_change))
        else:
            self.modified.append(util.Container(
                filename=rich_filename(paths[1], kind),
                text_modified=modified == 'modified', exe_change=exe_change))


# The lru_cache is not thread-safe, so we need a lock around it for
# all threads.
rev_info_memory_cache_lock = threading.RLock()


class RevInfoMemoryCache(object):
    """A store that validates values against the revids they were stored with.

    We use a unique key for each branch.

    The reason for not just using the revid as the key is so that when a new
    value is provided for a branch, we replace the old value used for the
    branch.

    There is another implementation of the same interface in
    loggerhead.changecache.RevInfoDiskCache.
    """

    def __init__(self, cache):
        self._cache = cache

    def get(self, key, revid):
        """Return the data associated with `key`, subject to a revid check.

        If a value was stored under `key`, with the same revid, return it.
        Otherwise return None.
        """
        rev_info_memory_cache_lock.acquire()
        try:
            cached = self._cache.get(key)
        finally:
            rev_info_memory_cache_lock.release()
        if cached is None:
            return None
        stored_revid, data = cached
        if revid == stored_revid:
            return data
        else:
            return None

    def set(self, key, revid, data):
        """Store `data` under `key`, to be checked against `revid` on get().
        """
        rev_info_memory_cache_lock.acquire()
        try:
            self._cache[key] = (revid, data)
        finally:
            rev_info_memory_cache_lock.release()

# Used to store locks that prevent multiple threads from building a
# revision graph for the same branch at the same time, because that can
# cause severe performance issues that are so bad that the system seems
# to hang.
revision_graph_locks = {}
revision_graph_check_lock = threading.Lock()

class History(object):
    """Decorate a branch to provide information for rendering.

    History objects are expected to be short lived -- when serving a request
    for a particular branch, open it, read-lock it, wrap a History object
    around it, serve the request, throw the History object away, unlock the
    branch and throw it away.

    :ivar _rev_info: A list of information about revisions.  This is by far
        the most cryptic data structure in loggerhead.  At the top level, it
        is a list of 3-tuples [(merge-info, where-merged, parents)].
        `merge-info` is (seq, revid, merge_depth, revno_str, end_of_merge) --
        like a merged sorted list, but the revno is stringified.
        `where-merged` is a tuple of revisions that have this revision as a
        non-lefthand parent.  Finally, `parents` is just the usual list of
        parents of this revision.
    :ivar _rev_indices: A dictionary mapping each revision id to the index of
        the information about it in _rev_info.
    :ivar _revno_revid: A dictionary mapping stringified revnos to revision
        ids.
    """

    def _load_whole_history_data(self, caches, cache_key):
        """Set the attributes relating to the whole history of the branch.

        :param caches: a list of caches with interfaces like
            `RevInfoMemoryCache` and be ordered from fastest to slowest.
        :param cache_key: the key to use with the caches.
        """
        self._rev_indices = None
        self._rev_info = None

        missed_caches = []
        def update_missed_caches():
            for cache in missed_caches:
                cache.set(cache_key, self.last_revid, self._rev_info)

        # Theoretically, it's possible for two threads to race in creating
        # the Lock() object for their branch, so we put a lock around
        # creating the per-branch Lock().
        revision_graph_check_lock.acquire()
        try:
            if cache_key not in revision_graph_locks:
                revision_graph_locks[cache_key] = threading.Lock()
        finally:
            revision_graph_check_lock.release()

        revision_graph_locks[cache_key].acquire()
        try:
            for cache in caches:
                data = cache.get(cache_key, self.last_revid)
                if data is not None:
                    self._rev_info = data
                    update_missed_caches()
                    break
                else:
                    missed_caches.append(cache)
            else:
                whole_history_data = compute_whole_history_data(self._branch)
                self._rev_info, self._rev_indices = whole_history_data
                update_missed_caches()
        finally:
            revision_graph_locks[cache_key].release()

        if self._rev_indices is not None:
            self._revno_revid = {}
            for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
                self._revno_revid[revno_str] = revid
        else:
            self._revno_revid = {}
            self._rev_indices = {}
            for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
                self._rev_indices[revid] = seq
                self._revno_revid[revno_str] = revid

    def __init__(self, branch, whole_history_data_cache,
                 revinfo_disk_cache=None, cache_key=None):
        assert branch.is_locked(), (
            "Can only construct a History object with a read-locked branch.")
        self._branch = branch
        self._branch_tags = None
        self._inventory_cache = {}
        self._branch_nick = self._branch.get_config().get_nickname()
        self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))

        self.last_revid = branch.last_revision()

        caches = [RevInfoMemoryCache(whole_history_data_cache)]
        if revinfo_disk_cache:
            caches.append(revinfo_disk_cache)
        self._load_whole_history_data(caches, cache_key)

    @property
    def has_revisions(self):
        return not breezy.revision.is_null(self.last_revid)

    def get_config(self):
        return self._branch.get_config()

    def get_revno(self, revid):
        if revid not in self._rev_indices:
            # ghost parent?
            return 'unknown'
        seq = self._rev_indices[revid]
        revno = self._rev_info[seq][0][3]
        return revno

    def get_revids_from(self, revid_list, start_revid):
        """
        Yield the mainline (wrt start_revid) revisions that merged each
        revid in revid_list.
        """
        if revid_list is None:
            # Just yield the mainline, starting at start_revid
            revid = start_revid
            is_null = breezy.revision.is_null
            while not is_null(revid):
                yield revid
                parents = self._rev_info[self._rev_indices[revid]][2]
                if not parents:
                    return
                revid = parents[0]
            return
        revid_set = set(revid_list)
        revid = start_revid

        def introduced_revisions(revid):
            r = set([revid])
            seq = self._rev_indices[revid]
            md = self._rev_info[seq][0][2]
            i = seq + 1
            while i < len(self._rev_info) and self._rev_info[i][0][2] > md:
                r.add(self._rev_info[i][0][1])
                i += 1
            return r
        while revid_set:
            if breezy.revision.is_null(revid):
                return
            rev_introduced = introduced_revisions(revid)
            matching = rev_introduced.intersection(revid_set)
            if matching:
                # We don't need to look for these anymore.
                revid_set.difference_update(matching)
                yield revid
            parents = self._rev_info[self._rev_indices[revid]][2]
            if len(parents) == 0:
                return
            revid = parents[0]

    def get_short_revision_history_by_fileid(self, file_id):
        # FIXME: would be awesome if we could get, for a folder, the list of
        # revisions where items within that folder changed.i
        possible_keys = [(file_id, revid) for revid in self._rev_indices]
        get_parent_map = self._branch.repository.texts.get_parent_map
        # We chunk the requests as this works better with GraphIndex.
        # See _filter_revisions_touching_file_id in breezy/log.py
        # for more information.
        revids = []
        chunk_size = 1000
        for start in range(0, len(possible_keys), chunk_size):
            next_keys = possible_keys[start:start + chunk_size]
            revids += [k[1] for k in get_parent_map(next_keys)]
        del possible_keys, next_keys
        return revids

    def get_revision_history_since(self, revid_list, date):
        # if a user asks for revisions starting at 01-sep, they mean inclusive,
        # so start at midnight on 02-sep.
        date = date + datetime.timedelta(days=1)
        # our revid list is sorted in REVERSE date order,
        # so go thru some hoops here...
        revid_list.reverse()
        index = bisect.bisect(_RevListToTimestamps(revid_list,
                                                   self._branch.repository),
                              date)
        if index == 0:
            return []
        revid_list.reverse()
        index = -index
        return revid_list[index:]

    def get_search_revid_list(self, query, revid_list):
        """
        given a "quick-search" query, try a few obvious possible meanings:

            - revision id or # ("128.1.3")
            - date (US style "mm/dd/yy", earth style "dd-mm-yy", or \
iso style "yyyy-mm-dd")
            - comment text as a fallback

        and return a revid list that matches.
        """
        # FIXME: there is some silliness in this action.  we have to look up
        # all the relevant changes (time-consuming) only to return a list of
        # revids which will be used to fetch a set of changes again.

        # if they entered a revid, just jump straight there;
        # ignore the passed-in revid_list
        revid = self.fix_revid(query)
        if revid is not None:
            changes = self.get_changes([revid])
            if (changes is not None) and (len(changes) > 0):
                return [revid]

        date = None
        m = self.us_date_re.match(query)
        if m is not None:
            date = datetime.datetime(util.fix_year(int(m.group(3))),
                                     int(m.group(1)),
                                     int(m.group(2)))
        else:
            m = self.earth_date_re.match(query)
            if m is not None:
                date = datetime.datetime(util.fix_year(int(m.group(3))),
                                         int(m.group(2)),
                                         int(m.group(1)))
            else:
                m = self.iso_date_re.match(query)
                if m is not None:
                    date = datetime.datetime(util.fix_year(int(m.group(1))),
                                             int(m.group(2)),
                                             int(m.group(3)))
        if date is not None:
            if revid_list is None:
                # if no limit to the query was given,
                # search only the direct-parent path.
                revid_list = list(self.get_revids_from(None, self.last_revid))
            return self.get_revision_history_since(revid_list, date)

    revno_re = re.compile(r'^[\d\.]+$')
    # the date regex are without a final '$' so that queries like
    # "2006-11-30 12:15" still mostly work.  (i think it's better to give
    # them 90% of what they want instead of nothing at all.)
    us_date_re = re.compile(r'^(\d{1,2})/(\d{1,2})/(\d\d(\d\d?))')
    earth_date_re = re.compile(r'^(\d{1,2})-(\d{1,2})-(\d\d(\d\d?))')
    iso_date_re = re.compile(r'^(\d\d\d\d)-(\d\d)-(\d\d)')

    def fix_revid(self, revid):
        # if a "revid" is actually a dotted revno, convert it to a revid
        if revid is None:
            return revid
        if not isinstance(revid, (str, util.text_type)):
            raise TypeError(revid)
        if revid == 'head:':
            return self.last_revid
        try:
            if self.revno_re.match(revid):
                revid = self._revno_revid[revid]
        except KeyError:
            raise breezy.errors.NoSuchRevision(self._branch_nick, revid)
        if not isinstance(revid, bytes):
            revid = revid.encode('utf-8')
        return revid

    def get_file_view(self, revid, file_id):
        """
        Given a revid and optional path, return a (revlist, revid) for
        navigation through the current scope: from the revid (or the latest
        revision) back to the original revision.

        If file_id is None, the entire revision history is the list scope.
        """
        if revid is None:
            revid = self.last_revid
        if file_id is not None:
            revlist = list(
                self.get_short_revision_history_by_fileid(file_id))
            revlist = self.get_revids_from(revlist, revid)
        else:
            revlist = self.get_revids_from(None, revid)
        return revlist

    @staticmethod
    def _iterate_sufficiently(iterable, stop_at, extra_rev_count):
        """Return a list of iterable.

        If extra_rev_count is None, fully consume iterable.
        Otherwise, stop at 'stop_at' + extra_rev_count.

        Example:
          iterate until you find stop_at, then iterate 10 more times.
        """
        if extra_rev_count is None:
            return list(iterable)
        result = []
        found = False
        for n in iterable:
            result.append(n)
            if n == stop_at:
                found = True
                break
        if found:
            for count, n in enumerate(iterable):
                if count >= extra_rev_count:
                    break
                result.append(n)
        return result

    def get_view(self, revid, start_revid, file_id, query=None,
                 extra_rev_count=None):
        """
        use the URL parameters (revid, start_revid, file_id, and query) to
        determine the revision list we're viewing (start_revid, file_id, query)
        and where we are in it (revid).

            - if a query is given, we're viewing query results.
            - if a file_id is given, we're viewing revisions for a specific
              file.
            - if a start_revid is given, we're viewing the branch from a
              specific revision up the tree.
            - if extra_rev_count is given, find the view from start_revid =>
              revid, and continue an additional 'extra_rev_count'. If not
              given, then revid_list will contain the full history of
              start_revid

        these may be combined to view revisions for a specific file, from
        a specific revision, with a specific search query.

        returns a new (revid, start_revid, revid_list) where:

            - revid: current position within the view
            - start_revid: starting revision of this view
            - revid_list: list of revision ids for this view

        file_id and query are never changed so aren't returned, but they may
        contain vital context for future url navigation.
        """
        if start_revid is None:
            start_revid = self.last_revid

        if query is None:
            revid_list = self.get_file_view(start_revid, file_id)
            revid_list = self._iterate_sufficiently(revid_list, revid,
                                                    extra_rev_count)
            if revid is None:
                revid = start_revid
            if revid not in revid_list:
                # if the given revid is not in the revlist, use a revlist that
                # starts at the given revid.
                revid_list = self.get_file_view(revid, file_id)
                revid_list = self._iterate_sufficiently(revid_list, revid,
                                                        extra_rev_count)
                start_revid = revid
            return revid, start_revid, revid_list

        # potentially limit the search
        if file_id is not None:
            revid_list = self.get_file_view(start_revid, file_id)
        else:
            revid_list = None
        revid_list = search.search_revisions(self._branch, query)
        if revid_list and len(revid_list) > 0:
            if revid not in revid_list:
                revid = revid_list[0]
            return revid, start_revid, revid_list
        else:
            # XXX: This should return a message saying that the search could
            # not be completed due to either missing the plugin or missing a
            # search index.
            return None, None, []

    def revision_tree(self, revid):
        return self._branch.repository.revision_tree(revid)

    def get_path(self, revid, file_id):
        if (file_id is None) or (file_id == ''):
            return ''
        path = self.revision_tree(revid).id2path(file_id)
        if (len(path) > 0) and not path.startswith('/'):
            path = '/' + path
        return path

    def get_file_id(self, revid, path):
        if (len(path) > 0) and not path.startswith('/'):
            path = '/' + path
        return self.revision_tree(revid).path2id(path)

    def get_merge_point_list(self, revid):
        """
        Return the list of revids that have merged this node.
        """
        if '.' not in self.get_revno(revid):
            return []

        merge_point = []
        nexts = [revid]
        while nexts:
            revid = nexts.pop()
            children = self._rev_info[self._rev_indices[revid]][1]
            for child in children:
                child_parents = self._rev_info[self._rev_indices[child]][2]
                if child_parents[0] == revid:
                    nexts.append(child)
                else:
                    merge_point.append(child)
        return merge_point

    def simplify_merge_point_list(self, revids):
        """if a revision is already merged, don't show further merge points"""
        d = {}
        for revid in revids:
            revno = self.get_revno(revid)
            revnol = revno.split(".")
            revnos = ".".join(revnol[:-2])
            revnolast = int(revnol[-1])
            if revnos in d:
                m = d[revnos][0]
                if revnolast < m:
                    d[revnos] = (revnolast, revid)
            else:
                d[revnos] = (revnolast, revid)

        return [revid for (_, revid) in d.values()]

    def add_branch_nicks(self, change):
        """
        given a 'change', fill in the branch nicks on all parents and merge
        points.
        """
        fetch_set = set()
        for p in change.parents:
            fetch_set.add(p.revid)
        for p in change.merge_points:
            fetch_set.add(p.revid)
        p_changes = self.get_changes(list(fetch_set))
        p_change_dict = {c.revid: c for c in p_changes}
        for p in change.parents:
            if p.revid in p_change_dict:
                p.branch_nick = p_change_dict[p.revid].branch_nick
            else:
                p.branch_nick = '(missing)'
        for p in change.merge_points:
            if p.revid in p_change_dict:
                p.branch_nick = p_change_dict[p.revid].branch_nick
            else:
                p.branch_nick = '(missing)'

    def get_changes(self, revid_list):
        """Return a list of changes objects for the given revids.

        Revisions not present and NULL_REVISION will be ignored.
        """
        for revid in revid_list:
            if not isinstance(revid, bytes):
                raise TypeError(revid_list)
        changes = self.get_changes_uncached(revid_list)
        if len(changes) == 0:
            return changes

        # some data needs to be recalculated each time, because it may
        # change as new revisions are added.
        for change in changes:
            merge_revids = self.simplify_merge_point_list(
                               self.get_merge_point_list(change.revid))
            change.merge_points = [
                util.Container(revid=r,
                revno=self.get_revno(r)) for r in merge_revids]
            if len(change.parents) > 0:
                change.parents = [util.Container(revid=r,
                    revno=self.get_revno(r)) for r in change.parents]
            change.revno = self.get_revno(change.revid)

        parity = 0
        for change in changes:
            change.parity = parity
            parity ^= 1

        return changes

    def get_changes_uncached(self, revid_list):
        # FIXME: deprecated method in getting a null revision
        revid_list = list(filter(lambda revid: not breezy.revision.is_null(revid),
                            revid_list))
        parent_map = self._branch.repository.get_graph().get_parent_map(
                         revid_list)
        # We need to return the answer in the same order as the input,
        # less any ghosts.
        present_revids = [revid for revid in revid_list
                          if revid in parent_map]
        rev_list = self._branch.repository.get_revisions(present_revids)

        return [self._change_from_revision(rev) for rev in rev_list]

    def _change_from_revision(self, revision):
        """
        Given a breezy Revision, return a processed "change" for use in
        templates.
        """
        message, short_message = clean_message(revision.message)

        if self._branch_tags is None:
            self._branch_tags = self._branch.tags.get_reverse_tag_dict()

        revtags = None
        if revision.revision_id in self._branch_tags:
          # tag.sort_* functions expect (tag, data) pairs, so we generate them,
          # and then strip them
          tags = [(t, None) for t in self._branch_tags[revision.revision_id]]
          sort_func = getattr(tag, 'sort_natural', None)
          if sort_func is None:
              tags.sort()
          else:
              sort_func(self._branch, tags)
          revtags = u', '.join([t[0] for t in tags])

        entry = {
            'revid': revision.revision_id,
            'date': datetime.datetime.fromtimestamp(revision.timestamp),
            'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
            'timestamp': revision.timestamp,
            'committer': revision.committer,
            'authors': revision.get_apparent_authors(),
            'branch_nick': revision.properties.get('branch-nick', None),
            'short_comment': short_message,
            'comment': revision.message,
            'comment_clean': [util.html_clean(s) for s in message],
            'parents': revision.parent_ids,
            'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
            'tags': revtags,
        }
        if isinstance(revision, breezy.foreign.ForeignRevision):
            foreign_revid, mapping = (
                revision.foreign_revid, revision.mapping)
        elif b":" in revision.revision_id:
            try:
                foreign_revid, mapping = \
                    breezy.foreign.foreign_vcs_registry.parse_revision_id(
                        revision.revision_id)
            except breezy.errors.InvalidRevisionId:
                foreign_revid = None
                mapping = None
        else:
            foreign_revid = None
        if foreign_revid is not None:
            entry["foreign_vcs"] = mapping.vcs.abbreviation
            entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
        return util.Container(entry)

    def get_file_changes(self, entry):
        if entry.parents:
            old_revid = entry.parents[0].revid
        else:
            old_revid = breezy.revision.NULL_REVISION
        return self.file_changes_for_revision_ids(old_revid, entry.revid)

    def add_changes(self, entry):
        changes = self.get_file_changes(entry)
        entry.changes = changes

    def get_file(self, path, revid):
        """Returns (path, filename, file contents)"""
        if not isinstance(path, str):
            raise TypeError(path)
        if not isinstance(revid, bytes):
            raise TypeError(revid)
        rev_tree = self._branch.repository.revision_tree(revid)
        display_path = path
        if not display_path.startswith('/'):
            path = '/' + path
        return (display_path, breezy.osutils.basename(path),
                rev_tree.get_file_text(path))

    def get_file_by_fileid(self, fileid, revid):
        """Returns (path, filename, file contents)"""
        if not isinstance(fileid, bytes):
            raise TypeError(fileid)
        if not isinstance(revid, bytes):
            raise TypeError(revid)
        rev_tree = self._branch.repository.revision_tree(revid)
        path = rev_tree.id2path(fileid)
        display_path = path
        if not display_path.startswith('/'):
            path = '/' + path
        return (display_path, breezy.osutils.basename(path),
                rev_tree.get_file_text(path))

    def file_changes_for_revision_ids(self, old_revid, new_revid):
        """
        Return a nested data structure containing the changes in a delta::

            added: list((filename, file_id)),
            renamed: list((old_filename, new_filename, file_id)),
            deleted: list((filename, file_id)),
            modified: list(
                filename: str,
                file_id: str,
            ),
            text_changes: list((filename, file_id)),
        """
        repo = self._branch.repository
        if (breezy.revision.is_null(old_revid) or
                breezy.revision.is_null(new_revid) or
                old_revid == new_revid):
            old_tree, new_tree = map(
                repo.revision_tree, [old_revid, new_revid])
        else:
            old_tree, new_tree = repo.revision_trees([old_revid, new_revid])

        reporter = FileChangeReporter(old_tree, new_tree)

        breezy.delta.report_changes(new_tree.iter_changes(old_tree), reporter)

        return util.Container(
            added=sorted(reporter.added, key=lambda x: x.filename),
            renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
            removed=sorted(reporter.removed, key=lambda x: x.filename),
            modified=sorted(reporter.modified, key=lambda x: x.filename),
            text_changes=sorted(reporter.text_changes,
                                key=lambda x: x.filename))

loggerhead-1.19~bzr511/loggerhead/load_test.py0000644000000000000000000002004013731072134017464 0ustar  00000000000000# Copyright 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335  USA

"""Code to do some load testing against a loggerhead instance.

This is basically meant to take a list of actions to take, and run it against a
real host, and see how the results respond.::

    {"parameters": {
         "base_url": "http://localhost:8080",
     },
     "requests": [
        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"}
     ],
    }

All threads have a Queue length of 1. When a third request for a given thread
is seen, no more requests are queued until that thread finishes its current
job. So this results in all requests being issued sequentially::

        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"}

While this would cause all requests to be sent in parallel:

        {"thread": "1", "relpath": "/changes"},
        {"thread": "2", "relpath": "/changes"},
        {"thread": "3", "relpath": "/changes"},
        {"thread": "4", "relpath": "/changes"}

This should keep 2 threads pipelined with activity, as long as they finish in
approximately the same speed. We'll start the first thread running, and the
second thread, and queue up both with a second request once the first finishes.
When we get to the third request for thread "1", we block on queuing up more
work until the first thread 1 request has finished.
        {"thread": "1", "relpath": "/changes"},
        {"thread": "2", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "2", "relpath": "/changes"},
        {"thread": "1", "relpath": "/changes"},
        {"thread": "2", "relpath": "/changes"}

There is not currently a way to say "run all these requests keeping exactly 2
threads active". Though if you know the load pattern, you could approximate
this.
"""

import threading
import time
try:
    from queue import Queue, Empty
except ImportError:  # Python < 3
    from Queue import Queue, Empty

import json

from breezy import (
    errors,
    transport,
    urlutils,
    )

# This code will be doing multi-threaded requests against breezy.transport
# code. We want to make sure to load everything ahead of time, so we don't get
# lazy-import failures
_ = transport.get_transport('http://example.com')


class RequestDescription(object):
    """Describes info about a request."""

    def __init__(self, descrip_dict):
        self.thread = descrip_dict.get('thread', '1')
        self.relpath = descrip_dict['relpath']


class RequestWorker(object):
    """Process requests in a worker thread."""

    _timer = time.time

    def __init__(self, identifier, blocking_time=1.0, _queue_size=1):
        self.identifier = identifier
        self.queue = Queue(_queue_size)
        self.start_time = self.end_time = None
        self.stats = []
        self.blocking_time = blocking_time

    def step_next(self):
        url = self.queue.get(True, self.blocking_time)
        if url == '':
            # This is usually an indicator that we want to stop, so just skip
            # this one.
            self.queue.task_done()
            return
        self.start_time = self._timer()
        success = self.process(url)
        self.end_time = self._timer()
        self.update_stats(url, success)
        self.queue.task_done()

    def run(self, stop_event):
        while not stop_event.isSet():
            try:
                self.step_next()
            except Empty:
                pass

    def process(self, url):
        base, path = urlutils.split(url)
        t = transport.get_transport(base)
        try:
            # TODO: We should probably look into using some part of
            #       blocking_timeout to decide when to stop trying to read
            #       content
            content = t.get_bytes(path)
        except (errors.TransportError, errors.NoSuchFile):
            return False
        return True

    def update_stats(self, url, success):
        self.stats.append((url, success, self.end_time - self.start_time))


class ActionScript(object):
    """This tracks the actions that we want to perform."""

    _worker_class = RequestWorker
    _default_base_url = 'http://localhost:8080'
    _default_blocking_timeout = 60.0

    def __init__(self):
        self.base_url = self._default_base_url
        self.blocking_timeout = self._default_blocking_timeout
        self._requests = []
        self._threads = {}
        self.stop_event = threading.Event()

    @classmethod
    def parse(cls, content):
        script = cls()
        if isinstance(content, bytes):
            content = content.decode('UTF-8')
        json_dict = json.loads(content)
        if 'parameters' not in json_dict:
            raise ValueError('Missing "parameters" section')
        if 'requests' not in json_dict:
            raise ValueError('Missing "requests" section')
        param_dict = json_dict['parameters']
        request_list = json_dict['requests']
        base_url = param_dict.get('base_url', None)
        if base_url is not None:
            script.base_url = base_url
        blocking_timeout = param_dict.get('blocking_timeout', None)
        if blocking_timeout is not None:
            script.blocking_timeout = blocking_timeout
        for request_dict in request_list:
            script.add_request(request_dict)
        return script

    def add_request(self, request_dict):
        request = RequestDescription(request_dict)
        self._requests.append(request)

    def _get_worker(self, thread_id):
        if thread_id in self._threads:
            return self._threads[thread_id][0]
        handler = self._worker_class(thread_id,
                                     blocking_time=self.blocking_timeout/10.0)

        t = threading.Thread(target=handler.run, args=(self.stop_event,),
                             name='Thread-%s' % (thread_id,))
        self._threads[thread_id] = (handler, t)
        t.start()
        return handler

    def finish_queues(self):
        """Wait for all queues of all children to finish."""
        for h, t in self._threads.values():
            h.queue.join()

    def stop_and_join(self):
        """Stop all running workers, and return.

        This will stop even if workers still have work items.
        """
        self.stop_event.set()
        for h, t in self._threads.values():
            # Signal the queue that it should stop blocking, we don't have to
            # wait for the queue to empty, because we may see stop_event before
            # we see the 
            h.queue.put('')
            # And join the controlling thread
            for i in range(10):
                t.join(self.blocking_timeout / 10.0)
                if not t.isAlive():
                    break

    def _full_url(self, relpath):
        return self.base_url + relpath

    def run(self):
        self.stop_event.clear()
        for request in self._requests:
            full_url = self._full_url(request.relpath)
            worker = self._get_worker(request.thread)
            worker.queue.put(full_url, True, self.blocking_timeout)
        self.finish_queues()
        self.stop_and_join()


def run_script(filename):
    with open(filename, 'rb') as f:
        content = f.read()
    script = ActionScript.parse(content)
    script.run()
    return script
loggerhead-1.19~bzr511/loggerhead/lsprof.py0000644000000000000000000001446213731072134017026 0ustar  00000000000000
#
# this is copied from the lsprof distro because somehow
# it is not installed by distutils
# I made one modification to profile so that it returns a pair
# instead of just the Stats object

import sys
try:
    from threading import get_ident
except ImportError: # python < 3
    from thread import get_ident
import threading
from _lsprof import Profiler, profiler_entry

__all__ = ['profile', 'Stats']

_g_threadmap = {}


def _thread_profile(f, *args, **kwds):
    # we lose the first profile point for a new thread in order to trampoline
    # a new Profile object into place
    global _g_threadmap
    thr = get_ident()
    _g_threadmap[thr] = p = Profiler()
    # this overrides our sys.setprofile hook:
    p.enable(subcalls=True, builtins=True)


def profile(f, *args, **kwds):
    """FIXME: docstring"""
    global _g_threadmap
    p = Profiler()
    p.enable(subcalls=True)
    threading.setprofile(_thread_profile)
    try:
        ret = f(*args, **kwds)
    finally:
        p.disable()
        for pp in _g_threadmap.values():
            pp.disable()
        threading.setprofile(None)

    threads = {}
    for tid, pp in _g_threadmap.items():
        threads[tid] = Stats(pp.getstats(), {})
    _g_threadmap = {}
    return ret, Stats(p.getstats(), threads)


class Stats(object):
    """FIXME: docstring"""

    def __init__(self, data, threads):
        self.data = data
        self.threads = threads

    def sort(self, crit="inlinetime"):
        """FIXME: docstring"""
        if crit not in profiler_entry.__dict__:
            raise ValueError("Can't sort by %s" % crit)
        self.data.sort(lambda b, a: cmp(getattr(a, crit),
                                        getattr(b, crit)))
        for e in self.data:
            if e.calls:
                e.calls.sort(lambda b, a: cmp(getattr(a, crit),
                                              getattr(b, crit)))

    def pprint(self, top=None, file=None):
        """FIXME: docstring"""
        if file is None:
            file = sys.stdout
        d = self.data
        if top is not None:
            d = d[:top]
        cols = "% 12s %12s %11.4f %11.4f   %s\n"
        hcols = "% 12s %12s %12s %12s %s\n"
        file.write(hcols % ("CallCount", "Recursive", "Total(ms)",
                            "Inline(ms)", "module:lineno(function)"))
        for e in d:
            file.write(cols % (e.callcount, e.reccallcount, e.totaltime,
                               e.inlinetime, label(e.code)))
            if e.calls:
                for se in e.calls:
                    file.write(cols % ("+%s" % se.callcount, se.reccallcount,
                                       se.totaltime, se.inlinetime,
                                       "+%s" % label(se.code)))

    def freeze(self):
        """Replace all references to code objects with string
        descriptions; this makes it possible to pickle the instance."""

        # this code is probably rather ickier than it needs to be!
        for i in range(len(self.data)):
            e = self.data[i]
            if not isinstance(e.code, str):
                self.data[i] = type(e)((label(e.code)) + e[1:])
            if e.calls:
                for j in range(len(e.calls)):
                    se = e.calls[j]
                    if not isinstance(se.code, str):
                        e.calls[j] = type(se)((label(se.code)) + se[1:])
        for s in self.threads.values():
            s.freeze()

    def calltree(self, file):
        """Output profiling data in calltree format (for KCacheGrind)."""
        _CallTreeFilter(self.data).output(file)


class _CallTreeFilter(object):

    def __init__(self, data):
        self.data = data
        self.out_file = None

    def output(self, out_file):
        self.out_file = out_file
        print >> out_file, 'events: Ticks'
        self._print_summary()
        for entry in self.data:
            self._entry(entry)

    def _print_summary(self):
        max_cost = 0
        for entry in self.data:
            totaltime = int(entry.totaltime * 1000)
            max_cost = max(max_cost, totaltime)
        print >> self.out_file, 'summary: %d' % (max_cost)

    def _entry(self, entry):
        out_file = self.out_file
        code = entry.code
        inlinetime = int(entry.inlinetime * 1000)
        #print >> out_file, 'ob=%s' % (code.co_filename,)
        print >> out_file, 'fi=%s' % (code.co_filename)
        print >> out_file, 'fn=%s' % (label(code, True))
        print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
        # recursive calls are counted in entry.calls
        if entry.calls:
            calls = entry.calls
        else:
            calls = []
        for subentry in calls:
            self._subentry(code.co_firstlineno, subentry)
        print >> out_file

    def _subentry(self, lineno, subentry):
        out_file = self.out_file
        code = subentry.code
        totaltime = int(subentry.totaltime * 1000)
        #print >> out_file, 'cob=%s' % (code.co_filename,)
        print >> out_file, 'cfn=%s' % (label(code, True))
        print >> out_file, 'cfi=%s' % (code.co_filename)
        print >> out_file, 'calls=%d %d' % (
            subentry.callcount, code.co_firstlineno)
        print >> out_file, '%d %d' % (lineno, totaltime)


_fn2mod = {}


def label(code, calltree=False):
    if isinstance(code, str):
        return code
    try:
        mname = _fn2mod[code.co_filename]
    except KeyError:
        for k, v in sys.modules.items():
            if v is None:
                continue
            if getattr(v, '__file__', None) is None:
                continue
            if not isinstance(v.__file__, str):
                continue
            if v.__file__.startswith(code.co_filename):
                mname = _fn2mod[code.co_filename] = k
                break
        else:
            mname = _fn2mod[code.co_filename] = '<%s>'%code.co_filename
    if calltree:
        return '%s %s:%d' % (code.co_name, mname, code.co_firstlineno)
    else:
        return '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)


if __name__ == '__main__':
    import os
    sys.argv = sys.argv[1:]
    if not sys.argv:
        print >> sys.stderr, "usage: lsprof.py 
      
    
  

  
      
          
            
          
      
    
    
        
    

    

Sorry, no results found for your search.

No revisions!

expand all expand all

Rev   Summary Authors Tags Date Diff Files
Diff Files
loggerhead-1.19~bzr511/loggerhead/templates/collapse-all-button.pt0000644000000000000000000000134113731072134023363 0ustar 00000000000000 collapse collapse all expand expand all loggerhead-1.19~bzr511/loggerhead/templates/collapse-button.pt0000644000000000000000000000141113731072134022613 0ustar 00000000000000 collapse expand loggerhead-1.19~bzr511/loggerhead/templates/directory.pt0000644000000000000000000000722113731072134021511 0ustar 00000000000000

Browsing

Filename Latest Rev Last Changed Committer Comment
..
Branch
Folder
loggerhead-1.19~bzr511/loggerhead/templates/error.pt0000644000000000000000000000206113731072134020633 0ustar 00000000000000

nice/branch/name : error

            



loggerhead-1.19~bzr511/loggerhead/templates/feed-link.pt0000644000000000000000000000042313731072134021340 0ustar 00000000000000 RSS loggerhead-1.19~bzr511/loggerhead/templates/filediff.pt0000644000000000000000000000165613731072134021263 0ustar 00000000000000
loggerhead-1.19~bzr511/loggerhead/templates/inventory.pt0000644000000000000000000002424613731072134021550 0ustar 00000000000000 loggerhead-1.19~bzr511/loggerhead/templates/macros.pt0000644000000000000000000000465113731072134020775 0ustar 00000000000000

loggerhead-1.19~bzr511/loggerhead/templates/menu.pt0000644000000000000000000000156113731072134020452 0ustar 00000000000000 loggerhead-1.19~bzr511/loggerhead/templates/revision.pt0000644000000000000000000002664213731072134021353 0ustar 00000000000000

Viewing all changes in revision .

« back to all changes in this revision

Viewing changes to

expand all expand all

Show diffs side-by-side

added added

removed removed

Lines of Context:
loggerhead-1.19~bzr511/loggerhead/templates/revisionfilechanges.pt0000644000000000000000000000420113731072134023527 0ustar 00000000000000
  • files added:
  • files removed:
  • files renamed:
  • old_filename => new_filename
  • files modified:
loggerhead-1.19~bzr511/loggerhead/templates/revisioninfo.pt0000644000000000000000000000572213731072134022223 0ustar 00000000000000
  • Committer:
  • Author(s):
  • Date:
  • mfrom:
  • mto:
  • mto: This revision was merged to the branch mainline in revision .
  • Revision ID:
loggerhead-1.19~bzr511/loggerhead/templates/revlog.pt0000644000000000000000000000140113731072134020775 0ustar 00000000000000
loggerhead-1.19~bzr511/loggerhead/templates/search-box.pt0000644000000000000000000000062713731072134021543 0ustar 00000000000000
loggerhead-1.19~bzr511/loggerhead/templates/search.pt0000644000000000000000000000055113731072134020751 0ustar 00000000000000
  • No results found.
loggerhead-1.19~bzr511/loggerhead/templates/view.pt0000644000000000000000000001177113731072134020464 0ustar 00000000000000 loggerhead-1.19~bzr511/loggerhead/tests/__init__.py0000644000000000000000000000227713731072134020423 0ustar 00000000000000# Copyright 2006, 2010, 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 __future__ import absolute_import def test_suite(): import unittest loader = unittest.TestLoader() return loader.loadTestsFromNames([ (__name__ + '.' + x) for x in [ 'test_controllers', 'test_corners', 'test_history', 'test_http_head', 'test_load_test', 'test_simple', 'test_revision_ui', 'test_templating', 'test_util', ]]) loggerhead-1.19~bzr511/loggerhead/tests/fixtures.py0000644000000000000000000000317213731072134020530 0ustar 00000000000000# Copyright (C) 2007, 2008, 2009, 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 __future__ import absolute_import from fixtures import Fixture class SampleBranch(Fixture): def __init__(self, testcase): # Must be a bzr TestCase to hook into branch creation, unfortunately. self.testcase = testcase def setUp(self): Fixture.setUp(self) self.tree = self.testcase.make_branch_and_tree('.') self.filecontents = ( 'some\nmultiline\ndata\n' 'with simple test page title
Hello, name
loggerhead-1.19~bzr511/loggerhead/tests/test_controllers.py0000644000000000000000000004550113731072134022266 0ustar 00000000000000# Copyright (C) 2008-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 __future__ import absolute_import import tarfile import tempfile from paste.fixture import ( AppError, ) from paste.httpexceptions import HTTPNotFound from testtools.matchers import ( Matcher, Mismatch, ) from ..apps.branch import BranchWSGIApp from ..controllers.annotate_ui import AnnotateUI from ..controllers.inventory_ui import InventoryUI from .test_simple import ( BasicTests, consume_app, TestWithSimpleTree, ) class TestInventoryUI(BasicTests): def make_bzrbranch_for_tree_shape(self, shape): tree = self.make_branch_and_tree('.') self.build_tree(shape) tree.smart_add([]) tree.commit('') self.addCleanup(tree.branch.lock_read().unlock) return tree.branch def make_bzrbranch_and_inventory_ui_for_tree_shape(self, shape): branch = self.make_bzrbranch_for_tree_shape(shape) branch_app = self.make_branch_app(branch) return branch, InventoryUI(branch_app, branch_app.get_history) def test_get_filelist(self): bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape( ['filename']) revtree = bzrbranch.repository.revision_tree(bzrbranch.last_revision()) self.assertEqual(1, len(inv_ui.get_filelist(revtree, '', 'filename', 'head'))) def test_smoke(self): bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape( ['filename']) start, content = consume_app(inv_ui, {'SCRIPT_NAME': '/files', 'PATH_INFO': ''}) self.assertEqual(('200 OK', [('Content-Type', 'text/html')], None), start) self.assertContainsRe(content, b'filename') def test_no_content_for_HEAD(self): bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape( ['filename']) start, content = consume_app(inv_ui, {'SCRIPT_NAME': '/files', 'PATH_INFO': '', 'REQUEST_METHOD': 'HEAD', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'}) self.assertEqual(('200 OK', [('Content-Type', 'text/html')], None), start) self.assertEqual(b'', content) def test_get_values_smoke(self): branch = self.make_bzrbranch_for_tree_shape(['a-file']) branch_app = self.make_branch_app(branch) env = {'SCRIPT_NAME': '', 'PATH_INFO': '/files', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} inv_ui = branch_app.lookup_app(env) inv_ui.parse_args(env) values = inv_ui.get_values('', {}, {}) self.assertEqual('a-file', values['filelist'][0].filename) def test_json_render_smoke(self): branch = self.make_bzrbranch_for_tree_shape(['a-file']) branch_app = self.make_branch_app(branch) env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/files', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} inv_ui = branch_app.lookup_app(env) self.assertOkJsonResponse(inv_ui, env) class TestRevisionUI(BasicTests): def make_branch_app_for_revision_ui(self, shape1, shape2): tree = self.make_branch_and_tree('.') self.build_tree_contents(shape1) tree.smart_add([]) tree.commit('msg 1', rev_id=b'rev-1') self.build_tree_contents(shape2) tree.smart_add([]) tree.commit('msg 2', rev_id=b'rev-2') branch = tree.branch self.addCleanup(branch.lock_read().unlock) return self.make_branch_app(branch) def test_get_values(self): branch_app = self.make_branch_app_for_revision_ui([], []) env = {'SCRIPT_NAME': '', 'PATH_INFO': '/revision/2', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} rev_ui = branch_app.lookup_app(env) rev_ui.parse_args(env) self.assertIsInstance(rev_ui.get_values('', {}, []), dict) def test_add_template_values(self): branch_app = self.make_branch_app_for_revision_ui( [('file', b'content\n')], [('file', b'new content\n')]) env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/revision/1/non-existent-file', 'QUERY_STRING':'start_revid=1', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revision_ui = branch_app.lookup_app(env) path = revision_ui.parse_args(env) values = revision_ui.get_values(path, revision_ui.kwargs, {}) revision_ui.add_template_values(values) self.assertIs(values['diff_chunks'], None) def test_add_template_values_with_changes(self): branch_app = self.make_branch_app_for_revision_ui( [('file', b'content\n')], [('file', b'new content\n')]) env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/revision/1/file', 'QUERY_STRING':'start_revid=1', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revision_ui = branch_app.lookup_app(env) path = revision_ui.parse_args(env) values = revision_ui.get_values(path, revision_ui.kwargs, {}) revision_ui.add_template_values(values) self.assertEqual(len(values['diff_chunks']), 1) def test_get_values_smoke(self): branch_app = self.make_branch_app_for_revision_ui( [('file', b'content\n'), ('other-file', b'other\n')], [('file', b'new content\n')]) env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/revision/head:', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revision_ui = branch_app.lookup_app(env) revision_ui.parse_args(env) values = revision_ui.get_values('', {}, {}) self.assertEqual(values['revid'], 'rev-2') self.assertEqual(values['change'].comment, 'msg 2') self.assertEqual(values['file_changes'].modified[0].filename, 'file') self.assertEqual(values['merged_in'], None) def test_json_render_smoke(self): branch_app = self.make_branch_app_for_revision_ui( [('file', b'content\n'), ('other-file', b'other\n')], [('file', b'new content\n')]) env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/revision/head:', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revision_ui = branch_app.lookup_app(env) self.assertOkJsonResponse(revision_ui, env) class TestAnnotateUI(BasicTests): def make_annotate_ui_for_file_history(self, file_id, rev_ids_texts): tree = self.make_branch_and_tree('.') self.build_tree_contents([('filename', '')]) tree.add(['filename'], [file_id]) for rev_id, text, message in rev_ids_texts: self.build_tree_contents([('filename', text)]) tree.commit(rev_id=rev_id, message=message) tree.branch.lock_read() self.addCleanup(tree.branch.unlock) branch_app = BranchWSGIApp(tree.branch, friendly_name='test_name') return AnnotateUI(branch_app, branch_app.get_history) def test_annotate_file(self): history = [(b'rev1', b'old\nold\n', '.'), (b'rev2', b'new\nold\n', '.')] ann_ui = self.make_annotate_ui_for_file_history(b'file_id', history) # A lot of this state is set up by __call__, but we'll do it directly # here. ann_ui.args = ['rev2'] annotate_info = ann_ui.get_values(u'filename', kwargs={'file_id': 'file_id'}, headers={}) annotated = annotate_info['annotated'] self.assertEqual(2, len(annotated)) self.assertEqual('2', annotated[1].change.revno) self.assertEqual('1', annotated[2].change.revno) def test_annotate_empty_comment(self): # Testing empty comment handling without breaking history = [(b'rev1', b'old\nold\n', '.'), (b'rev2', b'new\nold\n', '')] ann_ui = self.make_annotate_ui_for_file_history(b'file_id', history) ann_ui.args = ['rev2'] ann_ui.get_values( u'filename', kwargs={'file_id': 'file_id'}, headers={}) def test_annotate_file_zero_sized(self): # Test against a zero-sized file without breaking. No annotation # must be present. history = [(b'rev1', b'', '.')] ann_ui = self.make_annotate_ui_for_file_history(b'file_id', history) ann_ui.args = ['rev1'] annotate_info = ann_ui.get_values(u'filename', kwargs={'file_id': 'file_id'}, headers={}) annotated = annotate_info['annotated'] self.assertEqual(0, len(annotated)) def test_annotate_nonexistent_file(self): history = [(b'rev1', b'', '.')] ann_ui = self.make_annotate_ui_for_file_history(b'file_id', history) ann_ui.args = ['rev1'] self.assertRaises( HTTPNotFound, ann_ui.get_values, u'not-filename', {}, {}) def test_annotate_nonexistent_rev(self): history = [(b'rev1', b'', '.')] ann_ui = self.make_annotate_ui_for_file_history(b'file_id', history) ann_ui.args = ['norev'] self.assertRaises( HTTPNotFound, ann_ui.get_values, u'not-filename', {}, {}) class TestFileDiffUI(BasicTests): def make_branch_app_for_filediff_ui(self): builder = self.make_branch_builder('branch') builder.start_series() rev1 = builder.build_snapshot(None, [ ('add', ('', None, 'directory', '')), ('add', ('filename', None, 'file', b'content\n'))], message="First commit.") rev2 = builder.build_snapshot(None, [ ('modify', ('filename', b'new content\n'))]) builder.finish_series() branch = builder.get_branch() self.addCleanup(branch.lock_read().unlock) return self.make_branch_app(branch), (rev1, rev2) def test_get_values_smoke(self): branch_app, (rev1, rev2) = self.make_branch_app_for_filediff_ui() env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/+filediff/%s/%s/filename' % (rev2.decode('utf-8'), rev1.decode('utf-8')), 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} filediff_ui = branch_app.lookup_app(env) filediff_ui.parse_args(env) values = filediff_ui.get_values('', {}, {}) chunks = values['chunks'] self.assertEqual('insert', chunks[0].diff[1].type) self.assertEqual('new content', chunks[0].diff[1].line) def test_json_render_smoke(self): branch_app, (rev1, rev2) = self.make_branch_app_for_filediff_ui() env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/+json/+filediff/%s/%s/filename' % (rev2.decode('utf-8'), rev1.decode('utf-8')), 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} filediff_ui = branch_app.lookup_app(env) self.assertOkJsonResponse(filediff_ui, env) class TestRevLogUI(BasicTests): def make_branch_app_for_revlog_ui(self): builder = self.make_branch_builder('branch') builder.start_series() revid = builder.build_snapshot(None, [ ('add', ('', None, 'directory', '')), ('add', ('filename', None, 'file', b'content\n'))], message="First commit.") builder.finish_series() branch = builder.get_branch() self.addCleanup(branch.lock_read().unlock) return self.make_branch_app(branch), revid def test_get_values_smoke(self): branch_app, revid = self.make_branch_app_for_revlog_ui() env = {'SCRIPT_NAME': '/', 'PATH_INFO': '/+revlog/%s' % revid.decode('utf-8'), 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revlog_ui = branch_app.lookup_app(env) revlog_ui.parse_args(env) values = revlog_ui.get_values('', {}, {}) self.assertEqual(values['file_changes'].added[1].filename, 'filename') self.assertEqual(values['entry'].comment, "First commit.") def test_json_render_smoke(self): branch_app, revid = self.make_branch_app_for_revlog_ui() env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/+revlog/%s' % revid.decode('utf-8'), 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} revlog_ui = branch_app.lookup_app(env) self.assertOkJsonResponse(revlog_ui, env) class TestControllerHooks(BasicTests): def test_dummy_hook(self): return # A hook that returns None doesn't influence the searching for # a controller. env = {'SCRIPT_NAME': '', 'PATH_INFO': '/custom', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} myhook = lambda app, environ: None branch = self.make_branch('.') self.addCleanup(branch.lock_read().unlock) app = self.make_branch_app(branch) self.addCleanup(BranchWSGIApp.hooks.uninstall_named_hook, 'controller', 'captain hook') BranchWSGIApp.hooks.install_named_hook('controller', myhook, "captain hook") self.assertRaises(KeyError, app.lookup_app, env) def test_working_hook(self): # A hook can provide an app to use for a particular request. env = {'SCRIPT_NAME': '', 'PATH_INFO': '/custom', 'REQUEST_METHOD': 'GET', 'wsgi.url_scheme': 'http', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80'} myhook = lambda app, environ: "I am hooked" branch = self.make_branch('.') self.addCleanup(branch.lock_read().unlock) app = self.make_branch_app(branch) self.addCleanup(BranchWSGIApp.hooks.uninstall_named_hook, 'controller', 'captain hook') BranchWSGIApp.hooks.install_named_hook('controller', myhook, "captain hook") self.assertEquals("I am hooked", app.lookup_app(env)) class MatchesDownloadHeaders(Matcher): def __init__(self, expect_filename, expect_mimetype): self.expect_filename = expect_filename self.expect_mimetype = expect_mimetype def match(self, response): # Maybe the c-t should be more specific, but this is probably good for # making sure it gets saved without the client trying to decompress it # or anything. if (response.header('Content-Type') == self.expect_mimetype and response.header('Content-Disposition') == "attachment; filename*=utf-8''" + self.expect_filename): pass else: return Mismatch("wrong response headers: %r" % response.headers) def __str__(self): return 'MatchesDownloadHeaders(%r, %r)' % ( self.expect_filename, self.expect_mimetype) class TestDownloadUI(TestWithSimpleTree): def test_download(self): app = self.setUpLoggerhead() response = app.get('/download/1/myfilename') self.assertEqual( b'some\nmultiline\ndata\nwithhi' self.addFileAndCommit('myfilename', msg) app = self.setUpLoggerhead() res = app.get('/revision/1') self.assertFalse(msg in res.body) def test_empty_commit_message(self): """Check that an empty commit message does not break the rendering.""" self.createBranch() # Make a commit that has an empty message. self.addFileAndCommit('myfilename', '') # Check that it didn't break things. app = self.setUpLoggerhead() res = app.get('/changes') # It's not much of an assertion, but we only really care about # "assert not crashed". res.mustcontain('1') def test_whitespace_only_commit_message(self): """Check that a whitespace-only commit message does not break the rendering.""" self.createBranch() # Make a commit that has a whitespace only message. self.addFileAndCommit('myfilename', ' ') # Check that it didn't break things. app = self.setUpLoggerhead() res = app.get('/changes') # It's not much of an assertion, but we only really care about # "assert not crashed". res.mustcontain('1') loggerhead-1.19~bzr511/loggerhead/tests/test_history.py0000644000000000000000000003212213731072134021414 0ustar 00000000000000# 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 # """Direct tests of the loggerhead/history.py module""" from breezy.foreign import ( ForeignRevision, ForeignVcs, VcsMapping, ) from datetime import datetime from breezy import tag, tests from .. import history as _mod_history class TestCaseWithExamples(tests.TestCaseWithMemoryTransport): def make_linear_ancestry(self): # Time goes up # rev-3 # | # rev-2 # | # rev-1 builder = self.make_branch_builder('branch') builder.start_series() rev1 = builder.build_snapshot(None, [ ('add', ('', b'root-id', 'directory', None))]) rev2 = builder.build_snapshot([rev1], []) rev3 = builder.build_snapshot([rev2], []) builder.finish_series() b = builder.get_branch() self.addCleanup(b.lock_read().unlock) return _mod_history.History(b, {}), [rev1, rev2, rev3] def make_long_linear_ancestry(self): builder = self.make_branch_builder('branch') revs = [] builder.start_series() revs.append(builder.build_snapshot(None, [ ('add', ('', b'root-id', 'directory', None))])) for r in "BCDEFGHIJKLMNOPQRSTUVWXYZ": revs.append(builder.build_snapshot(None, [])) builder.finish_series() b = builder.get_branch() self.addCleanup(b.lock_read().unlock) return _mod_history.History(b, {}), revs def make_merged_ancestry(self): # Time goes up # rev-3 # | \ # | rev-2 # | / # rev-1 builder = self.make_branch_builder('branch') builder.start_series() rev1 = builder.build_snapshot(None, [ ('add', ('', b'root-id', 'directory', None))]) rev2 = builder.build_snapshot([rev1], []) rev3 = builder.build_snapshot([rev1, rev2], []) builder.finish_series() b = builder.get_branch() self.addCleanup(b.lock_read().unlock) return _mod_history.History(b, {}), [rev1, rev2, rev3] def make_deep_merged_ancestry(self): # Time goes up # F # |\ # | E # | |\ # | | D # | |/ # B C # |/ # A builder = self.make_branch_builder('branch') builder.start_series() rev_a = builder.build_snapshot(None, [ ('add', ('', b'root-id', 'directory', None))]) rev_b = builder.build_snapshot([rev_a], []) rev_c = builder.build_snapshot([rev_a], []) rev_d = builder.build_snapshot([rev_c], []) rev_e = builder.build_snapshot([rev_c, rev_d], []) rev_f = builder.build_snapshot([rev_b, rev_e], []) builder.finish_series() b = builder.get_branch() self.addCleanup(b.lock_read().unlock) return (_mod_history.History(b, {}), [rev_a, rev_b, rev_c, rev_d, rev_e, rev_f]) def assertRevidsFrom(self, expected, history, search_revs, tip_rev): self.assertEqual(expected, list(history.get_revids_from(search_revs, tip_rev))) class _DictProxy(object): def __init__(self, d): self._d = d self._accessed = set() self.__setitem__ = d.__setitem__ def __getitem__(self, name): self._accessed.add(name) return self._d[name] def __len__(self): return len(self._d) def track_rev_info_accesses(h): """Track __getitem__ access to History._rev_info, :return: set of items accessed """ h._rev_info = _DictProxy(h._rev_info) return h._rev_info._accessed class TestHistoryGetRevidsFrom(TestCaseWithExamples): def test_get_revids_from_simple_mainline(self): history, revs = self.make_linear_ancestry() self.assertRevidsFrom(list(reversed(revs)), history, None, revs[2]) def test_get_revids_from_merged_mainline(self): history, revs = self.make_merged_ancestry() self.assertRevidsFrom([revs[2], revs[0]], history, None, revs[2]) def test_get_revids_given_one_rev(self): history, revs = self.make_merged_ancestry() # rev-3 was the first mainline revision to see rev-2. self.assertRevidsFrom([revs[2]], history, [revs[1]], revs[2]) def test_get_revids_deep_ancestry(self): history, revs = self.make_deep_merged_ancestry() self.assertRevidsFrom([revs[-1]], history, [revs[-1]], revs[-1]) self.assertRevidsFrom([revs[-1]], history, [revs[-2]], revs[-1]) self.assertRevidsFrom([revs[-1]], history, [revs[-3]], revs[-1]) self.assertRevidsFrom([revs[-1]], history, [revs[-4]], revs[-1]) self.assertRevidsFrom([revs[1]], history, [revs[-5]], revs[-1]) self.assertRevidsFrom([revs[0]], history, [revs[-6]], revs[-1]) def test_get_revids_doesnt_over_produce_simple_mainline(self): # get_revids_from shouldn't walk the whole ancestry just to get the # answers for the first few revisions. history, revs = self.make_long_linear_ancestry() accessed = track_rev_info_accesses(history) result = history.get_revids_from(None, revs[-1]) self.assertEqual(set(), accessed) self.assertEqual(revs[-1], next(result)) # We already know revs[-1] because we passed it in. self.assertEqual(set(), accessed) self.assertEqual(revs[-2], next(result)) self.assertEqual(set([history._rev_indices[revs[-1]]]), accessed) def test_get_revids_doesnt_over_produce_for_merges(self): # get_revids_from shouldn't walk the whole ancestry just to get the # answers for the first few revisions. history, revs = self.make_long_linear_ancestry() accessed = track_rev_info_accesses(history) result = history.get_revids_from([revs[-3], revs[-5]], revs[-1]) self.assertEqual(set(), accessed) self.assertEqual(revs[-3], next(result)) # We access 'W' because we are checking that W wasn't merged into X. # The important bit is that we aren't getting the whole ancestry. self.assertEqual(set([history._rev_indices[x] for x in list(reversed(revs))[:4]]), accessed) self.assertEqual(revs[-5], next(result)) self.assertEqual(set([history._rev_indices[x] for x in list(reversed(revs))[:6]]), accessed) self.assertRaises(StopIteration, next, result) self.assertEqual(set([history._rev_indices[x] for x in list(reversed(revs))[:6]]), accessed) class TestHistoryChangeFromRevision(tests.TestCaseWithTransport): def make_single_commit(self): tree = self.make_branch_and_tree('test') rev_id = tree.commit('Commit Message', timestamp=1299838474.317, timezone=3600, committer='Joe Example ', revprops={}) self.addCleanup(tree.branch.lock_write().unlock) rev = tree.branch.repository.get_revision(rev_id) history = _mod_history.History(tree.branch, {}) return history, rev def test_simple(self): history, rev = self.make_single_commit() change = history._change_from_revision(rev) self.assertEqual(rev.revision_id, change.revid) self.assertEqual(datetime.fromtimestamp(1299838474.317), change.date) self.assertEqual(datetime.utcfromtimestamp(1299838474.317), change.utc_date) self.assertEqual(['Joe Example '], change.authors) self.assertEqual('test', change.branch_nick) self.assertEqual('Commit Message', change.short_comment) self.assertEqual('Commit Message', change.comment) self.assertEqual(['Commit Message'], change.comment_clean) self.assertEqual([], change.parents) self.assertEqual([], change.bugs) self.assertEqual(None, change.tags) def test_tags(self): history, rev = self.make_single_commit() b = history._branch b.tags.set_tag('tag-1', rev.revision_id) b.tags.set_tag('tag-2', rev.revision_id) b.tags.set_tag('Tag-10', rev.revision_id) change = history._change_from_revision(rev) # If available, tags are 'naturally' sorted. (sorting numbers in order, # and ignoring case, etc.) if getattr(tag, 'sort_natural', None) is not None: self.assertEqual('tag-1, tag-2, Tag-10', change.tags) else: self.assertEqual('Tag-10, tag-1, tag-2', change.tags) def test_committer_vs_authors(self): tree = self.make_branch_and_tree('test') rev_id = tree.commit('Commit Message', timestamp=1299838474.317, timezone=3600, committer='Joe Example ', revprops={'authors': u'A Author \n' u'B Author '}) self.addCleanup(tree.branch.lock_write().unlock) rev = tree.branch.repository.get_revision(rev_id) history = _mod_history.History(tree.branch, {}) change = history._change_from_revision(rev) self.assertEqual(u'Joe Example ', change.committer) self.assertEqual([u'A Author ', u'B Author '], change.authors) class TestHistory_IterateSufficiently(tests.TestCase): def assertIterate(self, expected, iterable, stop_at, extra_rev_count): self.assertEqual(expected, _mod_history.History._iterate_sufficiently( iterable, stop_at, extra_rev_count)) def test_iter_no_extra(self): lst = list('abcdefghijklmnopqrstuvwxyz') self.assertIterate(['a', 'b', 'c'], iter(lst), 'c', 0) self.assertIterate(['a', 'b', 'c', 'd'], iter(lst), 'd', 0) def test_iter_not_found(self): # If the key in question isn't found, we just exhaust the list lst = list('abcdefghijklmnopqrstuvwxyz') self.assertIterate(lst, iter(lst), 'not-there', 0) def test_iter_with_extra(self): lst = list('abcdefghijklmnopqrstuvwxyz') self.assertIterate(['a', 'b', 'c'], iter(lst), 'b', 1) self.assertIterate(['a', 'b', 'c', 'd', 'e'], iter(lst), 'c', 2) def test_iter_with_too_many_extra(self): lst = list('abcdefghijklmnopqrstuvwxyz') self.assertIterate(lst, iter(lst), 'y', 10) self.assertIterate(lst, iter(lst), 'z', 10) def test_iter_with_extra_None(self): lst = list('abcdefghijklmnopqrstuvwxyz') self.assertIterate(lst, iter(lst), 'z', None) class TestHistoryGetView(TestCaseWithExamples): def test_get_view_limited_history(self): # get_view should only load enough history to serve the result, not all # history. history, revs = self.make_long_linear_ancestry() accessed = track_rev_info_accesses(history) revid, start_revid, revid_list = history.get_view(revs[-1], revs[-1], None, extra_rev_count=5) self.assertEqual(list(reversed(revs))[:6], revid_list) self.assertEqual(revs[-1], revid) self.assertEqual(revs[-1], start_revid) self.assertEqual(set([history._rev_indices[x] for x in list(reversed(revs))[:6]]), accessed) class TestHistoryGetChangedUncached(TestCaseWithExamples): def test_native(self): history, revs = self.make_linear_ancestry() changes = history.get_changes_uncached([revs[0], revs[1]]) self.assertEquals(2, len(changes)) self.assertEquals(revs[0], changes[0].revid) self.assertEquals(revs[1], changes[1].revid) self.assertIs(None, getattr(changes[0], 'foreign_vcs', None)) self.assertIs(None, getattr(changes[0], 'foreign_revid', None)) def test_foreign(self): # Test with a mocked foreign revision, as it's not possible # to rely on any foreign plugins being installed. history, revs = self.make_linear_ancestry() foreign_vcs = ForeignVcs(None, "vcs") foreign_vcs.show_foreign_revid = repr foreign_rev = ForeignRevision(("uuid", 1234), VcsMapping(foreign_vcs), "revid-in-bzr", message="message", timestamp=234423423.3) change = history._change_from_revision(foreign_rev) self.assertEquals('revid-in-bzr', change.revid) self.assertEquals("('uuid', 1234)", change.foreign_revid) self.assertEquals("vcs", change.foreign_vcs) loggerhead-1.19~bzr511/loggerhead/tests/test_http_head.py0000644000000000000000000000573013731072134021660 0ustar 00000000000000# Copyright 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 """Tests for the HeadMiddleware app.""" from io import BytesIO from breezy import tests from ..apps import http_head content = [b"", b"Listed", b"Content", b"", ] headers = {'X-Ignored-Header': 'Value'} def yielding_app(environ, start_response): writer = start_response('200 OK', headers) for chunk in content: yield chunk def list_app(environ, start_response): writer = start_response('200 OK', headers) return content def writer_app(environ, start_response): writer = start_response('200 OK', headers) for chunk in content: writer(chunk) return [] class TestHeadMiddleware(tests.TestCase): def _trap_start_response(self, status, response_headers, exc_info=None): self._write_buffer = BytesIO() self._start_response_passed = (status, response_headers, exc_info) return self._write_buffer.write def _consume_app(self, app, request_method): environ = {'REQUEST_METHOD': request_method} value = list(app(environ, self._trap_start_response)) self._write_buffer.writelines(value) def _verify_get_passthrough(self, app): app = http_head.HeadMiddleware(app) self._consume_app(app, 'GET') self.assertEqual(('200 OK', headers, None), self._start_response_passed) self.assertEqualDiff(b''.join(content), self._write_buffer.getvalue()) def _verify_head_no_body(self, app): app = http_head.HeadMiddleware(app) self._consume_app(app, 'HEAD') self.assertEqual(('200 OK', headers, None), self._start_response_passed) self.assertEqualDiff(b'', self._write_buffer.getvalue()) def test_get_passthrough_yielding(self): self._verify_get_passthrough(yielding_app) def test_head_passthrough_yielding(self): self._verify_head_no_body(yielding_app) def test_get_passthrough_list(self): self._verify_get_passthrough(list_app) def test_head_passthrough_list(self): self._verify_head_no_body(list_app) def test_get_passthrough_writer(self): self._verify_get_passthrough(writer_app) def test_head_passthrough_writer(self): self._verify_head_no_body(writer_app) loggerhead-1.19~bzr511/loggerhead/tests/test_load_test.py0000644000000000000000000003054213731072134021675 0ustar 00000000000000# Copyright 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 # """Tests for the load testing code.""" import socket import time import threading try: from queue import Empty except ImportError: # Python < 3 from Queue import Empty from breezy import tests from breezy.tests import http_server from .. import load_test empty_script = """{ "parameters": {}, "requests": [] }""" class TestRequestDescription(tests.TestCase): def test_init_from_dict(self): rd = load_test.RequestDescription({'thread': '10', 'relpath': '/foo'}) self.assertEqual('10', rd.thread) self.assertEqual('/foo', rd.relpath) def test_default_thread_is_1(self): rd = load_test.RequestDescription({'relpath': '/bar'}) self.assertEqual('1', rd.thread) self.assertEqual('/bar', rd.relpath) _cur_time = time.time() def one_sec_timer(): """Every time this timer is called, it increments by 1 second.""" global _cur_time _cur_time += 1.0 return _cur_time class NoopRequestWorker(load_test.RequestWorker): # Every call to _timer will increment by one _timer = staticmethod(one_sec_timer) # Ensure that process never does anything def process(self, url): return True class TestRequestWorkerInfrastructure(tests.TestCase): """Tests various infrastructure bits, without doing actual requests.""" def test_step_next_tracks_time(self): rt = NoopRequestWorker('id') rt.queue.put('item') rt.step_next() self.assertTrue(rt.queue.empty()) self.assertEqual([('item', True, 1.0)], rt.stats) def test_step_multiple_items(self): rt = NoopRequestWorker('id') rt.queue.put('item') rt.step_next() rt.queue.put('next-item') rt.step_next() self.assertTrue(rt.queue.empty()) self.assertEqual([('item', True, 1.0), ('next-item', True, 1.0)], rt.stats) def test_step_next_does_nothing_for_noop(self): rt = NoopRequestWorker('id') rt.queue.put('item') rt.step_next() rt.queue.put('') rt.step_next() self.assertEqual([('item', True, 1.0)], rt.stats) def test_step_next_will_timeout(self): # We don't want step_next to block forever rt = NoopRequestWorker('id', blocking_time=0.001) self.assertRaises(Empty, rt.step_next) def test_run_stops_for_stop_event(self): rt = NoopRequestWorker('id', blocking_time=0.001, _queue_size=2) rt.queue.put('item1') rt.queue.put('item2') event = threading.Event() t = threading.Thread(target=rt.run, args=(event,)) t.start() # Wait for the queue to be processed rt.queue.join() # Now we can queue up another one, and wait for it rt.queue.put('item3') rt.queue.join() # Now set the stopping event event.set() # Add another item to the queue, which might get processed, but the # next item won't rt.queue.put('item4') rt.queue.put('item5') t.join() self.assertEqual([('item1', True, 1.0), ('item2', True, 1.0), ('item3', True, 1.0)], rt.stats[:3]) # The last event might be item4 or might be item3, the important thing # is that even though there are still queued events, we won't # process anything past the first self.assertNotEqual('item5', rt.stats[-1][0]) class TestRequestWorker(tests.TestCaseWithTransport): def setUp(self): super(TestRequestWorker, self).setUp() self.transport_readonly_server = http_server.HttpServer def test_request_items(self): rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2) self.build_tree(['file1', 'file2']) readonly_url1 = self.get_readonly_url('file1') self.assertStartsWith(readonly_url1, 'http://') readonly_url2 = self.get_readonly_url('file2') rt.queue.put(readonly_url1) rt.queue.put(readonly_url2) rt.step_next() rt.step_next() self.assertEqual(readonly_url1, rt.stats[0][0]) self.assertEqual(readonly_url2, rt.stats[1][0]) def test_request_nonexistant_items(self): rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2) readonly_url1 = self.get_readonly_url('no-file1') rt.queue.put(readonly_url1) rt.step_next() self.assertEqual(readonly_url1, rt.stats[0][0]) self.assertEqual(False, rt.stats[0][1]) def test_no_server(self): s = socket.socket() # Bind to a port, but don't listen on it s.bind(('localhost', 0)) url = 'http://%s:%s/path' % s.getsockname() rt = load_test.RequestWorker('id', blocking_time=0.01, _queue_size=2) rt.queue.put(url) rt.step_next() self.assertEqual((url, False), rt.stats[0][:2]) class NoActionScript(load_test.ActionScript): _thread_class = NoopRequestWorker _default_blocking_timeout = 0.01 class TestActionScriptInfrastructure(tests.TestCase): def test_parse_requires_parameters_and_requests(self): self.assertRaises(ValueError, load_test.ActionScript.parse, '') self.assertRaises(ValueError, load_test.ActionScript.parse, '{}') self.assertRaises(ValueError, load_test.ActionScript.parse, '{"parameters": {}}') self.assertRaises(ValueError, load_test.ActionScript.parse, '{"requests": []}') load_test.ActionScript.parse( '{"parameters": {}, "requests": [], "comment": "section"}') script = load_test.ActionScript.parse( empty_script) self.assertIsNot(None, script) def test_parse_default_base_url(self): script = load_test.ActionScript.parse(empty_script) self.assertEqual('http://localhost:8080', script.base_url) def test_parse_find_base_url(self): script = load_test.ActionScript.parse( '{"parameters": {"base_url": "http://example.com"},' ' "requests": []}') self.assertEqual('http://example.com', script.base_url) def test_parse_default_blocking_timeout(self): script = load_test.ActionScript.parse(empty_script) self.assertEqual(60.0, script.blocking_timeout) def test_parse_find_blocking_timeout(self): script = load_test.ActionScript.parse( '{"parameters": {"blocking_timeout": 10.0},' ' "requests": []}' ) self.assertEqual(10.0, script.blocking_timeout) def test_parse_finds_requests(self): script = load_test.ActionScript.parse( '{"parameters": {}, "requests": [' ' {"relpath": "/foo"},' ' {"relpath": "/bar"}' ' ]}') self.assertEqual(2, len(script._requests)) self.assertEqual("/foo", script._requests[0].relpath) self.assertEqual("/bar", script._requests[1].relpath) def test__get_worker(self): script = NoActionScript() # If an id is found, then we should create it self.assertEqual({}, script._threads) worker = script._get_worker('id') self.assertTrue('id' in script._threads) # We should have set the blocking timeout self.assertEqual(script.blocking_timeout / 10.0, worker.blocking_time) # Another request will return the identical object self.assertIs(worker, script._get_worker('id')) # And the stop event will stop the thread script.stop_and_join() def test__full_url(self): script = NoActionScript() self.assertEqual('http://localhost:8080/path', script._full_url('/path')) self.assertEqual('http://localhost:8080/path/to/foo', script._full_url('/path/to/foo')) script.base_url = 'http://example.com' self.assertEqual('http://example.com/path/to/foo', script._full_url('/path/to/foo')) script.base_url = 'http://example.com/base' self.assertEqual('http://example.com/base/path/to/foo', script._full_url('/path/to/foo')) script.base_url = 'http://example.com' self.assertEqual('http://example.com:8080/path', script._full_url(':8080/path')) def test_single_threaded(self): script = NoActionScript.parse("""{ "parameters": {"base_url": ""}, "requests": [ {"thread": "1", "relpath": "first"}, {"thread": "1", "relpath": "second"}, {"thread": "1", "relpath": "third"}, {"thread": "1", "relpath": "fourth"} ]}""") script.run() worker = script._get_worker("1") self.assertEqual(["first", "second", "third", "fourth"], [s[0] for s in worker.stats]) def test_two_threads(self): script = NoActionScript.parse("""{ "parameters": {"base_url": ""}, "requests": [ {"thread": "1", "relpath": "first"}, {"thread": "2", "relpath": "second"}, {"thread": "1", "relpath": "third"}, {"thread": "2", "relpath": "fourth"} ]}""") script.run() worker = script._get_worker("1") self.assertEqual(["first", "third"], [s[0] for s in worker.stats]) worker = script._get_worker("2") self.assertEqual(["second", "fourth"], [s[0] for s in worker.stats]) class TestActionScriptIntegration(tests.TestCaseWithTransport): def setUp(self): super(TestActionScriptIntegration, self).setUp() self.transport_readonly_server = http_server.HttpServer def test_full_integration(self): self.build_tree(['first', 'second', 'third', 'fourth']) url = self.get_readonly_url() script = load_test.ActionScript.parse("""{ "parameters": {"base_url": "%s", "blocking_timeout": 2.0}, "requests": [ {"thread": "1", "relpath": "first"}, {"thread": "2", "relpath": "second"}, {"thread": "1", "relpath": "no-this"}, {"thread": "2", "relpath": "or-this"}, {"thread": "1", "relpath": "third"}, {"thread": "2", "relpath": "fourth"} ]}""" % (url,)) script.run() worker = script._get_worker("1") self.assertEqual([("first", True), ('no-this', False), ("third", True)], [(s[0].rsplit('/', 1)[1], s[1]) for s in worker.stats]) worker = script._get_worker("2") self.assertEqual([("second", True), ('or-this', False), ("fourth", True)], [(s[0].rsplit('/', 1)[1], s[1]) for s in worker.stats]) class TestRunScript(tests.TestCaseWithTransport): def setUp(self): super(TestRunScript, self).setUp() self.transport_readonly_server = http_server.HttpServer def test_run_script(self): self.build_tree(['file1', 'file2', 'file3', 'file4']) url = self.get_readonly_url() self.build_tree_contents([('localhost.script', """{ "parameters": {"base_url": "%s", "blocking_timeout": 0.1}, "requests": [ {"thread": "1", "relpath": "file1"}, {"thread": "2", "relpath": "file2"}, {"thread": "1", "relpath": "file3"}, {"thread": "2", "relpath": "file4"} ] }""" % (url,))]) script = load_test.run_script('localhost.script') worker = script._threads["1"][0] self.assertEqual([("file1", True), ('file3', True)], [(s[0].rsplit('/', 1)[1], s[1]) for s in worker.stats]) worker = script._threads["2"][0] self.assertEqual([("file2", True), ("file4", True)], [(s[0].rsplit('/', 1)[1], s[1]) for s in worker.stats]) loggerhead-1.19~bzr511/loggerhead/tests/test_revision_ui.py0000644000000000000000000000403413731072134022247 0ustar 00000000000000# 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 __future__ import absolute_import from .test_simple import BasicTests class TestRevisionUI(BasicTests): def test_authors_vs_committer(self): self.createBranch() self.tree.commit('First', committer="Joe Example ", revprops={'authors': u'A Author \n' u'B Author '}) app = self.setUpLoggerhead() res = app.get('/revision/1') # We would like to assert that Joe Example is connected to Committer, # and the Authors are connected. However, that requires asserting the # exact HTML connections, which I wanted to avoid. res.mustcontain('Committer', 'Joe Example', 'Author(s)', 'A Author, B Author') def test_author_is_committer(self): self.createBranch() self.tree.commit('First', committer="Joe Example ") app = self.setUpLoggerhead() res = app.get('/revision/1') # We would like to assert that Joe Example is connected to Committer, # and the Authors are connected. However, that requires asserting the # exact HTML connections, which I wanted to avoid. res.mustcontain('Committer', 'Joe Example') self.assertFalse(b'Author(s)' in res.body) loggerhead-1.19~bzr511/loggerhead/tests/test_simple.py0000644000000000000000000002350313731072134021207 0ustar 00000000000000# Copyright (C) 2007, 2008, 2009, 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 __future__ import absolute_import try: from html import escape except ImportError: from cgi import escape import json import logging import re from io import BytesIO from breezy.tests import TestCaseWithTransport try: from breezy.util.configobj.configobj import ConfigObj except ImportError: from configobj import ConfigObj from breezy import config from ..apps.branch import BranchWSGIApp from ..apps.http_head import HeadMiddleware from paste.fixture import TestApp from paste.httpexceptions import HTTPExceptionHandler, HTTPMovedPermanently from .fixtures import ( SampleBranch, ) class BasicTests(TestCaseWithTransport): def setUp(self): TestCaseWithTransport.setUp(self) logging.basicConfig(level=logging.ERROR) logging.getLogger('bzr').setLevel(logging.CRITICAL) def createBranch(self): self.tree = self.make_branch_and_tree('.') def setUpLoggerhead(self, **kw): branch_app = BranchWSGIApp(self.tree.branch, '', **kw).app return TestApp(HTTPExceptionHandler(branch_app)) def assertOkJsonResponse(self, app, env): start, content = consume_app(app, env) self.assertEqual('200 OK', start[0]) self.assertEqual('application/json', dict(start[1])['Content-Type']) self.assertEqual(None, start[2]) json.loads(content.decode('UTF-8')) def make_branch_app(self, branch, **kw): branch_app = BranchWSGIApp(branch, friendly_name='friendly-name', **kw) branch_app._environ = { 'wsgi.url_scheme':'', 'SERVER_NAME':'', 'SERVER_PORT':'80', } branch_app._url_base = '' return branch_app class TestWithSimpleTree(BasicTests): def setUp(self): BasicTests.setUp(self) self.sample_branch_fixture = SampleBranch(self) # XXX: This could be cleaned up more... -- mbp 2011-11-25 self.useFixture(self.sample_branch_fixture) self.tree = self.sample_branch_fixture.tree self.fileid = self.sample_branch_fixture.fileid self.filecontents = self.sample_branch_fixture.filecontents self.msg = self.sample_branch_fixture.msg def test_public_private(self): app = self.make_branch_app(self.tree.branch, private=True) self.assertEqual(app.public_private_css(), 'private') app = self.make_branch_app(self.tree.branch) self.assertEqual(app.public_private_css(), 'public') def test_changes(self): app = self.setUpLoggerhead() res = app.get('/changes') res.mustcontain(escape(self.msg)) def test_changes_for_file(self): app = self.setUpLoggerhead() res = app.get('/changes?filter_file_id=myfilename-id') res.mustcontain(escape(self.msg)) def test_changes_branch_from(self): app = self.setUpLoggerhead(served_url="lp:loggerhead") res = app.get('/changes') self.failUnless("To get this branch, use:" in res) self.failUnless("lp:loggerhead" in res) def test_changes_search(self): app = self.setUpLoggerhead() res = app.get('/changes', params={'q': 'foo'}) res.mustcontain('Sorry, no results found for your search.') def test_annotate(self): app = self.setUpLoggerhead() res = app.get('/annotate', params={'file_id': self.fileid}) # If pygments is installed, it inserts with<' # 'htmlspecialchars # So we pre-filter the body, to make sure remove spans of that type. body_no_span = re.sub(b'', b'', res.body) body_no_span = body_no_span.replace(b'', b'') for line in self.filecontents.splitlines(): escaped = escape(line).encode('utf-8') self.assertTrue(escaped in body_no_span, "did not find %r in %r" % (escaped, body_no_span)) def test_inventory(self): app = self.setUpLoggerhead() res = app.get('/files') res.mustcontain('myfilename') res = app.get('/files/') res.mustcontain('myfilename') res = app.get('/files/1') res.mustcontain('myfilename') res = app.get('/files/1/') res.mustcontain('myfilename') res = app.get('/files/1/?file_id=' + self.tree.path2id('').decode('utf-8')) res.mustcontain('myfilename') def test_inventory_bad_rev_404(self): app = self.setUpLoggerhead() res = app.get('/files/200', status=404) res = app.get('/files/invalid-revid', status=404) def test_inventory_bad_path_404(self): app = self.setUpLoggerhead() res = app.get('/files/1/hooha', status=404) res = app.get('/files/1?file_id=dssadsada', status=404) def test_revision(self): app = self.setUpLoggerhead() res = app.get('/revision/1') res.mustcontain(no=['anotherfile<']) res.mustcontain('anotherfile<') res.mustcontain('myfilename') class TestEmptyBranch(BasicTests): """Test that an empty branch doesn't break""" def setUp(self): BasicTests.setUp(self) self.createBranch() def test_changes(self): app = self.setUpLoggerhead() res = app.get('/changes') res.mustcontain('No revisions!') def test_inventory(self): app = self.setUpLoggerhead() res = app.get('/files') res.mustcontain('No revisions!') class TestHiddenBranch(BasicTests): """ Test that hidden branches aren't shown FIXME: not tested that it doesn't show up on listings """ def setUp(self): BasicTests.setUp(self) self.createBranch() try: locations = config.locations_config_filename() except AttributeError: from breezy import bedding locations = bedding.locations_config_path() ensure_config_dir_exists = bedding.ensure_config_dir_exists else: ensure_config_dir_exists = config.ensure_config_dir_exists ensure_config_dir_exists() with open(locations, 'w') as f: f.write('[%s]\nhttp_serve = False' % ( self.tree.branch.base,)) def test_no_access(self): app = self.setUpLoggerhead() res = app.get('/changes', status=404) class TestControllerRedirects(BasicTests): """ Test that a file under /files redirects to /view, and a directory under /view redirects to /files. """ def setUp(self): BasicTests.setUp(self) self.createBranch() self.build_tree(('file', 'folder/', 'folder/file')) self.tree.smart_add([]) self.tree.commit('') def test_view_folder(self): app = TestApp(BranchWSGIApp(self.tree.branch, '').app) e = self.assertRaises(HTTPMovedPermanently, app.get, '/view/head:/folder') self.assertEqual(e.location(), '/files/head:/folder') def test_files_file(self): app = TestApp(BranchWSGIApp(self.tree.branch, '').app) e = self.assertRaises(HTTPMovedPermanently, app.get, '/files/head:/folder/file') self.assertEqual(e.location(), '/view/head:/folder/file') e = self.assertRaises(HTTPMovedPermanently, app.get, '/files/head:/file') self.assertEqual(e.location(), '/view/head:/file') class TestHeadMiddleware(BasicTests): def setUp(self): BasicTests.setUp(self) self.createBranch() self.msg = 'trivial commit message' self.revid = self.tree.commit(message=self.msg) def setUpLoggerhead(self, **kw): branch_app = BranchWSGIApp(self.tree.branch, '', **kw).app return TestApp(HTTPExceptionHandler(HeadMiddleware(branch_app))) def test_get(self): app = self.setUpLoggerhead() res = app.get('/changes') res.mustcontain(self.msg) self.assertEqual('text/html', res.header('Content-Type')) def test_head(self): app = self.setUpLoggerhead() res = app.get('/changes', extra_environ={'REQUEST_METHOD': 'HEAD'}) self.assertEqual('text/html', res.header('Content-Type')) self.assertEqualDiff(b'', res.body) def consume_app(app, env): body = BytesIO() start = [] def start_response(status, headers, exc_info=None): start.append((status, headers, exc_info)) return body.write extra_content = list(app(env, start_response)) body.writelines(extra_content) return start[0], body.getvalue() #class TestGlobalConfig(BasicTests): # """ # Test that global config settings are respected # """ # def setUp(self): # BasicTests.setUp(self) # self.createBranch() # config.GlobalConfig().set_user_option('http_version', 'True') # def test_setting_respected(self): #FIXME: Figure out how to test this properly # app = self.setUpLoggerhead() # res = app.get('/changes', status=200) loggerhead-1.19~bzr511/loggerhead/tests/test_templating.py0000644000000000000000000000063113731072134022057 0ustar 00000000000000from ..zptsupport import load_template RENDERED = u"\n\n%s\n\n\ \n
Hello, %s
\n\n" def test_template_lookup(): template = load_template("loggerhead.tests.simple") assert template TITLE="test" NAME="World" info = dict(title=TITLE, name=NAME) s = template.expand(**info) assert s.startswith(RENDERED % (TITLE, NAME)) loggerhead-1.19~bzr511/loggerhead/tests/test_util.py0000644000000000000000000000225713731072134020676 0ustar 00000000000000# Copyright 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 breezy import tests from ..util import html_escape, html_format class TestHTMLEscaping(tests.TestCase): def test_html_escape(self): self.assertEqual( "foo "'<>&", html_escape("foo \"'<>&")) def test_html_format(self): self.assertEqual( '<baz>&', html_format( '%s', "baz\"'", "&"))