Mercurial > hg
changeset 2380:46ec76f80e18
Merged hg-purge
author | Thomas Arendsen Hein <thomas@intevation.de> |
---|---|
date | Thu, 01 Jun 2006 23:58:43 +0200 |
parents | 482d3fb47d80 (diff) e90cff87f871 (current diff) |
children | ab7a438294fc |
files | |
diffstat | 326 files changed, 38689 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,26 @@ +syntax: glob + +*.elc +*.orig +*.rej +*~ +*.o +*.so +*.pyc +*.swp +*.prof +tests/.coverage* +tests/annotated +tests/*.err +build +contrib/hgsh/hgsh +dist +doc/*.[0-9] +doc/*.[0-9].gendoc.txt +doc/*.[0-9].{x,ht}ml +MANIFEST +patches +mercurial/__version__.py + +syntax: regexp +^\.pc/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgsigs Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1 @@ +35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0 iD8DBQBEYmO2ywK+sNU5EO8RAnaYAKCO7x15xUn5mnhqWNXqk/ehlhRt2QCfRDfY0LrUq2q4oK/KypuJYPHgq1A=
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +d40cc5aacc31ed673d9b5b24f98bee78c283062c 0.4f +1c590d34bf61e2ea12c71738e5a746cd74586157 0.4e +7eca4cfa8aad5fce9a04f7d8acadcd0452e2f34e 0.4d +b4d0c3786ad3e47beacf8412157326a32b6d25a4 0.4c +f40273b0ad7b3a6d3012fd37736d0611f41ecf54 0.5 +0a28dfe59f8fab54a5118c5be4f40da34a53cdb7 0.5b +12e0fdbc57a0be78f0e817fd1d170a3615cd35da 0.6 +4ccf3de52989b14c3d84e1097f59e39a992e00bd 0.6b +eac9c8efcd9bd8244e72fb6821f769f450457a32 0.6c +979c049974485125e1f9357f6bbe9c1b548a64c3 0.7 +3a56574f329a368d645853e0f9e09472aee62349 0.8 +6a03cff2b0f5d30281e6addefe96b993582f2eac 0.8.1 +35fb62a3a673d5322f6274a44ba6456e5e4b3b37 0.9
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CONTRIBUTORS Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,39 @@ +Andrea Arcangeli <andrea at suse.de> +Thomas Arendsen Hein <thomas at intevation.de> +Goffredo Baroncelli <kreijack at libero.it> +Muli Ben-Yehuda <mulix at mulix.org> +Mikael Berthe <mikael at lilotux.net> +Benoit Boissinot <bboissin at gmail.com> +Vincent Danjean <vdanjean.ml at free.fr> +Jake Edge <jake at edge2.net> +Michael Fetterman <michael.fetterman at intel.com> +Edouard Gomez <ed.gomez at free.fr> +Eric Hopper <hopper at omnifarious.org> +Alecs King <alecsk at gmail.com> +Volker Kleinfeld <Volker.Kleinfeld at gmx.de> +Vadim Lebedev <vadim at mbdsys.com> +Christopher Li <hg at chrisli.org> +Chris Mason <mason at suse.com> +Colin McMillen <mcmillen at cs.cmu.edu> +Wojciech Milkowski <wmilkowski at interia.pl> +Chad Netzer <chad.netzer at gmail.com> +Bryan O'Sullivan <bos at serpentine.com> +Vicent Seguà Pascual <vseguip at gmail.com> +Sean Perry <shaleh at speakeasy.net> +Nguyen Anh Quynh <aquynh at gmail.com> +Ollivier Robert <roberto at keltia.freenix.fr> +Alexander Schremmer <alex at alexanderweb.de> +Arun Sharma <arun at sharma-home.net> +Josef "Jeff" Sipek <jeffpc at optonline.net> +Kevin Smith <yarcs at qualitycode.com> +TK Soh <teekaysoh at yahoo.com> +Radoslaw Szkodzinski <astralstorm at gorzow.mm.pl> +Samuel Tardieu <sam at rfc1149.net> +K Thananchayan <thananck at yahoo.com> +Andrew Thompson <andrewkt at aktzero.com> +Michael S. Tsirkin <mst at mellanox.co.il> +Rafael Villar Burke <pachi at mmn-arquitectos.com> +Tristan Wibberley <tristan at wibberley.org> +Mark Williamson <mark.williamson at cl.cam.ac.uk> + +If you are a contributor and don't see your name here, please let me know.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,17 @@ +include hg +recursive-include mercurial *.py +include hgweb.cgi hgwebdir.cgi +include hgeditor rewrite-log +include tests/README tests/coverage.py tests/run-tests.py tests/md5sum.py tests/test-*[a-z0-9] tests/*.out +prune tests/*.err +include *.txt +include templates/map templates/map-*[a-z0-9] +include templates/*.tmpl +include templates/static/* +include doc/README doc/Makefile doc/gendoc.py doc/*.txt doc/*.html doc/*.[0-9] +recursive-include contrib * +include README +include CONTRIBUTORS +include COPYING +include Makefile +include MANIFEST.in
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,61 @@ +PREFIX=/usr/local +export PREFIX +PYTHON=python + +help: + @echo 'Commonly used make targets:' + @echo ' all - build program and documentation' + @echo ' install - install program and man pages to PREFIX ($(PREFIX))' + @echo ' install-home - install with setup.py install --home=HOME ($(HOME))' + @echo ' local - build C extensions for inplace usage' + @echo ' tests - run all tests in the automatic test suite' + @echo ' test-foo - run only specified tests (e.g. test-merge1)' + @echo ' dist - run all tests and create a source tarball in dist/' + @echo ' clean - remove files created by other targets' + @echo ' (except installed files or dist source tarball)' + @echo + @echo 'Example for a system-wide installation under /usr/local:' + @echo ' make all && su -c "make install" && hg version' + @echo + @echo 'Example for a local installation (usable in this directory):' + @echo ' make local && ./hg version' + +all: build doc + +local: + $(PYTHON) setup.py build_ext -i + +build: + $(PYTHON) setup.py build + +doc: + $(MAKE) -C doc + +clean: + -$(PYTHON) setup.py clean --all # ignore errors of this command + find . -name '*.py[co]' -exec rm -f '{}' ';' + rm -f MANIFEST mercurial/__version__.py mercurial/*.so tests/*.err + $(MAKE) -C doc clean + +install: all + $(PYTHON) setup.py install --prefix="$(PREFIX)" --force + cd doc && $(MAKE) $(MFLAGS) install + +install-home: all + $(PYTHON) setup.py install --home="$(HOME)" --force + cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install + +dist: tests dist-notests + +dist-notests: doc + TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py sdist --force-manifest + +tests: + cd tests && $(PYTHON) run-tests.py + +test-%: + cd tests && $(PYTHON) run-tests.py $@ + + +.PHONY: help all local build doc clean install install-home dist dist-notests tests +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,103 @@ +MERCURIAL QUICK-START + +Setting up Mercurial: + + Note: some distributions fails to include bits of distutils by + default, you'll need python-dev to install. You'll also need a C + compiler and a 3-way merge tool like merge, tkdiff, or kdiff3. + + First, unpack the source: + + $ tar xvzf mercurial-<ver>.tar.gz + $ cd mercurial-<ver> + + When installing, change python to python2.3 or python2.4 if 2.2 is the + default on your system. + + To install system-wide: + + $ python setup.py install --force + + To install in your home directory (~/bin and ~/lib, actually), run: + + $ python setup.py install --home=${HOME} --force + $ export PYTHONPATH=${HOME}/lib/python # (or lib64/ on some systems) + $ export PATH=${HOME}/bin:$PATH # add these to your .bashrc + + And finally: + + $ hg # test installation, show help + + If you get complaints about missing modules, you probably haven't set + PYTHONPATH correctly. + +Setting up a Mercurial project: + + $ hg init project # creates project directory + $ cd project + # copy files in, edit them + $ hg add # add all unknown files + $ hg remove --after # remove deleted files + $ hg commit # commit all changes, edit changelog entry + + Mercurial will look for a file named .hgignore in the root of your + repository which contains a set of regular expressions to ignore in + file paths. + +Branching and merging: + + $ hg clone linux linux-work # create a new branch + $ cd linux-work + $ <make changes> + $ hg commit + $ cd ../linux + $ hg pull ../linux-work # pull changesets from linux-work + $ hg merge # merge the new tip from linux-work into + # our working directory + $ hg commit # commit the result of the merge + +Importing patches: + + Fast: + $ patch < ../p/foo.patch + $ hg commit -A + + Faster: + $ patch < ../p/foo.patch + $ hg commit `lsdiff -p1 ../p/foo.patch` + + Fastest: + $ cat ../p/patchlist | xargs hg import -p1 -b ../p + +Exporting a patch: + + (make changes) + $ hg commit + $ hg tip + 28237:747a537bd090880c29eae861df4d81b245aa0190 + $ hg export 28237 > foo.patch # export changeset 28237 + +Network support: + + # pull from the primary Mercurial repo + foo$ hg clone http://selenic.com/hg/ + foo$ cd hg + + # export your current repo via HTTP with browsable interface + foo$ hg serve -n "My repo" -p 80 + + # pushing changes to a remote repo with SSH + foo$ hg push ssh://user@example.com/~/hg/ + + # merge changes from a remote machine + bar$ hg pull http://foo/ + bar$ hg merge # merge changes into your working directory + + # Set up a CGI server on your webserver + foo$ cp hgweb.cgi ~/public_html/hg/index.cgi + foo$ emacs ~/public_html/hg/index.cgi # adjust the defaults + +For more info: + + Documentation in doc/ + Mercurial website at http://selenic.com/mercurial
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/comparison.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,31 @@ + Mercurial git BK (*) +storage revlog delta compressed revisions SCCS weave +storage naming by filename by revision hash by filename +merge file DAGs changeset DAG file DAGs? +consistency SHA1 SHA1 CRC +signable? yes yes no + +retrieve file tip O(1) O(1) O(revs) +add rev O(1) O(1) O(revs) +find prev file rev O(1) O(changesets) O(revs) +annotate file O(revs) O(changesets) O(revs) +find file changeset O(1) O(changesets) ? + +checkout O(files) O(files) O(revs)? +commit O(changes) O(changes) ? + 6 patches/s 6 patches/s slow +diff working dir O(changes) O(changes) ? + < 1s < 1s ? +tree diff revs O(changes) O(changes) ? + < 1s < 1s ? +hardlink clone O(files) O(revisions) O(files) + +find remote csets O(log new) rsync: O(revisions) ? + git-http: O(changesets) +pull remote csets O(patch) O(modified files) O(patch) + +repo growth O(patch) O(revisions) O(patch) + kernel history 300M 3.5G? 250M? +lines of code 2500 6500 (+ cogito) ?? + +* I've never used BK so this is just guesses
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/bash_completion Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,385 @@ +# bash completion for the Mercurial distributed SCM + +# Docs: +# +# If you source this file from your .bashrc, bash should be able to +# complete a command line that uses hg with all the available commands +# and options and sometimes even arguments. +# +# Mercurial allows you to define additional commands through extensions. +# Bash should be able to automatically figure out the name of these new +# commands and their options. If you also want to tell it how to +# complete non-option arguments, see below for how to define an +# _hg_cmd_foo function. +# +# +# Notes about completion for specific commands: +# +# - the completion function for the email command from the patchbomb +# extension will try to call _hg_emails to get a list of e-mail +# addresses. It's up to the user to define this function. For +# example, put the addresses of the lists that you usually patchbomb +# in ~/.patchbomb-to and the addresses that you usually use to send +# the patchbombs in ~/.patchbomb-from and use something like this: +# +# _hg_emails() +# { +# if [ -r ~/.patchbomb-$1 ]; then +# cat ~/.patchbomb-$1 +# fi +# } +# +# +# Writing completion functions for additional commands: +# +# If it exists, the function _hg_cmd_foo will be called without +# arguments to generate the completion candidates for the hg command +# "foo". +# +# In addition to the regular completion variables provided by bash, +# the following variables are also set: +# - $hg - the hg program being used (e.g. /usr/bin/hg) +# - $cmd - the name of the hg command being completed +# - $cmd_index - the index of $cmd in $COMP_WORDS +# - $cur - the current argument being completed +# - $prev - the argument before $cur +# - $global_args - "|"-separated list of global options that accept +# an argument (e.g. '--cwd|-R|--repository') +# - $canonical - 1 if we canonicalized $cmd before calling the function +# 0 otherwise +# + +shopt -s extglob + +_hg_commands() +{ + local commands + commands="$("$hg" debugcomplete "$cur" 2>/dev/null)" || commands="" + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$commands' -- "$cur")) +} + +_hg_paths() +{ + local paths="$("$hg" paths 2>/dev/null | sed -e 's/ = .*$//')" + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$paths' -- "$cur")) +} + +_hg_repos() +{ + local i + for i in $(compgen -d -- "$cur"); do + test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i") + done +} + +_hg_status() +{ + local files="$("$hg" status -n$1 . 2>/dev/null)" + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur")) +} + +_hg_tags() +{ + local tags="$("$hg" tags -q 2>/dev/null)" + local IFS=$'\n' + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur")) +} + +# this is "kind of" ugly... +_hg_count_non_option() +{ + local i count=0 + local filters="$1" + + for ((i=1; $i<=$COMP_CWORD; i++)); do + if [[ "${COMP_WORDS[i]}" != -* ]]; then + if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then + continue + fi + count=$(($count + 1)) + fi + done + + echo $(($count - 1)) +} + +_hg() +{ + local cur prev cmd cmd_index opts i + # global options that receive an argument + local global_args='--cwd|-R|--repository' + local hg="$1" + + COMPREPLY=() + cur="$2" + prev="$3" + + # searching for the command + # (first non-option argument that doesn't follow a global option that + # receives an argument) + for ((i=1; $i<=$COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then + cmd="${COMP_WORDS[i]}" + cmd_index=$i + break + fi + fi + done + + if [[ "$cur" == -* ]]; then + opts=$("$hg" debugcomplete --options "$cmd" 2>/dev/null) + + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur")) + return + fi + + # global options + case "$prev" in + -R|--repository) + _hg_repos + return + ;; + --cwd) + # Stick with default bash completion + return + ;; + esac + + if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then + _hg_commands + return + fi + + # try to generate completion candidates for whatever command the user typed + local help + local canonical=0 + if _hg_command_specific; then + return + fi + + # canonicalize the command name and try again + help=$("$hg" help "$cmd" 2>/dev/null) + if [ $? -ne 0 ]; then + # Probably either the command doesn't exist or it's ambiguous + return + fi + cmd=${help#hg } + cmd=${cmd%%[$' \n']*} + canonical=1 + _hg_command_specific +} + +_hg_command_specific() +{ + if [ "$(type -t "_hg_cmd_$cmd")" = function ]; then + "_hg_cmd_$cmd" + return 0 + fi + + if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" == --rev ]; then + if [ $canonical = 1 ]; then + _hg_tags + return 0 + elif [[ status != "$cmd"* ]]; then + _hg_tags + return 0 + else + return 1 + fi + fi + + case "$cmd" in + help) + _hg_commands + ;; + export|manifest|update) + _hg_tags + ;; + pull|push|outgoing|incoming) + _hg_paths + _hg_repos + ;; + paths) + _hg_paths + ;; + add) + _hg_status "u" + ;; + commit) + _hg_status "mar" + ;; + remove) + _hg_status "d" + ;; + forget) + _hg_status "a" + ;; + diff) + _hg_status "mar" + ;; + revert) + _hg_status "mard" + ;; + clone) + local count=$(_hg_count_non_option) + if [ $count = 1 ]; then + _hg_paths + fi + _hg_repos + ;; + debugindex|debugindexdot) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.i" -- "$cur")) + ;; + debugdata) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -f -X "!*.d" -- "$cur")) + ;; + *) + return 1 + ;; + esac + + return 0 +} + +complete -o bashdefault -o default -F _hg hg 2>/dev/null \ + || complete -o default -F _hg hg + + +# Completion for commands provided by extensions + +# mq +_hg_ext_mq_patchlist() +{ + local patches=$("$hg" $1 2>/dev/null) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$patches' -- "$cur")) +} + +_hg_ext_mq_queues() +{ + local root=$("$hg" root 2>/dev/null) + local n + for n in $(cd "$root"/.hg && compgen -d -- "$cur"); do + # I think we're usually not interested in the regular "patches" queue + # so just filter it. + if [ "$n" != patches ] && [ -e "$root/.hg/$n/series" ]; then + COMPREPLY=(${COMPREPLY[@]:-} "$n") + fi + done +} + +_hg_cmd_qpop() +{ + if [[ "$prev" = @(-n|--name) ]]; then + _hg_ext_mq_queues + return + fi + _hg_ext_mq_patchlist qapplied +} + +_hg_cmd_qpush() +{ + if [[ "$prev" = @(-n|--name) ]]; then + _hg_ext_mq_queues + return + fi + _hg_ext_mq_patchlist qunapplied +} + +_hg_cmd_qdelete() +{ + _hg_ext_mq_patchlist qseries +} + +_hg_cmd_qsave() +{ + if [[ "$prev" = @(-n|--name) ]]; then + _hg_ext_mq_queues + return + fi +} + +_hg_cmd_strip() +{ + _hg_tags +} + +_hg_cmd_qcommit() +{ + local root=$("$hg" root 2>/dev/null) + # this is run in a sub-shell, so we can't use _hg_status + local files=$(cd "$root/.hg/patches" 2>/dev/null && + "$hg" status -nmar 2>/dev/null) + COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur")) +} + + +# hbisect +_hg_cmd_bisect() +{ + local i subcmd + + # find the sub-command + for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do + if [[ ${COMP_WORDS[i]} != -* ]]; then + if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then + subcmd="${COMP_WORDS[i]}" + break + fi + fi + done + + if [ -z "$subcmd" ] || [ $COMP_CWORD -eq $i ] || [ "$subcmd" = help ]; then + COMPREPLY=(${COMPREPLY[@]:-} + $(compgen -W 'bad good help init next reset' -- "$cur")) + return + fi + + case "$subcmd" in + good|bad) + _hg_tags + ;; + esac + + return +} + + +# patchbomb +_hg_cmd_email() +{ + case "$prev" in + -c|--cc|-t|--to|-f|--from) + # we need an e-mail address. let the user provide a function + # to get them + if [ "$(type -t _hg_emails)" = function ]; then + local arg=to + if [[ "$prev" == @(-f|--from) ]]; then + arg=from + fi + local addresses=$(_hg_emails $arg) + COMPREPLY=(${COMPREPLY[@]:-} + $(compgen -W '$addresses' -- "$cur")) + fi + return + ;; + -m|--mbox) + # fallback to standard filename completion + return + ;; + -s|--subject) + # free form string + return + ;; + esac + + _hg_tags + return +} + + +# gpg +_hg_cmd_sign() +{ + _hg_tags +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/buildrpm Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Build a Mercurial RPM in place. +# +# Bryan O'Sullivan <bos@serpentine.com> + +root="`hg root 2>/dev/null`" +specfile=contrib/mercurial.spec + +if [ -z "$root" ]; then + echo 'You are not inside a Mercurial repository!' 1>&2 + exit 1 +fi + +rpmdir=/tmp/"`basename $root | sed 's/ /_/'`"-rpm + +cd "$root" +rm -rf $rpmdir +mkdir -p $rpmdir/RPMS +hg clone "$root" $rpmdir/BUILD + +if [ ! -f $specfile ]; then + echo "Cannot find $specfile!" 1>&2 + exit 1 +fi + +tmpspec=/tmp/`basename "$specfile"`.$$ +# Use the most recent tag as the version. +version=`hg tags | perl -e 'while(<STDIN>){if(/^(\d\S+)/){print$1;exit}}'` +# Compute the release number as the difference in revision numbers +# between the tip and the most recent tag. +release=`hg tags | perl -e 'while(<STDIN>){/^(\S+)\s+(\d+)/;if($1eq"tip"){$t=$2}else{print$t-$2+1;exit}}'` +tip=`hg -q tip` + +# Beat up the spec file +sed -e 's,^Source:.*,Source: /dev/null,' \ + -e "s,^Version:.*,Version: $version," \ + -e "s,^Release:.*,Release: $release," \ + -e "s,^%prep.*,Changeset: $tip\n\0," \ + -e 's,^%setup.*,,' \ + $specfile > $tmpspec + +rpmbuild --define "_topdir $rpmdir" -bb $tmpspec +if [ $? = 0 ]; then + rm -rf $tmpspec $rpmdir/BUILD + mv $rpmdir/RPMS/*/* $rpmdir && rm -r $rpmdir/RPMS + echo + echo "Packages are in $rpmdir" +fi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/convert-repo Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,289 @@ +#!/usr/bin/env python +# +# This is a generalized framework for converting between SCM +# repository formats. +# +# In its current form, it's hardcoded to convert incrementally between +# git and Mercurial. +# +# To use, you must first import the first git version into Mercurial, +# and establish a mapping between the git commit hash and the hash in +# Mercurial for that version. This mapping is kept in a simple text +# file with lines like so: +# +# <git hash> <mercurial hash> +# +# To convert the rest of the repo, run: +# +# convert-repo <git-dir> <hg-dir> <mapfile> +# +# This updates the mapfile on each commit copied, so it can be +# interrupted and can be run repeatedly to copy new commits. + +import sys, os, zlib, sha, time +from mercurial import hg, ui, util + +class convert_git: + def __init__(self, path): + self.path = path + + def getheads(self): + return [file(self.path + "/HEAD").read()[:-1]] + + def catfile(self, rev, type): + if rev == "0" * 40: raise IOError() + fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" % (self.path, type, rev)) + return fh.read() + + def getfile(self, name, rev): + return self.catfile(rev, "blob") + + def getchanges(self, version): + fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version)) + changes = [] + for l in fh: + if "\t" not in l: continue + m, f = l[:-1].split("\t") + m = m.split() + h = m[3] + p = (m[1] == "100755") + changes.append((f, h, p)) + return changes + + def getcommit(self, version): + c = self.catfile(version, "commit") # read the commit hash + end = c.find("\n\n") + message = c[end+2:] + l = c[:end].splitlines() + manifest = l[0].split()[1] + parents = [] + for e in l[1:]: + n,v = e.split(" ", 1) + if n == "author": + p = v.split() + tm, tz = p[-2:] + author = " ".join(p[:-2]) + if author[0] == "<": author = author[1:-1] + if n == "committer": + p = v.split() + tm, tz = p[-2:] + committer = " ".join(p[:-2]) + if committer[0] == "<": committer = committer[1:-1] + message += "\ncommitter: %s\n" % v + if n == "parent": parents.append(v) + + tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] + tz = -int(tzs) * (int(tzh) * 3600 + int(tzm)) + date = tm + " " + str(tz) + return (parents, author, date, message) + + def gettags(self): + tags = {} + for f in os.listdir(self.path + "/refs/tags"): + try: + h = file(self.path + "/refs/tags/" + f).read().strip() + c = self.catfile(h, "tag") # read the commit hash + h = c.splitlines()[0].split()[1] + tags[f] = h + except: + pass + return tags + +class convert_mercurial: + def __init__(self, path): + self.path = path + u = ui.ui() + self.repo = hg.repository(u, path) + + def getheads(self): + h = self.repo.changelog.heads() + return [ hg.hex(x) for x in h ] + + def putfile(self, f, e, data): + self.repo.wfile(f, "w").write(data) + if self.repo.dirstate.state(f) == '?': + self.repo.dirstate.update([f], "a") + + util.set_exec(self.repo.wjoin(f), e) + + def delfile(self, f): + try: + os.unlink(self.repo.wjoin(f)) + #self.repo.remove([f]) + except: + pass + + def putcommit(self, files, parents, author, dest, text): + seen = {} + pl = [] + for p in parents: + if p not in seen: + pl.append(p) + seen[p] = 1 + parents = pl + + if len(parents) < 2: parents.append("0" * 40) + if len(parents) < 2: parents.append("0" * 40) + p2 = parents.pop(0) + + while parents: + p1 = p2 + p2 = parents.pop(0) + self.repo.rawcommit(files, text, author, dest, + hg.bin(p1), hg.bin(p2)) + text = "(octopus merge fixup)\n" + p2 = hg.hex(self.repo.changelog.tip()) + + return p2 + + def puttags(self, tags): + try: + old = self.repo.wfile(".hgtags").read() + oldlines = old.splitlines(1) + oldlines.sort() + except: + oldlines = [] + + k = tags.keys() + k.sort() + newlines = [] + for tag in k: + newlines.append("%s %s\n" % (tags[tag], tag)) + + newlines.sort() + + if newlines != oldlines: + #print "updating tags" + f = self.repo.wfile(".hgtags", "w") + f.write("".join(newlines)) + f.close() + if not oldlines: self.repo.add([".hgtags"]) + date = "%s 0" % int(time.mktime(time.gmtime())) + self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", + date, self.repo.changelog.tip(), hg.nullid) + return hg.hex(self.repo.changelog.tip()) + +class convert: + def __init__(self, source, dest, mapfile): + self.source = source + self.dest = dest + self.mapfile = mapfile + self.commitcache = {} + + self.map = {} + try: + for l in file(self.mapfile): + sv, dv = l[:-1].split() + self.map[sv] = dv + except IOError: + pass + + def walktree(self, heads): + visit = heads + known = {} + parents = {} + while visit: + n = visit.pop(0) + if n in known or n in self.map: continue + known[n] = 1 + self.commitcache[n] = self.source.getcommit(n) + cp = self.commitcache[n][0] + for p in cp: + parents.setdefault(n, []).append(p) + visit.append(p) + + return parents + + def toposort(self, parents): + visit = parents.keys() + seen = {} + children = {} + + while visit: + n = visit.pop(0) + if n in seen: continue + seen[n] = 1 + pc = 0 + if n in parents: + for p in parents[n]: + if p not in self.map: pc += 1 + visit.append(p) + children.setdefault(p, []).append(n) + if not pc: root = n + + s = [] + removed = {} + visit = children.keys() + while visit: + n = visit.pop(0) + if n in removed: continue + dep = 0 + if n in parents: + for p in parents[n]: + if p in self.map: continue + if p not in removed: + # we're still dependent + visit.append(n) + dep = 1 + break + + if not dep: + # all n's parents are in the list + removed[n] = 1 + s.append(n) + if n in children: + for c in children[n]: + visit.insert(0, c) + + return s + + def copy(self, rev): + p, a, d, t = self.commitcache[rev] + files = self.source.getchanges(rev) + + for f,v,e in files: + try: + data = self.source.getfile(f, v) + except IOError, inst: + self.dest.delfile(f) + else: + self.dest.putfile(f, e, data) + + r = [self.map[v] for v in p] + f = [f for f,v,e in files] + self.map[rev] = self.dest.putcommit(f, r, a, d, t) + file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev])) + + def convert(self): + heads = self.source.getheads() + parents = self.walktree(heads) + t = self.toposort(parents) + t = [n for n in t if n not in self.map] + num = len(t) + c = None + + for c in t: + num -= 1 + desc = self.commitcache[c][3].splitlines()[0] + #print num, desc + self.copy(c) + + tags = self.source.gettags() + ctags = {} + for k in tags: + v = tags[k] + if v in self.map: + ctags[k] = self.map[v] + + if c and ctags: + nrev = self.dest.puttags(ctags) + # write another hash correspondence to override the previous + # one so we don't end up with extra tag heads + file(self.mapfile, "a").write("%s %s\n" % (c, nrev)) + +gitpath, hgpath, mapfile = sys.argv[1:] +if os.path.isdir(gitpath + "/.git"): + gitpath += "/.git" + +c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile) +c.convert()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/darcs2hg.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,130 @@ +#!/usr/bin/env python +# Encoding: iso-8859-1 +# vim: tw=80 ts=4 sw=4 noet +# ----------------------------------------------------------------------------- +# Project : Basic Darcs to Mercurial conversion script +# ----------------------------------------------------------------------------- +# Author : Sebastien Pierre <sebastien@xprima.com> +# Creation : 24-May-2006 +# Last mod : 26-May-2006 +# History : +# 26-May-2006 - Updated +# 24-May-2006 - First implementation +# ----------------------------------------------------------------------------- + +import os, sys +import tempfile +import xml.dom.minidom as xml_dom +from time import strptime, mktime + +DARCS_REPO = None +HG_REPO = None + +USAGE = """\ +%s DARCSREPO HGREPO + + Converts the given Darcs repository to a new Mercurial repository. The given + HGREPO must not exist, as it will be created and filled up (this will avoid + overwriting valuable data. + +""" % (os.path.basename(sys.argv[0])) + +# ------------------------------------------------------------------------------ +# +# Utilities +# +# ------------------------------------------------------------------------------ + +def cmd(text, path=None): + """Executes a command, in the given directory (if any), and returns the + command result as a string.""" + cwd = None + if path: + path = os.path.abspath(path) + cwd = os.getcwd() + os.chdir(path) + print text + res = os.popen(text).read() + if path: + os.chdir(cwd) + return res + +def writefile(path, data): + """Writes the given data into the given file.""" + f = file(path, "w") ; f.write(data) ; f.close() + +# ------------------------------------------------------------------------------ +# +# Darcs interface +# +# ------------------------------------------------------------------------------ + +def darcs_changes(darcsRepo): + """Gets the changes list from the given darcs repository. This returns the + chronological list of changes as (change name, change summary).""" + changes = cmd("darcs changes --reverse --xml-output", darcsRepo) + doc = xml_dom.parseString(changes) + res = [] + for patch_node in doc.childNodes[0].childNodes: + name = filter(lambda n:n.nodeName == "name", patch_node.childNodes) + comm = filter(lambda n:n.nodeName == "comment", patch_node.childNodes) + if not name:continue + else: name = name[0].childNodes[0].data + if not comm: comm = "" + else: comm = comm[0].childNodes[0].data + author = patch_node.getAttribute("author") + date = patch_node.getAttribute("date") + yield author, date, name, comm + +def darcs_pull(hg_repo, darcs_repo, change): + cmd("darcs pull '%s' --all --patches='%s'" % (darcs_repo, change), hg_repo) + +# ------------------------------------------------------------------------------ +# +# Mercurial interface +# +# ------------------------------------------------------------------------------ + +def hg_commit( hg_repo, text, author, date ): + fd, tmpfile = tempfile.mkstemp(prefix="darcs2hg_") + writefile(tmpfile, text) + cmd("hg add -X _darcs", hg_repo) + cmd("hg remove -X _darcs --after", hg_repo) + cmd("hg commit -l %s -u '%s' -d '%s 0'" % (tmpfile, author, date), hg_repo) + os.unlink(tmpfile) + +# ------------------------------------------------------------------------------ +# +# Main +# +# ------------------------------------------------------------------------------ + +if __name__ == "__main__": + args = sys.argv[1:] + # We parse the arguments + if len(args) == 2: + darcs_repo = os.path.abspath(args[0]) + hg_repo = os.path.abspath(args[1]) + else: + print USAGE + sys.exit(-1) + # Initializes the target repo + if not os.path.isdir(darcs_repo + "/_darcs"): + print "No darcs directory found at: " + darc_repo + sys.exit(-1) + if not os.path.isdir(hg_repo): + os.mkdir(hg_repo) + else: + print "Given HG repository must not exist. It will be created" + sys.exit(-1) + cmd("hg init '%s'" % (hg_repo)) + cmd("darcs initialize", hg_repo) + # Get the changes from the Darcs repository + for author, date, summary, description in darcs_changes(darcs_repo): + text = summary + "\n" + description + darcs_pull(hg_repo, darcs_repo, summary) + epoch = int(mktime(strptime(date, '%Y%m%d%H%M%S'))) + hg_commit(hg_repo, text, author, epoch) + +# EOF +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/git-viz/git-cat-file Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#!/bin/sh + +op=`basename $0 | sed -e 's/^git-//'` +exec hgit $op "$@" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/git-viz/git-diff-tree Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#!/bin/sh + +op=`basename $0 | sed -e 's/^git-//'` +exec hgit $op "$@" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/git-viz/git-rev-list Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#!/bin/sh + +op=`basename $0 | sed -e 's/^git-//'` +exec hgit $op "$@" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/git-viz/git-rev-tree Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#!/bin/sh + +op=`basename $0 | sed -e 's/^git-//'` +exec hgit $op "$@" +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/git-viz/hg-viz Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +if test x"$1" != x ; then + cd $1 +fi + +if [ ! -d ".hg" ]; then + echo "${1:-.} is not a mercurial repository" 1>&2 + echo "Aborting" 1>&2 + exit 1 +fi +if [ ! -d ".git" ]; then + mkdir -v ".git" +fi +if [ -e ".git/HEAD" ]; then + if [ ! -e ".git/HEAD.hg-viz-save" ]; then + mv -v ".git/HEAD" ".git/HEAD.hg-viz-save" + else + rm -vf ".git/HEAD" + fi +fi +hg history | head -1 | awk -F: '{print $3}' > .git/HEAD +git-viz +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hg-ssh Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,48 @@ +#!/usr/bin/env python +# +# Copyright 2005, 2006 by Intevation GmbH <intevation@intevation.de> +# Author(s): +# Thomas Arendsen Hein <thomas@intevation.de> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +""" +hg-ssh - a wrapper for ssh access to a limited set of mercurial repos + +To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8): +command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ... +(probably together with these other useful options: + no-port-forwarding,no-X11-forwarding,no-agent-forwarding) + +This allows pull/push over ssh to to the repositories given as arguments. + +If all your repositories are subdirectories of a common directory, you can +allow shorter paths with: +command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2" + +You can use pattern matching of your normal shell, e.g.: +command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}" +""" + +from mercurial import commands + +import sys, os + +cwd = os.getcwd() +allowed_paths = [os.path.normpath(os.path.join(cwd, os.path.expanduser(path))) + for path in sys.argv[1:]] +orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?') + +if orig_cmd.startswith('hg -R ') and orig_cmd.endswith(' serve --stdio'): + path = orig_cmd[6:-14] + repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path))) + if repo in allowed_paths: + commands.dispatch(['-R', repo, 'serve', '--stdio']) + else: + sys.stderr.write("Illegal repository %r\n" % repo) + sys.exit(-1) +else: + sys.stderr.write("Illegal command %r\n" % orig_cmd) + sys.exit(-1) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgdiff Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,104 @@ +#!/usr/bin/env python + +import os, sys, struct, stat +import difflib +import re +from optparse import OptionParser +from mercurial.bdiff import bdiff, blocks +from mercurial.mdiff import bunidiff + +VERSION="0.2" +usage = "usage: %prog [options] file1 file2" +parser = OptionParser(usage=usage) + +parser.add_option("-d", "--difflib", action="store_true", default=False) +parser.add_option('-x', '--count', default=1) +parser.add_option('-c', '--context', type="int", default=3) +parser.add_option('-p', '--show-c-function', action="store_true", default=False) +parser.add_option('-w', '--ignore-all-space', action="store_true", + default=False) + +(options, args) = parser.parse_args() + +if not args: + parser.print_help() + sys.exit(1) + +# simple utility function to put all the +# files from a directory tree into a dict +def buildlist(names, top): + tlen = len(top) + for root, dirs, files in os.walk(top): + l = root[tlen + 1:] + for x in files: + p = os.path.join(root, x) + st = os.lstat(p) + if stat.S_ISREG(st.st_mode): + names[os.path.join(l, x)] = (st.st_dev, st.st_ino) + +def diff_files(file1, file2): + if file1 == None: + b = file(file2).read().splitlines(1) + l1 = "--- %s\n" % (file2) + l2 = "+++ %s\n" % (file2) + l3 = "@@ -0,0 +1,%d @@\n" % len(b) + l = [l1, l2, l3] + ["+" + e for e in b] + elif file2 == None: + a = file(file1).read().splitlines(1) + l1 = "--- %s\n" % (file1) + l2 = "+++ %s\n" % (file1) + l3 = "@@ -1,%d +0,0 @@\n" % len(a) + l = [l1, l2, l3] + ["-" + e for e in a] + else: + t1 = file(file1).read() + t2 = file(file2).read() + l1 = t1.splitlines(1) + l2 = t2.splitlines(1) + if options.difflib: + l = difflib.unified_diff(l1, l2, file1, file2) + else: + l = bunidiff(t1, t2, l1, l2, file1, file2, context=options.context, + showfunc=options.show_c_function, + ignorews=options.ignore_all_space) + for x in l: + if x[-1] != '\n': + x += "\n\ No newline at end of file\n" + print x, + +file1 = args[0] +file2 = args[1] + +if os.path.isfile(file1) and os.path.isfile(file2): + diff_files(file1, file2) +elif os.path.isdir(file1): + if not os.path.isdir(file2): + sys.stderr.write("file types don't match\n") + sys.exit(1) + + d1 = {} + d2 = {} + + buildlist(d1, file1) + buildlist(d2, file2) + keys = d1.keys() + keys.sort() + for x in keys: + if x not in d2: + f2 = None + else: + f2 = os.path.join(file2, x) + st1 = d1[x] + st2 = d2[x] + del d2[x] + if st1[0] == st2[0] and st1[1] == st2[1]: + sys.stderr.write("%s is a hard link\n" % x) + continue + x = os.path.join(file1, x) + diff_files(x, f2) + keys = d2.keys() + keys.sort() + for x in keys: + f1 = None + x = os.path.join(file2, x) + diff_files(f1, x) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgk Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,3654 @@ +#!/usr/bin/env wish + +# Copyright (C) 2005 Paul Mackerras. All rights reserved. +# This program is free software; it may be used, copied, modified +# and distributed under the terms of the GNU General Public Licence, +# either version 2, or (at your option) any later version. + +proc gitdir {} { + global env + if {[info exists env(GIT_DIR)]} { + return $env(GIT_DIR) + } else { + return ".hg" + } +} + +proc getcommits {rargs} { + global commits commfd phase canv mainfont env + global startmsecs nextupdate ncmupdate + global ctext maincursor textcursor leftover + + # check that we can find a .git directory somewhere... + set gitdir [gitdir] + if {![file isdirectory $gitdir]} { + error_popup "Cannot find the git directory \"$gitdir\"." + exit 1 + } + set commits {} + set phase getcommits + set startmsecs [clock clicks -milliseconds] + set nextupdate [expr $startmsecs + 100] + set ncmupdate 1 + if [catch { + set parse_args [concat --default HEAD $rargs] + set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"] + }] { + # if git-rev-parse failed for some reason... + if {$rargs == {}} { + set rargs HEAD + } + set parsed_args $rargs + } + if [catch { + set commfd [open "|hg debug-rev-list --header --topo-order --parents $parsed_args" r] + } err] { + puts stderr "Error executing hg debug-rev-list: $err" + exit 1 + } + set leftover {} + fconfigure $commfd -blocking 0 -translation lf + fileevent $commfd readable [list getcommitlines $commfd] + $canv delete all + $canv create text 3 3 -anchor nw -text "Reading commits..." \ + -font $mainfont -tags textitems + . config -cursor watch + settextcursor watch +} + +proc getcommitlines {commfd} { + global commits parents cdate children + global commitlisted phase commitinfo nextupdate + global stopped redisplaying leftover + + set stuff [read $commfd] + if {$stuff == {}} { + if {![eof $commfd]} return + # set it blocking so we wait for the process to terminate + fconfigure $commfd -blocking 1 + if {![catch {close $commfd} err]} { + after idle finishcommits + return + } + if {[string range $err 0 4] == "usage"} { + set err \ +{Gitk: error reading commits: bad arguments to git-rev-list. +(Note: arguments to gitk are passed to git-rev-list +to allow selection of commits to be displayed.)} + } else { + set err "Error reading commits: $err" + } + error_popup $err + exit 1 + } + set start 0 + while 1 { + set i [string first "\0" $stuff $start] + if {$i < 0} { + append leftover [string range $stuff $start end] + return + } + set cmit [string range $stuff $start [expr {$i - 1}]] + if {$start == 0} { + set cmit "$leftover$cmit" + set leftover {} + } + set start [expr {$i + 1}] + set j [string first "\n" $cmit] + set ok 0 + if {$j >= 0} { + set ids [string range $cmit 0 [expr {$j - 1}]] + set ok 1 + foreach id $ids { + if {![regexp {^[0-9a-f]{40}$} $id]} { + set ok 0 + break + } + } + } + if {!$ok} { + set shortcmit $cmit + if {[string length $shortcmit] > 80} { + set shortcmit "[string range $shortcmit 0 80]..." + } + error_popup "Can't parse hg debug-rev-list output: {$shortcmit}" + exit 1 + } + set id [lindex $ids 0] + set olds [lrange $ids 1 end] + set cmit [string range $cmit [expr {$j + 1}] end] + lappend commits $id + set commitlisted($id) 1 + parsecommit $id $cmit 1 [lrange $ids 1 end] + drawcommit $id + if {[clock clicks -milliseconds] >= $nextupdate} { + doupdate 1 + } + while {$redisplaying} { + set redisplaying 0 + if {$stopped == 1} { + set stopped 0 + set phase "getcommits" + foreach id $commits { + drawcommit $id + if {$stopped} break + if {[clock clicks -milliseconds] >= $nextupdate} { + doupdate 1 + } + } + } + } + } +} + +proc doupdate {reading} { + global commfd nextupdate numcommits ncmupdate + + if {$reading} { + fileevent $commfd readable {} + } + update + set nextupdate [expr {[clock clicks -milliseconds] + 100}] + if {$numcommits < 100} { + set ncmupdate [expr {$numcommits + 1}] + } elseif {$numcommits < 10000} { + set ncmupdate [expr {$numcommits + 10}] + } else { + set ncmupdate [expr {$numcommits + 100}] + } + if {$reading} { + fileevent $commfd readable [list getcommitlines $commfd] + } +} + +proc readcommit {id} { + if [catch {set contents [exec hg debug-cat-file commit $id]}] return + parsecommit $id $contents 0 {} +} + +proc parsecommit {id contents listed olds} { + global commitinfo children nchildren parents nparents cdate ncleft + + set inhdr 1 + set comment {} + set headline {} + set auname {} + set audate {} + set comname {} + set comdate {} + if {![info exists nchildren($id)]} { + set children($id) {} + set nchildren($id) 0 + set ncleft($id) 0 + } + set parents($id) $olds + set nparents($id) [llength $olds] + foreach p $olds { + if {![info exists nchildren($p)]} { + set children($p) [list $id] + set nchildren($p) 1 + set ncleft($p) 1 + } elseif {[lsearch -exact $children($p) $id] < 0} { + lappend children($p) $id + incr nchildren($p) + incr ncleft($p) + } + } + foreach line [split $contents "\n"] { + if {$inhdr} { + set line [split $line] + if {$line == {}} { + set inhdr 0 + } else { + set tag [lindex $line 0] + if {$tag == "author"} { + set x [expr {[llength $line] - 2}] + set audate [lindex $line $x] + set auname [join [lrange $line 1 [expr {$x - 1}]]] + } elseif {$tag == "committer"} { + set x [expr {[llength $line] - 2}] + set comdate [lindex $line $x] + set comname [join [lrange $line 1 [expr {$x - 1}]]] + } + } + } else { + if {$comment == {}} { + set headline [string trim $line] + } else { + append comment "\n" + } + if {!$listed} { + # git-rev-list indents the comment by 4 spaces; + # if we got this via git-cat-file, add the indentation + append comment " " + } + append comment $line + } + } + if {$audate != {}} { + set audate [clock format $audate -format "%Y-%m-%d %H:%M:%S"] + } + if {$comdate != {}} { + set cdate($id) $comdate + set comdate [clock format $comdate -format "%Y-%m-%d %H:%M:%S"] + } + set commitinfo($id) [list $headline $auname $audate \ + $comname $comdate $comment] +} + +proc readrefs {} { + global tagids idtags headids idheads tagcontents + + set tags [exec hg tags] + set lines [split $tags '\n'] + foreach f $lines { + set f [regexp -all -inline {\S+} $f] + set direct [lindex $f 0] + set full [lindex $f 1] + set sha [split $full ':'] + set tag [lindex $sha 1] + lappend tagids($direct) $tag + lappend idtags($tag) $direct + } +} + +proc readotherrefs {base dname excl} { + global otherrefids idotherrefs + + set git [gitdir] + set files [glob -nocomplain -types f [file join $git $base *]] + foreach f $files { + catch { + set fd [open $f r] + set line [read $fd 40] + if {[regexp {^[0-9a-f]{40}} $line id]} { + set name "$dname[file tail $f]" + set otherrefids($name) $id + lappend idotherrefs($id) $name + } + close $fd + } + } + set dirs [glob -nocomplain -types d [file join $git $base *]] + foreach d $dirs { + set dir [file tail $d] + if {[lsearch -exact $excl $dir] >= 0} continue + readotherrefs [file join $base $dir] "$dname$dir/" {} + } +} + +proc error_popup msg { + set w .error + toplevel $w + wm transient $w . + message $w.m -text $msg -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text OK -command "destroy $w" + pack $w.ok -side bottom -fill x + bind $w <Visibility> "grab $w; focus $w" + tkwait window $w +} + +proc makewindow {} { + global canv canv2 canv3 linespc charspc ctext cflist textfont + global findtype findtypemenu findloc findstring fstring geometry + global entries sha1entry sha1string sha1but + global maincursor textcursor curtextcursor + global rowctxmenu gaudydiff mergemax + + menu .bar + .bar add cascade -label "File" -menu .bar.file + menu .bar.file + .bar.file add command -label "Reread references" -command rereadrefs + .bar.file add command -label "Quit" -command doquit + menu .bar.help + .bar add cascade -label "Help" -menu .bar.help + .bar.help add command -label "About gitk" -command about + . configure -menu .bar + + if {![info exists geometry(canv1)]} { + set geometry(canv1) [expr 45 * $charspc] + set geometry(canv2) [expr 30 * $charspc] + set geometry(canv3) [expr 15 * $charspc] + set geometry(canvh) [expr 25 * $linespc + 4] + set geometry(ctextw) 80 + set geometry(ctexth) 30 + set geometry(cflistw) 30 + } + panedwindow .ctop -orient vertical + if {[info exists geometry(width)]} { + .ctop conf -width $geometry(width) -height $geometry(height) + set texth [expr {$geometry(height) - $geometry(canvh) - 56}] + set geometry(ctexth) [expr {($texth - 8) / + [font metrics $textfont -linespace]}] + } + frame .ctop.top + frame .ctop.top.bar + pack .ctop.top.bar -side bottom -fill x + set cscroll .ctop.top.csb + scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 + pack $cscroll -side right -fill y + panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4 + pack .ctop.top.clist -side top -fill both -expand 1 + .ctop add .ctop.top + set canv .ctop.top.clist.canv + canvas $canv -height $geometry(canvh) -width $geometry(canv1) \ + -bg white -bd 0 \ + -yscrollincr $linespc -yscrollcommand "$cscroll set" + .ctop.top.clist add $canv + set canv2 .ctop.top.clist.canv2 + canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \ + -bg white -bd 0 -yscrollincr $linespc + .ctop.top.clist add $canv2 + set canv3 .ctop.top.clist.canv3 + canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \ + -bg white -bd 0 -yscrollincr $linespc + .ctop.top.clist add $canv3 + bind .ctop.top.clist <Configure> {resizeclistpanes %W %w} + + set sha1entry .ctop.top.bar.sha1 + set entries $sha1entry + set sha1but .ctop.top.bar.sha1label + button $sha1but -text "SHA1 ID: " -state disabled -relief flat \ + -command gotocommit -width 8 + $sha1but conf -disabledforeground [$sha1but cget -foreground] + pack .ctop.top.bar.sha1label -side left + entry $sha1entry -width 40 -font $textfont -textvariable sha1string + trace add variable sha1string write sha1change + pack $sha1entry -side left -pady 2 + + image create bitmap bm-left -data { + #define left_width 16 + #define left_height 16 + static unsigned char left_bits[] = { + 0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00, + 0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00, + 0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01}; + } + image create bitmap bm-right -data { + #define right_width 16 + #define right_height 16 + static unsigned char right_bits[] = { + 0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c, + 0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c, + 0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01}; + } + button .ctop.top.bar.leftbut -image bm-left -command goback \ + -state disabled -width 26 + pack .ctop.top.bar.leftbut -side left -fill y + button .ctop.top.bar.rightbut -image bm-right -command goforw \ + -state disabled -width 26 + pack .ctop.top.bar.rightbut -side left -fill y + + button .ctop.top.bar.findbut -text "Find" -command dofind + pack .ctop.top.bar.findbut -side left + set findstring {} + set fstring .ctop.top.bar.findstring + lappend entries $fstring + entry $fstring -width 30 -font $textfont -textvariable findstring + pack $fstring -side left -expand 1 -fill x + set findtype Exact + set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \ + findtype Exact IgnCase Regexp] + set findloc "All fields" + tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \ + Comments Author Committer Files Pickaxe + pack .ctop.top.bar.findloc -side right + pack .ctop.top.bar.findtype -side right + # for making sure type==Exact whenever loc==Pickaxe + trace add variable findloc write findlocchange + + panedwindow .ctop.cdet -orient horizontal + .ctop add .ctop.cdet + frame .ctop.cdet.left + set ctext .ctop.cdet.left.ctext + text $ctext -bg white -state disabled -font $textfont \ + -width $geometry(ctextw) -height $geometry(ctexth) \ + -yscrollcommand ".ctop.cdet.left.sb set" \ + -xscrollcommand ".ctop.cdet.left.hb set" -wrap none + scrollbar .ctop.cdet.left.sb -command "$ctext yview" + scrollbar .ctop.cdet.left.hb -orient horizontal -command "$ctext xview" + pack .ctop.cdet.left.sb -side right -fill y + pack .ctop.cdet.left.hb -side bottom -fill x + pack $ctext -side left -fill both -expand 1 + .ctop.cdet add .ctop.cdet.left + + $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa" + if {$gaudydiff} { + $ctext tag conf hunksep -back blue -fore white + $ctext tag conf d0 -back "#ff8080" + $ctext tag conf d1 -back green + } else { + $ctext tag conf hunksep -fore blue + $ctext tag conf d0 -fore red + $ctext tag conf d1 -fore "#00a000" + $ctext tag conf m0 -fore red + $ctext tag conf m1 -fore blue + $ctext tag conf m2 -fore green + $ctext tag conf m3 -fore purple + $ctext tag conf m4 -fore brown + $ctext tag conf mmax -fore darkgrey + set mergemax 5 + $ctext tag conf mresult -font [concat $textfont bold] + $ctext tag conf msep -font [concat $textfont bold] + $ctext tag conf found -back yellow + } + + frame .ctop.cdet.right + set cflist .ctop.cdet.right.cfiles + listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \ + -yscrollcommand ".ctop.cdet.right.sb set" + scrollbar .ctop.cdet.right.sb -command "$cflist yview" + pack .ctop.cdet.right.sb -side right -fill y + pack $cflist -side left -fill both -expand 1 + .ctop.cdet add .ctop.cdet.right + bind .ctop.cdet <Configure> {resizecdetpanes %W %w} + + pack .ctop -side top -fill both -expand 1 + + bindall <1> {selcanvline %W %x %y} + #bindall <B1-Motion> {selcanvline %W %x %y} + bindall <ButtonRelease-4> "allcanvs yview scroll -5 units" + bindall <ButtonRelease-5> "allcanvs yview scroll 5 units" + bindall <2> "allcanvs scan mark 0 %y" + bindall <B2-Motion> "allcanvs scan dragto 0 %y" + bind . <Key-Up> "selnextline -1" + bind . <Key-Down> "selnextline 1" + bind . <Key-Prior> "allcanvs yview scroll -1 pages" + bind . <Key-Next> "allcanvs yview scroll 1 pages" + bindkey <Key-Delete> "$ctext yview scroll -1 pages" + bindkey <Key-BackSpace> "$ctext yview scroll -1 pages" + bindkey <Key-space> "$ctext yview scroll 1 pages" + bindkey p "selnextline -1" + bindkey n "selnextline 1" + bindkey b "$ctext yview scroll -1 pages" + bindkey d "$ctext yview scroll 18 units" + bindkey u "$ctext yview scroll -18 units" + bindkey / {findnext 1} + bindkey <Key-Return> {findnext 0} + bindkey ? findprev + bindkey f nextfile + bind . <Control-q> doquit + bind . <Control-w> doquit + bind . <Control-f> dofind + bind . <Control-g> {findnext 0} + bind . <Control-r> findprev + bind . <Control-equal> {incrfont 1} + bind . <Control-KP_Add> {incrfont 1} + bind . <Control-minus> {incrfont -1} + bind . <Control-KP_Subtract> {incrfont -1} + bind $cflist <<ListboxSelect>> listboxsel + bind . <Destroy> {savestuff %W} + bind . <Button-1> "click %W" + bind $fstring <Key-Return> dofind + bind $sha1entry <Key-Return> gotocommit + bind $sha1entry <<PasteSelection>> clearsha1 + + set maincursor [. cget -cursor] + set textcursor [$ctext cget -cursor] + set curtextcursor $textcursor + + set rowctxmenu .rowctxmenu + menu $rowctxmenu -tearoff 0 + $rowctxmenu add command -label "Diff this -> selected" \ + -command {diffvssel 0} + $rowctxmenu add command -label "Diff selected -> this" \ + -command {diffvssel 1} + $rowctxmenu add command -label "Make patch" -command mkpatch + $rowctxmenu add command -label "Create tag" -command mktag + $rowctxmenu add command -label "Write commit to file" -command writecommit +} + +# when we make a key binding for the toplevel, make sure +# it doesn't get triggered when that key is pressed in the +# find string entry widget. +proc bindkey {ev script} { + global entries + bind . $ev $script + set escript [bind Entry $ev] + if {$escript == {}} { + set escript [bind Entry <Key>] + } + foreach e $entries { + bind $e $ev "$escript; break" + } +} + +# set the focus back to the toplevel for any click outside +# the entry widgets +proc click {w} { + global entries + foreach e $entries { + if {$w == $e} return + } + focus . +} + +proc savestuff {w} { + global canv canv2 canv3 ctext cflist mainfont textfont + global stuffsaved findmergefiles gaudydiff maxgraphpct + global maxwidth + + if {$stuffsaved} return + if {![winfo viewable .]} return + catch { + set f [open "~/.gitk-new" w] + puts $f [list set mainfont $mainfont] + puts $f [list set textfont $textfont] + puts $f [list set findmergefiles $findmergefiles] + puts $f [list set gaudydiff $gaudydiff] + puts $f [list set maxgraphpct $maxgraphpct] + puts $f [list set maxwidth $maxwidth] + puts $f "set geometry(width) [winfo width .ctop]" + puts $f "set geometry(height) [winfo height .ctop]" + puts $f "set geometry(canv1) [expr [winfo width $canv]-2]" + puts $f "set geometry(canv2) [expr [winfo width $canv2]-2]" + puts $f "set geometry(canv3) [expr [winfo width $canv3]-2]" + puts $f "set geometry(canvh) [expr [winfo height $canv]-2]" + set wid [expr {([winfo width $ctext] - 8) \ + / [font measure $textfont "0"]}] + puts $f "set geometry(ctextw) $wid" + set wid [expr {([winfo width $cflist] - 11) \ + / [font measure [$cflist cget -font] "0"]}] + puts $f "set geometry(cflistw) $wid" + close $f + file rename -force "~/.gitk-new" "~/.gitk" + } + set stuffsaved 1 +} + +proc resizeclistpanes {win w} { + global oldwidth + if [info exists oldwidth($win)] { + set s0 [$win sash coord 0] + set s1 [$win sash coord 1] + if {$w < 60} { + set sash0 [expr {int($w/2 - 2)}] + set sash1 [expr {int($w*5/6 - 2)}] + } else { + set factor [expr {1.0 * $w / $oldwidth($win)}] + set sash0 [expr {int($factor * [lindex $s0 0])}] + set sash1 [expr {int($factor * [lindex $s1 0])}] + if {$sash0 < 30} { + set sash0 30 + } + if {$sash1 < $sash0 + 20} { + set sash1 [expr $sash0 + 20] + } + if {$sash1 > $w - 10} { + set sash1 [expr $w - 10] + if {$sash0 > $sash1 - 20} { + set sash0 [expr $sash1 - 20] + } + } + } + $win sash place 0 $sash0 [lindex $s0 1] + $win sash place 1 $sash1 [lindex $s1 1] + } + set oldwidth($win) $w +} + +proc resizecdetpanes {win w} { + global oldwidth + if [info exists oldwidth($win)] { + set s0 [$win sash coord 0] + if {$w < 60} { + set sash0 [expr {int($w*3/4 - 2)}] + } else { + set factor [expr {1.0 * $w / $oldwidth($win)}] + set sash0 [expr {int($factor * [lindex $s0 0])}] + if {$sash0 < 45} { + set sash0 45 + } + if {$sash0 > $w - 15} { + set sash0 [expr $w - 15] + } + } + $win sash place 0 $sash0 [lindex $s0 1] + } + set oldwidth($win) $w +} + +proc allcanvs args { + global canv canv2 canv3 + eval $canv $args + eval $canv2 $args + eval $canv3 $args +} + +proc bindall {event action} { + global canv canv2 canv3 + bind $canv $event $action + bind $canv2 $event $action + bind $canv3 $event $action +} + +proc about {} { + set w .about + if {[winfo exists $w]} { + raise $w + return + } + toplevel $w + wm title $w "About gitk" + message $w.m -text { +Gitk version 1.2 + +Copyright © 2005 Paul Mackerras + +Use and redistribute under the terms of the GNU General Public License} \ + -justify center -aspect 400 + pack $w.m -side top -fill x -padx 20 -pady 20 + button $w.ok -text Close -command "destroy $w" + pack $w.ok -side bottom +} + +proc assigncolor {id} { + global commitinfo colormap commcolors colors nextcolor + global parents nparents children nchildren + global cornercrossings crossings + + if [info exists colormap($id)] return + set ncolors [llength $colors] + if {$nparents($id) <= 1 && $nchildren($id) == 1} { + set child [lindex $children($id) 0] + if {[info exists colormap($child)] + && $nparents($child) == 1} { + set colormap($id) $colormap($child) + return + } + } + set badcolors {} + if {[info exists cornercrossings($id)]} { + foreach x $cornercrossings($id) { + if {[info exists colormap($x)] + && [lsearch -exact $badcolors $colormap($x)] < 0} { + lappend badcolors $colormap($x) + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors {} + } + } + set origbad $badcolors + if {[llength $badcolors] < $ncolors - 1} { + if {[info exists crossings($id)]} { + foreach x $crossings($id) { + if {[info exists colormap($x)] + && [lsearch -exact $badcolors $colormap($x)] < 0} { + lappend badcolors $colormap($x) + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors $origbad + } + } + set origbad $badcolors + } + if {[llength $badcolors] < $ncolors - 1} { + foreach child $children($id) { + if {[info exists colormap($child)] + && [lsearch -exact $badcolors $colormap($child)] < 0} { + lappend badcolors $colormap($child) + } + if {[info exists parents($child)]} { + foreach p $parents($child) { + if {[info exists colormap($p)] + && [lsearch -exact $badcolors $colormap($p)] < 0} { + lappend badcolors $colormap($p) + } + } + } + } + if {[llength $badcolors] >= $ncolors} { + set badcolors $origbad + } + } + for {set i 0} {$i <= $ncolors} {incr i} { + set c [lindex $colors $nextcolor] + if {[incr nextcolor] >= $ncolors} { + set nextcolor 0 + } + if {[lsearch -exact $badcolors $c]} break + } + set colormap($id) $c +} + +proc initgraph {} { + global canvy canvy0 lineno numcommits nextcolor linespc + global mainline mainlinearrow sidelines + global nchildren ncleft + global displist nhyperspace + + allcanvs delete all + set nextcolor 0 + set canvy $canvy0 + set lineno -1 + set numcommits 0 + catch {unset mainline} + catch {unset mainlinearrow} + catch {unset sidelines} + foreach id [array names nchildren] { + set ncleft($id) $nchildren($id) + } + set displist {} + set nhyperspace 0 +} + +proc bindline {t id} { + global canv + + $canv bind $t <Enter> "lineenter %x %y $id" + $canv bind $t <Motion> "linemotion %x %y $id" + $canv bind $t <Leave> "lineleave $id" + $canv bind $t <Button-1> "lineclick %x %y $id 1" +} + +proc drawlines {id xtra} { + global mainline mainlinearrow sidelines lthickness colormap canv + + $canv delete lines.$id + if {[info exists mainline($id)]} { + set t [$canv create line $mainline($id) \ + -width [expr {($xtra + 1) * $lthickness}] \ + -fill $colormap($id) -tags lines.$id \ + -arrow $mainlinearrow($id)] + $canv lower $t + bindline $t $id + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set coords [lindex $ls 0] + set thick [lindex $ls 1] + set arrow [lindex $ls 2] + set t [$canv create line $coords -fill $colormap($id) \ + -width [expr {($thick + $xtra) * $lthickness}] \ + -arrow $arrow -tags lines.$id] + $canv lower $t + bindline $t $id + } + } +} + +# level here is an index in displist +proc drawcommitline {level} { + global parents children nparents displist + global canv canv2 canv3 mainfont namefont canvy linespc + global lineid linehtag linentag linedtag commitinfo + global colormap numcommits currentparents dupparents + global idtags idline idheads idotherrefs + global lineno lthickness mainline mainlinearrow sidelines + global commitlisted rowtextx idpos lastuse displist + global oldnlines olddlevel olddisplist + + incr numcommits + incr lineno + set id [lindex $displist $level] + set lastuse($id) $lineno + set lineid($lineno) $id + set idline($id) $lineno + set ofill [expr {[info exists commitlisted($id)]? "blue": "white"}] + if {![info exists commitinfo($id)]} { + readcommit $id + if {![info exists commitinfo($id)]} { + set commitinfo($id) {"No commit information available"} + set nparents($id) 0 + } + } + assigncolor $id + set currentparents {} + set dupparents {} + if {[info exists commitlisted($id)] && [info exists parents($id)]} { + foreach p $parents($id) { + if {[lsearch -exact $currentparents $p] < 0} { + lappend currentparents $p + } else { + # remember that this parent was listed twice + lappend dupparents $p + } + } + } + set x [xcoord $level $level $lineno] + set y1 $canvy + set canvy [expr $canvy + $linespc] + allcanvs conf -scrollregion \ + [list 0 0 0 [expr $y1 + 0.5 * $linespc + 2]] + if {[info exists mainline($id)]} { + lappend mainline($id) $x $y1 + if {$mainlinearrow($id) ne "none"} { + set mainline($id) [trimdiagstart $mainline($id)] + } + } + drawlines $id 0 + set orad [expr {$linespc / 3}] + set t [$canv create oval [expr $x - $orad] [expr $y1 - $orad] \ + [expr $x + $orad - 1] [expr $y1 + $orad - 1] \ + -fill $ofill -outline black -width 1] + $canv raise $t + $canv bind $t <1> {selcanvline {} %x %y} + set xt [xcoord [llength $displist] $level $lineno] + if {[llength $currentparents] > 2} { + set xt [expr {$xt + ([llength $currentparents] - 2) * $linespc}] + } + set rowtextx($lineno) $xt + set idpos($id) [list $x $xt $y1] + if {[info exists idtags($id)] || [info exists idheads($id)] + || [info exists idotherrefs($id)]} { + set xt [drawtags $id $x $xt $y1] + } + set headline [lindex $commitinfo($id) 0] + set name [lindex $commitinfo($id) 1] + set date [lindex $commitinfo($id) 2] + set linehtag($lineno) [$canv create text $xt $y1 -anchor w \ + -text $headline -font $mainfont ] + $canv bind $linehtag($lineno) <Button-3> "rowmenu %X %Y $id" + set linentag($lineno) [$canv2 create text 3 $y1 -anchor w \ + -text $name -font $namefont] + set linedtag($lineno) [$canv3 create text 3 $y1 -anchor w \ + -text $date -font $mainfont] + + set olddlevel $level + set olddisplist $displist + set oldnlines [llength $displist] +} + +proc drawtags {id x xt y1} { + global idtags idheads idotherrefs + global linespc lthickness + global canv mainfont idline rowtextx + + set marks {} + set ntags 0 + set nheads 0 + if {[info exists idtags($id)]} { + set marks $idtags($id) + set ntags [llength $marks] + } + if {[info exists idheads($id)]} { + set marks [concat $marks $idheads($id)] + set nheads [llength $idheads($id)] + } + if {[info exists idotherrefs($id)]} { + set marks [concat $marks $idotherrefs($id)] + } + if {$marks eq {}} { + return $xt + } + + set delta [expr {int(0.5 * ($linespc - $lthickness))}] + set yt [expr $y1 - 0.5 * $linespc] + set yb [expr $yt + $linespc - 1] + set xvals {} + set wvals {} + foreach tag $marks { + set wid [font measure $mainfont $tag] + lappend xvals $xt + lappend wvals $wid + set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}] + } + set t [$canv create line $x $y1 [lindex $xvals end] $y1 \ + -width $lthickness -fill black -tags tag.$id] + $canv lower $t + foreach tag $marks x $xvals wid $wvals { + set xl [expr $x + $delta] + set xr [expr $x + $delta + $wid + $lthickness] + if {[incr ntags -1] >= 0} { + # draw a tag + set t [$canv create polygon $x [expr $yt + $delta] $xl $yt \ + $xr $yt $xr $yb $xl $yb $x [expr $yb - $delta] \ + -width 1 -outline black -fill yellow -tags tag.$id] + $canv bind $t <1> [list showtag $tag 1] + set rowtextx($idline($id)) [expr {$xr + $linespc}] + } else { + # draw a head or other ref + if {[incr nheads -1] >= 0} { + set col green + } else { + set col "#ddddff" + } + set xl [expr $xl - $delta/2] + $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ + -width 1 -outline black -fill $col -tags tag.$id + } + set t [$canv create text $xl $y1 -anchor w -text $tag \ + -font $mainfont -tags tag.$id] + if {$ntags >= 0} { + $canv bind $t <1> [list showtag $tag 1] + } + } + return $xt +} + +proc notecrossings {id lo hi corner} { + global olddisplist crossings cornercrossings + + for {set i $lo} {[incr i] < $hi} {} { + set p [lindex $olddisplist $i] + if {$p == {}} continue + if {$i == $corner} { + if {![info exists cornercrossings($id)] + || [lsearch -exact $cornercrossings($id) $p] < 0} { + lappend cornercrossings($id) $p + } + if {![info exists cornercrossings($p)] + || [lsearch -exact $cornercrossings($p) $id] < 0} { + lappend cornercrossings($p) $id + } + } else { + if {![info exists crossings($id)] + || [lsearch -exact $crossings($id) $p] < 0} { + lappend crossings($id) $p + } + if {![info exists crossings($p)] + || [lsearch -exact $crossings($p) $id] < 0} { + lappend crossings($p) $id + } + } + } +} + +proc xcoord {i level ln} { + global canvx0 xspc1 xspc2 + + set x [expr {$canvx0 + $i * $xspc1($ln)}] + if {$i > 0 && $i == $level} { + set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}] + } elseif {$i > $level} { + set x [expr {$x + $xspc2 - $xspc1($ln)}] + } + return $x +} + +# it seems Tk can't draw arrows on the end of diagonal line segments... +proc trimdiagend {line} { + while {[llength $line] > 4} { + set x1 [lindex $line end-3] + set y1 [lindex $line end-2] + set x2 [lindex $line end-1] + set y2 [lindex $line end] + if {($x1 == $x2) != ($y1 == $y2)} break + set line [lreplace $line end-1 end] + } + return $line +} + +proc trimdiagstart {line} { + while {[llength $line] > 4} { + set x1 [lindex $line 0] + set y1 [lindex $line 1] + set x2 [lindex $line 2] + set y2 [lindex $line 3] + if {($x1 == $x2) != ($y1 == $y2)} break + set line [lreplace $line 0 1] + } + return $line +} + +proc drawslants {id needonscreen nohs} { + global canv mainline mainlinearrow sidelines + global canvx0 canvy xspc1 xspc2 lthickness + global currentparents dupparents + global lthickness linespc canvy colormap lineno geometry + global maxgraphpct maxwidth + global displist onscreen lastuse + global parents commitlisted + global oldnlines olddlevel olddisplist + global nhyperspace numcommits nnewparents + + if {$lineno < 0} { + lappend displist $id + set onscreen($id) 1 + return 0 + } + + set y1 [expr {$canvy - $linespc}] + set y2 $canvy + + # work out what we need to get back on screen + set reins {} + if {$onscreen($id) < 0} { + # next to do isn't displayed, better get it on screen... + lappend reins [list $id 0] + } + # make sure all the previous commits's parents are on the screen + foreach p $currentparents { + if {$onscreen($p) < 0} { + lappend reins [list $p 0] + } + } + # bring back anything requested by caller + if {$needonscreen ne {}} { + lappend reins $needonscreen + } + + # try the shortcut + if {$currentparents == $id && $onscreen($id) == 0 && $reins eq {}} { + set dlevel $olddlevel + set x [xcoord $dlevel $dlevel $lineno] + set mainline($id) [list $x $y1] + set mainlinearrow($id) none + set lastuse($id) $lineno + set displist [lreplace $displist $dlevel $dlevel $id] + set onscreen($id) 1 + set xspc1([expr {$lineno + 1}]) $xspc1($lineno) + return $dlevel + } + + # update displist + set displist [lreplace $displist $olddlevel $olddlevel] + set j $olddlevel + foreach p $currentparents { + set lastuse($p) $lineno + if {$onscreen($p) == 0} { + set displist [linsert $displist $j $p] + set onscreen($p) 1 + incr j + } + } + if {$onscreen($id) == 0} { + lappend displist $id + set onscreen($id) 1 + } + + # remove the null entry if present + set nullentry [lsearch -exact $displist {}] + if {$nullentry >= 0} { + set displist [lreplace $displist $nullentry $nullentry] + } + + # bring back the ones we need now (if we did it earlier + # it would change displist and invalidate olddlevel) + foreach pi $reins { + # test again in case of duplicates in reins + set p [lindex $pi 0] + if {$onscreen($p) < 0} { + set onscreen($p) 1 + set lastuse($p) $lineno + set displist [linsert $displist [lindex $pi 1] $p] + incr nhyperspace -1 + } + } + + set lastuse($id) $lineno + + # see if we need to make any lines jump off into hyperspace + set displ [llength $displist] + if {$displ > $maxwidth} { + set ages {} + foreach x $displist { + lappend ages [list $lastuse($x) $x] + } + set ages [lsort -integer -index 0 $ages] + set k 0 + while {$displ > $maxwidth} { + set use [lindex $ages $k 0] + set victim [lindex $ages $k 1] + if {$use >= $lineno - 5} break + incr k + if {[lsearch -exact $nohs $victim] >= 0} continue + set i [lsearch -exact $displist $victim] + set displist [lreplace $displist $i $i] + set onscreen($victim) -1 + incr nhyperspace + incr displ -1 + if {$i < $nullentry} { + incr nullentry -1 + } + set x [lindex $mainline($victim) end-1] + lappend mainline($victim) $x $y1 + set line [trimdiagend $mainline($victim)] + set arrow "last" + if {$mainlinearrow($victim) ne "none"} { + set line [trimdiagstart $line] + set arrow "both" + } + lappend sidelines($victim) [list $line 1 $arrow] + unset mainline($victim) + } + } + + set dlevel [lsearch -exact $displist $id] + + # If we are reducing, put in a null entry + if {$displ < $oldnlines} { + # does the next line look like a merge? + # i.e. does it have > 1 new parent? + if {$nnewparents($id) > 1} { + set i [expr {$dlevel + 1}] + } elseif {$nnewparents([lindex $olddisplist $olddlevel]) == 0} { + set i $olddlevel + if {$nullentry >= 0 && $nullentry < $i} { + incr i -1 + } + } elseif {$nullentry >= 0} { + set i $nullentry + while {$i < $displ + && [lindex $olddisplist $i] == [lindex $displist $i]} { + incr i + } + } else { + set i $olddlevel + if {$dlevel >= $i} { + incr i + } + } + if {$i < $displ} { + set displist [linsert $displist $i {}] + incr displ + if {$dlevel >= $i} { + incr dlevel + } + } + } + + # decide on the line spacing for the next line + set lj [expr {$lineno + 1}] + set maxw [expr {$maxgraphpct * $geometry(canv1) / 100}] + if {$displ <= 1 || $canvx0 + $displ * $xspc2 <= $maxw} { + set xspc1($lj) $xspc2 + } else { + set xspc1($lj) [expr {($maxw - $canvx0 - $xspc2) / ($displ - 1)}] + if {$xspc1($lj) < $lthickness} { + set xspc1($lj) $lthickness + } + } + + foreach idi $reins { + set id [lindex $idi 0] + set j [lsearch -exact $displist $id] + set xj [xcoord $j $dlevel $lj] + set mainline($id) [list $xj $y2] + set mainlinearrow($id) first + } + + set i -1 + foreach id $olddisplist { + incr i + if {$id == {}} continue + if {$onscreen($id) <= 0} continue + set xi [xcoord $i $olddlevel $lineno] + if {$i == $olddlevel} { + foreach p $currentparents { + set j [lsearch -exact $displist $p] + set coords [list $xi $y1] + set xj [xcoord $j $dlevel $lj] + if {$xj < $xi - $linespc} { + lappend coords [expr {$xj + $linespc}] $y1 + notecrossings $p $j $i [expr {$j + 1}] + } elseif {$xj > $xi + $linespc} { + lappend coords [expr {$xj - $linespc}] $y1 + notecrossings $p $i $j [expr {$j - 1}] + } + if {[lsearch -exact $dupparents $p] >= 0} { + # draw a double-width line to indicate the doubled parent + lappend coords $xj $y2 + lappend sidelines($p) [list $coords 2 none] + if {![info exists mainline($p)]} { + set mainline($p) [list $xj $y2] + set mainlinearrow($p) none + } + } else { + # normal case, no parent duplicated + set yb $y2 + set dx [expr {abs($xi - $xj)}] + if {0 && $dx < $linespc} { + set yb [expr {$y1 + $dx}] + } + if {![info exists mainline($p)]} { + if {$xi != $xj} { + lappend coords $xj $yb + } + set mainline($p) $coords + set mainlinearrow($p) none + } else { + lappend coords $xj $yb + if {$yb < $y2} { + lappend coords $xj $y2 + } + lappend sidelines($p) [list $coords 1 none] + } + } + } + } else { + set j $i + if {[lindex $displist $i] != $id} { + set j [lsearch -exact $displist $id] + } + if {$j != $i || $xspc1($lineno) != $xspc1($lj) + || ($olddlevel < $i && $i < $dlevel) + || ($dlevel < $i && $i < $olddlevel)} { + set xj [xcoord $j $dlevel $lj] + lappend mainline($id) $xi $y1 $xj $y2 + } + } + } + return $dlevel +} + +# search for x in a list of lists +proc llsearch {llist x} { + set i 0 + foreach l $llist { + if {$l == $x || [lsearch -exact $l $x] >= 0} { + return $i + } + incr i + } + return -1 +} + +proc drawmore {reading} { + global displayorder numcommits ncmupdate nextupdate + global stopped nhyperspace parents commitlisted + global maxwidth onscreen displist currentparents olddlevel + + set n [llength $displayorder] + while {$numcommits < $n} { + set id [lindex $displayorder $numcommits] + set ctxend [expr {$numcommits + 10}] + if {!$reading && $ctxend > $n} { + set ctxend $n + } + set dlist {} + if {$numcommits > 0} { + set dlist [lreplace $displist $olddlevel $olddlevel] + set i $olddlevel + foreach p $currentparents { + if {$onscreen($p) == 0} { + set dlist [linsert $dlist $i $p] + incr i + } + } + } + set nohs {} + set reins {} + set isfat [expr {[llength $dlist] > $maxwidth}] + if {$nhyperspace > 0 || $isfat} { + if {$ctxend > $n} break + # work out what to bring back and + # what we want to don't want to send into hyperspace + set room 1 + for {set k $numcommits} {$k < $ctxend} {incr k} { + set x [lindex $displayorder $k] + set i [llsearch $dlist $x] + if {$i < 0} { + set i [llength $dlist] + lappend dlist $x + } + if {[lsearch -exact $nohs $x] < 0} { + lappend nohs $x + } + if {$reins eq {} && $onscreen($x) < 0 && $room} { + set reins [list $x $i] + } + set newp {} + if {[info exists commitlisted($x)]} { + set right 0 + foreach p $parents($x) { + if {[llsearch $dlist $p] < 0} { + lappend newp $p + if {[lsearch -exact $nohs $p] < 0} { + lappend nohs $p + } + if {$reins eq {} && $onscreen($p) < 0 && $room} { + set reins [list $p [expr {$i + $right}]] + } + } + set right 1 + } + } + set l [lindex $dlist $i] + if {[llength $l] == 1} { + set l $newp + } else { + set j [lsearch -exact $l $x] + set l [concat [lreplace $l $j $j] $newp] + } + set dlist [lreplace $dlist $i $i $l] + if {$room && $isfat && [llength $newp] <= 1} { + set room 0 + } + } + } + + set dlevel [drawslants $id $reins $nohs] + drawcommitline $dlevel + if {[clock clicks -milliseconds] >= $nextupdate + && $numcommits >= $ncmupdate} { + doupdate $reading + if {$stopped} break + } + } +} + +# level here is an index in todo +proc updatetodo {level noshortcut} { + global ncleft todo nnewparents + global commitlisted parents onscreen + + set id [lindex $todo $level] + set olds {} + if {[info exists commitlisted($id)]} { + foreach p $parents($id) { + if {[lsearch -exact $olds $p] < 0} { + lappend olds $p + } + } + } + if {!$noshortcut && [llength $olds] == 1} { + set p [lindex $olds 0] + if {$ncleft($p) == 1 && [lsearch -exact $todo $p] < 0} { + set ncleft($p) 0 + set todo [lreplace $todo $level $level $p] + set onscreen($p) 0 + set nnewparents($id) 1 + return 0 + } + } + + set todo [lreplace $todo $level $level] + set i $level + set n 0 + foreach p $olds { + incr ncleft($p) -1 + set k [lsearch -exact $todo $p] + if {$k < 0} { + set todo [linsert $todo $i $p] + set onscreen($p) 0 + incr i + incr n + } + } + set nnewparents($id) $n + + return 1 +} + +proc decidenext {{noread 0}} { + global ncleft todo + global datemode cdate + global commitinfo + + # choose which one to do next time around + set todol [llength $todo] + set level -1 + set latest {} + for {set k $todol} {[incr k -1] >= 0} {} { + set p [lindex $todo $k] + if {$ncleft($p) == 0} { + if {$datemode} { + if {![info exists commitinfo($p)]} { + if {$noread} { + return {} + } + readcommit $p + } + if {$latest == {} || $cdate($p) > $latest} { + set level $k + set latest $cdate($p) + } + } else { + set level $k + break + } + } + } + if {$level < 0} { + if {$todo != {}} { + puts "ERROR: none of the pending commits can be done yet:" + foreach p $todo { + puts " $p ($ncleft($p))" + } + } + return -1 + } + + return $level +} + +proc drawcommit {id} { + global phase todo nchildren datemode nextupdate + global numcommits ncmupdate displayorder todo onscreen + + if {$phase != "incrdraw"} { + set phase incrdraw + set displayorder {} + set todo {} + initgraph + } + if {$nchildren($id) == 0} { + lappend todo $id + set onscreen($id) 0 + } + set level [decidenext 1] + if {$level == {} || $id != [lindex $todo $level]} { + return + } + while 1 { + lappend displayorder [lindex $todo $level] + if {[updatetodo $level $datemode]} { + set level [decidenext 1] + if {$level == {}} break + } + set id [lindex $todo $level] + if {![info exists commitlisted($id)]} { + break + } + } + drawmore 1 +} + +proc finishcommits {} { + global phase + global canv mainfont ctext maincursor textcursor + + if {$phase != "incrdraw"} { + $canv delete all + $canv create text 3 3 -anchor nw -text "No commits selected" \ + -font $mainfont -tags textitems + set phase {} + } else { + drawrest + } + . config -cursor $maincursor + settextcursor $textcursor +} + +# Don't change the text pane cursor if it is currently the hand cursor, +# showing that we are over a sha1 ID link. +proc settextcursor {c} { + global ctext curtextcursor + + if {[$ctext cget -cursor] == $curtextcursor} { + $ctext config -cursor $c + } + set curtextcursor $c +} + +proc drawgraph {} { + global nextupdate startmsecs ncmupdate + global displayorder onscreen + + if {$displayorder == {}} return + set startmsecs [clock clicks -milliseconds] + set nextupdate [expr $startmsecs + 100] + set ncmupdate 1 + initgraph + foreach id $displayorder { + set onscreen($id) 0 + } + drawmore 0 +} + +proc drawrest {} { + global phase stopped redisplaying selectedline + global datemode todo displayorder + global numcommits ncmupdate + global nextupdate startmsecs + + set level [decidenext] + if {$level >= 0} { + set phase drawgraph + while 1 { + lappend displayorder [lindex $todo $level] + set hard [updatetodo $level $datemode] + if {$hard} { + set level [decidenext] + if {$level < 0} break + } + } + drawmore 0 + } + set phase {} + set drawmsecs [expr [clock clicks -milliseconds] - $startmsecs] + #puts "overall $drawmsecs ms for $numcommits commits" + if {$redisplaying} { + if {$stopped == 0 && [info exists selectedline]} { + selectline $selectedline 0 + } + if {$stopped == 1} { + set stopped 0 + after idle drawgraph + } else { + set redisplaying 0 + } + } +} + +proc findmatches {f} { + global findtype foundstring foundstrlen + if {$findtype == "Regexp"} { + set matches [regexp -indices -all -inline $foundstring $f] + } else { + if {$findtype == "IgnCase"} { + set str [string tolower $f] + } else { + set str $f + } + set matches {} + set i 0 + while {[set j [string first $foundstring $str $i]] >= 0} { + lappend matches [list $j [expr $j+$foundstrlen-1]] + set i [expr $j + $foundstrlen] + } + } + return $matches +} + +proc dofind {} { + global findtype findloc findstring markedmatches commitinfo + global numcommits lineid linehtag linentag linedtag + global mainfont namefont canv canv2 canv3 selectedline + global matchinglines foundstring foundstrlen + + stopfindproc + unmarkmatches + focus . + set matchinglines {} + if {$findloc == "Pickaxe"} { + findpatches + return + } + if {$findtype == "IgnCase"} { + set foundstring [string tolower $findstring] + } else { + set foundstring $findstring + } + set foundstrlen [string length $findstring] + if {$foundstrlen == 0} return + if {$findloc == "Files"} { + findfiles + return + } + if {![info exists selectedline]} { + set oldsel -1 + } else { + set oldsel $selectedline + } + set didsel 0 + set fldtypes {Headline Author Date Committer CDate Comment} + for {set l 0} {$l < $numcommits} {incr l} { + set id $lineid($l) + set info $commitinfo($id) + set doesmatch 0 + foreach f $info ty $fldtypes { + if {$findloc != "All fields" && $findloc != $ty} { + continue + } + set matches [findmatches $f] + if {$matches == {}} continue + set doesmatch 1 + if {$ty == "Headline"} { + markmatches $canv $l $f $linehtag($l) $matches $mainfont + } elseif {$ty == "Author"} { + markmatches $canv2 $l $f $linentag($l) $matches $namefont + } elseif {$ty == "Date"} { + markmatches $canv3 $l $f $linedtag($l) $matches $mainfont + } + } + if {$doesmatch} { + lappend matchinglines $l + if {!$didsel && $l > $oldsel} { + findselectline $l + set didsel 1 + } + } + } + if {$matchinglines == {}} { + bell + } elseif {!$didsel} { + findselectline [lindex $matchinglines 0] + } +} + +proc findselectline {l} { + global findloc commentend ctext + selectline $l 1 + if {$findloc == "All fields" || $findloc == "Comments"} { + # highlight the matches in the comments + set f [$ctext get 1.0 $commentend] + set matches [findmatches $f] + foreach match $matches { + set start [lindex $match 0] + set end [expr [lindex $match 1] + 1] + $ctext tag add found "1.0 + $start c" "1.0 + $end c" + } + } +} + +proc findnext {restart} { + global matchinglines selectedline + if {![info exists matchinglines]} { + if {$restart} { + dofind + } + return + } + if {![info exists selectedline]} return + foreach l $matchinglines { + if {$l > $selectedline} { + findselectline $l + return + } + } + bell +} + +proc findprev {} { + global matchinglines selectedline + if {![info exists matchinglines]} { + dofind + return + } + if {![info exists selectedline]} return + set prev {} + foreach l $matchinglines { + if {$l >= $selectedline} break + set prev $l + } + if {$prev != {}} { + findselectline $prev + } else { + bell + } +} + +proc findlocchange {name ix op} { + global findloc findtype findtypemenu + if {$findloc == "Pickaxe"} { + set findtype Exact + set state disabled + } else { + set state normal + } + $findtypemenu entryconf 1 -state $state + $findtypemenu entryconf 2 -state $state +} + +proc stopfindproc {{done 0}} { + global findprocpid findprocfile findids + global ctext findoldcursor phase maincursor textcursor + global findinprogress + + catch {unset findids} + if {[info exists findprocpid]} { + if {!$done} { + catch {exec kill $findprocpid} + } + catch {close $findprocfile} + unset findprocpid + } + if {[info exists findinprogress]} { + unset findinprogress + if {$phase != "incrdraw"} { + . config -cursor $maincursor + settextcursor $textcursor + } + } +} + +proc findpatches {} { + global findstring selectedline numcommits + global findprocpid findprocfile + global finddidsel ctext lineid findinprogress + global findinsertpos + + if {$numcommits == 0} return + + # make a list of all the ids to search, starting at the one + # after the selected line (if any) + if {[info exists selectedline]} { + set l $selectedline + } else { + set l -1 + } + set inputids {} + for {set i 0} {$i < $numcommits} {incr i} { + if {[incr l] >= $numcommits} { + set l 0 + } + append inputids $lineid($l) "\n" + } + + if {[catch { + set f [open [list | hg debug-diff-tree --stdin -s -r -S$findstring \ + << $inputids] r] + } err]} { + error_popup "Error starting search process: $err" + return + } + + set findinsertpos end + set findprocfile $f + set findprocpid [pid $f] + fconfigure $f -blocking 0 + fileevent $f readable readfindproc + set finddidsel 0 + . config -cursor watch + settextcursor watch + set findinprogress 1 +} + +proc readfindproc {} { + global findprocfile finddidsel + global idline matchinglines findinsertpos + + set n [gets $findprocfile line] + if {$n < 0} { + if {[eof $findprocfile]} { + stopfindproc 1 + if {!$finddidsel} { + bell + } + } + return + } + if {![regexp {^[0-9a-f]{40}} $line id]} { + error_popup "Can't parse git-diff-tree output: $line" + stopfindproc + return + } + if {![info exists idline($id)]} { + puts stderr "spurious id: $id" + return + } + set l $idline($id) + insertmatch $l $id +} + +proc insertmatch {l id} { + global matchinglines findinsertpos finddidsel + + if {$findinsertpos == "end"} { + if {$matchinglines != {} && $l < [lindex $matchinglines 0]} { + set matchinglines [linsert $matchinglines 0 $l] + set findinsertpos 1 + } else { + lappend matchinglines $l + } + } else { + set matchinglines [linsert $matchinglines $findinsertpos $l] + incr findinsertpos + } + markheadline $l $id + if {!$finddidsel} { + findselectline $l + set finddidsel 1 + } +} + +proc findfiles {} { + global selectedline numcommits lineid ctext + global ffileline finddidsel parents nparents + global findinprogress findstartline findinsertpos + global treediffs fdiffids fdiffsneeded fdiffpos + global findmergefiles + + if {$numcommits == 0} return + + if {[info exists selectedline]} { + set l [expr {$selectedline + 1}] + } else { + set l 0 + } + set ffileline $l + set findstartline $l + set diffsneeded {} + set fdiffsneeded {} + while 1 { + set id $lineid($l) + if {$findmergefiles || $nparents($id) == 1} { + foreach p $parents($id) { + if {![info exists treediffs([list $id $p])]} { + append diffsneeded "$id $p\n" + lappend fdiffsneeded [list $id $p] + } + } + } + if {[incr l] >= $numcommits} { + set l 0 + } + if {$l == $findstartline} break + } + + # start off a git-diff-tree process if needed + if {$diffsneeded ne {}} { + if {[catch { + set df [open [list | hg debug-diff-tree -r --stdin << $diffsneeded] r] + } err ]} { + error_popup "Error starting search process: $err" + return + } + catch {unset fdiffids} + set fdiffpos 0 + fconfigure $df -blocking 0 + fileevent $df readable [list readfilediffs $df] + } + + set finddidsel 0 + set findinsertpos end + set id $lineid($l) + set p [lindex $parents($id) 0] + . config -cursor watch + settextcursor watch + set findinprogress 1 + findcont [list $id $p] + update +} + +proc readfilediffs {df} { + global findids fdiffids fdiffs + + set n [gets $df line] + if {$n < 0} { + if {[eof $df]} { + donefilediff + if {[catch {close $df} err]} { + stopfindproc + bell + error_popup "Error in hg debug-diff-tree: $err" + } elseif {[info exists findids]} { + set ids $findids + stopfindproc + bell + error_popup "Couldn't find diffs for {$ids}" + } + } + return + } + if {[regexp {^([0-9a-f]{40}) \(from ([0-9a-f]{40})\)} $line match id p]} { + # start of a new string of diffs + donefilediff + set fdiffids [list $id $p] + set fdiffs {} + } elseif {[string match ":*" $line]} { + lappend fdiffs [lindex $line 5] + } +} + +proc donefilediff {} { + global fdiffids fdiffs treediffs findids + global fdiffsneeded fdiffpos + + if {[info exists fdiffids]} { + while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffids + && $fdiffpos < [llength $fdiffsneeded]} { + # git-diff-tree doesn't output anything for a commit + # which doesn't change anything + set nullids [lindex $fdiffsneeded $fdiffpos] + set treediffs($nullids) {} + if {[info exists findids] && $nullids eq $findids} { + unset findids + findcont $nullids + } + incr fdiffpos + } + incr fdiffpos + + if {![info exists treediffs($fdiffids)]} { + set treediffs($fdiffids) $fdiffs + } + if {[info exists findids] && $fdiffids eq $findids} { + unset findids + findcont $fdiffids + } + } +} + +proc findcont {ids} { + global findids treediffs parents nparents + global ffileline findstartline finddidsel + global lineid numcommits matchinglines findinprogress + global findmergefiles + + set id [lindex $ids 0] + set p [lindex $ids 1] + set pi [lsearch -exact $parents($id) $p] + set l $ffileline + while 1 { + if {$findmergefiles || $nparents($id) == 1} { + if {![info exists treediffs($ids)]} { + set findids $ids + set ffileline $l + return + } + set doesmatch 0 + foreach f $treediffs($ids) { + set x [findmatches $f] + if {$x != {}} { + set doesmatch 1 + break + } + } + if {$doesmatch} { + insertmatch $l $id + set pi $nparents($id) + } + } else { + set pi $nparents($id) + } + if {[incr pi] >= $nparents($id)} { + set pi 0 + if {[incr l] >= $numcommits} { + set l 0 + } + if {$l == $findstartline} break + set id $lineid($l) + } + set p [lindex $parents($id) $pi] + set ids [list $id $p] + } + stopfindproc + if {!$finddidsel} { + bell + } +} + +# mark a commit as matching by putting a yellow background +# behind the headline +proc markheadline {l id} { + global canv mainfont linehtag commitinfo + + set bbox [$canv bbox $linehtag($l)] + set t [$canv create rect $bbox -outline {} -tags matches -fill yellow] + $canv lower $t +} + +# mark the bits of a headline, author or date that match a find string +proc markmatches {canv l str tag matches font} { + set bbox [$canv bbox $tag] + set x0 [lindex $bbox 0] + set y0 [lindex $bbox 1] + set y1 [lindex $bbox 3] + foreach match $matches { + set start [lindex $match 0] + set end [lindex $match 1] + if {$start > $end} continue + set xoff [font measure $font [string range $str 0 [expr $start-1]]] + set xlen [font measure $font [string range $str 0 [expr $end]]] + set t [$canv create rect [expr $x0+$xoff] $y0 [expr $x0+$xlen+2] $y1 \ + -outline {} -tags matches -fill yellow] + $canv lower $t + } +} + +proc unmarkmatches {} { + global matchinglines findids + allcanvs delete matches + catch {unset matchinglines} + catch {unset findids} +} + +proc selcanvline {w x y} { + global canv canvy0 ctext linespc + global lineid linehtag linentag linedtag rowtextx + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax == {}} return + set yfrac [lindex [$canv yview] 0] + set y [expr {$y + $yfrac * $ymax}] + set l [expr {int(($y - $canvy0) / $linespc + 0.5)}] + if {$l < 0} { + set l 0 + } + if {$w eq $canv} { + if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return + } + unmarkmatches + selectline $l 1 +} + +proc commit_descriptor {p} { + global commitinfo + set l "..." + if {[info exists commitinfo($p)]} { + set l [lindex $commitinfo($p) 0] + } + return "$p ($l)" +} + +# append some text to the ctext widget, and make any SHA1 ID +# that we know about be a clickable link. +proc appendwithlinks {text} { + global ctext idline linknum + + set start [$ctext index "end - 1c"] + $ctext insert end $text + $ctext insert end "\n" + set links [regexp -indices -all -inline {[0-9a-f]{40}} $text] + foreach l $links { + set s [lindex $l 0] + set e [lindex $l 1] + set linkid [string range $text $s $e] + if {![info exists idline($linkid)]} continue + incr e + $ctext tag add link "$start + $s c" "$start + $e c" + $ctext tag add link$linknum "$start + $s c" "$start + $e c" + $ctext tag bind link$linknum <1> [list selectline $idline($linkid) 1] + incr linknum + } + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } +} + +proc selectline {l isnew} { + global canv canv2 canv3 ctext commitinfo selectedline + global lineid linehtag linentag linedtag + global canvy0 linespc parents nparents children + global cflist currentid sha1entry + global commentend idtags idline linknum + + $canv delete hover + normalline + if {![info exists lineid($l)] || ![info exists linehtag($l)]} return + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \ + -tags secsel -fill [$canv cget -selectbackground]] + $canv lower $t + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \ + -tags secsel -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + $canv3 delete secsel + set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \ + -tags secsel -fill [$canv3 cget -selectbackground]] + $canv3 lower $t + set y [expr {$canvy0 + $l * $linespc}] + set ymax [lindex [$canv cget -scrollregion] 3] + set ytop [expr {$y - $linespc - 1}] + set ybot [expr {$y + $linespc + 1}] + set wnow [$canv yview] + set wtop [expr [lindex $wnow 0] * $ymax] + set wbot [expr [lindex $wnow 1] * $ymax] + set wh [expr {$wbot - $wtop}] + set newtop $wtop + if {$ytop < $wtop} { + if {$ybot < $wtop} { + set newtop [expr {$y - $wh / 2.0}] + } else { + set newtop $ytop + if {$newtop > $wtop - $linespc} { + set newtop [expr {$wtop - $linespc}] + } + } + } elseif {$ybot > $wbot} { + if {$ytop > $wbot} { + set newtop [expr {$y - $wh / 2.0}] + } else { + set newtop [expr {$ybot - $wh}] + if {$newtop < $wtop + $linespc} { + set newtop [expr {$wtop + $linespc}] + } + } + } + if {$newtop != $wtop} { + if {$newtop < 0} { + set newtop 0 + } + allcanvs yview moveto [expr $newtop * 1.0 / $ymax] + } + + if {$isnew} { + addtohistory [list selectline $l 0] + } + + set selectedline $l + + set id $lineid($l) + set currentid $id + $sha1entry delete 0 end + $sha1entry insert 0 $id + $sha1entry selection from 0 + $sha1entry selection to end + + $ctext conf -state normal + $ctext delete 0.0 end + set linknum 0 + $ctext mark set fmark.0 0.0 + $ctext mark gravity fmark.0 left + set info $commitinfo($id) + $ctext insert end "Author: [lindex $info 1] [lindex $info 2]\n" + $ctext insert end "Committer: [lindex $info 3] [lindex $info 4]\n" + if {[info exists idtags($id)]} { + $ctext insert end "Tags:" + foreach tag $idtags($id) { + $ctext insert end " $tag" + } + $ctext insert end "\n" + } + + set comment {} + if {[info exists parents($id)]} { + foreach p $parents($id) { + append comment "Parent: [commit_descriptor $p]\n" + } + } + if {[info exists children($id)]} { + foreach c $children($id) { + append comment "Child: [commit_descriptor $c]\n" + } + } + append comment "\n" + append comment [lindex $info 5] + + # make anything that looks like a SHA1 ID be a clickable link + appendwithlinks $comment + + $ctext tag delete Comments + $ctext tag remove found 1.0 end + $ctext conf -state disabled + set commentend [$ctext index "end - 1c"] + + $cflist delete 0 end + $cflist insert end "Comments" + if {$nparents($id) == 1} { + startdiff [concat $id $parents($id)] + } elseif {$nparents($id) > 1} { + mergediff $id + } +} + +proc selnextline {dir} { + global selectedline + if {![info exists selectedline]} return + set l [expr $selectedline + $dir] + unmarkmatches + selectline $l 1 +} + +proc unselectline {} { + global selectedline + + catch {unset selectedline} + allcanvs delete secsel +} + +proc addtohistory {cmd} { + global history historyindex + + if {$historyindex > 0 + && [lindex $history [expr {$historyindex - 1}]] == $cmd} { + return + } + + if {$historyindex < [llength $history]} { + set history [lreplace $history $historyindex end $cmd] + } else { + lappend history $cmd + } + incr historyindex + if {$historyindex > 1} { + .ctop.top.bar.leftbut conf -state normal + } else { + .ctop.top.bar.leftbut conf -state disabled + } + .ctop.top.bar.rightbut conf -state disabled +} + +proc goback {} { + global history historyindex + + if {$historyindex > 1} { + incr historyindex -1 + set cmd [lindex $history [expr {$historyindex - 1}]] + eval $cmd + .ctop.top.bar.rightbut conf -state normal + } + if {$historyindex <= 1} { + .ctop.top.bar.leftbut conf -state disabled + } +} + +proc goforw {} { + global history historyindex + + if {$historyindex < [llength $history]} { + set cmd [lindex $history $historyindex] + incr historyindex + eval $cmd + .ctop.top.bar.leftbut conf -state normal + } + if {$historyindex >= [llength $history]} { + .ctop.top.bar.rightbut conf -state disabled + } +} + +proc mergediff {id} { + global parents diffmergeid diffmergegca mergefilelist diffpindex + + set diffmergeid $id + set diffpindex -1 + set diffmergegca [findgca $parents($id)] + if {[info exists mergefilelist($id)]} { + if {$mergefilelist($id) ne {}} { + showmergediff + } + } else { + contmergediff {} + } +} + +proc findgca {ids} { + set gca {} + foreach id $ids { + if {$gca eq {}} { + set gca $id + } else { + if {[catch { + set gca [exec hg debug-merge-base $gca $id] + } err]} { + return {} + } + } + } + return $gca +} + +proc contmergediff {ids} { + global diffmergeid diffpindex parents nparents diffmergegca + global treediffs mergefilelist diffids treepending + + # diff the child against each of the parents, and diff + # each of the parents against the GCA. + while 1 { + if {[lindex $ids 0] == $diffmergeid && $diffmergegca ne {}} { + set ids [list [lindex $ids 1] $diffmergegca] + } else { + if {[incr diffpindex] >= $nparents($diffmergeid)} break + set p [lindex $parents($diffmergeid) $diffpindex] + set ids [list $diffmergeid $p] + } + if {![info exists treediffs($ids)]} { + set diffids $ids + if {![info exists treepending]} { + gettreediffs $ids + } + return + } + } + + # If a file in some parent is different from the child and also + # different from the GCA, then it's interesting. + # If we don't have a GCA, then a file is interesting if it is + # different from the child in all the parents. + if {$diffmergegca ne {}} { + set files {} + foreach p $parents($diffmergeid) { + set gcadiffs $treediffs([list $p $diffmergegca]) + foreach f $treediffs([list $diffmergeid $p]) { + if {[lsearch -exact $files $f] < 0 + && [lsearch -exact $gcadiffs $f] >= 0} { + lappend files $f + } + } + } + set files [lsort $files] + } else { + set p [lindex $parents($diffmergeid) 0] + set files $treediffs([list $diffmergeid $p]) + for {set i 1} {$i < $nparents($diffmergeid) && $files ne {}} {incr i} { + set p [lindex $parents($diffmergeid) $i] + set df $treediffs([list $diffmergeid $p]) + set nf {} + foreach f $files { + if {[lsearch -exact $df $f] >= 0} { + lappend nf $f + } + } + set files $nf + } + } + + set mergefilelist($diffmergeid) $files + if {$files ne {}} { + showmergediff + } +} + +proc showmergediff {} { + global cflist diffmergeid mergefilelist parents + global diffopts diffinhunk currentfile currenthunk filelines + global diffblocked groupfilelast mergefds groupfilenum grouphunks + + set files $mergefilelist($diffmergeid) + foreach f $files { + $cflist insert end $f + } + set env(GIT_DIFF_OPTS) $diffopts + set flist {} + catch {unset currentfile} + catch {unset currenthunk} + catch {unset filelines} + catch {unset groupfilenum} + catch {unset grouphunks} + set groupfilelast -1 + foreach p $parents($diffmergeid) { + set cmd [list | hg debug-diff-tree -p $p $diffmergeid] + set cmd [concat $cmd $mergefilelist($diffmergeid)] + if {[catch {set f [open $cmd r]} err]} { + error_popup "Error getting diffs: $err" + foreach f $flist { + catch {close $f} + } + return + } + lappend flist $f + set ids [list $diffmergeid $p] + set mergefds($ids) $f + set diffinhunk($ids) 0 + set diffblocked($ids) 0 + fconfigure $f -blocking 0 + fileevent $f readable [list getmergediffline $f $ids $diffmergeid] + } +} + +proc getmergediffline {f ids id} { + global diffmergeid diffinhunk diffoldlines diffnewlines + global currentfile currenthunk + global diffoldstart diffnewstart diffoldlno diffnewlno + global diffblocked mergefilelist + global noldlines nnewlines difflcounts filelines + + set n [gets $f line] + if {$n < 0} { + if {![eof $f]} return + } + + if {!([info exists diffmergeid] && $diffmergeid == $id)} { + if {$n < 0} { + close $f + } + return + } + + if {$diffinhunk($ids) != 0} { + set fi $currentfile($ids) + if {$n > 0 && [regexp {^[-+ \\]} $line match]} { + # continuing an existing hunk + set line [string range $line 1 end] + set p [lindex $ids 1] + if {$match eq "-" || $match eq " "} { + set filelines($p,$fi,$diffoldlno($ids)) $line + incr diffoldlno($ids) + } + if {$match eq "+" || $match eq " "} { + set filelines($id,$fi,$diffnewlno($ids)) $line + incr diffnewlno($ids) + } + if {$match eq " "} { + if {$diffinhunk($ids) == 2} { + lappend difflcounts($ids) \ + [list $noldlines($ids) $nnewlines($ids)] + set noldlines($ids) 0 + set diffinhunk($ids) 1 + } + incr noldlines($ids) + } elseif {$match eq "-" || $match eq "+"} { + if {$diffinhunk($ids) == 1} { + lappend difflcounts($ids) [list $noldlines($ids)] + set noldlines($ids) 0 + set nnewlines($ids) 0 + set diffinhunk($ids) 2 + } + if {$match eq "-"} { + incr noldlines($ids) + } else { + incr nnewlines($ids) + } + } + # and if it's \ No newline at end of line, then what? + return + } + # end of a hunk + if {$diffinhunk($ids) == 1 && $noldlines($ids) != 0} { + lappend difflcounts($ids) [list $noldlines($ids)] + } elseif {$diffinhunk($ids) == 2 + && ($noldlines($ids) != 0 || $nnewlines($ids) != 0)} { + lappend difflcounts($ids) [list $noldlines($ids) $nnewlines($ids)] + } + set currenthunk($ids) [list $currentfile($ids) \ + $diffoldstart($ids) $diffnewstart($ids) \ + $diffoldlno($ids) $diffnewlno($ids) \ + $difflcounts($ids)] + set diffinhunk($ids) 0 + # -1 = need to block, 0 = unblocked, 1 = is blocked + set diffblocked($ids) -1 + processhunks + if {$diffblocked($ids) == -1} { + fileevent $f readable {} + set diffblocked($ids) 1 + } + } + + if {$n < 0} { + # eof + if {!$diffblocked($ids)} { + close $f + set currentfile($ids) [llength $mergefilelist($diffmergeid)] + set currenthunk($ids) [list $currentfile($ids) 0 0 0 0 {}] + processhunks + } + } elseif {[regexp {^diff --git a/(.*) b/} $line match fname]} { + # start of a new file + set currentfile($ids) \ + [lsearch -exact $mergefilelist($diffmergeid) $fname] + } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \ + $line match f1l f1c f2l f2c rest]} { + if {[info exists currentfile($ids)] && $currentfile($ids) >= 0} { + # start of a new hunk + if {$f1l == 0 && $f1c == 0} { + set f1l 1 + } + if {$f2l == 0 && $f2c == 0} { + set f2l 1 + } + set diffinhunk($ids) 1 + set diffoldstart($ids) $f1l + set diffnewstart($ids) $f2l + set diffoldlno($ids) $f1l + set diffnewlno($ids) $f2l + set difflcounts($ids) {} + set noldlines($ids) 0 + set nnewlines($ids) 0 + } + } +} + +proc processhunks {} { + global diffmergeid parents nparents currenthunk + global mergefilelist diffblocked mergefds + global grouphunks grouplinestart grouplineend groupfilenum + + set nfiles [llength $mergefilelist($diffmergeid)] + while 1 { + set fi $nfiles + set lno 0 + # look for the earliest hunk + foreach p $parents($diffmergeid) { + set ids [list $diffmergeid $p] + if {![info exists currenthunk($ids)]} return + set i [lindex $currenthunk($ids) 0] + set l [lindex $currenthunk($ids) 2] + if {$i < $fi || ($i == $fi && $l < $lno)} { + set fi $i + set lno $l + set pi $p + } + } + + if {$fi < $nfiles} { + set ids [list $diffmergeid $pi] + set hunk $currenthunk($ids) + unset currenthunk($ids) + if {$diffblocked($ids) > 0} { + fileevent $mergefds($ids) readable \ + [list getmergediffline $mergefds($ids) $ids $diffmergeid] + } + set diffblocked($ids) 0 + + if {[info exists groupfilenum] && $groupfilenum == $fi + && $lno <= $grouplineend} { + # add this hunk to the pending group + lappend grouphunks($pi) $hunk + set endln [lindex $hunk 4] + if {$endln > $grouplineend} { + set grouplineend $endln + } + continue + } + } + + # succeeding stuff doesn't belong in this group, so + # process the group now + if {[info exists groupfilenum]} { + processgroup + unset groupfilenum + unset grouphunks + } + + if {$fi >= $nfiles} break + + # start a new group + set groupfilenum $fi + set grouphunks($pi) [list $hunk] + set grouplinestart $lno + set grouplineend [lindex $hunk 4] + } +} + +proc processgroup {} { + global groupfilelast groupfilenum difffilestart + global mergefilelist diffmergeid ctext filelines + global parents diffmergeid diffoffset + global grouphunks grouplinestart grouplineend nparents + global mergemax + + $ctext conf -state normal + set id $diffmergeid + set f $groupfilenum + if {$groupfilelast != $f} { + $ctext insert end "\n" + set here [$ctext index "end - 1c"] + set difffilestart($f) $here + set mark fmark.[expr {$f + 1}] + $ctext mark set $mark $here + $ctext mark gravity $mark left + set header [lindex $mergefilelist($id) $f] + set l [expr {(78 - [string length $header]) / 2}] + set pad [string range "----------------------------------------" 1 $l] + $ctext insert end "$pad $header $pad\n" filesep + set groupfilelast $f + foreach p $parents($id) { + set diffoffset($p) 0 + } + } + + $ctext insert end "@@" msep + set nlines [expr {$grouplineend - $grouplinestart}] + set events {} + set pnum 0 + foreach p $parents($id) { + set startline [expr {$grouplinestart + $diffoffset($p)}] + set ol $startline + set nl $grouplinestart + if {[info exists grouphunks($p)]} { + foreach h $grouphunks($p) { + set l [lindex $h 2] + if {$nl < $l} { + for {} {$nl < $l} {incr nl} { + set filelines($p,$f,$ol) $filelines($id,$f,$nl) + incr ol + } + } + foreach chunk [lindex $h 5] { + if {[llength $chunk] == 2} { + set olc [lindex $chunk 0] + set nlc [lindex $chunk 1] + set nnl [expr {$nl + $nlc}] + lappend events [list $nl $nnl $pnum $olc $nlc] + incr ol $olc + set nl $nnl + } else { + incr ol [lindex $chunk 0] + incr nl [lindex $chunk 0] + } + } + } + } + if {$nl < $grouplineend} { + for {} {$nl < $grouplineend} {incr nl} { + set filelines($p,$f,$ol) $filelines($id,$f,$nl) + incr ol + } + } + set nlines [expr {$ol - $startline}] + $ctext insert end " -$startline,$nlines" msep + incr pnum + } + + set nlines [expr {$grouplineend - $grouplinestart}] + $ctext insert end " +$grouplinestart,$nlines @@\n" msep + + set events [lsort -integer -index 0 $events] + set nevents [llength $events] + set nmerge $nparents($diffmergeid) + set l $grouplinestart + for {set i 0} {$i < $nevents} {set i $j} { + set nl [lindex $events $i 0] + while {$l < $nl} { + $ctext insert end " $filelines($id,$f,$l)\n" + incr l + } + set e [lindex $events $i] + set enl [lindex $e 1] + set j $i + set active {} + while 1 { + set pnum [lindex $e 2] + set olc [lindex $e 3] + set nlc [lindex $e 4] + if {![info exists delta($pnum)]} { + set delta($pnum) [expr {$olc - $nlc}] + lappend active $pnum + } else { + incr delta($pnum) [expr {$olc - $nlc}] + } + if {[incr j] >= $nevents} break + set e [lindex $events $j] + if {[lindex $e 0] >= $enl} break + if {[lindex $e 1] > $enl} { + set enl [lindex $e 1] + } + } + set nlc [expr {$enl - $l}] + set ncol mresult + set bestpn -1 + if {[llength $active] == $nmerge - 1} { + # no diff for one of the parents, i.e. it's identical + for {set pnum 0} {$pnum < $nmerge} {incr pnum} { + if {![info exists delta($pnum)]} { + if {$pnum < $mergemax} { + lappend ncol m$pnum + } else { + lappend ncol mmax + } + break + } + } + } elseif {[llength $active] == $nmerge} { + # all parents are different, see if one is very similar + set bestsim 30 + for {set pnum 0} {$pnum < $nmerge} {incr pnum} { + set sim [similarity $pnum $l $nlc $f \ + [lrange $events $i [expr {$j-1}]]] + if {$sim > $bestsim} { + set bestsim $sim + set bestpn $pnum + } + } + if {$bestpn >= 0} { + lappend ncol m$bestpn + } + } + set pnum -1 + foreach p $parents($id) { + incr pnum + if {![info exists delta($pnum)] || $pnum == $bestpn} continue + set olc [expr {$nlc + $delta($pnum)}] + set ol [expr {$l + $diffoffset($p)}] + incr diffoffset($p) $delta($pnum) + unset delta($pnum) + for {} {$olc > 0} {incr olc -1} { + $ctext insert end "-$filelines($p,$f,$ol)\n" m$pnum + incr ol + } + } + set endl [expr {$l + $nlc}] + if {$bestpn >= 0} { + # show this pretty much as a normal diff + set p [lindex $parents($id) $bestpn] + set ol [expr {$l + $diffoffset($p)}] + incr diffoffset($p) $delta($bestpn) + unset delta($bestpn) + for {set k $i} {$k < $j} {incr k} { + set e [lindex $events $k] + if {[lindex $e 2] != $bestpn} continue + set nl [lindex $e 0] + set ol [expr {$ol + $nl - $l}] + for {} {$l < $nl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" $ncol + } + set c [lindex $e 3] + for {} {$c > 0} {incr c -1} { + $ctext insert end "-$filelines($p,$f,$ol)\n" m$bestpn + incr ol + } + set nl [lindex $e 1] + for {} {$l < $nl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" mresult + } + } + } + for {} {$l < $endl} {incr l} { + $ctext insert end "+$filelines($id,$f,$l)\n" $ncol + } + } + while {$l < $grouplineend} { + $ctext insert end " $filelines($id,$f,$l)\n" + incr l + } + $ctext conf -state disabled +} + +proc similarity {pnum l nlc f events} { + global diffmergeid parents diffoffset filelines + + set id $diffmergeid + set p [lindex $parents($id) $pnum] + set ol [expr {$l + $diffoffset($p)}] + set endl [expr {$l + $nlc}] + set same 0 + set diff 0 + foreach e $events { + if {[lindex $e 2] != $pnum} continue + set nl [lindex $e 0] + set ol [expr {$ol + $nl - $l}] + for {} {$l < $nl} {incr l} { + incr same [string length $filelines($id,$f,$l)] + incr same + } + set oc [lindex $e 3] + for {} {$oc > 0} {incr oc -1} { + incr diff [string length $filelines($p,$f,$ol)] + incr diff + incr ol + } + set nl [lindex $e 1] + for {} {$l < $nl} {incr l} { + incr diff [string length $filelines($id,$f,$l)] + incr diff + } + } + for {} {$l < $endl} {incr l} { + incr same [string length $filelines($id,$f,$l)] + incr same + } + if {$same == 0} { + return 0 + } + return [expr {200 * $same / (2 * $same + $diff)}] +} + +proc startdiff {ids} { + global treediffs diffids treepending diffmergeid + + set diffids $ids + catch {unset diffmergeid} + if {![info exists treediffs($ids)]} { + if {![info exists treepending]} { + gettreediffs $ids + } + } else { + addtocflist $ids + } +} + +proc addtocflist {ids} { + global treediffs cflist + foreach f $treediffs($ids) { + $cflist insert end $f + } + getblobdiffs $ids +} + +proc gettreediffs {ids} { + global treediff parents treepending + set treepending $ids + set treediff {} + set id [lindex $ids 0] + set p [lindex $ids 1] + if [catch {set gdtf [open "|hg debug-diff-tree -r $p $id" r]}] return + fconfigure $gdtf -blocking 0 + fileevent $gdtf readable [list gettreediffline $gdtf $ids] +} + +proc gettreediffline {gdtf ids} { + global treediff treediffs treepending diffids diffmergeid + + set n [gets $gdtf line] + if {$n < 0} { + if {![eof $gdtf]} return + close $gdtf + set treediffs($ids) $treediff + unset treepending + if {$ids != $diffids} { + gettreediffs $diffids + } else { + if {[info exists diffmergeid]} { + contmergediff $ids + } else { + addtocflist $ids + } + } + return + } + set file [lindex $line 5] + lappend treediff $file +} + +proc getblobdiffs {ids} { + global diffopts blobdifffd diffids env curdifftag curtagstart + global difffilestart nextupdate diffinhdr treediffs + + set id [lindex $ids 0] + set p [lindex $ids 1] + set env(GIT_DIFF_OPTS) $diffopts + set cmd [list | hg debug-diff-tree -r -p -C $p $id] + if {[catch {set bdf [open $cmd r]} err]} { + puts "error getting diffs: $err" + return + } + set diffinhdr 0 + fconfigure $bdf -blocking 0 + set blobdifffd($ids) $bdf + set curdifftag Comments + set curtagstart 0.0 + catch {unset difffilestart} + fileevent $bdf readable [list getblobdiffline $bdf $diffids] + set nextupdate [expr {[clock clicks -milliseconds] + 100}] +} + +proc getblobdiffline {bdf ids} { + global diffids blobdifffd ctext curdifftag curtagstart + global diffnexthead diffnextnote difffilestart + global nextupdate diffinhdr treediffs + global gaudydiff + + set n [gets $bdf line] + if {$n < 0} { + if {[eof $bdf]} { + close $bdf + if {$ids == $diffids && $bdf == $blobdifffd($ids)} { + $ctext tag add $curdifftag $curtagstart end + } + } + return + } + if {$ids != $diffids || $bdf != $blobdifffd($ids)} { + return + } + $ctext conf -state normal + if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} { + # start of a new file + $ctext insert end "\n" + $ctext tag add $curdifftag $curtagstart end + set curtagstart [$ctext index "end - 1c"] + set header $newname + set here [$ctext index "end - 1c"] + set i [lsearch -exact $treediffs($diffids) $fname] + if {$i >= 0} { + set difffilestart($i) $here + incr i + $ctext mark set fmark.$i $here + $ctext mark gravity fmark.$i left + } + if {$newname != $fname} { + set i [lsearch -exact $treediffs($diffids) $newname] + if {$i >= 0} { + set difffilestart($i) $here + incr i + $ctext mark set fmark.$i $here + $ctext mark gravity fmark.$i left + } + } + set curdifftag "f:$fname" + $ctext tag delete $curdifftag + set l [expr {(78 - [string length $header]) / 2}] + set pad [string range "----------------------------------------" 1 $l] + $ctext insert end "$pad $header $pad\n" filesep + set diffinhdr 1 + } elseif {[regexp {^(---|\+\+\+)} $line]} { + set diffinhdr 0 + } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \ + $line match f1l f1c f2l f2c rest]} { + if {$gaudydiff} { + $ctext insert end "\t" hunksep + $ctext insert end " $f1l " d0 " $f2l " d1 + $ctext insert end " $rest \n" hunksep + } else { + $ctext insert end "$line\n" hunksep + } + set diffinhdr 0 + } else { + set x [string range $line 0 0] + if {$x == "-" || $x == "+"} { + set tag [expr {$x == "+"}] + if {$gaudydiff} { + set line [string range $line 1 end] + } + $ctext insert end "$line\n" d$tag + } elseif {$x == " "} { + if {$gaudydiff} { + set line [string range $line 1 end] + } + $ctext insert end "$line\n" + } elseif {$diffinhdr || $x == "\\"} { + # e.g. "\ No newline at end of file" + $ctext insert end "$line\n" filesep + } else { + # Something else we don't recognize + if {$curdifftag != "Comments"} { + $ctext insert end "\n" + $ctext tag add $curdifftag $curtagstart end + set curtagstart [$ctext index "end - 1c"] + set curdifftag Comments + } + $ctext insert end "$line\n" filesep + } + } + $ctext conf -state disabled + if {[clock clicks -milliseconds] >= $nextupdate} { + incr nextupdate 100 + fileevent $bdf readable {} + update + fileevent $bdf readable "getblobdiffline $bdf {$ids}" + } +} + +proc nextfile {} { + global difffilestart ctext + set here [$ctext index @0,0] + for {set i 0} {[info exists difffilestart($i)]} {incr i} { + if {[$ctext compare $difffilestart($i) > $here]} { + if {![info exists pos] + || [$ctext compare $difffilestart($i) < $pos]} { + set pos $difffilestart($i) + } + } + } + if {[info exists pos]} { + $ctext yview $pos + } +} + +proc listboxsel {} { + global ctext cflist currentid + if {![info exists currentid]} return + set sel [lsort [$cflist curselection]] + if {$sel eq {}} return + set first [lindex $sel 0] + catch {$ctext yview fmark.$first} +} + +proc setcoords {} { + global linespc charspc canvx0 canvy0 mainfont + global xspc1 xspc2 lthickness + + set linespc [font metrics $mainfont -linespace] + set charspc [font measure $mainfont "m"] + set canvy0 [expr 3 + 0.5 * $linespc] + set canvx0 [expr 3 + 0.5 * $linespc] + set lthickness [expr {int($linespc / 9) + 1}] + set xspc1(0) $linespc + set xspc2 $linespc +} + +proc redisplay {} { + global stopped redisplaying phase + if {$stopped > 1} return + if {$phase == "getcommits"} return + set redisplaying 1 + if {$phase == "drawgraph" || $phase == "incrdraw"} { + set stopped 1 + } else { + drawgraph + } +} + +proc incrfont {inc} { + global mainfont namefont textfont ctext canv phase + global stopped entries + unmarkmatches + set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]] + set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]] + set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]] + setcoords + $ctext conf -font $textfont + $ctext tag conf filesep -font [concat $textfont bold] + foreach e $entries { + $e conf -font $mainfont + } + if {$phase == "getcommits"} { + $canv itemconf textitems -font $mainfont + } + redisplay +} + +proc clearsha1 {} { + global sha1entry sha1string + if {[string length $sha1string] == 40} { + $sha1entry delete 0 end + } +} + +proc sha1change {n1 n2 op} { + global sha1string currentid sha1but + if {$sha1string == {} + || ([info exists currentid] && $sha1string == $currentid)} { + set state disabled + } else { + set state normal + } + if {[$sha1but cget -state] == $state} return + if {$state == "normal"} { + $sha1but conf -state normal -relief raised -text "Goto: " + } else { + $sha1but conf -state disabled -relief flat -text "SHA1 ID: " + } +} + +proc gotocommit {} { + global sha1string currentid idline tagids + global lineid numcommits + + if {$sha1string == {} + || ([info exists currentid] && $sha1string == $currentid)} return + if {[info exists tagids($sha1string)]} { + set id $tagids($sha1string) + } else { + set id [string tolower $sha1string] + if {[regexp {^[0-9a-f]{4,39}$} $id]} { + set matches {} + for {set l 0} {$l < $numcommits} {incr l} { + if {[string match $id* $lineid($l)]} { + lappend matches $lineid($l) + } + } + if {$matches ne {}} { + if {[llength $matches] > 1} { + error_popup "Short SHA1 id $id is ambiguous" + return + } + set id [lindex $matches 0] + } + } + } + if {[info exists idline($id)]} { + selectline $idline($id) 1 + return + } + if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} { + set type "SHA1 id" + } else { + set type "Tag" + } + error_popup "$type $sha1string is not known" +} + +proc lineenter {x y id} { + global hoverx hovery hoverid hovertimer + global commitinfo canv + + if {![info exists commitinfo($id)]} return + set hoverx $x + set hovery $y + set hoverid $id + if {[info exists hovertimer]} { + after cancel $hovertimer + } + set hovertimer [after 500 linehover] + $canv delete hover +} + +proc linemotion {x y id} { + global hoverx hovery hoverid hovertimer + + if {[info exists hoverid] && $id == $hoverid} { + set hoverx $x + set hovery $y + if {[info exists hovertimer]} { + after cancel $hovertimer + } + set hovertimer [after 500 linehover] + } +} + +proc lineleave {id} { + global hoverid hovertimer canv + + if {[info exists hoverid] && $id == $hoverid} { + $canv delete hover + if {[info exists hovertimer]} { + after cancel $hovertimer + unset hovertimer + } + unset hoverid + } +} + +proc linehover {} { + global hoverx hovery hoverid hovertimer + global canv linespc lthickness + global commitinfo mainfont + + set text [lindex $commitinfo($hoverid) 0] + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax == {}} return + set yfrac [lindex [$canv yview] 0] + set x [expr {$hoverx + 2 * $linespc}] + set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}] + set x0 [expr {$x - 2 * $lthickness}] + set y0 [expr {$y - 2 * $lthickness}] + set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}] + set y1 [expr {$y + $linespc + 2 * $lthickness}] + set t [$canv create rectangle $x0 $y0 $x1 $y1 \ + -fill \#ffff80 -outline black -width 1 -tags hover] + $canv raise $t + set t [$canv create text $x $y -anchor nw -text $text -tags hover] + $canv raise $t +} + +proc clickisonarrow {id y} { + global mainline mainlinearrow sidelines lthickness + + set thresh [expr {2 * $lthickness + 6}] + if {[info exists mainline($id)]} { + if {$mainlinearrow($id) ne "none"} { + if {abs([lindex $mainline($id) 1] - $y) < $thresh} { + return "up" + } + } + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set coords [lindex $ls 0] + set arrow [lindex $ls 2] + if {$arrow eq "first" || $arrow eq "both"} { + if {abs([lindex $coords 1] - $y) < $thresh} { + return "up" + } + } + if {$arrow eq "last" || $arrow eq "both"} { + if {abs([lindex $coords end] - $y) < $thresh} { + return "down" + } + } + } + } + return {} +} + +proc arrowjump {id dirn y} { + global mainline sidelines canv + + set yt {} + if {$dirn eq "down"} { + if {[info exists mainline($id)]} { + set y1 [lindex $mainline($id) 1] + if {$y1 > $y} { + set yt $y1 + } + } + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set y1 [lindex $ls 0 1] + if {$y1 > $y && ($yt eq {} || $y1 < $yt)} { + set yt $y1 + } + } + } + } else { + if {[info exists sidelines($id)]} { + foreach ls $sidelines($id) { + set y1 [lindex $ls 0 end] + if {$y1 < $y && ($yt eq {} || $y1 > $yt)} { + set yt $y1 + } + } + } + } + if {$yt eq {}} return + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {} || $ymax <= 0} return + set view [$canv yview] + set yspan [expr {[lindex $view 1] - [lindex $view 0]}] + set yfrac [expr {$yt / $ymax - $yspan / 2}] + if {$yfrac < 0} { + set yfrac 0 + } + $canv yview moveto $yfrac +} + +proc lineclick {x y id isnew} { + global ctext commitinfo children cflist canv thickerline + + unmarkmatches + unselectline + normalline + $canv delete hover + # draw this line thicker than normal + drawlines $id 1 + set thickerline $id + if {$isnew} { + set ymax [lindex [$canv cget -scrollregion] 3] + if {$ymax eq {}} return + set yfrac [lindex [$canv yview] 0] + set y [expr {$y + $yfrac * $ymax}] + } + set dirn [clickisonarrow $id $y] + if {$dirn ne {}} { + arrowjump $id $dirn $y + return + } + + if {$isnew} { + addtohistory [list lineclick $x $y $id 0] + } + # fill the details pane with info about this line + $ctext conf -state normal + $ctext delete 0.0 end + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + $ctext insert end "Parent:\t" + $ctext insert end $id [list link link0] + $ctext tag bind link0 <1> [list selbyid $id] + set info $commitinfo($id) + $ctext insert end "\n\t[lindex $info 0]\n" + $ctext insert end "\tAuthor:\t[lindex $info 1]\n" + $ctext insert end "\tDate:\t[lindex $info 2]\n" + if {[info exists children($id)]} { + $ctext insert end "\nChildren:" + set i 0 + foreach child $children($id) { + incr i + set info $commitinfo($child) + $ctext insert end "\n\t" + $ctext insert end $child [list link link$i] + $ctext tag bind link$i <1> [list selbyid $child] + $ctext insert end "\n\t[lindex $info 0]" + $ctext insert end "\n\tAuthor:\t[lindex $info 1]" + $ctext insert end "\n\tDate:\t[lindex $info 2]\n" + } + } + $ctext conf -state disabled + + $cflist delete 0 end +} + +proc normalline {} { + global thickerline + if {[info exists thickerline]} { + drawlines $thickerline 0 + unset thickerline + } +} + +proc selbyid {id} { + global idline + if {[info exists idline($id)]} { + selectline $idline($id) 1 + } +} + +proc mstime {} { + global startmstime + if {![info exists startmstime]} { + set startmstime [clock clicks -milliseconds] + } + return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]] +} + +proc rowmenu {x y id} { + global rowctxmenu idline selectedline rowmenuid + + if {![info exists selectedline] || $idline($id) eq $selectedline} { + set state disabled + } else { + set state normal + } + $rowctxmenu entryconfigure 0 -state $state + $rowctxmenu entryconfigure 1 -state $state + $rowctxmenu entryconfigure 2 -state $state + set rowmenuid $id + tk_popup $rowctxmenu $x $y +} + +proc diffvssel {dirn} { + global rowmenuid selectedline lineid + + if {![info exists selectedline]} return + if {$dirn} { + set oldid $lineid($selectedline) + set newid $rowmenuid + } else { + set oldid $rowmenuid + set newid $lineid($selectedline) + } + addtohistory [list doseldiff $oldid $newid] + doseldiff $oldid $newid +} + +proc doseldiff {oldid newid} { + global ctext cflist + global commitinfo + + $ctext conf -state normal + $ctext delete 0.0 end + $ctext mark set fmark.0 0.0 + $ctext mark gravity fmark.0 left + $cflist delete 0 end + $cflist insert end "Top" + $ctext insert end "From " + $ctext tag conf link -foreground blue -underline 1 + $ctext tag bind link <Enter> { %W configure -cursor hand2 } + $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } + $ctext tag bind link0 <1> [list selbyid $oldid] + $ctext insert end $oldid [list link link0] + $ctext insert end "\n " + $ctext insert end [lindex $commitinfo($oldid) 0] + $ctext insert end "\n\nTo " + $ctext tag bind link1 <1> [list selbyid $newid] + $ctext insert end $newid [list link link1] + $ctext insert end "\n " + $ctext insert end [lindex $commitinfo($newid) 0] + $ctext insert end "\n" + $ctext conf -state disabled + $ctext tag delete Comments + $ctext tag remove found 1.0 end + startdiff [list $newid $oldid] +} + +proc mkpatch {} { + global rowmenuid currentid commitinfo patchtop patchnum + + if {![info exists currentid]} return + set oldid $currentid + set oldhead [lindex $commitinfo($oldid) 0] + set newid $rowmenuid + set newhead [lindex $commitinfo($newid) 0] + set top .patch + set patchtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Generate patch" + grid $top.title - -pady 10 + label $top.from -text "From:" + entry $top.fromsha1 -width 40 -relief flat + $top.fromsha1 insert 0 $oldid + $top.fromsha1 conf -state readonly + grid $top.from $top.fromsha1 -sticky w + entry $top.fromhead -width 60 -relief flat + $top.fromhead insert 0 $oldhead + $top.fromhead conf -state readonly + grid x $top.fromhead -sticky w + label $top.to -text "To:" + entry $top.tosha1 -width 40 -relief flat + $top.tosha1 insert 0 $newid + $top.tosha1 conf -state readonly + grid $top.to $top.tosha1 -sticky w + entry $top.tohead -width 60 -relief flat + $top.tohead insert 0 $newhead + $top.tohead conf -state readonly + grid x $top.tohead -sticky w + button $top.rev -text "Reverse" -command mkpatchrev -padx 5 + grid $top.rev x -pady 10 + label $top.flab -text "Output file:" + entry $top.fname -width 60 + $top.fname insert 0 [file normalize "patch$patchnum.patch"] + incr patchnum + grid $top.flab $top.fname -sticky w + frame $top.buts + button $top.buts.gen -text "Generate" -command mkpatchgo + button $top.buts.can -text "Cancel" -command mkpatchcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.fname +} + +proc mkpatchrev {} { + global patchtop + + set oldid [$patchtop.fromsha1 get] + set oldhead [$patchtop.fromhead get] + set newid [$patchtop.tosha1 get] + set newhead [$patchtop.tohead get] + foreach e [list fromsha1 fromhead tosha1 tohead] \ + v [list $newid $newhead $oldid $oldhead] { + $patchtop.$e conf -state normal + $patchtop.$e delete 0 end + $patchtop.$e insert 0 $v + $patchtop.$e conf -state readonly + } +} + +proc mkpatchgo {} { + global patchtop + + set oldid [$patchtop.fromsha1 get] + set newid [$patchtop.tosha1 get] + set fname [$patchtop.fname get] + if {[catch {exec hg debug-diff-tree -p $oldid $newid >$fname &} err]} { + error_popup "Error creating patch: $err" + } + catch {destroy $patchtop} + unset patchtop +} + +proc mkpatchcan {} { + global patchtop + + catch {destroy $patchtop} + unset patchtop +} + +proc mktag {} { + global rowmenuid mktagtop commitinfo + + set top .maketag + set mktagtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Create tag" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + entry $top.head -width 60 -relief flat + $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] + $top.head conf -state readonly + grid x $top.head -sticky w + label $top.tlab -text "Tag name:" + entry $top.tag -width 60 + grid $top.tlab $top.tag -sticky w + frame $top.buts + button $top.buts.gen -text "Create" -command mktaggo + button $top.buts.can -text "Cancel" -command mktagcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.tag +} + +proc domktag {} { + global mktagtop env tagids idtags + + set id [$mktagtop.sha1 get] + set tag [$mktagtop.tag get] + if {$tag == {}} { + error_popup "No tag name specified" + return + } + if {[info exists tagids($tag)]} { + error_popup "Tag \"$tag\" already exists" + return + } + if {[catch { + set out [exec hg tag -r $id $tag] + } err]} { + error_popup "Error creating tag: $err" + return + } + + set tagids($tag) $id + lappend idtags($id) $tag + redrawtags $id +} + +proc redrawtags {id} { + global canv linehtag idline idpos selectedline + + if {![info exists idline($id)]} return + $canv delete tag.$id + set xt [eval drawtags $id $idpos($id)] + $canv coords $linehtag($idline($id)) $xt [lindex $idpos($id) 2] + if {[info exists selectedline] && $selectedline == $idline($id)} { + selectline $selectedline 0 + } +} + +proc mktagcan {} { + global mktagtop + + catch {destroy $mktagtop} + unset mktagtop +} + +proc mktaggo {} { + domktag + mktagcan +} + +proc writecommit {} { + global rowmenuid wrcomtop commitinfo wrcomcmd + + set top .writecommit + set wrcomtop $top + catch {destroy $top} + toplevel $top + label $top.title -text "Write commit to file" + grid $top.title - -pady 10 + label $top.id -text "ID:" + entry $top.sha1 -width 40 -relief flat + $top.sha1 insert 0 $rowmenuid + $top.sha1 conf -state readonly + grid $top.id $top.sha1 -sticky w + entry $top.head -width 60 -relief flat + $top.head insert 0 [lindex $commitinfo($rowmenuid) 0] + $top.head conf -state readonly + grid x $top.head -sticky w + label $top.clab -text "Command:" + entry $top.cmd -width 60 -textvariable wrcomcmd + grid $top.clab $top.cmd -sticky w -pady 10 + label $top.flab -text "Output file:" + entry $top.fname -width 60 + $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"] + grid $top.flab $top.fname -sticky w + frame $top.buts + button $top.buts.gen -text "Write" -command wrcomgo + button $top.buts.can -text "Cancel" -command wrcomcan + grid $top.buts.gen $top.buts.can + grid columnconfigure $top.buts 0 -weight 1 -uniform a + grid columnconfigure $top.buts 1 -weight 1 -uniform a + grid $top.buts - -pady 10 -sticky ew + focus $top.fname +} + +proc wrcomgo {} { + global wrcomtop + + set id [$wrcomtop.sha1 get] + set cmd "echo $id | [$wrcomtop.cmd get]" + set fname [$wrcomtop.fname get] + if {[catch {exec sh -c $cmd >$fname &} err]} { + error_popup "Error writing commit: $err" + } + catch {destroy $wrcomtop} + unset wrcomtop +} + +proc wrcomcan {} { + global wrcomtop + + catch {destroy $wrcomtop} + unset wrcomtop +} + +proc listrefs {id} { + global idtags idheads idotherrefs + + set x {} + if {[info exists idtags($id)]} { + set x $idtags($id) + } + set y {} + if {[info exists idheads($id)]} { + set y $idheads($id) + } + set z {} + if {[info exists idotherrefs($id)]} { + set z $idotherrefs($id) + } + return [list $x $y $z] +} + +proc rereadrefs {} { + global idtags idheads idotherrefs + global tagids headids otherrefids + + set refids [concat [array names idtags] \ + [array names idheads] [array names idotherrefs]] + foreach id $refids { + if {![info exists ref($id)]} { + set ref($id) [listrefs $id] + } + } + foreach v {tagids idtags headids idheads otherrefids idotherrefs} { + catch {unset $v} + } + readrefs + set refids [lsort -unique [concat $refids [array names idtags] \ + [array names idheads] [array names idotherrefs]]] + foreach id $refids { + set v [listrefs $id] + if {![info exists ref($id)] || $ref($id) != $v} { + redrawtags $id + } + } +} + +proc showtag {tag isnew} { + global ctext cflist tagcontents tagids linknum + + if {$isnew} { + addtohistory [list showtag $tag 0] + } + $ctext conf -state normal + $ctext delete 0.0 end + set linknum 0 + if {[info exists tagcontents($tag)]} { + set text $tagcontents($tag) + } else { + set text "Tag: $tag\nId: $tagids($tag)" + } + appendwithlinks $text + $ctext conf -state disabled + $cflist delete 0 end +} + +proc doquit {} { + global stopped + set stopped 100 + destroy . +} + +# defaults... +set datemode 0 +set boldnames 0 +set diffopts "-U 5 -p" +set wrcomcmd "hg debug-diff-tree --stdin -p --pretty" + +set mainfont {Helvetica 9} +set textfont {Courier 9} +set findmergefiles 0 +set gaudydiff 0 +set maxgraphpct 50 +set maxwidth 16 + +set colors {green red blue magenta darkgrey brown orange} + +catch {source ~/.gitk} + +set namefont $mainfont +if {$boldnames} { + lappend namefont bold +} + +set revtreeargs {} +foreach arg $argv { + switch -regexp -- $arg { + "^$" { } + "^-b" { set boldnames 1 } + "^-d" { set datemode 1 } + default { + lappend revtreeargs $arg + } + } +} + +set history {} +set historyindex 0 + +set stopped 0 +set redisplaying 0 +set stuffsaved 0 +set patchnum 0 +setcoords +makewindow +readrefs +getcommits $revtreeargs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgk.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,339 @@ +# Minimal support for git commands on an hg repository +# +# Copyright 2005 Chris Mason <mason@suse.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import time, sys, signal, os +from mercurial import hg, mdiff, fancyopts, commands, ui, util + +def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, + changes=None, text=False): + def date(c): + return time.asctime(time.gmtime(c[2][0])) + + if not changes: + changes = repo.changes(node1, node2, files, match=match) + modified, added, removed, deleted, unknown = changes + if files: + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) + + if not modified and not added and not removed: + return + + if node2: + change = repo.changelog.read(node2) + mmap2 = repo.manifest.read(change[0]) + date2 = date(change) + def read(f): + return repo.file(f).read(mmap2[f]) + else: + date2 = time.asctime() + if not node1: + node1 = repo.dirstate.parents()[0] + def read(f): + return repo.wfile(f).read() + + change = repo.changelog.read(node1) + mmap = repo.manifest.read(change[0]) + date1 = date(change) + + for f in modified: + to = None + if f in mmap: + to = repo.file(f).read(mmap[f]) + tn = read(f) + fp.write("diff --git a/%s b/%s\n" % (f, f)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + for f in added: + to = None + tn = read(f) + fp.write("diff --git /dev/null b/%s\n" % (f)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + for f in removed: + to = repo.file(f).read(mmap[f]) + tn = None + fp.write("diff --git a/%s /dev/null\n" % (f)) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, None, text=text)) + +def difftree(ui, repo, node1=None, node2=None, **opts): + """diff trees from two commits""" + def __difftree(repo, node1, node2): + def date(c): + return time.asctime(time.gmtime(c[2][0])) + + if node2: + change = repo.changelog.read(node2) + mmap2 = repo.manifest.read(change[0]) + modified, added, removed, deleted, unknown = repo.changes(node1, node2) + def read(f): return repo.file(f).read(mmap2[f]) + date2 = date(change) + else: + date2 = time.asctime() + modified, added, removed, deleted, unknown = repo.changes(node1) + if not node1: + node1 = repo.dirstate.parents()[0] + def read(f): return file(os.path.join(repo.root, f)).read() + + change = repo.changelog.read(node1) + mmap = repo.manifest.read(change[0]) + date1 = date(change) + empty = "0" * 40; + + for f in modified: + # TODO get file permissions + print ":100664 100664 %s %s M\t%s\t%s" % (hg.hex(mmap[f]), + hg.hex(mmap2[f]), f, f) + for f in added: + print ":000000 100664 %s %s N\t%s\t%s" % (empty, hg.hex(mmap2[f]), f, f) + for f in removed: + print ":100664 000000 %s %s D\t%s\t%s" % (hg.hex(mmap[f]), empty, f, f) + ## + + while True: + if opts['stdin']: + try: + line = raw_input().split(' ') + node1 = line[0] + if len(line) > 1: + node2 = line[1] + else: + node2 = None + except EOFError: + break + node1 = repo.lookup(node1) + if node2: + node2 = repo.lookup(node2) + else: + node2 = node1 + node1 = repo.changelog.parents(node1)[0] + if opts['patch']: + if opts['pretty']: + catcommit(repo, node2, "") + dodiff(sys.stdout, ui, repo, node1, node2) + else: + __difftree(repo, node1, node2) + if not opts['stdin']: + break + +def catcommit(repo, n, prefix, changes=None): + nlprefix = '\n' + prefix; + (p1, p2) = repo.changelog.parents(n) + (h, h1, h2) = map(hg.hex, (n, p1, p2)) + (i1, i2) = map(repo.changelog.rev, (p1, p2)) + if not changes: + changes = repo.changelog.read(n) + print "tree %s" % (hg.hex(changes[0])) + if i1 != -1: print "parent %s" % (h1) + if i2 != -1: print "parent %s" % (h2) + date_ar = changes[2] + date = int(float(date_ar[0])) + lines = changes[4].splitlines() + if lines[-1].startswith('committer:'): + committer = lines[-1].split(': ')[1].rstrip() + else: + committer = changes[1] + + print "author %s %s %s" % (changes[1], date, date_ar[1]) + print "committer %s %s %s" % (committer, date, date_ar[1]) + print "" + if prefix != "": + print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip()) + else: + print changes[4] + if prefix: + sys.stdout.write('\0') + +def base(ui, repo, node1, node2): + """Output common ancestor information""" + node1 = repo.lookup(node1) + node2 = repo.lookup(node2) + n = repo.changelog.ancestor(node1, node2) + print hg.hex(n) + +def catfile(ui, repo, type=None, r=None, **opts): + """cat a specific revision""" + # in stdin mode, every line except the commit is prefixed with two + # spaces. This way the our caller can find the commit without magic + # strings + # + prefix = "" + if opts['stdin']: + try: + (type, r) = raw_input().split(' '); + prefix = " " + except EOFError: + return + + else: + if not type or not r: + ui.warn("cat-file: type or revision not supplied\n") + commands.help_(ui, 'cat-file') + + while r: + if type != "commit": + sys.stderr.write("aborting hg cat-file only understands commits\n") + sys.exit(1); + n = repo.lookup(r) + catcommit(repo, n, prefix) + if opts['stdin']: + try: + (type, r) = raw_input().split(' '); + except EOFError: + break + else: + break + +# git rev-tree is a confusing thing. You can supply a number of +# commit sha1s on the command line, and it walks the commit history +# telling you which commits are reachable from the supplied ones via +# a bitmask based on arg position. +# you can specify a commit to stop at by starting the sha1 with ^ +def revtree(args, repo, full="tree", maxnr=0, parents=False): + def chlogwalk(): + ch = repo.changelog + count = ch.count() + i = count + l = [0] * 100 + chunk = 100 + while True: + if chunk > i: + chunk = i + i = 0 + else: + i -= chunk + + for x in xrange(0, chunk): + if i + x >= count: + l[chunk - x:] = [0] * (chunk - x) + break + if full != None: + l[x] = ch.read(ch.node(i + x)) + else: + l[x] = 1 + for x in xrange(chunk-1, -1, -1): + if l[x] != 0: + yield (i + x, full != None and l[x] or None) + if i == 0: + break + + # calculate and return the reachability bitmask for sha + def is_reachable(ar, reachable, sha): + if len(ar) == 0: + return 1 + mask = 0 + for i in range(len(ar)): + if sha in reachable[i]: + mask |= 1 << i + + return mask + + reachable = [] + stop_sha1 = [] + want_sha1 = [] + count = 0 + + # figure out which commits they are asking for and which ones they + # want us to stop on + for i in range(len(args)): + if args[i].startswith('^'): + s = repo.lookup(args[i][1:]) + stop_sha1.append(s) + want_sha1.append(s) + elif args[i] != 'HEAD': + want_sha1.append(repo.lookup(args[i])) + + # calculate the graph for the supplied commits + for i in range(len(want_sha1)): + reachable.append({}); + n = want_sha1[i]; + visit = [n]; + reachable[i][n] = 1 + while visit: + n = visit.pop(0) + if n in stop_sha1: + continue + for p in repo.changelog.parents(n): + if p not in reachable[i]: + reachable[i][p] = 1 + visit.append(p) + if p in stop_sha1: + continue + + # walk the repository looking for commits that are in our + # reachability graph + #for i in range(repo.changelog.count()-1, -1, -1): + for i, changes in chlogwalk(): + n = repo.changelog.node(i) + mask = is_reachable(want_sha1, reachable, n) + if mask: + parentstr = "" + if parents: + pp = repo.changelog.parents(n) + if pp[0] != hg.nullid: + parentstr += " " + hg.hex(pp[0]) + if pp[1] != hg.nullid: + parentstr += " " + hg.hex(pp[1]) + if not full: + print hg.hex(n) + parentstr + elif full is "commit": + print hg.hex(n) + parentstr + catcommit(repo, n, ' ', changes) + else: + (p1, p2) = repo.changelog.parents(n) + (h, h1, h2) = map(hg.hex, (n, p1, p2)) + (i1, i2) = map(repo.changelog.rev, (p1, p2)) + + date = changes[2][0] + print "%s %s:%s" % (date, h, mask), + mask = is_reachable(want_sha1, reachable, p1) + if i1 != -1 and mask > 0: + print "%s:%s " % (h1, mask), + mask = is_reachable(want_sha1, reachable, p2) + if i2 != -1 and mask > 0: + print "%s:%s " % (h2, mask), + print "" + if maxnr and count >= maxnr: + break + count += 1 + +# git rev-list tries to order things by date, and has the ability to stop +# at a given commit without walking the whole repo. TODO add the stop +# parameter +def revlist(ui, repo, *revs, **opts): + """print revisions""" + if opts['header']: + full = "commit" + else: + full = None + copy = [x for x in revs] + revtree(copy, repo, full, opts['max_count'], opts['parents']) + +def view(ui, repo, *etc): + "start interactive history viewer" + os.chdir(repo.root) + os.system(ui.config("hgk", "path", "hgk") + " " + " ".join(etc)) + +cmdtable = { + "view": (view, [], 'hg view'), + "debug-diff-tree": (difftree, [('p', 'patch', None, 'generate patch'), + ('r', 'recursive', None, 'recursive'), + ('P', 'pretty', None, 'pretty'), + ('s', 'stdin', None, 'stdin'), + ('C', 'copy', None, 'detect copies'), + ('S', 'search', "", 'search')], + "hg git-diff-tree [options] node1 node2"), + "debug-cat-file": (catfile, [('s', 'stdin', None, 'stdin')], + "hg debug-cat-file [options] type file"), + "debug-merge-base": (base, [], "hg debug-merge-base node node"), + "debug-rev-list": (revlist, [('H', 'header', None, 'header'), + ('t', 'topo-order', None, 'topo-order'), + ('p', 'parents', None, 'parents'), + ('n', 'max-count', 0, 'max-count')], + "hg debug-rev-list [options] revs"), +} + +def reposetup(ui, repo): + pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgsh/Makefile Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +CC := gcc +CFLAGS := -g -O2 -Wall -Werror + +prefix ?= /usr/bin + +hgsh: hgsh.o + $(CC) -o $@ $< + +install: hgsh + install -m755 hgsh $(prefix) + +clean: + rm -f *.o hgsh
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hgsh/hgsh.c Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,372 @@ +/* + * hgsh.c - restricted login shell for mercurial + * + * Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> + * + * This software may be used and distributed according to the terms of the + * GNU General Public License, incorporated herein by reference. + * + * this program is login shell for dedicated mercurial user account. it + * only allows few actions: + * + * 1. run hg in server mode on specific repository. no other hg commands + * are allowed. we try to verify that repo to be accessed exists under + * given top-level directory. + * + * 2. (optional) forward ssh connection from firewall/gateway machine to + * "real" mercurial host, to let users outside intranet pull and push + * changes through firewall. + * + * 3. (optional) run normal shell, to allow to "su" to mercurial user, use + * "sudo" to run programs as that user, or run cron jobs as that user. + * + * only tested on linux yet. patches for non-linux systems welcome. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* for asprintf */ +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sysexits.h> +#include <unistd.h> + +/* + * user config. + * + * if you see a hostname below, just use first part of hostname. example, + * if you have host named foo.bar.com, use "foo". + */ + +/* + * HG_GATEWAY: hostname of gateway/firewall machine that people outside your + * intranet ssh into if they need to ssh to other machines. if you do not + * have such machine, set to NULL. + */ +#ifndef HG_GATEWAY +#define HG_GATEWAY "gateway" +#endif + +/* + * HG_HOST: hostname of mercurial server. if any machine is allowed, set to + * NULL. + */ +#ifndef HG_HOST +#define HG_HOST "mercurial" +#endif + +/* + * HG_USER: username to log in from HG_GATEWAY to HG_HOST. if gateway and + * host username are same, set to NULL. + */ +#ifndef HG_USER +#define HG_USER "hg" +#endif + +/* + * HG_ROOT: root of tree full of mercurial repos. if you do not want to + * validate location of repo when someone is try to access, set to NULL. + */ +#ifndef HG_ROOT +#define HG_ROOT "/home/hg/repos" +#endif + +/* + * HG: path to the mercurial executable to run. + */ +#ifndef HG +#define HG "/home/hg/bin/hg" +#endif + +/* + * HG_SHELL: shell to use for actions like "sudo" and "su" access to + * mercurial user, and cron jobs. if you want to make these things + * impossible, set to NULL. + */ +#ifndef HG_SHELL +#define HG_SHELL NULL +// #define HG_SHELL "/bin/bash" +#endif + +/* + * HG_HELP: some way for users to get support if they have problem. if they + * should not get helpful message, set to NULL. + */ +#ifndef HG_HELP +#define HG_HELP "please contact support@example.com for help." +#endif + +/* + * SSH: path to ssh executable to run, if forwarding from HG_GATEWAY to + * HG_HOST. if you want to use rsh instead (why?), you need to modify + * arguments it is called with. see forward_through_gateway. + */ +#ifndef SSH +#define SSH "/usr/bin/ssh" +#endif + +/* + * tell whether to print command that is to be executed. useful for + * debugging. should not interfere with mercurial operation, since + * mercurial only cares about stdin and stdout, and this prints to stderr. + */ +static const int debug = 0; + +static void print_cmdline(int argc, char **argv) +{ + FILE *fp = stderr; + int i; + + fputs("command: ", fp); + + for (i = 0; i < argc; i++) { + char *spc = strpbrk(argv[i], " \t\r\n"); + if (spc) { + fputc('\'', fp); + } + fputs(argv[i], fp); + if (spc) { + fputc('\'', fp); + } + if (i < argc - 1) { + fputc(' ', fp); + } + } + fputc('\n', fp); + fflush(fp); +} + +static void usage(const char *reason, int exitcode) +{ + char *hg_help = HG_HELP; + + if (reason) { + fprintf(stderr, "*** Error: %s.\n", reason); + } + fprintf(stderr, "*** This program has been invoked incorrectly.\n"); + if (hg_help) { + fprintf(stderr, "*** %s\n", hg_help); + } + exit(exitcode ? exitcode : EX_USAGE); +} + +/* + * run on gateway host to make another ssh connection, to "real" mercurial + * server. it sends its command line unmodified to far end. + * + * never called if HG_GATEWAY is NULL. + */ +static void forward_through_gateway(int argc, char **argv) +{ + char *ssh = SSH; + char *hg_host = HG_HOST; + char *hg_user = HG_USER; + char **nargv = alloca((10 + argc) * sizeof(char *)); + int i = 0, j; + + nargv[i++] = ssh; + nargv[i++] = "-q"; + nargv[i++] = "-T"; + nargv[i++] = "-x"; + if (hg_user) { + nargv[i++] = "-l"; + nargv[i++] = hg_user; + } + nargv[i++] = hg_host; + + /* + * sshd called us with added "-c", because it thinks we are a shell. + * drop it if we find it. + */ + j = 1; + if (j < argc && strcmp(argv[j], "-c") == 0) { + j++; + } + + for (; j < argc; i++, j++) { + nargv[i] = argv[j]; + } + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(ssh, nargv); + perror(ssh); + exit(EX_UNAVAILABLE); +} + +/* + * run shell. let administrator "su" to mercurial user's account to do + * administrative works. + * + * never called if HG_SHELL is NULL. + */ +static void run_shell(int argc, char **argv) +{ + char *hg_shell = HG_SHELL; + char **nargv; + char *c; + int i; + + nargv = alloca((argc + 3) * sizeof(char *)); + c = strrchr(hg_shell, '/'); + + /* tell "real" shell it is login shell, if needed. */ + + if (argv[0][0] == '-' && c) { + nargv[0] = strdup(c); + if (nargv[0] == NULL) { + perror("malloc"); + exit(EX_OSERR); + } + nargv[0][0] = '-'; + } else { + nargv[0] = hg_shell; + } + + for (i = 1; i < argc; i++) { + nargv[i] = argv[i]; + } + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(hg_shell, nargv); + perror(hg_shell); + exit(EX_OSFILE); +} + +/* + * paranoid wrapper, runs hg executable in server mode. + */ +static void serve_data(int argc, char **argv) +{ + char *hg_root = HG_ROOT; + char *repo, *abspath; + char *nargv[6]; + struct stat st; + size_t repolen; + int i; + + /* + * check argv for looking okay. we should be invoked with argv + * resembling like this: + * + * hgsh + * -c + * hg -R some/path serve --stdio + * + * the "-c" is added by sshd, because it thinks we are login shell. + */ + + if (argc != 3) { + goto badargs; + } + + if (strcmp(argv[1], "-c") != 0) { + goto badargs; + } + + if (sscanf(argv[2], "hg -R %as serve --stdio", &repo) != 1) { + goto badargs; + } + + repolen = repo ? strlen(repo) : 0; + + if (repolen == 0) { + goto badargs; + } + + if (hg_root) { + if (asprintf(&abspath, "%s/%s/.hg/data", hg_root, repo) == -1) { + goto badargs; + } + + /* + * attempt to stop break out from inside the repository tree. could + * do something more clever here, because e.g. we could traverse a + * symlink that looks safe, but really breaks us out of tree. + */ + + if (strstr(abspath, "/../") != NULL) { + goto badargs; + } + + /* verify that we really are looking at valid repo. */ + + if (stat(abspath, &st) == -1) { + perror(repo); + exit(EX_DATAERR); + } + + if (chdir(hg_root) == -1) { + perror(hg_root); + exit(EX_SOFTWARE); + } + } + + i = 0; + nargv[i++] = HG; + nargv[i++] = "-R"; + nargv[i++] = repo; + nargv[i++] = "serve"; + nargv[i++] = "--stdio"; + nargv[i] = NULL; + + if (debug) { + print_cmdline(i, nargv); + } + + execv(HG, nargv); + perror(HG); + exit(EX_UNAVAILABLE); + +badargs: + /* print useless error message. */ + + usage("invalid arguments", EX_DATAERR); +} + +int main(int argc, char **argv) +{ + char host[1024]; + char *c; + + if (gethostname(host, sizeof(host)) == -1) { + perror("gethostname"); + exit(EX_OSERR); + } + + if ((c = strchr(host, '.')) != NULL) { + *c = '\0'; + } + + if (getenv("SSH_CLIENT")) { + char *hg_gateway = HG_GATEWAY; + char *hg_host = HG_HOST; + + if (hg_gateway && strcmp(host, hg_gateway) == 0) { + forward_through_gateway(argc, argv); + } + + if (hg_host && strcmp(host, hg_host) != 0) { + usage("invoked on unexpected host", EX_USAGE); + } + + serve_data(argc, argv); + } else if (HG_SHELL) { + run_shell(argc, argv); + } else { + usage("invalid arguments", EX_DATAERR); + } + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/macosx/Readme.html Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,38 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta http-equiv="Content-Style-Type" content="text/css"> + <title></title> + <style type="text/css"> + p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica} + p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px} + p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica} + p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed} + span.s1 {text-decoration: underline} + span.s2 {font: 12.0px Courier} + </style> +</head> +<body> +<p class="p1"><b>Before you install</b></p> +<p class="p2"><br></p> +<p class="p3">This is <i>not</i> a stand-alone version of Mercurial.</p> +<p class="p2"><br></p> +<p class="p3">To use it, you must have the “official unofficial†MacPython 2.4.1 installed.</p> +<p class="p2"><br></p> +<p class="p3">You can download MacPython 2.4.1 from here:</p> +<p class="p4"><span class="s1"><a href="http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg">http://python.org/ftp/python/2.4.1/MacPython-OSX-2.4.1-1.dmg</a></span></p> +<p class="p2"><br></p> +<p class="p3">For more information on MacPython, go here:</p> +<p class="p4"><span class="s1"><a href="http://undefined.org/python/">http://undefined.org/python</a></span></p> +<p class="p2"><br></p> +<p class="p1"><b>After you install</b></p> +<p class="p2"><br></p> +<p class="p3">This package installs the <span class="s2">hg</span> executable in <span class="s2">/usr/local/bin</span>. This directory may not be in your shell's search path. Don't forget to check.</p> +<p class="p2"><br></p> +<p class="p1"><b>Reporting problems</b></p> +<p class="p2"><br></p> +<p class="p3">If you run into any problems, please file a bug online:</p> +<p class="p3"><a href="http://www.selenic.com/mercurial/bts">http://www.selenic.com/mercurial/bts</a></p> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/macosx/Welcome.html Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,17 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <meta http-equiv="Content-Style-Type" content="text/css"> + <title></title> + <style type="text/css"> + p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica} + p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px} + </style> +</head> +<body> +<p class="p1">This is a prepackaged release of <a href="http://www.selenic.com/mercurial">Mercurial</a> for Mac OS X.</p> +<p class="p2"><br></p> +<p class="p1">It is based on Mercurial 0.8.</p> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/macosx/macosx-build.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +to build a new macosx binary package: + +install macpython from http://undefined.org/python/ + +install py2app from http://pythonmac.org/packages/ + +make sure /usr/local/bin is in your path + +run bdist_mpkg in top-level hg directory + +find packaged stuff in dist directory
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/mercurial.el Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1135 @@ +;;; mercurial.el --- Emacs support for the Mercurial distributed SCM + +;; Copyright (C) 2005 Bryan O'Sullivan + +;; Author: Bryan O'Sullivan <bos@serpentine.com> + +;; mercurial.el is free software; you can redistribute it and/or +;; modify it under the terms of version 2 of the GNU General Public +;; License as published by the Free Software Foundation. + +;; mercurial.el 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 mercurial.el, GNU Emacs, or XEmacs; see the file COPYING +;; (`C-h C-l'). If not, write to the Free Software Foundation, Inc., +;; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; mercurial.el builds upon Emacs's VC mode to provide flexible +;; integration with the Mercurial distributed SCM tool. + +;; To get going as quickly as possible, load mercurial.el into Emacs and +;; type `C-c h h'; this runs hg-help-overview, which prints a helpful +;; usage overview. + +;; Much of the inspiration for mercurial.el comes from Rajesh +;; Vaidheeswarran's excellent p4.el, which does an admirably thorough +;; job for the commercial Perforce SCM product. In fact, substantial +;; chunks of code are adapted from p4.el. + +;; This code has been developed under XEmacs 21.5, and may not work as +;; well under GNU Emacs (albeit tested under 21.4). Patches to +;; enhance the portability of this code, fix bugs, and add features +;; are most welcome. You can clone a Mercurial repository for this +;; package from http://www.serpentine.com/hg/hg-emacs + +;; Please send problem reports and suggestions to bos@serpentine.com. + + +;;; Code: + +(require 'advice) +(require 'cl) +(require 'diff-mode) +(require 'easymenu) +(require 'executable) +(require 'vc) + + +;;; XEmacs has view-less, while GNU Emacs has view. Joy. + +(condition-case nil + (require 'view-less) + (error nil)) +(condition-case nil + (require 'view) + (error nil)) + + +;;; Variables accessible through the custom system. + +(defgroup mercurial nil + "Mercurial distributed SCM." + :group 'tools) + +(defcustom hg-binary + (or (executable-find "hg") + (dolist (path '("~/bin/hg" "/usr/bin/hg" "/usr/local/bin/hg")) + (when (file-executable-p path) + (return path)))) + "The path to Mercurial's hg executable." + :type '(file :must-match t) + :group 'mercurial) + +(defcustom hg-mode-hook nil + "Hook run when a buffer enters hg-mode." + :type 'sexp + :group 'mercurial) + +(defcustom hg-commit-mode-hook nil + "Hook run when a buffer is created to prepare a commit." + :type 'sexp + :group 'mercurial) + +(defcustom hg-pre-commit-hook nil + "Hook run before a commit is performed. +If you want to prevent the commit from proceeding, raise an error." + :type 'sexp + :group 'mercurial) + +(defcustom hg-log-mode-hook nil + "Hook run after a buffer is filled with log information." + :type 'sexp + :group 'mercurial) + +(defcustom hg-global-prefix "\C-ch" + "The global prefix for Mercurial keymap bindings." + :type 'sexp + :group 'mercurial) + +(defcustom hg-commit-allow-empty-message nil + "Whether to allow changes to be committed with empty descriptions." + :type 'boolean + :group 'mercurial) + +(defcustom hg-commit-allow-empty-file-list nil + "Whether to allow changes to be committed without any modified files." + :type 'boolean + :group 'mercurial) + +(defcustom hg-rev-completion-limit 100 + "The maximum number of revisions that hg-read-rev will offer to complete. +This affects memory usage and performance when prompting for revisions +in a repository with a lot of history." + :type 'integer + :group 'mercurial) + +(defcustom hg-log-limit 50 + "The maximum number of revisions that hg-log will display." + :type 'integer + :group 'mercurial) + +(defcustom hg-update-modeline t + "Whether to update the modeline with the status of a file after every save. +Set this to nil on platforms with poor process management, such as Windows." + :type 'boolean + :group 'mercurial) + +(defcustom hg-incoming-repository "default" + "The repository from which changes are pulled from by default. +This should be a symbolic repository name, since it is used for all +repository-related commands." + :type 'string + :group 'mercurial) + +(defcustom hg-outgoing-repository "default-push" + "The repository to which changes are pushed to by default. +This should be a symbolic repository name, since it is used for all +repository-related commands." + :type 'string + :group 'mercurial) + + +;;; Other variables. + +(defconst hg-running-xemacs (string-match "XEmacs" emacs-version) + "Is mercurial.el running under XEmacs?") + +(defvar hg-mode nil + "Is this file managed by Mercurial?") +(make-variable-buffer-local 'hg-mode) +(put 'hg-mode 'permanent-local t) + +(defvar hg-status nil) +(make-variable-buffer-local 'hg-status) +(put 'hg-status 'permanent-local t) + +(defvar hg-prev-buffer nil) +(make-variable-buffer-local 'hg-prev-buffer) +(put 'hg-prev-buffer 'permanent-local t) + +(defvar hg-root nil) +(make-variable-buffer-local 'hg-root) +(put 'hg-root 'permanent-local t) + +(defvar hg-output-buffer-name "*Hg*" + "The name to use for Mercurial output buffers.") + +(defvar hg-file-history nil) +(defvar hg-repo-history nil) +(defvar hg-rev-history nil) + + +;;; Random constants. + +(defconst hg-commit-message-start + "--- Enter your commit message. Type `C-c C-c' to commit. ---\n") + +(defconst hg-commit-message-end + "--- Files in bold will be committed. Click to toggle selection. ---\n") + + +;;; hg-mode keymap. + +(defvar hg-mode-map (make-sparse-keymap)) +(define-key hg-mode-map "\C-xv" 'hg-prefix-map) + +(defvar hg-prefix-map + (let ((map (copy-keymap vc-prefix-map))) + (if (functionp 'set-keymap-name) + (set-keymap-name map 'hg-prefix-map)); XEmacs + map) + "This keymap overrides some default vc-mode bindings.") +(fset 'hg-prefix-map hg-prefix-map) +(define-key hg-prefix-map "=" 'hg-diff) +(define-key hg-prefix-map "c" 'hg-undo) +(define-key hg-prefix-map "g" 'hg-annotate) +(define-key hg-prefix-map "l" 'hg-log) +(define-key hg-prefix-map "n" 'hg-commit-start) +;; (define-key hg-prefix-map "r" 'hg-update) +(define-key hg-prefix-map "u" 'hg-revert-buffer) +(define-key hg-prefix-map "~" 'hg-version-other-window) + +(add-minor-mode 'hg-mode 'hg-mode hg-mode-map) + + +;;; Global keymap. + +(global-set-key "\C-xvi" 'hg-add) + +(defvar hg-global-map (make-sparse-keymap)) +(fset 'hg-global-map hg-global-map) +(global-set-key hg-global-prefix 'hg-global-map) +(define-key hg-global-map "," 'hg-incoming) +(define-key hg-global-map "." 'hg-outgoing) +(define-key hg-global-map "<" 'hg-pull) +(define-key hg-global-map "=" 'hg-diff-repo) +(define-key hg-global-map ">" 'hg-push) +(define-key hg-global-map "?" 'hg-help-overview) +(define-key hg-global-map "A" 'hg-addremove) +(define-key hg-global-map "U" 'hg-revert) +(define-key hg-global-map "a" 'hg-add) +(define-key hg-global-map "c" 'hg-commit-start) +(define-key hg-global-map "f" 'hg-forget) +(define-key hg-global-map "h" 'hg-help-overview) +(define-key hg-global-map "i" 'hg-init) +(define-key hg-global-map "l" 'hg-log-repo) +(define-key hg-global-map "r" 'hg-root) +(define-key hg-global-map "s" 'hg-status) +(define-key hg-global-map "u" 'hg-update) + + +;;; View mode keymap. + +(defvar hg-view-mode-map + (let ((map (copy-keymap (if (boundp 'view-minor-mode-map) + view-minor-mode-map + view-mode-map)))) + (if (functionp 'set-keymap-name) + (set-keymap-name map 'hg-view-mode-map)); XEmacs + map)) +(fset 'hg-view-mode-map hg-view-mode-map) +(define-key hg-view-mode-map + (if hg-running-xemacs [button2] [mouse-2]) + 'hg-buffer-mouse-clicked) + + +;;; Commit mode keymaps. + +(defvar hg-commit-mode-map (make-sparse-keymap)) +(define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish) +(define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill) +(define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo) + +(defvar hg-commit-mode-file-map (make-sparse-keymap)) +(define-key hg-commit-mode-file-map + (if hg-running-xemacs [button2] [mouse-2]) + 'hg-commit-mouse-clicked) +(define-key hg-commit-mode-file-map " " 'hg-commit-toggle-file) +(define-key hg-commit-mode-file-map "\r" 'hg-commit-toggle-file) + + +;;; Convenience functions. + +(defsubst hg-binary () + (if hg-binary + hg-binary + (error "No `hg' executable found!"))) + +(defsubst hg-replace-in-string (str regexp newtext &optional literal) + "Replace all matches in STR for REGEXP with NEWTEXT string. +Return the new string. Optional LITERAL non-nil means do a literal +replacement. + +This function bridges yet another pointless impedance gap between +XEmacs and GNU Emacs." + (if (fboundp 'replace-in-string) + (replace-in-string str regexp newtext literal) + (replace-regexp-in-string regexp newtext str nil literal))) + +(defsubst hg-strip (str) + "Strip leading and trailing blank lines from a string." + (hg-replace-in-string (hg-replace-in-string str "[\r\n][ \t\r\n]*\\'" "") + "\\`[ \t\r\n]*[\r\n]" "")) + +(defsubst hg-chomp (str) + "Strip trailing newlines from a string." + (hg-replace-in-string str "[\r\n]+\'" "")) + +(defun hg-run-command (command &rest args) + "Run the shell command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT). +The list ARGS contains a list of arguments to pass to the command." + (let* (exit-code + (output + (with-output-to-string + (with-current-buffer + standard-output + (setq exit-code + (apply 'call-process command nil t nil args)))))) + (cons exit-code output))) + +(defun hg-run (command &rest args) + "Run the Mercurial command COMMAND, returning (EXIT-CODE . COMMAND-OUTPUT)." + (apply 'hg-run-command (hg-binary) command args)) + +(defun hg-run0 (command &rest args) + "Run the Mercurial command COMMAND, returning its output. +If the command does not exit with a zero status code, raise an error." + (let ((res (apply 'hg-run-command (hg-binary) command args))) + (if (not (eq (car res) 0)) + (error "Mercurial command failed %s - exit code %s" + (cons command args) + (car res)) + (cdr res)))) + +(defun hg-sync-buffers (path) + "Sync buffers visiting PATH with their on-disk copies. +If PATH is not being visited, but is under the repository root, sync +all buffers visiting files in the repository." + (let ((buf (find-buffer-visiting path))) + (if buf + (with-current-buffer buf + (vc-buffer-sync)) + (hg-do-across-repo path + (vc-buffer-sync))))) + +(defun hg-buffer-commands (pnt) + "Use the properties of a character to do something sensible." + (interactive "d") + (let ((rev (get-char-property pnt 'rev)) + (file (get-char-property pnt 'file)) + (date (get-char-property pnt 'date)) + (user (get-char-property pnt 'user)) + (host (get-char-property pnt 'host)) + (prev-buf (current-buffer))) + (cond + (file + (find-file-other-window file)) + (rev + (hg-diff hg-view-file-name rev rev prev-buf)) + ((message "I don't know how to do that yet"))))) + +(defsubst hg-event-point (event) + "Return the character position of the mouse event EVENT." + (if hg-running-xemacs + (event-point event) + (posn-point (event-start event)))) + +(defsubst hg-event-window (event) + "Return the window over which mouse event EVENT occurred." + (if hg-running-xemacs + (event-window event) + (posn-window (event-start event)))) + +(defun hg-buffer-mouse-clicked (event) + "Translate the mouse clicks in a HG log buffer to character events. +These are then handed off to `hg-buffer-commands'. + +Handle frickin' frackin' gratuitous event-related incompatibilities." + (interactive "e") + (select-window (hg-event-window event)) + (hg-buffer-commands (hg-event-point event))) + +(unless (fboundp 'view-minor-mode) + (defun view-minor-mode (prev-buffer exit-func) + (view-mode))) + +(defsubst hg-abbrev-file-name (file) + "Portable wrapper around abbreviate-file-name." + (if hg-running-xemacs + (abbreviate-file-name file t) + (abbreviate-file-name file))) + +(defun hg-read-file-name (&optional prompt default) + "Read a file or directory name, or a pattern, to use with a command." + (save-excursion + (while hg-prev-buffer + (set-buffer hg-prev-buffer)) + (let ((path (or default (buffer-file-name)))) + (if (or (not path) current-prefix-arg) + (expand-file-name + (eval (list* 'read-file-name + (format "File, directory or pattern%s: " + (or prompt "")) + (and path (file-name-directory path)) + nil nil + (and path (file-name-nondirectory path)) + (if hg-running-xemacs + (cons (quote 'hg-file-history) nil) + nil)))) + path)))) + +(defun hg-read-number (&optional prompt default) + "Read a integer value." + (save-excursion + (if (or (not default) current-prefix-arg) + (string-to-number + (eval (list* 'read-string + (or prompt "") + (if default (cons (format "%d" default) nil) nil)))) + default))) + +(defun hg-read-config () + "Return an alist of (key . value) pairs of Mercurial config data. +Each key is of the form (section . name)." + (let (items) + (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items) + (string-match "^\\([^=]*\\)=\\(.*\\)" line) + (let* ((left (substring line (match-beginning 1) (match-end 1))) + (right (substring line (match-beginning 2) (match-end 2))) + (key (split-string left "\\.")) + (value (hg-replace-in-string right "\\\\n" "\n" t))) + (setq items (cons (cons (cons (car key) (cadr key)) value) items)))))) + +(defun hg-config-section (section config) + "Return an alist of (name . value) pairs for SECTION of CONFIG." + (let (items) + (dolist (item config items) + (when (equal (caar item) section) + (setq items (cons (cons (cdar item) (cdr item)) items)))))) + +(defun hg-string-starts-with (sub str) + "Indicate whether string STR starts with the substring or character SUB." + (if (not (stringp sub)) + (and (> (length str) 0) (equal (elt str 0) sub)) + (let ((sub-len (length sub))) + (and (<= sub-len (length str)) + (string= sub (substring str 0 sub-len)))))) + +(defun hg-complete-repo (string predicate all) + "Attempt to complete a repository name. +We complete on either symbolic names from Mercurial's config or real +directory names from the file system. We do not penalise URLs." + (or (if all + (all-completions string hg-repo-completion-table predicate) + (try-completion string hg-repo-completion-table predicate)) + (let* ((str (expand-file-name string)) + (dir (file-name-directory str)) + (file (file-name-nondirectory str))) + (if all + (let (completions) + (dolist (name (delete "./" (file-name-all-completions file dir)) + completions) + (let ((path (concat dir name))) + (when (file-directory-p path) + (setq completions (cons name completions)))))) + (let ((comp (file-name-completion file dir))) + (if comp + (hg-abbrev-file-name (concat dir comp)))))))) + +(defun hg-read-repo-name (&optional prompt initial-contents default) + "Read the location of a repository." + (save-excursion + (while hg-prev-buffer + (set-buffer hg-prev-buffer)) + (let (hg-repo-completion-table) + (if current-prefix-arg + (progn + (dolist (path (hg-config-section "paths" (hg-read-config))) + (setq hg-repo-completion-table + (cons (cons (car path) t) hg-repo-completion-table)) + (unless (hg-string-starts-with directory-sep-char (cdr path)) + (setq hg-repo-completion-table + (cons (cons (cdr path) t) hg-repo-completion-table)))) + (completing-read (format "Repository%s: " (or prompt "")) + 'hg-complete-repo + nil + nil + initial-contents + 'hg-repo-history + default)) + default)))) + +(defun hg-read-rev (&optional prompt default) + "Read a revision or tag, offering completions." + (save-excursion + (while hg-prev-buffer + (set-buffer hg-prev-buffer)) + (let ((rev (or default "tip"))) + (if current-prefix-arg + (let ((revs (split-string + (hg-chomp + (hg-run0 "-q" "log" "-r" + (format "-%d:tip" hg-rev-completion-limit))) + "[\n:]"))) + (dolist (line (split-string (hg-chomp (hg-run0 "tags")) "\n")) + (setq revs (cons (car (split-string line "\\s-")) revs))) + (completing-read (format "Revision%s (%s): " + (or prompt "") + (or default "tip")) + (map 'list 'cons revs revs) + nil + nil + nil + 'hg-rev-history + (or default "tip"))) + rev)))) + +(defmacro hg-do-across-repo (path &rest body) + (let ((root-name (gensym "root-")) + (buf-name (gensym "buf-"))) + `(let ((,root-name (hg-root ,path))) + (save-excursion + (dolist (,buf-name (buffer-list)) + (set-buffer ,buf-name) + (when (and hg-status (equal (hg-root buffer-file-name) ,root-name)) + ,@body)))))) + +(put 'hg-do-across-repo 'lisp-indent-function 1) + + +;;; View mode bits. + +(defun hg-exit-view-mode (buf) + "Exit from hg-view-mode. +We delete the current window if entering hg-view-mode split the +current frame." + (when (and (eq buf (current-buffer)) + (> (length (window-list)) 1)) + (delete-window)) + (when (buffer-live-p buf) + (kill-buffer buf))) + +(defun hg-view-mode (prev-buffer &optional file-name) + (goto-char (point-min)) + (set-buffer-modified-p nil) + (toggle-read-only t) + (view-minor-mode prev-buffer 'hg-exit-view-mode) + (use-local-map hg-view-mode-map) + (setq truncate-lines t) + (when file-name + (set (make-local-variable 'hg-view-file-name) + (hg-abbrev-file-name file-name)))) + +(defun hg-file-status (file) + "Return status of FILE, or nil if FILE does not exist or is unmanaged." + (let* ((s (hg-run "status" file)) + (exit (car s)) + (output (cdr s))) + (if (= exit 0) + (let ((state (assoc (substring output 0 (min (length output) 2)) + '(("M " . modified) + ("A " . added) + ("R " . removed) + ("? " . nil))))) + (if state + (cdr state) + 'normal))))) + +(defun hg-tip () + (split-string (hg-chomp (hg-run0 "-q" "tip")) ":")) + +(defmacro hg-view-output (args &rest body) + "Execute BODY in a clean buffer, then quickly display that buffer. +If the buffer contains one line, its contents are displayed in the +minibuffer. Otherwise, the buffer is displayed in view-mode. +ARGS is of the form (BUFFER-NAME &optional FILE), where BUFFER-NAME is +the name of the buffer to create, and FILE is the name of the file +being viewed." + (let ((prev-buf (gensym "prev-buf-")) + (v-b-name (car args)) + (v-m-rest (cdr args))) + `(let ((view-buf-name ,v-b-name) + (,prev-buf (current-buffer))) + (get-buffer-create view-buf-name) + (kill-buffer view-buf-name) + (get-buffer-create view-buf-name) + (set-buffer view-buf-name) + (save-excursion + ,@body) + (case (count-lines (point-min) (point-max)) + ((0) + (kill-buffer view-buf-name) + (message "(No output)")) + ((1) + (let ((msg (hg-chomp (buffer-substring (point-min) (point-max))))) + (kill-buffer view-buf-name) + (message "%s" msg))) + (t + (pop-to-buffer view-buf-name) + (setq hg-prev-buffer ,prev-buf) + (hg-view-mode ,prev-buf ,@v-m-rest)))))) + +(put 'hg-view-output 'lisp-indent-function 1) + +;;; Context save and restore across revert. + +(defun hg-position-context (pos) + "Return information to help find the given position again." + (let* ((end (min (point-max) (+ pos 98)))) + (list pos + (buffer-substring (max (point-min) (- pos 2)) end) + (- end pos)))) + +(defun hg-buffer-context () + "Return information to help restore a user's editing context. +This is useful across reverts and merges, where a context is likely +to have moved a little, but not really changed." + (let ((point-context (hg-position-context (point))) + (mark-context (let ((mark (mark-marker))) + (and mark (hg-position-context mark))))) + (list point-context mark-context))) + +(defun hg-find-context (ctx) + "Attempt to find a context in the given buffer. +Always returns a valid, hopefully sane, position." + (let ((pos (nth 0 ctx)) + (str (nth 1 ctx)) + (fixup (nth 2 ctx))) + (save-excursion + (goto-char (max (point-min) (- pos 15000))) + (if (and (not (equal str "")) + (search-forward str nil t)) + (- (point) fixup) + (max pos (point-min)))))) + +(defun hg-restore-context (ctx) + "Attempt to restore the user's editing context." + (let ((point-context (nth 0 ctx)) + (mark-context (nth 1 ctx))) + (goto-char (hg-find-context point-context)) + (when mark-context + (set-mark (hg-find-context mark-context))))) + + +;;; Hooks. + +(defun hg-mode-line (&optional force) + "Update the modeline with the current status of a file. +An update occurs if optional argument FORCE is non-nil, +hg-update-modeline is non-nil, or we have not yet checked the state of +the file." + (when (and (hg-root) (or force hg-update-modeline (not hg-mode))) + (let ((status (hg-file-status buffer-file-name))) + (setq hg-status status + hg-mode (and status (concat " Hg:" + (car (hg-tip)) + (cdr (assq status + '((normal . "") + (removed . "r") + (added . "a") + (modified . "m"))))))) + status))) + +(defun hg-mode (&optional toggle) + "Minor mode for Mercurial distributed SCM integration. + +The Mercurial mode user interface is based on that of VC mode, so if +you're already familiar with VC, the same keybindings and functions +will generally work. + +Below is a list of many common SCM tasks. In the list, `G/L' +indicates whether a key binding is global (G) to a repository or local +(L) to a file. Many commands take a prefix argument. + +SCM Task G/L Key Binding Command Name +-------- --- ----------- ------------ +Help overview (what you are reading) G C-c h h hg-help-overview + +Tell Mercurial to manage a file G C-c h a hg-add +Commit changes to current file only L C-x v n hg-commit-start +Undo changes to file since commit L C-x v u hg-revert-buffer + +Diff file vs last checkin L C-x v = hg-diff + +View file change history L C-x v l hg-log +View annotated file L C-x v a hg-annotate + +Diff repo vs last checkin G C-c h = hg-diff-repo +View status of files in repo G C-c h s hg-status +Commit all changes G C-c h c hg-commit-start + +Undo all changes since last commit G C-c h U hg-revert +View repo change history G C-c h l hg-log-repo + +See changes that can be pulled G C-c h , hg-incoming +Pull changes G C-c h < hg-pull +Update working directory after pull G C-c h u hg-update +See changes that can be pushed G C-c h . hg-outgoing +Push changes G C-c h > hg-push" + (run-hooks 'hg-mode-hook)) + +(defun hg-find-file-hook () + (when (hg-mode-line) + (hg-mode))) + +(add-hook 'find-file-hooks 'hg-find-file-hook) + +(defun hg-after-save-hook () + (let ((old-status hg-status)) + (hg-mode-line) + (if (and (not old-status) hg-status) + (hg-mode)))) + +(add-hook 'after-save-hook 'hg-after-save-hook) + + +;;; User interface functions. + +(defun hg-help-overview () + "This is an overview of the Mercurial SCM mode for Emacs. + +You can find the source code, license (GPL v2), and credits for this +code by typing `M-x find-library mercurial RET'." + (interactive) + (hg-view-output ("Mercurial Help Overview") + (insert (documentation 'hg-help-overview)) + (let ((pos (point))) + (insert (documentation 'hg-mode)) + (goto-char pos) + (kill-line)))) + +(defun hg-add (path) + "Add PATH to the Mercurial repository on the next commit. +With a prefix argument, prompt for the path to add." + (interactive (list (hg-read-file-name " to add"))) + (let ((buf (current-buffer)) + (update (equal buffer-file-name path))) + (hg-view-output (hg-output-buffer-name) + (apply 'call-process (hg-binary) nil t nil (list "add" path))) + (when update + (with-current-buffer buf + (hg-mode-line))))) + +(defun hg-addremove () + (interactive) + (error "not implemented")) + +(defun hg-annotate () + (interactive) + (error "not implemented")) + +(defun hg-commit-toggle-file (pos) + "Toggle whether or not the file at POS will be committed." + (interactive "d") + (save-excursion + (goto-char pos) + (let ((face (get-text-property pos 'face)) + (inhibit-read-only t) + bol) + (beginning-of-line) + (setq bol (+ (point) 4)) + (end-of-line) + (if (eq face 'bold) + (progn + (remove-text-properties bol (point) '(face nil)) + (message "%s will not be committed" + (buffer-substring bol (point)))) + (add-text-properties bol (point) '(face bold)) + (message "%s will be committed" + (buffer-substring bol (point))))))) + +(defun hg-commit-mouse-clicked (event) + "Toggle whether or not the file at POS will be committed." + (interactive "@e") + (hg-commit-toggle-file (hg-event-point event))) + +(defun hg-commit-kill () + "Kill the commit currently being prepared." + (interactive) + (when (or (not (buffer-modified-p)) (y-or-n-p "Really kill this commit? ")) + (let ((buf hg-prev-buffer)) + (kill-buffer nil) + (switch-to-buffer buf)))) + +(defun hg-commit-finish () + "Finish preparing a commit, and perform the actual commit. +The hook hg-pre-commit-hook is run before anything else is done. If +the commit message is empty and hg-commit-allow-empty-message is nil, +an error is raised. If the list of files to commit is empty and +hg-commit-allow-empty-file-list is nil, an error is raised." + (interactive) + (let ((root hg-root)) + (save-excursion + (run-hooks 'hg-pre-commit-hook) + (goto-char (point-min)) + (search-forward hg-commit-message-start) + (let (message files) + (let ((start (point))) + (goto-char (point-max)) + (search-backward hg-commit-message-end) + (setq message (hg-strip (buffer-substring start (point))))) + (when (and (= (length message) 0) + (not hg-commit-allow-empty-message)) + (error "Cannot proceed - commit message is empty")) + (forward-line 1) + (beginning-of-line) + (while (< (point) (point-max)) + (let ((pos (+ (point) 4))) + (end-of-line) + (when (eq (get-text-property pos 'face) 'bold) + (end-of-line) + (setq files (cons (buffer-substring pos (point)) files)))) + (forward-line 1)) + (when (and (= (length files) 0) + (not hg-commit-allow-empty-file-list)) + (error "Cannot proceed - no files to commit")) + (setq message (concat message "\n")) + (apply 'hg-run0 "--cwd" hg-root "commit" "-m" message files)) + (let ((buf hg-prev-buffer)) + (kill-buffer nil) + (switch-to-buffer buf)) + (hg-do-across-repo root + (hg-mode-line))))) + +(defun hg-commit-mode () + "Mode for describing a commit of changes to a Mercurial repository. +This involves two actions: describing the changes with a commit +message, and choosing the files to commit. + +To describe the commit, simply type some text in the designated area. + +By default, all modified, added and removed files are selected for +committing. Files that will be committed are displayed in bold face\; +those that will not are displayed in normal face. + +To toggle whether a file will be committed, move the cursor over a +particular file and hit space or return. Alternatively, middle click +on the file. + +Key bindings +------------ +\\[hg-commit-finish] proceed with commit +\\[hg-commit-kill] kill commit + +\\[hg-diff-repo] view diff of pending changes" + (interactive) + (use-local-map hg-commit-mode-map) + (set-syntax-table text-mode-syntax-table) + (setq local-abbrev-table text-mode-abbrev-table + major-mode 'hg-commit-mode + mode-name "Hg-Commit") + (set-buffer-modified-p nil) + (setq buffer-undo-list nil) + (run-hooks 'text-mode-hook 'hg-commit-mode-hook)) + +(defun hg-commit-start () + "Prepare a commit of changes to the repository containing the current file." + (interactive) + (while hg-prev-buffer + (set-buffer hg-prev-buffer)) + (let ((root (hg-root)) + (prev-buffer (current-buffer)) + modified-files) + (unless root + (error "Cannot commit outside a repository!")) + (hg-sync-buffers root) + (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm"))) + (when (and (= (length modified-files) 0) + (not hg-commit-allow-empty-file-list)) + (error "No pending changes to commit")) + (let* ((buf-name (format "*Mercurial: Commit %s*" root))) + (pop-to-buffer (get-buffer-create buf-name)) + (when (= (point-min) (point-max)) + (set (make-local-variable 'hg-root) root) + (setq hg-prev-buffer prev-buffer) + (insert "\n") + (let ((bol (point))) + (insert hg-commit-message-end) + (add-text-properties bol (point) '(face bold-italic))) + (let ((file-area (point))) + (insert modified-files) + (goto-char file-area) + (while (< (point) (point-max)) + (let ((bol (point))) + (forward-char 1) + (insert " ") + (end-of-line) + (add-text-properties (+ bol 4) (point) + '(face bold mouse-face highlight))) + (forward-line 1)) + (goto-char file-area) + (add-text-properties (point) (point-max) + `(keymap ,hg-commit-mode-file-map)) + (goto-char (point-min)) + (insert hg-commit-message-start) + (add-text-properties (point-min) (point) '(face bold-italic)) + (insert "\n\n") + (forward-line -1) + (save-excursion + (goto-char (point-max)) + (search-backward hg-commit-message-end) + (add-text-properties (match-beginning 0) (point-max) + '(read-only t)) + (goto-char (point-min)) + (search-forward hg-commit-message-start) + (add-text-properties (match-beginning 0) (match-end 0) + '(read-only t))) + (hg-commit-mode)))))) + +(defun hg-diff (path &optional rev1 rev2) + "Show the differences between REV1 and REV2 of PATH. +When called interactively, the default behaviour is to treat REV1 as +the tip revision, REV2 as the current edited version of the file, and +PATH as the file edited in the current buffer. +With a prefix argument, prompt for all of these." + (interactive (list (hg-read-file-name " to diff") + (hg-read-rev " to start with") + (let ((rev2 (hg-read-rev " to end with" 'working-dir))) + (and (not (eq rev2 'working-dir)) rev2)))) + (hg-sync-buffers path) + (let ((a-path (hg-abbrev-file-name path)) + (r1 (or rev1 "tip")) + diff) + (hg-view-output ((cond + ((and (equal r1 "tip") (not rev2)) + (format "Mercurial: Diff against tip of %s" a-path)) + ((equal r1 rev2) + (format "Mercurial: Diff of rev %s of %s" r1 a-path)) + (t + (format "Mercurial: Diff from rev %s to %s of %s" + r1 (or rev2 "Current") a-path)))) + (if rev2 + (call-process (hg-binary) nil t nil "diff" "-r" r1 "-r" rev2 path) + (call-process (hg-binary) nil t nil "diff" "-r" r1 path)) + (diff-mode) + (setq diff (not (= (point-min) (point-max)))) + (font-lock-fontify-buffer)) + diff)) + +(defun hg-diff-repo () + "Show the differences between the working copy and the tip revision." + (interactive) + (hg-diff (hg-root))) + +(defun hg-forget (path) + "Lose track of PATH, which has been added, but not yet committed. +This will prevent the file from being incorporated into the Mercurial +repository on the next commit. +With a prefix argument, prompt for the path to forget." + (interactive (list (hg-read-file-name " to forget"))) + (let ((buf (current-buffer)) + (update (equal buffer-file-name path))) + (hg-view-output (hg-output-buffer-name) + (apply 'call-process (hg-binary) nil t nil (list "forget" path))) + (when update + (with-current-buffer buf + (hg-mode-line))))) + +(defun hg-incoming (&optional repo) + "Display changesets present in REPO that are not present locally." + (interactive (list (hg-read-repo-name " where changes would come from"))) + (hg-view-output ((format "Mercurial: Incoming from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-incoming-repository)))) + (call-process (hg-binary) nil t nil "incoming" + (or repo hg-incoming-repository)) + (hg-log-mode))) + +(defun hg-init () + (interactive) + (error "not implemented")) + +(defun hg-log-mode () + "Mode for viewing a Mercurial change log." + (goto-char (point-min)) + (when (looking-at "^searching for changes") + (kill-entire-line)) + (run-hooks 'hg-log-mode-hook)) + +(defun hg-log (path &optional rev1 rev2 log-limit) + "Display the revision history of PATH. +History is displayed between REV1 and REV2. +Number of displayed changesets is limited to LOG-LIMIT. +REV1 defaults to the tip, while +REV2 defaults to `hg-rev-completion-limit' changes from the tip revision. +LOG-LIMIT defaults to `hg-log-limit'. +With a prefix argument, prompt for each parameter." + (interactive (list (hg-read-file-name " to log") + (hg-read-rev " to start with" + "tip") + (hg-read-rev " to end with" + (format "%d" (- hg-rev-completion-limit))) + (hg-read-number "Output limited to: " + hg-log-limit))) + (let ((a-path (hg-abbrev-file-name path)) + (r1 (or rev1 (format "-%d" hg-rev-completion-limit))) + (r2 (or rev2 rev1 "tip")) + (limit (format "%d" (or log-limit hg-log-limit)))) + (hg-view-output ((if (equal r1 r2) + (format "Mercurial: Log of rev %s of %s" rev1 a-path) + (format + "Mercurial: at most %s log(s) from rev %s to %s of %s" + limit r1 r2 a-path))) + (eval (list* 'call-process (hg-binary) nil t nil + "log" + "-r" (format "%s:%s" r1 r2) + "-l" limit + (if (> (length path) (length (hg-root path))) + (cons path nil) + nil))) + (hg-log-mode)))) + +(defun hg-log-repo (path &optional rev1 rev2 log-limit) + "Display the revision history of the repository containing PATH. +History is displayed between REV1 and REV2. +Number of displayed changesets is limited to LOG-LIMIT, +REV1 defaults to the tip, while +REV2 defaults to `hg-rev-completion-limit' changes from the tip revision. +LOG-LIMIT defaults to `hg-log-limit'. +With a prefix argument, prompt for each parameter." + (interactive (list (hg-read-file-name " to log") + (hg-read-rev " to start with" + "tip") + (hg-read-rev " to end with" + (format "%d" (- hg-rev-completion-limit))) + (hg-read-number "Output limited to: " + hg-log-limit))) + (hg-log (hg-root path) rev1 rev2 log-limit)) + +(defun hg-outgoing (&optional repo) + "Display changesets present locally that are not present in REPO." + (interactive (list (hg-read-repo-name " where changes would go to" nil + hg-outgoing-repository))) + (hg-view-output ((format "Mercurial: Outgoing from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-outgoing-repository)))) + (call-process (hg-binary) nil t nil "outgoing" + (or repo hg-outgoing-repository)) + (hg-log-mode))) + +(defun hg-pull (&optional repo) + "Pull changes from repository REPO. +This does not update the working directory." + (interactive (list (hg-read-repo-name " to pull from"))) + (hg-view-output ((format "Mercurial: Pull to %s from %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-incoming-repository)))) + (call-process (hg-binary) nil t nil "pull" + (or repo hg-incoming-repository)))) + +(defun hg-push (&optional repo) + "Push changes to repository REPO." + (interactive (list (hg-read-repo-name " to push to"))) + (hg-view-output ((format "Mercurial: Push from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-outgoing-repository)))) + (call-process (hg-binary) nil t nil "push" + (or repo hg-outgoing-repository)))) + +(defun hg-revert-buffer-internal () + (let ((ctx (hg-buffer-context))) + (message "Reverting %s..." buffer-file-name) + (hg-run0 "revert" buffer-file-name) + (revert-buffer t t t) + (hg-restore-context ctx) + (hg-mode-line) + (message "Reverting %s...done" buffer-file-name))) + +(defun hg-revert-buffer () + "Revert current buffer's file back to the latest committed version. +If the file has not changed, nothing happens. Otherwise, this +displays a diff and asks for confirmation before reverting." + (interactive) + (let ((vc-suppress-confirm nil) + (obuf (current-buffer)) + diff) + (vc-buffer-sync) + (unwind-protect + (setq diff (hg-diff buffer-file-name)) + (when diff + (unless (yes-or-no-p "Discard changes? ") + (error "Revert cancelled"))) + (when diff + (let ((buf (current-buffer))) + (delete-window (selected-window)) + (kill-buffer buf)))) + (set-buffer obuf) + (when diff + (hg-revert-buffer-internal)))) + +(defun hg-root (&optional path) + "Return the root of the repository that contains the given path. +If the path is outside a repository, return nil. +When called interactively, the root is printed. A prefix argument +prompts for a path to check." + (interactive (list (hg-read-file-name))) + (if (or path (not hg-root)) + (let ((root (do ((prev nil dir) + (dir (file-name-directory (or path buffer-file-name "")) + (file-name-directory (directory-file-name dir)))) + ((equal prev dir)) + (when (file-directory-p (concat dir ".hg")) + (return dir))))) + (when (interactive-p) + (if root + (message "The root of this repository is `%s'." root) + (message "The path `%s' is not in a Mercurial repository." + (hg-abbrev-file-name path)))) + root) + hg-root)) + +(defun hg-status (path) + "Print revision control status of a file or directory. +With prefix argument, prompt for the path to give status for. +Names are displayed relative to the repository root." + (interactive (list (hg-read-file-name " for status" (hg-root)))) + (let ((root (hg-root))) + (hg-view-output ((format "Mercurial: Status of %s in %s" + (let ((name (substring (expand-file-name path) + (length root)))) + (if (> (length name) 0) + name + "*")) + (hg-abbrev-file-name root))) + (apply 'call-process (hg-binary) nil t nil + (list "--cwd" root "status" path))))) + +(defun hg-undo () + (interactive) + (error "not implemented")) + +(defun hg-update () + (interactive) + (error "not implemented")) + +(defun hg-version-other-window () + (interactive) + (error "not implemented")) + + +(provide 'mercurial) + + +;;; Local Variables: +;;; prompt-to-byte-compile: nil +;;; end:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/mercurial.spec Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,42 @@ +Summary: Mercurial -- a distributed SCM +Name: mercurial +Version: 0.8 +Release: 0 +License: GPL +Group: Development/Tools +Source: http://www.selenic.com/mercurial/release/%{name}-%{version}.tar.gz +URL: http://www.selenic.com/mercurial +BuildRoot: /tmp/build.%{name}-%{version}-%{release} + +%define pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))') +%define pythonlib %{_libdir}/python%{pythonver}/site-packages/%{name} +%define hgext %{_libdir}/python%{pythonver}/site-packages/hgext + +%description +Mercurial is a fast, lightweight source control management system designed +for efficient handling of very large distributed projects. + +%prep +rm -rf $RPM_BUILD_ROOT +%setup -q + +%build +python setup.py build + +%install +python setup.py install --root $RPM_BUILD_ROOT + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%doc doc/* *.cgi +%dir %{pythonlib} +%dir %{hgext} +%{_bindir}/hgmerge +%{_bindir}/hg +%{pythonlib}/templates +%{pythonlib}/*.py* +%{pythonlib}/*.so +%{hgext}/*.py*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/tcsh_completion Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,42 @@ +# +# tcsh completion for Mercurial +# +# This file has been auto-generated by tcsh_completion_build.sh for +# Mercurial Distributed SCM (version 325c07fd2ebd) +# +# Copyright (C) 2005 TK Soh. +# +# This 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. +# + +complete hg \ + 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \ + 'C/-/( -R --repository \ + --cwd \ + -y --noninteractive \ + -q --quiet \ + -v --verbose \ + --debug \ + --debugger \ + --traceback \ + --time \ + --profile \ + --version \ + -h --help)/' \ + 'p/1/(add addremove annotate bundle cat \ + clone commit ci copy cp \ + debugancestor debugcheckstate debugconfig debugdata debugindex \ + debugindexdot debugrename debugstate debugwalk diff \ + export forget grep heads help \ + identify id import patch incoming \ + in init locate log history \ + manifest outgoing out parents paths \ + pull push rawcommit recover remove \ + rm rename mv revert root \ + serve status tag tags tip \ + unbundle undo update up checkout \ + co verify version)/' +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/tcsh_completion_build.sh Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,73 @@ +#!/bin/sh + +# +# tcsh_completion_build.sh - script to generate tcsh completion +# +# +# Copyright (C) 2005 TK Soh. +# +# This 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. +# +# +# Description +# ----------- +# This script generates a tcsh source file to support completion +# of Mercurial commands and options. +# +# Instruction: +# ----------- +# Run this script to generate the tcsh source file, and source +# the file to add command completion support for Mercurial. +# +# tcsh% tcsh_completion.sh FILE +# tcsh% source FILE +# +# If FILE is not specified, tcsh_completion will be generated. +# +# Bugs: +# ---- +# 1. command specific options are not supported +# 2. hg commands must be specified immediately after 'hg'. +# + +tcsh_file=${1-tcsh_completion} + +hg_commands=`hg --debug help | \ + sed -e '1,/^list of commands:/d' \ + -e '/^global options:/,$d' \ + -e '/^ [^ ]/!d; s/[,:]//g;' | \ + xargs -n5 | \ + sed -e '$!s/$/ \\\\/g; 2,$s/^ */ /g'` + +hg_global_options=`hg -v help | \ + sed -e '1,/global/d;/^ *-/!d; s/ [^- ].*//' | \ + sed -e 's/ *$//; $!s/$/ \\\\/g; 2,$s/^ */ /g'` + +hg_version=`hg version | sed -e '1q'` + +script_name=`basename $0` + +cat > $tcsh_file <<END +# +# tcsh completion for Mercurial +# +# This file has been auto-generated by $script_name for +# $hg_version +# +# Copyright (C) 2005 TK Soh. +# +# This 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. +# + +complete hg \\ + 'n/--cwd/d/' 'n/-R/d/' 'n/--repository/d/' \\ + 'C/-/($hg_global_options)/' \\ + 'p/1/($hg_commands)/' + +END
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vim/hg-menu.vim Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,93 @@ +" vim600: set foldmethod=marker: +" ============================================================================= +" Name Of File: hg-menu.vim +" Description: Interface to Mercurial Version Control. +" Author: Steve Borho (modified Jeff Lanzarotta's RCS script) +" Date: Wednesday, October 5, 2005 +" Version: 0.1.0 +" Copyright: None. +" Usage: These command and gui menu displays useful hg functions +" Configuration: Your hg executable must be in your path. +" ============================================================================= + +" Section: Init {{{1 +if exists("loaded_hg_menu") + finish +endif +let loaded_hg_menu = 1 + +" Section: Menu Options {{{1 +if has("gui") +" amenu H&G.Commit\ File<Tab>,ci :!hg commit %<CR>:e!<CR> +" amenu H&G.Commit\ All<Tab>,call :!hg commit<CR>:e!<CR> +" amenu H&G.-SEP1- <nul> + amenu H&G.Add<Tab>\\add :!hg add %<CR><CR> + amenu H&G.Forget\ Add<Tab>\\fgt :!hg forget %<CR><CR> + amenu H&G.Show\ Differences<Tab>\\diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR> + amenu H&G.Revert\ to\ Last\ Version<Tab>\\revert :!hg revert %<CR>:e!<CR> + amenu H&G.Show\ History<Tab>\\log :call ShowResults("FileLog", "hg\ log")<CR><CR> + amenu H&G.Annotate<Tab>\\an :call ShowResults("annotate", "hg\ annotate")<CR><CR> + amenu H&G.-SEP1- <nul> + amenu H&G.Repo\ Status<Tab>\\stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR> + amenu H&G.Pull<Tab>\\pull :!hg pull<CR>:e!<CR> + amenu H&G.Update<Tab>\\upd :!hg update<CR>:e!<CR> +endif + +" Section: Mappings {{{1 +if(v:version >= 600) + " The default Leader is \ 'backslash' + map <Leader>add :!hg add %<CR><CR> + map <Leader>fgt :!hg forget %<CR><CR> + map <Leader>diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR> + map <Leader>revert :!hg revert %<CR>:e!<CR> + map <Leader>log :call ShowResults("FileLog", "hg\ log")<CR><CR> + map <Leader>an :call ShowResults("annotate", "hg\ annotate")<CR><CR> + map <Leader>stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR> + map <Leader>upd :!hg update<CR>:e!<CR> + map <Leader>pull :!hg pull<CR>:e!<CR> +else + " pre 6.0, the default Leader was a comma + map ,add :!hg add %<CR><CR> + map ,fgt :!hg forget %<CR><CR> + map ,diff :call ShowResults("FileDiff", "hg\ diff")<CR><CR> + map ,revert :!hg revert<CR>:e!<CR> + map ,log :call ShowResults("FileLog", "hg\ log")<CR><CR> + map ,an :call ShowResults("annotate", "hg\ annotate")<CR><CR> + map ,stat :call ShowResults("RepoStatus", "hg\ status")<CR><CR> + map ,upd :!hg update<CR>:e!<CR> + map ,pull :!hg pull<CR>:e!<CR> +endif + +" Section: Functions {{{1 +" Show the log results of the current file with a revision control system. +function! ShowResults(bufferName, cmdName) + " Modify the shortmess option: + " A don't give the "ATTENTION" message when an existing swap file is + " found. + set shortmess+=A + + " Get the name of the current buffer. + let currentBuffer = bufname("%") + + " If a buffer with the name rlog exists, delete it. + if bufexists(a:bufferName) + execute 'bd! ' a:bufferName + endif + + " Create a new buffer. + execute 'new ' a:bufferName + + " Execute the command. + execute 'r!' a:cmdName ' ' currentBuffer + + " Make is so that the file can't be edited. + setlocal nomodified + setlocal nomodifiable + setlocal readonly + + " Go to the beginning of the buffer. + execute "normal 1G" + + " Restore the shortmess option. + set shortmess-=A +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vim/patchreview.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,97 @@ +*patchreview.txt* Vim global plugin for doing single or multipatch code reviews + + Author: Manpreet Singh (junkblocker-CAT-yahoo-DOG-com) + (Replace -CAT- and -DOG- with @ and . first) + Copyright (C) 2006 by Manpreet Singh + License : This file is placed in the public domain. + +============================================================================= + +CONTENTS *patchreview* *patchreview-contents* + + 1. Contents.........................................: |patchreview-contents| + 2. Introduction.....................................: |patchreview-intro| + 3. PatchReview options..............................: |patchreview-options| + 4. PatchReview Usage................................: |patchreview-usage| + 4.1 PatchReview Usage............................: |:PatchReview| + 4.2 PatchReview Usage............................: |:PatchReviewCleanup| + +============================================================================= + +PatchReview Introduction *patchreview-intro* + +The Patch Review plugin allows single or multipatch code review to be done in +VIM. VIM provides the |:diffpatch| command to do single file reviews but can +not handle patch files containing multiple patches as is common with software +development projects. This plugin provides that missing functionality. It also +tries to improve on |:diffpatch|'s behaviour of creating the patched files in +the same directory as original file which can lead to project workspace +pollution. + +============================================================================= + +PatchReview Options *patchreview-options* + + g:patchreview_filterdiff : Optional path to filterdiff binary. PatchReview + tries to locate filterdiff on system path + automatically. If the binary is not on system + path, this option tell PatchReview the full path + to the binary. This option, if specified, + overrides the default filterdiff binary on the + path. + + examples: + (On Windows with Cygwin) + + let g:patchreview_filterdiff = 'c:\\cygwin\\bin\\filterdiff.exe' + + (On *nix systems) + + let g:patchreview_filterdiff = '/usr/bin/filterdiff' + + g:patchreview_patch : Optional path to patch binary. PatchReview tries + to locate patch on system path automatically. If + the binary is not on system path, this option + tell PatchReview the full path to the binary. + This option, if specified, overrides the default + patch binary on the path. + + examples: + (On Windows with Cygwin) + + let g:patchreview_patch = 'c:\\cygwin\\bin\\patch.exe' + + (On *nix systems) + + let g:patchreview_patch = '/usr/bin/gpatch' + + + g:patchreview_tmpdir : Optional path where the plugin can save temporary + files. If this is not specified, the plugin tries to + use TMP, TEMP and TMPDIR environment variables in + succession. + + examples: + (On Windows) let g:patchreview_tmpdir = 'c:\\tmp' + (On *nix systems) let g:patchreview_tmpdir = '~/tmp' + +============================================================================= + +PatchReview Usage *patchreview-usage* + *:PatchReview* + + :PatchReview patchfile_path [optional_source_directory] + + Perform a patch review in the current directory based on the supplied + patchfile_path. If optional_source_directory is specified, patchreview is + done on that directory. Othewise, the current directory is assumed to be + the source directory. + *:PatchReviewCleanup* + + :PatchReviewCleanup + + After you are done using the :PatchReview command, you can cleanup the + temporary files in the temporary directory using this command. + +============================================================================= +vim: ft=help:ts=2:sts=2:sw=2:tw=78:tw=78
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/vim/patchreview.vim Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,332 @@ +" Vim global plugin for doing single or multipatch code reviews"{{{ + +" Version : 0.1 "{{{ +" Last Modified : Thu 25 May 2006 10:15:11 PM PDT +" Author : Manpreet Singh (junkblocker AT yahoo DOT com) +" Copyright : 2006 by Manpreet Singh +" License : This file is placed in the public domain. +" +" History : 0.1 - First released +"}}} +" Documentation: "{{{ +" =========================================================================== +" This plugin allows single or multipatch code reviews to be done in VIM. Vim +" has :diffpatch command to do single file reviews but can not handle patch +" files containing multiple patches. This plugin provides that missing +" functionality and doesn't require the original file to be open. +" +" Installing: "{{{ +" +" For a quick start... +" +" Requirements: "{{{ +" +" 1) (g)vim 7.0 or higher built with +diff option. +" 2) patch and patchutils ( http://cyberelk.net/tim/patchutils/ ) installed +" for your OS. For windows it is availble from Cygwin ( +" http://www.cygwin.com ) or GnuWin32 ( http://gnuwin32.sourceforge.net/ +" ). +""}}} +" Install: "{{{ +" +" 1) Extract this in your $VIM/vimfiles or $HOME/.vim directory and restart +" vim. +" +" 2) Make sure that you have filterdiff from patchutils and patch commands +" installed. +" +" 3) Optinally, specify the locations to filterdiff and patch commands and +" location of a temporary directory to use in your .vimrc. +" +" let g:patchreview_filterdiff = '/path/to/filterdiff' +" let g:patchreview_patch = '/path/to/patch' +" let g:patchreview_tmpdir = '/tmp/or/something' +" +" 4) Optionally, generate help tags to use help +" +" :helptags ~/.vim/doc +" or +" :helptags c:\vim\vimfiles\doc +""}}} +""}}} +" Usage: "{{{ +" +" :PatchReview path_to_submitted_patchfile [optional_source_directory] +" +" after review is done +" +" :PatchReviewCleanup +" +" See :help patchreview for details after you've created help tags. +""}}} +"}}} +" Code "{{{ + +" Enabled only during development "{{{ +" unlet! g:loaded_patchreview " DEBUG +" unlet! g:patchreview_tmpdir " DEBUG +" unlet! g:patchreview_filterdiff " DEBUG +" unlet! g:patchreview_patch " DEBUG +"}}} + +" load only once "{{{ +if exists('g:loaded_patchreview') + finish +endif +let g:loaded_patchreview=1 +let s:msgbufname = 'Patch Review Messages' +"}}} + +function! <SID>PR_wipeMsgBuf() "{{{ + let s:winnum = bufwinnr(s:msgbufname) + if s:winnum != -1 " If the window is already open, jump to it + let s:cur_winnr = winnr() + if winnr() != s:winnum + exe s:winnum . 'wincmd w' + exe 'bw' + exe s:cur_winnr . 'wincmd w' + endif + endif +endfunction +"}}} + +function! <SID>PR_echo(...) "{{{ + " Usage: PR_echo(msg, [return_to_original_window_flag]) + " default return_to_original_window_flag = 0 + " + let s:cur_winnr = winnr() + let s:winnum = bufwinnr(s:msgbufname) + if s:winnum != -1 " If the window is already open, jump to it + if winnr() != s:winnum + exe s:winnum . 'wincmd w' + endif + else + let s:bufnum = bufnr(s:msgbufname) + if s:bufnum == -1 + let s:wcmd = s:msgbufname + else + let s:wcmd = '+buffer' . s:bufnum + endif + exe 'silent! botright 5split ' . s:wcmd + endif + setlocal modifiable + setlocal buftype=nofile + setlocal bufhidden=delete + setlocal noswapfile + setlocal nowrap + setlocal nobuflisted + if a:0 != 0 + silent! $put =a:1 + endif + exe ':$' + setlocal nomodifiable + if a:0 > 1 && a:2 + exe s:cur_winnr . 'wincmd w' + endif +endfunction +"}}} + +function! <SID>PR_checkBinary(BinaryName) "{{{ + " Verify that BinaryName is specified or available + if ! exists('g:patchreview_' . a:BinaryName) + if executable(a:BinaryName) + let g:patchreview_{a:BinaryName} = a:BinaryName + return 1 + else + call s:PR_echo('g:patchreview_' . a:BinaryName . ' is not defined and could not be found on path. Please define it in your .vimrc.') + return 0 + endif + elseif ! executable(g:patchreview_{a:BinaryName}) + call s:PR_echo('Specified g:patchreview_' . a:BinaryName . ' [' . g:patchreview_{a.BinaryName} . '] is not executable.') + return 0 + else + return 1 + endif +endfunction +"}}} + +function! <SID>PR_GetTempDirLocation(Quiet) "{{{ + if exists('g:patchreview_tmpdir') + if ! isdirectory(g:patchreview_tmpdir) || ! filewritable(g:patchreview_tmpdir) + if ! a:Quiet + call s:PR_echo('Temporary directory specified by g:patchreview_tmpdir [' . g:patchreview_tmpdir . '] is not accessible.') + return 0 + endif + endif + elseif exists("$TMP") && isdirectory($TMP) && filewritable($TMP) + let g:patchreview_tmpdir = $TMP + elseif exists("$TEMP") && isdirectory($TEMP) && filewritable($TEMP) + let g:patchreview_tmpdir = $TEMP + elseif exists("$TMPDIR") && isdirectory($TMPDIR) && filewritable($TMPDIR) + let g:patchreview_tmpdir = $TMPDIR + else + if ! a:Quiet + call s:PR_echo('Could not figure out a temporary directory to use. Please specify g:patchreview_tmpdir in your .vimrc.') + return 0 + endif + endif + let g:patchreview_tmpdir = g:patchreview_tmpdir . '/' + let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '\\', '/', 'g') + let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/+$', '/', '') + if has('win32') + let g:patchreview_tmpdir = substitute(g:patchreview_tmpdir, '/', '\\', 'g') + endif + return 1 +endfunction +"}}} + +function! <SID>PatchReview(...) "{{{ + " VIM 7+ required"{{{ + if version < 700 + call s:PR_echo('This plugin needs VIM 7 or higher') + return + endif +"}}} + + let s:save_shortmess = &shortmess + set shortmess+=aW + call s:PR_wipeMsgBuf() + + " Check passed arguments "{{{ + if a:0 == 0 + call s:PR_echo('PatchReview command needs at least one argument specifying a patchfile path.') + let &shortmess = s:save_shortmess + return + endif + if a:0 >= 1 && a:0 <= 2 + let s:PatchFilePath = expand(a:1, ':p') + if ! filereadable(s:PatchFilePath) + call s:PR_echo('File [' . s:PatchFilePath . '] is not accessible.') + let &shortmess = s:save_shortmess + return + endif + if a:0 == 2 + let s:SrcDirectory = expand(a:2, ':p') + if ! isdirectory(s:SrcDirectory) + call s:PR_echo('[' . s:SrcDirectory . '] is not a directory') + let &shortmess = s:save_shortmess + return + endif + try + exe 'cd ' . s:SrcDirectory + catch /^.*E344.*/ + call s:PR_echo('Could not change to directory [' . s:SrcDirectory . ']') + let &shortmess = s:save_shortmess + return + endtry + endif + else + call s:PR_echo('PatchReview command needs at most two arguments: patchfile path and optional source directory path.') + let &shortmess = s:save_shortmess + return + endif +"}}} + + " Verify that filterdiff and patch are specified or available "{{{ + if ! s:PR_checkBinary('filterdiff') || ! s:PR_checkBinary('patch') + let &shortmess = s:save_shortmess + return + endif + + let s:retval = s:PR_GetTempDirLocation(0) + if ! s:retval + let &shortmess = s:save_shortmess + return + endif +"}}} + + " Requirements met, now execute "{{{ + let s:PatchFilePath = fnamemodify(s:PatchFilePath, ':p') + call s:PR_echo('Patch file : ' . s:PatchFilePath) + call s:PR_echo('Source directory: ' . getcwd()) + call s:PR_echo('------------------') + let s:theFilterDiffCommand = '' . g:patchreview_filterdiff . ' --list -s ' . s:PatchFilePath + let s:theFilesString = system(s:theFilterDiffCommand) + let s:theFilesList = split(s:theFilesString, '[\r\n]') + for s:filewithchangetype in s:theFilesList + if s:filewithchangetype !~ '^[!+-] ' + call s:PR_echo('*** Skipping review generation due to understood change for [' . s:filewithchangetype . ']', 1) + continue + endif + unlet! s:RelativeFilePath + let s:RelativeFilePath = substitute(s:filewithchangetype, '^. ', '', '') + let s:RelativeFilePath = substitute(s:RelativeFilePath, '^[a-z][^\\\/]*[\\\/]' , '' , '') + if s:filewithchangetype =~ '^! ' + let s:msgtype = 'Modification : ' + elseif s:filewithchangetype =~ '^+ ' + let s:msgtype = 'Addition : ' + elseif s:filewithchangetype =~ '^- ' + let s:msgtype = 'Deletion : ' + endif + let s:bufnum = bufnr(s:RelativeFilePath) + if buflisted(s:bufnum) && getbufvar(s:bufnum, '&mod') + call s:PR_echo('Old buffer for file [' . s:RelativeFilePath . '] exists in modified state. Skipping review.', 1) + continue + endif + let s:tmpname = substitute(s:RelativeFilePath, '/', '_', 'g') + let s:tmpname = substitute(s:tmpname, '\\', '_', 'g') + let s:tmpname = g:patchreview_tmpdir . 'PatchReview.' . s:tmpname . '.' . strftime('%Y%m%d%H%M%S') + if has('win32') + let s:tmpname = substitute(s:tmpname, '/', '\\', 'g') + endif + if ! exists('s:patchreview_tmpfiles') + let s:patchreview_tmpfiles = [] + endif + let s:patchreview_tmpfiles = s:patchreview_tmpfiles + [s:tmpname] + + let s:filterdiffcmd = '!' . g:patchreview_filterdiff . ' -i ' . s:RelativeFilePath . ' ' . s:PatchFilePath . ' > ' . s:tmpname + silent! exe s:filterdiffcmd + if s:filewithchangetype =~ '^+ ' + if has('win32') + let s:inputfile = 'nul' + else + let s:inputfile = '/dev/null' + endif + else + let s:inputfile = expand(s:RelativeFilePath, ':p') + endif + silent exe '!' . g:patchreview_patch . ' -o ' . s:tmpname . '.file ' . s:inputfile . ' < ' . s:tmpname + let s:origtabpagenr = tabpagenr() + silent! exe 'tabedit ' . s:RelativeFilePath + silent! exe 'vert diffsplit ' . s:tmpname . '.file' + if filereadable(s:tmpname . '.file.rej') + silent! exe 'topleft 5split ' . s:tmpname . '.file.rej' + call s:PR_echo(s:msgtype . '*** REJECTED *** ' . s:RelativeFilePath, 1) + else + call s:PR_echo(s:msgtype . ' ' . s:RelativeFilePath, 1) + endif + silent! exe 'tabn ' . s:origtabpagenr + endfor + call s:PR_echo('-----') + call s:PR_echo('Done.') + let &shortmess = s:save_shortmess +"}}} +endfunction +"}}} + +function! <SID>PatchReviewCleanup() "{{{ + let s:retval = s:PR_GetTempDirLocation(1) + if s:retval && exists('g:patchreview_tmpdir') && isdirectory(g:patchreview_tmpdir) && filewritable(g:patchreview_tmpdir) + let s:zefilestr = globpath(g:patchreview_tmpdir, 'PatchReview.*') + let s:theFilesList = split(s:zefilestr, '\m[\r\n]\+') + for s:thefile in s:theFilesList + call delete(s:thefile) + endfor + endif +endfunction +"}}} + +" Commands "{{{ +"============================================================================ +" :PatchReview +command! -nargs=* -complete=file PatchReview call s:PatchReview (<f-args>) + + +" :PatchReviewCleanup +command! -nargs=0 PatchReviewCleanup call s:PatchReviewCleanup () +"}}} +"}}} + +" vim: textwidth=78 nowrap tabstop=2 shiftwidth=2 softtabstop=2 expandtab +" vim: filetype=vim encoding=latin1 fileformat=unix foldlevel=0 foldmethod=marker +"}}}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/ReadMe.html Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,146 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <title>Mercurial for Windows</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8" > + <style type="text/css"> + <!-- + .indented + { + padding-left: 10pt; + } + --> + </style> + </head> + + <body> + <h1>Mercurial version 0.9 for Windows</h1> + + <p>Welcome to Mercurial for Windows!</p> + + <p>Mercurial is a command-line application. You must run it from + the Windows command prompt (or if you're hard core, a <a + href="http://www.mingw.org/">MinGW</a> shell).</p> + + <p><div class="indented"><i>Note: the standard <a + href="http://www.mingw.org/">MinGW</a> msys startup script uses + rxvt which has problems setting up standard input and output. + Running bash directly works correctly.</i></div> + + <p>For documentation, please visit the <a + href="http://www.selenic.com/mercurial">Mercurial web site</a>.</p> + + <p>By default, Mercurial installs to <tt>C:\Mercurial</tt>. The + Mercurial command is called <tt>hg.exe</tt>. To run this + command, the install directory must be in your search path.</p> + + <h2>Setting your search path temporarily</h2> + + <p>To set your search path temporarily, type the following into a + command prompt window:</p> + + <pre> +set PATH=C:\Mercurial;%PATH% +</pre> + + <h2>Setting your search path permanently</h2> + + <p>To set your search path permanently, perform the following + steps. These instructions are for Windows NT, 2000 and XP.</p> + + <ol> + <li>Open the Control Panel. Under Windows XP, select the + "Classic View".</li> + + <li>Double-click on the "System" control panel.</li> + + <li>Click on the "Advanced" tab.</li> + + <li>Click on "Environment Variables". You'll find this near the + bottom of the window.</li> + + <li>Under "System variables", you will see "Path". Double-click + it.</li> + + <li>Edit "Variable value". Each path element is separated by a + semicolon (";") character. Append a semicolon to the end of the + list, followed by the path where you installed Mercurial + (e.g. <tt>C:\Mercurial</tt>).</li> + + <li>Click on the various "OK" buttons until you've completely + exited from the System control panel.</li> + + <li>Log out and log back in, or restart your system.</li> + + <li>The next time you run the Windows command prompt, you will be + able to run the <tt>hg</tt> command without any special + help.</li> + </ol> + + <h1>Testing Mercurial after you've installed it</h1> + + <p>The easiest way to check that Mercurial is installed properly is to + just type the following at the command prompt:</p> + + <pre> +hg +</pre> + + <p>This command should print a useful help message. If it does, + other Mercurial commands should work fine for you.</p> + + <h1>Configuration notes</h1> + <p>The default editor for commit messages is 'vi'. You can set the EDITOR + (or HGEDITOR) environment variable to specify your preference or set it in + mercurial.ini:</p> + <pre> +[ui] +editor = whatever +</pre> + + + <h1>Reporting problems</h1> + + <p>Before you report any problems, please consult the <a + href="http://www.selenic.com/mercurial">Mercurial web site</a> and + see if your question is already in our list of <a + href="http://www.selenic.com/mercurial/wiki/index.cgi/FAQ">Frequently + Answered Questions</a> (the "FAQ"). + + <p>If you cannot find an answer to your question, please feel + free to send mail to the Mercurial mailing list, at <a + href="mailto:mercurial@selenic.com">mercurial@selenic.com</a>. + <b>Remember</b>, the more useful information you include in your + report, the easier it will be for us to help you!</p> + + <p>If you are IRC-savvy, that's usually the fastest way to get + help. Go to <tt>#mercurial</tt> on + <tt>irc.freenode.net</tt>.</p> + + <h1>Author and copyright information</h1> + + <p>Mercurial was written by <a href="http://www.selenic.com">Matt + Mackall</a>, and is maintained by Matt and a team of + volunteers.</p> + + <p>The Windows installer was written by <a + href="http://www.serpentine.com/blog">Bryan + O'Sullivan</a>.</p> + + <p>Mercurial is Copyright 2005, 2006 Matt Mackall and others. See the + <tt>Contributors.txt</tt> file for a list of contributors.</p> + + <p>Mercurial is free software; you can redistribute it and/or + modify it under the terms of the <a + href="http://www.gnu.org/copyleft/gpl.html">GNU General Public + License</a> as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version.</p> + + <p>Mercurial is distributed in the hope that it will be useful, + but <b>without any warranty</b>; without even the implied + warranty of <b>merchantability</b> or <b>fitness for a + particular purpose</b>. See the GNU General Public License for + more details.</p> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/mercurial.ini Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,36 @@ +; System-wide Mercurial config file. To override these settings on a +; per-user basis, please edit the following file instead, where +; USERNAME is your Windows user name: +; C:\Documents and Settings\USERNAME\Mercurial.ini + +; By default, we try to encode and decode all files that do not +; contain ASCII NUL characters. What this means is that we try to set +; line endings to Windows style on update, and to Unix style on +; commit. This lets us cooperate with Linux and Unix users, so +; everybody sees files with their native line endings. + +[extensions] +; The win32text extension is available and installed by default. It +; provides built-in Python hooks to perform line ending conversions. +; This is normally much faster than running an external program. +hgext.win32text = + + +[encode] +; Encode files that don't contain NUL characters. +** = cleverencode: + +; Alternatively, you can explicitly specify each file extension that +; you want encoded (any you omit will be left untouched), like this: + +; *.txt = dumbencode: + + +[decode] +; Decode files that don't contain NUL characters. +** = cleverdecode: + +; Alternatively, you can explicitly specify each file extension that +; you want decoded (any you omit will be left untouched), like this: + +; **.txt = dumbdecode:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/mercurial.iss Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,57 @@ +; Script generated by the Inno Setup Script Wizard. +; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! + +[Setup] +AppCopyright=Copyright 2005, 2006 Matt Mackall and others +AppName=Mercurial +AppVerName=Mercurial version 0.9 +InfoAfterFile=contrib/win32/postinstall.txt +LicenseFile=COPYING +ShowLanguageDialog=yes +AppPublisher=Matt Mackall and others +AppPublisherURL=http://www.selenic.com/mercurial +AppSupportURL=http://www.selenic.com/mercurial +AppUpdatesURL=http://www.selenic.com/mercurial +AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3} +AppContact=mercurial@selenic.com +OutputBaseFilename=Mercurial-0.9 +DefaultDirName={sd}\Mercurial +SourceDir=C:\hg\hg-release +VersionInfoVersion=0.9 +VersionInfoDescription=Mercurial distributed SCM +VersionInfoCopyright=Copyright 2005, 2006 Matt Mackall and others +VersionInfoCompany=Matt Mackall and others +InternalCompressLevel=max +SolidCompression=true +SetupIconFile=contrib\favicon.ico +AllowNoIcons=true +DefaultGroupName=Mercurial + +[Files] +Source: ..\..\msys\1.0\bin\patch.exe; DestDir: {app} +Source: contrib\mercurial.el; DestDir: {app}/Contrib +Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme +Source: contrib\win32\mercurial.ini; DestDir: {app}; DestName: Mercurial.ini; Flags: confirmoverwrite +Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt +Source: dist\hg.exe; DestDir: {app} +Source: dist\library.zip; DestDir: {app} +Source: dist\mfc71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt +Source: dist\msvcr71.dll; DestDir: {sys}; Flags: sharedfile uninsnosharedfileprompt +Source: dist\w9xpopen.exe; DestDir: {app} +Source: doc\*.txt; DestDir: {app}\Docs +Source: templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs +Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt +Source: COPYING; DestDir: {app}; DestName: Copying.txt +Source: comparison.txt; DestDir: {app}\Docs; DestName: Comparison.txt +Source: notes.txt; DestDir: {app}\Docs; DestName: DesignNotes.txt + +[INI] +Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: http://www.selenic.com/mercurial/ + +[UninstallDelete] +Type: files; Name: {app}\Mercurial.url + +[Icons] +Name: {group}\Uninstall Mercurial; Filename: {uninstallexe} +Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.txt +Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/postinstall.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,112 @@ +Welcome to Mercurial for Windows! +--------------------------------- + +For configuration and usage directions, please read the ReadMe.html +file that comes with this package. + +Release Notes +------------- + +2006-05-10 v0.9 + +* Major changes between Mercurial 0.8.1 and 0.9: + + - The repository file format has been improved. + - This has resulted in an average 40% reduction in disk space usage. + - The new format (called RevlogNG) is now the default. + - Mercurial works perfectly with both the old and new repository + file formats. It can transfer changes transparently between + repositories of either format. + - To use the new repository format, simply use `hg clone --pull` to + clone an existing repository. + - Note: Versions 0.8.1 and earlier of Mercurial cannot read + RevlogNG repositories directly, but they can `clone`, `pull` + from, and `push` to servers that are serving RevlogNG + repositories. + - Memory usage has been improved by over 50% for many common operations. + - Substantial performance improvements on large repositories. + - New commands: + - 'archive' - generate a directory tree snapshot, tarball, or zip + file of a revision + - Deprecated commands: + - 'addremove' - replaced by 'add' and 'remove --after' + - 'forget' - replaced by 'revert' + - 'undo' - replaced by 'rollback' + - New extensions: + - Bugzilla integration hook + - Email notification hook + - Nested repositories are now supported. Mercurial will not recurse + into a subdirectory that contains a '.hg' directory. It is treated + as a separate repository. + - The standalone web server, 'hg serve', is now threaded, so it can + talk to multiple clients at a time. + - The web server can now display a "message of the day". + - Support added for hooks written in Python. + - Many improvements and clarifications to built-in help. + + +2006-04-07 v0.8.1 + +* Major changes from 0.8 to 0.8.1: + + - new extensions: + mq (manage a queue of patches, like quilt only better) + email (send changes as series of email patches) + - new command: merge (replaces "update -m") + - improved commands: log (--limit option added), pull/push ("-r" works + on specific revisions), revert (rewritten, much better) + - comprehensive hook support + - output templating added, supporting e.g. GNU changelog style + - Windows, Mac OS X: prebuilt binary packages, better support + - many reliability, performance, and memory usage improvements + + +2006-01-29 v0.8 + +* Upgrade notes: + + - diff and status command are now repo-wide by default + (use 'hg diff .' for the old behavior) + - GPG signing is now done with the gpg extension + - the --text option for commit, rawcommit, and tag has been removed + - the copy/rename --parents option has been removed + +* Major changes from 0.7 to 0.8: + + - faster status, diff, and commit + - reduced memory usage for push and pull + - improved extension API + - new bisect, gpg, hgk, and win32text extensions + - short URLs, binary file handling, and optional gitweb skin for hgweb + - numerous new command options including log --keyword and pull --rev + - improved hooks and file filtering + + +2005-09-21 v0.7 with modifications + +* New INI files have been added to control Mercurial's behaviour: + + System-wide - C:\Mercurial\Mercurial.ini + Per-user - C:\Documents and Settings\USERNAME\Mercurial.ini + + A default version of the system-wide INI file is installed with + Mercurial. No per-user INI file is installed, but it will be + honoured if you create one. + +* Windows line endings are now handled automatically and correctly by + the update and commit commands. See the INI file for how to + customise this behaviour. + +* NOTE: Much of the rest of the Mercurial code does not handle Windows + line endings properly. Accordingly, the output of the diff command, + for example, will appear huge until I fix this. + +* Packaged text files now have correct Windows line endings. + + +2005-09-21 v0.7 with modifications + +* This is the first standalone release of Mercurial for Windows. + +* I believe it to be mostly functional, with one exception: there is + no support yet for DOS <-> Unix line ending conversion.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/win32/win32-build.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,43 @@ +The standalone Windows installer for Mercurial is built in a somewhat +jury-rigged fashion. + +It has the following prerequisites, at least as I build it: + + Python for Windows + http://www.python.org/ftp/python/2.4.1/python-2.4.1.msi + + MinGW + http://www.mingw.org/ + + Python for Windows Extensions + http://sourceforge.net/projects/pywin32/ + + mfc71.dll (just download, don't install) + http://starship.python.net/crew/mhammond/win32/ + + The py2exe distutils extension + http://sourceforge.net/projects/py2exe/ + + Inno Setup + http://www.jrsoftware.org/isinfo.php + + ISTool + http://www.istool.org/default.aspx/ + +And, of course, Mercurial itself. + +Once you have all this installed and built, clone a copy of the +Mercurial repository you want to package, and name the repo +C:\hg\hg-release. + +In a shell, build a standalone copy of the hg.exe program: + + python setup.py build -c mingw32 py2exe -b 1 + +Copy mfc71.dll into the dist directory that just got created. + +Run ISTool, and open the C:\hg\hg-release\contrib\win32\mercurial.iss +file. + +In ISTool, type Ctrl-F9 to compile the installer file. The actual +installer will be in the C:\hg\hg-release\Output directory.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/zsh_completion Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,425 @@ +#compdef hg + +# Zsh completion script for mercurial. Rename this file to _hg and copy +# it into your zsh function path (/usr/share/zsh/site-functions for +# instance) +# +# Copyright (C) 2005 Steve Borho +# +# This 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. +# + +local curcontext="$curcontext" state line +typeset -A opt_args +local subcmds repos tags newFiles addedFiles includeExclude + +tags=($(hg tags 2> /dev/null | sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')) +subcmds=($(hg -v help | sed -e '1,/^list of commands:/d' \ + -e '/^global options:/,$d' -e '/^ [^ ]/!d; s/[,:].*//g;')) + +# A lot of commands have these arguments +includeExclude=( + '*-I-[include names matching the given patterns]:dir:_files -W $(hg root) -/' + '*--include-[include names matching the given patterns]:dir:_files -W $(hg root) -/' + '*-X-[exclude names matching the given patterns]:dir:_files -W $(hg root) -/' + '*--exclude-[exclude names matching the given patterns]:dir:_files -W $(hg root) -/') + +if [[ $service == "hg" ]]; then + _arguments -C -A "-*" \ + '(--repository)-R[repository root directory]:root:_files -/' \ + '(-R)--repository[repository root directory]:root:_files -/' \ + '--cwd[change working directory]:new working directory:_files -/' \ + '(--noninteractive)-y[do not prompt, assume yes for any required answers]' \ + '(-y)--noninteractive[do not prompt, assume yes for any required answers]' \ + '(--verbose)-v[enable additional output]' \ + '(-v)--verbose[enable additional output]' \ + '(--quiet)-q[suppress output]' \ + '(-q)--quiet[suppress output]' \ + '(--help)-h[display help and exit]' \ + '(-h)--help[display help and exit]' \ + '--debug[debug mode]' \ + '--debugger[start debugger]' \ + '--traceback[print traceback on exception]' \ + '--time[time how long the command takes]' \ + '--profile[profile]' \ + '--version[output version information and exit]' \ + '*::command:->subcmd' && return 0 + + if (( CURRENT == 1 )); then + _wanted commands expl 'hg command' compadd -a subcmds + return + fi + service="$words[1]" + curcontext="${curcontext%:*}=$service:" +fi + +case $service in + (add) + newFiles=(${(ps:\0:)"$(hg status -0un .)"}) + _arguments $includeExclude \ + '*:file:->unknown' + _wanted files expl 'unknown files' compadd -a newFiles + ;; + + (addremove) + _arguments $includeExclude \ + '*:directories:_files -/' # assume they want to add/remove a dir + ;; + + (forget) + addedFiles=(${(ps:\0:)"$(hg status -0an .)"}) + _arguments $includeExclude \ + '*:file:->added' + _wanted files expl 'newly added files' compadd -a addedFiles + ;; + + (remove|rm) + _arguments $includeExclude \ + '*:file:_files' + ;; + + (copy|cp) + _arguments $includeExclude \ + '(--after)-A[record a copy that has already occurred]' \ + '(-A)--after[record a copy that has already occurred]' \ + '(--force)-f[forcibly copy over an existing managed file]' \ + '(-f)--force[forcibly copy over an existing managed file]' \ + '(--parents)-p[append source path to dest]' \ + '(-p)--parents[append source path to dest]' \ + '*:files:_files' + ;; + + (rename|mv) + if (( CURRENT == 2 )); then + _arguments $includeExclude \ + '(--after)-A[record a rename that has already occurred]' \ + '(-A)--after[record a rename that has already occurred]' \ + '(--force)-f[replace destination if it exists]' \ + '(-F)--force[replace destination if it exists]' \ + '(--parents)-p[append source path to dest]' \ + '(-p)--parents[append source path to dest]' \ + '*:files:_files' + else + _arguments '*:destination:_files' + fi + ;; + + (diff) + _arguments $includeExclude \ + '*-r[revision]:revision:($tags)' \ + '*--rev[revision]:revision:($tags)' \ + '(--text)-a[treat all files as text]' \ + '(-a)--text[treat all files as text]' \ + '*:file:_files' + ;; + + (status|st) + _arguments $includeExclude \ + '(--no-status)-n[hide status prefix]' \ + '(-n)--no-status[hide status prefix]' \ + '(--print0)-0[end filenames with NUL, for use with xargs]' \ + '(-0)--print0[end filenames with NUL, for use with xargs]' \ + '(--modified)-m[show only modified files]' \ + '(-m)--modified[show only modified files]' \ + '(--added)-a[show only added files]' \ + '(-a)--added[show only added files]' \ + '(--removed)-r[show only removed files]' \ + '(-r)--removed[show only removed files]' \ + '(--unknown)-u[show only unknown files]' \ + '(-u)--unknown[show only unknown files]' \ + '*:search pattern, then files:_files' + ;; + + (revert) + addedFiles=(${(ps:\0:)"$(hg status -0amrn .)"}) + _arguments \ + '(--rev)-r[revision to revert to]:revision:($tags)' \ + '(-r)--rev[revision to revert to]:revision:($tags)' \ + '(--nonrecursive)-n[do not recurse into subdirectories]' \ + '(-n)--nonrecursive[do not recurse into subdirectories]' \ + '*:file:->modified' + _wanted files expl 'mofified files' compadd -a addedFiles + ;; + + (commit|ci) + addedFiles=(${(ps:\0:)"$(hg status -0amrn .)"}) + _arguments $includeExclude \ + '(--addremove)-A[run addremove during commit]' \ + '(-A)--addremove[run addremove during commit]' \ + '(--message)-m[use <txt> as commit message]:string:' \ + '(-m)--message[use <txt> as commit message]:string:' \ + '(--logfile)-l[read commit message from <file>]:.log file:_file -g \*.txt' \ + '(-l)--logfile[read commit message from <file>]:.log file:_file -g \*.txt' \ + '(--date)-d[record datecode as commit date]:date code:' \ + '(-d)--date[record datecode as commit date]:date code:' \ + '(--user)-u[record user as commiter]:user:' \ + '(-u)--user[record user as commiter]:user:' \ + '*:file:->modified' + _wanted files expl 'mofified files' compadd -a addedFiles + ;; + + (cat) + _arguments $includeExclude \ + '(--output)-o[print output to file with formatted name]:filespec:' \ + '(-o)--output[print output to file with formatted name]:filespec:' \ + '(--rev)-r[revision]:revision:($tags)' \ + '(-r)--rev[revision]:revision:($tags)' \ + '*:file:_files' + ;; + + (annotate) + _arguments $includeExclude \ + '(--rev)-r[annotate the specified revision]:revision:($tags)' \ + '(-r)--rev[annotate the specified revision]:revision:($tags)' \ + '(--text)-a[treat all files as text]' \ + '(-a)--text[treat all files as text]' \ + '(--user)-u[list the author]' \ + '(-u)--user[list the author]' \ + '(--changeset)-c[list the changeset]' \ + '(-c)--changeset[list the changeset]' \ + '(--number)-n[list the revision number (default)]' \ + '(-n)--number[list the revision number (default)]' \ + '*:files:_files' + ;; + + (grep) + _arguments $includeExclude \ + '*-r[search in given revision range]:revision:($tags)' \ + '*--rev[search in given revision range]:revision:($tags)' \ + '--all[print all revisions with matches]' \ + '(-print0)-0[end filenames with NUL, for use with xargs]' \ + '(-0)--print0[end filenames with NUL, for use with xargs]' \ + '(--ignore-case)-i[ignore case when matching]' \ + '(-i)--ignore-case[ignore case when matching]' \ + '(--files-with-matches)-l[print names of files and revs that match]' \ + '(-l)--files-with-matches[print names of files and revs that match]' \ + '(--line-number)-n[print matching line numbers]' \ + '(-n)--line-number[print matching line numbers]' \ + '(--user)-u[print user who committed change]' \ + '(-u)--user[print user who committed change]' \ + '*:search pattern:' + ;; + + (locate) + _arguments $includeExclude \ + '(--rev)-r[search repository as it stood at revision]:revision:($tags)' \ + '(-r)--rev[search repository as it stood at revision]:revision:($tags)' \ + '(--print0)-0[end filenames with NUL, for use with xargs]' \ + '(-0)--print0[end filenames with NUL, for use with xargs]' \ + '(--fullpath)-f[print complete paths]' \ + '(-f)--fullpath[print complete paths]' \ + '*:search pattern:' + ;; + + (log|history) + _arguments $includeExclude \ + '*-r[show the specified revision or range]:revision:($tags)' \ + '*--rev[show the specified revision or range]:revision:($tags)' \ + '(--no-merges -M --only-merges)-m[show only merge revisions]' \ + '(--no-merges -M -m)--only-merges[show only merge revisions]' \ + '(--only-merges -m --no-merges)-M[do not show merge revisions]' \ + '(--only-merges -m -M)--no-merges[do not show merge revisions]' \ + '(--keyword)-k[search for a keyword]:keyword:' \ + '(-k)--keyword[search for a keyword]:keyword:' \ + '(--branch)-b[show branches]' \ + '(-b)--branch[show branches]' \ + '(--patch)-p[show patch]' \ + '(-p)--patch[show patch]' \ + '*:file:_files' + ;; + + (update|checkout|co) + _arguments \ + '(--branch)-b[checkout the head of a specific branch]' \ + '(-b)--branch[checkout the head of a specific branch]' \ + '(-C --clean --merge)-m[allow merging of branches]' \ + '(-C --clean -m)--merge[allow merging of branches]' \ + '(-m --merge --clean)-C[overwrite locally modified files]' \ + '(-m --merge -C)--clean[overwrite locally modified files]' \ + '*:revision or tag:($tags)' + ;; + + (tag) + _arguments \ + '(--local)-l[make the tag local]' \ + '(-l)--local[make the tag local]' \ + '(--message)-m[message for tag commit log entry]:string:' \ + '(-m)--message[message for tag commit log entry]:string:' \ + '(--date)-d[record datecode as commit date]:date code:' \ + '(-d)--date[record datecode as commit date]:date code:' \ + '(--user)-u[record user as commiter]:user:' \ + '(-u)--user[record user as commiter]:user:' \ + '*:name, then revision:($tags)' + ;; + + (clone) + if (( CURRENT == 2 )); then + repos=( $(hg paths | sed -e 's/^.*= //') ) + _arguments \ + '(--no-update)-U[do not update the new working directory]' \ + '(-U)--no-update[do not update the new working directory]' \ + '(--ssh)-e[specify ssh command to use]:string:' \ + '(-e)--ssh[specify ssh command to use]:string:' \ + '--pull[use pull protocol to copy metadata]' \ + '--remotecmd[specify hg command to run on the remote side]:remote hg:' \ + '*:local repo:_files -/' + _wanted source expl 'source repository' compadd -a repos + elif (( CURRENT == 3 )); then + _arguments '*:dest repo:_files -/' + fi + ;; + + (rawcommit) + _arguments \ + '(--parent)-p[parent revision]:revision:($tags)' \ + '(-p)--parent[parent revision]:revision:($tags)' \ + '(--date)-d[record datecode as commit date]:date code:' \ + '(-d)--date[record datecode as commit date]:date code:' \ + '(--user)-u[record user as commiter]:user:' \ + '(-u)--user[record user as commiter]:user:' \ + '(--message)-m[use <txt> as commit message]:string:' \ + '(-m)--message[use <txt> as commit message]:string:' \ + '(--logfile)-l[read commit message from <file>]:.log file:_file -g \*.txt' \ + '(-l)--logfile[read commit message from <file>]:.log file:_file -g \*.txt' \ + '(--files)-F[file list]:file list:_files' \ + '(-F)--files[file list]:file list:_files' \ + '*:files to commit:_files' + ;; + + (bundle) + if (( CURRENT == 2 )); then + _arguments '*:changegroup file:_files -g \*.hg' + elif (( CURRENT == 3 )); then + _arguments '*:other repo:_files -/' + fi + ;; + + (unbundle) + _arguments '*:changegroup .hg file:_files -g \*.hg' + ;; + + (incoming) + _arguments \ + '(--patch)-p[show patch]' \ + '(-p)--patch[show patch]' \ + '(--no-merges)-M[do not show merge revisions]' \ + '(-M)--no-merges[do not show merge revisions]' \ + '(--newest-first)-n[show newest record first]' \ + '(-n)--newest-first[show newest record first]' \ + '*:mercurial repository:_files -/' + ;; + + (import|patch) + _arguments \ + '(--strip)-p[directory strip option for patch (default: 1)]:count:' \ + '(-p)--strip[directory strip option for patch (default: 1)]:count:' \ + '(--force)-f[skip check for outstanding uncommitted changes]' \ + '(-f)--force[skip check for outstanding uncommitted changes]' \ + '(--base)-b[base directory to read patches from]:file:_files -W $(hg root) -/' \ + '(-b)--base[base directory to read patches from]:file:_files -W $(hg root) -/' \ + '*:patch file:_files' + ;; + + (pull) + repos=( $(hg paths | sed -e 's/^.*= //') ) + _arguments \ + '(--update)-u[update working directory to tip after pull]' \ + '(-u)--update[update working directory to tip after pull]' \ + '(--ssh)-e[specify ssh command to use]:ssh command:' \ + '(-e)--ssh[specify ssh command to use]:ssh command:' \ + '--remotecmd[specify hg command to run on the remote side]:remote hg:' \ + '*:local repo:_files -/' + _wanted source expl 'source repository' compadd -a repos + ;; + + (outgoing) + _arguments \ + '(--patch)-p[show patch]' \ + '(-p)--patch[show patch]' \ + '(--no-merges)-M[do not show merge revisions]' \ + '(-M)--no-merges[do not show merge revisions]' \ + '(--newest-first)-n[show newest record first]' \ + '(-n)--newest-first[show newest record first]' \ + '*:local repo:_files -/' + _wanted source expl 'source repository' compadd -a repos + ;; + + (export) + _arguments \ + '(--outout)-o[print output to file with formatted name]:filespec:' \ + '(-o)--output[print output to file with formatted name]:filespec:' \ + '(--text)-a[treat all files as text]' \ + '(-a)--text[treat all files as text]' \ + '*:revision:->revs' + _wanted revs expl 'revision or tag' compadd -a tags + ;; + + (push) + repos=( $(hg paths | sed -e 's/^.*= //') ) + _arguments \ + '(--force)-f[force push]' \ + '(-f)--force[force push]' \ + '(--ssh)-e[specify ssh command to use]:ssh command:' \ + '(-e)--ssh[specify ssh command to use]:ssh command:' \ + '--remotecmd[specify hg command to run on the remote side]:remote hg:' \ + '*:local repo:_files -/' + _wanted source expl 'source repository' compadd -a repos + ;; + + (serve) + _arguments \ + '(--accesslog)-A[name of access log file]:log file:_files' \ + '(-A)--accesslog[name of access log file]:log file:_files' \ + '(--errorlog)-E[name of error log file]:log file:_files' \ + '(-E)--errorlog[name of error log file]:log file:_files' \ + '(--port)-p[listen port]:listen port:' \ + '(-p)--port[listen port]:listen port:' \ + '(--address)-a[interface address]:interface address:' \ + '(-a)--address[interface address]:interface address:' \ + '(--name)-n[name to show in web pages]:repository name:' \ + '(-n)--name[name to show in web pages]:repository name:' \ + '(--templates)-t[web template directory]:template dir:_files -/' \ + '(-t)--templates[web template directory]:template dir:_files -/' \ + '--style[web template style]:style' \ + '--stdio[for remote clients]' \ + '(--ipv6)-6[use IPv6 in addition to IPv4]' \ + '(-6)--ipv6[use IPv6 in addition to IPv4]' + ;; + + (help) + _wanted commands expl 'hg command' compadd -a subcmds + ;; + + (heads) + _arguments \ + '(--branches)-b[find branch info]' \ + '(-b)--branches[find branch info]' + ;; + + (paths) + _arguments '*:symbolic name:(default default-push)' + ;; + + (init) + _arguments '*:new repo directory:_files -/' + ;; + + (manifest) + _arguments '*:revision:($tags)' + ;; + + (parents) + _arguments '*:revision:($tags)' + ;; + + (identify|recover|root|undo|view|verify|version|ct|tags) + # no arguments for these commands + ;; + + (*) + _message "unknown hg command completion: $service" + ;; +esac
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/Makefile Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,37 @@ +SOURCES=$(wildcard *.[0-9].txt) +MAN=$(SOURCES:%.txt=%) +HTML=$(SOURCES:%.txt=%.html) +PREFIX=/usr/local +MANDIR=$(PREFIX)/man +INSTALL=install -c + +all: man html + +man: $(MAN) + +html: $(HTML) + +hg.1.txt: hg.1.gendoc.txt + touch hg.1.txt + +hg.1.gendoc.txt: ../mercurial/commands.py + python gendoc.py > $@ + +%: %.xml + xmlto man $*.xml + +%.xml: %.txt + asciidoc -d manpage -b docbook $*.txt + +%.html: %.txt + asciidoc -b html4 $*.txt || asciidoc -b html $*.txt + +install: man + for i in $(MAN) ; do \ + subdir=`echo $$i | sed -n 's/.\+\(\.[0-9]\)$$/man\1/p'` ; \ + mkdir -p $(MANDIR)/$$subdir ; \ + $(INSTALL) $$i $(MANDIR)/$$subdir ; \ + done + +clean: + $(RM) $(MAN) $(MAN:%=%.xml) $(MAN:%=%.html) *.[0-9].gendoc.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/README Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +Mercurial's documentation is currently kept in ASCIIDOC format, which +is a simple plain text format that's easy to read and edit. It's also +convertible to a variety of other formats including standard UNIX man +page format and HTML. + +To do this, you'll need to install ASCIIDOC: + + http://www.methods.co.nz/asciidoc/ + +To generate the man page: + + asciidoc -d manpage -b docbook hg.1.txt + xmlto man hg.1.xml + +To display: + + groff -mandoc -Tascii hg.1 | more + +To create the html page (without stylesheets): + + asciidoc -b html hg.1.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/gendoc.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,92 @@ +import sys, textwrap +# import from the live mercurial repo +sys.path.insert(0, "..") +from mercurial.commands import table, globalopts +from mercurial.i18n import gettext as _ + +def get_desc(docstr): + if not docstr: + return "", "" + # sanitize + docstr = docstr.strip("\n") + docstr = docstr.rstrip() + shortdesc = docstr.splitlines()[0].strip() + + i = docstr.find("\n") + if i != -1: + desc = docstr[i+2:] + else: + desc = " %s" % shortdesc + return (shortdesc, desc) + +def get_opts(opts): + for shortopt, longopt, default, desc in opts: + allopts = [] + if shortopt: + allopts.append("-%s" % shortopt) + if longopt: + allopts.append("--%s" % longopt) + desc += default and _(" (default: %s)") % default or "" + yield(", ".join(allopts), desc) + +def get_cmd(cmd): + d = {} + attr = table[cmd] + cmds = cmd.lstrip("^").split("|") + + d['synopsis'] = attr[2] + d['cmd'] = cmds[0] + d['aliases'] = cmd.split("|")[1:] + d['desc'] = get_desc(attr[0].__doc__) + d['opts'] = list(get_opts(attr[1])) + return d + + +def show_doc(ui): + def bold(s, text=""): + ui.write("%s\n%s\n%s\n" % (s, "="*len(s), text)) + def underlined(s, text=""): + ui.write("%s\n%s\n%s\n" % (s, "-"*len(s), text)) + + # print options + underlined(_("OPTIONS")) + for optstr, desc in get_opts(globalopts): + ui.write("%s::\n %s\n\n" % (optstr, desc)) + + # print cmds + underlined(_("COMMANDS")) + h = {} + for c, attr in table.items(): + f = c.split("|")[0] + f = f.lstrip("^") + h[f] = c + cmds = h.keys() + cmds.sort() + + for f in cmds: + if f.startswith("debug"): continue + d = get_cmd(h[f]) + # synopsis + ui.write("%s::\n" % d['synopsis'].replace("hg ","", 1)) + # description + ui.write("%s\n\n" % d['desc'][1]) + # options + opt_output = list(d['opts']) + if opt_output: + opts_len = max([len(line[0]) for line in opt_output]) + ui.write(_(" options:\n")) + for optstr, desc in opt_output: + if desc: + s = "%-*s %s" % (opts_len, optstr, desc) + else: + s = optstr + s = textwrap.fill(s, initial_indent=4 * " ", + subsequent_indent=(6 + opts_len) * " ") + ui.write("%s\n" % s) + ui.write("\n") + # aliases + if d['aliases']: + ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases'])) + +if __name__ == "__main__": + show_doc(sys.stdout)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/hg.1.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,221 @@ +HG(1) +===== +Matt Mackall <mpm@selenic.com> + +NAME +---- +hg - Mercurial source code management system + +SYNOPSIS +-------- +'hg' [-v -d -q -y] <command> [command options] [files] + +DESCRIPTION +----------- +The hg(1) command provides a command line interface to the Mercurial system. + +COMMAND ELEMENTS +---------------- + +files ...:: + indicates one or more filename or relative path filenames; see + "FILE NAME PATTERNS" for information on pattern matching + +path:: + indicates a path on the local machine + +revision:: + indicates a changeset which can be specified as a changeset revision + number, a tag, or a unique substring of the changeset hash value + +repository path:: + either the pathname of a local repository or the URI of a remote + repository. There are two available URI protocols, http:// which is + fast and the old-http:// protocol which is much slower but does not + require a special server on the web host. + + +include::hg.1.gendoc.txt[] + +FILE NAME PATTERNS +------------------ + + Mercurial accepts several notations for identifying one or more + files at a time. + + By default, Mercurial treats filenames as shell-style extended + glob patterns. + + Alternate pattern notations must be specified explicitly. + + To use a plain path name without any pattern matching, start a + name with "path:". These path names must match completely, from + the root of the current repository. + + To use an extended glob, start a name with "glob:". Globs are + rooted at the current directory; a glob such as "*.c" will match + files ending in ".c" in the current directory only. + + The supported glob syntax extensions are "**" to match any string + across path separators, and "{a,b}" to mean "a or b". + + To use a Perl/Python regular expression, start a name with "re:". + Regexp pattern matching is anchored at the root of the repository. + + Plain examples: + + path:foo/bar a name bar in a directory named foo in the root of + the repository + path:path:name a file or directory named "path:name" + + Glob examples: + + glob:*.c any name ending in ".c" in the current directory + *.c any name ending in ".c" in the current directory + **.c any name ending in ".c" in the current directory, or + any subdirectory + foo/*.c any name ending in ".c" in the directory foo + foo/**.c any name ending in ".c" in the directory foo, or any + subdirectory + + Regexp examples: + + re:.*\.c$ any name ending in ".c", anywhere in the repository + + +SPECIFYING SINGLE REVISIONS +--------------------------- + + Mercurial accepts several notations for identifying individual + revisions. + + A plain integer is treated as a revision number. Negative + integers are treated as offsets from the tip, with -1 denoting the + tip. + + A 40-digit hexadecimal string is treated as a unique revision + identifier. + + A hexadecimal string less than 40 characters long is treated as a + unique revision identifier, and referred to as a short-form + identifier. A short-form identifier is only valid if it is the + prefix of one full-length identifier. + + Any other string is treated as a tag name, which is a symbolic + name associated with a revision identifier. Tag names may not + contain the ":" character. + + The reserved name "tip" is a special tag that always identifies + the most recent revision. + +SPECIFYING MULTIPLE REVISIONS +----------------------------- + + When Mercurial accepts more than one revision, they may be + specified individually, or provided as a continuous range, + separated by the ":" character. + + The syntax of range notation is [BEGIN]:[END], where BEGIN and END + are revision identifiers. Both BEGIN and END are optional. If + BEGIN is not specified, it defaults to revision number 0. If END + is not specified, it defaults to the tip. The range ":" thus + means "all revisions". + + If BEGIN is greater than END, revisions are treated in reverse + order. + + A range acts as a closed interval. This means that a range of 3:5 + gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2. + +ENVIRONMENT VARIABLES +--------------------- + +HGEDITOR:: + This is the name of the editor to use when committing. Defaults to the + value of EDITOR. + + (deprecated, use .hgrc) + +HGMERGE:: + An executable to use for resolving merge conflicts. The program + will be executed with three arguments: local file, remote file, + ancestor file. + + The default program is "hgmerge", which is a shell script provided + by Mercurial with some sensible defaults. + + (deprecated, use .hgrc) + +HGRCPATH:: + A list of files or directories to search for hgrc files. Item + separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set, + platform default search path is used. If empty, only .hg/hgrc of + current repository is read. + + For each element in path, if a directory, all entries in directory + ending with ".rc" are added to path. Else, element itself is + added to path. + +HGUSER:: + This is the string used for the author of a commit. + + (deprecated, use .hgrc) + +EMAIL:: + If HGUSER is not set, this will be used as the author for a commit. + +LOGNAME:: + If neither HGUSER nor EMAIL is set, LOGNAME will be used (with + '@hostname' appended) as the author value for a commit. + +EDITOR:: + This is the name of the editor used in the hgmerge script. It will be + used for commit messages if HGEDITOR isn't set. Defaults to 'vi'. + +PYTHONPATH:: + This is used by Python to find imported modules and may need to be set + appropriately if Mercurial is not installed system-wide. + +FILES +----- + .hgignore:: + This file contains regular expressions (one per line) that describe file + names that should be ignored by hg. For details, see hgignore(5). + + .hgtags:: + This file contains changeset hash values and text tag names (one of each + separated by spaces) that correspond to tagged versions of the repository + contents. + + /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc:: + This file contains defaults and configuration. Values in .hg/hgrc + override those in $HOME/.hgrc, and these override settings made in the + global /etc/mercurial/hgrc configuration. See hgrc(5) for details of + the contents and format of these files. + +BUGS +---- +Probably lots, please post them to the mailing list (See Resources below) +when you find them. + +SEE ALSO +-------- +hgignore(5), hgrc(5) + +AUTHOR +------ +Written by Matt Mackall <mpm@selenic.com> + +RESOURCES +--------- +http://selenic.com/mercurial[Main Web Site] + +http://selenic.com/hg[Source code repository] + +http://selenic.com/mailman/listinfo/mercurial[Mailing list] + +COPYING +------- +Copyright \(C) 2005 Matt Mackall. +Free use of this software is granted under the terms of the GNU General +Public License (GPL).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/hgignore.5.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,92 @@ +HGIGNORE(5) +=========== +Vadim Gelfer <vadim.gelfer@gmail.com> + +NAME +---- +hgignore - syntax for Mercurial ignore files + +SYNOPSIS +-------- + +The Mercurial system uses a file called .hgignore in the root +directory of a repository to control its behavior when it finds files +that it is not currently managing. + +DESCRIPTION +----------- + +Mercurial ignores every unmanaged file that matches any pattern in an +ignore file. The patterns in an ignore file do not apply to files +managed by Mercurial. To control Mercurial's handling of files that +it manages, see the hg(1) man page. Look for the "-I" and "-X" +options. + +In addition, a Mercurial configuration file can point to a set of +per-user or global ignore files. See the hgrc(5) man page for details +of how to configure these files. Look for the "ignore" entry in the +"ui" section. + +SYNTAX +------ + +An ignore file is a plain text file consisting of a list of patterns, +with one pattern per line. Empty lines are skipped. The "#" +character is treated as a comment character, and the "\" character is +treated as an escape character. + +Mercurial supports several pattern syntaxes. The default syntax used +is Python/Perl-style regular expressions. + +To change the syntax used, use a line of the following form: + +syntax: NAME + +where NAME is one of the following: + +regexp:: + Regular expression, Python/Perl syntax. +glob:: + Shell-style glob. + +The chosen syntax stays in effect when parsing all patterns that +follow, until another syntax is selected. + +Neither glob nor regexp patterns are rooted. A glob-syntax pattern of +the form "*.c" will match a file ending in ".c" in any directory, and +a regexp pattern of the form "\.c$" will do the same. To root a +regexp pattern, start it with "^". + +EXAMPLE +------- + +Here is an example ignore file. + + # use glob syntax. + syntax: glob + + *.elc + *.pyc + *~ + .*.swp + + # switch to regexp syntax. + syntax: regexp + ^\.pc/ + +AUTHOR +------ +Vadim Gelfer <vadim.gelfer@gmail.com> + +Mercurial was written by Matt Mackall <mpm@selenic.com>. + +SEE ALSO +-------- +hg(1), hgrc(5) + +COPYING +------- +This manual page is copyright 2006 Vadim Gelfer. +Mercurial is copyright 2005, 2006 Matt Mackall. +Free use of this software is granted under the terms of the GNU General +Public License (GPL).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/hgmerge.1.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,35 @@ +HGMERGE(1) +========== +Matt Mackall <mpm@selenic.com> +v0.1, 27 May 2005 + +NAME +---- +hgmerge - default wrapper to merge files in Mercurial SCM system + +SYNOPSIS +-------- +'hgmerge' local ancestor remote + +DESCRIPTION +----------- +The hgmerge(1) command provides a graphical interface to merge files in the +Mercurial system. It is a simple wrapper around kdiff3, merge(1) and tkdiff(1), +or simply diff(1) and patch(1) depending on what is present on the system. + +hgmerge(1) is used by the Mercurial SCM if the environment variable HGMERGE is +not set. + +AUTHOR +------ +Written by Vincent Danjean <Vincent.Danjean@free.fr> + +SEE ALSO +-------- +hg(1) - the command line interface to Mercurial SCM + +COPYING +------- +Copyright \(C) 2005 Matt Mackall. +Free use of this software is granted under the terms of the GNU General +Public License (GPL).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/hgrc.5.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,420 @@ +HGRC(5) +======= +Bryan O'Sullivan <bos@serpentine.com> + +NAME +---- +hgrc - configuration files for Mercurial + +SYNOPSIS +-------- + +The Mercurial system uses a set of configuration files to control +aspects of its behaviour. + +FILES +----- + +Mercurial reads configuration data from several files, if they exist. +The names of these files depend on the system on which Mercurial is +installed. + +(Unix) <install-root>/etc/mercurial/hgrc.d/*.rc:: +(Unix) <install-root>/etc/mercurial/hgrc:: + Per-installation configuration files, searched for in the + directory where Mercurial is installed. For example, if installed + in /shared/tools, Mercurial will look in + /shared/tools/etc/mercurial/hgrc. Options in these files apply to + all Mercurial commands executed by any user in any directory. + +(Unix) /etc/mercurial/hgrc.d/*.rc:: +(Unix) /etc/mercurial/hgrc:: +(Windows) C:\Mercurial\Mercurial.ini:: + Per-system configuration files, for the system on which Mercurial + is running. Options in these files apply to all Mercurial + commands executed by any user in any directory. Options in these + files override per-installation options. + +(Unix) $HOME/.hgrc:: +(Windows) C:\Documents and Settings\USERNAME\Mercurial.ini:: +(Windows) $HOME\Mercurial.ini:: + Per-user configuration file, for the user running Mercurial. + Options in this file apply to all Mercurial commands executed by + any user in any directory. Options in this file override + per-installation and per-system options. + On Windows system, one of these is chosen exclusively according + to definition of HOME environment variable. + +(Unix, Windows) <repo>/.hg/hgrc:: + Per-repository configuration options that only apply in a + particular repository. This file is not version-controlled, and + will not get transferred during a "clone" operation. Options in + this file override options in all other configuration files. + +SYNTAX +------ + +A configuration file consists of sections, led by a "[section]" header +and followed by "name: value" entries; "name=value" is also accepted. + + [spam] + eggs=ham + green= + eggs + +Each line contains one entry. If the lines that follow are indented, +they are treated as continuations of that entry. + +Leading whitespace is removed from values. Empty lines are skipped. + +The optional values can contain format strings which refer to other +values in the same section, or values in a special DEFAULT section. + +Lines beginning with "#" or ";" are ignored and may be used to provide +comments. + +SECTIONS +-------- + +This section describes the different sections that may appear in a +Mercurial "hgrc" file, the purpose of each section, its possible +keys, and their possible values. + +decode/encode:: + Filters for transforming files on checkout/checkin. This would + typically be used for newline processing or other + localization/canonicalization of files. + + Filters consist of a filter pattern followed by a filter command. + Filter patterns are globs by default, rooted at the repository + root. For example, to match any file ending in ".txt" in the root + directory only, use the pattern "*.txt". To match any file ending + in ".c" anywhere in the repository, use the pattern "**.c". + + The filter command can start with a specifier, either "pipe:" or + "tempfile:". If no specifier is given, "pipe:" is used by default. + + A "pipe:" command must accept data on stdin and return the + transformed data on stdout. + + Pipe example: + + [encode] + # uncompress gzip files on checkin to improve delta compression + # note: not necessarily a good idea, just an example + *.gz = pipe: gunzip + + [decode] + # recompress gzip files when writing them to the working dir (we + # can safely omit "pipe:", because it's the default) + *.gz = gzip + + A "tempfile:" command is a template. The string INFILE is replaced + with the name of a temporary file that contains the data to be + filtered by the command. The string OUTFILE is replaced with the + name of an empty temporary file, where the filtered data must be + written by the command. + + NOTE: the tempfile mechanism is recommended for Windows systems, + where the standard shell I/O redirection operators often have + strange effects. In particular, if you are doing line ending + conversion on Windows using the popular dos2unix and unix2dos + programs, you *must* use the tempfile mechanism, as using pipes will + corrupt the contents of your files. + + Tempfile example: + + [encode] + # convert files to unix line ending conventions on checkin + **.txt = tempfile: dos2unix -n INFILE OUTFILE + + [decode] + # convert files to windows line ending conventions when writing + # them to the working dir + **.txt = tempfile: unix2dos -n INFILE OUTFILE + +email:: + Settings for extensions that send email messages. + from;; + Optional. Email address to use in "From" header and SMTP envelope + of outgoing messages. + method;; + Optional. Method to use to send email messages. If value is + "smtp" (default), use SMTP (see section "[mail]" for + configuration). Otherwise, use as name of program to run that + acts like sendmail (takes "-f" option for sender, list of + recipients on command line, message on stdin). Normally, setting + this to "sendmail" or "/usr/sbin/sendmail" is enough to use + sendmail to send messages. + + Email example: + + [email] + from = Joseph User <joe.user@example.com> + method = /usr/sbin/sendmail + +extensions:: + Mercurial has an extension mechanism for adding new features. To + enable an extension, create an entry for it in this section. + + If you know that the extension is already in Python's search path, + you can give the name of the module, followed by "=", with nothing + after the "=". + + Otherwise, give a name that you choose, followed by "=", followed by + the path to the ".py" file (including the file name extension) that + defines the extension. + +hooks:: + Commands or Python functions that get automatically executed by + various actions such as starting or finishing a commit. Multiple + hooks can be run for the same action by appending a suffix to the + action. Overriding a site-wide hook can be done by changing its + value or setting it to an empty string. + + Example .hg/hgrc: + + [hooks] + # do not use the site-wide hook + incoming = + incoming.email = /my/email/hook + incoming.autobuild = /my/build/hook + + Most hooks are run with environment variables set that give added + useful information. For each hook below, the environment variables + it is passed are listed with names of the form "$HG_foo". + + changegroup;; + Run after a changegroup has been added via push, pull or + unbundle. ID of the first new changeset is in $HG_NODE. + commit;; + Run after a changeset has been created in the local repository. + ID of the newly created changeset is in $HG_NODE. Parent + changeset IDs are in $HG_PARENT1 and $HG_PARENT2. + incoming;; + Run after a changeset has been pulled, pushed, or unbundled into + the local repository. The ID of the newly arrived changeset is in + $HG_NODE. + outgoing;; + Run after sending changes from local repository to another. ID of + first changeset sent is in $HG_NODE. Source of operation is in + $HG_SOURCE; see "preoutgoing" hook for description. + prechangegroup;; + Run before a changegroup is added via push, pull or unbundle. + Exit status 0 allows the changegroup to proceed. Non-zero status + will cause the push, pull or unbundle to fail. + precommit;; + Run before starting a local commit. Exit status 0 allows the + commit to proceed. Non-zero status will cause the commit to fail. + Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2. + preoutgoing;; + Run before computing changes to send from the local repository to + another. Non-zero status will cause failure. This lets you + prevent pull over http or ssh. Also prevents against local pull, + push (outbound) or bundle commands, but not effective, since you + can just copy files instead then. Source of operation is in + $HG_SOURCE. If "serve", operation is happening on behalf of + remote ssh or http repository. If "push", "pull" or "bundle", + operation is happening on behalf of repository on same system. + pretag;; + Run before creating a tag. Exit status 0 allows the tag to be + created. Non-zero status will cause the tag to fail. ID of + changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag + is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0. + pretxnchangegroup;; + Run after a changegroup has been added via push, pull or unbundle, + but before the transaction has been committed. Changegroup is + visible to hook program. This lets you validate incoming changes + before accepting them. Passed the ID of the first new changeset + in $HG_NODE. Exit status 0 allows the transaction to commit. + Non-zero status will cause the transaction to be rolled back and + the push, pull or unbundle will fail. + pretxncommit;; + Run after a changeset has been created but the transaction not yet + committed. Changeset is visible to hook program. This lets you + validate commit message and changes. Exit status 0 allows the + commit to proceed. Non-zero status will cause the transaction to + be rolled back. ID of changeset is in $HG_NODE. Parent changeset + IDs are in $HG_PARENT1 and $HG_PARENT2. + preupdate;; + Run before updating the working directory. Exit status 0 allows + the update to proceed. Non-zero status will prevent the update. + Changeset ID of first new parent is in $HG_PARENT1. If merge, ID + of second new parent is in $HG_PARENT2. + tag;; + Run after a tag is created. ID of tagged changeset is in + $HG_NODE. Name of tag is in $HG_TAG. Tag is local if + $HG_LOCAL=1, in repo if $HG_LOCAL=0. + update;; + Run after updating the working directory. Changeset ID of first + new parent is in $HG_PARENT1. If merge, ID of second new parent + is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update + failed (e.g. because conflicts not resolved), $HG_ERROR=1. + + Note: In earlier releases, the names of hook environment variables + did not have a "HG_" prefix. The old unprefixed names are no longer + provided in the environment. + + The syntax for Python hooks is as follows: + + hookname = python:modulename.submodule.callable + + Python hooks are run within the Mercurial process. Each hook is + called with at least three keyword arguments: a ui object (keyword + "ui"), a repository object (keyword "repo"), and a "hooktype" + keyword that tells what kind of hook is used. Arguments listed as + environment variables above are passed as keyword arguments, with no + "HG_" prefix, and names in lower case. + + A Python hook must return a "true" value to succeed. Returning a + "false" value or raising an exception is treated as failure of the + hook. + +http_proxy:: + Used to access web-based Mercurial repositories through a HTTP + proxy. + host;; + Host name and (optional) port of the proxy server, for example + "myproxy:8000". + no;; + Optional. Comma-separated list of host names that should bypass + the proxy. + passwd;; + Optional. Password to authenticate with at the proxy server. + user;; + Optional. User name to authenticate with at the proxy server. + +smtp:: + Configuration for extensions that need to send email messages. + host;; + Optional. Host name of mail server. Default: "mail". + port;; + Optional. Port to connect to on mail server. Default: 25. + tls;; + Optional. Whether to connect to mail server using TLS. True or + False. Default: False. + username;; + Optional. User name to authenticate to SMTP server with. + If username is specified, password must also be specified. + Default: none. + password;; + Optional. Password to authenticate to SMTP server with. + If username is specified, password must also be specified. + Default: none. + +paths:: + Assigns symbolic names to repositories. The left side is the + symbolic name, and the right gives the directory or URL that is the + location of the repository. Default paths can be declared by + setting the following entries. + default;; + Directory or URL to use when pulling if no source is specified. + Default is set to repository from which the current repository + was cloned. + default-push;; + Optional. Directory or URL to use when pushing if no destination + is specified. + +ui:: + User interface controls. + debug;; + Print debugging information. True or False. Default is False. + editor;; + The editor to use during a commit. Default is $EDITOR or "vi". + ignore;; + A file to read per-user ignore patterns from. This file should be in + the same format as a repository-wide .hgignore file. This option + supports hook syntax, so if you want to specify multiple ignore + files, you can do so by setting something like + "ignore.other = ~/.hgignore2". For details of the ignore file + format, see the hgignore(5) man page. + interactive;; + Allow to prompt the user. True or False. Default is True. + logtemplate;; + Template string for commands that print changesets. + style;; + Name of style to use for command output. + merge;; + The conflict resolution program to use during a manual merge. + Default is "hgmerge". + quiet;; + Reduce the amount of output printed. True or False. Default is False. + remotecmd;; + remote command to use for clone/push/pull operations. Default is 'hg'. + ssh;; + command to use for SSH connections. Default is 'ssh'. + timeout;; + The timeout used when a lock is held (in seconds), a negative value + means no timeout. Default is 600. + username;; + The committer of a changeset created when running "commit". + Typically a person's name and email address, e.g. "Fred Widget + <fred@example.com>". Default is $EMAIL or username@hostname, unless + username is set to an empty string, which enforces specifying the + username manually. + verbose;; + Increase the amount of output printed. True or False. Default is False. + + +web:: + Web interface configuration. + accesslog;; + Where to output the access log. Default is stdout. + address;; + Interface address to bind to. Default is all. + allow_archive;; + List of archive format (bz2, gz, zip) allowed for downloading. + Default is empty. + allowbz2;; + (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions. + Default is false. + allowgz;; + (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions. + Default is false. + allowpull;; + Whether to allow pulling from the repository. Default is true. + allowzip;; + (DEPRECATED) Whether to allow .zip downloading of repo revisions. + Default is false. This feature creates temporary files. + baseurl;; + Base URL to use when publishing URLs in other locations, so + third-party tools like email notification hooks can construct URLs. + Example: "http://hgserver/repos/" + description;; + Textual description of the repository's purpose or contents. + Default is "unknown". + errorlog;; + Where to output the error log. Default is stderr. + ipv6;; + Whether to use IPv6. Default is false. + name;; + Repository name to use in the web interface. Default is current + working directory. + maxchanges;; + Maximum number of changes to list on the changelog. Default is 10. + maxfiles;; + Maximum number of files to list per changeset. Default is 10. + port;; + Port to listen on. Default is 8000. + style;; + Which template map style to use. + templates;; + Where to find the HTML templates. Default is install path. + + +AUTHOR +------ +Bryan O'Sullivan <bos@serpentine.com>. + +Mercurial was written by Matt Mackall <mpm@selenic.com>. + +SEE ALSO +-------- +hg(1), hgignore(5) + +COPYING +------- +This manual page is copyright 2005 Bryan O'Sullivan. +Mercurial is copyright 2005, 2006 Matt Mackall. +Free use of this software is granted under the terms of the GNU General +Public License (GPL).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/Makefile Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +SOURCES=$(wildcard *.[0-9].ja.txt) +MAN=$(SOURCES:%.txt=%) +HTML=$(SOURCES:%.txt=%.html) + +all: man html + +man: $(MAN) + +html: $(HTML) + +%: %.xml + xmlto -x docbook.ja.xsl man $*.xml + +%.xml: %.txt + -asciidoc -d manpage -b docbook -f docbook.ja.conf $*.txt + +%.html: %.txt + asciidoc -b html4 $*.txt + +clean: + $(RM) $(MAN:%.ja=%) $(MAN:%=%.xml) $(MAN:%=%.html)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/docbook.ja.conf Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,583 @@ +# +# docbook.conf +# +# Asciidoc configuration file. +# Modified docbook backend for Japanese. +# + +[miscellaneous] +outfilesuffix=.xml +# Printable page width in pts. +pagewidth=380 +pageunits=pt + +[attributes] +basebackend=docbook +basebackend-docbook= + +[replacements] +# Line break markup is dropped (there is no DocBook line break tag). +(?m)^(.*)\s\+$=\1 +# Superscripts. +\^(.+?)\^=<superscript>\1</superscript> +# Subscripts. +~(.+?)~=<subscript>\1</subscript> + +[ruler-blockmacro] +# Only applies to HTML so don't output anything. + +[image-inlinemacro] +<inlinemediaobject> + <imageobject> + <imagedata fileref="{target}"{width? contentwidth="{width}pt"}{height? contentdepth="{height}pt"}/> + </imageobject> + <textobject><phrase>{1={target}}</phrase></textobject> +</inlinemediaobject> + +[image-blockmacro] +<figure{id? id="{id}"}><title>{title}</title> +{title%}<informalfigure{id? id="{id}"}> +<mediaobject> + <imageobject> + <imagedata fileref="{target}"{width? contentwidth="{width}pt"}{height? contentdepth="{height}pt"}/> + </imageobject> + <textobject><phrase>{1={target}}</phrase></textobject> +</mediaobject> +{title#}</figure> +{title%}</informalfigure> + +[indexterm-inlinemacro] +# Inline index term. +# Generate separate index entries for primary, secondary and tertiary +# descriptions. +# Primary only. +{2%}<indexterm> +{2%} <primary>{1}</primary> +{2%}</indexterm> +# Primary and secondary. +{2#}{3%}<indexterm> +{2#}{3%} <primary>{1}</primary><secondary>{2}</secondary> +{2#}{3%}</indexterm> +{2#}{3%}<indexterm> +{2#}{3%} <primary>{2}</primary> +{2#}{3%}</indexterm> +# Primary, secondary and tertiary. +{3#}<indexterm> + <primary>{1}</primary><secondary>{2}</secondary><tertiary>{3}</tertiary> +{3#}</indexterm> +{3#}<indexterm> + <primary>{2}</primary><secondary>{3}</secondary> +{3#}</indexterm> +{3#}<indexterm> + <primary>{3}</primary> +{3#}</indexterm> + +[indexterm2-inlinemacro] +# Inline index term. +# Single entry index term that is visible in the primary text flow. +<indexterm> + <primary>{1}</primary> +</indexterm> +{1} + +[footnote-inlinemacro] +# Inline footnote. +<footnote><simpara>{0}</simpara></footnote> + +[callout-inlinemacro] +# Inline callout. +<co id="{coid}"/> + +[tags] +# Bulleted, numbered and labeled list tags. +ilist=<itemizedlist{id? id="{id}"}>{title?<title>{title}</title>}|</itemizedlist> +ilistitem=<listitem>|</listitem> +ilisttext=<simpara>|</simpara> +olist=<orderedlist{id? id="{id}"}>{title?<title>{title}</title>}|</orderedlist> +olist2=<orderedlist{id? id="{id}"} numeration="loweralpha">|</orderedlist> +olistitem=<listitem>|</listitem> +olisttext=<simpara>|</simpara> +vlist=<variablelist{id? id="{id}"}>{title?<title>{title}</title>}|</variablelist> +vlistentry=<varlistentry>|</varlistentry> +vlistterm=<term>|</term> +vlisttext=<simpara>|</simpara> +vlistitem=<listitem>|</listitem> +# Horizontal labeled list (implemented with two column table). +# Hardwired column widths to 30%,70% because the current crop of PDF +# generators do not auto calculate column widths. +hlist=<{title?table}{title!informaltable}{id? id="{id}"} tabstyle="{style=hlabeledlist}" pgwide="0" frame="none" colsep="0" rowsep="0">{title?<title>{title}</title>}<tgroup cols="2"><colspec colwidth="{1=3}*"/><colspec colwidth="{2=7}*"/><tbody valign="top">|</tbody></tgroup><{title?/table}{title!/informaltable}> +hlistentry=<row>|</row> +hlisttext=<simpara>|</simpara> +hlistterm=<entry><simpara>|</simpara></entry> +hlistitem=<entry>|</entry> + +# Question and Answer list. +qlist=<qandaset{id? id="{id}"}>{title?<title>{title}</title>}|</qandaset> +qlistentry=<qandaentry>|</qandaentry> +qlistterm=<question><simpara>|</simpara></question> +qlistitem=<answer>|</answer> +qlisttext=<simpara>|</simpara> +# Bibliography list. +blist=| +blistitem=<bibliomixed>|</bibliomixed> +blisttext=<bibliomisc>|</bibliomisc> +# Glossary list. +glist=| +glistentry=<glossentry>|</glossentry> +glistterm=<glossterm>|</glossterm> +glistitem=<glossdef>|</glossdef> +glisttext=<simpara>|</simpara> +# Callout list. +colist=<calloutlist{id? id="{id}"}>{title?<title>{title}</title>}|</calloutlist> +colistitem=<callout arearefs="{coids}">|</callout> +colisttext=<simpara>|</simpara> + +# Quoted text +emphasis=<emphasis>|</emphasis> +strong=<emphasis role="strong">|</emphasis> +monospaced=<literal>|</literal> +quoted={amp}#8220;|{amp}#8221; + +# Inline macros +[http-inlinemacro] +<ulink url="{name}:{target}">{0={name}:{target}}</ulink> +[https-inlinemacro] +<ulink url="{name}:{target}">{0={name}:{target}}</ulink> +[ftp-inlinemacro] +<ulink url="{name}:{target}">{0={name}:{target}}</ulink> +[file-inlinemacro] +<ulink url="{name}:{target}">{0={name}:{target}}</ulink> +[mailto-inlinemacro] +<ulink url="{name}:{target}">{0={target}}</ulink> +#<email>{target}</email> +[link-inlinemacro] +<ulink url="{target}">{0={target}}</ulink> +# anchor:id[text] +[anchor-inlinemacro] +<anchor id="{target}" xreflabel="{0=[{target}]}"/> +# [[id,text]] +[anchor2-inlinemacro] +<anchor id="{1}" xreflabel="{2=[{1}]}"/> +# [[[id]]] +[anchor3-inlinemacro] +<anchor id="{1}" xreflabel="[{1}]"/>[{1}] +# xref:id[text] +[xref-inlinemacro] +<link linkend="{target}">{0}</link> +{2%}<xref linkend="{target}"/> +# <<id,text>> +[xref2-inlinemacro] +<link linkend="{1}">{2}</link> +{2%}<xref linkend="{1}"/> + + +# Special word macros +[emphasizedwords] +<emphasis>{words}</emphasis> +[monospacedwords] +<literal>{words}</literal> +[strongwords] +<emphasis role="strong">{words}</emphasis> + +# Paragraph substitution. +[paragraph] +<formalpara{id? id="{id}"}><title>{title}</title><para> +{title%}<simpara{id? id="{id}"}> +| +{title%}</simpara> +{title#}</para></formalpara> +{empty} + +[admonitionparagraph] +<{name}{id? id="{id}"}><simpara>|</simpara></{name}> + +[literalparagraph] +# The literal block employs the same markup. +template::[literalblock] + +[verseparagraph] +template::[verseblock] + +# Delimited blocks. +[literalblock] +<example><title>{title}</title> +<literallayout{id? id="{id}"} class="{font=monospaced}"> +| +</literallayout> +{title#}</example> + +[listingblock] +<example><title>{title}</title> +<screen> +| +</screen> +{title#}</example> + +[verseblock] +<formalpara{id? id="{id}"}><title>{title}</title><para> +{title%}<literallayout{id? id="{id}"}> +{title#}<literallayout> +| +</literallayout> +{title#}</para></formalpara> + +[sidebarblock] +<sidebar{id? id="{id}"}> +<title>{title}</title> +| +</sidebar> + +[backendblock] +| + +[quoteblock] +# The epigraph element may be more appropriate than blockquote. +<blockquote{id? id="{id}"}> +<title>{title}</title> +<attribution> +{attribution} +<citetitle>{citetitle}</citetitle> +</attribution> +| +</blockquote> + +[exampleblock] +<{title?example}{title!informalexample}{id? id="{id}"}> +<title>{title}</title> +| +</{title?example}{title!informalexample}> + +[admonitionblock] +<{name}{id? id="{id}"}> +<title>{title}</title> +| +</{name}> + +# Tables. +[tabledef-default] +template=table +colspec=<colspec colwidth="{colwidth}{pageunits}" align="{colalign}"/> +bodyrow=<row>|</row> +bodydata=<entry>|</entry> + +[table] +<{title?table}{title!informaltable}{id? id="{id}"} pgwide="0" +frame="{frame=topbot}" +{grid%rowsep="0" colsep="0"} +{eval:\{"none":"rowsep=\"0\" colsep=\"0\"", "cols":"rowsep=\"0\" colsep=\"1\"", "all":"rowsep=\"1\" colsep=\"1\"", "rows":"rowsep=\"1\" colsep=\"0\"" \}["{grid}"]} +> +<title>{title}</title> +<tgroup cols="{cols}"> +{colspecs} +{headrows#}<thead> +{headrows} +{headrows#}</thead> +{footrows#}<tfoot> +{footrows} +{footrows#}</tfoot> +<tbody> +{bodyrows} +</tbody> +</tgroup> +</{title?table}{title!informaltable}> + +[specialsections] +ifdef::doctype-article[] +^Abstract$=sect-abstract +endif::doctype-article[] + +ifdef::doctype-book[] +^Colophon$=sect-colophon +^Dedication$=sect-dedication +^Preface$=sect-preface +endif::doctype-book[] + +^Index$=sect-index +^(Bibliography|References)$=sect-bibliography +^Glossary$=sect-glossary +^Appendix [A-Z][:.](?P<title>.*)$=sect-appendix + +# Special sections. +[sect-preface] +<preface{id? id="{id}"}> +<title>{title}</title> +| +</preface> + +[sect-index] +<index{id? id="{id}"}> +<title>{title}</title> +| +</index> + +[sect-bibliography] +<bibliography{id? id="{id}"}> +<title>{title}</title> +| +</bibliography> + +[sect-glossary] +<glossary{id? id="{id}"}> +<title>{title}</title> +| +</glossary> + +[sect-appendix] +<appendix{id? id="{id}"}> +<title>{title}</title> +| +</appendix> + + +[header-declarations] +<?xml version="1.0" encoding="{encoding}"?> +<!DOCTYPE {eval:\{"article":"article", "book":"book", "manpage":"refentry"\}["{doctype}"]} PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +#------------------------- +# article document type +#------------------------- +ifdef::doctype-article[] + +[header] +template::[header-declarations] + +<article lang="ja"> +{doctitle#}<articleinfo> + <title>{doctitle}</title> + <date>{date}</date> + {authored#}<author> + <firstname>{firstname}</firstname> + <othername>{middlename}</othername> + <surname>{lastname}</surname> + <affiliation><address><email>{email}</email></address></affiliation> + {authored#}</author> + +# If file named like source document with -revhistory.xml suffix exists +# include it as the document history, otherwise use current revision. +{revisionhistory#}{include:{docdir}/{docname}-revhistory.xml} +{revisionhistory%}<revhistory><revision><revnumber>{revision}</revnumber><date>{date}</date>{revremark?<revremark>{revremark}</revremark>}</revision></revhistory> + + <corpname>{companyname}</corpname> +{doctitle#}</articleinfo> + +[footer] +</article> + +[preamble] +# Untitled elements between header and first section title. +| + +[sect-abstract] +<abstract{id? id="{id}"}> +| +</abstract> + +[sect1] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +[sect2] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +[sect3] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +[sect4] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +endif::doctype-article[] + +#------------------------- +# manpage document type +#------------------------- +ifdef::doctype-manpage[] + +[replacements] +# The roff format does not substitute special characters so just print them as +# text. +\(C\)=(C) +\(TM\)=(TM) + +[header] +template::[header-declarations] +<refentry> +<refmeta> +<refentrytitle>{mantitle}</refentrytitle> +<manvolnum>{manvolnum}</manvolnum> +</refmeta> +<refnamediv> + <refname>{manname}</refname> + <refpurpose>{manpurpose}</refpurpose> +</refnamediv> + +[footer] +</refentry> + +# Section macros +[sect-synopsis] +<refsynopsisdiv{id? id="{id}"}> +| +</refsynopsisdiv> + +[sect1] +<refsect1{id? id="{id}"}> +<title>{title}</title> +| +</refsect1> + +[sect2] +<refsect2{id? id="{id}"}> +<title>{title}</title> +| +</refsect2> + +[sect3] +<refsect3{id? id="{id}"}> +<title>{title}</title> +| +</refsect3> + +endif::doctype-manpage[] + +#------------------------- +# book document type +#------------------------- +ifdef::doctype-book[] + +[header] +template::[header-declarations] + +<book lang="ja"> +{doctitle#}<bookinfo> + <title>{doctitle}</title> + <date>{date}</date> + {authored#}<author> + <firstname>{firstname}</firstname> + <othername>{middlename}</othername> + <surname>{lastname}</surname> + <affiliation><address><email>{email}</email></address></affiliation> + {authored#}</author> + +# If file named like source document with -revhistory.xml suffix exists +# include it as the document history, otherwise use current revision. +{revisionhistory#}{include:{docdir}/{docname}-revhistory.xml} +{revisionhistory%}<revhistory><revision><revnumber>{revision}</revnumber><date>{date}</date>{revremark?<revremark>{revremark}</revremark>}</revision></revhistory> + + <corpname>{companyname}</corpname> +{doctitle#}</bookinfo> + +[footer] +</book> + +[preamble] +# Preamble is not allowed in DocBook book so wrap it in a preface. +<preface{id? id="{id}"}> +<title>Preface</title> +| +</preface> + +[sect-dedication] +<dedication{id? id="{id}"}> +| +</dedication> + +[sect-colophon] +<colophon{id? id="{id}"}> +| +</colophon> + +[sect0] +<part{id? id="{id}"}> +<title>{title}</title> +| +</part> + +[sect1] +<chapter{id? id="{id}"}> +<title>{title}</title> +| +</chapter> + +[sect2] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +[sect3] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +[sect4] +<section{id? id="{id}"}> +<title>{title}</title> +| +</section> + +endif::doctype-book[] + +ifdef::sgml[] +# +# Optional DocBook SGML. +# +# Most of the differences between DocBook XML and DocBook SGML boils +# down to the empty element syntax: SGML does not like the XML empty +# element <.../> syntax, use <...> instead. +# +[miscellaneous] +outfilesuffix=.sgml + +[header-declarations] +<!DOCTYPE {eval:\{"article":"article", "book":"book", "manpage":"refentry"\}["{doctype}"]} PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> + +[tabledef-default] +colspec=<colspec colwidth="{colwidth}{pageunits}" align="{colalign}"> + +[image-inlinemacro] +<inlinemediaobject> + <imageobject> + <imagedata fileref="{target}"{width? width="{width}pt"}{height? depth="{height}pt"}> + </imageobject> + <textobject><phrase>{1={target}}</phrase></textobject> +</inlinemediaobject> + +[image-blockmacro] +<figure><title>{title}</title> +{title%}<informalfigure> +<mediaobject> + <imageobject> + <imagedata fileref="{target}"{width? width="{width}pt"}{height? depth="{height}pt"}> + </imageobject> + <textobject><phrase>{1={target}}</phrase></textobject> +</mediaobject> +{title#}</figure> +{title%}</informalfigure> + +# Inline macros +[xref-inlinemacro] +<link linkend="{target}">{0}</link> +{2%}<xref linkend="{target}"> +[xref2-inlinemacro] +# <<id,text>> +<link linkend="{1}">{2}</link> +{2%}<xref linkend="{1}"> +[anchor-inlinemacro] +<anchor id="{target}" xreflabel="{0=[{target}]}"> +[anchor2-inlinemacro] +# [[id,text]] +<anchor id="{1}" xreflabel="{2=[{1}]}"> + +endif::sgml[]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/docbook.ja.xsl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,23 @@ +<?xml version='1.0' encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'> + <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl"/> + <xsl:output encoding="UTF-8"/> + + <xsl:template match="refnamediv"> + <xsl:text>.SH åå‰ </xsl:text> + <xsl:for-each select="refname"> + <xsl:if test="position()>1"> + <xsl:text>, </xsl:text> + </xsl:if> + <xsl:value-of select="."/> + </xsl:for-each> + <xsl:text> \- </xsl:text> + <xsl:value-of select="normalize-space (refpurpose)"/> + </xsl:template> + + <xsl:template match="refsynopsisdiv"> + <xsl:text> .SH "書å¼" </xsl:text> + <xsl:apply-templates/> + </xsl:template> + +</xsl:stylesheet>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/hg.1.ja.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,867 @@ +HG(1) +===== +Matt Mackall <mpm@selenic.com> + +åå‰ +-- +hg - Mercurial ソースコード管ç†ã‚·ã‚¹ãƒ†ãƒ + +æ›¸å¼ +-- +'hg' [-v -d -q -y] <command> [command options] [files] + +説明 +-- +hg(1) コマンド㯠Mercurial システムã¸ã®ã‚³ãƒžãƒ³ãƒ‰ãƒ©ã‚¤ãƒ³ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ +イスをæä¾›ã—ã¾ã™ã€‚ + +オプション +---- + +-R, --repository:: + リãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’指定ã—ã¾ã™ã€‚ + +--cwd:: + 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’変更ã—ã¾ã™ã€‚ + +-y, --noninteractive:: + プãƒãƒ³ãƒ—トを出ã•ãšã«ã€è¦æ±‚ã•ã‚ŒãŸç”ãˆãŒå…¨ã¦ 'yes' ã§ã‚ã‚‹ã¨ä»®å®š + ã—ã¾ã™ã€‚ + +-q, --quiet:: + 出力を抑制ã—ã¾ã™ã€‚ + +-v, --verbose:: + ã•ã‚‰ãªã‚‹å‡ºåŠ›ã‚’å¯èƒ½ã«ã—ã¾ã™ã€‚ + +7--debug:: + デãƒãƒƒã‚°å‡ºåŠ›ã‚’å¯èƒ½ã«ã—ã¾ã™ã€‚ + +--traceback:: + 例外時ã«ãƒˆãƒ¬ãƒ¼ã‚¹ãƒãƒƒã‚¯ã‚’表示ã—ã¾ã™ã€‚ + +--time:: + コマンドã«ã©ã®ãらã„時間ãŒã‹ã‹ã‚‹ã‹ã‚’表示ã—ã¾ã™ã€‚ + +--profile:: + コマンドを実行ã—ãŸã¨ãã®ãƒ—ãƒãƒ•ã‚¡ã‚¤ãƒ«ã‚’表示ã—ã¾ã™ã€‚ + +--version:: + ãƒãƒ¼ã‚¸ãƒ§ãƒ³æƒ…å ±ã‚’è¡¨ç¤ºã—ã¦çµ‚了ã—ã¾ã™ã€‚ + +-h, --help:: + ヘルプを表示ã—ã¦çµ‚了ã—ã¾ã™ã€‚ + +コマンドã®è¦ç´ +------- + +files ...:: + 1ã¤ä»¥ä¸Šã®ãƒ•ã‚¡ã‚¤ãƒ«åã‹ç›¸å¯¾çš„ãªãƒ‘スを表ã—ã¾ã™; パターンマッãƒãƒ³ + ã‚°ã®æƒ…å ±ã¯ã€Œãƒ•ã‚¡ã‚¤ãƒ«åã®ãƒ‘ターンã€ã‚’å‚ç…§ã—ã¦ãã ã•ã„。 + +path:: + ãƒãƒ¼ã‚«ãƒ«ãƒžã‚·ãƒ³ä¸Šã®ãƒ‘スを表ã—ã¾ã™ + +revision:: + ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒªãƒ“ジョンナンãƒãƒ¼, ã‚¿ã‚°, ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒãƒƒ + シュ値ã®ãƒ¦ãƒ‹ãƒ¼ã‚¯ãªéƒ¨åˆ†æ–‡å—列ã«ã‚ˆã‚ŠæŒ‡å®šã§ãã‚‹ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’表 + ã—ã¾ã™ + +repository path:: + ãƒãƒ¼ã‚«ãƒ«ã®ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ‘スåã‹ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒªãƒã‚¸ãƒˆãƒªã® URI を表 + ã—ã¾ã™ã€‚URI ã®ãƒ—ãƒãƒˆã‚³ãƒ«ã¨ã—ã¦ã¯ç¾åœ¨ 2 ã¤ãŒåˆ©ç”¨å¯èƒ½ã§ã™ã€‚ + http:// ã¯é«˜é€Ÿã§ã€old-http:// ã¯é…ã„ã§ã™ãŒã‚¦ã‚§ãƒ–ã®ãƒ›ã‚¹ãƒˆã«ç‰¹åˆ¥ + ãªã‚µãƒ¼ãƒã‚’å¿…è¦ã¨ã—ã¾ã›ã‚“。 + +コマンド +---- + +add [options] [files ...]:: + ファイルをãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ä¸‹ã«ç½®ãリãƒã‚¸ãƒˆãƒªã«è¿½åŠ ã™ã‚‹ã“ã¨ã‚’予定 + ã—ã¾ã™ã€‚ + + ファイルã¯æ¬¡ã«ã‚³ãƒŸãƒƒãƒˆæ™‚ã«ãƒªãƒã‚¸ãƒˆãƒªã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚ + + ファイルåãŒä¸Žãˆã‚‰ã‚Œãªã‘ã‚Œã°ã€ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã¨ã‚µãƒ–ディレク + トリã®å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’è¿½åŠ ã—ã¾ã™ã€‚ + +addremove [options] [files ...]:: + æ–°ã—ã„ファイルを全ã¦è¿½åŠ ã—ç„¡ããªã£ãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’å…¨ã¦ãƒªãƒã‚¸ãƒˆãƒªã‹ + らå–り除ãã¾ã™ã€‚ + + æ–°ã—ã„ファイル㯠.hgignore ä¸ã®ãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸå ´åˆç„¡è¦–ã• + ã‚Œã¾ã™ã€‚add ã®ã‚ˆã†ã«ã“ã®å¤‰æ›´ã¯æ¬¡ã®ã‚³ãƒŸãƒƒãƒˆæ™‚ã«åŠ¹æžœã‚’æŒã¡ã¾ã™ã€‚ + +annotate [-r <rev> -u -n -c] [files ...]:: + ファイルä¸ã®å¤‰æ›´ã‚’列挙ã—ã€å„è¡Œã®åŽŸå› ã§ã‚るリビジョン id を表示 + ã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã‚る変更ãŒç”Ÿã˜ãŸéš›ã«èª°ãŒãã®å¤‰æ›´ã‚’ã—ãŸã‹ã‚’発見ã™ã‚‹ + ã®ã«å½¹ã«ç«‹ã¡ã¾ã™ã€‚ + + -a オプションãŒç„¡ã„ã¨ã€annotate ã¯ãƒã‚¤ãƒŠãƒªã¨ã—ã¦æ¤œå‡ºã•ã‚ŒãŸãƒ•ã‚¡ + イルをé¿ã‘るよã†ã«ãªã‚Šã¾ã™ã€‚-a ãŒã‚ã‚‹ã¨ã€annotate ã¯ã¨ãã‹ã注 + 釈を生æˆã—ã€ãŠãらã望ã¾ã—ããªã„çµæžœã«ãªã‚‹ã§ã—ょã†ã€‚ + + オプション: + -a, --text å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’テã‚ストã¨ã—ã¦æ‰±ã„ã¾ã™ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ + ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + -r, --revision <rev> 指定ã•ã‚ŒãŸãƒªãƒ“ジョンã®æ³¨é‡ˆã‚’生æˆã—ã¾ã™ + -u, --user 著者を列挙ã—ã¾ã™ + -c, --changeset ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’列挙ã—ã¾ã™ + -n, --number リビジョンナンãƒãƒ¼ã‚’列挙ã—ã¾ã™ + (デフォルト) + +bundle <file> <other>:: + (実験的) + + ä»–ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã¯è¦‹ä»˜ã‹ã‚‰ãªã‹ã£ãŸå…¨ã¦ã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’集ã‚ãŸã€ + 圧縮済ã¿ãƒã‚§ãƒ³ã‚¸ã‚°ãƒ«ãƒ¼ãƒ—ファイルを生æˆã—ã¾ã™ã€‚ + + ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯å¾“æ¥ã®æ–¹æ³•ã§è»¢é€ã™ã‚‹ã“ã¨ãŒã§ãã€ä»–ã®ãƒªãƒã‚¸ãƒˆãƒªã« + unbundle コマンドã§é©ç”¨ã§ãã¾ã™ã€‚ã“れ㯠push 㨠pull ãŒä½¿ãˆãª + ã„ã‹ã€ãƒªãƒã‚¸ãƒˆãƒªå…¨ä½“をエクスãƒãƒ¼ãƒˆã—ã¦ã—ã¾ã†ã“ã¨ãŒæœ›ã¾ã—ããªã„ + ã¨ãã«ä¾¿åˆ©ã§ã™ã€‚標準ã®ãƒ•ã‚¡ã‚¤ãƒ«æ‹¡å¼µå㯠".hg" ã§ã™ã€‚ + + import/export ã¨é•ã£ã¦ã€ã“ã‚Œã¯ãƒ‘ーミッションã€åå‰å¤‰æ›´ã®ãƒ‡ãƒ¼ã‚¿ã€ + リビジョンã®å±¥æ´ã‚’å«ã‚ãŸãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®å†…容全ã¦ã‚’ä¿å˜ã—ã¾ã™ã€‚ + +cat [options] <file ...>:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’与ãˆã‚‰ã‚ŒãŸãƒªãƒ“ジョンã®å†…容ã§è¡¨ç¤ºã—ã¾ã™ã€‚リ + ビジョンãŒæŒ‡å®šã•ã‚Œãªã‹ã£ãŸå ´åˆã¯ tip ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + + 出力ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã«å¯¾ã—ã¦ã‚‚å¯èƒ½ã§ã™ã€‚ãã®å ´åˆã€ãƒ•ã‚¡ã‚¤ãƒ«åã¯ãƒ•ã‚©ãƒ¼ + マット文å—列ã«ã‚ˆã‚ŠæŒ‡å®šã•ã‚Œã¾ã™ã€‚フォーマットè¦å‰‡ã¯ export コマ + ンドã¨åŒã˜ã§ã™ãŒã€ã•ã‚‰ã«æ¬¡ã®ã‚‚ã®ãŒè¿½åŠ ã•ã‚Œã¾ã™ã€‚ + + %s 出力ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒ™ãƒ¼ã‚¹å + %d 出力ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªåã‹ã€ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆ + ã«ã„ã‚‹å ´åˆã¯ "." + %p 出力ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒ«ãƒ¼ãƒˆã‹ã‚‰ã®ç›¸å¯¾ãƒ‘ス + + オプション: + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ + ã‚’å«ã‚ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ + を除外ã—ã¾ã™ + -o, --output <filespec> æ•´å½¢ã•ã‚ŒãŸåå‰ã§ãƒ•ã‚¡ã‚¤ãƒ«ã«å‡ºåŠ›ã—ã¾ + ã™ + -r, --rev <rev> 与ãˆã‚‰ã‚ŒãŸãƒªãƒ“ジョンを表示ã—ã¾ã™ + +clone [-U] <source> [dest]:: + æ—¢å˜ã®ãƒªãƒã‚¸ãƒˆãƒªã®ã‚³ãƒ”ーを新ã—ã„ディレクトリã«ä½œæˆã—ã¾ã™ + + コピー先ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªåãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ã‚½ãƒ¼ã‚¹ + ã®ãƒ™ãƒ¼ã‚¹åを使用ã—ã¾ã™ã€‚ + + 今後㮠pull ã«ä½¿ãˆã‚‹ã‚ˆã†ã«ã€ã‚³ãƒ”ー元ãŒæ–°ã—ã„リãƒã‚¸ãƒˆãƒªã® + .hg/hgrc ã«è¿½åŠ ã•ã‚Œã¾ã™ã€‚ + + 効率ã®ãŸã‚ã«ã€ã‚³ãƒ”ー元ã¨ã‚³ãƒ”ー先ãŒåŒã˜ãƒ•ã‚¡ã‚¤ãƒ«ã‚·ã‚¹ãƒ†ãƒ 上ã«ã‚ã‚‹ + å ´åˆã¯ãƒãƒ¼ãƒ‰ãƒªãƒ³ã‚¯ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + + オプションン: + -U, --noupdate æ–°ã—ã„作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ update ã‚’è¡Œã„ã¾ã›ã‚“ + -e, --ssh 使用ã•ã‚Œã‚‹ ssh コマンドを指定ã—ã¾ã™ + --remotecmd リモートå´ã§å®Ÿè¡Œã™ã‚‹ hg コマンドを指定ã—ã¾ã™ + +commit [options] [files...]:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®å¤‰æ›´ã‚’リãƒã‚¸ãƒˆãƒªã«ã‚³ãƒŸãƒƒãƒˆã—ã¾ã™ã€‚ + + ã‚‚ã—ファイルã®ãƒªã‚¹ãƒˆãŒçœç•¥ã•ã‚ŒãŸã‚‰ã€ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆã‹ã‚‰å®Ÿè¡Œ + ã—ãŸ"hg status" ã§å ±å‘Šã•ã‚Œã‚‹å…¨ã¦ã®å¤‰æ›´ãŒã‚³ãƒŸãƒƒãƒˆã•ã‚Œã¾ã™ã€‚ + + HGEDITOR ã‚„ EDITOR 環境変数ã¯ã‚³ãƒŸãƒƒãƒˆæ™‚ã®ã‚³ãƒ¡ãƒ³ãƒˆã‚’è¿½åŠ ã™ã‚‹ã‚¨ + ディタを起動ã™ã‚‹ãŸã‚ã«ä½¿ã‚ã‚Œã¾ã™ã€‚ + + オプション: + + -A, --addremove コミットä¸ã« addremove を実行ã—ã¾ã™ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ + ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + -m, --message <text> <text> をコミットメッセージã¨ã—ã¦ä½¿ã„ã¾ + ã™ + -l, --logfile <file> <file> ã‹ã‚‰ã‚³ãƒŸãƒƒãƒˆãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’èªã¿è¾¼ã¿ + ã¾ã™ + -d, --date <datecode> datecode をコミットã—ãŸæ—¥ä»˜ã¨ã—ã¦è¨˜éŒ²ã— + ã¾ã™ + -u, --user <user> user をコミッタã¨ã—ã¦è¨˜éŒ²ã—ã¾ã™ã€‚ + + 別å: ci + +copy <source ...> <dest>:: + コピー先ãŒã‚³ãƒ”ー元ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®ã‚³ãƒ”ーをæŒã£ã¦ã„ã‚‹ã¨å°ã‚’付ã‘ã¾ã™ã€‚ + ã‚‚ã—コピー先ãŒãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãªã‚‰ã€ã‚³ãƒ”ーã¯ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã«ç½®ã‹ã‚Œ + ã¾ã™ã€‚ã‚‚ã—コピー先ãŒãƒ•ã‚¡ã‚¤ãƒ«ãªã‚‰ã€ã‚³ãƒ”ー元ã¯1ã¤ã®ã¿æŒ‡å®šå¯èƒ½ã§ + ã™ã€‚ + + デフォルトã§ã¯ã€ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒ•ã‚¡ã‚¤ãƒ«ãŒãã®ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã« + ã‚ã‚‹ã‚‚ã®ã¨ã—ã¦ãã®å†…容をコピーã—ã¾ã™ã€‚ã‚‚ã— --after ã¨ä¸€ç·’ã«å‘¼ + ã³å‡ºã•ã‚Œã‚Œã°ã€æ“作ã¯è¨˜éŒ²ã•ã‚Œã¾ã™ãŒã€ã‚³ãƒ”ーã¯å®Ÿè¡Œã•ã‚Œã¾ã›ã‚“。 + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯æ¬¡ã®ã‚³ãƒŸãƒƒãƒˆæ™‚ã«åŠ¹æžœã‚’æŒã¡ã¾ã™ã€‚ + + 注æ„: ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯å®Ÿé¨“çš„ã§ã™ã€‚リãƒãƒ¼ãƒ ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’é©åˆ‡ã« + 記録ã§ãã¾ã™ãŒã€ã“ã®æƒ…å ±ã¯ãƒžãƒ¼ã‚¸ã«ã‚ˆã£ã¦ã¾ã 完全ã«ã¯ä½¿ã‚ã‚Œã¾ã› + ã‚“ã—ã€ãƒã‚°ã§å®Œå…¨ã«å ±å‘Šã•ã‚Œã‚‹ã“ã¨ã‚‚ã‚ã‚Šã¾ã›ã‚“。 + + オプション: + -A, --after æ—¢ã«ç™ºç”Ÿã—ãŸã‚³ãƒ”ーを記録ã—ã¾ã™ã€‚ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ + ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + -f, --force æ—¢å˜ã®å¤‰æ›´ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã«ç„¡ç†çŸ¢ç†ã‚³ãƒ”ー + ã—ã¾ã™ + -p, --parents コピー先ã«ã‚³ãƒ”ー元ã®ãƒ‘ã‚¹ã‚’è¿½åŠ ã—ã¾ã™ + + 別å: cp + +diff [-a] [-r revision] [-r revision] [files ...]:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒªãƒ“ジョン間ã®å·®åˆ†ã‚’表示ã—ã¾ã™ã€‚ + + ファイル間ã®å·®åˆ†ã¯ unified diff å½¢å¼ã§è¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ + + 2ã¤ã®ãƒªãƒ“ジョンãŒå¼•æ•°ã¨ã—ã¦æŒ‡å®šã•ã‚ŒãŸå ´åˆã€ãれらã®ãƒªãƒ“ジョン + é–“ã®å·®åˆ†ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚1ã¤ã®ãƒªãƒ“ジョンã—ã‹æŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ + ãã®ãƒªãƒ“ジョンã¯ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã¨æ¯”較ã•ã‚Œã¾ã™ã€‚ãã—㦠リビジョ + ンãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒãã®è¦ªã¨æ¯”較 + ã•ã‚Œã¾ã™ã€‚ + + -a オプション無ã—ã§ã¯ã€diff ã¯ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ«ã‚’検出ã—ãŸã¨ãã« + ãã®å·®åˆ†ã‚’生æˆã™ã‚‹ã®ã‚’é¿ã‘ã¾ã™ã€‚-a オプションã§ã¯ã€diff ã¯ã¨ã« + ã‹ã差分を生æˆã—ã€æらã望ã¾ã—ããªã„çµæžœã‚’ã‚‚ãŸã‚‰ã™ã§ã—ょã†ã€‚ + + オプション: + -a, --text å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’テã‚ストã¨ã—ã¦æ‰±ã„ã¾ã™ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ + ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + +export [-o filespec] [revision] ...:: + 1ã¤ä»¥ä¸Šã®ãƒªãƒ“ジョンã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒ˜ãƒƒãƒ€ã¨å·®åˆ†ã‚’出力ã—ã¾ã™ã€‚ + + ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒ˜ãƒƒãƒ€ã«è¡¨ç¤ºã•ã‚Œã‚‹æƒ…å ±ã¯: 著者ã€ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆ + ã®ãƒãƒƒã‚·ãƒ¥ã€è¦ªã€ã‚³ãƒŸãƒƒãƒˆæ™‚ã®ã‚³ãƒ¡ãƒ³ãƒˆã§ã™ã€‚ + + 出力ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã«å¯¾ã—ã¦ã‚‚å¯èƒ½ã§ã™ã€‚ãã®å ´åˆã€ãƒ•ã‚¡ã‚¤ãƒ«åã¯ãƒ•ã‚©ãƒ¼ + マット文å—列ã«ã‚ˆã‚ŠæŒ‡å®šã•ã‚Œã¾ã™ã€‚フォーマットè¦å‰‡ã¯ä¸‹è¨˜ã®é€šã‚Šã§ + ã™: + + %% ãã®ã¾ã¾ã® "%" æ–‡å— + %H ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒãƒƒã‚·ãƒ¥ (40 ãƒã‚¤ãƒˆã® 16 進数) + %N 生æˆã•ã‚Œã¦ã„るパッãƒã®ç•ªå· + %R ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒªãƒ“ジョンナンãƒãƒ¼ + %b エクスãƒãƒ¼ãƒˆã—ã¦ã„るリãƒã‚¸ãƒˆãƒªã®ãƒ¡ãƒ¼ã‚¹å + %h çŸã„å½¢å¼ã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ãƒãƒƒã‚·ãƒ¥ (12 ãƒã‚¤ãƒˆã® 16 進数) + %n 0 㧠詰ã‚られ㟠1 ã‹ã‚‰å§‹ã¾ã‚‹é€£ç•ª + %r 0 㧠詰ã‚られãŸãƒªãƒ“ジョンナンãƒãƒ¼ + + -a オプション無ã—ã§ã¯ã€diff ã¯ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ«ã‚’検出ã—ãŸã¨ãã« + ãã®å·®åˆ†ã‚’生æˆã™ã‚‹ã®ã‚’é¿ã‘ã¾ã™ã€‚-a オプションã§ã¯ã€diff ã¯ã¨ã« + ã‹ã差分を生æˆã—ã€æらã望ã¾ã—ããªã„çµæžœã‚’ã‚‚ãŸã‚‰ã™ã§ã—ょã†ã€‚ + + オプション: + -a, --text å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’テã‚ストã¨ã—ã¦æ‰±ã„ã¾ã™ + -o, --output <filespec> æ•´å½¢ã•ã‚ŒãŸåå‰ã§ãƒ•ã‚¡ã‚¤ãƒ«ã«å‡ºåŠ›ã—ã¾ã™ + +forget [options] [files]:: + 次ã®ã‚³ãƒŸãƒƒãƒˆæ™‚ã«äºˆå®šã•ã‚ŒãŸ 'hg add' ã‚’å–り消ã—ã¾ã™ã€‚ + + オプション: + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ã¾ + -ã™X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + -ã—ã¾ã™ + +grep [options] pattern [files]:: + æ£è¦è¡¨ç¾ã«ã‚ˆã‚Šãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒªãƒ“ジョンを検索ã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ Unix ã® grep ã¨ã¯é•ã†æŒ¯èˆžã„ã‚’ã—ã¾ã™ã€‚ã“れ㯠+ Python/Perl ã®æ£è¦è¡¨ç¾ã ã‘ã‚’å—ã‘ã¤ã‘ã¾ã™ã€‚ã“ã‚Œã¯ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆ + リã§ã¯ãªãリãƒã‚¸ãƒˆãƒªã®å±¥æ´ã‚’検索ã—ã¾ã™ã€‚ã“ã‚Œã¯å¸¸ã«ãƒžãƒƒãƒã—ãŸã‚‚ + ã®ãŒç¾ã‚ŒãŸãƒªãƒ“ジョンナンãƒãƒ¼ã‚’表示ã—ã¾ã™ã€‚ + + デフォルトã§ã¯ã€grep ã¯ãƒžãƒƒãƒã—ãŸã‚‚ã®ãŒè¦‹ã¤ã‹ã£ãŸãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€ + åˆã®ãƒªãƒ“ジョンを出力ã—ã¾ã™ã€‚マッãƒçŠ¶æ³ã®å¤‰åŒ–("-" ã¯ãƒžãƒƒãƒãŒéž + マッãƒã«ã€"+" ã¯éžãƒžãƒƒãƒãŒãƒžãƒƒãƒã«)ã‚’å«ã‚“ã å„リビジョンを表示 + ã™ã‚‹ã«ã¯ã€--all フラグを使ã£ã¦ãã ã•ã„。 + + オプション: + -0, --print0 ファイルåã‚’ NUL ã§çµ‚ãˆã¾ã™ã€‚ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ + ã‚’å«ã‚ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ + を除外ã—ã¾ã™ + --all マッãƒã—ãŸå…¨ã¦ã®ãƒªãƒ“ジョンを表示㗠+ ã¾ã™ + -i, --ignore-case マッãƒã®ã¨ãã«è‹±å¤§æ–‡å—ã¨å°æ–‡å—を区 + 別ã—ãªã„よã†ã«ã—ã¾ã™ + -l, --files-with-matches マッãƒã—ãŸãƒ•ã‚¡ã‚¤ãƒ«åã¨ãƒªãƒ“ジョン㮠+ ã¿ã‚’表示ã—ã¾ã™ + -n, --line-number マッãƒã—ãŸè¡Œç•ªå·ã‚’表示ã—ã¾ã™ + -r, --rev <rev> 指定ã•ã‚ŒãŸãƒªãƒ“ジョンã®é–“ã§æ¤œç´¢ã—ã¾ + ã™ + -u, --user ãã®å¤‰æ›´ã‚’コミットã—ãŸãƒ¦ãƒ¼ã‚¶ã‚’表示 + ã—ã¾ã™ + +heads:: + リãƒã‚¸ãƒˆãƒªã®å…ˆé ã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’å…¨ã¦è¡¨ç¤ºã—ã¾ã™ã€‚ + + リãƒã‚¸ãƒˆãƒªã®ã€Œå…ˆé ã€ã¨ã¯åã®ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’æŒã£ã¦ã„ãªã„ãƒã‚§ãƒ³ + ジセットã§ã™ã€‚ãれらã¯å¤§æŠµé–‹ç™ºãŒè¡Œã‚ã‚Œã‚‹å ´æ‰€ã§ã€é€šå¸¸ update 㨠+ merge æ“作ã®å¯¾è±¡ã¨ãªã‚‹ã¨ã“ã‚ã§ã™ã€‚ + +identify:: + レãƒã‚¸ãƒˆãƒªã®ç¾åœ¨ã®çŠ¶æ…‹ã®çŸã„サマリを表示ã—ã¾ã™ã€‚ + + ã“ã®ã‚µãƒžãƒªã¯ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã‚’1ã¤ã¾ãŸã¯2ã¤ã®è¦ªã®ãƒãƒƒã‚·ãƒ¥è˜åˆ¥å + を使ã£ã¦è˜åˆ¥ã—ã¾ã™ã€‚親ã®ãƒãƒƒã‚·ãƒ¥è˜åˆ¥åã¯ã‚‚ã—作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã« + コミットã•ã‚Œã¦ã„ãªã„変更ãŒã‚ã‚Œã°å¾Œã‚ã« + ãŒä»˜ãã€æ›´ã«ãã®å¾Œã« + ã“ã®ãƒªãƒ“ジョンã®ã‚¿ã‚°ã®ãƒªã‚¹ãƒˆãŒä»˜ãã¾ã™ã€‚ + + 別å: id + +import [-p <n> -b <base> -f] <patches>:: + 一連ã®ãƒ‘ッãƒã‚’インãƒãƒ¼ãƒˆã—ã€ãã‚Œãžã‚Œå€‹åˆ¥ã«ã‚³ãƒŸãƒƒãƒˆã—ã¾ã™ã€‚ + + 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æœªè§£æ±ºã®å¤‰æ›´ãŒã‚ã£ãŸå ´åˆã€import 㯠-f フラ + ã‚°ãŒæŒ‡å®šã•ã‚Œã¦ãªã‘ã‚Œã°ä¸æ–ã—ã¾ã™ã€‚ + + ã‚‚ã—パッãƒãŒãƒ¡ãƒ¼ãƒ«ã®ã‚ˆã†(最åˆã®è¡ŒãŒ "From " ã‹ RFC 822 ヘッダ + ã®ã‚ˆã†) ã§ã‚ã‚Œã°ã€-f オプションãŒä½¿ã‚ã‚Œã¦ã„ãªã„é™ã‚Šãã‚Œã¯é©ç”¨ + ã•ã‚Œã¾ã›ã‚“。インãƒãƒ¼ãƒˆæ©Ÿæ§‹ã¯ãƒ¡ãƒ¼ãƒ«ã®ãƒ˜ãƒƒãƒ€ã‚’パースもã—ãªã‘れ㰠+ ç ´æ£„ã‚‚ã—ãªã„ã®ã§ã€æœ¬ç‰©ã®ãƒ¡ãƒ¼ãƒ«ã‚’インãƒãƒ¼ãƒˆã—ãªã„よã†ã«ã™ã‚‹ã€Œãƒ¡ãƒ¼ + ルã®ã‚ˆã†ãªã‚‚ã®ã®ã€å¥å…¨æ€§ãƒã‚§ãƒƒã‚¯ã‚’上書ãã™ã‚‹ãŸã‚ã ã‘ã« -f を使㣠+ ã¦ãã ã•ã„。 + + オプション: + -p, --strip <n> patch 㮠ディレクトリ除去オプションã§ã™ã€‚ã“ã‚Œ + ã¯é–¢é€£ã™ã‚‹ patch ã®ã‚ªãƒ—ションã¨åŒã˜æ„味をæŒã¡ + ã¾ã™ + -b <path> パッãƒã‚’èªã¿è¾¼ã‚€ãƒ™ãƒ¼ã‚¹ã¨ãªã‚‹ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’指 + 定ã—ã¾ã™ + -f, --force 未解決ã§ã¾ã コミットã•ã‚Œã¦ã„ãªã„変更ã®ãƒã‚§ãƒƒ + クをçœç•¥ã—ã¾ã™ + + 別å: patch + +incoming [-p] [source]:: + 指定ã•ã‚ŒãŸãƒªãƒã‚¸ãƒˆãƒªã‹ã€ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ pull ã™ã‚‹ãƒªãƒã‚¸ãƒˆãƒªä¸ã«è¦‹ + ã¤ã‹ã£ãŸæ–°ã—ã„ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’表示ã—ã¾ã™ã€‚ã“れら㯠pull ãŒè¦æ±‚ + ã•ã‚ŒãŸã¨ãã«pull ã•ã‚Œã‚‹ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã§ã™ã€‚ + + ç¾åœ¨ã¯ãƒãƒ¼ã‚«ãƒ«ã®ãƒªãƒã‚¸ãƒˆãƒªã®ã¿ãŒã‚µãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã¾ã™ã€‚ + + オプション: + -p, --patch パッãƒã‚’表示ã—ã¾ã™ + + 別å: in + +init [dest]:: + 指定ã•ã‚ŒãŸãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã«æ–°ã—ã„リãƒã‚¸ãƒˆãƒªã‚’åˆæœŸåŒ–ã—ã¾ã™ã€‚指定 + ã•ã‚ŒãŸãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãŒå˜åœ¨ã—ãªã„å ´åˆã¯ä½œæˆã•ã‚Œã¾ã™ã€‚ + + ディレクトリãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªãŒä½¿ç”¨ã•ã‚Œã¾ + ã™ã€‚ + +locate [options] [files]:: + Mercurial ã®ç®¡ç†ä¸‹ã«ã‚るファイルã§åå‰ãŒæŒ‡å®šã•ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒ + ãƒã—ãŸã‚‚ã®ã‚’å…¨ã¦è¡¨ç¤ºã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã¨ã‚µãƒ–ディレクトリを検索ã—ã¾ã™ã€‚ + リãƒã‚¸ãƒˆãƒªå…¨ä½“を検索ã™ã‚‹ã«ã¯ã€ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆã«ç§»å‹•ã—ã¦ãã + ã•ã„。 + + ã‚‚ã—マッãƒã™ã‚‹ãŸã‚ã®ãƒ‘ターンãŒä¸Žãˆã‚‰ã‚Œãªã‘ã‚Œã°ã€ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ + å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®åå‰ã‚’表示ã—ã¾ã™ã€‚ + + ã‚‚ã—ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã®å‡ºåŠ›ã‚’ "xargs" コマンドã«é€ã‚ŠãŸã„ãªã‚‰ã€ + "-0" オプションをã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¨ "xargs" コマンドã®ä¸¡æ–¹ã§ä½¿ç”¨ã— + ã¦ãã ã•ã„。ã“れ㯠"xargs" ãŒã‚¹ãƒšãƒ¼ã‚¹ã®å…¥ã£ãŸãƒ•ã‚¡ã‚¤ãƒ«åを複数 + ã®ãƒ•ã‚¡ã‚¤ãƒ«åã¨ã—ã¦æ‰±ã‚ãªã„よã†ã«ã—ã¾ã™ã€‚ + + オプション: + + -0, --print0 xargs ã¨ä¸€ç·’ã«ä½¿ã†ãŸã‚ã«ã€ãƒ•ã‚¡ã‚¤ãƒ«åã‚’ + NUL ã§çµ‚ãˆã¾ã™ + -f, --fullpath ファイルシステムã®ãƒ«ãƒ¼ãƒˆã‹ã‚‰ã®å®Œå…¨ãªãƒ‘ + スを表示ã—ã¾ã™ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å« + ã‚ã¾ã™ + -r, --rev <rev> rev ã®ã¨ãã®ãƒªãƒã‚¸ãƒˆãƒªã‚’検索ã—ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + +log [-r revision ...] [-p] [files]:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‹ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆå…¨ä½“ã®ãƒªãƒ“ジョンã®å±¥æ´ã‚’表示㗠+ ã¾ã™ã€‚ + + デフォルトã§ã¯ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯æ¬¡ã®ã‚‚ã®ã‚’出力ã—ã¾ã™: ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒ + トã®id ã¨ãƒãƒƒã‚·ãƒ¥ã€ã‚¿ã‚°ã€è¦ªã€ãƒ¦ãƒ¼ã‚¶ã€æ—¥ä»˜ã€å„コミットã®ã‚µãƒž + リ。-v スイッãƒã¯å¤‰æ›´ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚„マニフェストã®ãƒãƒƒã‚·ãƒ¥ã€ + メッセージã®ã‚·ã‚°ãƒãƒãƒ£ã¨ã„ã£ãŸã‚ˆã‚Šè©³ã—ã„æƒ…å ±ã‚’è¿½åŠ ã—ã¾ã™ã€‚ + + オプション: + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ + ã¾ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外 + ã—ã¾ã™ + -r, --rev <A> 指定ã•ã‚ŒãŸãƒªãƒ“ジョンã¾ãŸã¯ç¯„囲を表示ã—ã¾ + ã™ + -p, --patch パッãƒã‚’表示ã—ã¾ã™ + + 別å: history + +manifest [revision]:: + 指定ã•ã‚ŒãŸãƒªãƒ“ジョンã§ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã•ã‚Œã¦ã„るファイルã®ãƒªã‚¹ãƒˆ + を表示ã—ã¾ã™ã€‚ + + manifest ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã•ã‚Œã¦ã„るファイルã®ãƒªã‚¹ãƒˆã§ã™ã€‚ã‚‚ã— + リビジョンãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€tip ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + +outgoing [-p] [dest]:: + 指定ã•ã‚ŒãŸè¡Œãå…ˆã®ãƒªãƒã‚¸ãƒˆãƒªã‹ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ push ã™ã‚‹ãƒªãƒã‚¸ãƒˆãƒª + ä¸ã«è¦‹ä»˜ã‹ã‚‰ãªã‹ã£ãŸãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã‚’表示ã—ã¾ã™ã€‚ã“れら㯠push + ãŒè¦æ±‚ã•ã‚ŒãŸã¨ãã« push ã•ã‚Œã‚‹ã§ã‚ã‚ã†ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã§ã™ã€‚ + + オプション: + -p, --patch パッãƒã‚’表示ã—ã¾ã™ + + 別å: out + +parents:: + 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®è¦ªãƒªãƒ“ジョンを表示ã—ã¾ã™ã€‚ + +paths [NAME]:: + シンボルã®ãƒ‘スåã§ã‚ã‚‹ NAME ã®è¡Œã先を表示ã—ã¾ã™ã€‚ã‚‚ã—シンボル + åãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€åˆ©ç”¨å¯èƒ½ãªã‚·ãƒ³ãƒœãƒ«åã®è¡Œã先を表示ã—ã¾ã™ã€‚ + + パスå㯠/etc/mercurial/hgrc 㨠$HOME/.hgrc ã® [paths] セクショ + ンã§å®šç¾©ã•ã‚Œã¾ã™ã€‚ã‚‚ã—リãƒã‚¸ãƒˆãƒªã®å†…部ã§å®Ÿè¡Œã•ã‚ŒãŸå ´ + åˆã€.hg/hgrc も使用ã•ã‚Œã¾ã™ã€‚ + +pull <repository path>:: + リモートã®ãƒªãƒã‚¸ãƒˆãƒªã®å¤‰æ›´ã‚’ãƒãƒ¼ã‚«ãƒ«ã®ãƒªãƒã‚¸ãƒˆãƒªã« pull ã—ã¾ã™ã€‚ + + ã“ã‚Œã¯æŒ‡å®šã•ã‚ŒãŸãƒ‘スや URL ã«ã‚るリãƒã‚¸ãƒˆãƒªã®å…¨ã¦ã®å¤‰æ›´ã‚’見㤠+ ã‘ã¦ã€ãれらをãƒãƒ¼ã‚«ãƒ«ã®ãƒªãƒã‚¸ãƒˆãƒªã«è¿½åŠ ã—ã¾ã™ã€‚デフォルトã§ã¯ã€ + ã“ã‚Œã¯ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã®ã‚³ãƒ”ーを更新ã—ã¾ã›ã‚“。 + + 有効㪠URL ã®æ¬¡ã®å½¢å¼ã§ã™: + + local/filesystem/path + http://[user@]host[:port][/path] + https://[user@]host[:port][/path] + ssh://[user@]host[:port][/path] + + SSH ã¯è¡Œãå…ˆã®ãƒžã‚·ãƒ³ã®ã‚·ã‚§ãƒ«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ‘スã«hg + ã®ã‚³ãƒ”ーãŒå¿…è¦ã«ãªã‚Šã¾ã™ã€‚SSH を使用ã™ã‚‹ã¨ã€ãƒ‘スã¯ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ + ã¯ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ¦ãƒ¼ã‚¶ã®ãƒ›ãƒ¼ãƒ ディレクトリã®ç›¸å¯¾ãƒ‘スã«ãªã‚Šã¾ã™; ファ + イルシステムã®ãƒ«ãƒ¼ãƒˆã‹ã‚‰ã®ç›¸å¯¾ãƒ‘スã§ã‚ã‚‹ã“ã¨ã‚’指定ã™ã‚‹ã«ã¯ã€ãƒ‘ + スã®æœ€åˆã«ã‚¹ãƒ©ãƒƒã‚·ãƒ¥ã‚’ 2ã¤ä½¿ç”¨ã—ã¦ãã ã•ã„。 + + オプション: + -u, --update pull ã®å¾Œã«ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’ tip ã«æ›´æ–°ã—ã¾ã™ + -e, --ssh 使用ã™ã‚‹ ssh コマンドを指定ã—ã¾ã™ + --remotecmd リモートå´ã§ä½¿ã‚れる hg コマンドを指定ã—ã¾ã™ + +push <destination>:: + ãƒãƒ¼ã‚«ãƒ«ã®ãƒªãƒã‚¸ãƒˆãƒªã®å¤‰æ›´ã‚’指定ã•ã‚ŒãŸè¡Œã先㫠push ã—ã¾ã™ã€‚ + + ã“れ㯠pull ã¨å¯¾ç§°çš„ãªæ“作ã§ã™ã€‚ã“ã‚Œã¯ç¾åœ¨ã®ãƒªãƒã‚¸ãƒˆãƒªã®å¤‰æ›´ã‚’ + ä»–ã®ãƒªãƒã‚¸ãƒˆãƒªã¸ç§»ã™ã®ã«å½¹ç«‹ã¡ã¾ã™ã€‚ã‚‚ã—è¡Œãå…ˆãŒãƒãƒ¼ã‚«ãƒ«ã§ã‚ã‚Œ + ã°ã€ã“ã‚Œã¯ãã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‹ã‚‰ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«å¯¾ã—㦠pull + ã™ã‚‹ã®ã¨åŒã˜ã§ã™ã€‚ + + デフォルトã§ã¯ã€push ã¯å®Ÿè¡Œã—ãŸçµæžœãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ˜ãƒƒãƒ‰ã®æ•°ãŒå¢—㈠+ ã‚‹ãªã‚‰ã°ã€å®Ÿè¡Œã‚’æ‹’å¦ã—ã¾ã™ã€‚ã“ã‚Œã¯ãŸã„ã¦ã„クライアント㌠push + ã™ã‚‹å‰ã« sync ã¨merge を忘れã¦ã„ã‚‹ã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚ + + 有効㪠URL ã¯æ¬¡ã®å½¢å¼ã§ã™: + + local/filesystem/path + ssh://[user@]host[:port][/path] + + SSH ã¯è¡Œãå…ˆã®ãƒžã‚·ãƒ³ã®ã‚·ã‚§ãƒ«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¨ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ‘ス㫠hg + ã®ã‚³ãƒ”ーãŒå¿…è¦ã«ãªã‚Šã¾ã™ã€‚ + + オプション: + + -f, --force update ã‚’å¼·è¡Œã—ã¾ã™ + -e, --ssh 使用ã•ã‚Œã‚‹ ssh コマンドを指定ã—ã¾ã™ + --remotecmd リモートå´ã§å®Ÿè¡Œã•ã‚Œã‚‹ hg コマンドを指定ã—ã¾ã™ + +rawcommit [-p -d -u -F -m -l]:: + 低レベルã®ã‚³ãƒŸãƒƒãƒˆã§ã€ãƒ˜ãƒ«ãƒ‘ースクリプトä¸ã§ä½¿ç”¨ã•ã‚Œã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯é€šå¸¸ã®ãƒ¦ãƒ¼ã‚¶ã«ä½¿ã‚れるã“ã¨ã¯æƒ³å®šã—ã¦ã„ã¾ã›ã‚“。㓠+ ã‚Œã¯ä¸»ã«ä»–ã® SCM ã‹ã‚‰ã‚¤ãƒ³ãƒãƒ¼ãƒˆã™ã‚‹ã¨ãã«ä¾¿åˆ©ã§ã™ã€‚ + +recover:: + ä¸æ–ã•ã‚ŒãŸ commit ã‚„ pull ã‹ã‚‰å¾©å¸°ã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ä¸æ–ã•ã‚ŒãŸæ“作ã‹ã‚‰ãƒªãƒã‚¸ãƒˆãƒªã®çŠ¶æ…‹ã‚’修整ã—よã†ã¨ + 試ã¿ã¾ã™ã€‚ã“れ㯠Mercurial ãŒãã†ã™ã‚‹ã‚ˆã†æ案ã—ãŸã¨ãã®ã¿å¿…è¦ + ã§ã—ょã†ã€‚ + +remove [options] [files ...]:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’リãƒã‚¸ãƒˆãƒªã‹ã‚‰å‰Šé™¤ã™ã‚‹ã“ã¨ã‚’予定ã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã‚’次ã®ã‚³ãƒŸãƒƒãƒˆæ™‚ã«å‰Šé™¤ã™ã‚‹ã“ã¨ã‚’予定ã—ã¾ + ã™ã€‚ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ç¾åœ¨ã®æžã‹ã‚‰å–り除ãã ã‘ã§ã€ãƒ—ãƒã‚¸ã‚§ + クトã®å±¥æ´å…¨ä½“ã‹ã‚‰ã¯å‰Šé™¤ã—ã¾ã›ã‚“。もã—ファイルãŒä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆ + リä¸ã«ã¾ã å˜åœ¨ã—ã¦ã„ã‚Œã°ã€ãれらã¯ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‹ã‚‰å‰Šé™¤ã•ã‚Œ + ã¾ã™ã€‚ + + 別å: rm + +rename <source ...> <dest>:: + コピー先をコピー元ã®ã‚³ãƒ”ーã®ã‚³ãƒ”ーã§ã‚ã‚‹ã¨å°ã‚’ã¤ã‘ã¾ã™; コピー + å…ƒã«å‰Šé™¤ã®å°ã‚’ã¤ã‘ã¾ã™ã€‚ã‚‚ã—コピー先ãŒãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ã‚ã‚Œã°ã€ã‚³ + ピーã¯ãã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã«ç½®ã‹ã‚Œã¾ã™ã€‚ã‚‚ã—コピー先ãŒãƒ•ã‚¡ã‚¤ãƒ«ãª + らã€ã‚³ãƒ”ー元㯠1 ã¤ã®ã¿æŒ‡å®šå¯èƒ½ã§ã™ã€‚ + + デフォルトã§ã¯ã€ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯ãƒ•ã‚¡ã‚¤ãƒ«ãŒãã®ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã« + ã‚ã‚‹ã‚‚ã®ã¨ã—ã¦ãã®å†…容をコピーã—ã¾ã™ã€‚ã‚‚ã— --after ã¨ä¸€ç·’ã«å‘¼ + ã³å‡ºã•ã‚Œã‚Œã°ã€æ“作ã¯è¨˜éŒ²ã•ã‚Œã¾ã™ãŒã€ã‚³ãƒ”ーã¯å®Ÿè¡Œã•ã‚Œã¾ã›ã‚“。 + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯æ¬¡ã®ã‚³ãƒŸãƒƒãƒˆæ™‚ã«åŠ¹æžœã‚’æŒã¡ã¾ã™ã€‚ + + 注æ„: ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯å®Ÿé¨“çš„ã§ã™ã€‚リãƒãƒ¼ãƒ ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’é©åˆ‡ã« + 記録ã§ãã¾ã™ãŒã€ã“ã®æƒ…å ±ã¯ãƒžãƒ¼ã‚¸ã«ã‚ˆã£ã¦ã¾ã 完全ã«ã¯ä½¿ã‚ã‚Œã¾ã› + ã‚“ã—ã€ãƒã‚°ã§å®Œå…¨ã«å ±å‘Šã•ã‚Œã‚‹ã“ã¨ã‚‚ã‚ã‚Šã¾ã›ã‚“。 + + オプション: + -A, --after æ—¢ã«ç™ºç”Ÿã—ãŸãƒªãƒãƒ¼ãƒ を記録ã—ã¾ã™ + -f, --force æ—¢å˜ã®å¤‰æ›´ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã«ç„¡ç†çŸ¢ç†ã‚³ãƒ”ー㗠+ ã¾ã™ + -p, --parents コピー先ã«ã‚³ãƒ”ー元ã®ãƒ‘ã‚¹ã‚’è¿½åŠ ã—ã¾ã™ + + 別å: mv + +revert [names ...]:: + 指定ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚„ディレクトリã®æœªã‚³ãƒŸãƒƒãƒˆã®å¤‰æ›´ã‚’å–り消ã—ã¾ + ã™ã€‚ã“ã‚Œã¯é–¢é€£ã—ãŸãƒ•ã‚¡ã‚¤ãƒ«ã®å†…容をコミットã•ã‚Œã¦ãªã„状態ã«æˆ»ã— + ã¾ã™ã€‚ + + ã‚‚ã—ファイルãŒå‰Šé™¤ã•ã‚Œã¦ã„ã‚Œã°ã€å†ä½œæˆã•ã‚Œã¾ã™ã€‚ã‚‚ã—ファイル㮠+ 実行モードãŒå¤‰æ›´ã•ã‚Œã¦ã„ã‚Œã°ã€ãƒªã‚»ãƒƒãƒˆã•ã‚Œã¾ã™ã€‚ + + ディレクトリãŒæŒ‡å®šã•ã‚ŒãŸå ´åˆã€ãã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã®ã™ã¹ã¦ã®ãƒ•ã‚¡ + イルã¨ã‚µãƒ–ディレクトリãŒå…ƒã«æˆ»ã•ã‚Œã¾ã™ã€‚ + + ã‚‚ã—引数ãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã®å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ + ルã¨ã‚µãƒ–ディレクトリãŒå…ƒã®æˆ»ã•ã‚Œã¾ã™ã€‚ + + オプション: + -r, --rev <rev> å…ƒã«æˆ»ã™å…ˆã®ãƒªãƒ“ジョンを指定ã—ã¾ã™ + -n, --nonrecursive サブディレクトリをå†å¸°çš„ã«è¾¿ã‚‰ãªã„よã†ã« + ã—ã¾ã™ + +root:: + ç¾åœ¨ã®ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’表示ã—ã¾ã™ã€‚ + +serve [options]:: + ãƒãƒ¼ã‚«ãƒ«ã® HTTP リãƒã‚¸ãƒˆãƒªã¨ pull サーãƒã‚’èµ·å‹•ã—ã¾ã™ã€‚ + + デフォルトã§ã¯ã€ã‚µãƒ¼ãƒã¯ã‚¢ã‚¯ã‚»ã‚¹ãƒã‚°ã‚’標準出力ã«ã€ã‚¨ãƒ©ãƒ¼ãƒã‚°ã‚’ + 標準エラー出力ã«å‡ºåŠ›ã—ã¾ã™ã€‚ファイルã«ãƒã‚°ã‚’å–ã‚‹ã«ã¯ "-A" 㨠+ "-E" オプションを使ã£ã¦ãã ã•ã„。 + + オプション: + -A, --accesslog <file> アクセスãƒã‚°ãŒå‡ºåŠ›ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®åå‰ + を指定ã—ã¾ã™ + -E, --errorlog <file> エラーãƒã‚°ãŒå‡ºåŠ›ã•ã‚Œã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®åå‰ã‚’ + 指定ã—ã¾ã™ + -a, --address <addr> 使用ã™ã‚‹ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’指定ã—ã¾ã™ + -p, --port <n> 使用ã™ã‚‹ãƒãƒ¼ãƒˆã‚’指定ã—ã¾ã™ + (デフォルト: 8000) + -n, --name <name> ウェブページã§è¡¨ç¤ºã™ã‚‹åå‰ã‚’指定ã—ã¾ã™ + (デフォルト: working dir) + -t, --templatedir <path> 使用ã™ã‚‹ã‚¦ã‚§ãƒ–ã®é››åž‹ã‚’指定ã—ã¾ã™ + -6, --ipv6 IPv4 ã«åŠ ãˆã¦ IPv6 も使用ã—ã¾ã™ + +status [options] [files]:: + 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã®å¤‰æ›´ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’表示ã—ã¾ã™ã€‚åå‰ãŒæŒ‡å®š + ã•ã‚Œãªã‘ã‚Œã°ã€å…¨ã¦ã®ãƒ•ã‚¡ã‚¤ãƒ«ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚åå‰ãŒæŒ‡å®šã•ã‚Œã‚Œã°ã€ + 指定ã•ã‚ŒãŸåå‰ã«ãƒžãƒƒãƒã—ãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ãŒè¡¨ç¤ºã•ã‚Œã¾ã™ã€‚ + + ファイルã®çŠ¶æ…‹ã‚’表示ã™ã‚‹ã®ã«ä½¿ã‚れる記å·: + + M = 変更ã•ã‚Œã¾ã—㟠+ A = è¿½åŠ ã•ã‚Œã¾ã—㟠+ R = 削除ã•ã‚Œã¾ã—㟠+ ? = ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ä¸‹ã«ã‚ã‚Šã¾ã›ã‚“ + + オプション: + + -m, --modified 変更ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ã‚’表示ã—ã¾ã™ + -a, --added è¿½åŠ ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ã‚’表示ã—ã¾ã™ + -r, --removed 削除ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã®ã¿ã‚’表示ã—ã¾ã™ + -u, --unknown ä¸æ˜Žãª(ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ä¸‹ã«ãªã„)ファイルã®ã¿ + を表示ã—ã¾ã™ + -n, --no-status 状態を示ã™æŽ¥é è¾žã‚’éš ã—ã¾ã™ + -0, --print0 xargs ã¨ä¸€ç·’ã«ä½¿ã†ãŸã‚ã«ã€ãƒ•ã‚¡ã‚¤ãƒ«åã‚’ NUL + ã§çµ‚ãˆã¾ã™ + -I, --include <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’å«ã‚ã¾ + ã™ + -X, --exclude <pat> 与ãˆã‚‰ã‚ŒãŸãƒ‘ターンã«ãƒžãƒƒãƒã—ãŸåå‰ã‚’除外㗠+ ã¾ã™ + +tag [-l -m <text> -d <datecode> -u <user>] <name> [revision]:: + 特定ã®ãƒªãƒ“ジョン㫠<name> を使ã£ã¦åå‰ã‚’付ã‘ã¾ã™ã€‚ + + ã‚¿ã‚°ã¯ãƒªãƒã‚¸ãƒˆãƒªã®ç‰¹å®šã®ãƒªãƒ“ジョンã«åå‰ã‚’付ã‘ã‚‹ãŸã‚ã«ä½¿ã‚れ〠+ ãã—ã¦ç•°ãªã‚‹ãƒªãƒ“ジョンを比較ã—ãŸã‚Šã€é‡è¦ãªä»¥å‰ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«æˆ»ã£ + ãŸã‚Šã€ãƒªãƒªãƒ¼ã‚¹ç‰ã®åˆ†å²ç‚¹ã«å°ã‚’ã¤ã‘ãŸã‚Šã™ã‚‹ã®ã«ä¾¿åˆ©ã§ã™ã€‚ + + ã‚‚ã—ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€tip ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + + ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã€é…布ã€ã‚¿ã‚°ã®ãƒžãƒ¼ã‚¸ã‚’楽ã«ã™ã‚‹ãŸã‚ã«ã€ãれら㯠+ ".hgtags" ã¨ã„ã†åå‰ã®ãƒ•ã‚¡ã‚¤ãƒ«ã«æ ¼ç´ã•ã‚Œã€ä»–ã®ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã®ãƒ•ã‚¡ + イルã¨åŒæ§˜ã«æ‰±ã£ãŸã‚Šã€å¿…è¦ã§ã‚ã‚Œã°æ‰‹ã§ç·¨é›†ã§ãã¾ã™ã€‚ + + オプション: + -l, --local ã‚¿ã‚°ã‚’ãƒãƒ¼ã‚«ãƒ«ã«ã—ã¾ã™ + -m, --message <text> タグをコミットã—ãŸã¨ãã®ãƒã‚°ã®ã‚¨ãƒ³ãƒˆãƒªã® + メッセージを指定ã—ã¾ã™ + -d, --date <datecode> コミットã®æ—¥ä»˜ã‚’指定ã—ã¾ã™ + -u, --user <user> コミットã™ã‚‹ãƒ¦ãƒ¼ã‚¶ã‚’指定ã—ã¾ã™ + + 注æ„: ãƒãƒ¼ã‚«ãƒ«ã®ã‚¿ã‚°ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã‚„é…布ã•ã‚Œã‚‹ã“ã¨ã¯ãªãã€ã¾ + ãŸ. hg/localtags ファイルã«æ ¼ç´ã•ã‚Œã¾ã™ã€‚ã‚‚ã—åŒã˜åå‰ã®ãƒãƒ¼ã‚« + ルã®ã‚¿ã‚°ã¨å…¬é–‹ã•ã‚ŒãŸã‚¿ã‚°ãŒã‚ã‚Œã°ã€ãƒãƒ¼ã‚«ãƒ«ã®ã‚¿ã‚°ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + +tags:: + リãƒã‚¸ãƒˆãƒªã®ã‚¿ã‚°ã‚’列挙ã—ã¾ã™ã€‚ + + ã“ã‚Œã¯é€šå¸¸ã®ã‚¿ã‚°ã¨ãƒãƒ¼ã‚«ãƒ«ã®ã‚¿ã‚°ã‚’両方列挙ã—ã¾ã™ã€‚ + +tip:: + tip ã®ãƒªãƒ“ジョンを表示ã—ã¾ã™ã€‚ + +unbundle <file>:: + (実験的) + + bundle コマンドã§ç”Ÿæˆã•ã‚ŒãŸã€åœ§ç¸®æ¸ˆã¿ãƒã‚§ãƒ³ã‚¸ã‚°ãƒ«ãƒ¼ãƒ—ファイル + ã‚’é©ç”¨ã—ã¾ã™ã€‚ + +undo:: + 最後㮠commit ã‚„ pull ã®å‡¦ç†ã‚’å–り消ã—ã¾ã™ã€‚ + + リãƒã‚¸ãƒˆãƒªã®æœ€å¾Œã® pull ã‚„ commit 処ç†ã‚’巻戻ã—ã€ãƒ—ãƒã‚¸ã‚§ã‚¯ãƒˆã‚’ + ãれよりå‰ã®çŠ¶æ…‹ã«æˆ»ã—ã¾ã™ã€‚ + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯æ³¨æ„ã—ã¦ä½¿ã£ã¦ãã ã•ã„。ã¾ã 1回㮠undo ã ã‘ã§ã€ + redo ã¯ã‚ã‚Šã¾ã›ã‚“。 + + ã“ã®ã‚³ãƒžãƒ³ãƒ‰ã¯å…¬é–‹ã—ãŸãƒªãƒã‚¸ãƒˆãƒªã§ä½¿ã‚れるã“ã¨ã¯æƒ³å®šã—ã¦ã„ã¾ã› + ん。ã„ã£ãŸã‚“ä»–ã®ãƒ¦ãƒ¼ã‚¶ã‹ã‚‰ pull ã§å¤‰æ›´ãŒè¦‹ãˆã‚‹ã‚ˆã†ã«ãªã‚Œã°ã€ãƒãƒ¼ + カルã§ãれをå–り消ã—ã¦ã‚‚æ„味ãŒã‚ã‚Šã¾ã›ã‚“。 + +update [-m -C] [revision]:: + 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’指定ã•ã‚ŒãŸãƒªãƒ“ジョンã«æ›´æ–°ã—ã¾ã™ã€‚ + + デフォルトã§ã¯ã€æ›´æ–°ã«ã‚ˆã‚Šãƒãƒ¼ã‚«ãƒ«ã®å¤‰æ›´ã‚’マージã—ãŸã‚Šç ´æ£„ã—㟠+ ã‚Šã™ã‚‹ã“ã¨ãŒå¿…è¦ã¨ãªã‚‹ã¨ãã€update ã¯ãれを拒å¦ã—ã¾ã™ã€‚ + + -m オプションã§ã€ãƒžãƒ¼ã‚¸ãŒå®Ÿè¡Œã•ã‚Œã¾ã™ã€‚ + + -C オプションã§ã€ãƒãƒ¼ã‚«ãƒ«ã®å¤‰æ›´ãŒå¤±ã‚ã‚Œã¾ã™ã€‚ + + オプション: + -m, --merge æžã®ãƒžãƒ¼ã‚¸ã‚’許å¯ã—ã¾ã™ + -C, --clean ãƒãƒ¼ã‚«ãƒ«ã§å¤‰æ›´ã•ã‚ŒãŸãƒ•ã‚¡ã‚¤ãƒ«ã‚’上書ãã—ã¾ã™ + + 別å: up checkout co + +verify:: + ç¾åœ¨ã®ãƒªãƒã‚¸ãƒˆãƒªã®æ•´åˆæ€§ã‚’検証ã—ã¾ã™ã€‚ + + ã“ã‚Œã¯ãƒªãƒã‚¸ãƒˆãƒªã®æ•´åˆæ€§ã‚’å…¨é¢çš„ã«ãƒã‚§ãƒƒã‚¯ã—ã€ãƒã‚§ãƒ³ã‚¸ãƒã‚°ã®å„ + エントリã€manifest, 管ç†ä¸‹ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®ãƒãƒƒã‚·ãƒ¥ã¨ãƒã‚§ãƒƒã‚¯ã‚µãƒ ã‚’ + 検証ã—ã€ã¾ãŸã‚¯ãƒã‚¹ãƒªãƒ³ã‚¯ã¨ã‚¤ãƒ³ãƒ‡ã‚¯ã‚¹ã®æ•´åˆæ€§ã‚‚検証ã—ã¾ã™ã€‚ + +ファイルåã¨ãƒ‘ターン +--------- + + Mercurial ã§ã¯åŒæ™‚ã«è¤‡æ•°ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‚’è˜åˆ¥ã™ã‚‹ã®ã«è¤‡æ•°ã®è¨˜æ³•ãŒä½¿ + ãˆã¾ã™ã€‚ + + デフォルトã§ã¯ã€Mercurial ã¯ãƒ•ã‚¡ã‚¤ãƒ«åをシェルã®ã‚¹ã‚¿ã‚¤ãƒ«ã®æ‹¡å¼µ + glob パターンã¨ã—ã¦æ‰±ã„ã¾ã™ã€‚ + + 別ã®ãƒ‘ターン表記ã¯æ˜Žç¤ºçš„ã«æŒ‡å®šã™ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + パターンマッãƒãƒ³ã‚°ãªã—ã®å˜ç´”ãªãƒ‘スåを使ã†ã«ã¯ã€ãƒ‘スåã‚’ + "path:" ã§å§‹ã‚ã¦ãã ã•ã„。ã“れらã®ãƒ‘スåã¯ã€ç¾åœ¨ã®ãƒªãƒã‚¸ãƒˆãƒªã® + ルートã‹ã‚‰å®Œå…¨ã«ãƒžãƒƒãƒã—ã¦ã„ã‚‹å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + æ‹¡å¼µ glob を使ã†ã«ã¯ã€åå‰ã‚’ "glob:" ã§å§‹ã‚ã¦ãã ã•ã„。glob 㯠+ ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ã¿ã«é©ç”¨ã•ã‚Œã¾ã™: "*.c" ã¨ã„ã£ãŸ glob ã¯ç¾ + 在ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã® ".c" ã§çµ‚ã‚るファイルã®ã¿ã«ãƒžãƒƒãƒã—ã¾ã™ã€‚ + + サãƒãƒ¼ãƒˆã•ã‚Œã¦ã„ã‚‹ glob 文法ã®æ‹¡å¼µã¯ãƒ‘スã®åˆ†é›¢è¨˜å·ã‚’越ãˆã¦å…¨ã¦ + ã®æ–‡å—列ã«ãƒžãƒƒãƒã™ã‚‹ "**" ã¨ã€"a ã¾ãŸã¯ b" ã‚’æ„味ã™ã‚‹ "{a,b}" + ã§ã™ã€‚ + + Perl/Python ã®æ£è¦è¡¨ç¾ã‚’使ã†ã«ã¯ã€åå‰ã‚’ "re:" ã§å§‹ã‚ã¦ãã ã• + ã„。æ£è¦è¡¨ç¾ã«ã‚ˆã‚‹ãƒžãƒƒãƒã¯ãƒªãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆã®å›ºå®šã•ã‚Œã¦ã„ã¾ã™ã€‚ + + å˜ç´”ãªä¾‹: + + path:foo/bar リãƒã‚¸ãƒˆãƒªã®ãƒ«ãƒ¼ãƒˆã«ã‚ã‚‹ foo ã¨ã„ã†ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒª + ã® bar ã¨ã„ã†åå‰ + path:path:name "path:name" ã¨ã„ã†åå‰ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¾ãŸã¯ãƒ‡ã‚£ãƒ¬ã‚¯ + トリ + + Glob ã®ä¾‹: + + glob:*.c ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã® ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + *.c ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ã® ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + **.c ç¾åœ¨ã®ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã¨å…¨ã¦ã®ã‚µãƒ–ディレクトリä¸ã® + ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + foo/*.c ディレクトリ foo ä¸ã® ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + foo/**.c ディレクトリ foo ã¨ãã®å…¨ã¦ã®ã‚µãƒ–ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªä¸ + ã® ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + + æ£è¦è¡¨ç¾ã®ä¾‹: + + re:.*\.c$ リãƒã‚¸ãƒˆãƒªå…¨ä½“ã®ä¸ã® ".c" ã§çµ‚ã‚ã‚‹å…¨ã¦ã®åå‰ + + +å˜ä¸€ã®ãƒªãƒ“ジョンã®æŒ‡å®šæ³• +----------- + + Mercurial ã§ã¯å€‹ã€…ã®ãƒªãƒ“ジョンをè˜åˆ¥ã™ã‚‹ã®ã«è¤‡æ•°ã®è¨˜æ³•ãŒä½¿ãˆã¾ + ã™ã€‚ + + å˜ç´”ãªæ•´æ•°ã¯ãƒªãƒ“ジョンナンãƒãƒ¼ã¨ã—ã¦æ‰±ã‚ã‚Œã¾ã™ã€‚è² ã®æ•´æ•°ã¯tip + ã‹ã‚‰ã®ã‚ªãƒ•ã‚»ãƒƒãƒˆã¨ã—ã¦æ‰±ã‚ã‚Œã€-1 ㌠tip を表ã—ã¾ã™ã€‚ + + 40 æ¡ã® 16 進数ã®æ–‡å—列ã¯ãƒ¦ãƒ‹ãƒ¼ã‚¯ãªãƒªãƒ“ジョンè˜åˆ¥åã¨ã—ã¦æ‰±ã‚ + ã‚Œã¾ã™ã€‚ + + 40 æ¡ã‚ˆã‚Šå°‘ãªã„ 16 進数ã®æ–‡å—列ã¯ãƒ¦ãƒ‹ãƒ¼ã‚¯ãªãƒªãƒ“ジョンè˜åˆ¥å㨠+ ã—ã¦æ‰±ã‚ã‚Œã€çŸã„å½¢å¼ã®è˜åˆ¥åã¨å‘¼ã°ã‚Œã¾ã™ã€‚çŸã„å½¢å¼ã®è˜åˆ¥åã¯ã + ã‚ŒãŒå®Œå…¨ãªé•·ã•ã®è˜åˆ¥åã®æŽ¥é 語ã§ã‚ã‚‹ã¨ãã ã‘有効ã§ã™ã€‚ + + ä»–ã®æ–‡å—列ã¯å…¨ã¦ã‚¿ã‚°åã¨ã—ã¦æ‰±ã‚ã‚Œã¾ã™ã€‚ã‚¿ã‚°ã¯ã‚ã‚‹ãƒªãƒ“ã‚¸ãƒ§ãƒ³è˜ + 別åã«é–¢é€£ä»˜ã‘られãŸã‚·ãƒ³ãƒœãƒ«åã§ã™ã€‚ã‚¿ã‚°å㯠":" æ–‡å—ã‚’å«ã‚“㧠+ ã¯ã„ã‘ã¾ã›ã‚“。 + + リビジョンå "tip" ã¯ç‰¹åˆ¥ãªã‚¿ã‚°ã§ã€å¸¸ã«ä¸€ç•ªæœ€æ–°ã®ãƒªãƒ“ジョンを + 指ã—ã¾ã™ã€‚ + +複数ã®ãƒªãƒ“ジョンã®æŒ‡å®šæ³• +----------- + + Mercurial ㌠1ã¤ã‚ˆã‚Šå¤šãã®ãƒªãƒ“ジョンをå—ã‘ã„れるã¨ãã€ãれら㯠+ 別々ã«æŒ‡å®šã•ã‚Œã‚‹ã‹ã€é€£ç¶šã—ãŸç¯„囲ã¨ã—㦠":" æ–‡å—ã§åŒºåˆ‡ã£ã¦ä¸Žãˆ + られるã‹ã‚‚ã‚Œã¾ã›ã‚“。 + + 範囲表記ã®æ§‹æ–‡ã¯ [BEGIN]:[END] 㧠BEGIN 㨠END ã¯ãƒªãƒ“ジョン㮠+ è˜åˆ¥åã§ã™ã€‚BEGIN ã‚‚ END も両方ã¨ã‚‚ä»»æ„ã§ã™ã€‚ã‚‚ã— BEGIN ãŒæŒ‡å®š + ã•ã‚Œãªã‘ã‚Œã°ã€ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ãƒªãƒ“ジョンナンãƒãƒ¼ 0 ã«ãªã‚Šã¾ã™ã€‚ã‚‚ + ã— END ãŒæŒ‡å®šã•ã‚Œãªã‘ã‚Œã°ã€ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã§ tip ã«ãªã‚Šã¾ã™ã€‚従ã£ã¦ + 範囲 ":" 㯠"å…¨ã¦ã®ãƒªãƒ“ジョン" ã‚’æ„味ã—ã¾ã™ã€‚ + + ã‚‚ã— BEGIN ㌠END より大ãã‘ã‚Œã°ã€ãƒªãƒ“ジョンã¯é€†ã®é †åºã¨ã—ã¦æ‰± + ã‚ã‚Œã¾ã™ã€‚ + + 範囲ã¯é–‰åŒºé–“ã¨ã—ã¦å‹•ä½œã—ã¾ã™ã€‚ã“ã‚Œã¯ç¯„囲㌠3:5 㯠3,4,5 ã«ãªã‚‹ + ã“ã¨ã‚’æ„味ã—ã¾ã™ã€‚åŒæ§˜ã«ã€ç¯„囲 4:2 㯠4,3,2 ã«ãªã‚Šã¾ã™ã€‚ + +環境変数 +---- + +HGEDITOR:: + ã“ã‚Œã¯ã‚³ãƒŸãƒƒãƒæ™‚ã«ä½¿ã‚れるエディタã®åå‰ã§ã™ã€‚デフォルトã§ã¯ + EDITOR ã®å€¤ãŒä½¿ã‚ã‚Œã¾ã™ã€‚ + + (廃æ¢äºˆå®šã§ã™, .hgrc を使ã£ã¦ãã ã•ã„) + +HGMERGE:: + merge 時ã®è¡çªã‚’解決ã™ã‚‹ã®ã«ä½¿ã‚れる実行ファイルã§ã™ã€‚プãƒã‚°ãƒ© + ムã¯3 ã¤ã®å¼•æ•°ã§å®Ÿè¡Œã•ã‚Œã¾ã™: ãƒãƒ¼ã‚«ãƒ«ã®ãƒ•ã‚¡ã‚¤ãƒ«ã€ãƒªãƒ¢ãƒ¼ãƒˆã®ãƒ•ã‚¡ + イルã€1 世代å‰ã®ãƒ•ã‚¡ã‚¤ãƒ«ã§ã™ã€‚ + + デフォルトã®ãƒ—ãƒã‚°ãƒ©ãƒ 㯠"hgmerge" ã§ã€ã“れ㯠Mercurial ã«ã‚ˆã£ + ã¦æä¾›ã•ã‚Œã‚‹å¸¸è˜çš„ãªè¨å®šã®ã‚·ã‚§ãƒ«ã‚¹ã‚¯ãƒªãƒ—トã§ã™ã€‚ + + (廃æ¢äºˆå®šã§ã™, .hgrc を使ã£ã¦ãã ã•ã„) + +HGUSER:: + ã“ã‚Œã¯ã‚³ãƒŸãƒƒãƒˆæ™‚ã®è‘—者ã¨ã—ã¦ä½¿ã‚れる文å—列ã§ã™ã€‚ + + (廃æ¢äºˆå®šã§ã™, .hgrc を使ã£ã¦ãã ã•ã„) + +EMAIL:: + ã‚‚ã— HGUSER ãŒè¨å®šã•ã‚Œã¦ã„ãªã‘ã‚Œã°ã€ã“ã‚ŒãŒã‚³ãƒŸãƒƒãƒˆæ™‚ã®è‘—者ã¨ã— + ã¦ä½¿ã‚ã‚Œã¾ã™ã€‚ + +LOGNAME:: + ã‚‚ã— HGUSER ã‚‚ EMAIL ã‚‚è¨å®šã•ã‚Œã¦ã„ãªã‘ã‚Œã°ã€ã‚³ãƒŸãƒƒãƒˆæ™‚ã®è‘—者 + ã¨ã—ã¦LOGNAME ãŒ('@hostname' を付ã‘ãŸå½¢ã§)使ã‚ã‚Œã¾ã™ã€‚ + +EDITOR:: + ã“れ㯠hgmerge スクリプトä¸ã§ä½¿ã‚れるエディタã®åå‰ã§ã™ã€‚ã‚‚ã— + HGEDITOR ãŒè¨å®šã•ã‚Œã¦ã„ãªã‘ã‚Œã°ã€ã‚³ãƒŸãƒƒãƒˆæ™‚ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã«ä½¿ã‚ + ã‚Œã¾ã™ã€‚デフォルト㯠'vi' ã§ã™ã€‚ + +PYTHONPATH:: + ã“ã‚Œã¯ã‚¤ãƒ³ãƒãƒ¼ãƒˆã•ã‚Œã‚‹ãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã‚’見ã¤ã‘ã‚‹ãŸã‚ã« Python ã«ã‚ˆã£ + ã¦ä½¿ã‚ã‚Œã€Mercurial ãŒã‚·ã‚¹ãƒ†ãƒ 全体ã«ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•ã‚Œã¦ã„ãªã‘ã‚Œ + ã°ã€é©åˆ‡ã«è¨å®šã•ã‚Œã‚‹å¿…è¦ãŒã‚ã‚‹ã§ã—ょã†ã€‚ + +ファイル +---- + .hgignore:: + ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯(1è¡Œã”ã¨ã«) hg ã«ã‚ˆã£ã¦ç„¡è¦–ã•ã‚Œã‚‹ã¹ãファイルを + 記述ã—ãŸæ£è¦è¡¨ç¾ã‚’å«ã¿ã¾ã™ã€‚ + + .hgtags:: + ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯ãƒªãƒã‚¸ãƒˆãƒªã®å†…容ã®ã‚¿ã‚°ä»˜ã‘ã•ã‚ŒãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ã«ä¸€è‡´ + ã—ãŸãƒãƒƒã‚·ãƒ¥å€¤ã¨ãƒ†ã‚ストã®ã‚¿ã‚°å(ãã‚Œãžã‚Œã¯ç©ºç™½ã§åŒºåˆ‡ã‚‰ã‚Œã¾ã™)ã‚’ + å«ã¿ã¾ã™ã€‚ + + /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc:: + ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯ãƒ‡ãƒ•ã‚©ãƒ«ãƒˆã®è¨å®šã‚’å«ã¿ã¾ã™ã€‚.hg/hgrc ã®å€¤ã¯ + $HOME/.hgrc ã®è¨å®šã‚’上書ãã—ã€$HOME/.hgrc ã®è¨å®šã¯ã‚°ãƒãƒ¼ãƒãƒ«ãª + /etc/mercurial/hgrc ã®è¨å®šã‚’上書ãã—ã¾ã™ã€‚ã“れらã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å†… + 容ã¨æ›¸å¼ã®è©³ç´°ã«ã¤ã„ã¦ã¯ hgrc(5) ã‚’å‚ç…§ã—ã¦ãã ã•ã„。 + +ãƒã‚° +-- +沢山ã‚ã‚‹ã§ã—ょã†ã‹ã‚‰ã€ã‚‚ã—ãƒã‚°ã‚’見ã¤ã‘ãŸã‚‰ãれをメーリングリスト +(下ã®æƒ…å ±æºã‚’å‚ç…§)ã«é€ã£ã¦ãã ã•ã„。 + +é–¢é€£é …ç›® +---- +hgrc(5) + +著者 +-- +Matt Mackall <mpm@selenic.com> ã«ã‚ˆã‚Šæ›¸ã‹ã‚Œã¾ã—ãŸã€‚ + +æƒ…å ±æº +--- +http://selenic.com/mercurial[主ãªã‚¦ã‚§ãƒ–サイト] + +http://www.serpentine.com/mercurial[Wiki サイト] + +http://selenic.com/hg[ソースコードã®ãƒªãƒã‚¸ãƒˆãƒª] + +http://selenic.com/mailman/listinfo/mercurial[メーリングリスト] + +è‘—ä½œæ¨©æƒ…å ± +----- +Copyright (C) 2005 Matt Mackall. +ã“ã®ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã®è‡ªç”±ãªä½¿ç”¨ã¯ GNU 一般公有使用許諾 (GPL) ã®ã‚‚ã¨ã§ +èªã‚られã¾ã™ã€‚
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/hgmerge.1.ja.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,37 @@ +HGMERGE(1) +========== +Matt Mackall <mpm@selenic.com> +v0.1, 27 May 2005 + +åå‰ +-- +hgmerge - Mercurial ソースコード管ç†ã‚·ã‚¹ãƒ†ãƒ ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’マージã™ã‚‹ +ã®ã«ä½¿ã‚れるデフォルトã®ãƒ©ãƒƒãƒ‘ー + +æ›¸å¼ +-- +'hgmerge' local ancestor remote + +説明 +-- +hgmerge(1) コマンド㯠Mercurial システムã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’マージã™ã‚‹ãŸã‚ +ã®ã‚°ãƒ©ãƒ•ã‚£ã‚«ãƒ«ãªã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ã‚¤ã‚¹ã‚’æä¾›ã—ã¾ã™ã€‚ã“れ㯠kdiff3, +merge(1), tkdiff(1), ã¾ãŸã¯å˜ç´”ã« diff(1) 㨠patch(1) ã®ãƒ©ãƒƒãƒ‘ーã§ã€ +ã©ã‚ŒãŒã‚·ã‚¹ãƒ†ãƒ 上ã«ã‚ã‚‹ã‹ã«ä¾å˜ã—ã¾ã™ã€‚ + +hgmerge(1) 㯠Mercurial ソースコード管ç†ã‚·ã‚¹ãƒ†ãƒ ã§ç’°å¢ƒå¤‰æ•° +HGMERGE ãŒè¨å®šã•ã‚Œã¦ã„ãªã„å ´åˆã«ä½¿ã‚ã‚Œã¾ã™ã€‚ + +著者 +-- +Vincent Danjean <Vincent.Danjean@free.fr> ã«ã‚ˆã£ã¦æ›¸ã‹ã‚Œã¾ã—ãŸã€‚ + +é–¢é€£æƒ…å ± +-- +hg(1) - Mercurial システムã¸ã®ã‚³ãƒžãƒ³ãƒ‰ãƒ©ã‚¤ãƒ³ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ã‚¤ã‚¹ + +è‘—ä½œæ¨©æƒ…å ± +---- +Copyright (C) 2005 Matt Mackall. +ã“ã®ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã®è‡ªç”±ãªä½¿ç”¨ã¯ GNU 一般公有使用許諾 (GPL) ã®ã‚‚ã¨ã§ +èªã‚られã¾ã™ã€‚
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ja/hgrc.5.ja.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,204 @@ +HGRC(5) +======= +Bryan O'Sullivan <bos@serpentine.com> + +åå‰ +-- +hgrc - Mercurial ã®è¨å®šãƒ•ã‚¡ã‚¤ãƒ« + +æ›¸å¼ +-- + +Mercurial システムã¯ãã®æŒ¯èˆžã„ã®æ£å¸¸ã‚’制御ã™ã‚‹ã®ã«ã€ä¸€é€£ã®è¨å®šãƒ•ã‚¡ +イルを使用ã—ã¾ã™ã€‚ + +ファイル +---- + +Mercurial 㯠3ã¤ã®ãƒ•ã‚¡ã‚¤ãƒ«ã‹ã‚‰è¨å®šã‚’èªã¿ã¾ã™: + +/etc/mercurial/hgrc:: + ã“ã®ã‚°ãƒãƒ¼ãƒãƒ«ã®è¨å®šãƒ•ã‚¡ã‚¤ãƒ«ã®ã‚ªãƒ—ションã¯å®Ÿè¡Œã—ãŸãƒ¦ãƒ¼ã‚¶ã€ãƒ‡ã‚£ + レクトリをå•ã‚ãšå…¨ã¦ã® Mercurial コマンドã«é©ç”¨ã•ã‚Œã¾ã™ã€‚ + +$HOME/.hgrc:: + ユーザ毎ã®è¨å®šã‚ªãƒ—ションã§ã€ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’å•ã‚ãšå…¨ã¦ã® + Mercurial コマンドã«é©ç”¨ã•ã‚Œã¾ã™ã€‚ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å€¤ã¯ã‚°ãƒãƒ¼ãƒãƒ« + ã®è¨å®šã‚’上書ãã—ã¾ã™ã€‚ + +<repo>/.hg/hgrc:: + リãƒã‚¸ãƒˆãƒªæ¯Žã®è¨å®šã‚ªãƒ—ションã§ã€ãã®ãƒªãƒã‚¸ãƒˆãƒªã®ã¿ã«é©ç”¨ã•ã‚Œã¾ + ã™ã€‚ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³ç®¡ç†ã•ã‚Œãšã€ "clone" æ“作ã§è»¢é€ã• + れるã“ã¨ã‚‚ã‚ã‚Šã¾ã›ã‚“。ã“ã®ãƒ•ã‚¡ã‚¤ãƒ«ã®å€¤ã¯ã‚°ãƒãƒ¼ãƒãƒ«ã®è¨å®šã¨ãƒ¦ãƒ¼ + ザ毎ã®è¨å®šã‚’上書ãã—ã¾ã™ã€‚ + +構文 +-- + +è¨å®šãƒ•ã‚¡ã‚¤ãƒ«ã¯ "[セクション]" ヘッダã‹ã‚‰å§‹ã¾ã‚‹ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã¨ã€ãれ㫠+続ã"åå‰: 値"ã®ã‚¨ãƒ³ãƒˆãƒªã‹ã‚‰æˆã‚Šã¾ã™: "åå‰=値"ã‚‚èªã‚られã¾ã™ã€‚ + + [spam] + eggs=ham + green= + eggs + +å„è¡Œã¯1ã¤ã®ã‚¨ãƒ³ãƒˆãƒªã‚’å«ã¿ã¾ã™ã€‚ã‚‚ã—次ã®è¡ŒãŒã‚¤ãƒ³ãƒ‡ãƒ³ãƒˆã•ã‚Œã¦ã„ãŸå ´ +åˆã€ãã‚Œã¯å‰ã®è¡Œã®ç¶šãã¨ã—ã¦æ‰±ã‚ã‚Œã¾ã™ã€‚ + +先行ã™ã‚‹ç©ºç™½ã¯å€¤ã‹ã‚‰å–り除ã‹ã‚Œã¾ã™ã€‚空行ã¯èªã¿é£›ã°ã•ã‚Œã¾ã™ã€‚ + +オプションã®å€¤ã¯åŒã˜ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã‚„ã€ç‰¹åˆ¥ãª DEFAULT セクションã®åˆ¥ã® +値をå‚ç…§ã™ã‚‹ãƒ•ã‚©ãƒ¼ãƒžãƒƒãƒˆæ–‡å—列をå«ã‚€ã“ã¨ãŒã§ãã¾ã™ã€‚ + +"#" ã‚„ ";" ã§å§‹ã¾ã‚‹è¡Œã¯ç„¡è¦–ã•ã‚Œã‚‹ã®ã§ã€ã‚³ãƒ¡ãƒ³ãƒˆã¨ã—ã¦ä½¿ã†ã“ã¨ãŒã§ +ãã¾ã™ã€‚ + +セクション +----- + +ã“ã®ã‚»ã‚¯ã‚·ãƒ§ãƒ³ã¯ Merucurial ã® "hgrc" ã«ä½¿ã†ã“ã¨ãŒã§ãã‚‹ç•°ãªã£ãŸã‚» +クションã®ãã‚Œãžã‚Œã®ç›®çš„ã‚„å¯èƒ½ãªã‚ーã€ãã—ã¦å–り得る値ã«ã¤ã„ã¦è¨˜è¿° +ã—ã¾ã™ã€‚ + +decode/encode:: + checkout/checkin ã§ãƒ•ã‚¡ã‚¤ãƒ«ã‚’転é€ã™ã‚‹ã¨ãã®ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼ã§ã™ã€‚ã“ã‚Œ + ã¯å…¸åž‹çš„ã«ã¯æ”¹è¡Œã‚’処ç†ã—ãŸã‚Šã€ä»–ã®åœ°åŸŸåŒ–/標準化ã«ä½¿ã‚れるã§ã—ょ + ã†ã€‚ + + フィルターã¯ãƒ•ã‚£ãƒ«ã‚¿ãƒ¼ãƒ‘ターンã¨ãã‚Œã«ç¶šãフィルターコマンドã‹ã‚‰ + ãªã‚Šã¾ã™ã€‚コマンドã¯æ¨™æº–入力ã‹ã‚‰ã®ãƒ‡ãƒ¼ã‚¿ã‚’å—ã‘付ã‘ã€å¤‰æ›ã—ãŸãƒ‡ãƒ¼ + タを標準出力ã«è¿”ã™å¿…è¦ãŒã‚ã‚Šã¾ã™ã€‚ + + 例: + + [encode] + # delta 圧縮を改善ã™ã‚‹ãŸã‚ã«ãƒã‚§ãƒƒã‚¯ã‚¤ãƒ³æ™‚ã« gzip ファイルを + # 伸長ã—ã¾ã™ + # 注æ„: å¿…ãšã—も良ã„アイディアã§ã¯ã‚ã‚Šã¾ã›ã‚“。ãŸã ã®ä¾‹ã§ã™ + *.gz = gunzip + + [decode] + # 作æ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã«æ›¸ã出ã™ã¨ãã«ãƒ•ã‚¡ã‚¤ãƒ«ã‚’ gzip ã§å†åœ§ç¸®ã—ã¾ã™ + *.gz = gzip + +hooks:: + コミットã®é–‹å§‹ã€çµ‚了時ãªã©æ§˜ã€…ãªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã§è‡ªå‹•çš„ã«å®Ÿè¡Œã•ã‚Œã‚‹ã‚³ + マンドã§ã™ã€‚ + changegroup;; + push ã‚„ pull ã§ãƒã‚§ãƒ³ã‚¸ã‚°ãƒ«ãƒ¼ãƒ—ãŒåŠ ãˆã‚‰ã‚ŒãŸã‚ã¨ã«èµ·å‹•ã—ã¾ã™ã€‚ + commit;; + ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆãŒä½œæˆã•ã‚ŒãŸå¾Œã«èµ·å‹•ã—ã¾ã™ã€‚æ–°ã—ã作æˆã•ã‚ŒãŸãƒã‚§ + ンジセット㮠ID ãŒæ¸¡ã•ã‚Œã¾ã™ã€‚ + precommit;; + コミットå‰ã«èµ·å‹•ã—ã¾ã™ã€‚終了ステータス 0 ã«ã‚ˆã‚Šã‚³ãƒŸãƒƒãƒˆã‚’続行 + ã—ã¾ã™ã€‚éžã‚¼ãƒã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹ã§ã‚³ãƒŸãƒƒãƒˆã¯å¤±æ•—ã—ã¾ã™ã€‚ + +http_proxy:: + HTTP プãƒã‚シを通ã—ã¦ã‚¦ã‚§ãƒ–を使ã£ãŸ Mercurial ã®ãƒªãƒã‚¸ãƒˆãƒªã«ã‚¢ã‚¯ + セスã™ã‚‹ã®ã«ä½¿ã‚ã‚Œã¾ã™ã€‚ + host;; + プãƒã‚シサーãƒã®ãƒ›ã‚¹ãƒˆåã¨(オプションã®)ãƒãƒ¼ãƒˆã§ã€ä¾‹ãˆã° + "myproxy:8000"ãªã©ã§ã™ã€‚ + no;; + オプションã§ã™ã€‚コンマã§åŒºåˆ‡ã‚‰ã‚ŒãŸãƒ—ãƒã‚シを通éŽã™ã¹ãホストå + ã®ãƒªã‚¹ãƒˆã§ã™ã€‚ + passwd;; + オプションã§ã™ã€‚プãƒã‚シサーãƒã®èªè¨¼ç”¨ã®ãƒ‘スワードã§ã™ã€‚ + user;; + オプションã§ã™ã€‚プãƒã‚シサーãƒã®èªè¨¼ç”¨ã®ãƒ¦ãƒ¼ã‚¶åã§ã™ã€‚ + +paths:: + リãƒã‚¸ãƒˆãƒªã«ã‚·ãƒ³ãƒœãƒ«åを割当ã¦ã¾ã™ã€‚å·¦å´ãŒã‚·ãƒ³ãƒœãƒ«åã§ã€å³å´ãŒãƒª + ãƒã‚¸ãƒˆãƒªã®å ´æ‰€ã‚’示ã™ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚„ URL ã§ã™ã€‚ + +ui:: + ユーザインターフェースã®è¨å®šã§ã™ã€‚ + debug;; + デãƒãƒƒã‚°æƒ…å ±ã‚’è¡¨ç¤ºã—ã¾ã™ã€‚True ã‹ False ã‚’å–ã‚Šã¾ã™ã€‚デフォルト + ã§ã¯ False ã§ã™ã€‚ + editor;; + コミットä¸ã«ä½¿ç”¨ã™ã‚‹ã‚¨ãƒ‡ã‚£ã‚¿ã§ã™ã€‚デフォルト㯠$EDITOR ã‹ + "vi" ã§ã™ã€‚ + interactive;; + ユーザã«å¯¾ã—ã¦ãƒ—ãƒãƒ³ãƒ—トを出ã™ã‚ˆã†ã«ã—ã¾ã™ã€‚True ã‹ False ã‚’å– + ã‚Šã¾ã™ã€‚デフォルトã§ã¯ True ã§ã™ã€‚ + merge;; + 手動ã§ã® merge ä¸ã«è¡çªã‚’解決ã™ã‚‹ãŸã‚ã«ä½¿ã‚れるプãƒã‚°ãƒ©ãƒ ã§ã™ã€‚ + デフォルト㯠"hgmerge" ã§ã™ã€‚ + quiet;; + 表示ã•ã‚Œã‚‹å‡ºåŠ›ã®é‡ã‚’減らã—ã¾ã™ã€‚True ã‹ False ã‚’å–ã‚Šã¾ã™ã€‚デフォ + ルト㯠False ã§ã™ã€‚ + remotecmd;; + clone/push/pull æ“作ã§ä½¿ã‚れるリモートã®ã‚³ãƒžãƒ³ãƒ‰ã§ã™ã€‚デフォル + ト㯠'hg' ã§ã™ã€‚ + ssh;; + SSH 接続ã§ä½¿ã‚れるコマンドã§ã™ã€‚デフォルト㯠'ssh' ã§ã™ã€‚ + username;; + コミットを実行ã—ãŸã¨ãã«ä½œæˆã•ã‚Œã‚‹ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã®ã‚³ãƒŸãƒƒã‚¿ã§ã™ã€‚ + 一般的ã«ã¯äººåã¨é›»åメールアドレスã§ã€ä¾‹ãˆã° "Fred Widget + <fred@example.com>" ãªã©ã§ã™ã€‚デフォルト㯠$EMAIL ã‹ + username@hostname ã§ã™ã€‚ + verbose;; + 表示ã•ã‚Œã‚‹å‡ºåŠ›ã®é‡ã‚’増やã—ã¾ã™ã€‚True ã‹ False ã‚’å–ã‚Šã¾ã™ã€‚デフォ + ルト㯠False ã§ã™ã€‚ + +web:: + ウェブインターフェイスã®è¨å®šã§ã™ã€‚ + accesslog;; + アクセスãƒã‚°ã®å‡ºåŠ›å…ˆã§ã™ã€‚デフォルトã§ã¯æ¨™æº–出力ã§ã™ã€‚ + address;; + ãƒã‚¤ãƒ³ãƒ‰ã™ã‚‹ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ã‚§ã‚¤ã‚¹ã‚¢ãƒ‰ãƒ¬ã‚¹ã§ã™ã€‚デフォルトã§ã¯å…¨ã¦ã§ + ã™ã€‚ + allowbz2;; + リãƒã‚¸ãƒˆãƒªã®ãƒªãƒ“ジョンã‹ã‚‰ .tar.bz2 をダウンãƒãƒ¼ãƒ‰ã•ã›ã‚‹ã‹ã©ã† + ã‹ã§ã™ã€‚デフォルトã§ã¯ false ã§ã™ã€‚ + allowgz;; + リãƒã‚¸ãƒˆãƒªã®ãƒªãƒ“ジョンã‹ã‚‰ .tar.gz をダウンãƒãƒ¼ãƒ‰ã•ã›ã‚‹ã‹ã©ã† + ã‹ã§ã™ã€‚デフォルトã§ã¯ false ã§ã™ã€‚ + allowpull;; + リãƒã‚¸ãƒˆãƒªã‹ã‚‰ pull ã•ã›ã‚‹ã‹ã©ã†ã‹ã§ã™ã€‚デフォルトã§ã¯ true 㧠+ ã™ã€‚ + allowzip;; + リãƒã‚¸ãƒˆãƒªã®ãƒªãƒ“ジョンã‹ã‚‰ .zip をダウンãƒãƒ¼ãƒ‰ã•ã›ã‚‹ã‹ã©ã†ã‹ã§ + ã™ã€‚デフォルトã§ã¯ false ã§ã™ã€‚ã“ã®æ©Ÿèƒ½ã¯ä¸€æ™‚ファイルを作æˆã— + ã¾ã™ã€‚ + description;; + リãƒã‚¸ãƒˆãƒªã®ç›®çš„や内容ã«ã¤ã„ã¦ã®ãƒ†ã‚ストã«ã‚ˆã‚‹èª¬æ˜Žã§ã™ã€‚デフォ + ルトã§ã¯"unknown" ã§ã™ã€‚ + errorlog;; + エラーãƒã‚°ã®å‡ºåŠ›å…ˆã§ã™ã€‚デフォルトã§ã¯æ¨™æº–エラー出力ã§ã™ã€‚ + ipv6;; + IPv6 を使ã†ã‹ã©ã†ã‹ã§ã™ã€‚デフォルトã§ã¯ false ã§ã™ã€‚ + name;; + ウェブインターフェイスを使ã†ã¨ãã®ãƒªãƒã‚¸ãƒˆãƒªã®åå‰ã§ã™ã€‚デフォ + ルトã¯ç¾åœ¨ã®ä½œæ¥ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã§ã™ã€‚ + maxchanges;; + ãƒã‚§ãƒ³ã‚¸ãƒã‚°ã«è¨˜è¼‰ã™ã‚‹å¤‰æ›´ã®æœ€å¤§æ•°ã§ã™ã€‚デフォルトã§ã¯ 10 ã§ã™ã€‚ + maxfiles;; + ãƒã‚§ãƒ³ã‚¸ã‚»ãƒƒãƒˆã«è¨˜è¼‰ã™ã‚‹ãƒ•ã‚¡ã‚¤ãƒ«ã®æœ€å¤§æ•°ã§ã™ã€‚デフォルトã§ã¯ + 10 ã§ã™ã€‚ + port;; + リスンã™ã‚‹ãƒãƒ¼ãƒˆã§ã™ã€‚デフォルト 㯠8000 ã§ã™ã€‚ + style;; + 使用ã™ã‚‹ãƒ†ãƒ³ãƒ—レートマップã®ã‚¹ã‚¿ã‚¤ãƒ«ã§ã™ã€‚ + templates;; + HTML テンプレートã®åœ¨å‡¦ã§ã™ã€‚デフォルトã§ã¯ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã—ãŸã¨ + ãã®ãƒ‘スã§ã™ã€‚ + +著者 +-- +Bryan O'Sullivan <bos@serpentine.com>. + +Mercurial 㯠Matt Mackall <mpm@selenic.com> ã«ã‚ˆã‚Šæ›¸ã‹ã‚Œã¾ã—ãŸã€‚ + +é–¢é€£é …ç›® +---- +hg(1) + +COPYING +------- +ã“ã®ãƒžãƒ‹ãƒ¥ã‚¢ãƒ«ã®è‘—作権㯠2005 Bryan O'Sullivan ã§ã™ã€‚ +Mercurial ã®è‘—作権㯠2005 Matt Mackall ã§ã™ã€‚ +ã“ã®ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã®è‡ªç”±ãªä½¿ç”¨ã¯ GNU 一般公有使用許諾 (GPL) ã®ã‚‚ã¨ã§ +èªã‚られã¾ã™ã€‚
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hg Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# +# mercurial - scalable distributed SCM +# +# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial import commands + +commands.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgeditor Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,54 @@ +#!/bin/sh +# +# This is an example of using HGEDITOR to create of diff to review the +# changes while commiting. + +# If you want to pass your favourite editor some other parameters +# only for Mercurial, modify this: +case "${EDITOR}" in + "") + EDITOR="vi" + ;; + emacs) + EDITOR="$EDITOR -nw" + ;; + gvim|vim) + EDITOR="$EDITOR -f -o" + ;; +esac + + +HGTMP="" +cleanup_exit() { + rm -rf "$HGTMP" +} + +# Remove temporary files even if we get interrupted +trap "cleanup_exit" 0 # normal exit +trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM + +HGTMP="${TMPDIR-/tmp}/hgeditor.$RANDOM.$RANDOM.$RANDOM.$$" +(umask 077 && mkdir "$HGTMP") || { + echo "Could not create temporary directory! Exiting." 1>&2 + exit 1 +} + +( + grep '^HG: changed' "$1" | cut -b 13- | while read changed; do + hg diff "$changed" >> "$HGTMP/diff" + done +) + +cat "$1" > "$HGTMP/msg" + +CHECKSUM=`md5sum "$HGTMP/msg"` +if [ -s "$HGTMP/diff" ]; then + $EDITOR "$HGTMP/msg" "$HGTMP/diff" || exit $? +else + $EDITOR "$HGTMP/msg" || exit $? +fi +echo "$CHECKSUM" | md5sum -c >/dev/null 2>&1 && exit 13 + +mv "$HGTMP/msg" "$1" + +exit $?
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/__init__.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1 @@ +# placeholder
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/acl.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,124 @@ +# acl.py - changeset access control for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# this hook allows to allow or deny access to parts of a repo when +# taking incoming changesets. +# +# authorization is against local user name on system where hook is +# run, not committer of original changeset (since that is easy to +# spoof). +# +# acl hook is best to use if you use hgsh to set up restricted shells +# for authenticated users to only push to / pull from. not safe if +# user has interactive shell access, because they can disable hook. +# also not safe if remote users share one local account, because then +# no way to tell remote users apart. +# +# to use, configure acl extension in hgrc like this: +# +# [extensions] +# hgext.acl = +# +# [hooks] +# pretxnchangegroup.acl = python:hgext.acl.hook +# +# [acl] +# sources = serve # check if source of incoming changes in this list +# # ("serve" == ssh or http, "push", "pull", "bundle") +# +# allow and deny lists have subtree pattern (default syntax is glob) +# on left, user names on right. deny list checked before allow list. +# +# [acl.allow] +# # if acl.allow not present, all users allowed by default +# # empty acl.allow = no users allowed +# docs/** = doc_writer +# .hgtags = release_engineer +# +# [acl.deny] +# # if acl.deny not present, no users denied by default +# # empty acl.deny = all users allowed +# glob pattern = user4, user5 +# ** = user6 + +from mercurial.demandload import * +from mercurial.i18n import gettext as _ +from mercurial.node import * +demandload(globals(), 'getpass mercurial:util') + +class checker(object): + '''acl checker.''' + + def buildmatch(self, key): + '''return tuple of (match function, list enabled).''' + if not self.ui.has_config(key): + self.ui.debug(_('acl: %s not enabled\n') % key) + return None, False + + thisuser = self.getuser() + pats = [pat for pat, user in self.ui.configitems(key) + if user == thisuser] + self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') % + (key, len(pats), thisuser)) + if pats: + match = util.matcher(self.repo.root, names=pats)[1] + else: + match = util.never + return match, True + + def getuser(self): + '''return name of authenticated user.''' + return self.user + + def __init__(self, ui, repo): + self.ui = ui + self.repo = repo + self.user = getpass.getuser() + cfg = self.ui.config('acl', 'config') + if cfg: + self.ui.readconfig(cfg) + self.allow, self.allowable = self.buildmatch('acl.allow') + self.deny, self.deniable = self.buildmatch('acl.deny') + + def skipsource(self, source): + '''true if incoming changes from this source should be skipped.''' + ok_sources = self.ui.config('acl', 'sources', 'serve').split() + return source not in ok_sources + + def check(self, node): + '''return if access allowed, raise exception if not.''' + files = self.repo.changelog.read(node)[3] + if self.deniable: + for f in files: + if self.deny(f): + self.ui.debug(_('acl: user %s denied on %s\n') % + (self.getuser(), f)) + raise util.Abort(_('acl: access denied for changeset %s') % + short(node)) + if self.allowable: + for f in files: + if not self.allow(f): + self.ui.debug(_('acl: user %s not allowed on %s\n') % + (self.getuser(), f)) + raise util.Abort(_('acl: access denied for changeset %s') % + short(node)) + self.ui.debug(_('acl: allowing changeset %s\n') % short(node)) + +def hook(ui, repo, hooktype, node=None, source=None, **kwargs): + if hooktype != 'pretxnchangegroup': + raise util.Abort(_('config error - hook type "%s" cannot stop ' + 'incoming changesets') % hooktype) + + c = checker(ui, repo) + if c.skipsource(source): + ui.debug(_('acl: changes have source "%s" - skipping\n') % source) + return + + start = repo.changelog.rev(bin(node)) + end = repo.changelog.count() + for rev in xrange(start, end): + c.check(repo.changelog.node(rev))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/bugzilla.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,310 @@ +# bugzilla.py - bugzilla integration for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# hook extension to update comments of bugzilla bugs when changesets +# that refer to bugs by id are seen. this hook does not change bug +# status, only comments. +# +# to configure, add items to '[bugzilla]' section of hgrc. +# +# to use, configure bugzilla extension and enable like this: +# +# [extensions] +# hgext.bugzilla = +# +# [hooks] +# # run bugzilla hook on every change pulled or pushed in here +# incoming.bugzilla = python:hgext.bugzilla.hook +# +# config items: +# +# section name is 'bugzilla'. +# [bugzilla] +# +# REQUIRED: +# host = bugzilla # mysql server where bugzilla database lives +# password = ** # user's password +# version = 2.16 # version of bugzilla installed +# +# OPTIONAL: +# bzuser = ... # fallback bugzilla user name to record comments with +# db = bugs # database to connect to +# notify = ... # command to run to get bugzilla to send mail +# regexp = ... # regexp to match bug ids (must contain one "()" group) +# strip = 0 # number of slashes to strip for url paths +# style = ... # style file to use when formatting comments +# template = ... # template to use when formatting comments +# timeout = 5 # database connection timeout (seconds) +# user = bugs # user to connect to database as +# [web] +# baseurl = http://hgserver/... # root of hg web site for browsing commits +# +# if hg committer names are not same as bugzilla user names, use +# "usermap" feature to map from committer email to bugzilla user name. +# usermap can be in hgrc or separate config file. +# +# [bugzilla] +# usermap = filename # cfg file with "committer"="bugzilla user" info +# [usermap] +# committer_email = bugzilla_user_name + +from mercurial.demandload import * +from mercurial.i18n import gettext as _ +from mercurial.node import * +demandload(globals(), 'mercurial:templater,util os re time') + +MySQLdb = None + +def buglist(ids): + return '(' + ','.join(map(str, ids)) + ')' + +class bugzilla_2_16(object): + '''support for bugzilla version 2.16.''' + + def __init__(self, ui): + self.ui = ui + host = self.ui.config('bugzilla', 'host', 'localhost') + user = self.ui.config('bugzilla', 'user', 'bugs') + passwd = self.ui.config('bugzilla', 'password') + db = self.ui.config('bugzilla', 'db', 'bugs') + timeout = int(self.ui.config('bugzilla', 'timeout', 5)) + usermap = self.ui.config('bugzilla', 'usermap') + if usermap: + self.ui.readconfig(usermap) + self.ui.note(_('connecting to %s:%s as %s, password %s\n') % + (host, db, user, '*' * len(passwd))) + self.conn = MySQLdb.connect(host=host, user=user, passwd=passwd, + db=db, connect_timeout=timeout) + self.cursor = self.conn.cursor() + self.run('select fieldid from fielddefs where name = "longdesc"') + ids = self.cursor.fetchall() + if len(ids) != 1: + raise util.Abort(_('unknown database schema')) + self.longdesc_id = ids[0][0] + self.user_ids = {} + + def run(self, *args, **kwargs): + '''run a query.''' + self.ui.note(_('query: %s %s\n') % (args, kwargs)) + try: + self.cursor.execute(*args, **kwargs) + except MySQLdb.MySQLError, err: + self.ui.note(_('failed query: %s %s\n') % (args, kwargs)) + raise + + def filter_real_bug_ids(self, ids): + '''filter not-existing bug ids from list.''' + self.run('select bug_id from bugs where bug_id in %s' % buglist(ids)) + ids = [c[0] for c in self.cursor.fetchall()] + ids.sort() + return ids + + def filter_unknown_bug_ids(self, node, ids): + '''filter bug ids from list that already refer to this changeset.''' + + self.run('''select bug_id from longdescs where + bug_id in %s and thetext like "%%%s%%"''' % + (buglist(ids), short(node))) + unknown = dict.fromkeys(ids) + for (id,) in self.cursor.fetchall(): + self.ui.status(_('bug %d already knows about changeset %s\n') % + (id, short(node))) + unknown.pop(id, None) + ids = unknown.keys() + ids.sort() + return ids + + def notify(self, ids): + '''tell bugzilla to send mail.''' + + self.ui.status(_('telling bugzilla to send mail:\n')) + for id in ids: + self.ui.status(_(' bug %s\n') % id) + cmd = self.ui.config('bugzilla', 'notify', + 'cd /var/www/html/bugzilla && ' + './processmail %s nobody@nowhere.com') % id + fp = os.popen('(%s) 2>&1' % cmd) + out = fp.read() + ret = fp.close() + if ret: + self.ui.warn(out) + raise util.Abort(_('bugzilla notify command %s') % + util.explain_exit(ret)[0]) + self.ui.status(_('done\n')) + + def get_user_id(self, user): + '''look up numeric bugzilla user id.''' + try: + return self.user_ids[user] + except KeyError: + try: + userid = int(user) + except ValueError: + self.ui.note(_('looking up user %s\n') % user) + self.run('''select userid from profiles + where login_name like %s''', user) + all = self.cursor.fetchall() + if len(all) != 1: + raise KeyError(user) + userid = int(all[0][0]) + self.user_ids[user] = userid + return userid + + def map_committer(self, user): + '''map name of committer to bugzilla user name.''' + for committer, bzuser in self.ui.configitems('usermap'): + if committer.lower() == user.lower(): + return bzuser + return user + + def add_comment(self, bugid, text, committer): + '''add comment to bug. try adding comment as committer of + changeset, otherwise as default bugzilla user.''' + user = self.map_committer(committer) + try: + userid = self.get_user_id(user) + except KeyError: + try: + defaultuser = self.ui.config('bugzilla', 'bzuser') + if not defaultuser: + raise util.Abort(_('cannot find bugzilla user id for %s') % + user) + userid = self.get_user_id(defaultuser) + except KeyError: + raise util.Abort(_('cannot find bugzilla user id for %s or %s') % + (user, defaultuser)) + now = time.strftime('%Y-%m-%d %H:%M:%S') + self.run('''insert into longdescs + (bug_id, who, bug_when, thetext) + values (%s, %s, %s, %s)''', + (bugid, userid, now, text)) + self.run('''insert into bugs_activity (bug_id, who, bug_when, fieldid) + values (%s, %s, %s, %s)''', + (bugid, userid, now, self.longdesc_id)) + +class bugzilla(object): + # supported versions of bugzilla. different versions have + # different schemas. + _versions = { + '2.16': bugzilla_2_16, + } + + _default_bug_re = (r'bugs?\s*,?\s*(?:#|nos?\.?|num(?:ber)?s?)?\s*' + r'((?:\d+\s*(?:,?\s*(?:and)?)?\s*)+)') + + _bz = None + + def __init__(self, ui, repo): + self.ui = ui + self.repo = repo + + def bz(self): + '''return object that knows how to talk to bugzilla version in + use.''' + + if bugzilla._bz is None: + bzversion = self.ui.config('bugzilla', 'version') + try: + bzclass = bugzilla._versions[bzversion] + except KeyError: + raise util.Abort(_('bugzilla version %s not supported') % + bzversion) + bugzilla._bz = bzclass(self.ui) + return bugzilla._bz + + def __getattr__(self, key): + return getattr(self.bz(), key) + + _bug_re = None + _split_re = None + + def find_bug_ids(self, node, desc): + '''find valid bug ids that are referred to in changeset + comments and that do not already have references to this + changeset.''' + + if bugzilla._bug_re is None: + bugzilla._bug_re = re.compile( + self.ui.config('bugzilla', 'regexp', bugzilla._default_bug_re), + re.IGNORECASE) + bugzilla._split_re = re.compile(r'\D+') + start = 0 + ids = {} + while True: + m = bugzilla._bug_re.search(desc, start) + if not m: + break + start = m.end() + for id in bugzilla._split_re.split(m.group(1)): + if not id: continue + ids[int(id)] = 1 + ids = ids.keys() + if ids: + ids = self.filter_real_bug_ids(ids) + if ids: + ids = self.filter_unknown_bug_ids(node, ids) + return ids + + def update(self, bugid, node, changes): + '''update bugzilla bug with reference to changeset.''' + + def webroot(root): + '''strip leading prefix of repo root and turn into + url-safe path.''' + count = int(self.ui.config('bugzilla', 'strip', 0)) + root = util.pconvert(root) + while count > 0: + c = root.find('/') + if c == -1: + break + root = root[c+1:] + count -= 1 + return root + + mapfile = self.ui.config('bugzilla', 'style') + tmpl = self.ui.config('bugzilla', 'template') + sio = templater.stringio() + t = templater.changeset_templater(self.ui, self.repo, mapfile, sio) + if not mapfile and not tmpl: + tmpl = _('changeset {node|short} in repo {root} refers ' + 'to bug {bug}.\ndetails:\n\t{desc|tabindent}') + if tmpl: + tmpl = templater.parsestring(tmpl, quoted=False) + t.use_template(tmpl) + t.show(changenode=node, changes=changes, + bug=str(bugid), + hgweb=self.ui.config('web', 'baseurl'), + root=self.repo.root, + webroot=webroot(self.repo.root)) + self.add_comment(bugid, sio.getvalue(), templater.email(changes[1])) + +def hook(ui, repo, hooktype, node=None, **kwargs): + '''add comment to bugzilla for each changeset that refers to a + bugzilla bug id. only add a comment once per bug, so same change + seen multiple times does not fill bug with duplicate data.''' + try: + import MySQLdb as mysql + global MySQLdb + MySQLdb = mysql + except ImportError, err: + raise util.Abort(_('python mysql support not available: %s') % err) + + if node is None: + raise util.Abort(_('hook type %s does not pass a changeset id') % + hooktype) + try: + bz = bugzilla(ui, repo) + bin_node = bin(node) + changes = repo.changelog.read(bin_node) + ids = bz.find_bug_ids(bin_node, changes[4]) + if ids: + for id in ids: + bz.update(id, bin_node, changes) + bz.notify(ids) + except MySQLdb.MySQLError, err: + raise util.Abort(_('database error: %s') % err[1]) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/extdiff.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,150 @@ +# extdiff.py - external diff program support for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# allow to use external programs to compare revisions, or revision +# with working dir. program is called with two arguments: paths to +# directories containing snapshots of files to compare. +# +# to enable: +# +# [extensions] +# hgext.extdiff = +# +# also allows to configure new diff commands, so you do not need to +# type "hg extdiff -p kdiff3" always. +# +# [extdiff] +# # add new command called vdiff, runs kdiff3 +# cmd.vdiff = kdiff3 +# # add new command called meld, runs meld (no need to name twice) +# cmd.meld = +# +# you can use -I/-X and list of file or directory names like normal +# "hg diff" command. extdiff makes snapshots of only needed files, so +# compare program will be fast. + +from mercurial.demandload import demandload +from mercurial.i18n import gettext as _ +from mercurial.node import * +demandload(globals(), 'mercurial:commands,util os shutil tempfile') + +def dodiff(ui, repo, diffcmd, pats, opts): + def snapshot_node(files, node): + '''snapshot files as of some revision''' + changes = repo.changelog.read(node) + mf = repo.manifest.read(changes[0]) + dirname = '%s.%s' % (os.path.basename(repo.root), short(node)) + base = os.path.join(tmproot, dirname) + os.mkdir(base) + if not ui.quiet: + ui.write_err(_('making snapshot of %d files from rev %s\n') % + (len(files), short(node))) + for fn in files: + wfn = util.pconvert(fn) + ui.note(' %s\n' % wfn) + dest = os.path.join(base, wfn) + destdir = os.path.dirname(dest) + if not os.path.isdir(destdir): + os.makedirs(destdir) + repo.wwrite(wfn, repo.file(fn).read(mf[fn]), open(dest, 'w')) + return dirname + + def snapshot_wdir(files): + '''snapshot files from working directory. + if not using snapshot, -I/-X does not work and recursive diff + in tools like kdiff3 and meld displays too many files.''' + dirname = os.path.basename(repo.root) + base = os.path.join(tmproot, dirname) + os.mkdir(base) + if not ui.quiet: + ui.write_err(_('making snapshot of %d files from working dir\n') % + (len(files))) + for fn in files: + wfn = util.pconvert(fn) + ui.note(' %s\n' % wfn) + dest = os.path.join(base, wfn) + destdir = os.path.dirname(dest) + if not os.path.isdir(destdir): + os.makedirs(destdir) + fp = open(dest, 'w') + for chunk in util.filechunkiter(repo.wopener(wfn)): + fp.write(chunk) + return dirname + + node1, node2 = commands.revpair(ui, repo, opts['rev']) + files, matchfn, anypats = commands.matchpats(repo, pats, opts) + modified, added, removed, deleted, unknown = repo.changes( + node1, node2, files, match=matchfn) + if not (modified or added or removed): + return 0 + + tmproot = tempfile.mkdtemp(prefix='extdiff.') + try: + dir1 = snapshot_node(modified + removed, node1) + if node2: + dir2 = snapshot_node(modified + added, node2) + else: + dir2 = snapshot_wdir(modified + added) + util.system('%s %s "%s" "%s"' % + (diffcmd, ' '.join(opts['option']), dir1, dir2), + cwd=tmproot) + return 1 + finally: + ui.note(_('cleaning up temp directory\n')) + shutil.rmtree(tmproot) + +def extdiff(ui, repo, *pats, **opts): + '''use external program to diff repository (or selected files) + + Show differences between revisions for the specified files, using + an external program. The default program used is "diff -Npru". + To select a different program, use the -p option. The program + will be passed the names of two directories to compare. To pass + additional options to the program, use the -o option. These will + be passed before the names of the directories to compare. + + When two revision arguments are given, then changes are + shown between those revisions. If only one revision is + specified then that revision is compared to the working + directory, and, when no revisions are specified, the + working directory files are compared to its parent.''' + return dodiff(ui, repo, opts['program'] or 'diff -Npru', pats, opts) + +cmdtable = { + "extdiff": + (extdiff, + [('p', 'program', '', _('comparison program to run')), + ('o', 'option', [], _('pass option to comparison program')), + ('r', 'rev', [], _('revision')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg extdiff [OPT]... [FILE]...')), + } + +def uisetup(ui): + for cmd, path in ui.configitems('extdiff'): + if not cmd.startswith('cmd.'): continue + cmd = cmd[4:] + if not path: path = cmd + def save(cmd, path): + '''use closure to save diff command to use''' + def mydiff(ui, repo, *pats, **opts): + return dodiff(ui, repo, path, pats, opts) + mydiff.__doc__ = '''use %s to diff repository (or selected files) + + Show differences between revisions for the specified + files, using the %s program. + + When two revision arguments are given, then changes are + shown between those revisions. If only one revision is + specified then that revision is compared to the working + directory, and, when no revisions are specified, the + working directory files are compared to its parent.''' % (cmd, cmd) + return mydiff + cmdtable[cmd] = (save(cmd, path), + cmdtable['extdiff'][1][1:], + _('hg %s [OPT]... [FILE]...') % cmd)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/gpg.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,269 @@ +# GnuPG signing extension for Mercurial +# +# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os, tempfile, binascii +from mercurial import util +from mercurial import node as hgnode +from mercurial.i18n import gettext as _ + +class gpg: + def __init__(self, path, key=None): + self.path = path + self.key = (key and " --local-user \"%s\"" % key) or "" + + def sign(self, data): + gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key) + return util.filter(data, gpgcmd) + + def verify(self, data, sig): + """ returns of the good and bad signatures""" + sigfile = datafile = None + try: + # create temporary files + fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig") + fp = os.fdopen(fd, 'wb') + fp.write(sig) + fp.close() + fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt") + fp = os.fdopen(fd, 'wb') + fp.write(data) + fp.close() + gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify " + "\"%s\" \"%s\"" % (self.path, sigfile, datafile)) + ret = util.filter("", gpgcmd) + finally: + for f in (sigfile, datafile): + try: + if f: os.unlink(f) + except: pass + keys = [] + key, fingerprint = None, None + err = "" + for l in ret.splitlines(): + # see DETAILS in the gnupg documentation + # filter the logger output + if not l.startswith("[GNUPG:]"): + continue + l = l[9:] + if l.startswith("ERRSIG"): + err = _("error while verifying signature") + break + elif l.startswith("VALIDSIG"): + # fingerprint of the primary key + fingerprint = l.split()[10] + elif (l.startswith("GOODSIG") or + l.startswith("EXPSIG") or + l.startswith("EXPKEYSIG") or + l.startswith("BADSIG")): + if key is not None: + keys.append(key + [fingerprint]) + key = l.split(" ", 2) + fingerprint = None + if err: + return err, [] + if key is not None: + keys.append(key + [fingerprint]) + return err, keys + +def newgpg(ui, **opts): + """create a new gpg instance""" + gpgpath = ui.config("gpg", "cmd", "gpg") + gpgkey = opts.get('key') + if not gpgkey: + gpgkey = ui.config("gpg", "key", None) + return gpg(gpgpath, gpgkey) + +def sigwalk(repo): + """ + walk over every sigs, yields a couple + ((node, version, sig), (filename, linenumber)) + """ + def parsefile(fileiter, context): + ln = 1 + for l in fileiter: + if not l: + continue + yield (l.split(" ", 2), (context, ln)) + ln +=1 + + fl = repo.file(".hgsigs") + h = fl.heads() + h.reverse() + # read the heads + for r in h: + fn = ".hgsigs|%s" % hgnode.short(r) + for item in parsefile(fl.read(r).splitlines(), fn): + yield item + try: + # read local signatures + fn = "localsigs" + for item in parsefile(repo.opener(fn), fn): + yield item + except IOError: + pass + +def getkeys(ui, repo, mygpg, sigdata, context): + """get the keys who signed a data""" + fn, ln = context + node, version, sig = sigdata + prefix = "%s:%d" % (fn, ln) + node = hgnode.bin(node) + + data = node2txt(repo, node, version) + sig = binascii.a2b_base64(sig) + err, keys = mygpg.verify(data, sig) + if err: + ui.warn("%s:%d %s\n" % (fn, ln , err)) + return None + + validkeys = [] + # warn for expired key and/or sigs + for key in keys: + if key[0] == "BADSIG": + ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2])) + continue + if key[0] == "EXPSIG": + ui.write(_("%s Note: Signature has expired" + " (signed by: \"%s\")\n") % (prefix, key[2])) + elif key[0] == "EXPKEYSIG": + ui.write(_("%s Note: This key has expired" + " (signed by: \"%s\")\n") % (prefix, key[2])) + validkeys.append((key[1], key[2], key[3])) + return validkeys + +def sigs(ui, repo): + """list signed changesets""" + mygpg = newgpg(ui) + revs = {} + + for data, context in sigwalk(repo): + node, version, sig = data + fn, ln = context + try: + n = repo.lookup(node) + except KeyError: + ui.warn(_("%s:%d node does not exist\n") % (fn, ln)) + continue + r = repo.changelog.rev(n) + keys = getkeys(ui, repo, mygpg, data, context) + if not keys: + continue + revs.setdefault(r, []) + revs[r].extend(keys) + nodes = list(revs) + nodes.reverse() + for rev in nodes: + for k in revs[rev]: + r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev))) + ui.write("%-30s %s\n" % (keystr(ui, k), r)) + +def check(ui, repo, rev): + """verify all the signatures there may be for a particular revision""" + mygpg = newgpg(ui) + rev = repo.lookup(rev) + hexrev = hgnode.hex(rev) + keys = [] + + for data, context in sigwalk(repo): + node, version, sig = data + if node == hexrev: + k = getkeys(ui, repo, mygpg, data, context) + if k: + keys.extend(k) + + if not keys: + ui.write(_("No valid signature for %s\n") % hgnode.short(rev)) + return + + # print summary + ui.write("%s is signed by:\n" % hgnode.short(rev)) + for key in keys: + ui.write(" %s\n" % keystr(ui, key)) + +def keystr(ui, key): + """associate a string to a key (username, comment)""" + keyid, user, fingerprint = key + comment = ui.config("gpg", fingerprint, None) + if comment: + return "%s (%s)" % (user, comment) + else: + return user + +def sign(ui, repo, *revs, **opts): + """add a signature for the current tip or a given revision""" + mygpg = newgpg(ui, **opts) + sigver = "0" + sigmessage = "" + if revs: + nodes = [repo.lookup(n) for n in revs] + else: + nodes = [repo.changelog.tip()] + + for n in nodes: + hexnode = hgnode.hex(n) + ui.write("Signing %d:%s\n" % (repo.changelog.rev(n), + hgnode.short(n))) + # build data + data = node2txt(repo, n, sigver) + sig = mygpg.sign(data) + if not sig: + raise util.Abort(_("Error while signing")) + sig = binascii.b2a_base64(sig) + sig = sig.replace("\n", "") + sigmessage += "%s %s %s\n" % (hexnode, sigver, sig) + + # write it + if opts['local']: + repo.opener("localsigs", "ab").write(sigmessage) + return + + for x in repo.changes(): + if ".hgsigs" in x and not opts["force"]: + raise util.Abort(_("working copy of .hgsigs is changed " + "(please commit .hgsigs manually " + "or use --force)")) + + repo.wfile(".hgsigs", "ab").write(sigmessage) + + if repo.dirstate.state(".hgsigs") == '?': + repo.add([".hgsigs"]) + + if opts["no_commit"]: + return + + message = opts['message'] + if not message: + message = "\n".join([_("Added signature for changeset %s") + % hgnode.hex(n) + for n in nodes]) + try: + repo.commit([".hgsigs"], message, opts['user'], opts['date']) + except ValueError, inst: + raise util.Abort(str(inst)) + +def node2txt(repo, node, ver): + """map a manifest into some text""" + if ver == "0": + return "%s\n" % hgnode.hex(node) + else: + raise util.Abort(_("unknown signature version")) + +cmdtable = { + "sign": + (sign, + [('l', 'local', None, _("make the signature local")), + ('f', 'force', None, _("sign even if the sigfile is modified")), + ('', 'no-commit', None, _("do not commit the sigfile after signing")), + ('m', 'message', "", _("commit message")), + ('d', 'date', "", _("date code")), + ('u', 'user', "", _("user")), + ('k', 'key', "", _("the key id to sign with"))], + _("hg sign [OPTION]... [REVISION]...")), + "sigcheck": (check, [], _('hg sigcheck REVISION')), + "sigs": (sigs, [], _('hg sigs')), +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/hbisect.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,296 @@ +# bisect extension for mercurial +# +# Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org> +# Inspired by git bisect, extension skeleton taken from mq.py. +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial.i18n import gettext as _ +from mercurial.demandload import demandload +demandload(globals(), "os sys sets mercurial:hg,util,commands") + +versionstr = "0.0.3" + +def lookup_rev(ui, repo, rev=None): + """returns rev or the checked-out revision if rev is None""" + if not rev is None: + return repo.lookup(rev) + parents = [p for p in repo.dirstate.parents() if p != hg.nullid] + if len(parents) != 1: + raise util.Abort(_("unexpected number of parents, " + "please commit or revert")) + return parents.pop() + +def check_clean(ui, repo): + modified, added, removed, deleted, unknown = repo.changes() + if modified or added or removed: + ui.warn("Repository is not clean, please commit or revert\n") + sys.exit(1) + +class bisect(object): + """dichotomic search in the DAG of changesets""" + def __init__(self, ui, repo): + self.repo = repo + self.path = repo.join("bisect") + self.opener = util.opener(self.path) + self.ui = ui + self.goodrevs = [] + self.badrev = None + self.good_dirty = 0 + self.bad_dirty = 0 + self.good_path = "good" + self.bad_path = "bad" + + if os.path.exists(os.path.join(self.path, self.good_path)): + self.goodrevs = self.opener(self.good_path).read().splitlines() + self.goodrevs = [hg.bin(x) for x in self.goodrevs] + if os.path.exists(os.path.join(self.path, self.bad_path)): + r = self.opener(self.bad_path).read().splitlines() + if r: + self.badrev = hg.bin(r.pop(0)) + + def __del__(self): + if not os.path.isdir(self.path): + return + f = self.opener(self.good_path, "w") + f.write("\n".join([hg.hex(r) for r in self.goodrevs])) + if len(self.goodrevs) > 0: + f.write("\n") + f = self.opener(self.bad_path, "w") + if self.badrev: + f.write(hg.hex(self.badrev) + "\n") + + def init(self): + """start a new bisection""" + if os.path.isdir(self.path): + raise util.Abort(_("bisect directory already exists\n")) + os.mkdir(self.path) + check_clean(self.ui, self.repo) + return 0 + + def reset(self): + """finish a bisection""" + if os.path.isdir(self.path): + sl = [os.path.join(self.path, p) + for p in [self.bad_path, self.good_path]] + for s in sl: + if os.path.exists(s): + os.unlink(s) + os.rmdir(self.path) + # Not sure about this + #self.ui.write("Going back to tip\n") + #self.repo.update(self.repo.changelog.tip()) + return 1 + + def num_ancestors(self, head=None, stop=None): + """ + returns a dict with the mapping: + node -> number of ancestors (self included) + for all nodes who are ancestor of head and + not in stop. + """ + if head is None: + head = self.badrev + return self.__ancestors_and_nb_ancestors(head, stop)[1] + + def ancestors(self, head=None, stop=None): + """ + returns the set of the ancestors of head (self included) + who are not in stop. + """ + if head is None: + head = self.badrev + return self.__ancestors_and_nb_ancestors(head, stop)[0] + + def __ancestors_and_nb_ancestors(self, head, stop=None): + """ + if stop is None then ancestors of goodrevs are used as + lower limit. + + returns (anc, n_child) where anc is the set of the ancestors of head + and n_child is a dictionary with the following mapping: + node -> number of ancestors (self included) + """ + cl = self.repo.changelog + if not stop: + stop = sets.Set([]) + for i in xrange(len(self.goodrevs)-1, -1, -1): + g = self.goodrevs[i] + if g in stop: + continue + stop.update(cl.reachable(g)) + def num_children(a): + """ + returns a dictionnary with the following mapping + node -> [number of children, empty set] + """ + d = {a: [0, sets.Set([])]} + for i in xrange(cl.rev(a)+1): + n = cl.node(i) + if not d.has_key(n): + d[n] = [0, sets.Set([])] + parents = [p for p in cl.parents(n) if p != hg.nullid] + for p in parents: + d[p][0] += 1 + return d + + if head in stop: + raise util.Abort(_("Unconsistent state, %s:%s is good and bad") + % (cl.rev(head), hg.short(head))) + n_child = num_children(head) + for i in xrange(cl.rev(head)+1): + n = cl.node(i) + parents = [p for p in cl.parents(n) if p != hg.nullid] + for p in parents: + n_child[p][0] -= 1 + if not n in stop: + n_child[n][1].union_update(n_child[p][1]) + if n_child[p][0] == 0: + n_child[p] = len(n_child[p][1]) + if not n in stop: + n_child[n][1].add(n) + if n_child[n][0] == 0: + if n == head: + anc = n_child[n][1] + n_child[n] = len(n_child[n][1]) + return anc, n_child + + def next(self): + if not self.badrev: + raise util.Abort(_("You should give at least one bad revision")) + if not self.goodrevs: + self.ui.warn(_("No good revision given\n")) + self.ui.warn(_("Marking the first revision as good\n")) + ancestors, num_ancestors = self.__ancestors_and_nb_ancestors( + self.badrev) + tot = len(ancestors) + if tot == 1: + if ancestors.pop() != self.badrev: + raise util.Abort(_("Could not find the first bad revision")) + self.ui.write(_("The first bad revision is:\n")) + displayer = commands.show_changeset(self.ui, self.repo, {}) + displayer.show(changenode=self.badrev) + return None + best_rev = None + best_len = -1 + for n in ancestors: + l = num_ancestors[n] + l = min(l, tot - l) + if l > best_len: + best_len = l + best_rev = n + assert best_rev is not None + nb_tests = 0 + q, r = divmod(tot, 2) + while q: + nb_tests += 1 + q, r = divmod(q, 2) + msg = _("Testing changeset %s:%s (%s changesets remaining, " + "~%s tests)\n") % (self.repo.changelog.rev(best_rev), + hg.short(best_rev), tot, nb_tests) + self.ui.write(msg) + return best_rev + + def autonext(self): + """find and update to the next revision to test""" + check_clean(self.ui, self.repo) + rev = self.next() + if rev is not None: + return self.repo.update(rev, force=True) + + def good(self, rev): + self.goodrevs.append(rev) + + def autogood(self, rev=None): + """mark revision as good and update to the next revision to test""" + check_clean(self.ui, self.repo) + rev = lookup_rev(self.ui, self.repo, rev) + self.good(rev) + if self.badrev: + return self.autonext() + + def bad(self, rev): + self.badrev = rev + + def autobad(self, rev=None): + """mark revision as bad and update to the next revision to test""" + check_clean(self.ui, self.repo) + rev = lookup_rev(self.ui, self.repo, rev) + self.bad(rev) + if self.goodrevs: + self.autonext() + +# should we put it in the class ? +def test(ui, repo, rev): + """test the bisection code""" + b = bisect(ui, repo) + rev = repo.lookup(rev) + ui.write("testing with rev %s\n" % hg.hex(rev)) + anc = b.ancestors() + while len(anc) > 1: + if not rev in anc: + ui.warn("failure while bisecting\n") + sys.exit(1) + ui.write("it worked :)\n") + new_rev = b.next() + ui.write("choosing if good or bad\n") + if rev in b.ancestors(head=new_rev): + b.bad(new_rev) + ui.write("it is bad\n") + else: + b.good(new_rev) + ui.write("it is good\n") + anc = b.ancestors() + #repo.update(new_rev, force=True) + for v in anc: + if v != rev: + ui.warn("fail to found cset! :(\n") + return 1 + ui.write("Found bad cset: %s\n" % hg.hex(b.badrev)) + ui.write("Everything is ok :)\n") + return 0 + +def bisect_run(ui, repo, cmd=None, *args): + """bisect extension: dichotomic search in the DAG of changesets +for subcommands see "hg bisect help\" + """ + def help_(cmd=None, *args): + """show help for a given bisect subcommand or all subcommands""" + cmdtable = bisectcmdtable + if cmd: + doc = cmdtable[cmd][0].__doc__ + synopsis = cmdtable[cmd][2] + ui.write(synopsis + "\n") + ui.write("\n" + doc + "\n") + return + ui.write(_("list of subcommands for the bisect extension\n\n")) + cmds = cmdtable.keys() + cmds.sort() + m = max([len(c) for c in cmds]) + for cmd in cmds: + doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip() + ui.write(" %-*s %s\n" % (m, cmd, doc)) + + b = bisect(ui, repo) + bisectcmdtable = { + "init": (b.init, 0, _("hg bisect init")), + "bad": (b.autobad, 1, _("hg bisect bad [<rev>]")), + "good": (b.autogood, 1, _("hg bisect good [<rev>]")), + "next": (b.autonext, 0, _("hg bisect next")), + "reset": (b.reset, 0, _("hg bisect reset")), + "help": (help_, 1, _("hg bisect help [<subcommand>]")), + } + + if not bisectcmdtable.has_key(cmd): + ui.warn(_("bisect: Unknown sub-command\n")) + return help_() + if len(args) > bisectcmdtable[cmd][1]: + ui.warn(_("bisect: Too many arguments\n")) + return help_() + return bisectcmdtable[cmd][0](*args) + +cmdtable = { + "bisect": (bisect_run, [], _("hg bisect [help|init|reset|next|good|bad]")), + #"bisect-test": (test, [], "hg bisect-test rev"), +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/mq.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1309 @@ +# queue.py - patch queues for mercurial +# +# Copyright 2005 Chris Mason <mason@suse.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial.demandload import * +demandload(globals(), "os sys re struct traceback errno bz2") +from mercurial.i18n import gettext as _ +from mercurial import ui, hg, revlog, commands, util + +versionstr = "0.45" + +repomap = {} + +commands.norepo += " qversion" +class queue: + def __init__(self, ui, path, patchdir=None): + self.basepath = path + if patchdir: + self.path = patchdir + else: + self.path = os.path.join(path, "patches") + self.opener = util.opener(self.path) + self.ui = ui + self.applied = [] + self.full_series = [] + self.applied_dirty = 0 + self.series_dirty = 0 + self.series_path = "series" + self.status_path = "status" + + if os.path.exists(os.path.join(self.path, self.series_path)): + self.full_series = self.opener(self.series_path).read().splitlines() + self.read_series(self.full_series) + + if os.path.exists(os.path.join(self.path, self.status_path)): + self.applied = self.opener(self.status_path).read().splitlines() + + def find_series(self, patch): + pre = re.compile("(\s*)([^#]+)") + index = 0 + for l in self.full_series: + m = pre.match(l) + if m: + s = m.group(2) + s = s.rstrip() + if s == patch: + return index + index += 1 + return None + + def read_series(self, list): + def matcher(list): + pre = re.compile("(\s*)([^#]+)") + for l in list: + m = pre.match(l) + if m: + s = m.group(2) + s = s.rstrip() + if len(s) > 0: + yield s + self.series = [] + self.series = [ x for x in matcher(list) ] + + def save_dirty(self): + if self.applied_dirty: + if len(self.applied) > 0: + nl = "\n" + else: + nl = "" + f = self.opener(self.status_path, "w") + f.write("\n".join(self.applied) + nl) + if self.series_dirty: + if len(self.full_series) > 0: + nl = "\n" + else: + nl = "" + f = self.opener(self.series_path, "w") + f.write("\n".join(self.full_series) + nl) + + def readheaders(self, patch): + def eatdiff(lines): + while lines: + l = lines[-1] + if (l.startswith("diff -") or + l.startswith("Index:") or + l.startswith("===========")): + del lines[-1] + else: + break + def eatempty(lines): + while lines: + l = lines[-1] + if re.match('\s*$', l): + del lines[-1] + else: + break + + pf = os.path.join(self.path, patch) + message = [] + comments = [] + user = None + date = None + format = None + subject = None + diffstart = 0 + + for line in file(pf): + line = line.rstrip() + if diffstart: + if line.startswith('+++ '): + diffstart = 2 + break + if line.startswith("--- "): + diffstart = 1 + continue + elif format == "hgpatch": + # parse values when importing the result of an hg export + if line.startswith("# User "): + user = line[7:] + elif line.startswith("# Date "): + date = line[7:] + elif not line.startswith("# ") and line: + message.append(line) + format = None + elif line == '# HG changeset patch': + format = "hgpatch" + elif (format != "tagdone" and (line.startswith("Subject: ") or + line.startswith("subject: "))): + subject = line[9:] + format = "tag" + elif (format != "tagdone" and (line.startswith("From: ") or + line.startswith("from: "))): + user = line[6:] + format = "tag" + elif format == "tag" and line == "": + # when looking for tags (subject: from: etc) they + # end once you find a blank line in the source + format = "tagdone" + elif message or line: + message.append(line) + comments.append(line) + + eatdiff(message) + eatdiff(comments) + eatempty(message) + eatempty(comments) + + # make sure message isn't empty + if format and format.startswith("tag") and subject: + message.insert(0, "") + message.insert(0, subject) + return (message, comments, user, date, diffstart > 1) + + def mergeone(self, repo, mergeq, head, patch, rev, wlock): + # first try just applying the patch + (err, n) = self.apply(repo, [ patch ], update_status=False, + strict=True, merge=rev, wlock=wlock) + + if err == 0: + return (err, n) + + if n is None: + self.ui.warn("apply failed for patch %s\n" % patch) + sys.exit(1) + + self.ui.warn("patch didn't work out, merging %s\n" % patch) + + # apply failed, strip away that rev and merge. + repo.update(head, allow=False, force=True, wlock=wlock) + self.strip(repo, n, update=False, backup='strip', wlock=wlock) + + c = repo.changelog.read(rev) + ret = repo.update(rev, allow=True, wlock=wlock) + if ret: + self.ui.warn("update returned %d\n" % ret) + sys.exit(1) + n = repo.commit(None, c[4], c[1], force=1, wlock=wlock) + if n == None: + self.ui.warn("repo commit failed\n") + sys.exit(1) + try: + message, comments, user, date, patchfound = mergeq.readheaders(patch) + except: + self.ui.warn("Unable to read %s\n" % patch) + sys.exit(1) + + patchf = self.opener(patch, "w") + if comments: + comments = "\n".join(comments) + '\n\n' + patchf.write(comments) + commands.dodiff(patchf, self.ui, repo, head, n) + patchf.close() + return (0, n) + + def qparents(self, repo, rev=None): + if rev is None: + (p1, p2) = repo.dirstate.parents() + if p2 == revlog.nullid: + return p1 + if len(self.applied) == 0: + return None + (top, patch) = self.applied[-1].split(':') + top = revlog.bin(top) + return top + pp = repo.changelog.parents(rev) + if pp[1] != revlog.nullid: + arevs = [ x.split(':')[0] for x in self.applied ] + p0 = revlog.hex(pp[0]) + p1 = revlog.hex(pp[1]) + if p0 in arevs: + return pp[0] + if p1 in arevs: + return pp[1] + return None + return pp[0] + + def mergepatch(self, repo, mergeq, series, wlock): + if len(self.applied) == 0: + # each of the patches merged in will have two parents. This + # can confuse the qrefresh, qdiff, and strip code because it + # needs to know which parent is actually in the patch queue. + # so, we insert a merge marker with only one parent. This way + # the first patch in the queue is never a merge patch + # + pname = ".hg.patches.merge.marker" + n = repo.commit(None, '[mq]: merge marker', user=None, force=1, + wlock=wlock) + self.applied.append(revlog.hex(n) + ":" + pname) + self.applied_dirty = 1 + + head = self.qparents(repo) + + for patch in series: + patch = mergeq.lookup(patch) + if not patch: + self.ui.warn("patch %s does not exist\n" % patch) + return (1, None) + + info = mergeq.isapplied(patch) + if not info: + self.ui.warn("patch %s is not applied\n" % patch) + return (1, None) + rev = revlog.bin(info[1]) + (err, head) = self.mergeone(repo, mergeq, head, patch, rev, wlock) + if head: + self.applied.append(revlog.hex(head) + ":" + patch) + self.applied_dirty = 1 + if err: + return (err, head) + return (0, head) + + def apply(self, repo, series, list=False, update_status=True, + strict=False, patchdir=None, merge=None, wlock=None): + # TODO unify with commands.py + if not patchdir: + patchdir = self.path + pwd = os.getcwd() + os.chdir(repo.root) + err = 0 + if not wlock: + wlock = repo.wlock() + lock = repo.lock() + tr = repo.transaction() + n = None + for patch in series: + self.ui.warn("applying %s\n" % patch) + pf = os.path.join(patchdir, patch) + + try: + message, comments, user, date, patchfound = self.readheaders(patch) + except: + self.ui.warn("Unable to read %s\n" % pf) + err = 1 + break + + if not message: + message = "imported patch %s\n" % patch + else: + if list: + message.append("\nimported patch %s" % patch) + message = '\n'.join(message) + + try: + pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') + f = os.popen("%s -p1 --no-backup-if-mismatch < '%s'" % (pp, pf)) + except: + self.ui.warn("patch failed, unable to continue (try -v)\n") + err = 1 + break + files = [] + fuzz = False + for l in f: + l = l.rstrip('\r\n'); + if self.ui.verbose: + self.ui.warn(l + "\n") + if l[:14] == 'patching file ': + pf = os.path.normpath(l[14:]) + # when patch finds a space in the file name, it puts + # single quotes around the filename. strip them off + if pf[0] == "'" and pf[-1] == "'": + pf = pf[1:-1] + if pf not in files: + files.append(pf) + printed_file = False + file_str = l + elif l.find('with fuzz') >= 0: + if not printed_file: + self.ui.warn(file_str + '\n') + printed_file = True + self.ui.warn(l + '\n') + fuzz = True + elif l.find('saving rejects to file') >= 0: + self.ui.warn(l + '\n') + elif l.find('FAILED') >= 0: + if not printed_file: + self.ui.warn(file_str + '\n') + printed_file = True + self.ui.warn(l + '\n') + patcherr = f.close() + + if merge and len(files) > 0: + # Mark as merged and update dirstate parent info + repo.dirstate.update(repo.dirstate.filterfiles(files), 'm') + p1, p2 = repo.dirstate.parents() + repo.dirstate.setparents(p1, merge) + if len(files) > 0: + commands.addremove_lock(self.ui, repo, files, + opts={}, wlock=wlock) + n = repo.commit(files, message, user, date, force=1, lock=lock, + wlock=wlock) + + if n == None: + self.ui.warn("repo commit failed\n") + sys.exit(1) + + if update_status: + self.applied.append(revlog.hex(n) + ":" + patch) + + if patcherr: + if not patchfound: + self.ui.warn("patch %s is empty\n" % patch) + err = 0 + else: + self.ui.warn("patch failed, rejects left in working dir\n") + err = 1 + break + + if fuzz and strict: + self.ui.warn("fuzz found when applying patch, stopping\n") + err = 1 + break + tr.close() + os.chdir(pwd) + return (err, n) + + def delete(self, repo, patch): + patch = self.lookup(patch) + info = self.isapplied(patch) + if info: + self.ui.warn("cannot delete applied patch %s\n" % patch) + sys.exit(1) + if patch not in self.series: + self.ui.warn("patch %s not in series file\n" % patch) + sys.exit(1) + i = self.find_series(patch) + del self.full_series[i] + self.read_series(self.full_series) + self.series_dirty = 1 + + def check_toppatch(self, repo): + if len(self.applied) > 0: + (top, patch) = self.applied[-1].split(':') + top = revlog.bin(top) + pp = repo.dirstate.parents() + if top not in pp: + self.ui.warn("queue top not at dirstate parents. top %s dirstate %s %s\n" %( revlog.short(top), revlog.short(pp[0]), revlog.short(pp[1]))) + sys.exit(1) + return top + return None + def check_localchanges(self, repo): + (c, a, r, d, u) = repo.changes(None, None) + if c or a or d or r: + self.ui.write("Local changes found, refresh first\n") + sys.exit(1) + def new(self, repo, patch, msg=None, force=None): + if not force: + self.check_localchanges(repo) + self.check_toppatch(repo) + wlock = repo.wlock() + insert = self.series_end() + if msg: + n = repo.commit([], "[mq]: %s" % msg, force=True, wlock=wlock) + else: + n = repo.commit([], + "New patch: %s" % patch, force=True, wlock=wlock) + if n == None: + self.ui.warn("repo commit failed\n") + sys.exit(1) + self.full_series[insert:insert] = [patch] + self.applied.append(revlog.hex(n) + ":" + patch) + self.read_series(self.full_series) + self.series_dirty = 1 + self.applied_dirty = 1 + p = self.opener(patch, "w") + if msg: + msg = msg + "\n" + p.write(msg) + p.close() + wlock = None + r = self.qrepo() + if r: r.add([patch]) + + def strip(self, repo, rev, update=True, backup="all", wlock=None): + def limitheads(chlog, stop): + """return the list of all nodes that have no children""" + p = {} + h = [] + stoprev = 0 + if stop in chlog.nodemap: + stoprev = chlog.rev(stop) + + for r in range(chlog.count() - 1, -1, -1): + n = chlog.node(r) + if n not in p: + h.append(n) + if n == stop: + break + if r < stoprev: + break + for pn in chlog.parents(n): + p[pn] = 1 + return h + + def bundle(cg): + backupdir = repo.join("strip-backup") + if not os.path.isdir(backupdir): + os.mkdir(backupdir) + name = os.path.join(backupdir, "%s" % revlog.short(rev)) + name = savename(name) + self.ui.warn("saving bundle to %s\n" % name) + # TODO, exclusive open + f = open(name, "wb") + try: + f.write("HG10") + z = bz2.BZ2Compressor(9) + while 1: + chunk = cg.read(4096) + if not chunk: + break + f.write(z.compress(chunk)) + f.write(z.flush()) + except: + os.unlink(name) + raise + f.close() + return name + + def stripall(rev, revnum): + cl = repo.changelog + c = cl.read(rev) + mm = repo.manifest.read(c[0]) + seen = {} + + for x in xrange(revnum, cl.count()): + c = cl.read(cl.node(x)) + for f in c[3]: + if f in seen: + continue + seen[f] = 1 + if f in mm: + filerev = mm[f] + else: + filerev = 0 + seen[f] = filerev + # we go in two steps here so the strip loop happens in a + # sensible order. When stripping many files, this helps keep + # our disk access patterns under control. + list = seen.keys() + list.sort() + for f in list: + ff = repo.file(f) + filerev = seen[f] + if filerev != 0: + if filerev in ff.nodemap: + filerev = ff.rev(filerev) + else: + filerev = 0 + ff.strip(filerev, revnum) + + if not wlock: + wlock = repo.wlock() + lock = repo.lock() + chlog = repo.changelog + # TODO delete the undo files, and handle undo of merge sets + pp = chlog.parents(rev) + revnum = chlog.rev(rev) + + if update: + urev = self.qparents(repo, rev) + repo.update(urev, allow=False, force=True, wlock=wlock) + repo.dirstate.write() + + # save is a list of all the branches we are truncating away + # that we actually want to keep. changegroup will be used + # to preserve them and add them back after the truncate + saveheads = [] + savebases = {} + + tip = chlog.tip() + heads = limitheads(chlog, rev) + seen = {} + + # search through all the heads, finding those where the revision + # we want to strip away is an ancestor. Also look for merges + # that might be turned into new heads by the strip. + while heads: + h = heads.pop() + n = h + while True: + seen[n] = 1 + pp = chlog.parents(n) + if pp[1] != revlog.nullid and chlog.rev(pp[1]) > revnum: + if pp[1] not in seen: + heads.append(pp[1]) + if pp[0] == revlog.nullid: + break + if chlog.rev(pp[0]) < revnum: + break + n = pp[0] + if n == rev: + break + r = chlog.reachable(h, rev) + if rev not in r: + saveheads.append(h) + for x in r: + if chlog.rev(x) > revnum: + savebases[x] = 1 + + # create a changegroup for all the branches we need to keep + if backup is "all": + backupch = repo.changegroupsubset([rev], chlog.heads(), 'strip') + bundle(backupch) + if saveheads: + backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip') + chgrpfile = bundle(backupch) + + stripall(rev, revnum) + + change = chlog.read(rev) + repo.manifest.strip(repo.manifest.rev(change[0]), revnum) + chlog.strip(revnum, revnum) + if saveheads: + self.ui.status("adding branch\n") + commands.unbundle(self.ui, repo, chgrpfile, update=False) + if backup is not "strip": + os.unlink(chgrpfile) + + def isapplied(self, patch): + """returns (index, rev, patch)""" + for i in xrange(len(self.applied)): + p = self.applied[i] + a = p.split(':') + if a[1] == patch: + return (i, a[0], a[1]) + return None + + def lookup(self, patch): + if patch == None: + return None + if patch in self.series: + return patch + if not os.path.isfile(os.path.join(self.path, patch)): + try: + sno = int(patch) + except(ValueError, OverflowError): + self.ui.warn("patch %s not in series\n" % patch) + sys.exit(1) + if sno >= len(self.series): + self.ui.warn("patch number %d is out of range\n" % sno) + sys.exit(1) + patch = self.series[sno] + else: + self.ui.warn("patch %s not in series\n" % patch) + sys.exit(1) + return patch + + def push(self, repo, patch=None, force=False, list=False, + mergeq=None, wlock=None): + if not wlock: + wlock = repo.wlock() + patch = self.lookup(patch) + if patch and self.isapplied(patch): + self.ui.warn("patch %s is already applied\n" % patch) + sys.exit(1) + if self.series_end() == len(self.series): + self.ui.warn("File series fully applied\n") + sys.exit(1) + if not force: + self.check_localchanges(repo) + + self.applied_dirty = 1; + start = self.series_end() + if start > 0: + self.check_toppatch(repo) + if not patch: + patch = self.series[start] + end = start + 1 + else: + end = self.series.index(patch, start) + 1 + s = self.series[start:end] + if mergeq: + ret = self.mergepatch(repo, mergeq, s, wlock) + else: + ret = self.apply(repo, s, list, wlock=wlock) + top = self.applied[-1].split(':')[1] + if ret[0]: + self.ui.write("Errors during apply, please fix and refresh %s\n" % + top) + else: + self.ui.write("Now at: %s\n" % top) + return ret[0] + + def pop(self, repo, patch=None, force=False, update=True, wlock=None): + def getfile(f, rev): + t = repo.file(f).read(rev) + try: + repo.wfile(f, "w").write(t) + except IOError: + try: + os.makedirs(os.path.dirname(repo.wjoin(f))) + except OSError, err: + if err.errno != errno.EEXIST: raise + repo.wfile(f, "w").write(t) + + if not wlock: + wlock = repo.wlock() + if patch: + # index, rev, patch + info = self.isapplied(patch) + if not info: + patch = self.lookup(patch) + info = self.isapplied(patch) + if not info: + self.ui.warn("patch %s is not applied\n" % patch) + sys.exit(1) + if len(self.applied) == 0: + self.ui.warn("No patches applied\n") + sys.exit(1) + + if not update: + parents = repo.dirstate.parents() + rr = [ revlog.bin(x.split(':')[0]) for x in self.applied ] + for p in parents: + if p in rr: + self.ui.warn("qpop: forcing dirstate update\n") + update = True + + if not force and update: + self.check_localchanges(repo) + + self.applied_dirty = 1; + end = len(self.applied) + if not patch: + info = [len(self.applied) - 1] + self.applied[-1].split(':') + start = info[0] + rev = revlog.bin(info[1]) + + # we know there are no local changes, so we can make a simplified + # form of hg.update. + if update: + top = self.check_toppatch(repo) + qp = self.qparents(repo, rev) + changes = repo.changelog.read(qp) + mf1 = repo.manifest.readflags(changes[0]) + mmap = repo.manifest.read(changes[0]) + (c, a, r, d, u) = repo.changes(qp, top) + if d: + raise util.Abort("deletions found between repo revs") + for f in c: + getfile(f, mmap[f]) + for f in r: + getfile(f, mmap[f]) + util.set_exec(repo.wjoin(f), mf1[f]) + repo.dirstate.update(c + r, 'n') + for f in a: + try: os.unlink(repo.wjoin(f)) + except: raise + try: os.removedirs(os.path.dirname(repo.wjoin(f))) + except: pass + if a: + repo.dirstate.forget(a) + repo.dirstate.setparents(qp, revlog.nullid) + self.strip(repo, rev, update=False, backup='strip', wlock=wlock) + del self.applied[start:end] + if len(self.applied): + self.ui.write("Now at: %s\n" % self.applied[-1].split(':')[1]) + else: + self.ui.write("Patch queue now empty\n") + + def diff(self, repo, files): + top = self.check_toppatch(repo) + if not top: + self.ui.write("No patches applied\n") + return + qp = self.qparents(repo, top) + commands.dodiff(sys.stdout, self.ui, repo, qp, None, files) + + def refresh(self, repo, short=False): + if len(self.applied) == 0: + self.ui.write("No patches applied\n") + return + wlock = repo.wlock() + self.check_toppatch(repo) + qp = self.qparents(repo) + (top, patch) = self.applied[-1].split(':') + top = revlog.bin(top) + cparents = repo.changelog.parents(top) + patchparent = self.qparents(repo, top) + message, comments, user, date, patchfound = self.readheaders(patch) + + patchf = self.opener(patch, "w") + if comments: + comments = "\n".join(comments) + '\n\n' + patchf.write(comments) + + tip = repo.changelog.tip() + if top == tip: + # if the top of our patch queue is also the tip, there is an + # optimization here. We update the dirstate in place and strip + # off the tip commit. Then just commit the current directory + # tree. We can also send repo.commit the list of files + # changed to speed up the diff + # + # in short mode, we only diff the files included in the + # patch already + # + # this should really read: + #(cc, dd, aa, aa2, uu) = repo.changes(tip, patchparent) + # but we do it backwards to take advantage of manifest/chlog + # caching against the next repo.changes call + # + (cc, aa, dd, aa2, uu) = repo.changes(patchparent, tip) + if short: + filelist = cc + aa + dd + else: + filelist = None + (c, a, r, d, u) = repo.changes(None, None, filelist) + + # we might end up with files that were added between tip and + # the dirstate parent, but then changed in the local dirstate. + # in this case, we want them to only show up in the added section + for x in c: + if x not in aa: + cc.append(x) + # we might end up with files added by the local dirstate that + # were deleted by the patch. In this case, they should only + # show up in the changed section. + for x in a: + if x in dd: + del dd[dd.index(x)] + cc.append(x) + else: + aa.append(x) + # make sure any files deleted in the local dirstate + # are not in the add or change column of the patch + forget = [] + for x in d + r: + if x in aa: + del aa[aa.index(x)] + forget.append(x) + continue + elif x in cc: + del cc[cc.index(x)] + dd.append(x) + + c = list(util.unique(cc)) + r = list(util.unique(dd)) + a = list(util.unique(aa)) + filelist = list(util.unique(c + r + a )) + commands.dodiff(patchf, self.ui, repo, patchparent, None, + filelist, changes=(c, a, r, [], u)) + patchf.close() + + changes = repo.changelog.read(tip) + repo.dirstate.setparents(*cparents) + repo.dirstate.update(a, 'a') + repo.dirstate.update(r, 'r') + repo.dirstate.update(c, 'n') + repo.dirstate.forget(forget) + + if not message: + message = "patch queue: %s\n" % patch + else: + message = "\n".join(message) + self.strip(repo, top, update=False, backup='strip', wlock=wlock) + n = repo.commit(filelist, message, changes[1], force=1, wlock=wlock) + self.applied[-1] = revlog.hex(n) + ':' + patch + self.applied_dirty = 1 + else: + commands.dodiff(patchf, self.ui, repo, patchparent, None) + patchf.close() + self.pop(repo, force=True, wlock=wlock) + self.push(repo, force=True, wlock=wlock) + + def init(self, repo, create=False): + if os.path.isdir(self.path): + raise util.Abort("patch queue directory already exists") + os.mkdir(self.path) + if create: + return self.qrepo(create=True) + + def unapplied(self, repo, patch=None): + if patch and patch not in self.series: + self.ui.warn("%s not in the series file\n" % patch) + sys.exit(1) + if not patch: + start = self.series_end() + else: + start = self.series.index(patch) + 1 + for p in self.series[start:]: + self.ui.write("%s\n" % p) + + def qseries(self, repo, missing=None): + start = self.series_end() + if not missing: + for p in self.series[:start]: + if self.ui.verbose: + self.ui.write("%d A " % self.series.index(p)) + self.ui.write("%s\n" % p) + for p in self.series[start:]: + if self.ui.verbose: + self.ui.write("%d U " % self.series.index(p)) + self.ui.write("%s\n" % p) + else: + list = [] + for root, dirs, files in os.walk(self.path): + d = root[len(self.path) + 1:] + for f in files: + fl = os.path.join(d, f) + if (fl not in self.series and + fl not in (self.status_path, self.series_path) + and not fl.startswith('.')): + list.append(fl) + list.sort() + if list: + for x in list: + if self.ui.verbose: + self.ui.write("D ") + self.ui.write("%s\n" % x) + + def issaveline(self, l): + name = l.split(':')[1] + if name == '.hg.patches.save.line': + return True + + def qrepo(self, create=False): + if create or os.path.isdir(os.path.join(self.path, ".hg")): + return hg.repository(self.ui, path=self.path, create=create) + + def restore(self, repo, rev, delete=None, qupdate=None): + c = repo.changelog.read(rev) + desc = c[4].strip() + lines = desc.splitlines() + i = 0 + datastart = None + series = [] + applied = [] + qpp = None + for i in xrange(0, len(lines)): + if lines[i] == 'Patch Data:': + datastart = i + 1 + elif lines[i].startswith('Dirstate:'): + l = lines[i].rstrip() + l = l[10:].split(' ') + qpp = [ hg.bin(x) for x in l ] + elif datastart != None: + l = lines[i].rstrip() + index = l.index(':') + id = l[:index] + file = l[index + 1:] + if id: + applied.append(l) + series.append(file) + if datastart == None: + self.ui.warn("No saved patch data found\n") + return 1 + self.ui.warn("restoring status: %s\n" % lines[0]) + self.full_series = series + self.applied = applied + self.read_series(self.full_series) + self.series_dirty = 1 + self.applied_dirty = 1 + heads = repo.changelog.heads() + if delete: + if rev not in heads: + self.ui.warn("save entry has children, leaving it alone\n") + else: + self.ui.warn("removing save entry %s\n" % hg.short(rev)) + pp = repo.dirstate.parents() + if rev in pp: + update = True + else: + update = False + self.strip(repo, rev, update=update, backup='strip') + if qpp: + self.ui.warn("saved queue repository parents: %s %s\n" % + (hg.short(qpp[0]), hg.short(qpp[1]))) + if qupdate: + print "queue directory updating" + r = self.qrepo() + if not r: + self.ui.warn("Unable to load queue repository\n") + return 1 + r.update(qpp[0], allow=False, force=True) + + def save(self, repo, msg=None): + if len(self.applied) == 0: + self.ui.warn("save: no patches applied, exiting\n") + return 1 + if self.issaveline(self.applied[-1]): + self.ui.warn("status is already saved\n") + return 1 + + ar = [ ':' + x for x in self.full_series ] + if not msg: + msg = "hg patches saved state" + else: + msg = "hg patches: " + msg.rstrip('\r\n') + r = self.qrepo() + if r: + pp = r.dirstate.parents() + msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1])) + msg += "\n\nPatch Data:\n" + text = msg + "\n".join(self.applied) + '\n' + (ar and "\n".join(ar) + + '\n' or "") + n = repo.commit(None, text, user=None, force=1) + if not n: + self.ui.warn("repo commit failed\n") + return 1 + self.applied.append(revlog.hex(n) + ":" + '.hg.patches.save.line') + self.applied_dirty = 1 + + def series_end(self): + end = 0 + if len(self.applied) > 0: + (top, p) = self.applied[-1].split(':') + try: + end = self.series.index(p) + except ValueError: + return 0 + return end + 1 + return end + + def qapplied(self, repo, patch=None): + if patch and patch not in self.series: + self.ui.warn("%s not in the series file\n" % patch) + sys.exit(1) + if not patch: + end = len(self.applied) + else: + end = self.series.index(patch) + 1 + for x in xrange(end): + p = self.appliedname(x) + self.ui.write("%s\n" % p) + + def appliedname(self, index): + p = self.applied[index] + if not self.ui.verbose: + p = p.split(':')[1] + return p + + def top(self, repo): + if len(self.applied): + p = self.appliedname(-1) + self.ui.write(p + '\n') + else: + self.ui.write("No patches applied\n") + + def next(self, repo): + end = self.series_end() + if end == len(self.series): + self.ui.write("All patches applied\n") + else: + self.ui.write(self.series[end] + '\n') + + def prev(self, repo): + if len(self.applied) > 1: + p = self.appliedname(-2) + self.ui.write(p + '\n') + elif len(self.applied) == 1: + self.ui.write("Only one patch applied\n") + else: + self.ui.write("No patches applied\n") + + def qimport(self, repo, files, patch=None, existing=None, force=None): + if len(files) > 1 and patch: + self.ui.warn("-n option not valid when importing multiple files\n") + sys.exit(1) + i = 0 + for filename in files: + if existing: + if not patch: + patch = filename + if not os.path.isfile(os.path.join(self.path, patch)): + self.ui.warn("patch %s does not exist\n" % patch) + sys.exit(1) + else: + try: + text = file(filename).read() + except IOError: + self.ui.warn("Unable to read %s\n" % patch) + sys.exit(1) + if not patch: + patch = os.path.split(filename)[1] + if not force and os.path.isfile(os.path.join(self.path, patch)): + self.ui.warn("patch %s already exists\n" % patch) + sys.exit(1) + patchf = self.opener(patch, "w") + patchf.write(text) + if patch in self.series: + self.ui.warn("patch %s is already in the series file\n" % patch) + sys.exit(1) + index = self.series_end() + i + self.full_series[index:index] = [patch] + self.read_series(self.full_series) + self.ui.warn("adding %s to series file\n" % patch) + i += 1 + patch = None + self.series_dirty = 1 + +def delete(ui, repo, patch, **opts): + """remove a patch from the series file""" + q = repomap[repo] + q.delete(repo, patch) + q.save_dirty() + return 0 + +def applied(ui, repo, patch=None, **opts): + """print the patches already applied""" + repomap[repo].qapplied(repo, patch) + return 0 + +def unapplied(ui, repo, patch=None, **opts): + """print the patches not yet applied""" + repomap[repo].unapplied(repo, patch) + return 0 + +def qimport(ui, repo, *filename, **opts): + """import a patch""" + q = repomap[repo] + q.qimport(repo, filename, patch=opts['name'], + existing=opts['existing'], force=opts['force']) + q.save_dirty() + return 0 + +def init(ui, repo, **opts): + """init a new queue repository""" + q = repomap[repo] + r = q.init(repo, create=opts['create_repo']) + q.save_dirty() + if r: + fp = r.wopener('.hgignore', 'w') + print >> fp, 'syntax: glob' + print >> fp, 'status' + fp.close() + r.wopener('series', 'w').close() + r.add(['.hgignore', 'series']) + return 0 + +def commit(ui, repo, *pats, **opts): + q = repomap[repo] + r = q.qrepo() + if not r: raise util.Abort('no queue repository') + commands.commit(r.ui, r, *pats, **opts) + +def series(ui, repo, **opts): + """print the entire series file""" + repomap[repo].qseries(repo, missing=opts['missing']) + return 0 + +def top(ui, repo, **opts): + """print the name of the current patch""" + repomap[repo].top(repo) + return 0 + +def next(ui, repo, **opts): + """print the name of the next patch""" + repomap[repo].next(repo) + return 0 + +def prev(ui, repo, **opts): + """print the name of the previous patch""" + repomap[repo].prev(repo) + return 0 + +def new(ui, repo, patch, **opts): + """create a new patch""" + q = repomap[repo] + q.new(repo, patch, msg=opts['message'], force=opts['force']) + q.save_dirty() + return 0 + +def refresh(ui, repo, **opts): + """update the current patch""" + q = repomap[repo] + q.refresh(repo, short=opts['short']) + q.save_dirty() + return 0 + +def diff(ui, repo, *files, **opts): + """diff of the current patch""" + # deep in the dirstate code, the walkhelper method wants a list, not a tuple + repomap[repo].diff(repo, list(files)) + return 0 + +def lastsavename(path): + (dir, base) = os.path.split(path) + names = os.listdir(dir) + namere = re.compile("%s.([0-9]+)" % base) + max = None + maxname = None + for f in names: + m = namere.match(f) + if m: + index = int(m.group(1)) + if max == None or index > max: + max = index + maxname = f + if maxname: + return (os.path.join(dir, maxname), max) + return (None, None) + +def savename(path): + (last, index) = lastsavename(path) + if last is None: + index = 0 + newpath = path + ".%d" % (index + 1) + return newpath + +def push(ui, repo, patch=None, **opts): + """push the next patch onto the stack""" + q = repomap[repo] + mergeq = None + + if opts['all']: + patch = q.series[-1] + if opts['merge']: + if opts['name']: + newpath = opts['name'] + else: + newpath, i = lastsavename(q.path) + if not newpath: + ui.warn("no saved queues found, please use -n\n") + return 1 + mergeq = queue(ui, repo.join(""), newpath) + ui.warn("merging with queue at: %s\n" % mergeq.path) + ret = q.push(repo, patch, force=opts['force'], list=opts['list'], + mergeq=mergeq) + q.save_dirty() + return ret + +def pop(ui, repo, patch=None, **opts): + """pop the current patch off the stack""" + localupdate = True + if opts['name']: + q = queue(ui, repo.join(""), repo.join(opts['name'])) + ui.warn('using patch queue: %s\n' % q.path) + localupdate = False + else: + q = repomap[repo] + if opts['all'] and len(q.applied) > 0: + patch = q.applied[0].split(':')[1] + q.pop(repo, patch, force=opts['force'], update=localupdate) + q.save_dirty() + return 0 + +def restore(ui, repo, rev, **opts): + """restore the queue state saved by a rev""" + rev = repo.lookup(rev) + q = repomap[repo] + q.restore(repo, rev, delete=opts['delete'], + qupdate=opts['update']) + q.save_dirty() + return 0 + +def save(ui, repo, **opts): + """save current queue state""" + q = repomap[repo] + ret = q.save(repo, msg=opts['message']) + if ret: + return ret + q.save_dirty() + if opts['copy']: + path = q.path + if opts['name']: + newpath = os.path.join(q.basepath, opts['name']) + if os.path.exists(newpath): + if not os.path.isdir(newpath): + ui.warn("destination %s exists and is not a directory\n" % + newpath) + sys.exit(1) + if not opts['force']: + ui.warn("destination %s exists, use -f to force\n" % + newpath) + sys.exit(1) + else: + newpath = savename(path) + ui.warn("copy %s to %s\n" % (path, newpath)) + util.copyfiles(path, newpath) + if opts['empty']: + try: + os.unlink(os.path.join(q.path, q.status_path)) + except: + pass + return 0 + +def strip(ui, repo, rev, **opts): + """strip a revision and all later revs on the same branch""" + rev = repo.lookup(rev) + backup = 'all' + if opts['backup']: + backup = 'strip' + elif opts['nobackup']: + backup = 'none' + repomap[repo].strip(repo, rev, backup=backup) + return 0 + +def version(ui, q=None): + """print the version number""" + ui.write("mq version %s\n" % versionstr) + return 0 + +def reposetup(ui, repo): + repomap[repo] = queue(ui, repo.join("")) + +cmdtable = { + "qapplied": (applied, [], 'hg qapplied [PATCH]'), + "qcommit|qci": + (commit, + commands.table["^commit|ci"][1], + 'hg qcommit [OPTION]... [FILE]...'), + "^qdiff": (diff, [], 'hg qdiff [FILE]...'), + "qdelete": (delete, [], 'hg qdelete PATCH'), + "^qimport": + (qimport, + [('e', 'existing', None, 'import file in patch dir'), + ('n', 'name', '', 'patch file name'), + ('f', 'force', None, 'overwrite existing files')], + 'hg qimport [-e] [-n NAME] [-f] FILE...'), + "^qinit": + (init, + [('c', 'create-repo', None, 'create patch repository')], + 'hg qinit [-c]'), + "qnew": + (new, + [('m', 'message', '', 'commit message'), + ('f', 'force', None, 'force')], + 'hg qnew [-m TEXT] [-f] PATCH'), + "qnext": (next, [], 'hg qnext'), + "qprev": (prev, [], 'hg qprev'), + "^qpop": + (pop, + [('a', 'all', None, 'pop all patches'), + ('n', 'name', '', 'queue name to pop'), + ('f', 'force', None, 'forget any local changes')], + 'hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]'), + "^qpush": + (push, + [('f', 'force', None, 'apply if the patch has rejects'), + ('l', 'list', None, 'list patch name in commit text'), + ('a', 'all', None, 'apply all patches'), + ('m', 'merge', None, 'merge from another queue'), + ('n', 'name', '', 'merge queue name')], + 'hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]'), + "^qrefresh": + (refresh, + [('s', 'short', None, 'short refresh')], + 'hg qrefresh [-s]'), + "qrestore": + (restore, + [('d', 'delete', None, 'delete save entry'), + ('u', 'update', None, 'update queue working dir')], + 'hg qrestore [-d] [-u] REV'), + "qsave": + (save, + [('m', 'message', '', 'commit message'), + ('c', 'copy', None, 'copy patch directory'), + ('n', 'name', '', 'copy directory name'), + ('e', 'empty', None, 'clear queue status file'), + ('f', 'force', None, 'force copy')], + 'hg qsave [-m TEXT] [-c] [-n NAME] [-e] [-f]'), + "qseries": + (series, + [('m', 'missing', None, 'print patches not in series')], + 'hg qseries [-m]'), + "^strip": + (strip, + [('f', 'force', None, 'force multi-head removal'), + ('b', 'backup', None, 'bundle unrelated changesets'), + ('n', 'nobackup', None, 'no backups')], + 'hg strip [-f] [-b] [-n] REV'), + "qtop": (top, [], 'hg qtop'), + "qunapplied": (unapplied, [], 'hg qunapplied [PATCH]'), + "qversion": (version, [], 'hg qversion') +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/notify.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,276 @@ +# notify.py - email notifications for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +# +# hook extension to email notifications to people when changesets are +# committed to a repo they subscribe to. +# +# default mode is to print messages to stdout, for testing and +# configuring. +# +# to use, configure notify extension and enable in hgrc like this: +# +# [extensions] +# hgext.notify = +# +# [hooks] +# # one email for each incoming changeset +# incoming.notify = python:hgext.notify.hook +# # batch emails when many changesets incoming at one time +# changegroup.notify = python:hgext.notify.hook +# +# [notify] +# # config items go in here +# +# config items: +# +# REQUIRED: +# config = /path/to/file # file containing subscriptions +# +# OPTIONAL: +# test = True # print messages to stdout for testing +# strip = 3 # number of slashes to strip for url paths +# domain = example.com # domain to use if committer missing domain +# style = ... # style file to use when formatting email +# template = ... # template to use when formatting email +# incoming = ... # template to use when run as incoming hook +# changegroup = ... # template when run as changegroup hook +# maxdiff = 300 # max lines of diffs to include (0=none, -1=all) +# maxsubject = 67 # truncate subject line longer than this +# sources = serve # notify if source of incoming changes in this list +# # (serve == ssh or http, push, pull, bundle) +# [email] +# from = user@host.com # email address to send as if none given +# [web] +# baseurl = http://hgserver/... # root of hg web site for browsing commits +# +# notify config file has same format as regular hgrc. it has two +# sections so you can express subscriptions in whatever way is handier +# for you. +# +# [usersubs] +# # key is subscriber email, value is ","-separated list of glob patterns +# user@host = pattern +# +# [reposubs] +# # key is glob pattern, value is ","-separated list of subscriber emails +# pattern = user@host +# +# glob patterns are matched against path to repo root. +# +# if you like, you can put notify config file in repo that users can +# push changes to, they can manage their own subscriptions. + +from mercurial.demandload import * +from mercurial.i18n import gettext as _ +from mercurial.node import * +demandload(globals(), 'email.Parser mercurial:commands,templater,util') +demandload(globals(), 'fnmatch socket time') + +# template for single changeset can include email headers. +single_template = ''' +Subject: changeset in {webroot}: {desc|firstline|strip} +From: {author} + +changeset {node|short} in {root} +details: {baseurl}{webroot}?cmd=changeset;node={node|short} +description: +\t{desc|tabindent|strip} +'''.lstrip() + +# template for multiple changesets should not contain email headers, +# because only first set of headers will be used and result will look +# strange. +multiple_template = ''' +changeset {node|short} in {root} +details: {baseurl}{webroot}?cmd=changeset;node={node|short} +summary: {desc|firstline} +''' + +deftemplates = { + 'changegroup': multiple_template, + } + +class notifier(object): + '''email notification class.''' + + def __init__(self, ui, repo, hooktype): + self.ui = ui + cfg = self.ui.config('notify', 'config') + if cfg: + self.ui.readconfig(cfg) + self.repo = repo + self.stripcount = int(self.ui.config('notify', 'strip', 0)) + self.root = self.strip(self.repo.root) + self.domain = self.ui.config('notify', 'domain') + self.sio = templater.stringio() + self.subs = self.subscribers() + + mapfile = self.ui.config('notify', 'style') + template = (self.ui.config('notify', hooktype) or + self.ui.config('notify', 'template')) + self.t = templater.changeset_templater(self.ui, self.repo, mapfile, + self.sio) + if not mapfile and not template: + template = deftemplates.get(hooktype) or single_template + if template: + template = templater.parsestring(template, quoted=False) + self.t.use_template(template) + + def strip(self, path): + '''strip leading slashes from local path, turn into web-safe path.''' + + path = util.pconvert(path) + count = self.stripcount + while count > 0: + c = path.find('/') + if c == -1: + break + path = path[c+1:] + count -= 1 + return path + + def fixmail(self, addr): + '''try to clean up email addresses.''' + + addr = templater.email(addr.strip()) + a = addr.find('@localhost') + if a != -1: + addr = addr[:a] + if '@' not in addr: + return addr + '@' + self.domain + return addr + + def subscribers(self): + '''return list of email addresses of subscribers to this repo.''' + + subs = {} + for user, pats in self.ui.configitems('usersubs'): + for pat in pats.split(','): + if fnmatch.fnmatch(self.repo.root, pat.strip()): + subs[self.fixmail(user)] = 1 + for pat, users in self.ui.configitems('reposubs'): + if fnmatch.fnmatch(self.repo.root, pat): + for user in users.split(','): + subs[self.fixmail(user)] = 1 + subs = subs.keys() + subs.sort() + return subs + + def url(self, path=None): + return self.ui.config('web', 'baseurl') + (path or self.root) + + def node(self, node): + '''format one changeset.''' + + self.t.show(changenode=node, changes=self.repo.changelog.read(node), + baseurl=self.ui.config('web', 'baseurl'), + root=self.repo.root, + webroot=self.root) + + def skipsource(self, source): + '''true if incoming changes from this source should be skipped.''' + ok_sources = self.ui.config('notify', 'sources', 'serve').split() + return source not in ok_sources + + def send(self, node, count): + '''send message.''' + + p = email.Parser.Parser() + self.sio.seek(0) + msg = p.parse(self.sio) + + def fix_subject(): + '''try to make subject line exist and be useful.''' + + subject = msg['Subject'] + if not subject: + if count > 1: + subject = _('%s: %d new changesets') % (self.root, count) + else: + changes = self.repo.changelog.read(node) + s = changes[4].lstrip().split('\n', 1)[0].rstrip() + subject = '%s: %s' % (self.root, s) + maxsubject = int(self.ui.config('notify', 'maxsubject', 67)) + if maxsubject and len(subject) > maxsubject: + subject = subject[:maxsubject-3] + '...' + del msg['Subject'] + msg['Subject'] = subject + + def fix_sender(): + '''try to make message have proper sender.''' + + sender = msg['From'] + if not sender: + sender = self.ui.config('email', 'from') or self.ui.username() + if '@' not in sender or '@localhost' in sender: + sender = self.fixmail(sender) + del msg['From'] + msg['From'] = sender + + fix_subject() + fix_sender() + + msg['X-Hg-Notification'] = 'changeset ' + short(node) + if not msg['Message-Id']: + msg['Message-Id'] = ('<hg.%s.%s.%s@%s>' % + (short(node), int(time.time()), + hash(self.repo.root), socket.getfqdn())) + msg['To'] = ', '.join(self.subs) + + msgtext = msg.as_string(0) + if self.ui.configbool('notify', 'test', True): + self.ui.write(msgtext) + if not msgtext.endswith('\n'): + self.ui.write('\n') + else: + self.ui.status(_('notify: sending %d subscribers %d changes\n') % + (len(self.subs), count)) + mail = self.ui.sendmail() + mail.sendmail(templater.email(msg['From']), self.subs, msgtext) + + def diff(self, node, ref): + maxdiff = int(self.ui.config('notify', 'maxdiff', 300)) + if maxdiff == 0: + return + fp = templater.stringio() + prev = self.repo.changelog.parents(node)[0] + commands.dodiff(fp, self.ui, self.repo, prev, ref) + difflines = fp.getvalue().splitlines(1) + if maxdiff > 0 and len(difflines) > maxdiff: + self.sio.write(_('\ndiffs (truncated from %d to %d lines):\n\n') % + (len(difflines), maxdiff)) + difflines = difflines[:maxdiff] + elif difflines: + self.sio.write(_('\ndiffs (%d lines):\n\n') % len(difflines)) + self.sio.write(*difflines) + +def hook(ui, repo, hooktype, node=None, source=None, **kwargs): + '''send email notifications to interested subscribers. + + if used as changegroup hook, send one email for all changesets in + changegroup. else send one email per changeset.''' + n = notifier(ui, repo, hooktype) + if not n.subs: + ui.debug(_('notify: no subscribers to this repo\n')) + return + if n.skipsource(source): + ui.debug(_('notify: changes have source "%s" - skipping\n') % + source) + return + node = bin(node) + if hooktype == 'changegroup': + start = repo.changelog.rev(node) + end = repo.changelog.count() + count = end - start + for rev in xrange(start, end): + n.node(repo.changelog.node(rev)) + n.diff(node, repo.changelog.tip()) + else: + count = 1 + n.node(node) + n.diff(node, node) + n.send(node, count)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/patchbomb.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,270 @@ +# Command for sending a collection of Mercurial changesets as a series +# of patch emails. +# +# The series is started off with a "[PATCH 0 of N]" introduction, +# which describes the series as a whole. +# +# Each patch email has a Subject line of "[PATCH M of N] ...", using +# the first line of the changeset description as the subject text. +# The message contains two or three body parts: +# +# The remainder of the changeset description. +# +# [Optional] If the diffstat program is installed, the result of +# running diffstat on the patch. +# +# The patch itself, as generated by "hg export". +# +# Each message refers to all of its predecessors using the In-Reply-To +# and References headers, so they will show up as a sequence in +# threaded mail and news readers, and in mail archives. +# +# For each changeset, you will be prompted with a diffstat summary and +# the changeset summary, so you can be sure you are sending the right +# changes. +# +# It is best to run this script with the "-n" (test only) flag before +# firing it up "for real", in which case it will use your pager to +# display each of the messages that it would send. +# +# The "-m" (mbox) option will create an mbox file instead of sending +# the messages directly. This can be reviewed e.g. with "mutt -R -f mbox", +# and finally sent with "formail -s sendmail -bm -t < mbox". +# +# To configure other defaults, add a section like this to your hgrc +# file: +# +# [email] +# from = My Name <my@email> +# to = recipient1, recipient2, ... +# cc = cc1, cc2, ... + +from mercurial.demandload import * +demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils + mercurial:commands,hg,ui + os errno popen2 socket sys tempfile time''') +from mercurial.i18n import gettext as _ + +try: + # readline gives raw_input editing capabilities, but is not + # present on windows + import readline +except ImportError: pass + +def diffstat(patch): + fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt") + try: + p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) + try: + for line in patch: print >> p.tochild, line + p.tochild.close() + if p.wait(): return + fp = os.fdopen(fd, 'r') + stat = [] + for line in fp: stat.append(line.lstrip()) + last = stat.pop() + stat.insert(0, last) + stat = ''.join(stat) + if stat.startswith('0 files'): raise ValueError + return stat + except: raise + finally: + try: os.unlink(name) + except: pass + +def patchbomb(ui, repo, *revs, **opts): + '''send changesets as a series of patch emails + + The series starts with a "[PATCH 0 of N]" introduction, which + describes the series as a whole. + + Each patch email has a Subject line of "[PATCH M of N] ...", using + the first line of the changeset description as the subject text. + The message contains two or three body parts. First, the rest of + the changeset description. Next, (optionally) if the diffstat + program is installed, the result of running diffstat on the patch. + Finally, the patch itself, as generated by "hg export".''' + def prompt(prompt, default = None, rest = ': ', empty_ok = False): + if default: prompt += ' [%s]' % default + prompt += rest + while True: + r = raw_input(prompt) + if r: return r + if default is not None: return default + if empty_ok: return r + ui.warn(_('Please enter a valid value.\n')) + + def confirm(s): + if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): + raise ValueError + + def cdiffstat(summary, patch): + s = diffstat(patch) + if s: + if summary: + ui.write(summary, '\n') + ui.write(s, '\n') + confirm(_('Does the diffstat above look okay')) + return s + + def makepatch(patch, idx, total): + desc = [] + node = None + body = '' + for line in patch: + if line.startswith('#'): + if line.startswith('# Node ID'): node = line.split()[-1] + continue + if line.startswith('diff -r'): break + desc.append(line) + if not node: raise ValueError + + #body = ('\n'.join(desc[1:]).strip() or + # 'Patch subject is complete summary.') + #body += '\n\n\n' + + if opts['plain']: + while patch and patch[0].startswith('# '): patch.pop(0) + if patch: patch.pop(0) + while patch and not patch[0].strip(): patch.pop(0) + if opts['diffstat']: + body += cdiffstat('\n'.join(desc), patch) + '\n\n' + body += '\n'.join(patch) + msg = email.MIMEText.MIMEText(body) + if total == 1: + subj = '[PATCH] ' + desc[0].strip() + else: + subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) + if subj.endswith('.'): subj = subj[:-1] + msg['Subject'] = subj + msg['X-Mercurial-Node'] = node + return msg + + start_time = int(time.time()) + + def genmsgid(id): + return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) + + patches = [] + + class exportee: + def __init__(self, container): + self.lines = [] + self.container = container + self.name = 'email' + + def write(self, data): + self.lines.append(data) + + def close(self): + self.container.append(''.join(self.lines).split('\n')) + self.lines = [] + + commands.export(ui, repo, *revs, **{'output': exportee(patches), + 'switch_parent': False, + 'text': None}) + + jumbo = [] + msgs = [] + + ui.write(_('This patch series consists of %d patches.\n\n') % len(patches)) + + for p, i in zip(patches, range(len(patches))): + jumbo.extend(p) + msgs.append(makepatch(p, i + 1, len(patches))) + + sender = (opts['from'] or ui.config('email', 'from') or + ui.config('patchbomb', 'from') or + prompt('From', ui.username())) + + def getaddrs(opt, prpt, default = None): + addrs = opts[opt] or (ui.config('email', opt) or + ui.config('patchbomb', opt) or + prompt(prpt, default = default)).split(',') + return [a.strip() for a in addrs if a.strip()] + to = getaddrs('to', 'To') + cc = getaddrs('cc', 'Cc', '') + + if len(patches) > 1: + ui.write(_('\nWrite the introductory message for the patch series.\n\n')) + + msg = email.MIMEMultipart.MIMEMultipart() + msg['Subject'] = '[PATCH 0 of %d] %s' % ( + len(patches), + opts['subject'] or + prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) + + ui.write(_('Finish with ^D or a dot on a line by itself.\n\n')) + + body = [] + + while True: + try: l = raw_input() + except EOFError: break + if l == '.': break + body.append(l) + + msg.attach(email.MIMEText.MIMEText('\n'.join(body) + '\n')) + + if opts['diffstat']: + d = cdiffstat(_('Final summary:\n'), jumbo) + if d: msg.attach(email.MIMEText.MIMEText(d)) + + msgs.insert(0, msg) + + ui.write('\n') + + if not opts['test'] and not opts['mbox']: + mail = ui.sendmail() + parent = None + tz = time.strftime('%z') + sender_addr = email.Utils.parseaddr(sender)[1] + for m in msgs: + try: + m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) + except TypeError: + m['Message-Id'] = genmsgid('patchbomb') + if parent: + m['In-Reply-To'] = parent + else: + parent = m['Message-Id'] + m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz + start_time += 1 + m['From'] = sender + m['To'] = ', '.join(to) + if cc: m['Cc'] = ', '.join(cc) + if opts['test']: + ui.status('Displaying ', m['Subject'], ' ...\n') + fp = os.popen(os.getenv('PAGER', 'more'), 'w') + try: + fp.write(m.as_string(0)) + fp.write('\n') + except IOError, inst: + if inst.errno != errno.EPIPE: + raise + fp.close() + elif opts['mbox']: + ui.status('Writing ', m['Subject'], ' ...\n') + fp = open(opts['mbox'], m.has_key('In-Reply-To') and 'ab+' or 'wb+') + date = time.asctime(time.localtime(start_time)) + fp.write('From %s %s\n' % (sender_addr, date)) + fp.write(m.as_string(0)) + fp.write('\n\n') + fp.close() + else: + ui.status('Sending ', m['Subject'], ' ...\n') + mail.sendmail(sender, to + cc, m.as_string(0)) + +cmdtable = { + 'email': + (patchbomb, + [('c', 'cc', [], 'email addresses of copy recipients'), + ('d', 'diffstat', None, 'add diffstat output to messages'), + ('f', 'from', '', 'email address of sender'), + ('', 'plain', None, 'omit hg patch header'), + ('n', 'test', None, 'print messages that would be sent'), + ('m', 'mbox', '', 'write messages to mbox file instead of sending them'), + ('s', 'subject', '', 'subject of introductory message'), + ('t', 'to', [], 'email addresses of recipients')], + "hg email [OPTION]... [REV]...") + }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/win32text.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,28 @@ +import mercurial.util + +def dumbdecode(s, cmd): + return s.replace('\n', '\r\n') + +def dumbencode(s, cmd): + return s.replace('\r\n', '\n') + +def clevertest(s, cmd): + if '\0' in s: return False + return True + +def cleverdecode(s, cmd): + if clevertest(s, cmd): + return dumbdecode(s, cmd) + return s + +def cleverencode(s, cmd): + if clevertest(s, cmd): + return dumbencode(s, cmd) + return s + +mercurial.util.filtertable.update({ + 'dumbdecode:': dumbdecode, + 'dumbencode:': dumbencode, + 'cleverdecode:': cleverdecode, + 'cleverencode:': cleverencode, + })
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgmerge Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,188 @@ +#!/bin/sh +# +# hgmerge - default merge helper for Mercurial +# +# This tries to find a way to do three-way merge on the current system. +# The result ought to end up in $1. Script is run in root directory of +# repository. +# +# Environment variables set by Mercurial: +# HG_FILE name of file within repo +# HG_MY_NODE revision being merged +# HG_OTHER_NODE revision being merged + +set -e # bail out quickly on failure + +LOCAL="$1" +BASE="$2" +OTHER="$3" + +if [ -z "$EDITOR" ]; then + EDITOR="vi" +fi + +# find decent versions of our utilities, insisting on the GNU versions where we +# need to +MERGE="merge" +DIFF3="gdiff3" +DIFF="gdiff" +PATCH="gpatch" + +type "$MERGE" >/dev/null 2>&1 || MERGE= +type "$DIFF3" >/dev/null 2>&1 || DIFF3="diff3" +$DIFF3 --version >/dev/null 2>&1 || DIFF3= +type "$DIFF" >/dev/null 2>&1 || DIFF="diff" +type "$DIFF" >/dev/null 2>&1 || DIFF= +type "$PATCH" >/dev/null 2>&1 || PATCH="patch" +type "$PATCH" >/dev/null 2>&1 || PATCH= + +# find optional visual utilities +FILEMERGE="/Developer/Applications/Utilities/FileMerge.app/Contents/MacOS/FileMerge" +KDIFF3="kdiff3" +TKDIFF="tkdiff" +MELD="meld" + +type "$FILEMERGE" >/dev/null 2>&1 || FILEMERGE= +type "$KDIFF3" >/dev/null 2>&1 || KDIFF3= +type "$TKDIFF" >/dev/null 2>&1 || TKDIFF= +type "$MELD" >/dev/null 2>&1 || MELD= + +# Hack for Solaris +TEST="/usr/bin/test" +type "$TEST" >/dev/null 2>&1 || TEST="/bin/test" +type "$TEST" >/dev/null 2>&1 || TEST="test" + +# random part of names +RAND="$RANDOM$RANDOM" + +# temporary directory for diff+patch merge +HGTMP="${TMPDIR-/tmp}/hgmerge.$RAND" + +# backup file +BACKUP="$LOCAL.orig.$RAND" + +# file used to test for file change +CHGTEST="$LOCAL.chg.$RAND" + +# put all your required cleanup here +cleanup() { + rm -f "$BACKUP" "$CHGTEST" + rm -rf "$HGTMP" +} + +# functions concerning program exit +success() { + cleanup + exit 0 +} + +failure() { + echo "merge failed" 1>&2 + mv "$BACKUP" "$LOCAL" + cleanup + exit 1 +} + +# Ask if the merge was successful +ask_if_merged() { + while true; do + echo "$LOCAL seems unchanged." + echo "Was the merge successful? [y/n]" + read answer + case "$answer" in + y*|Y*) success;; + n*|N*) failure;; + esac + done +} + +# Clean up when interrupted +trap "failure" 1 2 3 6 15 # HUP INT QUIT ABRT TERM + +# Back up our file (and try hard to keep the mtime unchanged) +mv "$LOCAL" "$BACKUP" +cp "$BACKUP" "$LOCAL" + +# Attempt to do a non-interactive merge +if [ -n "$MERGE" -o -n "$DIFF3" ]; then + if [ -n "$MERGE" ]; then + $MERGE "$LOCAL" "$BASE" "$OTHER" 2> /dev/null && success + elif [ -n "$DIFF3" ]; then + $DIFF3 -m "$BACKUP" "$BASE" "$OTHER" > "$LOCAL" && success + fi + if [ $? -gt 1 ]; then + echo "automatic merge failed! Exiting." 1>&2 + failure + fi +fi + +# on MacOS X try FileMerge.app, shipped with Apple's developer tools +if [ -n "$FILEMERGE" ]; then + cp "$BACKUP" "$LOCAL" + cp "$BACKUP" "$CHGTEST" + # filemerge prefers the right by default + $FILEMERGE -left "$OTHER" -right "$LOCAL" -ancestor "$BASE" -merge "$LOCAL" + [ $? -ne 0 ] && echo "FileMerge failed to launch" && failure + $TEST "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged +fi + +if [ -n "$DISPLAY" ]; then + # try using kdiff3, which is fairly nice + if [ -n "$KDIFF3" ]; then + $KDIFF3 --auto "$BASE" "$BACKUP" "$OTHER" -o "$LOCAL" || failure + success + fi + + # try using tkdiff, which is a bit less sophisticated + if [ -n "$TKDIFF" ]; then + $TKDIFF "$BACKUP" "$OTHER" -a "$BASE" -o "$LOCAL" || failure + success + fi + + if [ -n "$MELD" ]; then + cp "$BACKUP" "$CHGTEST" + # protect our feet - meld allows us to save to the left file + cp "$BACKUP" "$LOCAL.tmp.$RAND" + # Meld doesn't have automatic merging, so to reduce intervention + # use the file with conflicts + $MELD "$LOCAL.tmp.$RAND" "$LOCAL" "$OTHER" || failure + # Also it doesn't return good error code + $TEST "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged + fi +fi + +# Attempt to do a merge with $EDITOR +if [ -n "$MERGE" -o -n "$DIFF3" ]; then + echo "conflicts detected in $LOCAL" + cp "$BACKUP" "$CHGTEST" + $EDITOR "$LOCAL" || failure + # Some editors do not return meaningful error codes + # Do not take any chances + $TEST "$LOCAL" -nt "$CHGTEST" && success || ask_if_merged +fi + +# attempt to manually merge with diff and patch +if [ -n "$DIFF" -a -n "$PATCH" ]; then + + (umask 077 && mkdir "$HGTMP") || { + echo "Could not create temporary directory $HGTMP" 1>&2 + failure + } + + $DIFF -u "$BASE" "$OTHER" > "$HGTMP/diff" || : + if $PATCH "$LOCAL" < "$HGTMP/diff"; then + success + else + # If rejects are empty after using the editor, merge was ok + $EDITOR "$LOCAL" "$LOCAL.rej" || failure + $TEST -s "$LOCAL.rej" || success + fi + failure +fi + +echo +echo "hgmerge: unable to find any merge utility!" +echo "supported programs:" +echo "merge, FileMerge, tkdiff, kdiff3, meld, diff+patch" +echo +failure
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgweb.cgi Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# +# An example CGI script to use hgweb, edit as necessary + +import cgitb, os, sys +cgitb.enable() + +# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install +from mercurial import hgweb + +h = hgweb.hgweb("/path/to/repo", "repository name") +h.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgwebdir.cgi Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# +# An example CGI script to export multiple hgweb repos, edit as necessary + +import cgitb, sys +cgitb.enable() + +# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install +from mercurial import hgweb + +# The config file looks like this. You can have paths to individual +# repos, collections of repos in a directory tree, or both. +# +# [paths] +# virtual/path = /real/path +# virtual/path = /real/path +# +# [collections] +# /prefix/to/strip/off = /root/of/tree/full/of/repos +# +# collections example: say directory tree /foo contains repos /foo/bar, +# /foo/quux/baz. Give this config section: +# [collections] +# /foo = /foo +# Then repos will list as bar and quux/baz. + +# Alternatively you can pass a list of ('virtual/path', '/real/path') tuples +# or use a dictionary with entries like 'virtual/path': '/real/path' + +h = hgweb.hgwebdir("hgweb.config") +h.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/appendfile.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,162 @@ +# appendfile.py - special classes to make repo updates atomic +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from demandload import * +demandload(globals(), "cStringIO changelog errno manifest os tempfile util") + +# writes to metadata files are ordered. reads: changelog, manifest, +# normal files. writes: normal files, manifest, changelog. + +# manifest contains pointers to offsets in normal files. changelog +# contains pointers to offsets in manifest. if reader reads old +# changelog while manifest or normal files are written, it has no +# pointers into new parts of those files that are maybe not consistent +# yet, so will not read them. + +# localrepo.addchangegroup thinks it writes changelog first, then +# manifest, then normal files (this is order they are available, and +# needed for computing linkrev fields), but uses appendfile to hide +# updates from readers. data not written to manifest or changelog +# until all normal files updated. write manifest first, then +# changelog. + +# with this write ordering, readers cannot see inconsistent view of +# repo during update. + +class appendfile(object): + '''implement enough of file protocol to append to revlog file. + appended data is written to temp file. reads and seeks span real + file and temp file. readers cannot see appended data until + writedata called.''' + + def __init__(self, fp, tmpname): + if tmpname: + self.tmpname = tmpname + self.tmpfp = util.posixfile(self.tmpname, 'ab+') + else: + fd, self.tmpname = tempfile.mkstemp(prefix="hg-appendfile-") + os.close(fd) + self.tmpfp = util.posixfile(self.tmpname, 'ab+') + self.realfp = fp + self.offset = fp.tell() + # real file is not written by anyone else. cache its size so + # seek and read can be fast. + self.realsize = util.fstat(fp).st_size + self.name = fp.name + + def end(self): + self.tmpfp.flush() # make sure the stat is correct + return self.realsize + util.fstat(self.tmpfp).st_size + + def tell(self): + return self.offset + + def flush(self): + self.tmpfp.flush() + + def close(self): + self.realfp.close() + self.tmpfp.close() + + def seek(self, offset, whence=0): + '''virtual file offset spans real file and temp file.''' + if whence == 0: + self.offset = offset + elif whence == 1: + self.offset += offset + elif whence == 2: + self.offset = self.end() + offset + + if self.offset < self.realsize: + self.realfp.seek(self.offset) + else: + self.tmpfp.seek(self.offset - self.realsize) + + def read(self, count=-1): + '''only trick here is reads that span real file and temp file.''' + fp = cStringIO.StringIO() + old_offset = self.offset + if self.offset < self.realsize: + s = self.realfp.read(count) + fp.write(s) + self.offset += len(s) + if count > 0: + count -= len(s) + if count != 0: + if old_offset != self.offset: + self.tmpfp.seek(self.offset - self.realsize) + s = self.tmpfp.read(count) + fp.write(s) + self.offset += len(s) + return fp.getvalue() + + def write(self, s): + '''append to temp file.''' + self.tmpfp.seek(0, 2) + self.tmpfp.write(s) + # all writes are appends, so offset must go to end of file. + self.offset = self.realsize + self.tmpfp.tell() + +class appendopener(object): + '''special opener for files that only read or append.''' + + def __init__(self, opener): + self.realopener = opener + # key: file name, value: appendfile name + self.tmpnames = {} + + def __call__(self, name, mode='r'): + '''open file.''' + + assert mode in 'ra+' + try: + realfp = self.realopener(name, 'r') + except IOError, err: + if err.errno != errno.ENOENT: raise + realfp = self.realopener(name, 'w+') + tmpname = self.tmpnames.get(name) + fp = appendfile(realfp, tmpname) + if tmpname is None: + self.tmpnames[name] = fp.tmpname + return fp + + def writedata(self): + '''copy data from temp files to real files.''' + # write .d file before .i file. + tmpnames = self.tmpnames.items() + tmpnames.sort() + for name, tmpname in tmpnames: + ifp = open(tmpname, 'rb') + ofp = self.realopener(name, 'a') + for chunk in util.filechunkiter(ifp): + ofp.write(chunk) + ifp.close() + os.unlink(tmpname) + del self.tmpnames[name] + ofp.close() + + def cleanup(self): + '''delete temp files (this discards unwritten data!)''' + for tmpname in self.tmpnames.values(): + os.unlink(tmpname) + +# files for changelog and manifest are in different appendopeners, so +# not mixed up together. + +class appendchangelog(changelog.changelog, appendopener): + def __init__(self, opener, version): + appendopener.__init__(self, opener) + changelog.changelog.__init__(self, self, version) + def checkinlinesize(self, fp, tr): + return + +class appendmanifest(manifest.manifest, appendopener): + def __init__(self, opener, version): + appendopener.__init__(self, opener) + manifest.manifest.__init__(self, self, version) + def checkinlinesize(self, fp, tr): + return
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/archival.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,173 @@ +# archival.py - revision archival for mercurial +# +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of +# the GNU General Public License, incorporated herein by reference. + +from demandload import * +from i18n import gettext as _ +from node import * +demandload(globals(), 'cStringIO os stat tarfile time util zipfile') + +def tidyprefix(dest, prefix, suffixes): + '''choose prefix to use for names in archive. make sure prefix is + safe for consumers.''' + + if prefix: + prefix = prefix.replace('\\', '/') + else: + if not isinstance(dest, str): + raise ValueError('dest must be string if no prefix') + prefix = os.path.basename(dest) + lower = prefix.lower() + for sfx in suffixes: + if lower.endswith(sfx): + prefix = prefix[:-len(sfx)] + break + lpfx = os.path.normpath(util.localpath(prefix)) + prefix = util.pconvert(lpfx) + if not prefix.endswith('/'): + prefix += '/' + if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix: + raise util.Abort(_('archive prefix contains illegal components')) + return prefix + +class tarit: + '''write archive to tar file or stream. can write uncompressed, + or compress with gzip or bzip2.''' + + def __init__(self, dest, prefix, kind=''): + self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz', + '.tgz', 'tbz2']) + self.mtime = int(time.time()) + if isinstance(dest, str): + self.z = tarfile.open(dest, mode='w:'+kind) + else: + self.z = tarfile.open(mode='w|'+kind, fileobj=dest) + + def addfile(self, name, mode, data): + i = tarfile.TarInfo(self.prefix + name) + i.mtime = self.mtime + i.size = len(data) + i.mode = mode + self.z.addfile(i, cStringIO.StringIO(data)) + + def done(self): + self.z.close() + +class tellable: + '''provide tell method for zipfile.ZipFile when writing to http + response file object.''' + + def __init__(self, fp): + self.fp = fp + self.offset = 0 + + def __getattr__(self, key): + return getattr(self.fp, key) + + def write(self, s): + self.fp.write(s) + self.offset += len(s) + + def tell(self): + return self.offset + +class zipit: + '''write archive to zip file or stream. can write uncompressed, + or compressed with deflate.''' + + def __init__(self, dest, prefix, compress=True): + self.prefix = tidyprefix(dest, prefix, ('.zip',)) + if not isinstance(dest, str): + try: + dest.tell() + except (AttributeError, IOError): + dest = tellable(dest) + self.z = zipfile.ZipFile(dest, 'w', + compress and zipfile.ZIP_DEFLATED or + zipfile.ZIP_STORED) + self.date_time = time.gmtime(time.time())[:6] + + def addfile(self, name, mode, data): + i = zipfile.ZipInfo(self.prefix + name, self.date_time) + i.compress_type = self.z.compression + i.flag_bits = 0x08 + # unzip will not honor unix file modes unless file creator is + # set to unix (id 3). + i.create_system = 3 + i.external_attr = (mode | stat.S_IFREG) << 16L + self.z.writestr(i, data) + + def done(self): + self.z.close() + +class fileit: + '''write archive as files in directory.''' + + def __init__(self, name, prefix): + if prefix: + raise util.Abort(_('cannot give prefix when archiving to files')) + self.basedir = name + self.dirs = {} + self.oflags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY | + getattr(os, 'O_BINARY', 0) | + getattr(os, 'O_NOFOLLOW', 0)) + + def addfile(self, name, mode, data): + destfile = os.path.join(self.basedir, name) + destdir = os.path.dirname(destfile) + if destdir not in self.dirs: + if not os.path.isdir(destdir): + os.makedirs(destdir) + self.dirs[destdir] = 1 + os.fdopen(os.open(destfile, self.oflags, mode), 'wb').write(data) + + def done(self): + pass + +archivers = { + 'files': fileit, + 'tar': tarit, + 'tbz2': lambda name, prefix: tarit(name, prefix, 'bz2'), + 'tgz': lambda name, prefix: tarit(name, prefix, 'gz'), + 'uzip': lambda name, prefix: zipit(name, prefix, False), + 'zip': zipit, + } + +def archive(repo, dest, node, kind, decode=True, matchfn=None, + prefix=None): + '''create archive of repo as it was at node. + + dest can be name of directory, name of archive file, or file + object to write archive to. + + kind is type of archive to create. + + decode tells whether to put files through decode filters from + hgrc. + + matchfn is function to filter names of files to write to archive. + + prefix is name of path to put before every archive member.''' + + def write(name, mode, data): + if matchfn and not matchfn(name): return + if decode: + fp = cStringIO.StringIO() + repo.wwrite(name, data, fp) + data = fp.getvalue() + archiver.addfile(name, mode, data) + + archiver = archivers[kind](dest, prefix) + mn = repo.changelog.read(node)[0] + mf = repo.manifest.read(mn).items() + mff = repo.manifest.readflags(mn) + mf.sort() + write('.hg_archival.txt', 0644, + 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node))) + for filename, filenode in mf: + write(filename, mff[filename] and 0755 or 0644, + repo.file(filename).read(filenode)) + archiver.done()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/bdiff.c Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,361 @@ +/* + bdiff.c - efficient binary diff extension for Mercurial + + Copyright 2005 Matt Mackall <mpm@selenic.com> + + This software may be used and distributed according to the terms of + the GNU General Public License, incorporated herein by reference. + + Based roughly on Python difflib +*/ + +#include <Python.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __hpux +#define inline +#endif + +#ifdef __SUNPRO_C +# define inline +#endif + +#ifdef _WIN32 +#ifdef _MSC_VER +#define inline __inline +typedef unsigned long uint32_t; +#else +#include <stdint.h> +#endif +static uint32_t htonl(uint32_t x) +{ + return ((x & 0x000000ffUL) << 24) | + ((x & 0x0000ff00UL) << 8) | + ((x & 0x00ff0000UL) >> 8) | + ((x & 0xff000000UL) >> 24); +} +#else +#include <sys/types.h> +#include <arpa/inet.h> +#endif + +struct line { + int h, len, n, e; + const char *l; +}; + +struct pos { + int pos, len; +}; + +struct hunk { + int a1, a2, b1, b2; +}; + +struct hunklist { + struct hunk *base, *head; +}; + +static inline uint32_t rol32(uint32_t word, unsigned int shift) +{ + return (word << shift) | (word >> (32 - shift)); +} + +int splitlines(const char *a, int len, struct line **lr) +{ + int h, i; + const char *p, *b = a; + struct line *l; + + /* count the lines */ + i = 1; /* extra line for sentinel */ + for (p = a; p < a + len; p++) + if (*p == '\n' || p == a + len - 1) + i++; + + *lr = l = (struct line *)malloc(sizeof(struct line) * i); + if (!l) + return -1; + + /* build the line array and calculate hashes */ + h = 0; + for (p = a; p < a + len; p++) { + h = *p + rol32(h, 7); /* a simple hash from GNU diff */ + if (*p == '\n' || p == a + len - 1) { + l->len = p - b + 1; + l->h = h * l->len; + l->l = b; + l->n = -1; + l++; + b = p + 1; + h = 0; + } + } + + /* set up a sentinel */ + l->h = l->len = 0; + l->l = a + len; + return i - 1; +} + +int inline cmp(struct line *a, struct line *b) +{ + return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len); +} + +static int equatelines(struct line *a, int an, struct line *b, int bn) +{ + int i, j, buckets = 1, t; + struct pos *h; + + /* build a hash table of the next highest power of 2 */ + while (buckets < bn + 1) + buckets *= 2; + + h = (struct pos *)malloc(buckets * sizeof(struct pos)); + buckets = buckets - 1; + if (!h) + return 0; + + /* clear the hash table */ + for (i = 0; i <= buckets; i++) { + h[i].pos = -1; + h[i].len = 0; + } + + /* add lines to the hash table chains */ + for (i = bn - 1; i >= 0; i--) { + /* find the equivalence class */ + for (j = b[i].h & buckets; h[j].pos != -1; + j = (j + 1) & buckets) + if (!cmp(b + i, b + h[j].pos)) + break; + + /* add to the head of the equivalence class */ + b[i].n = h[j].pos; + b[i].e = j; + h[j].pos = i; + h[j].len++; /* keep track of popularity */ + } + + /* compute popularity threshold */ + t = (bn >= 200) ? bn / 100 : bn + 1; + + /* match items in a to their equivalence class in b */ + for (i = 0; i < an; i++) { + /* find the equivalence class */ + for (j = a[i].h & buckets; h[j].pos != -1; + j = (j + 1) & buckets) + if (!cmp(a + i, b + h[j].pos)) + break; + + a[i].e = j; /* use equivalence class for quick compare */ + if (h[j].len <= t) + a[i].n = h[j].pos; /* point to head of match list */ + else + a[i].n = -1; /* too popular */ + } + + /* discard hash tables */ + free(h); + return 1; +} + +static int longest_match(struct line *a, struct line *b, struct pos *pos, + int a1, int a2, int b1, int b2, int *omi, int *omj) +{ + int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k; + + for (i = a1; i < a2; i++) { + /* skip things before the current block */ + for (j = a[i].n; j != -1 && j < b1; j = b[j].n) + ; + + /* loop through all lines match a[i] in b */ + for (; j != -1 && j < b2; j = b[j].n) { + /* does this extend an earlier match? */ + if (i > a1 && j > b1 && pos[j - 1].pos == i - 1) + k = pos[j - 1].len + 1; + else + k = 1; + pos[j].pos = i; + pos[j].len = k; + + /* best match so far? */ + if (k > mk) { + mi = i; + mj = j; + mk = k; + } + } + } + + if (mk) { + mi = mi - mk + 1; + mj = mj - mk + 1; + } + + /* expand match to include neighboring popular lines */ + while (mi - mb > a1 && mj - mb > b1 && + a[mi - mb - 1].e == b[mj - mb - 1].e) + mb++; + while (mi + mk < a2 && mj + mk < b2 && + a[mi + mk].e == b[mj + mk].e) + mk++; + + *omi = mi - mb; + *omj = mj - mb; + return mk + mb; +} + +static void recurse(struct line *a, struct line *b, struct pos *pos, + int a1, int a2, int b1, int b2, struct hunklist *l) +{ + int i, j, k; + + /* find the longest match in this chunk */ + k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j); + if (!k) + return; + + /* and recurse on the remaining chunks on either side */ + recurse(a, b, pos, a1, i, b1, j, l); + l->head->a1 = i; + l->head->a2 = i + k; + l->head->b1 = j; + l->head->b2 = j + k; + l->head++; + recurse(a, b, pos, i + k, a2, j + k, b2, l); +} + +static struct hunklist diff(struct line *a, int an, struct line *b, int bn) +{ + struct hunklist l; + struct pos *pos; + int t; + + /* allocate and fill arrays */ + t = equatelines(a, an, b, bn); + pos = (struct pos *)calloc(bn, sizeof(struct pos)); + /* we can't have more matches than lines in the shorter file */ + l.head = l.base = (struct hunk *)malloc(sizeof(struct hunk) * + ((an<bn ? an:bn) + 1)); + + if (pos && l.base && t) { + /* generate the matching block list */ + recurse(a, b, pos, 0, an, 0, bn, &l); + l.head->a1 = an; + l.head->b1 = bn; + l.head++; + } + + free(pos); + return l; +} + +static PyObject *blocks(PyObject *self, PyObject *args) +{ + PyObject *sa, *sb, *rl = NULL, *m; + struct line *a, *b; + struct hunklist l = {NULL, NULL}; + struct hunk *h; + int an, bn, pos = 0; + + if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb)) + return NULL; + + an = splitlines(PyString_AsString(sa), PyString_Size(sa), &a); + bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &b); + if (!a || !b) + goto nomem; + + l = diff(a, an, b, bn); + rl = PyList_New(l.head - l.base); + if (!l.head || !rl) + goto nomem; + + for (h = l.base; h != l.head; h++) { + m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2); + PyList_SetItem(rl, pos, m); + pos++; + } + +nomem: + free(a); + free(b); + free(l.base); + return rl ? rl : PyErr_NoMemory(); +} + +static PyObject *bdiff(PyObject *self, PyObject *args) +{ + PyObject *sa, *sb, *result = NULL; + struct line *al, *bl; + struct hunklist l = {NULL, NULL}; + struct hunk *h; + char encode[12], *rb; + int an, bn, len = 0, la = 0, lb = 0; + + if (!PyArg_ParseTuple(args, "SS:bdiff", &sa, &sb)) + return NULL; + + an = splitlines(PyString_AsString(sa), PyString_Size(sa), &al); + bn = splitlines(PyString_AsString(sb), PyString_Size(sb), &bl); + if (!al || !bl) + goto nomem; + + l = diff(al, an, bl, bn); + if (!l.head) + goto nomem; + + /* calculate length of output */ + for (h = l.base; h != l.head; h++) { + if (h->a1 != la || h->b1 != lb) + len += 12 + bl[h->b1].l - bl[lb].l; + la = h->a2; + lb = h->b2; + } + + result = PyString_FromStringAndSize(NULL, len); + if (!result) + goto nomem; + + /* build binary patch */ + rb = PyString_AsString(result); + la = lb = 0; + + for (h = l.base; h != l.head; h++) { + if (h->a1 != la || h->b1 != lb) { + len = bl[h->b1].l - bl[lb].l; + *(uint32_t *)(encode) = htonl(al[la].l - al->l); + *(uint32_t *)(encode + 4) = htonl(al[h->a1].l - al->l); + *(uint32_t *)(encode + 8) = htonl(len); + memcpy(rb, encode, 12); + memcpy(rb + 12, bl[lb].l, len); + rb += 12 + len; + } + la = h->a2; + lb = h->b2; + } + +nomem: + free(al); + free(bl); + free(l.base); + return result ? result : PyErr_NoMemory(); +} + +static char mdiff_doc[] = "Efficient binary diff."; + +static PyMethodDef methods[] = { + {"bdiff", bdiff, METH_VARARGS, "calculate a binary diff\n"}, + {"blocks", blocks, METH_VARARGS, "find a list of matching lines\n"}, + {NULL, NULL} +}; + +PyMODINIT_FUNC initbdiff(void) +{ + Py_InitModule3("bdiff", methods, mdiff_doc); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/bundlerepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,232 @@ +""" +bundlerepo.py - repository class for viewing uncompressed bundles + +This provides a read-only repository interface to bundles as if +they were part of the actual repository. + +Copyright 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" + +from node import * +from i18n import gettext as _ +from demandload import demandload +demandload(globals(), "changegroup util os struct bz2 tempfile") + +import localrepo, changelog, manifest, filelog, revlog + +class bundlerevlog(revlog.revlog): + def __init__(self, opener, indexfile, datafile, bundlefile, + linkmapper=None): + # How it works: + # to retrieve a revision, we need to know the offset of + # the revision in the bundlefile (an opened file). + # + # We store this offset in the index (start), to differentiate a + # rev in the bundle and from a rev in the revlog, we check + # len(index[r]). If the tuple is bigger than 7, it is a bundle + # (it is bigger since we store the node to which the delta is) + # + revlog.revlog.__init__(self, opener, indexfile, datafile) + self.bundlefile = bundlefile + self.basemap = {} + def chunkpositer(): + for chunk in changegroup.chunkiter(bundlefile): + pos = bundlefile.tell() + yield chunk, pos - len(chunk) + n = self.count() + prev = None + for chunk, start in chunkpositer(): + size = len(chunk) + if size < 80: + raise util.Abort("invalid changegroup") + start += 80 + size -= 80 + node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80]) + if node in self.nodemap: + prev = node + continue + for p in (p1, p2): + if not p in self.nodemap: + raise revlog.RevlogError(_("unknown parent %s") % short(p1)) + if linkmapper is None: + link = n + else: + link = linkmapper(cs) + + if not prev: + prev = p1 + # start, size, base is not used, link, p1, p2, delta ref + if self.version == 0: + e = (start, size, None, link, p1, p2, node) + else: + e = (self.offset_type(start, 0), size, -1, None, link, + self.rev(p1), self.rev(p2), node) + self.basemap[n] = prev + self.index.append(e) + self.nodemap[node] = n + prev = node + n += 1 + + def bundle(self, rev): + """is rev from the bundle""" + if rev < 0: + return False + return rev in self.basemap + def bundlebase(self, rev): return self.basemap[rev] + def chunk(self, rev, df=None, cachelen=4096): + # Warning: in case of bundle, the diff is against bundlebase, + # not against rev - 1 + # XXX: could use some caching + if not self.bundle(rev): + return revlog.revlog.chunk(self, rev, df, cachelen) + self.bundlefile.seek(self.start(rev)) + return self.bundlefile.read(self.length(rev)) + + def revdiff(self, rev1, rev2): + """return or calculate a delta between two revisions""" + if self.bundle(rev1) and self.bundle(rev2): + # hot path for bundle + revb = self.rev(self.bundlebase(rev2)) + if revb == rev1: + return self.chunk(rev2) + elif not self.bundle(rev1) and not self.bundle(rev2): + return revlog.revlog.chunk(self, rev1, rev2) + + return self.diff(self.revision(self.node(rev1)), + self.revision(self.node(rev2))) + + def revision(self, node): + """return an uncompressed revision of a given""" + if node == nullid: return "" + + text = None + chain = [] + iter_node = node + rev = self.rev(iter_node) + # reconstruct the revision if it is from a changegroup + while self.bundle(rev): + if self.cache and self.cache[0] == iter_node: + text = self.cache[2] + break + chain.append(rev) + iter_node = self.bundlebase(rev) + rev = self.rev(iter_node) + if text is None: + text = revlog.revlog.revision(self, iter_node) + + while chain: + delta = self.chunk(chain.pop()) + text = self.patches(text, [delta]) + + p1, p2 = self.parents(node) + if node != revlog.hash(text, p1, p2): + raise revlog.RevlogError(_("integrity check failed on %s:%d") + % (self.datafile, self.rev(node))) + + self.cache = (node, self.rev(node), text) + return text + + def addrevision(self, text, transaction, link, p1=None, p2=None, d=None): + raise NotImplementedError + def addgroup(self, revs, linkmapper, transaction, unique=0): + raise NotImplementedError + def strip(self, rev, minlink): + raise NotImplementedError + def checksize(self): + raise NotImplementedError + +class bundlechangelog(bundlerevlog, changelog.changelog): + def __init__(self, opener, bundlefile): + changelog.changelog.__init__(self, opener) + bundlerevlog.__init__(self, opener, "00changelog.i", "00changelog.d", + bundlefile) + +class bundlemanifest(bundlerevlog, manifest.manifest): + def __init__(self, opener, bundlefile, linkmapper): + manifest.manifest.__init__(self, opener) + bundlerevlog.__init__(self, opener, self.indexfile, self.datafile, + bundlefile, linkmapper) + +class bundlefilelog(bundlerevlog, filelog.filelog): + def __init__(self, opener, path, bundlefile, linkmapper): + filelog.filelog.__init__(self, opener, path) + bundlerevlog.__init__(self, opener, self.indexfile, self.datafile, + bundlefile, linkmapper) + +class bundlerepository(localrepo.localrepository): + def __init__(self, ui, path, bundlename): + localrepo.localrepository.__init__(self, ui, path) + self.tempfile = None + self.bundlefile = open(bundlename, "rb") + header = self.bundlefile.read(6) + if not header.startswith("HG"): + raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename) + elif not header.startswith("HG10"): + raise util.Abort(_("%s: unknown bundle version") % bundlename) + elif header == "HG10BZ": + fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-", + suffix=".hg10un", dir=self.path) + self.tempfile = temp + fptemp = os.fdopen(fdtemp, 'wb') + def generator(f): + zd = bz2.BZ2Decompressor() + zd.decompress("BZ") + for chunk in f: + yield zd.decompress(chunk) + gen = generator(util.filechunkiter(self.bundlefile, 4096)) + + try: + fptemp.write("HG10UN") + for chunk in gen: + fptemp.write(chunk) + finally: + fptemp.close() + self.bundlefile.close() + + self.bundlefile = open(self.tempfile, "rb") + # seek right after the header + self.bundlefile.seek(6) + elif header == "HG10UN": + # nothing to do + pass + else: + raise util.Abort(_("%s: unknown bundle compression type") + % bundlename) + self.changelog = bundlechangelog(self.opener, self.bundlefile) + self.manifest = bundlemanifest(self.opener, self.bundlefile, + self.changelog.rev) + # dict with the mapping 'filename' -> position in the bundle + self.bundlefilespos = {} + while 1: + f = changegroup.getchunk(self.bundlefile) + if not f: + break + self.bundlefilespos[f] = self.bundlefile.tell() + for c in changegroup.chunkiter(self.bundlefile): + pass + + def dev(self): + return -1 + + def file(self, f): + if f[0] == '/': + f = f[1:] + if f in self.bundlefilespos: + self.bundlefile.seek(self.bundlefilespos[f]) + return bundlefilelog(self.opener, f, self.bundlefile, + self.changelog.rev) + else: + return filelog.filelog(self.opener, f) + + def close(self): + """Close assigned bundle file immediately.""" + self.bundlefile.close() + + def __del__(self): + if not self.bundlefile.closed: + self.bundlefile.close() + if self.tempfile is not None: + os.unlink(self.tempfile)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/byterange.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,450 @@ +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, +# Boston, MA 02111-1307 USA + +# This file is part of urlgrabber, a high-level cross-protocol url-grabber +# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko + +# $Id: byterange.py,v 1.9 2005/02/14 21:55:07 mstenner Exp $ + +import os +import stat +import urllib +import urllib2 +import rfc822 + +try: + from cStringIO import StringIO +except ImportError, msg: + from StringIO import StringIO + +class RangeError(IOError): + """Error raised when an unsatisfiable range is requested.""" + pass + +class HTTPRangeHandler(urllib2.BaseHandler): + """Handler that enables HTTP Range headers. + + This was extremely simple. The Range header is a HTTP feature to + begin with so all this class does is tell urllib2 that the + "206 Partial Content" reponse from the HTTP server is what we + expected. + + Example: + import urllib2 + import byterange + + range_handler = range.HTTPRangeHandler() + opener = urllib2.build_opener(range_handler) + + # install it + urllib2.install_opener(opener) + + # create Request and set Range header + req = urllib2.Request('http://www.python.org/') + req.header['Range'] = 'bytes=30-50' + f = urllib2.urlopen(req) + """ + + def http_error_206(self, req, fp, code, msg, hdrs): + # 206 Partial Content Response + r = urllib.addinfourl(fp, hdrs, req.get_full_url()) + r.code = code + r.msg = msg + return r + + def http_error_416(self, req, fp, code, msg, hdrs): + # HTTP's Range Not Satisfiable error + raise RangeError('Requested Range Not Satisfiable') + +class RangeableFileObject: + """File object wrapper to enable raw range handling. + This was implemented primarilary for handling range + specifications for file:// urls. This object effectively makes + a file object look like it consists only of a range of bytes in + the stream. + + Examples: + # expose 10 bytes, starting at byte position 20, from + # /etc/aliases. + >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30)) + # seek seeks within the range (to position 23 in this case) + >>> fo.seek(3) + # tell tells where your at _within the range_ (position 3 in + # this case) + >>> fo.tell() + # read EOFs if an attempt is made to read past the last + # byte in the range. the following will return only 7 bytes. + >>> fo.read(30) + """ + + def __init__(self, fo, rangetup): + """Create a RangeableFileObject. + fo -- a file like object. only the read() method need be + supported but supporting an optimized seek() is + preferable. + rangetup -- a (firstbyte,lastbyte) tuple specifying the range + to work over. + The file object provided is assumed to be at byte offset 0. + """ + self.fo = fo + (self.firstbyte, self.lastbyte) = range_tuple_normalize(rangetup) + self.realpos = 0 + self._do_seek(self.firstbyte) + + def __getattr__(self, name): + """This effectively allows us to wrap at the instance level. + Any attribute not found in _this_ object will be searched for + in self.fo. This includes methods.""" + if hasattr(self.fo, name): + return getattr(self.fo, name) + raise AttributeError, name + + def tell(self): + """Return the position within the range. + This is different from fo.seek in that position 0 is the + first byte position of the range tuple. For example, if + this object was created with a range tuple of (500,899), + tell() will return 0 when at byte position 500 of the file. + """ + return (self.realpos - self.firstbyte) + + def seek(self,offset,whence=0): + """Seek within the byte range. + Positioning is identical to that described under tell(). + """ + assert whence in (0, 1, 2) + if whence == 0: # absolute seek + realoffset = self.firstbyte + offset + elif whence == 1: # relative seek + realoffset = self.realpos + offset + elif whence == 2: # absolute from end of file + # XXX: are we raising the right Error here? + raise IOError('seek from end of file not supported.') + + # do not allow seek past lastbyte in range + if self.lastbyte and (realoffset >= self.lastbyte): + realoffset = self.lastbyte + + self._do_seek(realoffset - self.realpos) + + def read(self, size=-1): + """Read within the range. + This method will limit the size read based on the range. + """ + size = self._calc_read_size(size) + rslt = self.fo.read(size) + self.realpos += len(rslt) + return rslt + + def readline(self, size=-1): + """Read lines within the range. + This method will limit the size read based on the range. + """ + size = self._calc_read_size(size) + rslt = self.fo.readline(size) + self.realpos += len(rslt) + return rslt + + def _calc_read_size(self, size): + """Handles calculating the amount of data to read based on + the range. + """ + if self.lastbyte: + if size > -1: + if ((self.realpos + size) >= self.lastbyte): + size = (self.lastbyte - self.realpos) + else: + size = (self.lastbyte - self.realpos) + return size + + def _do_seek(self,offset): + """Seek based on whether wrapped object supports seek(). + offset is relative to the current position (self.realpos). + """ + assert offset >= 0 + if not hasattr(self.fo, 'seek'): + self._poor_mans_seek(offset) + else: + self.fo.seek(self.realpos + offset) + self.realpos+= offset + + def _poor_mans_seek(self,offset): + """Seek by calling the wrapped file objects read() method. + This is used for file like objects that do not have native + seek support. The wrapped objects read() method is called + to manually seek to the desired position. + offset -- read this number of bytes from the wrapped + file object. + raise RangeError if we encounter EOF before reaching the + specified offset. + """ + pos = 0 + bufsize = 1024 + while pos < offset: + if (pos + bufsize) > offset: + bufsize = offset - pos + buf = self.fo.read(bufsize) + if len(buf) != bufsize: + raise RangeError('Requested Range Not Satisfiable') + pos+= bufsize + +class FileRangeHandler(urllib2.FileHandler): + """FileHandler subclass that adds Range support. + This class handles Range headers exactly like an HTTP + server would. + """ + def open_local_file(self, req): + import mimetypes + import mimetools + host = req.get_host() + file = req.get_selector() + localfile = urllib.url2pathname(file) + stats = os.stat(localfile) + size = stats[stat.ST_SIZE] + modified = rfc822.formatdate(stats[stat.ST_MTIME]) + mtype = mimetypes.guess_type(file)[0] + if host: + host, port = urllib.splitport(host) + if port or socket.gethostbyname(host) not in self.get_names(): + raise urllib2.URLError('file not on local host') + fo = open(localfile,'rb') + brange = req.headers.get('Range',None) + brange = range_header_to_tuple(brange) + assert brange != () + if brange: + (fb,lb) = brange + if lb == '': lb = size + if fb < 0 or fb > size or lb > size: + raise RangeError('Requested Range Not Satisfiable') + size = (lb - fb) + fo = RangeableFileObject(fo, (fb,lb)) + headers = mimetools.Message(StringIO( + 'Content-Type: %s\nContent-Length: %d\nLast-modified: %s\n' % + (mtype or 'text/plain', size, modified))) + return urllib.addinfourl(fo, headers, 'file:'+file) + + +# FTP Range Support +# Unfortunately, a large amount of base FTP code had to be copied +# from urllib and urllib2 in order to insert the FTP REST command. +# Code modifications for range support have been commented as +# follows: +# -- range support modifications start/end here + +from urllib import splitport, splituser, splitpasswd, splitattr, \ + unquote, addclosehook, addinfourl +import ftplib +import socket +import sys +import mimetypes +import mimetools + +class FTPRangeHandler(urllib2.FTPHandler): + def ftp_open(self, req): + host = req.get_host() + if not host: + raise IOError, ('ftp error', 'no host given') + host, port = splitport(host) + if port is None: + port = ftplib.FTP_PORT + + # username/password handling + user, host = splituser(host) + if user: + user, passwd = splitpasswd(user) + else: + passwd = None + host = unquote(host) + user = unquote(user or '') + passwd = unquote(passwd or '') + + try: + host = socket.gethostbyname(host) + except socket.error, msg: + raise urllib2.URLError(msg) + path, attrs = splitattr(req.get_selector()) + dirs = path.split('/') + dirs = map(unquote, dirs) + dirs, file = dirs[:-1], dirs[-1] + if dirs and not dirs[0]: + dirs = dirs[1:] + try: + fw = self.connect_ftp(user, passwd, host, port, dirs) + type = file and 'I' or 'D' + for attr in attrs: + attr, value = splitattr(attr) + if attr.lower() == 'type' and \ + value in ('a', 'A', 'i', 'I', 'd', 'D'): + type = value.upper() + + # -- range support modifications start here + rest = None + range_tup = range_header_to_tuple(req.headers.get('Range',None)) + assert range_tup != () + if range_tup: + (fb,lb) = range_tup + if fb > 0: rest = fb + # -- range support modifications end here + + fp, retrlen = fw.retrfile(file, type, rest) + + # -- range support modifications start here + if range_tup: + (fb,lb) = range_tup + if lb == '': + if retrlen is None or retrlen == 0: + raise RangeError('Requested Range Not Satisfiable due to unobtainable file length.') + lb = retrlen + retrlen = lb - fb + if retrlen < 0: + # beginning of range is larger than file + raise RangeError('Requested Range Not Satisfiable') + else: + retrlen = lb - fb + fp = RangeableFileObject(fp, (0,retrlen)) + # -- range support modifications end here + + headers = "" + mtype = mimetypes.guess_type(req.get_full_url())[0] + if mtype: + headers += "Content-Type: %s\n" % mtype + if retrlen is not None and retrlen >= 0: + headers += "Content-Length: %d\n" % retrlen + sf = StringIO(headers) + headers = mimetools.Message(sf) + return addinfourl(fp, headers, req.get_full_url()) + except ftplib.all_errors, msg: + raise IOError, ('ftp error', msg), sys.exc_info()[2] + + def connect_ftp(self, user, passwd, host, port, dirs): + fw = ftpwrapper(user, passwd, host, port, dirs) + return fw + +class ftpwrapper(urllib.ftpwrapper): + # range support note: + # this ftpwrapper code is copied directly from + # urllib. The only enhancement is to add the rest + # argument and pass it on to ftp.ntransfercmd + def retrfile(self, file, type, rest=None): + self.endtransfer() + if type in ('d', 'D'): cmd = 'TYPE A'; isdir = 1 + else: cmd = 'TYPE ' + type; isdir = 0 + try: + self.ftp.voidcmd(cmd) + except ftplib.all_errors: + self.init() + self.ftp.voidcmd(cmd) + conn = None + if file and not isdir: + # Use nlst to see if the file exists at all + try: + self.ftp.nlst(file) + except ftplib.error_perm, reason: + raise IOError, ('ftp error', reason), sys.exc_info()[2] + # Restore the transfer mode! + self.ftp.voidcmd(cmd) + # Try to retrieve as a file + try: + cmd = 'RETR ' + file + conn = self.ftp.ntransfercmd(cmd, rest) + except ftplib.error_perm, reason: + if str(reason).startswith('501'): + # workaround for REST not supported error + fp, retrlen = self.retrfile(file, type) + fp = RangeableFileObject(fp, (rest,'')) + return (fp, retrlen) + elif not str(reason).startswith('550'): + raise IOError, ('ftp error', reason), sys.exc_info()[2] + if not conn: + # Set transfer mode to ASCII! + self.ftp.voidcmd('TYPE A') + # Try a directory listing + if file: cmd = 'LIST ' + file + else: cmd = 'LIST' + conn = self.ftp.ntransfercmd(cmd) + self.busy = 1 + # Pass back both a suitably decorated object and a retrieval length + return (addclosehook(conn[0].makefile('rb'), + self.endtransfer), conn[1]) + + +#################################################################### +# Range Tuple Functions +# XXX: These range tuple functions might go better in a class. + +_rangere = None +def range_header_to_tuple(range_header): + """Get a (firstbyte,lastbyte) tuple from a Range header value. + + Range headers have the form "bytes=<firstbyte>-<lastbyte>". This + function pulls the firstbyte and lastbyte values and returns + a (firstbyte,lastbyte) tuple. If lastbyte is not specified in + the header value, it is returned as an empty string in the + tuple. + + Return None if range_header is None + Return () if range_header does not conform to the range spec + pattern. + + """ + global _rangere + if range_header is None: return None + if _rangere is None: + import re + _rangere = re.compile(r'^bytes=(\d{1,})-(\d*)') + match = _rangere.match(range_header) + if match: + tup = range_tuple_normalize(match.group(1,2)) + if tup and tup[1]: + tup = (tup[0],tup[1]+1) + return tup + return () + +def range_tuple_to_header(range_tup): + """Convert a range tuple to a Range header value. + Return a string of the form "bytes=<firstbyte>-<lastbyte>" or None + if no range is needed. + """ + if range_tup is None: return None + range_tup = range_tuple_normalize(range_tup) + if range_tup: + if range_tup[1]: + range_tup = (range_tup[0],range_tup[1] - 1) + return 'bytes=%s-%s' % range_tup + +def range_tuple_normalize(range_tup): + """Normalize a (first_byte,last_byte) range tuple. + Return a tuple whose first element is guaranteed to be an int + and whose second element will be '' (meaning: the last byte) or + an int. Finally, return None if the normalized tuple == (0,'') + as that is equivelant to retrieving the entire file. + """ + if range_tup is None: return None + # handle first byte + fb = range_tup[0] + if fb in (None,''): fb = 0 + else: fb = int(fb) + # handle last byte + try: lb = range_tup[1] + except IndexError: lb = '' + else: + if lb is None: lb = '' + elif lb != '': lb = int(lb) + # check if range is over the entire file + if (fb,lb) == (0,''): return None + # check that the range is valid + if lb < fb: raise RangeError('Invalid byte range: %s-%s' % (fb,lb)) + return (fb,lb)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/changegroup.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,44 @@ +""" +changegroup.py - Mercurial changegroup manipulation functions + + Copyright 2006 Matt Mackall <mpm@selenic.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" +import struct +from i18n import gettext as _ +from demandload import * +demandload(globals(), "util") + +def getchunk(source): + """get a chunk from a changegroup""" + d = source.read(4) + if not d: + return "" + l = struct.unpack(">l", d)[0] + if l <= 4: + return "" + d = source.read(l - 4) + if len(d) < l - 4: + raise util.Abort(_("premature EOF reading chunk" + " (got %d bytes, expected %d)") + % (len(d), l - 4)) + return d + +def chunkiter(source): + """iterate through the chunks in source""" + while 1: + c = getchunk(source) + if not c: + break + yield c + +def genchunk(data): + """build a changegroup chunk""" + header = struct.pack(">l", len(data)+ 4) + return "%s%s" % (header, data) + +def closechunk(): + return struct.pack(">l", 0) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/changelog.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,59 @@ +# changelog.py - changelog class for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from revlog import * +from i18n import gettext as _ +from demandload import demandload +demandload(globals(), "os time util") + +class changelog(revlog): + def __init__(self, opener, defversion=REVLOGV0): + revlog.__init__(self, opener, "00changelog.i", "00changelog.d", + defversion) + + def extract(self, text): + if not text: + return (nullid, "", (0, 0), [], "") + last = text.index("\n\n") + desc = text[last + 2:] + l = text[:last].splitlines() + manifest = bin(l[0]) + user = l[1] + date = l[2].split(' ') + time = float(date.pop(0)) + try: + # various tools did silly things with the time zone field. + timezone = int(date[0]) + except: + timezone = 0 + files = l[3:] + return (manifest, user, (time, timezone), files, desc) + + def read(self, node): + return self.extract(self.revision(node)) + + def add(self, manifest, list, desc, transaction, p1=None, p2=None, + user=None, date=None): + if date: + # validate explicit (probably user-specified) date and + # time zone offset. values must fit in signed 32 bits for + # current 32-bit linux runtimes. timezones go from UTC-12 + # to UTC+14 + try: + when, offset = map(int, date.split(' ')) + except ValueError: + raise ValueError(_('invalid date: %r') % date) + if abs(when) > 0x7fffffff: + raise ValueError(_('date exceeds 32 bits: %d') % when) + if offset < -50400 or offset > 43200: + raise ValueError(_('impossible time zone offset: %d') % offset) + else: + date = "%d %d" % util.makedate() + list.sort() + l = [hex(manifest), user, date] + list + ["", desc] + text = "\n".join(l) + return self.addrevision(text, transaction, self.count(), p1, p2)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/commands.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,3481 @@ +# commands.py - command processing for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from demandload import demandload +from node import * +from i18n import gettext as _ +demandload(globals(), "os re sys signal shutil imp urllib pdb") +demandload(globals(), "fancyopts ui hg util lock revlog templater bundlerepo") +demandload(globals(), "fnmatch mdiff random signal tempfile time") +demandload(globals(), "traceback errno socket version struct atexit sets bz2") +demandload(globals(), "archival changegroup") +demandload(globals(), "mercurial.hgweb.server:create_server") +demandload(globals(), "mercurial.hgweb:hgweb,hgwebdir") + +class UnknownCommand(Exception): + """Exception raised if command is not in the command table.""" +class AmbiguousCommand(Exception): + """Exception raised if command shortcut matches more than one command.""" + +def bail_if_changed(repo): + modified, added, removed, deleted, unknown = repo.changes() + if modified or added or removed or deleted: + raise util.Abort(_("outstanding uncommitted changes")) + +def filterfiles(filters, files): + l = [x for x in files if x in filters] + + for t in filters: + if t and t[-1] != "/": + t += "/" + l += [x for x in files if x.startswith(t)] + return l + +def relpath(repo, args): + cwd = repo.getcwd() + if cwd: + return [util.normpath(os.path.join(cwd, x)) for x in args] + return args + +def matchpats(repo, pats=[], opts={}, head=''): + cwd = repo.getcwd() + if not pats and cwd: + opts['include'] = [os.path.join(cwd, i) for i in opts['include']] + opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] + cwd = '' + return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'), + opts.get('exclude'), head) + +def makewalk(repo, pats, opts, node=None, head='', badmatch=None): + files, matchfn, anypats = matchpats(repo, pats, opts, head) + exact = dict(zip(files, files)) + def walk(): + for src, fn in repo.walk(node=node, files=files, match=matchfn, + badmatch=badmatch): + yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact + return files, matchfn, walk() + +def walk(repo, pats, opts, node=None, head='', badmatch=None): + files, matchfn, results = makewalk(repo, pats, opts, node, head, badmatch) + for r in results: + yield r + +def walkchangerevs(ui, repo, pats, opts): + '''Iterate over files and the revs they changed in. + + Callers most commonly need to iterate backwards over the history + it is interested in. Doing so has awful (quadratic-looking) + performance, so we use iterators in a "windowed" way. + + We walk a window of revisions in the desired order. Within the + window, we first walk forwards to gather data, then in the desired + order (usually backwards) to display it. + + This function returns an (iterator, getchange, matchfn) tuple. The + getchange function returns the changelog entry for a numeric + revision. The iterator yields 3-tuples. They will be of one of + the following forms: + + "window", incrementing, lastrev: stepping through a window, + positive if walking forwards through revs, last rev in the + sequence iterated over - use to reset state for the current window + + "add", rev, fns: out-of-order traversal of the given file names + fns, which changed during revision rev - use to gather data for + possible display + + "iter", rev, None: in-order traversal of the revs earlier iterated + over with "add" - use to display data''' + + def increasing_windows(start, end, windowsize=8, sizelimit=512): + if start < end: + while start < end: + yield start, min(windowsize, end-start) + start += windowsize + if windowsize < sizelimit: + windowsize *= 2 + else: + while start > end: + yield start, min(windowsize, start-end-1) + start -= windowsize + if windowsize < sizelimit: + windowsize *= 2 + + + files, matchfn, anypats = matchpats(repo, pats, opts) + + if repo.changelog.count() == 0: + return [], False, matchfn + + revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) + wanted = {} + slowpath = anypats + fncache = {} + + chcache = {} + def getchange(rev): + ch = chcache.get(rev) + if ch is None: + chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev))) + return ch + + if not slowpath and not files: + # No files, no patterns. Display all revs. + wanted = dict(zip(revs, revs)) + if not slowpath: + # Only files, no patterns. Check the history of each file. + def filerevgen(filelog): + for i, window in increasing_windows(filelog.count()-1, -1): + revs = [] + for j in xrange(i - window, i + 1): + revs.append(filelog.linkrev(filelog.node(j))) + revs.reverse() + for rev in revs: + yield rev + + minrev, maxrev = min(revs), max(revs) + for file_ in files: + filelog = repo.file(file_) + # A zero count may be a directory or deleted file, so + # try to find matching entries on the slow path. + if filelog.count() == 0: + slowpath = True + break + for rev in filerevgen(filelog): + if rev <= maxrev: + if rev < minrev: + break + fncache.setdefault(rev, []) + fncache[rev].append(file_) + wanted[rev] = 1 + if slowpath: + # The slow path checks files modified in every changeset. + def changerevgen(): + for i, window in increasing_windows(repo.changelog.count()-1, -1): + for j in xrange(i - window, i + 1): + yield j, getchange(j)[3] + + for rev, changefiles in changerevgen(): + matches = filter(matchfn, changefiles) + if matches: + fncache[rev] = matches + wanted[rev] = 1 + + def iterate(): + for i, window in increasing_windows(0, len(revs)): + yield 'window', revs[0] < revs[-1], revs[-1] + nrevs = [rev for rev in revs[i:i+window] + if rev in wanted] + srevs = list(nrevs) + srevs.sort() + for rev in srevs: + fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3]) + yield 'add', rev, fns + for rev in nrevs: + yield 'iter', rev, None + return iterate(), getchange, matchfn + +revrangesep = ':' + +def revfix(repo, val, defval): + '''turn user-level id of changeset into rev number. + user-level id can be tag, changeset, rev number, or negative rev + number relative to number of revs (-1 is tip, etc).''' + if not val: + return defval + try: + num = int(val) + if str(num) != val: + raise ValueError + if num < 0: + num += repo.changelog.count() + if num < 0: + num = 0 + elif num >= repo.changelog.count(): + raise ValueError + except ValueError: + try: + num = repo.changelog.rev(repo.lookup(val)) + except KeyError: + raise util.Abort(_('invalid revision identifier %s'), val) + return num + +def revpair(ui, repo, revs): + '''return pair of nodes, given list of revisions. second item can + be None, meaning use working dir.''' + if not revs: + return repo.dirstate.parents()[0], None + end = None + if len(revs) == 1: + start = revs[0] + if revrangesep in start: + start, end = start.split(revrangesep, 1) + start = revfix(repo, start, 0) + end = revfix(repo, end, repo.changelog.count() - 1) + else: + start = revfix(repo, start, None) + elif len(revs) == 2: + if revrangesep in revs[0] or revrangesep in revs[1]: + raise util.Abort(_('too many revisions specified')) + start = revfix(repo, revs[0], None) + end = revfix(repo, revs[1], None) + else: + raise util.Abort(_('too many revisions specified')) + if end is not None: end = repo.lookup(str(end)) + return repo.lookup(str(start)), end + +def revrange(ui, repo, revs): + """Yield revision as strings from a list of revision specifications.""" + seen = {} + for spec in revs: + if spec.find(revrangesep) >= 0: + start, end = spec.split(revrangesep, 1) + start = revfix(repo, start, 0) + end = revfix(repo, end, repo.changelog.count() - 1) + step = start > end and -1 or 1 + for rev in xrange(start, end+step, step): + if rev in seen: + continue + seen[rev] = 1 + yield str(rev) + else: + rev = revfix(repo, spec, None) + if rev in seen: + continue + seen[rev] = 1 + yield str(rev) + +def make_filename(repo, r, pat, node=None, + total=None, seqno=None, revwidth=None, pathname=None): + node_expander = { + 'H': lambda: hex(node), + 'R': lambda: str(r.rev(node)), + 'h': lambda: short(node), + } + expander = { + '%': lambda: '%', + 'b': lambda: os.path.basename(repo.root), + } + + try: + if node: + expander.update(node_expander) + if node and revwidth is not None: + expander['r'] = lambda: str(r.rev(node)).zfill(revwidth) + if total is not None: + expander['N'] = lambda: str(total) + if seqno is not None: + expander['n'] = lambda: str(seqno) + if total is not None and seqno is not None: + expander['n'] = lambda:str(seqno).zfill(len(str(total))) + if pathname is not None: + expander['s'] = lambda: os.path.basename(pathname) + expander['d'] = lambda: os.path.dirname(pathname) or '.' + expander['p'] = lambda: pathname + + newname = [] + patlen = len(pat) + i = 0 + while i < patlen: + c = pat[i] + if c == '%': + i += 1 + c = pat[i] + c = expander[c]() + newname.append(c) + i += 1 + return ''.join(newname) + except KeyError, inst: + raise util.Abort(_("invalid format spec '%%%s' in output file name"), + inst.args[0]) + +def make_file(repo, r, pat, node=None, + total=None, seqno=None, revwidth=None, mode='wb', pathname=None): + if not pat or pat == '-': + return 'w' in mode and sys.stdout or sys.stdin + if hasattr(pat, 'write') and 'w' in mode: + return pat + if hasattr(pat, 'read') and 'r' in mode: + return pat + return open(make_filename(repo, r, pat, node, total, seqno, revwidth, + pathname), + mode) + +def write_bundle(cg, filename=None, compress=True): + """Write a bundle file and return its filename. + + Existing files will not be overwritten. + If no filename is specified, a temporary file is created. + bz2 compression can be turned off. + The bundle file will be deleted in case of errors. + """ + class nocompress(object): + def compress(self, x): + return x + def flush(self): + return "" + + fh = None + cleanup = None + try: + if filename: + if os.path.exists(filename): + raise util.Abort(_("file '%s' already exists"), filename) + fh = open(filename, "wb") + else: + fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg") + fh = os.fdopen(fd, "wb") + cleanup = filename + + if compress: + fh.write("HG10") + z = bz2.BZ2Compressor(9) + else: + fh.write("HG10UN") + z = nocompress() + # parse the changegroup data, otherwise we will block + # in case of sshrepo because we don't know the end of the stream + + # an empty chunkiter is the end of the changegroup + empty = False + while not empty: + empty = True + for chunk in changegroup.chunkiter(cg): + empty = False + fh.write(z.compress(changegroup.genchunk(chunk))) + fh.write(z.compress(changegroup.closechunk())) + fh.write(z.flush()) + cleanup = None + return filename + finally: + if fh is not None: + fh.close() + if cleanup is not None: + os.unlink(cleanup) + +def dodiff(fp, ui, repo, node1, node2, files=None, match=util.always, + changes=None, text=False, opts={}): + if not node1: + node1 = repo.dirstate.parents()[0] + # reading the data for node1 early allows it to play nicely + # with repo.changes and the revlog cache. + change = repo.changelog.read(node1) + mmap = repo.manifest.read(change[0]) + date1 = util.datestr(change[2]) + + if not changes: + changes = repo.changes(node1, node2, files, match=match) + modified, added, removed, deleted, unknown = changes + if files: + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) + + if not modified and not added and not removed: + return + + if node2: + change = repo.changelog.read(node2) + mmap2 = repo.manifest.read(change[0]) + date2 = util.datestr(change[2]) + def read(f): + return repo.file(f).read(mmap2[f]) + else: + date2 = util.datestr() + def read(f): + return repo.wread(f) + + if ui.quiet: + r = None + else: + hexfunc = ui.verbose and hex or short + r = [hexfunc(node) for node in [node1, node2] if node] + + diffopts = ui.diffopts() + showfunc = opts.get('show_function') or diffopts['showfunc'] + ignorews = opts.get('ignore_all_space') or diffopts['ignorews'] + for f in modified: + to = None + if f in mmap: + to = repo.file(f).read(mmap[f]) + tn = read(f) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) + for f in added: + to = None + tn = read(f) + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) + for f in removed: + to = repo.file(f).read(mmap[f]) + tn = None + fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text, + showfunc=showfunc, ignorews=ignorews)) + +def trimuser(ui, name, rev, revcache): + """trim the name of the user who committed a change""" + user = revcache.get(rev) + if user is None: + user = revcache[rev] = ui.shortuser(name) + return user + +class changeset_printer(object): + '''show changeset information when templating not requested.''' + + def __init__(self, ui, repo): + self.ui = ui + self.repo = repo + + def show(self, rev=0, changenode=None, brinfo=None): + '''show a single changeset or file revision''' + log = self.repo.changelog + if changenode is None: + changenode = log.node(rev) + elif not rev: + rev = log.rev(changenode) + + if self.ui.quiet: + self.ui.write("%d:%s\n" % (rev, short(changenode))) + return + + changes = log.read(changenode) + date = util.datestr(changes[2]) + + parents = [(log.rev(p), self.ui.verbose and hex(p) or short(p)) + for p in log.parents(changenode) + if self.ui.debugflag or p != nullid] + if (not self.ui.debugflag and len(parents) == 1 and + parents[0][0] == rev-1): + parents = [] + + if self.ui.verbose: + self.ui.write(_("changeset: %d:%s\n") % (rev, hex(changenode))) + else: + self.ui.write(_("changeset: %d:%s\n") % (rev, short(changenode))) + + for tag in self.repo.nodetags(changenode): + self.ui.status(_("tag: %s\n") % tag) + for parent in parents: + self.ui.write(_("parent: %d:%s\n") % parent) + + if brinfo and changenode in brinfo: + br = brinfo[changenode] + self.ui.write(_("branch: %s\n") % " ".join(br)) + + self.ui.debug(_("manifest: %d:%s\n") % + (self.repo.manifest.rev(changes[0]), hex(changes[0]))) + self.ui.status(_("user: %s\n") % changes[1]) + self.ui.status(_("date: %s\n") % date) + + if self.ui.debugflag: + files = self.repo.changes(log.parents(changenode)[0], changenode) + for key, value in zip([_("files:"), _("files+:"), _("files-:")], + files): + if value: + self.ui.note("%-12s %s\n" % (key, " ".join(value))) + else: + self.ui.note(_("files: %s\n") % " ".join(changes[3])) + + description = changes[4].strip() + if description: + if self.ui.verbose: + self.ui.status(_("description:\n")) + self.ui.status(description) + self.ui.status("\n\n") + else: + self.ui.status(_("summary: %s\n") % + description.splitlines()[0]) + self.ui.status("\n") + +def show_changeset(ui, repo, opts): + '''show one changeset. uses template or regular display. caller + can pass in 'style' and 'template' options in opts.''' + + tmpl = opts.get('template') + if tmpl: + tmpl = templater.parsestring(tmpl, quoted=False) + else: + tmpl = ui.config('ui', 'logtemplate') + if tmpl: tmpl = templater.parsestring(tmpl) + mapfile = opts.get('style') or ui.config('ui', 'style') + if tmpl or mapfile: + if mapfile: + if not os.path.isfile(mapfile): + mapname = templater.templatepath('map-cmdline.' + mapfile) + if not mapname: mapname = templater.templatepath(mapfile) + if mapname: mapfile = mapname + try: + t = templater.changeset_templater(ui, repo, mapfile) + except SyntaxError, inst: + raise util.Abort(inst.args[0]) + if tmpl: t.use_template(tmpl) + return t + return changeset_printer(ui, repo) + +def show_version(ui): + """output version and copyright information""" + ui.write(_("Mercurial Distributed SCM (version %s)\n") + % version.get_version()) + ui.status(_( + "\nCopyright (C) 2005 Matt Mackall <mpm@selenic.com>\n" + "This is free software; see the source for copying conditions. " + "There is NO\nwarranty; " + "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" + )) + +def help_(ui, cmd=None, with_version=False): + """show help for a given command or all commands""" + option_lists = [] + if cmd and cmd != 'shortlist': + if with_version: + show_version(ui) + ui.write('\n') + aliases, i = find(cmd) + # synopsis + ui.write("%s\n\n" % i[2]) + + # description + doc = i[0].__doc__ + if not doc: + doc = _("(No help text available)") + if ui.quiet: + doc = doc.splitlines(0)[0] + ui.write("%s\n" % doc.rstrip()) + + if not ui.quiet: + # aliases + if len(aliases) > 1: + ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) + + # options + if i[1]: + option_lists.append(("options", i[1])) + + else: + # program name + if ui.verbose or with_version: + show_version(ui) + else: + ui.status(_("Mercurial Distributed SCM\n")) + ui.status('\n') + + # list of commands + if cmd == "shortlist": + ui.status(_('basic commands (use "hg help" ' + 'for the full list or option "-v" for details):\n\n')) + elif ui.verbose: + ui.status(_('list of commands:\n\n')) + else: + ui.status(_('list of commands (use "hg help -v" ' + 'to show aliases and global options):\n\n')) + + h = {} + cmds = {} + for c, e in table.items(): + f = c.split("|")[0] + if cmd == "shortlist" and not f.startswith("^"): + continue + f = f.lstrip("^") + if not ui.debugflag and f.startswith("debug"): + continue + doc = e[0].__doc__ + if not doc: + doc = _("(No help text available)") + h[f] = doc.splitlines(0)[0].rstrip() + cmds[f] = c.lstrip("^") + + fns = h.keys() + fns.sort() + m = max(map(len, fns)) + for f in fns: + if ui.verbose: + commands = cmds[f].replace("|",", ") + ui.write(" %s:\n %s\n"%(commands, h[f])) + else: + ui.write(' %-*s %s\n' % (m, f, h[f])) + + # global options + if ui.verbose: + option_lists.append(("global options", globalopts)) + + # list all option lists + opt_output = [] + for title, options in option_lists: + opt_output.append(("\n%s:\n" % title, None)) + for shortopt, longopt, default, desc in options: + opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt, + longopt and " --%s" % longopt), + "%s%s" % (desc, + default + and _(" (default: %s)") % default + or ""))) + + if opt_output: + opts_len = max([len(line[0]) for line in opt_output if line[1]]) + for first, second in opt_output: + if second: + ui.write(" %-*s %s\n" % (opts_len, first, second)) + else: + ui.write("%s\n" % first) + +# Commands start here, listed alphabetically + +def add(ui, repo, *pats, **opts): + """add the specified files on the next commit + + Schedule files to be version controlled and added to the repository. + + The files will be added to the repository at the next commit. + + If no names are given, add all files in the repository. + """ + + names = [] + for src, abs, rel, exact in walk(repo, pats, opts): + if exact: + if ui.verbose: + ui.status(_('adding %s\n') % rel) + names.append(abs) + elif repo.dirstate.state(abs) == '?': + ui.status(_('adding %s\n') % rel) + names.append(abs) + repo.add(names) + +def addremove(ui, repo, *pats, **opts): + """add all new files, delete all missing files (DEPRECATED) + + (DEPRECATED) + Add all new files and remove all missing files from the repository. + + New files are ignored if they match any of the patterns in .hgignore. As + with add, these changes take effect at the next commit. + + This command is now deprecated and will be removed in a future + release. Please use add and remove --after instead. + """ + ui.warn(_('(the addremove command is deprecated; use add and remove ' + '--after instead)\n')) + return addremove_lock(ui, repo, pats, opts) + +def addremove_lock(ui, repo, pats, opts, wlock=None): + add, remove = [], [] + for src, abs, rel, exact in walk(repo, pats, opts): + if src == 'f' and repo.dirstate.state(abs) == '?': + add.append(abs) + if ui.verbose or not exact: + ui.status(_('adding %s\n') % ((pats and rel) or abs)) + if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): + remove.append(abs) + if ui.verbose or not exact: + ui.status(_('removing %s\n') % ((pats and rel) or abs)) + repo.add(add, wlock=wlock) + repo.remove(remove, wlock=wlock) + +def annotate(ui, repo, *pats, **opts): + """show changeset information per file line + + List changes in files, showing the revision id responsible for each line + + This command is useful to discover who did a change or when a change took + place. + + Without the -a option, annotate will avoid processing files it + detects as binary. With -a, annotate will generate an annotation + anyway, probably with undesirable results. + """ + def getnode(rev): + return short(repo.changelog.node(rev)) + + ucache = {} + def getname(rev): + cl = repo.changelog.read(repo.changelog.node(rev)) + return trimuser(ui, cl[1], rev, ucache) + + dcache = {} + def getdate(rev): + datestr = dcache.get(rev) + if datestr is None: + cl = repo.changelog.read(repo.changelog.node(rev)) + datestr = dcache[rev] = util.datestr(cl[2]) + return datestr + + if not pats: + raise util.Abort(_('at least one file name or pattern required')) + + opmap = [['user', getname], ['number', str], ['changeset', getnode], + ['date', getdate]] + if not opts['user'] and not opts['changeset'] and not opts['date']: + opts['number'] = 1 + + if opts['rev']: + node = repo.changelog.lookup(opts['rev']) + else: + node = repo.dirstate.parents()[0] + change = repo.changelog.read(node) + mmap = repo.manifest.read(change[0]) + + for src, abs, rel, exact in walk(repo, pats, opts, node=node): + f = repo.file(abs) + if not opts['text'] and util.binary(f.read(mmap[abs])): + ui.write(_("%s: binary file\n") % ((pats and rel) or abs)) + continue + + lines = f.annotate(mmap[abs]) + pieces = [] + + for o, f in opmap: + if opts[o]: + l = [f(n) for n, dummy in lines] + if l: + m = max(map(len, l)) + pieces.append(["%*s" % (m, x) for x in l]) + + if pieces: + for p, l in zip(zip(*pieces), lines): + ui.write("%s: %s" % (" ".join(p), l[1])) + +def archive(ui, repo, dest, **opts): + '''create unversioned archive of a repository revision + + By default, the revision used is the parent of the working + directory; use "-r" to specify a different revision. + + To specify the type of archive to create, use "-t". Valid + types are: + + "files" (default): a directory full of files + "tar": tar archive, uncompressed + "tbz2": tar archive, compressed using bzip2 + "tgz": tar archive, compressed using gzip + "uzip": zip archive, uncompressed + "zip": zip archive, compressed using deflate + + The exact name of the destination archive or directory is given + using a format string; see "hg help export" for details. + + Each member added to an archive file has a directory prefix + prepended. Use "-p" to specify a format string for the prefix. + The default is the basename of the archive, with suffixes removed. + ''' + + if opts['rev']: + node = repo.lookup(opts['rev']) + else: + node, p2 = repo.dirstate.parents() + if p2 != nullid: + raise util.Abort(_('uncommitted merge - please provide a ' + 'specific revision')) + + dest = make_filename(repo, repo.changelog, dest, node) + prefix = make_filename(repo, repo.changelog, opts['prefix'], node) + if os.path.realpath(dest) == repo.root: + raise util.Abort(_('repository root cannot be destination')) + dummy, matchfn, dummy = matchpats(repo, [], opts) + archival.archive(repo, dest, node, opts.get('type') or 'files', + not opts['no_decode'], matchfn, prefix) + +def backout(ui, repo, rev, **opts): + '''reverse effect of earlier changeset + + Commit the backed out changes as a new changeset. The new + changeset is a child of the backed out changeset. + + If you back out a changeset other than the tip, a new head is + created. This head is the parent of the working directory. If + you back out an old changeset, your working directory will appear + old after the backout. You should merge the backout changeset + with another head. + + The --merge option remembers the parent of the working directory + before starting the backout, then merges the new head with that + changeset afterwards. This saves you from doing the merge by + hand. The result of this merge is not committed, as for a normal + merge.''' + + bail_if_changed(repo) + op1, op2 = repo.dirstate.parents() + if op2 != nullid: + raise util.Abort(_('outstanding uncommitted merge')) + node = repo.lookup(rev) + parent, p2 = repo.changelog.parents(node) + if parent == nullid: + raise util.Abort(_('cannot back out a change with no parents')) + if p2 != nullid: + raise util.Abort(_('cannot back out a merge')) + repo.update(node, force=True, show_stats=False) + revert_opts = opts.copy() + revert_opts['rev'] = hex(parent) + revert(ui, repo, **revert_opts) + commit_opts = opts.copy() + commit_opts['addremove'] = False + if not commit_opts['message'] and not commit_opts['logfile']: + commit_opts['message'] = _("Backed out changeset %s") % (hex(node)) + commit_opts['force_editor'] = True + commit(ui, repo, **commit_opts) + def nice(node): + return '%d:%s' % (repo.changelog.rev(node), short(node)) + ui.status(_('changeset %s backs out changeset %s\n') % + (nice(repo.changelog.tip()), nice(node))) + if opts['merge'] and op1 != node: + ui.status(_('merging with changeset %s\n') % nice(op1)) + doupdate(ui, repo, hex(op1), **opts) + +def bundle(ui, repo, fname, dest="default-push", **opts): + """create a changegroup file + + Generate a compressed changegroup file collecting all changesets + not found in the other repository. + + This file can then be transferred using conventional means and + applied to another repository with the unbundle command. This is + useful when native push and pull are not available or when + exporting an entire repository is undesirable. The standard file + extension is ".hg". + + Unlike import/export, this exactly preserves all changeset + contents including permissions, rename data, and revision history. + """ + dest = ui.expandpath(dest) + other = hg.repository(ui, dest) + o = repo.findoutgoing(other, force=opts['force']) + cg = repo.changegroup(o, 'bundle') + write_bundle(cg, fname) + +def cat(ui, repo, file1, *pats, **opts): + """output the latest or given revisions of files + + Print the specified files as they were at the given revision. + If no revision is given then the tip is used. + + Output may be to a file, in which case the name of the file is + given using a format string. The formatting rules are the same as + for the export command, with the following additions: + + %s basename of file being printed + %d dirname of file being printed, or '.' if in repo root + %p root-relative path name of file being printed + """ + mf = {} + rev = opts['rev'] + if rev: + node = repo.lookup(rev) + else: + node = repo.changelog.tip() + change = repo.changelog.read(node) + mf = repo.manifest.read(change[0]) + for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node): + r = repo.file(abs) + n = mf[abs] + fp = make_file(repo, r, opts['output'], node=n, pathname=abs) + fp.write(r.read(n)) + +def clone(ui, source, dest=None, **opts): + """make a copy of an existing repository + + Create a copy of an existing repository in a new directory. + + If no destination directory name is specified, it defaults to the + basename of the source. + + The location of the source is added to the new repository's + .hg/hgrc file, as the default to be used for future pulls. + + For efficiency, hardlinks are used for cloning whenever the source + and destination are on the same filesystem. Some filesystems, + such as AFS, implement hardlinking incorrectly, but do not report + errors. In these cases, use the --pull option to avoid + hardlinking. + + See pull for valid source format details. + """ + if dest is None: + dest = os.path.basename(os.path.normpath(source)) + + if os.path.exists(dest): + raise util.Abort(_("destination '%s' already exists"), dest) + + dest = os.path.realpath(dest) + + class Dircleanup(object): + def __init__(self, dir_): + self.rmtree = shutil.rmtree + self.dir_ = dir_ + os.mkdir(dir_) + def close(self): + self.dir_ = None + def __del__(self): + if self.dir_: + self.rmtree(self.dir_, True) + + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) + + source = ui.expandpath(source) + + d = Dircleanup(dest) + abspath = source + other = hg.repository(ui, source) + + copy = False + if other.dev() != -1: + abspath = os.path.abspath(source) + if not opts['pull'] and not opts['rev']: + copy = True + + if copy: + try: + # we use a lock here because if we race with commit, we + # can end up with extra data in the cloned revlogs that's + # not pointed to by changesets, thus causing verify to + # fail + l1 = other.lock() + except lock.LockException: + copy = False + + if copy: + # we lock here to avoid premature writing to the target + os.mkdir(os.path.join(dest, ".hg")) + l2 = lock.lock(os.path.join(dest, ".hg", "lock")) + + files = "data 00manifest.d 00manifest.i 00changelog.d 00changelog.i" + for f in files.split(): + src = os.path.join(source, ".hg", f) + dst = os.path.join(dest, ".hg", f) + try: + util.copyfiles(src, dst) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + + repo = hg.repository(ui, dest) + + else: + revs = None + if opts['rev']: + if not other.local(): + error = _("clone -r not supported yet for remote repositories.") + raise util.Abort(error) + else: + revs = [other.lookup(rev) for rev in opts['rev']] + repo = hg.repository(ui, dest, create=1) + repo.pull(other, heads = revs) + + f = repo.opener("hgrc", "w", text=True) + f.write("[paths]\n") + f.write("default = %s\n" % abspath) + f.close() + + if not opts['noupdate']: + doupdate(repo.ui, repo) + + d.close() + +def commit(ui, repo, *pats, **opts): + """commit the specified files or all outstanding changes + + Commit changes to the given files into the repository. + + If a list of files is omitted, all changes reported by "hg status" + will be committed. + + If no commit message is specified, the editor configured in your hgrc + or in the EDITOR environment variable is started to enter a message. + """ + message = opts['message'] + logfile = opts['logfile'] + + if message and logfile: + raise util.Abort(_('options --message and --logfile are mutually ' + 'exclusive')) + if not message and logfile: + try: + if logfile == '-': + message = sys.stdin.read() + else: + message = open(logfile).read() + except IOError, inst: + raise util.Abort(_("can't read commit message '%s': %s") % + (logfile, inst.strerror)) + + if opts['addremove']: + addremove_lock(ui, repo, pats, opts) + fns, match, anypats = matchpats(repo, pats, opts) + if pats: + modified, added, removed, deleted, unknown = ( + repo.changes(files=fns, match=match)) + files = modified + added + removed + else: + files = [] + try: + repo.commit(files, message, opts['user'], opts['date'], match, + force_editor=opts.get('force_editor')) + except ValueError, inst: + raise util.Abort(str(inst)) + +def docopy(ui, repo, pats, opts, wlock): + # called with the repo lock held + cwd = repo.getcwd() + errors = 0 + copied = [] + targets = {} + + def okaytocopy(abs, rel, exact): + reasons = {'?': _('is not managed'), + 'a': _('has been marked for add'), + 'r': _('has been marked for remove')} + state = repo.dirstate.state(abs) + reason = reasons.get(state) + if reason: + if state == 'a': + origsrc = repo.dirstate.copied(abs) + if origsrc is not None: + return origsrc + if exact: + ui.warn(_('%s: not copying - file %s\n') % (rel, reason)) + else: + return abs + + def copy(origsrc, abssrc, relsrc, target, exact): + abstarget = util.canonpath(repo.root, cwd, target) + reltarget = util.pathto(cwd, abstarget) + prevsrc = targets.get(abstarget) + if prevsrc is not None: + ui.warn(_('%s: not overwriting - %s collides with %s\n') % + (reltarget, abssrc, prevsrc)) + return + if (not opts['after'] and os.path.exists(reltarget) or + opts['after'] and repo.dirstate.state(abstarget) not in '?r'): + if not opts['force']: + ui.warn(_('%s: not overwriting - file exists\n') % + reltarget) + return + if not opts['after']: + os.unlink(reltarget) + if opts['after']: + if not os.path.exists(reltarget): + return + else: + targetdir = os.path.dirname(reltarget) or '.' + if not os.path.isdir(targetdir): + os.makedirs(targetdir) + try: + restore = repo.dirstate.state(abstarget) == 'r' + if restore: + repo.undelete([abstarget], wlock) + try: + shutil.copyfile(relsrc, reltarget) + shutil.copymode(relsrc, reltarget) + restore = False + finally: + if restore: + repo.remove([abstarget], wlock) + except shutil.Error, inst: + raise util.Abort(str(inst)) + except IOError, inst: + if inst.errno == errno.ENOENT: + ui.warn(_('%s: deleted in working copy\n') % relsrc) + else: + ui.warn(_('%s: cannot copy - %s\n') % + (relsrc, inst.strerror)) + errors += 1 + return + if ui.verbose or not exact: + ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) + targets[abstarget] = abssrc + if abstarget != origsrc: + repo.copy(origsrc, abstarget, wlock) + copied.append((abssrc, relsrc, exact)) + + def targetpathfn(pat, dest, srcs): + if os.path.isdir(pat): + abspfx = util.canonpath(repo.root, cwd, pat) + if destdirexists: + striplen = len(os.path.split(abspfx)[0]) + else: + striplen = len(abspfx) + if striplen: + striplen += len(os.sep) + res = lambda p: os.path.join(dest, p[striplen:]) + elif destdirexists: + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + res = lambda p: dest + return res + + def targetpathafterfn(pat, dest, srcs): + if util.patkind(pat, None)[0]: + # a mercurial pattern + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + abspfx = util.canonpath(repo.root, cwd, pat) + if len(abspfx) < len(srcs[0][0]): + # A directory. Either the target path contains the last + # component of the source path or it does not. + def evalpath(striplen): + score = 0 + for s in srcs: + t = os.path.join(dest, s[0][striplen:]) + if os.path.exists(t): + score += 1 + return score + + striplen = len(abspfx) + if striplen: + striplen += len(os.sep) + if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])): + score = evalpath(striplen) + striplen1 = len(os.path.split(abspfx)[0]) + if striplen1: + striplen1 += len(os.sep) + if evalpath(striplen1) > score: + striplen = striplen1 + res = lambda p: os.path.join(dest, p[striplen:]) + else: + # a file + if destdirexists: + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + res = lambda p: dest + return res + + + pats = list(pats) + if not pats: + raise util.Abort(_('no source or destination specified')) + if len(pats) == 1: + raise util.Abort(_('no destination specified')) + dest = pats.pop() + destdirexists = os.path.isdir(dest) + if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists: + raise util.Abort(_('with multiple sources, destination must be an ' + 'existing directory')) + if opts['after']: + tfn = targetpathafterfn + else: + tfn = targetpathfn + copylist = [] + for pat in pats: + srcs = [] + for tag, abssrc, relsrc, exact in walk(repo, [pat], opts): + origsrc = okaytocopy(abssrc, relsrc, exact) + if origsrc: + srcs.append((origsrc, abssrc, relsrc, exact)) + if not srcs: + continue + copylist.append((tfn(pat, dest, srcs), srcs)) + if not copylist: + raise util.Abort(_('no files to copy')) + + for targetpath, srcs in copylist: + for origsrc, abssrc, relsrc, exact in srcs: + copy(origsrc, abssrc, relsrc, targetpath(abssrc), exact) + + if errors: + ui.warn(_('(consider using --after)\n')) + return errors, copied + +def copy(ui, repo, *pats, **opts): + """mark files as copied for the next commit + + Mark dest as having copies of source files. If dest is a + directory, copies are put in that directory. If dest is a file, + there can only be one source. + + By default, this command copies the contents of files as they + stand in the working directory. If invoked with --after, the + operation is recorded, but no copying is performed. + + This command takes effect in the next commit. + + NOTE: This command should be treated as experimental. While it + should properly record copied files, this information is not yet + fully used by merge, nor fully reported by log. + """ + wlock = repo.wlock(0) + errs, copied = docopy(ui, repo, pats, opts, wlock) + return errs + +def debugancestor(ui, index, rev1, rev2): + """find the ancestor revision of two revisions in a given index""" + r = revlog.revlog(util.opener(os.getcwd(), audit=False), index, "", 0) + a = r.ancestor(r.lookup(rev1), r.lookup(rev2)) + ui.write("%d:%s\n" % (r.rev(a), hex(a))) + +def debugcomplete(ui, cmd='', **opts): + """returns the completion list associated with the given command""" + + if opts['options']: + options = [] + otables = [globalopts] + if cmd: + aliases, entry = find(cmd) + otables.append(entry[1]) + for t in otables: + for o in t: + if o[0]: + options.append('-%s' % o[0]) + options.append('--%s' % o[1]) + ui.write("%s\n" % "\n".join(options)) + return + + clist = findpossible(cmd).keys() + clist.sort() + ui.write("%s\n" % "\n".join(clist)) + +def debugrebuildstate(ui, repo, rev=None): + """rebuild the dirstate as it would look like for the given revision""" + if not rev: + rev = repo.changelog.tip() + else: + rev = repo.lookup(rev) + change = repo.changelog.read(rev) + n = change[0] + files = repo.manifest.readflags(n) + wlock = repo.wlock() + repo.dirstate.rebuild(rev, files.iteritems()) + +def debugcheckstate(ui, repo): + """validate the correctness of the current dirstate""" + parent1, parent2 = repo.dirstate.parents() + repo.dirstate.read() + dc = repo.dirstate.map + keys = dc.keys() + keys.sort() + m1n = repo.changelog.read(parent1)[0] + m2n = repo.changelog.read(parent2)[0] + m1 = repo.manifest.read(m1n) + m2 = repo.manifest.read(m2n) + errors = 0 + for f in dc: + state = repo.dirstate.state(f) + if state in "nr" and f not in m1: + ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state)) + errors += 1 + if state in "a" and f in m1: + ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state)) + errors += 1 + if state in "m" and f not in m1 and f not in m2: + ui.warn(_("%s in state %s, but not in either manifest\n") % + (f, state)) + errors += 1 + for f in m1: + state = repo.dirstate.state(f) + if state not in "nrm": + ui.warn(_("%s in manifest1, but listed as state %s") % (f, state)) + errors += 1 + if errors: + error = _(".hg/dirstate inconsistent with current parent's manifest") + raise util.Abort(error) + +def debugconfig(ui, repo): + """show combined config settings from all hgrc files""" + for section, name, value in ui.walkconfig(): + ui.write('%s.%s=%s\n' % (section, name, value)) + +def debugsetparents(ui, repo, rev1, rev2=None): + """manually set the parents of the current working directory + + This is useful for writing repository conversion tools, but should + be used with care. + """ + + if not rev2: + rev2 = hex(nullid) + + repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2)) + +def debugstate(ui, repo): + """show the contents of the current dirstate""" + repo.dirstate.read() + dc = repo.dirstate.map + keys = dc.keys() + keys.sort() + for file_ in keys: + ui.write("%c %3o %10d %s %s\n" + % (dc[file_][0], dc[file_][1] & 0777, dc[file_][2], + time.strftime("%x %X", + time.localtime(dc[file_][3])), file_)) + for f in repo.dirstate.copies: + ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copies[f], f)) + +def debugdata(ui, file_, rev): + """dump the contents of an data file revision""" + r = revlog.revlog(util.opener(os.getcwd(), audit=False), + file_[:-2] + ".i", file_, 0) + try: + ui.write(r.revision(r.lookup(rev))) + except KeyError: + raise util.Abort(_('invalid revision identifier %s'), rev) + +def debugindex(ui, file_): + """dump the contents of an index file""" + r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0) + ui.write(" rev offset length base linkrev" + + " nodeid p1 p2\n") + for i in range(r.count()): + node = r.node(i) + pp = r.parents(node) + ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % ( + i, r.start(i), r.length(i), r.base(i), r.linkrev(node), + short(node), short(pp[0]), short(pp[1]))) + +def debugindexdot(ui, file_): + """dump an index DAG as a .dot file""" + r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_, "", 0) + ui.write("digraph G {\n") + for i in range(r.count()): + node = r.node(i) + pp = r.parents(node) + ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i)) + if pp[1] != nullid: + ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i)) + ui.write("}\n") + +def debugrename(ui, repo, file, rev=None): + """dump rename information""" + r = repo.file(relpath(repo, [file])[0]) + if rev: + try: + # assume all revision numbers are for changesets + n = repo.lookup(rev) + change = repo.changelog.read(n) + m = repo.manifest.read(change[0]) + n = m[relpath(repo, [file])[0]] + except (hg.RepoError, KeyError): + n = r.lookup(rev) + else: + n = r.tip() + m = r.renamed(n) + if m: + ui.write(_("renamed from %s:%s\n") % (m[0], hex(m[1]))) + else: + ui.write(_("not renamed\n")) + +def debugwalk(ui, repo, *pats, **opts): + """show how files match on given patterns""" + items = list(walk(repo, pats, opts)) + if not items: + return + fmt = '%%s %%-%ds %%-%ds %%s' % ( + max([len(abs) for (src, abs, rel, exact) in items]), + max([len(rel) for (src, abs, rel, exact) in items])) + for src, abs, rel, exact in items: + line = fmt % (src, abs, rel, exact and 'exact' or '') + ui.write("%s\n" % line.rstrip()) + +def diff(ui, repo, *pats, **opts): + """diff repository (or selected files) + + Show differences between revisions for the specified files. + + Differences between files are shown using the unified diff format. + + When two revision arguments are given, then changes are shown + between those revisions. If only one revision is specified then + that revision is compared to the working directory, and, when no + revisions are specified, the working directory files are compared + to its parent. + + Without the -a option, diff will avoid generating diffs of files + it detects as binary. With -a, diff will generate a diff anyway, + probably with undesirable results. + """ + node1, node2 = revpair(ui, repo, opts['rev']) + + fns, matchfn, anypats = matchpats(repo, pats, opts) + + dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn, + text=opts['text'], opts=opts) + +def doexport(ui, repo, changeset, seqno, total, revwidth, opts): + node = repo.lookup(changeset) + parents = [p for p in repo.changelog.parents(node) if p != nullid] + if opts['switch_parent']: + parents.reverse() + prev = (parents and parents[0]) or nullid + change = repo.changelog.read(node) + + fp = make_file(repo, repo.changelog, opts['output'], + node=node, total=total, seqno=seqno, + revwidth=revwidth) + if fp != sys.stdout: + ui.note("%s\n" % fp.name) + + fp.write("# HG changeset patch\n") + fp.write("# User %s\n" % change[1]) + fp.write("# Date %d %d\n" % change[2]) + fp.write("# Node ID %s\n" % hex(node)) + fp.write("# Parent %s\n" % hex(prev)) + if len(parents) > 1: + fp.write("# Parent %s\n" % hex(parents[1])) + fp.write(change[4].rstrip()) + fp.write("\n\n") + + dodiff(fp, ui, repo, prev, node, text=opts['text']) + if fp != sys.stdout: + fp.close() + +def export(ui, repo, *changesets, **opts): + """dump the header and diffs for one or more changesets + + Print the changeset header and diffs for one or more revisions. + + The information shown in the changeset header is: author, + changeset hash, parent and commit comment. + + Output may be to a file, in which case the name of the file is + given using a format string. The formatting rules are as follows: + + %% literal "%" character + %H changeset hash (40 bytes of hexadecimal) + %N number of patches being generated + %R changeset revision number + %b basename of the exporting repository + %h short-form changeset hash (12 bytes of hexadecimal) + %n zero-padded sequence number, starting at 1 + %r zero-padded changeset revision number + + Without the -a option, export will avoid generating diffs of files + it detects as binary. With -a, export will generate a diff anyway, + probably with undesirable results. + + With the --switch-parent option, the diff will be against the second + parent. It can be useful to review a merge. + """ + if not changesets: + raise util.Abort(_("export requires at least one changeset")) + seqno = 0 + revs = list(revrange(ui, repo, changesets)) + total = len(revs) + revwidth = max(map(len, revs)) + msg = len(revs) > 1 and _("Exporting patches:\n") or _("Exporting patch:\n") + ui.note(msg) + for cset in revs: + seqno += 1 + doexport(ui, repo, cset, seqno, total, revwidth, opts) + +def forget(ui, repo, *pats, **opts): + """don't add the specified files on the next commit (DEPRECATED) + + (DEPRECATED) + Undo an 'hg add' scheduled for the next commit. + + This command is now deprecated and will be removed in a future + release. Please use revert instead. + """ + ui.warn(_("(the forget command is deprecated; use revert instead)\n")) + forget = [] + for src, abs, rel, exact in walk(repo, pats, opts): + if repo.dirstate.state(abs) == 'a': + forget.append(abs) + if ui.verbose or not exact: + ui.status(_('forgetting %s\n') % ((pats and rel) or abs)) + repo.forget(forget) + +def grep(ui, repo, pattern, *pats, **opts): + """search for a pattern in specified files and revisions + + Search revisions of files for a regular expression. + + This command behaves differently than Unix grep. It only accepts + Python/Perl regexps. It searches repository history, not the + working directory. It always prints the revision number in which + a match appears. + + By default, grep only prints output for the first revision of a + file in which it finds a match. To get it to print every revision + that contains a change in match status ("-" for a match that + becomes a non-match, or "+" for a non-match that becomes a match), + use the --all flag. + """ + reflags = 0 + if opts['ignore_case']: + reflags |= re.I + regexp = re.compile(pattern, reflags) + sep, eol = ':', '\n' + if opts['print0']: + sep = eol = '\0' + + fcache = {} + def getfile(fn): + if fn not in fcache: + fcache[fn] = repo.file(fn) + return fcache[fn] + + def matchlines(body): + begin = 0 + linenum = 0 + while True: + match = regexp.search(body, begin) + if not match: + break + mstart, mend = match.span() + linenum += body.count('\n', begin, mstart) + 1 + lstart = body.rfind('\n', begin, mstart) + 1 or begin + lend = body.find('\n', mend) + yield linenum, mstart - lstart, mend - lstart, body[lstart:lend] + begin = lend + 1 + + class linestate(object): + def __init__(self, line, linenum, colstart, colend): + self.line = line + self.linenum = linenum + self.colstart = colstart + self.colend = colend + def __eq__(self, other): + return self.line == other.line + def __hash__(self): + return hash(self.line) + + matches = {} + def grepbody(fn, rev, body): + matches[rev].setdefault(fn, {}) + m = matches[rev][fn] + for lnum, cstart, cend, line in matchlines(body): + s = linestate(line, lnum, cstart, cend) + m[s] = s + + # FIXME: prev isn't used, why ? + prev = {} + ucache = {} + def display(fn, rev, states, prevstates): + diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates))) + diff.sort(lambda x, y: cmp(x.linenum, y.linenum)) + counts = {'-': 0, '+': 0} + filerevmatches = {} + for l in diff: + if incrementing or not opts['all']: + change = ((l in prevstates) and '-') or '+' + r = rev + else: + change = ((l in states) and '-') or '+' + r = prev[fn] + cols = [fn, str(rev)] + if opts['line_number']: + cols.append(str(l.linenum)) + if opts['all']: + cols.append(change) + if opts['user']: + cols.append(trimuser(ui, getchange(rev)[1], rev, + ucache)) + if opts['files_with_matches']: + c = (fn, rev) + if c in filerevmatches: + continue + filerevmatches[c] = 1 + else: + cols.append(l.line) + ui.write(sep.join(cols), eol) + counts[change] += 1 + return counts['+'], counts['-'] + + fstate = {} + skip = {} + changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts) + count = 0 + incrementing = False + for st, rev, fns in changeiter: + if st == 'window': + incrementing = rev + matches.clear() + elif st == 'add': + change = repo.changelog.read(repo.lookup(str(rev))) + mf = repo.manifest.read(change[0]) + matches[rev] = {} + for fn in fns: + if fn in skip: + continue + fstate.setdefault(fn, {}) + try: + grepbody(fn, rev, getfile(fn).read(mf[fn])) + except KeyError: + pass + elif st == 'iter': + states = matches[rev].items() + states.sort() + for fn, m in states: + if fn in skip: + continue + if incrementing or not opts['all'] or fstate[fn]: + pos, neg = display(fn, rev, m, fstate[fn]) + count += pos + neg + if pos and not opts['all']: + skip[fn] = True + fstate[fn] = m + prev[fn] = rev + + if not incrementing: + fstate = fstate.items() + fstate.sort() + for fn, state in fstate: + if fn in skip: + continue + display(fn, rev, {}, state) + return (count == 0 and 1) or 0 + +def heads(ui, repo, **opts): + """show current repository heads + + Show all repository head changesets. + + Repository "heads" are changesets that don't have children + changesets. They are where development generally takes place and + are the usual targets for update and merge operations. + """ + if opts['rev']: + heads = repo.heads(repo.lookup(opts['rev'])) + else: + heads = repo.heads() + br = None + if opts['branches']: + br = repo.branchlookup(heads) + displayer = show_changeset(ui, repo, opts) + for n in heads: + displayer.show(changenode=n, brinfo=br) + +def identify(ui, repo): + """print information about the working copy + + Print a short summary of the current state of the repo. + + This summary identifies the repository state using one or two parent + hash identifiers, followed by a "+" if there are uncommitted changes + in the working directory, followed by a list of tags for this revision. + """ + parents = [p for p in repo.dirstate.parents() if p != nullid] + if not parents: + ui.write(_("unknown\n")) + return + + hexfunc = ui.verbose and hex or short + modified, added, removed, deleted, unknown = repo.changes() + output = ["%s%s" % + ('+'.join([hexfunc(parent) for parent in parents]), + (modified or added or removed or deleted) and "+" or "")] + + if not ui.quiet: + # multiple tags for a single parent separated by '/' + parenttags = ['/'.join(tags) + for tags in map(repo.nodetags, parents) if tags] + # tags for multiple parents separated by ' + ' + if parenttags: + output.append(' + '.join(parenttags)) + + ui.write("%s\n" % ' '.join(output)) + +def import_(ui, repo, patch1, *patches, **opts): + """import an ordered set of patches + + Import a list of patches and commit them individually. + + If there are outstanding changes in the working directory, import + will abort unless given the -f flag. + + If a patch looks like a mail message (its first line starts with + "From " or looks like an RFC822 header), it will not be applied + unless the -f option is used. The importer neither parses nor + discards mail headers, so use -f only to override the "mailness" + safety check, not to import a real mail message. + """ + patches = (patch1,) + patches + + if not opts['force']: + bail_if_changed(repo) + + d = opts["base"] + strip = opts["strip"] + + mailre = re.compile(r'(?:From |[\w-]+:)') + + # attempt to detect the start of a patch + # (this heuristic is borrowed from quilt) + diffre = re.compile(r'(?:Index:[ \t]|diff[ \t]|RCS file: |' + + 'retrieving revision [0-9]+(\.[0-9]+)*$|' + + '(---|\*\*\*)[ \t])') + + for patch in patches: + ui.status(_("applying %s\n") % patch) + pf = os.path.join(d, patch) + + message = [] + user = None + date = None + hgpatch = False + for line in file(pf): + line = line.rstrip() + if (not message and not hgpatch and + mailre.match(line) and not opts['force']): + if len(line) > 35: + line = line[:32] + '...' + raise util.Abort(_('first line looks like a ' + 'mail header: ') + line) + if diffre.match(line): + break + elif hgpatch: + # parse values when importing the result of an hg export + if line.startswith("# User "): + user = line[7:] + ui.debug(_('User: %s\n') % user) + elif line.startswith("# Date "): + date = line[7:] + elif not line.startswith("# ") and line: + message.append(line) + hgpatch = False + elif line == '# HG changeset patch': + hgpatch = True + message = [] # We may have collected garbage + elif message or line: + message.append(line) + + # make sure message isn't empty + if not message: + message = _("imported patch %s\n") % patch + else: + message = '\n'.join(message).rstrip() + ui.debug(_('message:\n%s\n') % message) + + files = util.patch(strip, pf, ui) + + if len(files) > 0: + addremove_lock(ui, repo, files, {}) + repo.commit(files, message, user, date) + +def incoming(ui, repo, source="default", **opts): + """show new changesets found in source + + Show new changesets found in the specified path/URL or the default + pull location. These are the changesets that would be pulled if a pull + was requested. + + For remote repository, using --bundle avoids downloading the changesets + twice if the incoming is followed by a pull. + + See pull for valid source format details. + """ + source = ui.expandpath(source) + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) + + other = hg.repository(ui, source) + incoming = repo.findincoming(other, force=opts["force"]) + if not incoming: + ui.status(_("no changes found\n")) + return + + cleanup = None + try: + fname = opts["bundle"] + if fname or not other.local(): + # create a bundle (uncompressed if other repo is not local) + cg = other.changegroup(incoming, "incoming") + fname = cleanup = write_bundle(cg, fname, compress=other.local()) + # keep written bundle? + if opts["bundle"]: + cleanup = None + if not other.local(): + # use the created uncompressed bundlerepo + other = bundlerepo.bundlerepository(ui, repo.root, fname) + + o = other.changelog.nodesbetween(incoming)[0] + if opts['newest_first']: + o.reverse() + displayer = show_changeset(ui, other, opts) + for n in o: + parents = [p for p in other.changelog.parents(n) if p != nullid] + if opts['no_merges'] and len(parents) == 2: + continue + displayer.show(changenode=n) + if opts['patch']: + prev = (parents and parents[0]) or nullid + dodiff(ui, ui, other, prev, n) + ui.write("\n") + finally: + if hasattr(other, 'close'): + other.close() + if cleanup: + os.unlink(cleanup) + +def init(ui, dest="."): + """create a new repository in the given directory + + Initialize a new repository in the given directory. If the given + directory does not exist, it is created. + + If no directory is given, the current directory is used. + """ + if not os.path.exists(dest): + os.mkdir(dest) + hg.repository(ui, dest, create=1) + +def locate(ui, repo, *pats, **opts): + """locate files matching specific patterns + + Print all files under Mercurial control whose names match the + given patterns. + + This command searches the current directory and its + subdirectories. To search an entire repository, move to the root + of the repository. + + If no patterns are given to match, this command prints all file + names. + + If you want to feed the output of this command into the "xargs" + command, use the "-0" option to both this command and "xargs". + This will avoid the problem of "xargs" treating single filenames + that contain white space as multiple filenames. + """ + end = opts['print0'] and '\0' or '\n' + rev = opts['rev'] + if rev: + node = repo.lookup(rev) + else: + node = None + + for src, abs, rel, exact in walk(repo, pats, opts, node=node, + head='(?:.*/|)'): + if not node and repo.dirstate.state(abs) == '?': + continue + if opts['fullpath']: + ui.write(os.path.join(repo.root, abs), end) + else: + ui.write(((pats and rel) or abs), end) + +def log(ui, repo, *pats, **opts): + """show revision history of entire repository or files + + Print the revision history of the specified files or the entire project. + + By default this command outputs: changeset id and hash, tags, + non-trivial parents, user, date and time, and a summary for each + commit. When the -v/--verbose switch is used, the list of changed + files and full commit message is shown. + """ + class dui(object): + # Implement and delegate some ui protocol. Save hunks of + # output for later display in the desired order. + def __init__(self, ui): + self.ui = ui + self.hunk = {} + self.header = {} + def bump(self, rev): + self.rev = rev + self.hunk[rev] = [] + self.header[rev] = [] + def note(self, *args): + if self.verbose: + self.write(*args) + def status(self, *args): + if not self.quiet: + self.write(*args) + def write(self, *args): + self.hunk[self.rev].append(args) + def write_header(self, *args): + self.header[self.rev].append(args) + def debug(self, *args): + if self.debugflag: + self.write(*args) + def __getattr__(self, key): + return getattr(self.ui, key) + + changeiter, getchange, matchfn = walkchangerevs(ui, repo, pats, opts) + + if opts['limit']: + try: + limit = int(opts['limit']) + except ValueError: + raise util.Abort(_('limit must be a positive integer')) + if limit <= 0: raise util.Abort(_('limit must be positive')) + else: + limit = sys.maxint + count = 0 + + displayer = show_changeset(ui, repo, opts) + for st, rev, fns in changeiter: + if st == 'window': + du = dui(ui) + displayer.ui = du + elif st == 'add': + du.bump(rev) + changenode = repo.changelog.node(rev) + parents = [p for p in repo.changelog.parents(changenode) + if p != nullid] + if opts['no_merges'] and len(parents) == 2: + continue + if opts['only_merges'] and len(parents) != 2: + continue + + if opts['keyword']: + changes = getchange(rev) + miss = 0 + for k in [kw.lower() for kw in opts['keyword']]: + if not (k in changes[1].lower() or + k in changes[4].lower() or + k in " ".join(changes[3][:20]).lower()): + miss = 1 + break + if miss: + continue + + br = None + if opts['branches']: + br = repo.branchlookup([repo.changelog.node(rev)]) + + displayer.show(rev, brinfo=br) + if opts['patch']: + prev = (parents and parents[0]) or nullid + dodiff(du, du, repo, prev, changenode, match=matchfn) + du.write("\n\n") + elif st == 'iter': + if count == limit: break + if du.header[rev]: + for args in du.header[rev]: + ui.write_header(*args) + if du.hunk[rev]: + count += 1 + for args in du.hunk[rev]: + ui.write(*args) + +def manifest(ui, repo, rev=None): + """output the latest or given revision of the project manifest + + Print a list of version controlled files for the given revision. + + The manifest is the list of files being version controlled. If no revision + is given then the tip is used. + """ + if rev: + try: + # assume all revision numbers are for changesets + n = repo.lookup(rev) + change = repo.changelog.read(n) + n = change[0] + except hg.RepoError: + n = repo.manifest.lookup(rev) + else: + n = repo.manifest.tip() + m = repo.manifest.read(n) + mf = repo.manifest.readflags(n) + files = m.keys() + files.sort() + + for f in files: + ui.write("%40s %3s %s\n" % (hex(m[f]), mf[f] and "755" or "644", f)) + +def merge(ui, repo, node=None, **opts): + """Merge working directory with another revision + + Merge the contents of the current working directory and the + requested revision. Files that changed between either parent are + marked as changed for the next commit and a commit must be + performed before any further updates are allowed. + """ + return doupdate(ui, repo, node=node, merge=True, **opts) + +def outgoing(ui, repo, dest="default-push", **opts): + """show changesets not found in destination + + Show changesets not found in the specified destination repository or + the default push location. These are the changesets that would be pushed + if a push was requested. + + See pull for valid destination format details. + """ + dest = ui.expandpath(dest) + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) + + other = hg.repository(ui, dest) + o = repo.findoutgoing(other, force=opts['force']) + if not o: + ui.status(_("no changes found\n")) + return + o = repo.changelog.nodesbetween(o)[0] + if opts['newest_first']: + o.reverse() + displayer = show_changeset(ui, repo, opts) + for n in o: + parents = [p for p in repo.changelog.parents(n) if p != nullid] + if opts['no_merges'] and len(parents) == 2: + continue + displayer.show(changenode=n) + if opts['patch']: + prev = (parents and parents[0]) or nullid + dodiff(ui, ui, repo, prev, n) + ui.write("\n") + +def parents(ui, repo, rev=None, branches=None, **opts): + """show the parents of the working dir or revision + + Print the working directory's parent revisions. + """ + if rev: + p = repo.changelog.parents(repo.lookup(rev)) + else: + p = repo.dirstate.parents() + + br = None + if branches is not None: + br = repo.branchlookup(p) + displayer = show_changeset(ui, repo, opts) + for n in p: + if n != nullid: + displayer.show(changenode=n, brinfo=br) + +def paths(ui, repo, search=None): + """show definition of symbolic path names + + Show definition of symbolic path name NAME. If no name is given, show + definition of available names. + + Path names are defined in the [paths] section of /etc/mercurial/hgrc + and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too. + """ + if search: + for name, path in ui.configitems("paths"): + if name == search: + ui.write("%s\n" % path) + return + ui.warn(_("not found!\n")) + return 1 + else: + for name, path in ui.configitems("paths"): + ui.write("%s = %s\n" % (name, path)) + +def postincoming(ui, repo, modheads, optupdate): + if modheads == 0: + return + if optupdate: + if modheads == 1: + return doupdate(ui, repo) + else: + ui.status(_("not updating, since new heads added\n")) + if modheads > 1: + ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n")) + else: + ui.status(_("(run 'hg update' to get a working copy)\n")) + +def pull(ui, repo, source="default", **opts): + """pull changes from the specified source + + Pull changes from a remote repository to a local one. + + This finds all changes from the repository at the specified path + or URL and adds them to the local repository. By default, this + does not update the copy of the project in the working directory. + + Valid URLs are of the form: + + local/filesystem/path + http://[user@]host[:port][/path] + https://[user@]host[:port][/path] + ssh://[user@]host[:port][/path] + + Some notes about using SSH with Mercurial: + - SSH requires an accessible shell account on the destination machine + and a copy of hg in the remote path or specified with as remotecmd. + - /path is relative to the remote user's home directory by default. + Use two slashes at the start of a path to specify an absolute path. + - Mercurial doesn't use its own compression via SSH; the right thing + to do is to configure it in your ~/.ssh/ssh_config, e.g.: + Host *.mylocalnetwork.example.com + Compression off + Host * + Compression on + Alternatively specify "ssh -C" as your ssh command in your hgrc or + with the --ssh command line option. + """ + source = ui.expandpath(source) + ui.status(_('pulling from %s\n') % (source)) + + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) + + other = hg.repository(ui, source) + revs = None + if opts['rev'] and not other.local(): + raise util.Abort(_("pull -r doesn't work for remote repositories yet")) + elif opts['rev']: + revs = [other.lookup(rev) for rev in opts['rev']] + modheads = repo.pull(other, heads=revs, force=opts['force']) + return postincoming(ui, repo, modheads, opts['update']) + +def push(ui, repo, dest="default-push", **opts): + """push changes to the specified destination + + Push changes from the local repository to the given destination. + + This is the symmetrical operation for pull. It helps to move + changes from the current repository to a different one. If the + destination is local this is identical to a pull in that directory + from the current one. + + By default, push will refuse to run if it detects the result would + increase the number of remote heads. This generally indicates the + the client has forgotten to sync and merge before pushing. + + Valid URLs are of the form: + + local/filesystem/path + ssh://[user@]host[:port][/path] + + Look at the help text for the pull command for important details + about ssh:// URLs. + """ + dest = ui.expandpath(dest) + ui.status('pushing to %s\n' % (dest)) + + if opts['ssh']: + ui.setconfig("ui", "ssh", opts['ssh']) + if opts['remotecmd']: + ui.setconfig("ui", "remotecmd", opts['remotecmd']) + + other = hg.repository(ui, dest) + revs = None + if opts['rev']: + revs = [repo.lookup(rev) for rev in opts['rev']] + r = repo.push(other, opts['force'], revs=revs) + return r == 0 + +def rawcommit(ui, repo, *flist, **rc): + """raw commit interface (DEPRECATED) + + (DEPRECATED) + Lowlevel commit, for use in helper scripts. + + This command is not intended to be used by normal users, as it is + primarily useful for importing from other SCMs. + + This command is now deprecated and will be removed in a future + release, please use debugsetparents and commit instead. + """ + + ui.warn(_("(the rawcommit command is deprecated)\n")) + + message = rc['message'] + if not message and rc['logfile']: + try: + message = open(rc['logfile']).read() + except IOError: + pass + if not message and not rc['logfile']: + raise util.Abort(_("missing commit message")) + + files = relpath(repo, list(flist)) + if rc['files']: + files += open(rc['files']).read().splitlines() + + rc['parent'] = map(repo.lookup, rc['parent']) + + try: + repo.rawcommit(files, message, rc['user'], rc['date'], *rc['parent']) + except ValueError, inst: + raise util.Abort(str(inst)) + +def recover(ui, repo): + """roll back an interrupted transaction + + Recover from an interrupted commit or pull. + + This command tries to fix the repository status after an interrupted + operation. It should only be necessary when Mercurial suggests it. + """ + if repo.recover(): + return repo.verify() + return 1 + +def remove(ui, repo, *pats, **opts): + """remove the specified files on the next commit + + Schedule the indicated files for removal from the repository. + + This command schedules the files to be removed at the next commit. + This only removes files from the current branch, not from the + entire project history. If the files still exist in the working + directory, they will be deleted from it. If invoked with --after, + files that have been manually deleted are marked as removed. + + Modified files and added files are not removed by default. To + remove them, use the -f/--force option. + """ + names = [] + if not opts['after'] and not pats: + raise util.Abort(_('no files specified')) + files, matchfn, anypats = matchpats(repo, pats, opts) + exact = dict.fromkeys(files) + mardu = map(dict.fromkeys, repo.changes(files=files, match=matchfn)) + modified, added, removed, deleted, unknown = mardu + remove, forget = [], [] + for src, abs, rel, exact in walk(repo, pats, opts): + reason = None + if abs not in deleted and opts['after']: + reason = _('is still present') + elif abs in modified and not opts['force']: + reason = _('is modified (use -f to force removal)') + elif abs in added: + if opts['force']: + forget.append(abs) + continue + reason = _('has been marked for add (use -f to force removal)') + elif abs in unknown: + reason = _('is not managed') + elif abs in removed: + continue + if reason: + if exact: + ui.warn(_('not removing %s: file %s\n') % (rel, reason)) + else: + if ui.verbose or not exact: + ui.status(_('removing %s\n') % rel) + remove.append(abs) + repo.forget(forget) + repo.remove(remove, unlink=not opts['after']) + +def rename(ui, repo, *pats, **opts): + """rename files; equivalent of copy + remove + + Mark dest as copies of sources; mark sources for deletion. If + dest is a directory, copies are put in that directory. If dest is + a file, there can only be one source. + + By default, this command copies the contents of files as they + stand in the working directory. If invoked with --after, the + operation is recorded, but no copying is performed. + + This command takes effect in the next commit. + + NOTE: This command should be treated as experimental. While it + should properly record rename files, this information is not yet + fully used by merge, nor fully reported by log. + """ + wlock = repo.wlock(0) + errs, copied = docopy(ui, repo, pats, opts, wlock) + names = [] + for abs, rel, exact in copied: + if ui.verbose or not exact: + ui.status(_('removing %s\n') % rel) + names.append(abs) + repo.remove(names, True, wlock) + return errs + +def revert(ui, repo, *pats, **opts): + """revert files or dirs to their states as of some revision + + With no revision specified, revert the named files or directories + to the contents they had in the parent of the working directory. + This restores the contents of the affected files to an unmodified + state. If the working directory has two parents, you must + explicitly specify the revision to revert to. + + Modified files are saved with a .orig suffix before reverting. + To disable these backups, use --no-backup. + + Using the -r option, revert the given files or directories to + their contents as of a specific revision. This can be helpful to"roll + back" some or all of a change that should not have been committed. + + Revert modifies the working directory. It does not commit any + changes, or change the parent of the working directory. If you + revert to a revision other than the parent of the working + directory, the reverted files will thus appear modified + afterwards. + + If a file has been deleted, it is recreated. If the executable + mode of a file was changed, it is reset. + + If names are given, all files matching the names are reverted. + + If no arguments are given, all files in the repository are reverted. + """ + parent, p2 = repo.dirstate.parents() + if opts['rev']: + node = repo.lookup(opts['rev']) + elif p2 != nullid: + raise util.Abort(_('working dir has two parents; ' + 'you must specify the revision to revert to')) + else: + node = parent + pmf = None + mf = repo.manifest.read(repo.changelog.read(node)[0]) + + wlock = repo.wlock() + + # need all matching names in dirstate and manifest of target rev, + # so have to walk both. do not print errors if files exist in one + # but not other. + + names = {} + target_only = {} + + # walk dirstate. + + for src, abs, rel, exact in walk(repo, pats, opts, badmatch=mf.has_key): + names[abs] = (rel, exact) + if src == 'b': + target_only[abs] = True + + # walk target manifest. + + for src, abs, rel, exact in walk(repo, pats, opts, node=node, + badmatch=names.has_key): + if abs in names: continue + names[abs] = (rel, exact) + target_only[abs] = True + + changes = repo.changes(match=names.has_key, wlock=wlock) + modified, added, removed, deleted, unknown = map(dict.fromkeys, changes) + + revert = ([], _('reverting %s\n')) + add = ([], _('adding %s\n')) + remove = ([], _('removing %s\n')) + forget = ([], _('forgetting %s\n')) + undelete = ([], _('undeleting %s\n')) + update = {} + + disptable = ( + # dispatch table: + # file state + # action if in target manifest + # action if not in target manifest + # make backup if in target manifest + # make backup if not in target manifest + (modified, revert, remove, True, True), + (added, revert, forget, True, False), + (removed, undelete, None, False, False), + (deleted, revert, remove, False, False), + (unknown, add, None, True, False), + (target_only, add, None, False, False), + ) + + entries = names.items() + entries.sort() + + for abs, (rel, exact) in entries: + in_mf = abs in mf + def handle(xlist, dobackup): + xlist[0].append(abs) + if dobackup and not opts['no_backup'] and os.path.exists(rel): + bakname = "%s.orig" % rel + ui.note(_('saving current version of %s as %s\n') % + (rel, bakname)) + shutil.copyfile(rel, bakname) + shutil.copymode(rel, bakname) + if ui.verbose or not exact: + ui.status(xlist[1] % rel) + for table, hitlist, misslist, backuphit, backupmiss in disptable: + if abs not in table: continue + # file has changed in dirstate + if in_mf: + handle(hitlist, backuphit) + elif misslist is not None: + handle(misslist, backupmiss) + else: + if exact: ui.warn(_('file not managed: %s\n' % rel)) + break + else: + # file has not changed in dirstate + if node == parent: + if exact: ui.warn(_('no changes needed to %s\n' % rel)) + continue + if not in_mf: + if pmf is None: + # only need parent manifest in this unlikely case, + # so do not read by default + pmf = repo.manifest.read(repo.changelog.read(parent)[0]) + if abs in pmf: + handle(remove, False) + update[abs] = True + + repo.dirstate.forget(forget[0]) + r = repo.update(node, False, True, update.has_key, False, wlock=wlock, + show_stats=False) + repo.dirstate.update(add[0], 'a') + repo.dirstate.update(undelete[0], 'n') + repo.dirstate.update(remove[0], 'r') + return r + +def rollback(ui, repo): + """roll back the last transaction in this repository + + Roll back the last transaction in this repository, restoring the + project to its state prior to the transaction. + + Transactions are used to encapsulate the effects of all commands + that create new changesets or propagate existing changesets into a + repository. For example, the following commands are transactional, + and their effects can be rolled back: + + commit + import + pull + push (with this repository as destination) + unbundle + + This command should be used with care. There is only one level of + rollback, and there is no way to undo a rollback. + + This command is not intended for use on public repositories. Once + changes are visible for pull by other users, rolling a transaction + back locally is ineffective (someone else may already have pulled + the changes). Furthermore, a race is possible with readers of the + repository; for example an in-progress pull from the repository + may fail if a rollback is performed. + """ + repo.rollback() + +def root(ui, repo): + """print the root (top) of the current working dir + + Print the root directory of the current repository. + """ + ui.write(repo.root + "\n") + +def serve(ui, repo, **opts): + """export the repository via HTTP + + Start a local HTTP repository browser and pull server. + + By default, the server logs accesses to stdout and errors to + stderr. Use the "-A" and "-E" options to log to files. + """ + + if opts["stdio"]: + if repo is None: + raise hg.RepoError(_('no repo found')) + fin, fout = sys.stdin, sys.stdout + sys.stdout = sys.stderr + + # Prevent insertion/deletion of CRs + util.set_binary(fin) + util.set_binary(fout) + + def getarg(): + argline = fin.readline()[:-1] + arg, l = argline.split() + val = fin.read(int(l)) + return arg, val + def respond(v): + fout.write("%d\n" % len(v)) + fout.write(v) + fout.flush() + + lock = None + + while 1: + cmd = fin.readline()[:-1] + if cmd == '': + return + if cmd == "heads": + h = repo.heads() + respond(" ".join(map(hex, h)) + "\n") + if cmd == "lock": + lock = repo.lock() + respond("") + if cmd == "unlock": + if lock: + lock.release() + lock = None + respond("") + elif cmd == "branches": + arg, nodes = getarg() + nodes = map(bin, nodes.split(" ")) + r = [] + for b in repo.branches(nodes): + r.append(" ".join(map(hex, b)) + "\n") + respond("".join(r)) + elif cmd == "between": + arg, pairs = getarg() + pairs = [map(bin, p.split("-")) for p in pairs.split(" ")] + r = [] + for b in repo.between(pairs): + r.append(" ".join(map(hex, b)) + "\n") + respond("".join(r)) + elif cmd == "changegroup": + nodes = [] + arg, roots = getarg() + nodes = map(bin, roots.split(" ")) + + cg = repo.changegroup(nodes, 'serve') + while 1: + d = cg.read(4096) + if not d: + break + fout.write(d) + + fout.flush() + + elif cmd == "addchangegroup": + if not lock: + respond("not locked") + continue + respond("") + + r = repo.addchangegroup(fin, 'serve') + respond(str(r)) + + optlist = ("name templates style address port ipv6" + " accesslog errorlog webdir_conf") + for o in optlist.split(): + if opts[o]: + ui.setconfig("web", o, opts[o]) + + if repo is None and not ui.config("web", "webdir_conf"): + raise hg.RepoError(_('no repo found')) + + if opts['daemon'] and not opts['daemon_pipefds']: + rfd, wfd = os.pipe() + args = sys.argv[:] + args.append('--daemon-pipefds=%d,%d' % (rfd, wfd)) + pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0), + args[0], args) + os.close(wfd) + os.read(rfd, 1) + os._exit(0) + + try: + httpd = create_server(ui, repo, hgwebdir, hgweb) + except socket.error, inst: + raise util.Abort(_('cannot start server: ') + inst.args[1]) + + if ui.verbose: + addr, port = httpd.socket.getsockname() + if addr == '0.0.0.0': + addr = socket.gethostname() + else: + try: + addr = socket.gethostbyaddr(addr)[0] + except socket.error: + pass + if port != 80: + ui.status(_('listening at http://%s:%d/\n') % (addr, port)) + else: + ui.status(_('listening at http://%s/\n') % addr) + + if opts['pid_file']: + fp = open(opts['pid_file'], 'w') + fp.write(str(os.getpid())) + fp.close() + + if opts['daemon_pipefds']: + rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')] + os.close(rfd) + os.write(wfd, 'y') + os.close(wfd) + sys.stdout.flush() + sys.stderr.flush() + fd = os.open(util.nulldev, os.O_RDWR) + if fd != 0: os.dup2(fd, 0) + if fd != 1: os.dup2(fd, 1) + if fd != 2: os.dup2(fd, 2) + if fd not in (0, 1, 2): os.close(fd) + + httpd.serve_forever() + +def status(ui, repo, *pats, **opts): + """show changed files in the working directory + + Show changed files in the repository. If names are + given, only files that match are shown. + + The codes used to show the status of files are: + M = modified + A = added + R = removed + ! = deleted, but still tracked + ? = not tracked + I = ignored (not shown by default) + """ + + show_ignored = opts['ignored'] and True or False + files, matchfn, anypats = matchpats(repo, pats, opts) + cwd = (pats and repo.getcwd()) or '' + modified, added, removed, deleted, unknown, ignored = [ + [util.pathto(cwd, x) for x in n] + for n in repo.changes(files=files, match=matchfn, + show_ignored=show_ignored)] + + changetypes = [('modified', 'M', modified), + ('added', 'A', added), + ('removed', 'R', removed), + ('deleted', '!', deleted), + ('unknown', '?', unknown), + ('ignored', 'I', ignored)] + + end = opts['print0'] and '\0' or '\n' + + for opt, char, changes in ([ct for ct in changetypes if opts[ct[0]]] + or changetypes): + if opts['no_status']: + format = "%%s%s" % end + else: + format = "%s %%s%s" % (char, end) + + for f in changes: + ui.write(format % f) + +def tag(ui, repo, name, rev_=None, **opts): + """add a tag for the current tip or a given revision + + Name a particular revision using <name>. + + Tags are used to name particular revisions of the repository and are + very useful to compare different revision, to go back to significant + earlier versions or to mark branch points as releases, etc. + + If no revision is given, the tip is used. + + To facilitate version control, distribution, and merging of tags, + they are stored as a file named ".hgtags" which is managed + similarly to other project files and can be hand-edited if + necessary. The file '.hg/localtags' is used for local tags (not + shared among repositories). + """ + if name == "tip": + raise util.Abort(_("the name 'tip' is reserved")) + if rev_ is not None: + ui.warn(_("use of 'hg tag NAME [REV]' is deprecated, " + "please use 'hg tag [-r REV] NAME' instead\n")) + if opts['rev']: + raise util.Abort(_("use only one form to specify the revision")) + if opts['rev']: + rev_ = opts['rev'] + if rev_: + r = hex(repo.lookup(rev_)) + else: + r = hex(repo.changelog.tip()) + + disallowed = (revrangesep, '\r', '\n') + for c in disallowed: + if name.find(c) >= 0: + raise util.Abort(_("%s cannot be used in a tag name") % repr(c)) + + repo.hook('pretag', throw=True, node=r, tag=name, + local=int(not not opts['local'])) + + if opts['local']: + repo.opener("localtags", "a").write("%s %s\n" % (r, name)) + repo.hook('tag', node=r, tag=name, local=1) + return + + for x in repo.changes(): + if ".hgtags" in x: + raise util.Abort(_("working copy of .hgtags is changed " + "(please commit .hgtags manually)")) + + repo.wfile(".hgtags", "ab").write("%s %s\n" % (r, name)) + if repo.dirstate.state(".hgtags") == '?': + repo.add([".hgtags"]) + + message = (opts['message'] or + _("Added tag %s for changeset %s") % (name, r)) + try: + repo.commit([".hgtags"], message, opts['user'], opts['date']) + repo.hook('tag', node=r, tag=name, local=0) + except ValueError, inst: + raise util.Abort(str(inst)) + +def tags(ui, repo): + """list repository tags + + List the repository tags. + + This lists both regular and local tags. + """ + + l = repo.tagslist() + l.reverse() + for t, n in l: + try: + r = "%5d:%s" % (repo.changelog.rev(n), hex(n)) + except KeyError: + r = " ?:?" + if ui.quiet: + ui.write("%s\n" % t) + else: + ui.write("%-30s %s\n" % (t, r)) + +def tip(ui, repo, **opts): + """show the tip revision + + Show the tip revision. + """ + n = repo.changelog.tip() + br = None + if opts['branches']: + br = repo.branchlookup([n]) + show_changeset(ui, repo, opts).show(changenode=n, brinfo=br) + if opts['patch']: + dodiff(ui, ui, repo, repo.changelog.parents(n)[0], n) + +def unbundle(ui, repo, fname, **opts): + """apply a changegroup file + + Apply a compressed changegroup file generated by the bundle + command. + """ + f = urllib.urlopen(fname) + + header = f.read(6) + if not header.startswith("HG"): + raise util.Abort(_("%s: not a Mercurial bundle file") % fname) + elif not header.startswith("HG10"): + raise util.Abort(_("%s: unknown bundle version") % fname) + elif header == "HG10BZ": + def generator(f): + zd = bz2.BZ2Decompressor() + zd.decompress("BZ") + for chunk in f: + yield zd.decompress(chunk) + elif header == "HG10UN": + def generator(f): + for chunk in f: + yield chunk + else: + raise util.Abort(_("%s: unknown bundle compression type") + % fname) + gen = generator(util.filechunkiter(f, 4096)) + modheads = repo.addchangegroup(util.chunkbuffer(gen), 'unbundle') + return postincoming(ui, repo, modheads, opts['update']) + +def undo(ui, repo): + """undo the last commit or pull (DEPRECATED) + + (DEPRECATED) + This command is now deprecated and will be removed in a future + release. Please use the rollback command instead. For usage + instructions, see the rollback command. + """ + ui.warn(_('(the undo command is deprecated; use rollback instead)\n')) + repo.rollback() + +def update(ui, repo, node=None, merge=False, clean=False, force=None, + branch=None, **opts): + """update or merge working directory + + Update the working directory to the specified revision. + + If there are no outstanding changes in the working directory and + there is a linear relationship between the current version and the + requested version, the result is the requested version. + + To merge the working directory with another revision, use the + merge command. + + By default, update will refuse to run if doing so would require + merging or discarding local changes. + """ + if merge: + ui.warn(_('(the -m/--merge option is deprecated; ' + 'use the merge command instead)\n')) + return doupdate(ui, repo, node, merge, clean, force, branch, **opts) + +def doupdate(ui, repo, node=None, merge=False, clean=False, force=None, + branch=None, **opts): + if branch: + br = repo.branchlookup(branch=branch) + found = [] + for x in br: + if branch in br[x]: + found.append(x) + if len(found) > 1: + ui.warn(_("Found multiple heads for %s\n") % branch) + for x in found: + show_changeset(ui, repo, opts).show(changenode=x, brinfo=br) + return 1 + if len(found) == 1: + node = found[0] + ui.warn(_("Using head %s for branch %s\n") % (short(node), branch)) + else: + ui.warn(_("branch %s not found\n") % (branch)) + return 1 + else: + node = node and repo.lookup(node) or repo.changelog.tip() + return repo.update(node, allow=merge, force=clean, forcemerge=force) + +def verify(ui, repo): + """verify the integrity of the repository + + Verify the integrity of the current repository. + + This will perform an extensive check of the repository's + integrity, validating the hashes and checksums of each entry in + the changelog, manifest, and tracked files, as well as the + integrity of their crosslinks and indices. + """ + return repo.verify() + +# Command options and aliases are listed here, alphabetically + +table = { + "^add": + (add, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg add [OPTION]... [FILE]...')), + "debugaddremove|addremove": + (addremove, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg addremove [OPTION]... [FILE]...')), + "^annotate": + (annotate, + [('r', 'rev', '', _('annotate the specified revision')), + ('a', 'text', None, _('treat all files as text')), + ('u', 'user', None, _('list the author')), + ('d', 'date', None, _('list the date')), + ('n', 'number', None, _('list the revision number (default)')), + ('c', 'changeset', None, _('list the changeset')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg annotate [-r REV] [-a] [-u] [-d] [-n] [-c] FILE...')), + "archive": + (archive, + [('', 'no-decode', None, _('do not pass files through decoders')), + ('p', 'prefix', '', _('directory prefix for files in archive')), + ('r', 'rev', '', _('revision to distribute')), + ('t', 'type', '', _('type of distribution to create')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg archive [OPTION]... DEST')), + "backout": + (backout, + [('', 'merge', None, + _('merge with old dirstate parent after backout')), + ('m', 'message', '', _('use <text> as commit message')), + ('l', 'logfile', '', _('read commit message from <file>')), + ('d', 'date', '', _('record datecode as commit date')), + ('u', 'user', '', _('record user as committer')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg backout [OPTION]... REV')), + "bundle": + (bundle, + [('f', 'force', None, + _('run even when remote repository is unrelated'))], + _('hg bundle FILE DEST')), + "cat": + (cat, + [('o', 'output', '', _('print output to file with formatted name')), + ('r', 'rev', '', _('print the given revision')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg cat [OPTION]... FILE...')), + "^clone": + (clone, + [('U', 'noupdate', None, _('do not update the new working directory')), + ('r', 'rev', [], + _('a changeset you would like to have after cloning')), + ('', 'pull', None, _('use pull protocol to copy metadata')), + ('e', 'ssh', '', _('specify ssh command to use')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], + _('hg clone [OPTION]... SOURCE [DEST]')), + "^commit|ci": + (commit, + [('A', 'addremove', None, + _('mark new/missing files as added/removed before committing')), + ('m', 'message', '', _('use <text> as commit message')), + ('l', 'logfile', '', _('read the commit message from <file>')), + ('d', 'date', '', _('record datecode as commit date')), + ('u', 'user', '', _('record user as commiter')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg commit [OPTION]... [FILE]...')), + "copy|cp": + (copy, + [('A', 'after', None, _('record a copy that has already occurred')), + ('f', 'force', None, + _('forcibly copy over an existing managed file')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg copy [OPTION]... [SOURCE]... DEST')), + "debugancestor": (debugancestor, [], _('debugancestor INDEX REV1 REV2')), + "debugcomplete": + (debugcomplete, + [('o', 'options', None, _('show the command options'))], + _('debugcomplete [-o] CMD')), + "debugrebuildstate": + (debugrebuildstate, + [('r', 'rev', '', _('revision to rebuild to'))], + _('debugrebuildstate [-r REV] [REV]')), + "debugcheckstate": (debugcheckstate, [], _('debugcheckstate')), + "debugconfig": (debugconfig, [], _('debugconfig')), + "debugsetparents": (debugsetparents, [], _('debugsetparents REV1 [REV2]')), + "debugstate": (debugstate, [], _('debugstate')), + "debugdata": (debugdata, [], _('debugdata FILE REV')), + "debugindex": (debugindex, [], _('debugindex FILE')), + "debugindexdot": (debugindexdot, [], _('debugindexdot FILE')), + "debugrename": (debugrename, [], _('debugrename FILE [REV]')), + "debugwalk": + (debugwalk, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('debugwalk [OPTION]... [FILE]...')), + "^diff": + (diff, + [('r', 'rev', [], _('revision')), + ('a', 'text', None, _('treat all files as text')), + ('p', 'show-function', None, + _('show which function each change is in')), + ('w', 'ignore-all-space', None, + _('ignore white space when comparing lines')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]...')), + "^export": + (export, + [('o', 'output', '', _('print output to file with formatted name')), + ('a', 'text', None, _('treat all files as text')), + ('', 'switch-parent', None, _('diff against the second parent'))], + _('hg export [-a] [-o OUTFILESPEC] REV...')), + "debugforget|forget": + (forget, + [('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg forget [OPTION]... FILE...')), + "grep": + (grep, + [('0', 'print0', None, _('end fields with NUL')), + ('', 'all', None, _('print all revisions that match')), + ('i', 'ignore-case', None, _('ignore case when matching')), + ('l', 'files-with-matches', None, + _('print only filenames and revs that match')), + ('n', 'line-number', None, _('print matching line numbers')), + ('r', 'rev', [], _('search in given revision range')), + ('u', 'user', None, _('print user who committed change')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg grep [OPTION]... PATTERN [FILE]...')), + "heads": + (heads, + [('b', 'branches', None, _('show branches')), + ('', 'style', '', _('display using template map file')), + ('r', 'rev', '', _('show only heads which are descendants of rev')), + ('', 'template', '', _('display with template'))], + _('hg heads [-b] [-r <rev>]')), + "help": (help_, [], _('hg help [COMMAND]')), + "identify|id": (identify, [], _('hg identify')), + "import|patch": + (import_, + [('p', 'strip', 1, + _('directory strip option for patch. This has the same\n' + 'meaning as the corresponding patch option')), + ('b', 'base', '', _('base path')), + ('f', 'force', None, + _('skip check for outstanding uncommitted changes'))], + _('hg import [-p NUM] [-b BASE] [-f] PATCH...')), + "incoming|in": (incoming, + [('M', 'no-merges', None, _('do not show merges')), + ('f', 'force', None, + _('run even when remote repository is unrelated')), + ('', 'style', '', _('display using template map file')), + ('n', 'newest-first', None, _('show newest record first')), + ('', 'bundle', '', _('file to store the bundles into')), + ('p', 'patch', None, _('show patch')), + ('', 'template', '', _('display with template')), + ('e', 'ssh', '', _('specify ssh command to use')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], + _('hg incoming [-p] [-n] [-M] [--bundle FILENAME] [SOURCE]')), + "^init": (init, [], _('hg init [DEST]')), + "locate": + (locate, + [('r', 'rev', '', _('search the repository as it stood at rev')), + ('0', 'print0', None, + _('end filenames with NUL, for use with xargs')), + ('f', 'fullpath', None, + _('print complete paths from the filesystem root')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg locate [OPTION]... [PATTERN]...')), + "^log|history": + (log, + [('b', 'branches', None, _('show branches')), + ('k', 'keyword', [], _('search for a keyword')), + ('l', 'limit', '', _('limit number of changes displayed')), + ('r', 'rev', [], _('show the specified revision or range')), + ('M', 'no-merges', None, _('do not show merges')), + ('', 'style', '', _('display using template map file')), + ('m', 'only-merges', None, _('show only merges')), + ('p', 'patch', None, _('show patch')), + ('', 'template', '', _('display with template')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg log [OPTION]... [FILE]')), + "manifest": (manifest, [], _('hg manifest [REV]')), + "merge": + (merge, + [('b', 'branch', '', _('merge with head of a specific branch')), + ('f', 'force', None, _('force a merge with outstanding changes'))], + _('hg merge [-b TAG] [-f] [REV]')), + "outgoing|out": (outgoing, + [('M', 'no-merges', None, _('do not show merges')), + ('f', 'force', None, + _('run even when remote repository is unrelated')), + ('p', 'patch', None, _('show patch')), + ('', 'style', '', _('display using template map file')), + ('n', 'newest-first', None, _('show newest record first')), + ('', 'template', '', _('display with template')), + ('e', 'ssh', '', _('specify ssh command to use')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], + _('hg outgoing [-M] [-p] [-n] [DEST]')), + "^parents": + (parents, + [('b', 'branches', None, _('show branches')), + ('', 'style', '', _('display using template map file')), + ('', 'template', '', _('display with template'))], + _('hg parents [-b] [REV]')), + "paths": (paths, [], _('hg paths [NAME]')), + "^pull": + (pull, + [('u', 'update', None, + _('update the working directory to tip after pull')), + ('e', 'ssh', '', _('specify ssh command to use')), + ('f', 'force', None, + _('run even when remote repository is unrelated')), + ('r', 'rev', [], _('a specific revision you would like to pull')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], + _('hg pull [-u] [-e FILE] [-r REV]... [--remotecmd FILE] [SOURCE]')), + "^push": + (push, + [('f', 'force', None, _('force push')), + ('e', 'ssh', '', _('specify ssh command to use')), + ('r', 'rev', [], _('a specific revision you would like to push')), + ('', 'remotecmd', '', + _('specify hg command to run on the remote side'))], + _('hg push [-f] [-e FILE] [-r REV]... [--remotecmd FILE] [DEST]')), + "debugrawcommit|rawcommit": + (rawcommit, + [('p', 'parent', [], _('parent')), + ('d', 'date', '', _('date code')), + ('u', 'user', '', _('user')), + ('F', 'files', '', _('file list')), + ('m', 'message', '', _('commit message')), + ('l', 'logfile', '', _('commit message file'))], + _('hg debugrawcommit [OPTION]... [FILE]...')), + "recover": (recover, [], _('hg recover')), + "^remove|rm": + (remove, + [('A', 'after', None, _('record remove that has already occurred')), + ('f', 'force', None, _('remove file even if modified')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg remove [OPTION]... FILE...')), + "rename|mv": + (rename, + [('A', 'after', None, _('record a rename that has already occurred')), + ('f', 'force', None, + _('forcibly copy over an existing managed file')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg rename [OPTION]... SOURCE... DEST')), + "^revert": + (revert, + [('r', 'rev', '', _('revision to revert to')), + ('', 'no-backup', None, _('do not save backup copies of files')), + ('I', 'include', [], _('include names matching given patterns')), + ('X', 'exclude', [], _('exclude names matching given patterns'))], + _('hg revert [-r REV] [NAME]...')), + "rollback": (rollback, [], _('hg rollback')), + "root": (root, [], _('hg root')), + "^serve": + (serve, + [('A', 'accesslog', '', _('name of access log file to write to')), + ('d', 'daemon', None, _('run server in background')), + ('', 'daemon-pipefds', '', _('used internally by daemon mode')), + ('E', 'errorlog', '', _('name of error log file to write to')), + ('p', 'port', 0, _('port to use (default: 8000)')), + ('a', 'address', '', _('address to use')), + ('n', 'name', '', + _('name to show in web pages (default: working dir)')), + ('', 'webdir-conf', '', _('name of the webdir config file' + ' (serve more than one repo)')), + ('', 'pid-file', '', _('name of file to write process ID to')), + ('', 'stdio', None, _('for remote clients')), + ('t', 'templates', '', _('web templates to use')), + ('', 'style', '', _('template style to use')), + ('6', 'ipv6', None, _('use IPv6 in addition to IPv4'))], + _('hg serve [OPTION]...')), + "^status|st": + (status, + [('m', 'modified', None, _('show only modified files')), + ('a', 'added', None, _('show only added files')), + ('r', 'removed', None, _('show only removed files')), + ('d', 'deleted', None, _('show only deleted (but tracked) files')), + ('u', 'unknown', None, _('show only unknown (not tracked) files')), + ('i', 'ignored', None, _('show ignored files')), + ('n', 'no-status', None, _('hide status prefix')), + ('0', 'print0', None, + _('end filenames with NUL, for use with xargs')), + ('I', 'include', [], _('include names matching the given patterns')), + ('X', 'exclude', [], _('exclude names matching the given patterns'))], + _('hg status [OPTION]... [FILE]...')), + "tag": + (tag, + [('l', 'local', None, _('make the tag local')), + ('m', 'message', '', _('message for tag commit log entry')), + ('d', 'date', '', _('record datecode as commit date')), + ('u', 'user', '', _('record user as commiter')), + ('r', 'rev', '', _('revision to tag'))], + _('hg tag [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME')), + "tags": (tags, [], _('hg tags')), + "tip": + (tip, + [('b', 'branches', None, _('show branches')), + ('', 'style', '', _('display using template map file')), + ('p', 'patch', None, _('show patch')), + ('', 'template', '', _('display with template'))], + _('hg tip [-b] [-p]')), + "unbundle": + (unbundle, + [('u', 'update', None, + _('update the working directory to tip after unbundle'))], + _('hg unbundle [-u] FILE')), + "debugundo|undo": (undo, [], _('hg undo')), + "^update|up|checkout|co": + (update, + [('b', 'branch', '', _('checkout the head of a specific branch')), + ('m', 'merge', None, _('allow merging of branches (DEPRECATED)')), + ('C', 'clean', None, _('overwrite locally modified files')), + ('f', 'force', None, _('force a merge with outstanding changes'))], + _('hg update [-b TAG] [-m] [-C] [-f] [REV]')), + "verify": (verify, [], _('hg verify')), + "version": (show_version, [], _('hg version')), +} + +globalopts = [ + ('R', 'repository', '', + _('repository root directory or symbolic path name')), + ('', 'cwd', '', _('change working directory')), + ('y', 'noninteractive', None, + _('do not prompt, assume \'yes\' for any required answers')), + ('q', 'quiet', None, _('suppress output')), + ('v', 'verbose', None, _('enable additional output')), + ('', 'config', [], _('set/override config option')), + ('', 'debug', None, _('enable debugging output')), + ('', 'debugger', None, _('start debugger')), + ('', 'traceback', None, _('print traceback on exception')), + ('', 'time', None, _('time how long the command takes')), + ('', 'profile', None, _('print command execution profile')), + ('', 'version', None, _('output version information and exit')), + ('h', 'help', None, _('display help and exit')), +] + +norepo = ("clone init version help debugancestor debugcomplete debugdata" + " debugindex debugindexdot") +optionalrepo = ("paths serve debugconfig") + +def findpossible(cmd): + """ + Return cmd -> (aliases, command table entry) + for each matching command. + Return debug commands (or their aliases) only if no normal command matches. + """ + choice = {} + debugchoice = {} + for e in table.keys(): + aliases = e.lstrip("^").split("|") + found = None + if cmd in aliases: + found = cmd + else: + for a in aliases: + if a.startswith(cmd): + found = a + break + if found is not None: + if aliases[0].startswith("debug"): + debugchoice[found] = (aliases, table[e]) + else: + choice[found] = (aliases, table[e]) + + if not choice and debugchoice: + choice = debugchoice + + return choice + +def find(cmd): + """Return (aliases, command table entry) for command string.""" + choice = findpossible(cmd) + + if choice.has_key(cmd): + return choice[cmd] + + if len(choice) > 1: + clist = choice.keys() + clist.sort() + raise AmbiguousCommand(cmd, clist) + + if choice: + return choice.values()[0] + + raise UnknownCommand(cmd) + +def catchterm(*args): + raise util.SignalInterrupt + +def run(): + sys.exit(dispatch(sys.argv[1:])) + +class ParseError(Exception): + """Exception raised on errors in parsing the command line.""" + +def parse(ui, args): + options = {} + cmdoptions = {} + + try: + args = fancyopts.fancyopts(args, globalopts, options) + except fancyopts.getopt.GetoptError, inst: + raise ParseError(None, inst) + + if args: + cmd, args = args[0], args[1:] + aliases, i = find(cmd) + cmd = aliases[0] + defaults = ui.config("defaults", cmd) + if defaults: + args = defaults.split() + args + c = list(i[1]) + else: + cmd = None + c = [] + + # combine global options into local + for o in globalopts: + c.append((o[0], o[1], options[o[1]], o[3])) + + try: + args = fancyopts.fancyopts(args, c, cmdoptions) + except fancyopts.getopt.GetoptError, inst: + raise ParseError(cmd, inst) + + # separate global options back out + for o in globalopts: + n = o[1] + options[n] = cmdoptions[n] + del cmdoptions[n] + + return (cmd, cmd and i[0] or None, args, options, cmdoptions) + +def dispatch(args): + for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM': + num = getattr(signal, name, None) + if num: signal.signal(num, catchterm) + + try: + u = ui.ui(traceback='--traceback' in sys.argv[1:]) + except util.Abort, inst: + sys.stderr.write(_("abort: %s\n") % inst) + return -1 + + external = [] + for x in u.extensions(): + try: + if x[1]: + mod = imp.load_source(x[0], x[1]) + else: + def importh(name): + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + try: + mod = importh("hgext." + x[0]) + except ImportError: + mod = importh(x[0]) + external.append(mod) + except Exception, inst: + u.warn(_("*** failed to import extension %s: %s\n") % (x[0], inst)) + if u.print_exc(): + return 1 + + for x in external: + uisetup = getattr(x, 'uisetup', None) + if uisetup: + uisetup(u) + cmdtable = getattr(x, 'cmdtable', {}) + for t in cmdtable: + if t in table: + u.warn(_("module %s overrides %s\n") % (x.__name__, t)) + table.update(cmdtable) + + try: + cmd, func, args, options, cmdoptions = parse(u, args) + if options["time"]: + def get_times(): + t = os.times() + if t[4] == 0.0: # Windows leaves this as zero, so use time.clock() + t = (t[0], t[1], t[2], t[3], time.clock()) + return t + s = get_times() + def print_time(): + t = get_times() + u.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") % + (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3])) + atexit.register(print_time) + + u.updateopts(options["verbose"], options["debug"], options["quiet"], + not options["noninteractive"], options["traceback"], + options["config"]) + + # enter the debugger before command execution + if options['debugger']: + pdb.set_trace() + + try: + if options['cwd']: + try: + os.chdir(options['cwd']) + except OSError, inst: + raise util.Abort('%s: %s' % + (options['cwd'], inst.strerror)) + + path = u.expandpath(options["repository"]) or "" + repo = path and hg.repository(u, path=path) or None + + if options['help']: + return help_(u, cmd, options['version']) + elif options['version']: + return show_version(u) + elif not cmd: + return help_(u, 'shortlist') + + if cmd not in norepo.split(): + try: + if not repo: + repo = hg.repository(u, path=path) + u = repo.ui + for x in external: + if hasattr(x, 'reposetup'): + x.reposetup(u, repo) + except hg.RepoError: + if cmd not in optionalrepo.split(): + raise + d = lambda: func(u, repo, *args, **cmdoptions) + else: + d = lambda: func(u, *args, **cmdoptions) + + try: + if options['profile']: + import hotshot, hotshot.stats + prof = hotshot.Profile("hg.prof") + try: + try: + return prof.runcall(d) + except: + try: + u.warn(_('exception raised - generating ' + 'profile anyway\n')) + except: + pass + raise + finally: + prof.close() + stats = hotshot.stats.load("hg.prof") + stats.strip_dirs() + stats.sort_stats('time', 'calls') + stats.print_stats(40) + else: + return d() + finally: + u.flush() + except: + # enter the debugger when we hit an exception + if options['debugger']: + pdb.post_mortem(sys.exc_info()[2]) + u.print_exc() + raise + except ParseError, inst: + if inst.args[0]: + u.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1])) + help_(u, inst.args[0]) + else: + u.warn(_("hg: %s\n") % inst.args[1]) + help_(u, 'shortlist') + except AmbiguousCommand, inst: + u.warn(_("hg: command '%s' is ambiguous:\n %s\n") % + (inst.args[0], " ".join(inst.args[1]))) + except UnknownCommand, inst: + u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) + help_(u, 'shortlist') + except hg.RepoError, inst: + u.warn(_("abort: %s!\n") % inst) + except lock.LockHeld, inst: + if inst.errno == errno.ETIMEDOUT: + reason = _('timed out waiting for lock held by %s') % inst.locker + else: + reason = _('lock held by %s') % inst.locker + u.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason)) + except lock.LockUnavailable, inst: + u.warn(_("abort: could not lock %s: %s\n") % + (inst.desc or inst.filename, inst.strerror)) + except revlog.RevlogError, inst: + u.warn(_("abort: "), inst, "!\n") + except util.SignalInterrupt: + u.warn(_("killed!\n")) + except KeyboardInterrupt: + try: + u.warn(_("interrupted!\n")) + except IOError, inst: + if inst.errno == errno.EPIPE: + if u.debugflag: + u.warn(_("\nbroken pipe\n")) + else: + raise + except IOError, inst: + if hasattr(inst, "code"): + u.warn(_("abort: %s\n") % inst) + elif hasattr(inst, "reason"): + u.warn(_("abort: error: %s\n") % inst.reason[1]) + elif hasattr(inst, "args") and inst[0] == errno.EPIPE: + if u.debugflag: + u.warn(_("broken pipe\n")) + elif getattr(inst, "strerror", None): + if getattr(inst, "filename", None): + u.warn(_("abort: %s - %s\n") % (inst.strerror, inst.filename)) + else: + u.warn(_("abort: %s\n") % inst.strerror) + else: + raise + except OSError, inst: + if hasattr(inst, "filename"): + u.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename)) + else: + u.warn(_("abort: %s\n") % inst.strerror) + except util.Abort, inst: + u.warn(_('abort: '), inst.args[0] % inst.args[1:], '\n') + except TypeError, inst: + # was this an argument error? + tb = traceback.extract_tb(sys.exc_info()[2]) + if len(tb) > 2: # no + raise + u.debug(inst, "\n") + u.warn(_("%s: invalid arguments\n") % cmd) + help_(u, cmd) + except SystemExit, inst: + # Commands shouldn't sys.exit directly, but give a return code. + # Just in case catch this and and pass exit code to caller. + return inst.code + except: + u.warn(_("** unknown exception encountered, details follow\n")) + u.warn(_("** report bug details to mercurial@selenic.com\n")) + u.warn(_("** Mercurial Distributed SCM (version %s)\n") + % version.get_version()) + raise + + return -1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/demandload.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,129 @@ +'''Demand load modules when used, not when imported.''' + +__author__ = '''Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>. +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference.''' + +# this is based on matt's original demandload module. it is a +# complete rewrite. some time, we may need to support syntax of +# "import foo as bar". + +class _importer(object): + '''import a module. it is not imported until needed, and is + imported at most once per scope.''' + + def __init__(self, scope, modname, fromlist): + '''scope is context (globals() or locals()) in which import + should be made. modname is name of module to import. + fromlist is list of modules for "from foo import ..." + emulation.''' + + self.scope = scope + self.modname = modname + self.fromlist = fromlist + self.mod = None + + def module(self): + '''import the module if needed, and return.''' + if self.mod is None: + self.mod = __import__(self.modname, self.scope, self.scope, + self.fromlist) + del self.modname, self.fromlist + return self.mod + +class _replacer(object): + '''placeholder for a demand loaded module. demandload puts this in + a target scope. when an attribute of this object is looked up, + this object is replaced in the target scope with the actual + module. + + we use __getattribute__ to avoid namespace clashes between + placeholder object and real module.''' + + def __init__(self, importer, target): + self.importer = importer + self.target = target + # consider case where we do this: + # demandload(globals(), 'foo.bar foo.quux') + # foo will already exist in target scope when we get to + # foo.quux. so we remember that we will need to demandload + # quux into foo's scope when we really load it. + self.later = [] + + def module(self): + return object.__getattribute__(self, 'importer').module() + + def __getattribute__(self, key): + '''look up an attribute in a module and return it. replace the + name of the module in the caller\'s dict with the actual + module.''' + + module = object.__getattribute__(self, 'module')() + target = object.__getattribute__(self, 'target') + importer = object.__getattribute__(self, 'importer') + later = object.__getattribute__(self, 'later') + + if later: + demandload(module.__dict__, ' '.join(later)) + + importer.scope[target] = module + + return getattr(module, key) + +class _replacer_from(_replacer): + '''placeholder for a demand loaded module. used for "from foo + import ..." emulation. semantics of this are different than + regular import, so different implementation needed.''' + + def module(self): + importer = object.__getattribute__(self, 'importer') + target = object.__getattribute__(self, 'target') + + return getattr(importer.module(), target) + + def __call__(self, *args, **kwargs): + target = object.__getattribute__(self, 'module')() + return target(*args, **kwargs) + +def demandload(scope, modules): + '''import modules into scope when each is first used. + + scope should be the value of globals() in the module calling this + function, or locals() in the calling function. + + modules is a string listing module names, separated by white + space. names are handled like this: + + foo import foo + foo bar import foo, bar + foo.bar import foo.bar + foo:bar from foo import bar + foo:bar,quux from foo import bar, quux + foo.bar:quux from foo.bar import quux''' + + for mod in modules.split(): + col = mod.find(':') + if col >= 0: + fromlist = mod[col+1:].split(',') + mod = mod[:col] + else: + fromlist = [] + importer = _importer(scope, mod, fromlist) + if fromlist: + for name in fromlist: + scope[name] = _replacer_from(importer, name) + else: + dot = mod.find('.') + if dot >= 0: + basemod = mod[:dot] + val = scope.get(basemod) + # if base module has already been demandload()ed, + # remember to load this submodule into its namespace + # when needed. + if isinstance(val, _replacer): + later = object.__getattribute__(val, 'later') + later.append(mod[dot+1:]) + continue + else: + basemod = mod + scope[basemod] = _replacer(importer, basemod)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/dirstate.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,473 @@ +""" +dirstate.py - working directory tracking for mercurial + +Copyright 2005 Matt Mackall <mpm@selenic.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" + +import struct, os +from node import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "time bisect stat util re errno") + +class dirstate(object): + def __init__(self, opener, ui, root): + self.opener = opener + self.root = root + self.dirty = 0 + self.ui = ui + self.map = None + self.pl = None + self.copies = {} + self.ignorefunc = None + self.blockignore = False + + def wjoin(self, f): + return os.path.join(self.root, f) + + def getcwd(self): + cwd = os.getcwd() + if cwd == self.root: return '' + return cwd[len(self.root) + 1:] + + def hgignore(self): + '''return the contents of .hgignore files as a list of patterns. + + the files parsed for patterns include: + .hgignore in the repository root + any additional files specified in the [ui] section of ~/.hgrc + + trailing white space is dropped. + the escape character is backslash. + comments start with #. + empty lines are skipped. + + lines can be of the following formats: + + syntax: regexp # defaults following lines to non-rooted regexps + syntax: glob # defaults following lines to non-rooted globs + re:pattern # non-rooted regular expression + glob:pattern # non-rooted glob + pattern # pattern of the current default type''' + syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'} + def parselines(fp): + for line in fp: + escape = False + for i in xrange(len(line)): + if escape: escape = False + elif line[i] == '\\': escape = True + elif line[i] == '#': break + line = line[:i].rstrip() + if line: yield line + repoignore = self.wjoin('.hgignore') + files = [repoignore] + files.extend(self.ui.hgignorefiles()) + pats = {} + for f in files: + try: + pats[f] = [] + fp = open(f) + syntax = 'relre:' + for line in parselines(fp): + if line.startswith('syntax:'): + s = line[7:].strip() + try: + syntax = syntaxes[s] + except KeyError: + self.ui.warn(_("%s: ignoring invalid " + "syntax '%s'\n") % (f, s)) + continue + pat = syntax + line + for s in syntaxes.values(): + if line.startswith(s): + pat = line + break + pats[f].append(pat) + except IOError, inst: + if f != repoignore: + self.ui.warn(_("skipping unreadable ignore file" + " '%s': %s\n") % (f, inst.strerror)) + return pats + + def ignore(self, fn): + '''default match function used by dirstate and + localrepository. this honours the repository .hgignore file + and any other files specified in the [ui] section of .hgrc.''' + if self.blockignore: + return False + if not self.ignorefunc: + ignore = self.hgignore() + allpats = [] + [allpats.extend(patlist) for patlist in ignore.values()] + if allpats: + try: + files, self.ignorefunc, anypats = ( + util.matcher(self.root, inc=allpats, src='.hgignore')) + except util.Abort: + # Re-raise an exception where the src is the right file + for f, patlist in ignore.items(): + files, self.ignorefunc, anypats = ( + util.matcher(self.root, inc=patlist, src=f)) + else: + self.ignorefunc = util.never + return self.ignorefunc(fn) + + def __del__(self): + if self.dirty: + self.write() + + def __getitem__(self, key): + try: + return self.map[key] + except TypeError: + self.lazyread() + return self[key] + + def __contains__(self, key): + self.lazyread() + return key in self.map + + def parents(self): + self.lazyread() + return self.pl + + def markdirty(self): + if not self.dirty: + self.dirty = 1 + + def setparents(self, p1, p2=nullid): + self.lazyread() + self.markdirty() + self.pl = p1, p2 + + def state(self, key): + try: + return self[key][0] + except KeyError: + return "?" + + def lazyread(self): + if self.map is None: + self.read() + + def read(self): + self.map = {} + self.pl = [nullid, nullid] + try: + st = self.opener("dirstate").read() + if not st: return + except: return + + self.pl = [st[:20], st[20: 40]] + + pos = 40 + while pos < len(st): + e = struct.unpack(">cllll", st[pos:pos+17]) + l = e[4] + pos += 17 + f = st[pos:pos + l] + if '\0' in f: + f, c = f.split('\0') + self.copies[f] = c + self.map[f] = e[:4] + pos += l + + def copy(self, source, dest): + self.lazyread() + self.markdirty() + self.copies[dest] = source + + def copied(self, file): + return self.copies.get(file, None) + + def update(self, files, state, **kw): + ''' current states: + n normal + m needs merging + r marked for removal + a marked for addition''' + + if not files: return + self.lazyread() + self.markdirty() + for f in files: + if state == "r": + self.map[f] = ('r', 0, 0, 0) + else: + s = os.lstat(self.wjoin(f)) + st_size = kw.get('st_size', s.st_size) + st_mtime = kw.get('st_mtime', s.st_mtime) + self.map[f] = (state, s.st_mode, st_size, st_mtime) + if self.copies.has_key(f): + del self.copies[f] + + def forget(self, files): + if not files: return + self.lazyread() + self.markdirty() + for f in files: + try: + del self.map[f] + except KeyError: + self.ui.warn(_("not in dirstate: %s!\n") % f) + pass + + def clear(self): + self.map = {} + self.copies = {} + self.markdirty() + + def rebuild(self, parent, files): + self.clear() + umask = os.umask(0) + os.umask(umask) + for f, mode in files: + if mode: + self.map[f] = ('n', ~umask, -1, 0) + else: + self.map[f] = ('n', ~umask & 0666, -1, 0) + self.pl = (parent, nullid) + self.markdirty() + + def write(self): + if not self.dirty: + return + st = self.opener("dirstate", "w", atomic=True) + st.write("".join(self.pl)) + for f, e in self.map.items(): + c = self.copied(f) + if c: + f = f + "\0" + c + e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f)) + st.write(e + f) + self.dirty = 0 + + def filterfiles(self, files): + ret = {} + unknown = [] + + for x in files: + if x == '.': + return self.map.copy() + if x not in self.map: + unknown.append(x) + else: + ret[x] = self.map[x] + + if not unknown: + return ret + + b = self.map.keys() + b.sort() + blen = len(b) + + for x in unknown: + bs = bisect.bisect(b, x) + if bs != 0 and b[bs-1] == x: + ret[x] = self.map[x] + continue + while bs < blen: + s = b[bs] + if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/': + ret[s] = self.map[s] + else: + break + bs += 1 + return ret + + def supported_type(self, f, st, verbose=False): + if stat.S_ISREG(st.st_mode): + return True + if verbose: + kind = 'unknown' + if stat.S_ISCHR(st.st_mode): kind = _('character device') + elif stat.S_ISBLK(st.st_mode): kind = _('block device') + elif stat.S_ISFIFO(st.st_mode): kind = _('fifo') + elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link') + elif stat.S_ISSOCK(st.st_mode): kind = _('socket') + elif stat.S_ISDIR(st.st_mode): kind = _('directory') + self.ui.warn(_('%s: unsupported file type (type is %s)\n') % ( + util.pathto(self.getcwd(), f), + kind)) + return False + + def statwalk(self, files=None, match=util.always, dc=None, ignored=False, + badmatch=None): + self.lazyread() + + # walk all files by default + if not files: + files = [self.root] + if not dc: + dc = self.map.copy() + elif not dc: + dc = self.filterfiles(files) + + def statmatch(file_, stat): + file_ = util.pconvert(file_) + if not ignored and file_ not in dc and self.ignore(file_): + return False + return match(file_) + + return self.walkhelper(files=files, statmatch=statmatch, dc=dc, + badmatch=badmatch) + + def walk(self, files=None, match=util.always, dc=None, badmatch=None): + # filter out the stat + for src, f, st in self.statwalk(files, match, dc, badmatch=badmatch): + yield src, f + + # walk recursively through the directory tree, finding all files + # matched by the statmatch function + # + # results are yielded in a tuple (src, filename, st), where src + # is one of: + # 'f' the file was found in the directory tree + # 'm' the file was only in the dirstate and not in the tree + # and st is the stat result if the file was found in the directory. + # + # dc is an optional arg for the current dirstate. dc is not modified + # directly by this function, but might be modified by your statmatch call. + # + def walkhelper(self, files, statmatch, dc, badmatch=None): + # recursion free walker, faster than os.walk. + def findfiles(s): + work = [s] + while work: + top = work.pop() + names = os.listdir(top) + names.sort() + # nd is the top of the repository dir tree + nd = util.normpath(top[len(self.root) + 1:]) + if nd == '.': + nd = '' + else: + # do not recurse into a repo contained in this + # one. use bisect to find .hg directory so speed + # is good on big directory. + hg = bisect.bisect_left(names, '.hg') + if hg < len(names) and names[hg] == '.hg': + if os.path.isdir(os.path.join(top, '.hg')): + continue + for f in names: + np = util.pconvert(os.path.join(nd, f)) + if seen(np): + continue + p = os.path.join(top, f) + # don't trip over symlinks + st = os.lstat(p) + if stat.S_ISDIR(st.st_mode): + ds = os.path.join(nd, f +'/') + if statmatch(ds, st): + work.append(p) + if statmatch(np, st) and np in dc: + yield 'm', np, st + elif statmatch(np, st): + if self.supported_type(np, st): + yield 'f', np, st + elif np in dc: + yield 'm', np, st + + known = {'.hg': 1} + def seen(fn): + if fn in known: return True + known[fn] = 1 + + # step one, find all files that match our criteria + files.sort() + for ff in util.unique(files): + f = self.wjoin(ff) + try: + st = os.lstat(f) + except OSError, inst: + nf = util.normpath(ff) + found = False + for fn in dc: + if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'): + found = True + break + if not found: + if inst.errno != errno.ENOENT or not badmatch: + self.ui.warn('%s: %s\n' % ( + util.pathto(self.getcwd(), ff), + inst.strerror)) + elif badmatch and badmatch(ff) and statmatch(ff, None): + yield 'b', ff, None + continue + if stat.S_ISDIR(st.st_mode): + cmp1 = (lambda x, y: cmp(x[1], y[1])) + sorted_ = [ x for x in findfiles(f) ] + sorted_.sort(cmp1) + for e in sorted_: + yield e + else: + ff = util.normpath(ff) + if seen(ff): + continue + self.blockignore = True + if statmatch(ff, st): + if self.supported_type(ff, st, verbose=True): + yield 'f', ff, st + elif ff in dc: + yield 'm', ff, st + self.blockignore = False + + # step two run through anything left in the dc hash and yield + # if we haven't already seen it + ks = dc.keys() + ks.sort() + for k in ks: + if not seen(k) and (statmatch(k, None)): + yield 'm', k, None + + def changes(self, files=None, match=util.always, show_ignored=None): + lookup, modified, added, unknown, ignored = [], [], [], [], [] + removed, deleted = [], [] + + for src, fn, st in self.statwalk(files, match, ignored=show_ignored): + try: + type_, mode, size, time = self[fn] + except KeyError: + if show_ignored and self.ignore(fn): + ignored.append(fn) + else: + unknown.append(fn) + continue + if src == 'm': + nonexistent = True + if not st: + try: + f = self.wjoin(fn) + st = os.lstat(f) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + st = None + # We need to re-check that it is a valid file + if st and self.supported_type(fn, st): + nonexistent = False + # XXX: what to do with file no longer present in the fs + # who are not removed in the dirstate ? + if nonexistent and type_ in "nm": + deleted.append(fn) + continue + # check the common case first + if type_ == 'n': + if not st: + st = os.stat(fn) + if size >= 0 and (size != st.st_size + or (mode ^ st.st_mode) & 0100): + modified.append(fn) + elif time != st.st_mtime: + lookup.append(fn) + elif type_ == 'm': + modified.append(fn) + elif type_ == 'a': + added.append(fn) + elif type_ == 'r': + removed.append(fn) + + return (lookup, modified, added, removed, deleted, unknown, ignored)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/fancyopts.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,30 @@ +import getopt + +def fancyopts(args, options, state): + long = [] + short = '' + map = {} + dt = {} + + for s, l, d, c in options: + pl = l.replace('-', '_') + map['-'+s] = map['--'+l] = pl + state[pl] = d + dt[pl] = type(d) + if not d is None and not callable(d): + if s: s += ':' + if l: l += '=' + if s: short = short + s + if l: long.append(l) + + opts, args = getopt.getopt(args, short, long) + + for opt, arg in opts: + if dt[map[opt]] is type(fancyopts): state[map[opt]](state,map[opt],arg) + elif dt[map[opt]] is type(1): state[map[opt]] = int(arg) + elif dt[map[opt]] is type(''): state[map[opt]] = arg + elif dt[map[opt]] is type([]): state[map[opt]].append(arg) + elif dt[map[opt]] is type(None): state[map[opt]] = 1 + + return args +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/filelog.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,108 @@ +# filelog.py - file history class for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +from revlog import * +from demandload import * +demandload(globals(), "bdiff") + +class filelog(revlog): + def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION): + revlog.__init__(self, opener, + os.path.join("data", self.encodedir(path + ".i")), + os.path.join("data", self.encodedir(path + ".d")), + defversion) + + # This avoids a collision between a file named foo and a dir named + # foo.i or foo.d + def encodedir(self, path): + return (path + .replace(".hg/", ".hg.hg/") + .replace(".i/", ".i.hg/") + .replace(".d/", ".d.hg/")) + + def decodedir(self, path): + return (path + .replace(".d.hg/", ".d/") + .replace(".i.hg/", ".i/") + .replace(".hg.hg/", ".hg/")) + + def read(self, node): + t = self.revision(node) + if not t.startswith('\1\n'): + return t + s = t.find('\1\n', 2) + return t[s+2:] + + def readmeta(self, node): + t = self.revision(node) + if not t.startswith('\1\n'): + return {} + s = t.find('\1\n', 2) + mt = t[2:s] + m = {} + for l in mt.splitlines(): + k, v = l.split(": ", 1) + m[k] = v + return m + + def add(self, text, meta, transaction, link, p1=None, p2=None): + if meta or text.startswith('\1\n'): + mt = "" + if meta: + mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ] + text = "\1\n%s\1\n%s" % ("".join(mt), text) + return self.addrevision(text, transaction, link, p1, p2) + + def renamed(self, node): + if self.parents(node)[0] != nullid: + return False + m = self.readmeta(node) + if m and m.has_key("copy"): + return (m["copy"], bin(m["copyrev"])) + return False + + def annotate(self, node): + + def decorate(text, rev): + return ([rev] * len(text.splitlines()), text) + + def pair(parent, child): + for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]): + child[0][b1:b2] = parent[0][a1:a2] + return child + + # find all ancestors + needed = {node:1} + visit = [node] + while visit: + n = visit.pop(0) + for p in self.parents(n): + if p not in needed: + needed[p] = 1 + visit.append(p) + else: + # count how many times we'll use this + needed[p] += 1 + + # sort by revision which is a topological order + visit = [ (self.rev(n), n) for n in needed.keys() ] + visit.sort() + hist = {} + + for r,n in visit: + curr = decorate(self.read(n), self.linkrev(n)) + for p in self.parents(n): + if p != nullid: + curr = pair(hist[p], curr) + # trim the history of unneeded revs + needed[p] -= 1 + if not needed[p]: + del hist[p] + hist[n] = curr + + return zip(hist[n][0], hist[n][1].splitlines(1))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hg.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,36 @@ +# hg.py - repository classes for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from node import * +from repo import * +from demandload import * +demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo") + +def repository(ui, path=None, create=0): + if path: + if path.startswith("http://"): + return httprepo.httprepository(ui, path) + if path.startswith("https://"): + return httprepo.httpsrepository(ui, path) + if path.startswith("hg://"): + return httprepo.httprepository( + ui, path.replace("hg://", "http://")) + if path.startswith("old-http://"): + return statichttprepo.statichttprepository( + ui, path.replace("old-http://", "http://")) + if path.startswith("ssh://"): + return sshrepo.sshrepository(ui, path) + if path.startswith("bundle://"): + path = path[9:] + s = path.split("+", 1) + if len(s) == 1: + repopath, bundlename = "", s[0] + else: + repopath, bundlename = s + return bundlerepo.bundlerepository(ui, repopath, bundlename) + + return localrepo.localrepository(ui, path, create)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/__init__.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial.demandload import demandload +demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") +demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/common.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,42 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os, mimetypes +import os.path + +def get_mtime(repo_path): + hg_path = os.path.join(repo_path, ".hg") + cl_path = os.path.join(hg_path, "00changelog.i") + if os.path.exists(os.path.join(cl_path)): + return os.stat(cl_path).st_mtime + else: + return os.stat(hg_path).st_mtime + +def staticfile(directory, fname): + """return a file inside directory with guessed content-type header + + fname always uses '/' as directory separator and isn't allowed to + contain unusual path components. + Content-type is guessed using the mimetypes module. + Return an empty string if fname is illegal or file not found. + + """ + parts = fname.split('/') + path = directory + for part in parts: + if (part in ('', os.curdir, os.pardir) or + os.sep in part or os.altsep is not None and os.altsep in part): + return "" + path = os.path.join(path, part) + try: + os.stat(path) + ct = mimetypes.guess_type(path)[0] or "text/plain" + return "Content-type: %s\n\n%s" % (ct, file(path).read()) + except (TypeError, OSError): + # illegal fname or unreadable file + return ""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/hgweb_mod.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,822 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +import os.path +import mimetypes +from mercurial.demandload import demandload +demandload(globals(), "re zlib ConfigParser") +demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") +demandload(globals(), "mercurial.hgweb.request:hgrequest") +demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") +from mercurial.node import * +from mercurial.i18n import gettext as _ + +def _up(p): + if p[0] != "/": + p = "/" + p + if p[-1] == "/": + p = p[:-1] + up = os.path.dirname(p) + if up == "/": + return "/" + return up + "/" + +class hgweb(object): + def __init__(self, repo, name=None): + if type(repo) == type(""): + self.repo = hg.repository(ui.ui(), repo) + else: + self.repo = repo + + self.mtime = -1 + self.reponame = name + self.archives = 'zip', 'gz', 'bz2' + + def refresh(self): + mtime = get_mtime(self.repo.root) + if mtime != self.mtime: + self.mtime = mtime + self.repo = hg.repository(self.repo.ui, self.repo.root) + self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10)) + self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10)) + self.allowpull = self.repo.ui.configbool("web", "allowpull", True) + + def archivelist(self, nodeid): + allowed = (self.repo.ui.config("web", "allow_archive", "") + .replace(",", " ").split()) + for i in self.archives: + if i in allowed or self.repo.ui.configbool("web", "allow" + i): + yield {"type" : i, "node" : nodeid, "url": ""} + + def listfiles(self, files, mf): + for f in files[:self.maxfiles]: + yield self.t("filenodelink", node=hex(mf[f]), file=f) + if len(files) > self.maxfiles: + yield self.t("fileellipses") + + def listfilediffs(self, files, changeset): + for f in files[:self.maxfiles]: + yield self.t("filedifflink", node=hex(changeset), file=f) + if len(files) > self.maxfiles: + yield self.t("fileellipses") + + def siblings(self, siblings=[], rev=None, hiderev=None, **args): + if not rev: + rev = lambda x: "" + siblings = [s for s in siblings if s != nullid] + if len(siblings) == 1 and rev(siblings[0]) == hiderev: + return + for s in siblings: + yield dict(node=hex(s), rev=rev(s), **args) + + def renamelink(self, fl, node): + r = fl.renamed(node) + if r: + return [dict(file=r[0], node=hex(r[1]))] + return [] + + def showtag(self, t1, node=nullid, **args): + for t in self.repo.nodetags(node): + yield self.t(t1, tag=t, **args) + + def diff(self, node1, node2, files): + def filterfiles(filters, files): + l = [x for x in files if x in filters] + + for t in filters: + if t and t[-1] != os.sep: + t += os.sep + l += [x for x in files if x.startswith(t)] + return l + + parity = [0] + def diffblock(diff, f, fn): + yield self.t("diffblock", + lines=prettyprintlines(diff), + parity=parity[0], + file=f, + filenode=hex(fn or nullid)) + parity[0] = 1 - parity[0] + + def prettyprintlines(diff): + for l in diff.splitlines(1): + if l.startswith('+'): + yield self.t("difflineplus", line=l) + elif l.startswith('-'): + yield self.t("difflineminus", line=l) + elif l.startswith('@'): + yield self.t("difflineat", line=l) + else: + yield self.t("diffline", line=l) + + r = self.repo + cl = r.changelog + mf = r.manifest + change1 = cl.read(node1) + change2 = cl.read(node2) + mmap1 = mf.read(change1[0]) + mmap2 = mf.read(change2[0]) + date1 = util.datestr(change1[2]) + date2 = util.datestr(change2[2]) + + modified, added, removed, deleted, unknown = r.changes(node1, node2) + if files: + modified, added, removed = map(lambda x: filterfiles(files, x), + (modified, added, removed)) + + diffopts = self.repo.ui.diffopts() + showfunc = diffopts['showfunc'] + ignorews = diffopts['ignorews'] + for f in modified: + to = r.file(f).read(mmap1[f]) + tn = r.file(f).read(mmap2[f]) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) + for f in added: + to = None + tn = r.file(f).read(mmap2[f]) + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) + for f in removed: + to = r.file(f).read(mmap1[f]) + tn = None + yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, + showfunc=showfunc, ignorews=ignorews), f, tn) + + def changelog(self, pos): + def changenav(**map): + def seq(factor, maxchanges=None): + if maxchanges: + yield maxchanges + if maxchanges >= 20 and maxchanges <= 40: + yield 50 + else: + yield 1 * factor + yield 3 * factor + for f in seq(factor * 10): + yield f + + l = [] + last = 0 + for f in seq(1, self.maxchanges): + if f < self.maxchanges or f <= last: + continue + if f > count: + break + last = f + r = "%d" % f + if pos + f < count: + l.append(("+" + r, pos + f)) + if pos - f >= 0: + l.insert(0, ("-" + r, pos - f)) + + yield {"rev": 0, "label": "(0)"} + + for label, rev in l: + yield {"label": label, "rev": rev} + + yield {"label": "tip", "rev": "tip"} + + def changelist(**map): + parity = (start - end) & 1 + cl = self.repo.changelog + l = [] # build a list in forward order for efficiency + for i in range(start, end): + n = cl.node(i) + changes = cl.read(n) + hn = hex(n) + + l.insert(0, {"parity": parity, + "author": changes[1], + "parent": self.siblings(cl.parents(n), cl.rev, + cl.rev(n) - 1), + "child": self.siblings(cl.children(n), cl.rev, + cl.rev(n) + 1), + "changelogtag": self.showtag("changelogtag",n), + "manifest": hex(changes[0]), + "desc": changes[4], + "date": changes[2], + "files": self.listfilediffs(changes[3], n), + "rev": i, + "node": hn}) + parity = 1 - parity + + for e in l: + yield e + + cl = self.repo.changelog + mf = cl.read(cl.tip())[0] + count = cl.count() + start = max(0, pos - self.maxchanges + 1) + end = min(count, start + self.maxchanges) + pos = end - 1 + + yield self.t('changelog', + changenav=changenav, + manifest=hex(mf), + rev=pos, changesets=count, entries=changelist, + archives=self.archivelist("tip")) + + def search(self, query): + + def changelist(**map): + cl = self.repo.changelog + count = 0 + qw = query.lower().split() + + def revgen(): + for i in range(cl.count() - 1, 0, -100): + l = [] + for j in range(max(0, i - 100), i): + n = cl.node(j) + changes = cl.read(n) + l.append((n, j, changes)) + l.reverse() + for e in l: + yield e + + for n, i, changes in revgen(): + miss = 0 + for q in qw: + if not (q in changes[1].lower() or + q in changes[4].lower() or + q in " ".join(changes[3][:20]).lower()): + miss = 1 + break + if miss: + continue + + count += 1 + hn = hex(n) + + yield self.t('searchentry', + parity=count & 1, + author=changes[1], + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), + changelogtag=self.showtag("changelogtag",n), + manifest=hex(changes[0]), + desc=changes[4], + date=changes[2], + files=self.listfilediffs(changes[3], n), + rev=i, + node=hn) + + if count >= self.maxchanges: + break + + cl = self.repo.changelog + mf = cl.read(cl.tip())[0] + + yield self.t('search', + query=query, + manifest=hex(mf), + entries=changelist) + + def changeset(self, nodeid): + cl = self.repo.changelog + n = self.repo.lookup(nodeid) + nodeid = hex(n) + changes = cl.read(n) + p1 = cl.parents(n)[0] + + files = [] + mf = self.repo.manifest.read(changes[0]) + for f in changes[3]: + files.append(self.t("filenodelink", + filenode=hex(mf.get(f, nullid)), file=f)) + + def diff(**map): + yield self.diff(p1, n, None) + + yield self.t('changeset', + diff=diff, + rev=cl.rev(n), + node=nodeid, + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), + changesettag=self.showtag("changesettag",n), + manifest=hex(changes[0]), + author=changes[1], + desc=changes[4], + date=changes[2], + files=files, + archives=self.archivelist(nodeid)) + + def filelog(self, f, filenode): + cl = self.repo.changelog + fl = self.repo.file(f) + filenode = hex(fl.lookup(filenode)) + count = fl.count() + + def entries(**map): + l = [] + parity = (count - 1) & 1 + + for i in range(count): + n = fl.node(i) + lr = fl.linkrev(n) + cn = cl.node(lr) + cs = cl.read(cl.node(lr)) + + l.insert(0, {"parity": parity, + "filenode": hex(n), + "filerev": i, + "file": f, + "node": hex(cn), + "author": cs[1], + "date": cs[2], + "rename": self.renamelink(fl, n), + "parent": self.siblings(fl.parents(n), + fl.rev, file=f), + "child": self.siblings(fl.children(n), + fl.rev, file=f), + "desc": cs[4]}) + parity = 1 - parity + + for e in l: + yield e + + yield self.t("filelog", file=f, filenode=filenode, entries=entries) + + def filerevision(self, f, node): + fl = self.repo.file(f) + n = fl.lookup(node) + node = hex(n) + text = fl.read(n) + changerev = fl.linkrev(n) + cl = self.repo.changelog + cn = cl.node(changerev) + cs = cl.read(cn) + mfn = cs[0] + + mt = mimetypes.guess_type(f)[0] + rawtext = text + if util.binary(text): + mt = mt or 'application/octet-stream' + text = "(binary:%s)" % mt + mt = mt or 'text/plain' + + def lines(): + for l, t in enumerate(text.splitlines(1)): + yield {"line": t, + "linenumber": "% 6d" % (l + 1), + "parity": l & 1} + + yield self.t("filerevision", + file=f, + filenode=node, + path=_up(f), + text=lines(), + raw=rawtext, + mimetype=mt, + rev=changerev, + node=hex(cn), + manifest=hex(mfn), + author=cs[1], + date=cs[2], + parent=self.siblings(fl.parents(n), fl.rev, file=f), + child=self.siblings(fl.children(n), fl.rev, file=f), + rename=self.renamelink(fl, n), + permissions=self.repo.manifest.readflags(mfn)[f]) + + def fileannotate(self, f, node): + bcache = {} + ncache = {} + fl = self.repo.file(f) + n = fl.lookup(node) + node = hex(n) + changerev = fl.linkrev(n) + + cl = self.repo.changelog + cn = cl.node(changerev) + cs = cl.read(cn) + mfn = cs[0] + + def annotate(**map): + parity = 1 + last = None + for r, l in fl.annotate(n): + try: + cnode = ncache[r] + except KeyError: + cnode = ncache[r] = self.repo.changelog.node(r) + + try: + name = bcache[r] + except KeyError: + cl = self.repo.changelog.read(cnode) + bcache[r] = name = self.repo.ui.shortuser(cl[1]) + + if last != cnode: + parity = 1 - parity + last = cnode + + yield {"parity": parity, + "node": hex(cnode), + "rev": r, + "author": name, + "file": f, + "line": l} + + yield self.t("fileannotate", + file=f, + filenode=node, + annotate=annotate, + path=_up(f), + rev=changerev, + node=hex(cn), + manifest=hex(mfn), + author=cs[1], + date=cs[2], + rename=self.renamelink(fl, n), + parent=self.siblings(fl.parents(n), fl.rev, file=f), + child=self.siblings(fl.children(n), fl.rev, file=f), + permissions=self.repo.manifest.readflags(mfn)[f]) + + def manifest(self, mnode, path): + man = self.repo.manifest + mn = man.lookup(mnode) + mnode = hex(mn) + mf = man.read(mn) + rev = man.rev(mn) + changerev = man.linkrev(mn) + node = self.repo.changelog.node(changerev) + mff = man.readflags(mn) + + files = {} + + p = path[1:] + if p and p[-1] != "/": + p += "/" + l = len(p) + + for f,n in mf.items(): + if f[:l] != p: + continue + remain = f[l:] + if "/" in remain: + short = remain[:remain.find("/") + 1] # bleah + files[short] = (f, None) + else: + short = os.path.basename(remain) + files[short] = (f, n) + + def filelist(**map): + parity = 0 + fl = files.keys() + fl.sort() + for f in fl: + full, fnode = files[f] + if not fnode: + continue + + yield {"file": full, + "manifest": mnode, + "filenode": hex(fnode), + "parity": parity, + "basename": f, + "permissions": mff[full]} + parity = 1 - parity + + def dirlist(**map): + parity = 0 + fl = files.keys() + fl.sort() + for f in fl: + full, fnode = files[f] + if fnode: + continue + + yield {"parity": parity, + "path": os.path.join(path, f), + "manifest": mnode, + "basename": f[:-1]} + parity = 1 - parity + + yield self.t("manifest", + manifest=mnode, + rev=rev, + node=hex(node), + path=path, + up=_up(path), + fentries=filelist, + dentries=dirlist, + archives=self.archivelist(hex(node))) + + def tags(self): + cl = self.repo.changelog + mf = cl.read(cl.tip())[0] + + i = self.repo.tagslist() + i.reverse() + + def entries(notip=False, **map): + parity = 0 + for k,n in i: + if notip and k == "tip": continue + yield {"parity": parity, + "tag": k, + "tagmanifest": hex(cl.read(n)[0]), + "date": cl.read(n)[2], + "node": hex(n)} + parity = 1 - parity + + yield self.t("tags", + manifest=hex(mf), + entries=lambda **x: entries(False, **x), + entriesnotip=lambda **x: entries(True, **x)) + + def summary(self): + cl = self.repo.changelog + mf = cl.read(cl.tip())[0] + + i = self.repo.tagslist() + i.reverse() + + def tagentries(**map): + parity = 0 + count = 0 + for k,n in i: + if k == "tip": # skip tip + continue; + + count += 1 + if count > 10: # limit to 10 tags + break; + + c = cl.read(n) + m = c[0] + t = c[2] + + yield self.t("tagentry", + parity = parity, + tag = k, + node = hex(n), + date = t, + tagmanifest = hex(m)) + parity = 1 - parity + + def changelist(**map): + parity = 0 + cl = self.repo.changelog + l = [] # build a list in forward order for efficiency + for i in range(start, end): + n = cl.node(i) + changes = cl.read(n) + hn = hex(n) + t = changes[2] + + l.insert(0, self.t( + 'shortlogentry', + parity = parity, + author = changes[1], + manifest = hex(changes[0]), + desc = changes[4], + date = t, + rev = i, + node = hn)) + parity = 1 - parity + + yield l + + cl = self.repo.changelog + mf = cl.read(cl.tip())[0] + count = cl.count() + start = max(0, count - self.maxchanges) + end = min(count, start + self.maxchanges) + pos = end - 1 + + yield self.t("summary", + desc = self.repo.ui.config("web", "description", "unknown"), + owner = (self.repo.ui.config("ui", "username") or # preferred + self.repo.ui.config("web", "contact") or # deprecated + self.repo.ui.config("web", "author", "unknown")), # also + lastchange = (0, 0), # FIXME + manifest = hex(mf), + tags = tagentries, + shortlog = changelist) + + def filediff(self, file, changeset): + cl = self.repo.changelog + n = self.repo.lookup(changeset) + changeset = hex(n) + p1 = cl.parents(n)[0] + cs = cl.read(n) + mf = self.repo.manifest.read(cs[0]) + + def diff(**map): + yield self.diff(p1, n, [file]) + + yield self.t("filediff", + file=file, + filenode=hex(mf.get(file, nullid)), + node=changeset, + rev=self.repo.changelog.rev(n), + parent=self.siblings(cl.parents(n), cl.rev), + child=self.siblings(cl.children(n), cl.rev), + diff=diff) + + archive_specs = { + 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), + 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), + 'zip': ('application/zip', 'zip', '.zip', None), + } + + def archive(self, req, cnode, type): + reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) + name = "%s-%s" % (reponame, short(cnode)) + mimetype, artype, extension, encoding = self.archive_specs[type] + headers = [('Content-type', mimetype), + ('Content-disposition', 'attachment; filename=%s%s' % + (name, extension))] + if encoding: + headers.append(('Content-encoding', encoding)) + req.header(headers) + archival.archive(self.repo, req.out, cnode, artype, prefix=name) + + # add tags to things + # tags -> list of changesets corresponding to tags + # find tag, changeset, file + + def run(self, req=hgrequest()): + def clean(path): + p = util.normpath(path) + if p[:2] == "..": + raise "suspicious path" + return p + + def header(**map): + yield self.t("header", **map) + + def footer(**map): + yield self.t("footer", + motd=self.repo.ui.config("web", "motd", ""), + **map) + + def expand_form(form): + shortcuts = { + 'cl': [('cmd', ['changelog']), ('rev', None)], + 'cs': [('cmd', ['changeset']), ('node', None)], + 'f': [('cmd', ['file']), ('filenode', None)], + 'fl': [('cmd', ['filelog']), ('filenode', None)], + 'fd': [('cmd', ['filediff']), ('node', None)], + 'fa': [('cmd', ['annotate']), ('filenode', None)], + 'mf': [('cmd', ['manifest']), ('manifest', None)], + 'ca': [('cmd', ['archive']), ('node', None)], + 'tags': [('cmd', ['tags'])], + 'tip': [('cmd', ['changeset']), ('node', ['tip'])], + 'static': [('cmd', ['static']), ('file', None)] + } + + for k in shortcuts.iterkeys(): + if form.has_key(k): + for name, value in shortcuts[k]: + if value is None: + value = form[k] + form[name] = value + del form[k] + + self.refresh() + + expand_form(req.form) + + t = self.repo.ui.config("web", "templates", templater.templatepath()) + static = self.repo.ui.config("web", "static", os.path.join(t,"static")) + m = os.path.join(t, "map") + style = self.repo.ui.config("web", "style", "") + if req.form.has_key('style'): + style = req.form['style'][0] + if style: + b = os.path.basename("map-" + style) + p = os.path.join(t, b) + if os.path.isfile(p): + m = p + + port = req.env["SERVER_PORT"] + port = port != "80" and (":" + port) or "" + uri = req.env["REQUEST_URI"] + if "?" in uri: + uri = uri.split("?")[0] + url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri) + if not self.reponame: + self.reponame = (self.repo.ui.config("web", "name") + or uri.strip('/') or self.repo.root) + + self.t = templater.templater(m, templater.common_filters, + defaults={"url": url, + "repo": self.reponame, + "header": header, + "footer": footer, + }) + + if not req.form.has_key('cmd'): + req.form['cmd'] = [self.t.cache['default'],] + + cmd = req.form['cmd'][0] + if cmd == 'changelog': + hi = self.repo.changelog.count() - 1 + if req.form.has_key('rev'): + hi = req.form['rev'][0] + try: + hi = self.repo.changelog.rev(self.repo.lookup(hi)) + except hg.RepoError: + req.write(self.search(hi)) # XXX redirect to 404 page? + return + + req.write(self.changelog(hi)) + + elif cmd == 'changeset': + req.write(self.changeset(req.form['node'][0])) + + elif cmd == 'manifest': + req.write(self.manifest(req.form['manifest'][0], + clean(req.form['path'][0]))) + + elif cmd == 'tags': + req.write(self.tags()) + + elif cmd == 'summary': + req.write(self.summary()) + + elif cmd == 'filediff': + req.write(self.filediff(clean(req.form['file'][0]), + req.form['node'][0])) + + elif cmd == 'file': + req.write(self.filerevision(clean(req.form['file'][0]), + req.form['filenode'][0])) + + elif cmd == 'annotate': + req.write(self.fileannotate(clean(req.form['file'][0]), + req.form['filenode'][0])) + + elif cmd == 'filelog': + req.write(self.filelog(clean(req.form['file'][0]), + req.form['filenode'][0])) + + elif cmd == 'heads': + req.httphdr("application/mercurial-0.1") + h = self.repo.heads() + req.write(" ".join(map(hex, h)) + "\n") + + elif cmd == 'branches': + req.httphdr("application/mercurial-0.1") + nodes = [] + if req.form.has_key('nodes'): + nodes = map(bin, req.form['nodes'][0].split(" ")) + for b in self.repo.branches(nodes): + req.write(" ".join(map(hex, b)) + "\n") + + elif cmd == 'between': + req.httphdr("application/mercurial-0.1") + nodes = [] + if req.form.has_key('pairs'): + pairs = [map(bin, p.split("-")) + for p in req.form['pairs'][0].split(" ")] + for b in self.repo.between(pairs): + req.write(" ".join(map(hex, b)) + "\n") + + elif cmd == 'changegroup': + req.httphdr("application/mercurial-0.1") + nodes = [] + if not self.allowpull: + return + + if req.form.has_key('roots'): + nodes = map(bin, req.form['roots'][0].split(" ")) + + z = zlib.compressobj() + f = self.repo.changegroup(nodes, 'serve') + while 1: + chunk = f.read(4096) + if not chunk: + break + req.write(z.compress(chunk)) + + req.write(z.flush()) + + elif cmd == 'archive': + changeset = self.repo.lookup(req.form['node'][0]) + type = req.form['type'][0] + allowed = self.repo.ui.config("web", "allow_archive", "").split() + if (type in self.archives and (type in allowed or + self.repo.ui.configbool("web", "allow" + type, False))): + self.archive(req, changeset, type) + return + + req.write(self.t("error")) + + elif cmd == 'static': + fname = req.form['file'][0] + req.write(staticfile(static, fname) + or self.t("error", error="%r not found" % fname)) + + else: + req.write(self.t("error"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/hgwebdir_mod.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,156 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +from mercurial.demandload import demandload +demandload(globals(), "ConfigParser") +demandload(globals(), "mercurial:ui,hg,util,templater") +demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") +demandload(globals(), "mercurial.hgweb.request:hgrequest") +demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") +from mercurial.i18n import gettext as _ + +# This is a stopgap +class hgwebdir(object): + def __init__(self, config): + def cleannames(items): + return [(name.strip(os.sep), path) for name, path in items] + + self.motd = "" + self.repos_sorted = ('name', False) + if isinstance(config, (list, tuple)): + self.repos = cleannames(config) + self.repos_sorted = ('', False) + elif isinstance(config, dict): + self.repos = cleannames(config.items()) + self.repos.sort() + else: + cp = ConfigParser.SafeConfigParser() + cp.read(config) + self.repos = [] + if cp.has_section('web') and cp.has_option('web', 'motd'): + self.motd = cp.get('web', 'motd') + if cp.has_section('paths'): + self.repos.extend(cleannames(cp.items('paths'))) + if cp.has_section('collections'): + for prefix, root in cp.items('collections'): + for path in util.walkrepos(root): + repo = os.path.normpath(path) + name = repo + if name.startswith(prefix): + name = name[len(prefix):] + self.repos.append((name.lstrip(os.sep), repo)) + self.repos.sort() + + def run(self, req=hgrequest()): + def header(**map): + yield tmpl("header", **map) + + def footer(**map): + yield tmpl("footer", motd=self.motd, **map) + + m = os.path.join(templater.templatepath(), "map") + tmpl = templater.templater(m, templater.common_filters, + defaults={"header": header, + "footer": footer}) + + def archivelist(ui, nodeid, url): + allowed = (ui.config("web", "allow_archive", "") + .replace(",", " ").split()) + for i in ['zip', 'gz', 'bz2']: + if i in allowed or ui.configbool("web", "allow" + i): + yield {"type" : i, "node": nodeid, "url": url} + + def entries(sortcolumn="", descending=False, **map): + rows = [] + parity = 0 + for name, path in self.repos: + u = ui.ui() + try: + u.readconfig(os.path.join(path, '.hg', 'hgrc')) + except IOError: + pass + get = u.config + + url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name]) + .replace("//", "/")) + + # update time with local timezone + try: + d = (get_mtime(path), util.makedate()[1]) + except OSError: + continue + + contact = (get("ui", "username") or # preferred + get("web", "contact") or # deprecated + get("web", "author", "")) # also + description = get("web", "description", "") + name = get("web", "name", name) + row = dict(contact=contact or "unknown", + contact_sort=contact.upper() or "unknown", + name=name, + name_sort=name, + url=url, + description=description or "unknown", + description_sort=description.upper() or "unknown", + lastchange=d, + lastchange_sort=d[1]-d[0], + archives=archivelist(u, "tip", url)) + if (not sortcolumn + or (sortcolumn, descending) == self.repos_sorted): + # fast path for unsorted output + row['parity'] = parity + parity = 1 - parity + yield row + else: + rows.append((row["%s_sort" % sortcolumn], row)) + if rows: + rows.sort() + if descending: + rows.reverse() + for key, row in rows: + row['parity'] = parity + parity = 1 - parity + yield row + + virtual = req.env.get("PATH_INFO", "").strip('/') + if virtual: + real = dict(self.repos).get(virtual) + if real: + try: + hgweb(real).run(req) + except IOError, inst: + req.write(tmpl("error", error=inst.strerror)) + except hg.RepoError, inst: + req.write(tmpl("error", error=str(inst))) + else: + req.write(tmpl("notfound", repo=virtual)) + else: + if req.form.has_key('static'): + static = os.path.join(templater.templatepath(), "static") + fname = req.form['static'][0] + req.write(staticfile(static, fname) + or tmpl("error", error="%r not found" % fname)) + else: + sortable = ["name", "description", "contact", "lastchange"] + sortcolumn, descending = self.repos_sorted + if req.form.has_key('sort'): + sortcolumn = req.form['sort'][0] + descending = sortcolumn.startswith('-') + if descending: + sortcolumn = sortcolumn[1:] + if sortcolumn not in sortable: + sortcolumn = "" + + sort = [("sort_%s" % column, + "%s%s" % ((not descending and column == sortcolumn) + and "-" or "", column)) + for column in sortable] + req.write(tmpl("index", entries=entries, + sortcolumn=sortcolumn, descending=descending, + **dict(sort)))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/request.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,44 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial.demandload import demandload +demandload(globals(), "socket sys cgi os") +from mercurial.i18n import gettext as _ + +class hgrequest(object): + def __init__(self, inp=None, out=None, env=None): + self.inp = inp or sys.stdin + self.out = out or sys.stdout + self.env = env or os.environ + self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) + + def write(self, *things): + for thing in things: + if hasattr(thing, "__iter__"): + for part in thing: + self.write(part) + else: + try: + self.out.write(str(thing)) + except socket.error, inst: + if inst[0] != errno.ECONNRESET: + raise + + def header(self, headers=[('Content-type','text/html')]): + for header in headers: + self.out.write("%s: %s\r\n" % header) + self.out.write("\r\n") + + def httphdr(self, type, file="", size=0): + + headers = [('Content-type', type)] + if file: + headers.append(('Content-disposition', 'attachment; filename=%s' % file)) + if size > 0: + headers.append(('Content-length', str(size))) + self.header(headers)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/hgweb/server.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,150 @@ +# hgweb.py - web interface to a mercurial repository +# +# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from mercurial.demandload import demandload +import os, sys, errno +demandload(globals(), "urllib BaseHTTPServer socket SocketServer") +demandload(globals(), "mercurial:ui,hg,util,templater") +demandload(globals(), "mercurial.hgweb.request:hgrequest") +from mercurial.i18n import gettext as _ + +def _splitURI(uri): + """ Return path and query splited from uri + + Just like CGI environment, the path is unquoted, the query is + not. + """ + if '?' in uri: + path, query = uri.split('?', 1) + else: + path, query = uri, '' + return urllib.unquote(path), query + +class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): + def __init__(self, *args, **kargs): + BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs) + + def log_error(self, format, *args): + errorlog = self.server.errorlog + errorlog.write("%s - - [%s] %s\n" % (self.address_string(), + self.log_date_time_string(), + format % args)) + + def log_message(self, format, *args): + accesslog = self.server.accesslog + accesslog.write("%s - - [%s] %s\n" % (self.address_string(), + self.log_date_time_string(), + format % args)) + + def do_POST(self): + try: + self.do_hgweb() + except socket.error, inst: + if inst[0] != errno.EPIPE: + raise + + def do_GET(self): + self.do_POST() + + def do_hgweb(self): + path_info, query = _splitURI(self.path) + + env = {} + env['GATEWAY_INTERFACE'] = 'CGI/1.1' + env['REQUEST_METHOD'] = self.command + env['SERVER_NAME'] = self.server.server_name + env['SERVER_PORT'] = str(self.server.server_port) + env['REQUEST_URI'] = "/" + env['PATH_INFO'] = path_info + if query: + env['QUERY_STRING'] = query + host = self.address_string() + if host != self.client_address[0]: + env['REMOTE_HOST'] = host + env['REMOTE_ADDR'] = self.client_address[0] + + if self.headers.typeheader is None: + env['CONTENT_TYPE'] = self.headers.type + else: + env['CONTENT_TYPE'] = self.headers.typeheader + length = self.headers.getheader('content-length') + if length: + env['CONTENT_LENGTH'] = length + accept = [] + for line in self.headers.getallmatchingheaders('accept'): + if line[:1] in "\t\n\r ": + accept.append(line.strip()) + else: + accept = accept + line[7:].split(',') + env['HTTP_ACCEPT'] = ','.join(accept) + + req = hgrequest(self.rfile, self.wfile, env) + self.send_response(200, "Script output follows") + self.server.make_and_run_handler(req) + +def create_server(ui, repo, webdirmaker, repoviewmaker): + use_threads = True + + def openlog(opt, default): + if opt and opt != '-': + return open(opt, 'w') + return default + + address = ui.config("web", "address", "") + port = int(ui.config("web", "port", 8000)) + use_ipv6 = ui.configbool("web", "ipv6") + webdir_conf = ui.config("web", "webdir_conf") + accesslog = openlog(ui.config("web", "accesslog", "-"), sys.stdout) + errorlog = openlog(ui.config("web", "errorlog", "-"), sys.stderr) + + if use_threads: + try: + from threading import activeCount + except ImportError: + use_threads = False + + if use_threads: + _mixin = SocketServer.ThreadingMixIn + else: + if hasattr(os, "fork"): + _mixin = SocketServer.ForkingMixIn + else: + class _mixin: pass + + class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer): + def __init__(self, *args, **kargs): + BaseHTTPServer.HTTPServer.__init__(self, *args, **kargs) + self.accesslog = accesslog + self.errorlog = errorlog + self.repo = repo + self.webdir_conf = webdir_conf + self.webdirmaker = webdirmaker + self.repoviewmaker = repoviewmaker + + def make_and_run_handler(self, req): + if self.webdir_conf: + hgwebobj = self.webdirmaker(self.server.webdir_conf) + elif self.repo is not None: + hgwebobj = self.repoviewmaker(repo.__class__(repo.ui, + repo.origroot)) + else: + raise hg.RepoError(_('no repo found')) + hgwebobj.run(req) + + class IPv6HTTPServer(MercurialHTTPServer): + address_family = getattr(socket, 'AF_INET6', None) + + def __init__(self, *args, **kwargs): + if self.address_family is None: + raise hg.RepoError(_('IPv6 not available on this system')) + super(IPv6HTTPServer, self).__init__(*args, **kargs) + + if use_ipv6: + return IPv6HTTPServer((address, port), _hgwebhandler) + else: + return MercurialHTTPServer((address, port), _hgwebhandler)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httprangereader.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,28 @@ +# httprangereader.py - just what it says +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import byterange, urllib2 + +class httprangereader(object): + def __init__(self, url): + self.url = url + self.pos = 0 + def seek(self, pos): + self.pos = pos + def read(self, bytes=None): + opener = urllib2.build_opener(byterange.HTTPRangeHandler()) + urllib2.install_opener(opener) + req = urllib2.Request(self.url) + end = '' + if bytes: + end = self.pos + bytes - 1 + req.add_header('Range', 'bytes=%d-%s' % (self.pos, end)) + f = urllib2.urlopen(req) + data = f.read() + if bytes: + data = data[:bytes] + return data
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/httprepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,226 @@ +# httprepo.py - HTTP repository proxy classes for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from node import * +from remoterepo import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib") + +class passwordmgr(urllib2.HTTPPasswordMgr): + def __init__(self, ui): + urllib2.HTTPPasswordMgr.__init__(self) + self.ui = ui + + def find_user_password(self, realm, authuri): + authinfo = urllib2.HTTPPasswordMgr.find_user_password( + self, realm, authuri) + if authinfo != (None, None): + return authinfo + + if not ui.interactive: + raise util.Abort(_('http authorization required')) + + self.ui.write(_("http authorization required\n")) + self.ui.status(_("realm: %s\n") % realm) + user = self.ui.prompt(_("user:"), default=None) + passwd = self.ui.getpass() + + self.add_password(realm, authuri, user, passwd) + return (user, passwd) + +def netlocsplit(netloc): + '''split [user[:passwd]@]host[:port] into 4-tuple.''' + + a = netloc.find('@') + if a == -1: + user, passwd = None, None + else: + userpass, netloc = netloc[:a], netloc[a+1:] + c = userpass.find(':') + if c == -1: + user, passwd = urllib.unquote(userpass), None + else: + user = urllib.unquote(userpass[:c]) + passwd = urllib.unquote(userpass[c+1:]) + c = netloc.find(':') + if c == -1: + host, port = netloc, None + else: + host, port = netloc[:c], netloc[c+1:] + return host, port, user, passwd + +def netlocunsplit(host, port, user=None, passwd=None): + '''turn host, port, user, passwd into [user[:passwd]@]host[:port].''' + if port: + hostport = host + ':' + port + else: + hostport = host + if user: + if passwd: + userpass = urllib.quote(user) + ':' + urllib.quote(passwd) + else: + userpass = urllib.quote(user) + return userpass + '@' + hostport + return hostport + +class httprepository(remoterepository): + def __init__(self, ui, path): + scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path) + if query or frag: + raise util.Abort(_('unsupported URL component: "%s"') % + (query or frag)) + if not urlpath: urlpath = '/' + host, port, user, passwd = netlocsplit(netloc) + + # urllib cannot handle URLs with embedded user or passwd + self.url = urlparse.urlunsplit((scheme, netlocunsplit(host, port), + urlpath, '', '')) + self.ui = ui + + proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') + proxyauthinfo = None + handler = urllib2.BaseHandler() + + if proxyurl: + # proxy can be proper url or host[:port] + if not (proxyurl.startswith('http:') or + proxyurl.startswith('https:')): + proxyurl = 'http://' + proxyurl + '/' + snpqf = urlparse.urlsplit(proxyurl) + proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf + hpup = netlocsplit(proxynetloc) + + proxyhost, proxyport, proxyuser, proxypasswd = hpup + if not proxyuser: + proxyuser = ui.config("http_proxy", "user") + proxypasswd = ui.config("http_proxy", "passwd") + + # see if we should use a proxy for this url + no_list = [ "localhost", "127.0.0.1" ] + no_list.extend([p.strip().lower() for + p in ui.config("http_proxy", "no", '').split(',') + if p.strip()]) + no_list.extend([p.strip().lower() for + p in os.getenv("no_proxy", '').split(',') + if p.strip()]) + # "http_proxy.always" config is for running tests on localhost + if (not ui.configbool("http_proxy", "always") and + host.lower() in no_list): + ui.debug(_('disabling proxy for %s\n') % host) + else: + proxyurl = urlparse.urlunsplit(( + proxyscheme, netlocunsplit(proxyhost, proxyport, + proxyuser, proxypasswd or ''), + proxypath, proxyquery, proxyfrag)) + handler = urllib2.ProxyHandler({scheme: proxyurl}) + ui.debug(_('proxying through %s\n') % proxyurl) + + # urllib2 takes proxy values from the environment and those + # will take precedence if found, so drop them + for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]: + try: + if os.environ.has_key(env): + del os.environ[env] + except OSError: + pass + + passmgr = passwordmgr(ui) + if user: + ui.debug(_('will use user %s for http auth\n') % user) + passmgr.add_password(None, host, user, passwd or '') + + opener = urllib2.build_opener( + handler, + urllib2.HTTPBasicAuthHandler(passmgr), + urllib2.HTTPDigestAuthHandler(passmgr)) + + # 1.0 here is the _protocol_ version + opener.addheaders = [('User-agent', 'mercurial/proto-1.0')] + urllib2.install_opener(opener) + + def dev(self): + return -1 + + def lock(self): + raise util.Abort(_('operation not supported over http')) + + def do_cmd(self, cmd, **args): + self.ui.debug(_("sending %s command\n") % cmd) + q = {"cmd": cmd} + q.update(args) + qs = urllib.urlencode(q) + cu = "%s?%s" % (self.url, qs) + try: + resp = urllib2.urlopen(cu) + except httplib.HTTPException, inst: + self.ui.debug(_('http error while sending %s command\n') % cmd) + self.ui.print_exc() + raise IOError(None, inst) + proto = resp.headers['content-type'] + + # accept old "text/plain" and "application/hg-changegroup" for now + if not proto.startswith('application/mercurial') and \ + not proto.startswith('text/plain') and \ + not proto.startswith('application/hg-changegroup'): + raise hg.RepoError(_("'%s' does not appear to be an hg repository") % + self.url) + + if proto.startswith('application/mercurial'): + version = proto[22:] + if float(version) > 0.1: + raise hg.RepoError(_("'%s' uses newer protocol %s") % + (self.url, version)) + + return resp + + def heads(self): + d = self.do_cmd("heads").read() + try: + return map(bin, d[:-1].split(" ")) + except: + self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n") + raise + + def branches(self, nodes): + n = " ".join(map(hex, nodes)) + d = self.do_cmd("branches", nodes=n).read() + try: + br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ] + return br + except: + self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n") + raise + + def between(self, pairs): + n = "\n".join(["-".join(map(hex, p)) for p in pairs]) + d = self.do_cmd("between", pairs=n).read() + try: + p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ] + return p + except: + self.ui.warn(_("unexpected response:\n") + d[:400] + "\n...\n") + raise + + def changegroup(self, nodes, kind): + n = " ".join(map(hex, nodes)) + f = self.do_cmd("changegroup", roots=n) + bytes = 0 + + def zgenerator(f): + zd = zlib.decompressobj() + try: + for chnk in f: + yield zd.decompress(chnk) + except httplib.HTTPException, inst: + raise IOError(None, _('connection ended unexpectedly')) + yield zd.flush() + + return util.chunkbuffer(zgenerator(util.filechunkiter(f))) + +class httpsrepository(httprepository): + pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/i18n.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +""" +i18n.py - internationalization support for mercurial + +Copyright 2005 Matt Mackall <mpm@selenic.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" + +# the import from gettext is _really_ slow +# for now we use a dummy function +gettext = lambda x: x +#import gettext +#t = gettext.translation('hg', '/usr/share/locale', fallback=1) +#gettext = t.gettext
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/localrepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,2122 @@ +# localrepo.py - read/write repository class for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os, util +import filelog, manifest, changelog, dirstate, repo +from node import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "appendfile changegroup") +demandload(globals(), "re lock transaction tempfile stat mdiff errno ui") +demandload(globals(), "revlog") + +class localrepository(object): + def __del__(self): + self.transhandle = None + def __init__(self, parentui, path=None, create=0): + if not path: + p = os.getcwd() + while not os.path.isdir(os.path.join(p, ".hg")): + oldp = p + p = os.path.dirname(p) + if p == oldp: + raise repo.RepoError(_("no repo found")) + path = p + self.path = os.path.join(path, ".hg") + + if not create and not os.path.isdir(self.path): + raise repo.RepoError(_("repository %s not found") % path) + + self.root = os.path.abspath(path) + self.origroot = path + self.ui = ui.ui(parentui=parentui) + self.opener = util.opener(self.path) + self.wopener = util.opener(self.root) + + try: + self.ui.readconfig(self.join("hgrc"), self.root) + except IOError: + pass + + v = self.ui.revlogopts + self.revlogversion = int(v.get('format', revlog.REVLOG_DEFAULT_FORMAT)) + self.revlogv1 = self.revlogversion != revlog.REVLOGV0 + fl = v.get('flags', None) + flags = 0 + if fl != None: + for x in fl.split(): + flags |= revlog.flagstr(x) + elif self.revlogv1: + flags = revlog.REVLOG_DEFAULT_FLAGS + + v = self.revlogversion | flags + self.manifest = manifest.manifest(self.opener, v) + self.changelog = changelog.changelog(self.opener, v) + + # the changelog might not have the inline index flag + # on. If the format of the changelog is the same as found in + # .hgrc, apply any flags found in the .hgrc as well. + # Otherwise, just version from the changelog + v = self.changelog.version + if v == self.revlogversion: + v |= flags + self.revlogversion = v + + self.tagscache = None + self.nodetagscache = None + self.encodepats = None + self.decodepats = None + self.transhandle = None + + if create: + os.mkdir(self.path) + os.mkdir(self.join("data")) + + self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) + + def hook(self, name, throw=False, **args): + def callhook(hname, funcname): + '''call python hook. hook is callable object, looked up as + name in python module. if callable returns "true", hook + fails, else passes. if hook raises exception, treated as + hook failure. exception propagates if throw is "true". + + reason for "true" meaning "hook failed" is so that + unmodified commands (e.g. mercurial.commands.update) can + be run as hooks without wrappers to convert return values.''' + + self.ui.note(_("calling hook %s: %s\n") % (hname, funcname)) + d = funcname.rfind('.') + if d == -1: + raise util.Abort(_('%s hook is invalid ("%s" not in a module)') + % (hname, funcname)) + modname = funcname[:d] + try: + obj = __import__(modname) + except ImportError: + raise util.Abort(_('%s hook is invalid ' + '(import of "%s" failed)') % + (hname, modname)) + try: + for p in funcname.split('.')[1:]: + obj = getattr(obj, p) + except AttributeError, err: + raise util.Abort(_('%s hook is invalid ' + '("%s" is not defined)') % + (hname, funcname)) + if not callable(obj): + raise util.Abort(_('%s hook is invalid ' + '("%s" is not callable)') % + (hname, funcname)) + try: + r = obj(ui=self.ui, repo=self, hooktype=name, **args) + except (KeyboardInterrupt, util.SignalInterrupt): + raise + except Exception, exc: + if isinstance(exc, util.Abort): + self.ui.warn(_('error: %s hook failed: %s\n') % + (hname, exc.args[0] % exc.args[1:])) + else: + self.ui.warn(_('error: %s hook raised an exception: ' + '%s\n') % (hname, exc)) + if throw: + raise + self.ui.print_exc() + return True + if r: + if throw: + raise util.Abort(_('%s hook failed') % hname) + self.ui.warn(_('warning: %s hook failed\n') % hname) + return r + + def runhook(name, cmd): + self.ui.note(_("running hook %s: %s\n") % (name, cmd)) + env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()]) + r = util.system(cmd, environ=env, cwd=self.root) + if r: + desc, r = util.explain_exit(r) + if throw: + raise util.Abort(_('%s hook %s') % (name, desc)) + self.ui.warn(_('warning: %s hook %s\n') % (name, desc)) + return r + + r = False + hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks") + if hname.split(".", 1)[0] == name and cmd] + hooks.sort() + for hname, cmd in hooks: + if cmd.startswith('python:'): + r = callhook(hname, cmd[7:].strip()) or r + else: + r = runhook(hname, cmd) or r + return r + + def tags(self): + '''return a mapping of tag to node''' + if not self.tagscache: + self.tagscache = {} + + def parsetag(line, context): + if not line: + return + s = l.split(" ", 1) + if len(s) != 2: + self.ui.warn(_("%s: cannot parse entry\n") % context) + return + node, key = s + key = key.strip() + try: + bin_n = bin(node) + except TypeError: + self.ui.warn(_("%s: node '%s' is not well formed\n") % + (context, node)) + return + if bin_n not in self.changelog.nodemap: + self.ui.warn(_("%s: tag '%s' refers to unknown node\n") % + (context, key)) + return + self.tagscache[key] = bin_n + + # read the tags file from each head, ending with the tip, + # and add each tag found to the map, with "newer" ones + # taking precedence + heads = self.heads() + heads.reverse() + fl = self.file(".hgtags") + for node in heads: + change = self.changelog.read(node) + rev = self.changelog.rev(node) + fn, ff = self.manifest.find(change[0], '.hgtags') + if fn is None: continue + count = 0 + for l in fl.read(fn).splitlines(): + count += 1 + parsetag(l, _(".hgtags (rev %d:%s), line %d") % + (rev, short(node), count)) + try: + f = self.opener("localtags") + count = 0 + for l in f: + count += 1 + parsetag(l, _("localtags, line %d") % count) + except IOError: + pass + + self.tagscache['tip'] = self.changelog.tip() + + return self.tagscache + + def tagslist(self): + '''return a list of tags ordered by revision''' + l = [] + for t, n in self.tags().items(): + try: + r = self.changelog.rev(n) + except: + r = -2 # sort to the beginning of the list if unknown + l.append((r, t, n)) + l.sort() + return [(t, n) for r, t, n in l] + + def nodetags(self, node): + '''return the tags associated with a node''' + if not self.nodetagscache: + self.nodetagscache = {} + for t, n in self.tags().items(): + self.nodetagscache.setdefault(n, []).append(t) + return self.nodetagscache.get(node, []) + + def lookup(self, key): + try: + return self.tags()[key] + except KeyError: + try: + return self.changelog.lookup(key) + except: + raise repo.RepoError(_("unknown revision '%s'") % key) + + def dev(self): + return os.stat(self.path).st_dev + + def local(self): + return True + + def join(self, f): + return os.path.join(self.path, f) + + def wjoin(self, f): + return os.path.join(self.root, f) + + def file(self, f): + if f[0] == '/': + f = f[1:] + return filelog.filelog(self.opener, f, self.revlogversion) + + def getcwd(self): + return self.dirstate.getcwd() + + def wfile(self, f, mode='r'): + return self.wopener(f, mode) + + def wread(self, filename): + if self.encodepats == None: + l = [] + for pat, cmd in self.ui.configitems("encode"): + mf = util.matcher(self.root, "", [pat], [], [])[1] + l.append((mf, cmd)) + self.encodepats = l + + data = self.wopener(filename, 'r').read() + + for mf, cmd in self.encodepats: + if mf(filename): + self.ui.debug(_("filtering %s through %s\n") % (filename, cmd)) + data = util.filter(data, cmd) + break + + return data + + def wwrite(self, filename, data, fd=None): + if self.decodepats == None: + l = [] + for pat, cmd in self.ui.configitems("decode"): + mf = util.matcher(self.root, "", [pat], [], [])[1] + l.append((mf, cmd)) + self.decodepats = l + + for mf, cmd in self.decodepats: + if mf(filename): + self.ui.debug(_("filtering %s through %s\n") % (filename, cmd)) + data = util.filter(data, cmd) + break + + if fd: + return fd.write(data) + return self.wopener(filename, 'w').write(data) + + def transaction(self): + tr = self.transhandle + if tr != None and tr.running(): + return tr.nest() + + # save dirstate for rollback + try: + ds = self.opener("dirstate").read() + except IOError: + ds = "" + self.opener("journal.dirstate", "w").write(ds) + + tr = transaction.transaction(self.ui.warn, self.opener, + self.join("journal"), + aftertrans(self.path)) + self.transhandle = tr + return tr + + def recover(self): + l = self.lock() + if os.path.exists(self.join("journal")): + self.ui.status(_("rolling back interrupted transaction\n")) + transaction.rollback(self.opener, self.join("journal")) + self.reload() + return True + else: + self.ui.warn(_("no interrupted transaction available\n")) + return False + + def rollback(self, wlock=None): + if not wlock: + wlock = self.wlock() + l = self.lock() + if os.path.exists(self.join("undo")): + self.ui.status(_("rolling back last transaction\n")) + transaction.rollback(self.opener, self.join("undo")) + util.rename(self.join("undo.dirstate"), self.join("dirstate")) + self.reload() + self.wreload() + else: + self.ui.warn(_("no rollback information available\n")) + + def wreload(self): + self.dirstate.read() + + def reload(self): + self.changelog.load() + self.manifest.load() + self.tagscache = None + self.nodetagscache = None + + def do_lock(self, lockname, wait, releasefn=None, acquirefn=None, + desc=None): + try: + l = lock.lock(self.join(lockname), 0, releasefn, desc=desc) + except lock.LockHeld, inst: + if not wait: + raise + self.ui.warn(_("waiting for lock on %s held by %s\n") % + (desc, inst.args[0])) + # default to 600 seconds timeout + l = lock.lock(self.join(lockname), + int(self.ui.config("ui", "timeout") or 600), + releasefn, desc=desc) + if acquirefn: + acquirefn() + return l + + def lock(self, wait=1): + return self.do_lock("lock", wait, acquirefn=self.reload, + desc=_('repository %s') % self.origroot) + + def wlock(self, wait=1): + return self.do_lock("wlock", wait, self.dirstate.write, + self.wreload, + desc=_('working directory of %s') % self.origroot) + + def checkfilemerge(self, filename, text, filelog, manifest1, manifest2): + "determine whether a new filenode is needed" + fp1 = manifest1.get(filename, nullid) + fp2 = manifest2.get(filename, nullid) + + if fp2 != nullid: + # is one parent an ancestor of the other? + fpa = filelog.ancestor(fp1, fp2) + if fpa == fp1: + fp1, fp2 = fp2, nullid + elif fpa == fp2: + fp2 = nullid + + # is the file unmodified from the parent? report existing entry + if fp2 == nullid and text == filelog.read(fp1): + return (fp1, None, None) + + return (None, fp1, fp2) + + def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None): + orig_parent = self.dirstate.parents()[0] or nullid + p1 = p1 or self.dirstate.parents()[0] or nullid + p2 = p2 or self.dirstate.parents()[1] or nullid + c1 = self.changelog.read(p1) + c2 = self.changelog.read(p2) + m1 = self.manifest.read(c1[0]) + mf1 = self.manifest.readflags(c1[0]) + m2 = self.manifest.read(c2[0]) + changed = [] + + if orig_parent == p1: + update_dirstate = 1 + else: + update_dirstate = 0 + + if not wlock: + wlock = self.wlock() + l = self.lock() + tr = self.transaction() + mm = m1.copy() + mfm = mf1.copy() + linkrev = self.changelog.count() + for f in files: + try: + t = self.wread(f) + tm = util.is_exec(self.wjoin(f), mfm.get(f, False)) + r = self.file(f) + mfm[f] = tm + + (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2) + if entry: + mm[f] = entry + continue + + mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2) + changed.append(f) + if update_dirstate: + self.dirstate.update([f], "n") + except IOError: + try: + del mm[f] + del mfm[f] + if update_dirstate: + self.dirstate.forget([f]) + except: + # deleted from p2? + pass + + mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0]) + user = user or self.ui.username() + n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date) + tr.close() + if update_dirstate: + self.dirstate.setparents(n, nullid) + + def commit(self, files=None, text="", user=None, date=None, + match=util.always, force=False, lock=None, wlock=None, + force_editor=False): + commit = [] + remove = [] + changed = [] + + if files: + for f in files: + s = self.dirstate.state(f) + if s in 'nmai': + commit.append(f) + elif s == 'r': + remove.append(f) + else: + self.ui.warn(_("%s not tracked!\n") % f) + else: + modified, added, removed, deleted, unknown = self.changes(match=match) + commit = modified + added + remove = removed + + p1, p2 = self.dirstate.parents() + c1 = self.changelog.read(p1) + c2 = self.changelog.read(p2) + m1 = self.manifest.read(c1[0]) + mf1 = self.manifest.readflags(c1[0]) + m2 = self.manifest.read(c2[0]) + + if not commit and not remove and not force and p2 == nullid: + self.ui.status(_("nothing changed\n")) + return None + + xp1 = hex(p1) + if p2 == nullid: xp2 = '' + else: xp2 = hex(p2) + + self.hook("precommit", throw=True, parent1=xp1, parent2=xp2) + + if not wlock: + wlock = self.wlock() + if not lock: + lock = self.lock() + tr = self.transaction() + + # check in files + new = {} + linkrev = self.changelog.count() + commit.sort() + for f in commit: + self.ui.note(f + "\n") + try: + mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False)) + t = self.wread(f) + except IOError: + self.ui.warn(_("trouble committing %s!\n") % f) + raise + + r = self.file(f) + + meta = {} + cp = self.dirstate.copied(f) + if cp: + meta["copy"] = cp + meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid))) + self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"])) + fp1, fp2 = nullid, nullid + else: + entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2) + if entry: + new[f] = entry + continue + + new[f] = r.add(t, meta, tr, linkrev, fp1, fp2) + # remember what we've added so that we can later calculate + # the files to pull from a set of changesets + changed.append(f) + + # update manifest + m1 = m1.copy() + m1.update(new) + for f in remove: + if f in m1: + del m1[f] + mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], + (new, remove)) + + # add changeset + new = new.keys() + new.sort() + + user = user or self.ui.username() + if not text or force_editor: + edittext = [] + if text: + edittext.append(text) + edittext.append("") + if p2 != nullid: + edittext.append("HG: branch merge") + edittext.extend(["HG: changed %s" % f for f in changed]) + edittext.extend(["HG: removed %s" % f for f in remove]) + if not changed and not remove: + edittext.append("HG: no files changed") + edittext.append("") + # run editor in the repository root + olddir = os.getcwd() + os.chdir(self.root) + text = self.ui.edit("\n".join(edittext), user) + os.chdir(olddir) + + lines = [line.rstrip() for line in text.rstrip().splitlines()] + while lines and not lines[0]: + del lines[0] + if not lines: + return None + text = '\n'.join(lines) + n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date) + self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, + parent2=xp2) + tr.close() + + self.dirstate.setparents(n) + self.dirstate.update(new, "n") + self.dirstate.forget(remove) + + self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) + return n + + def walk(self, node=None, files=[], match=util.always, badmatch=None): + if node: + fdict = dict.fromkeys(files) + for fn in self.manifest.read(self.changelog.read(node)[0]): + fdict.pop(fn, None) + if match(fn): + yield 'm', fn + for fn in fdict: + if badmatch and badmatch(fn): + if match(fn): + yield 'b', fn + else: + self.ui.warn(_('%s: No such file in rev %s\n') % ( + util.pathto(self.getcwd(), fn), short(node))) + else: + for src, fn in self.dirstate.walk(files, match, badmatch=badmatch): + yield src, fn + + def changes(self, node1=None, node2=None, files=[], match=util.always, + wlock=None, show_ignored=None): + """return changes between two nodes or node and working directory + + If node1 is None, use the first dirstate parent instead. + If node2 is None, compare node1 with working directory. + """ + + def fcmp(fn, mf): + t1 = self.wread(fn) + t2 = self.file(fn).read(mf.get(fn, nullid)) + return cmp(t1, t2) + + def mfmatches(node): + change = self.changelog.read(node) + mf = dict(self.manifest.read(change[0])) + for fn in mf.keys(): + if not match(fn): + del mf[fn] + return mf + + if node1: + # read the manifest from node1 before the manifest from node2, + # so that we'll hit the manifest cache if we're going through + # all the revisions in parent->child order. + mf1 = mfmatches(node1) + + # are we comparing the working directory? + if not node2: + if not wlock: + try: + wlock = self.wlock(wait=0) + except lock.LockException: + wlock = None + lookup, modified, added, removed, deleted, unknown, ignored = ( + self.dirstate.changes(files, match, show_ignored)) + + # are we comparing working dir against its parent? + if not node1: + if lookup: + # do a full compare of any files that might have changed + mf2 = mfmatches(self.dirstate.parents()[0]) + for f in lookup: + if fcmp(f, mf2): + modified.append(f) + elif wlock is not None: + self.dirstate.update([f], "n") + else: + # we are comparing working dir against non-parent + # generate a pseudo-manifest for the working dir + mf2 = mfmatches(self.dirstate.parents()[0]) + for f in lookup + modified + added: + mf2[f] = "" + for f in removed: + if f in mf2: + del mf2[f] + else: + # we are comparing two revisions + deleted, unknown, ignored = [], [], [] + mf2 = mfmatches(node2) + + if node1: + # flush lists from dirstate before comparing manifests + modified, added = [], [] + + for fn in mf2: + if mf1.has_key(fn): + if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)): + modified.append(fn) + del mf1[fn] + else: + added.append(fn) + + removed = mf1.keys() + + # sort and return results: + for l in modified, added, removed, deleted, unknown, ignored: + l.sort() + if show_ignored is None: + return (modified, added, removed, deleted, unknown) + else: + return (modified, added, removed, deleted, unknown, ignored) + + def add(self, list, wlock=None): + if not wlock: + wlock = self.wlock() + for f in list: + p = self.wjoin(f) + if not os.path.exists(p): + self.ui.warn(_("%s does not exist!\n") % f) + elif not os.path.isfile(p): + self.ui.warn(_("%s not added: only files supported currently\n") + % f) + elif self.dirstate.state(f) in 'an': + self.ui.warn(_("%s already tracked!\n") % f) + else: + self.dirstate.update([f], "a") + + def forget(self, list, wlock=None): + if not wlock: + wlock = self.wlock() + for f in list: + if self.dirstate.state(f) not in 'ai': + self.ui.warn(_("%s not added!\n") % f) + else: + self.dirstate.forget([f]) + + def remove(self, list, unlink=False, wlock=None): + if unlink: + for f in list: + try: + util.unlink(self.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + raise + if not wlock: + wlock = self.wlock() + for f in list: + p = self.wjoin(f) + if os.path.exists(p): + self.ui.warn(_("%s still exists!\n") % f) + elif self.dirstate.state(f) == 'a': + self.dirstate.forget([f]) + elif f not in self.dirstate: + self.ui.warn(_("%s not tracked!\n") % f) + else: + self.dirstate.update([f], "r") + + def undelete(self, list, wlock=None): + p = self.dirstate.parents()[0] + mn = self.changelog.read(p)[0] + mf = self.manifest.readflags(mn) + m = self.manifest.read(mn) + if not wlock: + wlock = self.wlock() + for f in list: + if self.dirstate.state(f) not in "r": + self.ui.warn("%s not removed!\n" % f) + else: + t = self.file(f).read(m[f]) + self.wwrite(f, t) + util.set_exec(self.wjoin(f), mf[f]) + self.dirstate.update([f], "n") + + def copy(self, source, dest, wlock=None): + p = self.wjoin(dest) + if not os.path.exists(p): + self.ui.warn(_("%s does not exist!\n") % dest) + elif not os.path.isfile(p): + self.ui.warn(_("copy failed: %s is not a file\n") % dest) + else: + if not wlock: + wlock = self.wlock() + if self.dirstate.state(dest) == '?': + self.dirstate.update([dest], "a") + self.dirstate.copy(source, dest) + + def heads(self, start=None): + heads = self.changelog.heads(start) + # sort the output in rev descending order + heads = [(-self.changelog.rev(h), h) for h in heads] + heads.sort() + return [n for (r, n) in heads] + + # branchlookup returns a dict giving a list of branches for + # each head. A branch is defined as the tag of a node or + # the branch of the node's parents. If a node has multiple + # branch tags, tags are eliminated if they are visible from other + # branch tags. + # + # So, for this graph: a->b->c->d->e + # \ / + # aa -----/ + # a has tag 2.6.12 + # d has tag 2.6.13 + # e would have branch tags for 2.6.12 and 2.6.13. Because the node + # for 2.6.12 can be reached from the node 2.6.13, that is eliminated + # from the list. + # + # It is possible that more than one head will have the same branch tag. + # callers need to check the result for multiple heads under the same + # branch tag if that is a problem for them (ie checkout of a specific + # branch). + # + # passing in a specific branch will limit the depth of the search + # through the parents. It won't limit the branches returned in the + # result though. + def branchlookup(self, heads=None, branch=None): + if not heads: + heads = self.heads() + headt = [ h for h in heads ] + chlog = self.changelog + branches = {} + merges = [] + seenmerge = {} + + # traverse the tree once for each head, recording in the branches + # dict which tags are visible from this head. The branches + # dict also records which tags are visible from each tag + # while we traverse. + while headt or merges: + if merges: + n, found = merges.pop() + visit = [n] + else: + h = headt.pop() + visit = [h] + found = [h] + seen = {} + while visit: + n = visit.pop() + if n in seen: + continue + pp = chlog.parents(n) + tags = self.nodetags(n) + if tags: + for x in tags: + if x == 'tip': + continue + for f in found: + branches.setdefault(f, {})[n] = 1 + branches.setdefault(n, {})[n] = 1 + break + if n not in found: + found.append(n) + if branch in tags: + continue + seen[n] = 1 + if pp[1] != nullid and n not in seenmerge: + merges.append((pp[1], [x for x in found])) + seenmerge[n] = 1 + if pp[0] != nullid: + visit.append(pp[0]) + # traverse the branches dict, eliminating branch tags from each + # head that are visible from another branch tag for that head. + out = {} + viscache = {} + for h in heads: + def visible(node): + if node in viscache: + return viscache[node] + ret = {} + visit = [node] + while visit: + x = visit.pop() + if x in viscache: + ret.update(viscache[x]) + elif x not in ret: + ret[x] = 1 + if x in branches: + visit[len(visit):] = branches[x].keys() + viscache[node] = ret + return ret + if h not in branches: + continue + # O(n^2), but somewhat limited. This only searches the + # tags visible from a specific head, not all the tags in the + # whole repo. + for b in branches[h]: + vis = False + for bb in branches[h].keys(): + if b != bb: + if b in visible(bb): + vis = True + break + if not vis: + l = out.setdefault(h, []) + l[len(l):] = self.nodetags(b) + return out + + def branches(self, nodes): + if not nodes: + nodes = [self.changelog.tip()] + b = [] + for n in nodes: + t = n + while 1: + p = self.changelog.parents(n) + if p[1] != nullid or p[0] == nullid: + b.append((t, n, p[0], p[1])) + break + n = p[0] + return b + + def between(self, pairs): + r = [] + + for top, bottom in pairs: + n, l, i = top, [], 0 + f = 1 + + while n != bottom: + p = self.changelog.parents(n)[0] + if i == f: + l.append(n) + f = f * 2 + n = p + i += 1 + + r.append(l) + + return r + + def findincoming(self, remote, base=None, heads=None, force=False): + """Return list of roots of the subsets of missing nodes from remote + + If base dict is specified, assume that these nodes and their parents + exist on the remote side and that no child of a node of base exists + in both remote and self. + Furthermore base will be updated to include the nodes that exists + in self and remote but no children exists in self and remote. + If a list of heads is specified, return only nodes which are heads + or ancestors of these heads. + + All the ancestors of base are in self and in remote. + All the descendants of the list returned are missing in self. + (and so we know that the rest of the nodes are missing in remote, see + outgoing) + """ + m = self.changelog.nodemap + search = [] + fetch = {} + seen = {} + seenbranch = {} + if base == None: + base = {} + + if not heads: + heads = remote.heads() + + if self.changelog.tip() == nullid: + base[nullid] = 1 + if heads != [nullid]: + return [nullid] + return [] + + # assume we're closer to the tip than the root + # and start by examining the heads + self.ui.status(_("searching for changes\n")) + + unknown = [] + for h in heads: + if h not in m: + unknown.append(h) + else: + base[h] = 1 + + if not unknown: + return [] + + req = dict.fromkeys(unknown) + reqcnt = 0 + + # search through remote branches + # a 'branch' here is a linear segment of history, with four parts: + # head, root, first parent, second parent + # (a branch always has two parents (or none) by definition) + unknown = remote.branches(unknown) + while unknown: + r = [] + while unknown: + n = unknown.pop(0) + if n[0] in seen: + continue + + self.ui.debug(_("examining %s:%s\n") + % (short(n[0]), short(n[1]))) + if n[0] == nullid: # found the end of the branch + pass + elif n in seenbranch: + self.ui.debug(_("branch already found\n")) + continue + elif n[1] and n[1] in m: # do we know the base? + self.ui.debug(_("found incomplete branch %s:%s\n") + % (short(n[0]), short(n[1]))) + search.append(n) # schedule branch range for scanning + seenbranch[n] = 1 + else: + if n[1] not in seen and n[1] not in fetch: + if n[2] in m and n[3] in m: + self.ui.debug(_("found new changeset %s\n") % + short(n[1])) + fetch[n[1]] = 1 # earliest unknown + for p in n[2:4]: + if p in m: + base[p] = 1 # latest known + + for p in n[2:4]: + if p not in req and p not in m: + r.append(p) + req[p] = 1 + seen[n[0]] = 1 + + if r: + reqcnt += 1 + self.ui.debug(_("request %d: %s\n") % + (reqcnt, " ".join(map(short, r)))) + for p in range(0, len(r), 10): + for b in remote.branches(r[p:p+10]): + self.ui.debug(_("received %s:%s\n") % + (short(b[0]), short(b[1]))) + unknown.append(b) + + # do binary search on the branches we found + while search: + n = search.pop(0) + reqcnt += 1 + l = remote.between([(n[0], n[1])])[0] + l.append(n[1]) + p = n[0] + f = 1 + for i in l: + self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i))) + if i in m: + if f <= 2: + self.ui.debug(_("found new branch changeset %s\n") % + short(p)) + fetch[p] = 1 + base[i] = 1 + else: + self.ui.debug(_("narrowed branch search to %s:%s\n") + % (short(p), short(i))) + search.append((p, i)) + break + p, f = i, f * 2 + + # sanity check our fetch list + for f in fetch.keys(): + if f in m: + raise repo.RepoError(_("already have changeset ") + short(f[:4])) + + if base.keys() == [nullid]: + if force: + self.ui.warn(_("warning: repository is unrelated\n")) + else: + raise util.Abort(_("repository is unrelated")) + + self.ui.note(_("found new changesets starting at ") + + " ".join([short(f) for f in fetch]) + "\n") + + self.ui.debug(_("%d total queries\n") % reqcnt) + + return fetch.keys() + + def findoutgoing(self, remote, base=None, heads=None, force=False): + """Return list of nodes that are roots of subsets not in remote + + If base dict is specified, assume that these nodes and their parents + exist on the remote side. + If a list of heads is specified, return only nodes which are heads + or ancestors of these heads, and return a second element which + contains all remote heads which get new children. + """ + if base == None: + base = {} + self.findincoming(remote, base, heads, force=force) + + self.ui.debug(_("common changesets up to ") + + " ".join(map(short, base.keys())) + "\n") + + remain = dict.fromkeys(self.changelog.nodemap) + + # prune everything remote has from the tree + del remain[nullid] + remove = base.keys() + while remove: + n = remove.pop(0) + if n in remain: + del remain[n] + for p in self.changelog.parents(n): + remove.append(p) + + # find every node whose parents have been pruned + subset = [] + # find every remote head that will get new children + updated_heads = {} + for n in remain: + p1, p2 = self.changelog.parents(n) + if p1 not in remain and p2 not in remain: + subset.append(n) + if heads: + if p1 in heads: + updated_heads[p1] = True + if p2 in heads: + updated_heads[p2] = True + + # this is the set of all roots we have to push + if heads: + return subset, updated_heads.keys() + else: + return subset + + def pull(self, remote, heads=None, force=False): + l = self.lock() + + fetch = self.findincoming(remote, force=force) + if fetch == [nullid]: + self.ui.status(_("requesting all changes\n")) + + if not fetch: + self.ui.status(_("no changes found\n")) + return 0 + + if heads is None: + cg = remote.changegroup(fetch, 'pull') + else: + cg = remote.changegroupsubset(fetch, heads, 'pull') + return self.addchangegroup(cg, 'pull') + + def push(self, remote, force=False, revs=None): + lock = remote.lock() + + base = {} + remote_heads = remote.heads() + inc = self.findincoming(remote, base, remote_heads, force=force) + if not force and inc: + self.ui.warn(_("abort: unsynced remote changes!\n")) + self.ui.status(_("(did you forget to sync?" + " use push -f to force)\n")) + return 1 + + update, updated_heads = self.findoutgoing(remote, base, remote_heads) + if revs is not None: + msng_cl, bases, heads = self.changelog.nodesbetween(update, revs) + else: + bases, heads = update, self.changelog.heads() + + if not bases: + self.ui.status(_("no changes found\n")) + return 1 + elif not force: + # FIXME we don't properly detect creation of new heads + # in the push -r case, assume the user knows what he's doing + if not revs and len(remote_heads) < len(heads) \ + and remote_heads != [nullid]: + self.ui.warn(_("abort: push creates new remote branches!\n")) + self.ui.status(_("(did you forget to merge?" + " use push -f to force)\n")) + return 1 + + if revs is None: + cg = self.changegroup(update, 'push') + else: + cg = self.changegroupsubset(update, revs, 'push') + return remote.addchangegroup(cg, 'push') + + def changegroupsubset(self, bases, heads, source): + """This function generates a changegroup consisting of all the nodes + that are descendents of any of the bases, and ancestors of any of + the heads. + + It is fairly complex as determining which filenodes and which + manifest nodes need to be included for the changeset to be complete + is non-trivial. + + Another wrinkle is doing the reverse, figuring out which changeset in + the changegroup a particular filenode or manifestnode belongs to.""" + + self.hook('preoutgoing', throw=True, source=source) + + # Set up some initial variables + # Make it easy to refer to self.changelog + cl = self.changelog + # msng is short for missing - compute the list of changesets in this + # changegroup. + msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads) + # Some bases may turn out to be superfluous, and some heads may be + # too. nodesbetween will return the minimal set of bases and heads + # necessary to re-create the changegroup. + + # Known heads are the list of heads that it is assumed the recipient + # of this changegroup will know about. + knownheads = {} + # We assume that all parents of bases are known heads. + for n in bases: + for p in cl.parents(n): + if p != nullid: + knownheads[p] = 1 + knownheads = knownheads.keys() + if knownheads: + # Now that we know what heads are known, we can compute which + # changesets are known. The recipient must know about all + # changesets required to reach the known heads from the null + # changeset. + has_cl_set, junk, junk = cl.nodesbetween(None, knownheads) + junk = None + # Transform the list into an ersatz set. + has_cl_set = dict.fromkeys(has_cl_set) + else: + # If there were no known heads, the recipient cannot be assumed to + # know about any changesets. + has_cl_set = {} + + # Make it easy to refer to self.manifest + mnfst = self.manifest + # We don't know which manifests are missing yet + msng_mnfst_set = {} + # Nor do we know which filenodes are missing. + msng_filenode_set = {} + + junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex + junk = None + + # A changeset always belongs to itself, so the changenode lookup + # function for a changenode is identity. + def identity(x): + return x + + # A function generating function. Sets up an environment for the + # inner function. + def cmp_by_rev_func(revlog): + # Compare two nodes by their revision number in the environment's + # revision history. Since the revision number both represents the + # most efficient order to read the nodes in, and represents a + # topological sorting of the nodes, this function is often useful. + def cmp_by_rev(a, b): + return cmp(revlog.rev(a), revlog.rev(b)) + return cmp_by_rev + + # If we determine that a particular file or manifest node must be a + # node that the recipient of the changegroup will already have, we can + # also assume the recipient will have all the parents. This function + # prunes them from the set of missing nodes. + def prune_parents(revlog, hasset, msngset): + haslst = hasset.keys() + haslst.sort(cmp_by_rev_func(revlog)) + for node in haslst: + parentlst = [p for p in revlog.parents(node) if p != nullid] + while parentlst: + n = parentlst.pop() + if n not in hasset: + hasset[n] = 1 + p = [p for p in revlog.parents(n) if p != nullid] + parentlst.extend(p) + for n in hasset: + msngset.pop(n, None) + + # This is a function generating function used to set up an environment + # for the inner function to execute in. + def manifest_and_file_collector(changedfileset): + # This is an information gathering function that gathers + # information from each changeset node that goes out as part of + # the changegroup. The information gathered is a list of which + # manifest nodes are potentially required (the recipient may + # already have them) and total list of all files which were + # changed in any changeset in the changegroup. + # + # We also remember the first changenode we saw any manifest + # referenced by so we can later determine which changenode 'owns' + # the manifest. + def collect_manifests_and_files(clnode): + c = cl.read(clnode) + for f in c[3]: + # This is to make sure we only have one instance of each + # filename string for each filename. + changedfileset.setdefault(f, f) + msng_mnfst_set.setdefault(c[0], clnode) + return collect_manifests_and_files + + # Figure out which manifest nodes (of the ones we think might be part + # of the changegroup) the recipient must know about and remove them + # from the changegroup. + def prune_manifests(): + has_mnfst_set = {} + for n in msng_mnfst_set: + # If a 'missing' manifest thinks it belongs to a changenode + # the recipient is assumed to have, obviously the recipient + # must have that manifest. + linknode = cl.node(mnfst.linkrev(n)) + if linknode in has_cl_set: + has_mnfst_set[n] = 1 + prune_parents(mnfst, has_mnfst_set, msng_mnfst_set) + + # Use the information collected in collect_manifests_and_files to say + # which changenode any manifestnode belongs to. + def lookup_manifest_link(mnfstnode): + return msng_mnfst_set[mnfstnode] + + # A function generating function that sets up the initial environment + # the inner function. + def filenode_collector(changedfiles): + next_rev = [0] + # This gathers information from each manifestnode included in the + # changegroup about which filenodes the manifest node references + # so we can include those in the changegroup too. + # + # It also remembers which changenode each filenode belongs to. It + # does this by assuming the a filenode belongs to the changenode + # the first manifest that references it belongs to. + def collect_msng_filenodes(mnfstnode): + r = mnfst.rev(mnfstnode) + if r == next_rev[0]: + # If the last rev we looked at was the one just previous, + # we only need to see a diff. + delta = mdiff.patchtext(mnfst.delta(mnfstnode)) + # For each line in the delta + for dline in delta.splitlines(): + # get the filename and filenode for that line + f, fnode = dline.split('\0') + fnode = bin(fnode[:40]) + f = changedfiles.get(f, None) + # And if the file is in the list of files we care + # about. + if f is not None: + # Get the changenode this manifest belongs to + clnode = msng_mnfst_set[mnfstnode] + # Create the set of filenodes for the file if + # there isn't one already. + ndset = msng_filenode_set.setdefault(f, {}) + # And set the filenode's changelog node to the + # manifest's if it hasn't been set already. + ndset.setdefault(fnode, clnode) + else: + # Otherwise we need a full manifest. + m = mnfst.read(mnfstnode) + # For every file in we care about. + for f in changedfiles: + fnode = m.get(f, None) + # If it's in the manifest + if fnode is not None: + # See comments above. + clnode = msng_mnfst_set[mnfstnode] + ndset = msng_filenode_set.setdefault(f, {}) + ndset.setdefault(fnode, clnode) + # Remember the revision we hope to see next. + next_rev[0] = r + 1 + return collect_msng_filenodes + + # We have a list of filenodes we think we need for a file, lets remove + # all those we now the recipient must have. + def prune_filenodes(f, filerevlog): + msngset = msng_filenode_set[f] + hasset = {} + # If a 'missing' filenode thinks it belongs to a changenode we + # assume the recipient must have, then the recipient must have + # that filenode. + for n in msngset: + clnode = cl.node(filerevlog.linkrev(n)) + if clnode in has_cl_set: + hasset[n] = 1 + prune_parents(filerevlog, hasset, msngset) + + # A function generator function that sets up the a context for the + # inner function. + def lookup_filenode_link_func(fname): + msngset = msng_filenode_set[fname] + # Lookup the changenode the filenode belongs to. + def lookup_filenode_link(fnode): + return msngset[fnode] + return lookup_filenode_link + + # Now that we have all theses utility functions to help out and + # logically divide up the task, generate the group. + def gengroup(): + # The set of changed files starts empty. + changedfiles = {} + # Create a changenode group generator that will call our functions + # back to lookup the owning changenode and collect information. + group = cl.group(msng_cl_lst, identity, + manifest_and_file_collector(changedfiles)) + for chnk in group: + yield chnk + + # The list of manifests has been collected by the generator + # calling our functions back. + prune_manifests() + msng_mnfst_lst = msng_mnfst_set.keys() + # Sort the manifestnodes by revision number. + msng_mnfst_lst.sort(cmp_by_rev_func(mnfst)) + # Create a generator for the manifestnodes that calls our lookup + # and data collection functions back. + group = mnfst.group(msng_mnfst_lst, lookup_manifest_link, + filenode_collector(changedfiles)) + for chnk in group: + yield chnk + + # These are no longer needed, dereference and toss the memory for + # them. + msng_mnfst_lst = None + msng_mnfst_set.clear() + + changedfiles = changedfiles.keys() + changedfiles.sort() + # Go through all our files in order sorted by name. + for fname in changedfiles: + filerevlog = self.file(fname) + # Toss out the filenodes that the recipient isn't really + # missing. + if msng_filenode_set.has_key(fname): + prune_filenodes(fname, filerevlog) + msng_filenode_lst = msng_filenode_set[fname].keys() + else: + msng_filenode_lst = [] + # If any filenodes are left, generate the group for them, + # otherwise don't bother. + if len(msng_filenode_lst) > 0: + yield changegroup.genchunk(fname) + # Sort the filenodes by their revision # + msng_filenode_lst.sort(cmp_by_rev_func(filerevlog)) + # Create a group generator and only pass in a changenode + # lookup function as we need to collect no information + # from filenodes. + group = filerevlog.group(msng_filenode_lst, + lookup_filenode_link_func(fname)) + for chnk in group: + yield chnk + if msng_filenode_set.has_key(fname): + # Don't need this anymore, toss it to free memory. + del msng_filenode_set[fname] + # Signal that no more groups are left. + yield changegroup.closechunk() + + if msng_cl_lst: + self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source) + + return util.chunkbuffer(gengroup()) + + def changegroup(self, basenodes, source): + """Generate a changegroup of all nodes that we have that a recipient + doesn't. + + This is much easier than the previous function as we can assume that + the recipient has any changenode we aren't sending them.""" + + self.hook('preoutgoing', throw=True, source=source) + + cl = self.changelog + nodes = cl.nodesbetween(basenodes, None)[0] + revset = dict.fromkeys([cl.rev(n) for n in nodes]) + + def identity(x): + return x + + def gennodelst(revlog): + for r in xrange(0, revlog.count()): + n = revlog.node(r) + if revlog.linkrev(n) in revset: + yield n + + def changed_file_collector(changedfileset): + def collect_changed_files(clnode): + c = cl.read(clnode) + for fname in c[3]: + changedfileset[fname] = 1 + return collect_changed_files + + def lookuprevlink_func(revlog): + def lookuprevlink(n): + return cl.node(revlog.linkrev(n)) + return lookuprevlink + + def gengroup(): + # construct a list of all changed files + changedfiles = {} + + for chnk in cl.group(nodes, identity, + changed_file_collector(changedfiles)): + yield chnk + changedfiles = changedfiles.keys() + changedfiles.sort() + + mnfst = self.manifest + nodeiter = gennodelst(mnfst) + for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)): + yield chnk + + for fname in changedfiles: + filerevlog = self.file(fname) + nodeiter = gennodelst(filerevlog) + nodeiter = list(nodeiter) + if nodeiter: + yield changegroup.genchunk(fname) + lookup = lookuprevlink_func(filerevlog) + for chnk in filerevlog.group(nodeiter, lookup): + yield chnk + + yield changegroup.closechunk() + + if nodes: + self.hook('outgoing', node=hex(nodes[0]), source=source) + + return util.chunkbuffer(gengroup()) + + def addchangegroup(self, source, srctype): + """add changegroup to repo. + returns number of heads modified or added + 1.""" + + def csmap(x): + self.ui.debug(_("add changeset %s\n") % short(x)) + return cl.count() + + def revmap(x): + return cl.rev(x) + + if not source: + return 0 + + self.hook('prechangegroup', throw=True, source=srctype) + + changesets = files = revisions = 0 + + tr = self.transaction() + + # write changelog and manifest data to temp files so + # concurrent readers will not see inconsistent view + cl = None + try: + cl = appendfile.appendchangelog(self.opener, self.changelog.version) + + oldheads = len(cl.heads()) + + # pull off the changeset group + self.ui.status(_("adding changesets\n")) + cor = cl.count() - 1 + chunkiter = changegroup.chunkiter(source) + if cl.addgroup(chunkiter, csmap, tr, 1) is None: + raise util.Abort(_("received changelog group is empty")) + cnr = cl.count() - 1 + changesets = cnr - cor + + mf = None + try: + mf = appendfile.appendmanifest(self.opener, + self.manifest.version) + + # pull off the manifest group + self.ui.status(_("adding manifests\n")) + chunkiter = changegroup.chunkiter(source) + # no need to check for empty manifest group here: + # if the result of the merge of 1 and 2 is the same in 3 and 4, + # no new manifest will be created and the manifest group will + # be empty during the pull + mf.addgroup(chunkiter, revmap, tr) + + # process the files + self.ui.status(_("adding file changes\n")) + while 1: + f = changegroup.getchunk(source) + if not f: + break + self.ui.debug(_("adding %s revisions\n") % f) + fl = self.file(f) + o = fl.count() + chunkiter = changegroup.chunkiter(source) + if fl.addgroup(chunkiter, revmap, tr) is None: + raise util.Abort(_("received file revlog group is empty")) + revisions += fl.count() - o + files += 1 + + # write order here is important so concurrent readers will see + # consistent view of repo + mf.writedata() + finally: + if mf: + mf.cleanup() + cl.writedata() + finally: + if cl: + cl.cleanup() + + # make changelog and manifest see real files again + self.changelog = changelog.changelog(self.opener, self.changelog.version) + self.manifest = manifest.manifest(self.opener, self.manifest.version) + self.changelog.checkinlinesize(tr) + self.manifest.checkinlinesize(tr) + + newheads = len(self.changelog.heads()) + heads = "" + if oldheads and newheads > oldheads: + heads = _(" (+%d heads)") % (newheads - oldheads) + + self.ui.status(_("added %d changesets" + " with %d changes to %d files%s\n") + % (changesets, revisions, files, heads)) + + if changesets > 0: + self.hook('pretxnchangegroup', throw=True, + node=hex(self.changelog.node(cor+1)), source=srctype) + + tr.close() + + if changesets > 0: + self.hook("changegroup", node=hex(self.changelog.node(cor+1)), + source=srctype) + + for i in range(cor + 1, cnr + 1): + self.hook("incoming", node=hex(self.changelog.node(i)), + source=srctype) + + return newheads - oldheads + 1 + + def update(self, node, allow=False, force=False, choose=None, + moddirstate=True, forcemerge=False, wlock=None, show_stats=True): + pl = self.dirstate.parents() + if not force and pl[1] != nullid: + raise util.Abort(_("outstanding uncommitted merges")) + + err = False + + p1, p2 = pl[0], node + pa = self.changelog.ancestor(p1, p2) + m1n = self.changelog.read(p1)[0] + m2n = self.changelog.read(p2)[0] + man = self.manifest.ancestor(m1n, m2n) + m1 = self.manifest.read(m1n) + mf1 = self.manifest.readflags(m1n) + m2 = self.manifest.read(m2n).copy() + mf2 = self.manifest.readflags(m2n) + ma = self.manifest.read(man) + mfa = self.manifest.readflags(man) + + modified, added, removed, deleted, unknown = self.changes() + + # is this a jump, or a merge? i.e. is there a linear path + # from p1 to p2? + linear_path = (pa == p1 or pa == p2) + + if allow and linear_path: + raise util.Abort(_("there is nothing to merge, " + "just use 'hg update'")) + if allow and not forcemerge: + if modified or added or removed: + raise util.Abort(_("outstanding uncommitted changes")) + + if not forcemerge and not force: + for f in unknown: + if f in m2: + t1 = self.wread(f) + t2 = self.file(f).read(m2[f]) + if cmp(t1, t2) != 0: + raise util.Abort(_("'%s' already exists in the working" + " dir and differs from remote") % f) + + # resolve the manifest to determine which files + # we care about merging + self.ui.note(_("resolving manifests\n")) + self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") % + (force, allow, moddirstate, linear_path)) + self.ui.debug(_(" ancestor %s local %s remote %s\n") % + (short(man), short(m1n), short(m2n))) + + merge = {} + get = {} + remove = [] + + # construct a working dir manifest + mw = m1.copy() + mfw = mf1.copy() + umap = dict.fromkeys(unknown) + + for f in added + modified + unknown: + mw[f] = "" + mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False)) + + if moddirstate and not wlock: + wlock = self.wlock() + + for f in deleted + removed: + if f in mw: + del mw[f] + + # If we're jumping between revisions (as opposed to merging), + # and if neither the working directory nor the target rev has + # the file, then we need to remove it from the dirstate, to + # prevent the dirstate from listing the file when it is no + # longer in the manifest. + if moddirstate and linear_path and f not in m2: + self.dirstate.forget((f,)) + + # Compare manifests + for f, n in mw.iteritems(): + if choose and not choose(f): + continue + if f in m2: + s = 0 + + # is the wfile new since m1, and match m2? + if f not in m1: + t1 = self.wread(f) + t2 = self.file(f).read(m2[f]) + if cmp(t1, t2) == 0: + n = m2[f] + del t1, t2 + + # are files different? + if n != m2[f]: + a = ma.get(f, nullid) + # are both different from the ancestor? + if n != a and m2[f] != a: + self.ui.debug(_(" %s versions differ, resolve\n") % f) + # merge executable bits + # "if we changed or they changed, change in merge" + a, b, c = mfa.get(f, 0), mfw[f], mf2[f] + mode = ((a^b) | (a^c)) ^ a + merge[f] = (m1.get(f, nullid), m2[f], mode) + s = 1 + # are we clobbering? + # is remote's version newer? + # or are we going back in time? + elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]): + self.ui.debug(_(" remote %s is newer, get\n") % f) + get[f] = m2[f] + s = 1 + elif f in umap or f in added: + # this unknown file is the same as the checkout + # we need to reset the dirstate if the file was added + get[f] = m2[f] + + if not s and mfw[f] != mf2[f]: + if force: + self.ui.debug(_(" updating permissions for %s\n") % f) + util.set_exec(self.wjoin(f), mf2[f]) + else: + a, b, c = mfa.get(f, 0), mfw[f], mf2[f] + mode = ((a^b) | (a^c)) ^ a + if mode != b: + self.ui.debug(_(" updating permissions for %s\n") + % f) + util.set_exec(self.wjoin(f), mode) + del m2[f] + elif f in ma: + if n != ma[f]: + r = _("d") + if not force and (linear_path or allow): + r = self.ui.prompt( + (_(" local changed %s which remote deleted\n") % f) + + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) + if r == _("d"): + remove.append(f) + else: + self.ui.debug(_("other deleted %s\n") % f) + remove.append(f) # other deleted it + else: + # file is created on branch or in working directory + if force and f not in umap: + self.ui.debug(_("remote deleted %s, clobbering\n") % f) + remove.append(f) + elif n == m1.get(f, nullid): # same as parent + if p2 == pa: # going backwards? + self.ui.debug(_("remote deleted %s\n") % f) + remove.append(f) + else: + self.ui.debug(_("local modified %s, keeping\n") % f) + else: + self.ui.debug(_("working dir created %s, keeping\n") % f) + + for f, n in m2.iteritems(): + if choose and not choose(f): + continue + if f[0] == "/": + continue + if f in ma and n != ma[f]: + r = _("k") + if not force and (linear_path or allow): + r = self.ui.prompt( + (_("remote changed %s which local deleted\n") % f) + + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) + if r == _("k"): + get[f] = n + elif f not in ma: + self.ui.debug(_("remote created %s\n") % f) + get[f] = n + else: + if force or p2 == pa: # going backwards? + self.ui.debug(_("local deleted %s, recreating\n") % f) + get[f] = n + else: + self.ui.debug(_("local deleted %s\n") % f) + + del mw, m1, m2, ma + + if force: + for f in merge: + get[f] = merge[f][1] + merge = {} + + if linear_path or force: + # we don't need to do any magic, just jump to the new rev + branch_merge = False + p1, p2 = p2, nullid + else: + if not allow: + self.ui.status(_("this update spans a branch" + " affecting the following files:\n")) + fl = merge.keys() + get.keys() + fl.sort() + for f in fl: + cf = "" + if f in merge: + cf = _(" (resolve)") + self.ui.status(" %s%s\n" % (f, cf)) + self.ui.warn(_("aborting update spanning branches!\n")) + self.ui.status(_("(use 'hg merge' to merge across branches" + " or 'hg update -C' to lose changes)\n")) + return 1 + branch_merge = True + + xp1 = hex(p1) + xp2 = hex(p2) + if p2 == nullid: xxp2 = '' + else: xxp2 = xp2 + + self.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2) + + # get the files we don't need to change + files = get.keys() + files.sort() + for f in files: + if f[0] == "/": + continue + self.ui.note(_("getting %s\n") % f) + t = self.file(f).read(get[f]) + self.wwrite(f, t) + util.set_exec(self.wjoin(f), mf2[f]) + if moddirstate: + if branch_merge: + self.dirstate.update([f], 'n', st_mtime=-1) + else: + self.dirstate.update([f], 'n') + + # merge the tricky bits + failedmerge = [] + files = merge.keys() + files.sort() + for f in files: + self.ui.status(_("merging %s\n") % f) + my, other, flag = merge[f] + ret = self.merge3(f, my, other, xp1, xp2) + if ret: + err = True + failedmerge.append(f) + util.set_exec(self.wjoin(f), flag) + if moddirstate: + if branch_merge: + # We've done a branch merge, mark this file as merged + # so that we properly record the merger later + self.dirstate.update([f], 'm') + else: + # We've update-merged a locally modified file, so + # we set the dirstate to emulate a normal checkout + # of that file some time in the past. Thus our + # merge will appear as a normal local file + # modification. + f_len = len(self.file(f).read(other)) + self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) + + remove.sort() + for f in remove: + self.ui.note(_("removing %s\n") % f) + util.audit_path(f) + try: + util.unlink(self.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + self.ui.warn(_("update failed to remove %s: %s!\n") % + (f, inst.strerror)) + if moddirstate: + if branch_merge: + self.dirstate.update(remove, 'r') + else: + self.dirstate.forget(remove) + + if moddirstate: + self.dirstate.setparents(p1, p2) + + if show_stats: + stats = ((len(get), _("updated")), + (len(merge) - len(failedmerge), _("merged")), + (len(remove), _("removed")), + (len(failedmerge), _("unresolved"))) + note = ", ".join([_("%d files %s") % s for s in stats]) + self.ui.status("%s\n" % note) + if moddirstate: + if branch_merge: + if failedmerge: + self.ui.status(_("There are unresolved merges," + " you can redo the full merge using:\n" + " hg update -C %s\n" + " hg merge %s\n" + % (self.changelog.rev(p1), + self.changelog.rev(p2)))) + else: + self.ui.status(_("(branch merge, don't forget to commit)\n")) + elif failedmerge: + self.ui.status(_("There are unresolved merges with" + " locally modified files.\n")) + + self.hook('update', parent1=xp1, parent2=xxp2, error=int(err)) + return err + + def merge3(self, fn, my, other, p1, p2): + """perform a 3-way merge in the working directory""" + + def temp(prefix, node): + pre = "%s~%s." % (os.path.basename(fn), prefix) + (fd, name) = tempfile.mkstemp(prefix=pre) + f = os.fdopen(fd, "wb") + self.wwrite(fn, fl.read(node), f) + f.close() + return name + + fl = self.file(fn) + base = fl.ancestor(my, other) + a = self.wjoin(fn) + b = temp("base", base) + c = temp("other", other) + + self.ui.note(_("resolving %s\n") % fn) + self.ui.debug(_("file %s: my %s other %s ancestor %s\n") % + (fn, short(my), short(other), short(base))) + + cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge") + or "hgmerge") + r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root, + environ={'HG_FILE': fn, + 'HG_MY_NODE': p1, + 'HG_OTHER_NODE': p2, + 'HG_FILE_MY_NODE': hex(my), + 'HG_FILE_OTHER_NODE': hex(other), + 'HG_FILE_BASE_NODE': hex(base)}) + if r: + self.ui.warn(_("merging %s failed!\n") % fn) + + os.unlink(b) + os.unlink(c) + return r + + def verify(self): + filelinkrevs = {} + filenodes = {} + changesets = revisions = files = 0 + errors = [0] + warnings = [0] + neededmanifests = {} + + def err(msg): + self.ui.warn(msg + "\n") + errors[0] += 1 + + def warn(msg): + self.ui.warn(msg + "\n") + warnings[0] += 1 + + def checksize(obj, name): + d = obj.checksize() + if d[0]: + err(_("%s data length off by %d bytes") % (name, d[0])) + if d[1]: + err(_("%s index contains %d extra bytes") % (name, d[1])) + + def checkversion(obj, name): + if obj.version != revlog.REVLOGV0: + if not revlogv1: + warn(_("warning: `%s' uses revlog format 1") % name) + elif revlogv1: + warn(_("warning: `%s' uses revlog format 0") % name) + + revlogv1 = self.revlogversion != revlog.REVLOGV0 + if self.ui.verbose or revlogv1 != self.revlogv1: + self.ui.status(_("repository uses revlog format %d\n") % + (revlogv1 and 1 or 0)) + + seen = {} + self.ui.status(_("checking changesets\n")) + checksize(self.changelog, "changelog") + + for i in range(self.changelog.count()): + changesets += 1 + n = self.changelog.node(i) + l = self.changelog.linkrev(n) + if l != i: + err(_("incorrect link (%d) for changeset revision %d") %(l, i)) + if n in seen: + err(_("duplicate changeset at revision %d") % i) + seen[n] = 1 + + for p in self.changelog.parents(n): + if p not in self.changelog.nodemap: + err(_("changeset %s has unknown parent %s") % + (short(n), short(p))) + try: + changes = self.changelog.read(n) + except KeyboardInterrupt: + self.ui.warn(_("interrupted")) + raise + except Exception, inst: + err(_("unpacking changeset %s: %s") % (short(n), inst)) + continue + + neededmanifests[changes[0]] = n + + for f in changes[3]: + filelinkrevs.setdefault(f, []).append(i) + + seen = {} + self.ui.status(_("checking manifests\n")) + checkversion(self.manifest, "manifest") + checksize(self.manifest, "manifest") + + for i in range(self.manifest.count()): + n = self.manifest.node(i) + l = self.manifest.linkrev(n) + + if l < 0 or l >= self.changelog.count(): + err(_("bad manifest link (%d) at revision %d") % (l, i)) + + if n in neededmanifests: + del neededmanifests[n] + + if n in seen: + err(_("duplicate manifest at revision %d") % i) + + seen[n] = 1 + + for p in self.manifest.parents(n): + if p not in self.manifest.nodemap: + err(_("manifest %s has unknown parent %s") % + (short(n), short(p))) + + try: + delta = mdiff.patchtext(self.manifest.delta(n)) + except KeyboardInterrupt: + self.ui.warn(_("interrupted")) + raise + except Exception, inst: + err(_("unpacking manifest %s: %s") % (short(n), inst)) + continue + + try: + ff = [ l.split('\0') for l in delta.splitlines() ] + for f, fn in ff: + filenodes.setdefault(f, {})[bin(fn[:40])] = 1 + except (ValueError, TypeError), inst: + err(_("broken delta in manifest %s: %s") % (short(n), inst)) + + self.ui.status(_("crosschecking files in changesets and manifests\n")) + + for m, c in neededmanifests.items(): + err(_("Changeset %s refers to unknown manifest %s") % + (short(m), short(c))) + del neededmanifests + + for f in filenodes: + if f not in filelinkrevs: + err(_("file %s in manifest but not in changesets") % f) + + for f in filelinkrevs: + if f not in filenodes: + err(_("file %s in changeset but not in manifest") % f) + + self.ui.status(_("checking files\n")) + ff = filenodes.keys() + ff.sort() + for f in ff: + if f == "/dev/null": + continue + files += 1 + if not f: + err(_("file without name in manifest %s") % short(n)) + continue + fl = self.file(f) + checkversion(fl, f) + checksize(fl, f) + + nodes = {nullid: 1} + seen = {} + for i in range(fl.count()): + revisions += 1 + n = fl.node(i) + + if n in seen: + err(_("%s: duplicate revision %d") % (f, i)) + if n not in filenodes[f]: + err(_("%s: %d:%s not in manifests") % (f, i, short(n))) + else: + del filenodes[f][n] + + flr = fl.linkrev(n) + if flr not in filelinkrevs.get(f, []): + err(_("%s:%s points to unexpected changeset %d") + % (f, short(n), flr)) + else: + filelinkrevs[f].remove(flr) + + # verify contents + try: + t = fl.read(n) + except KeyboardInterrupt: + self.ui.warn(_("interrupted")) + raise + except Exception, inst: + err(_("unpacking file %s %s: %s") % (f, short(n), inst)) + + # verify parents + (p1, p2) = fl.parents(n) + if p1 not in nodes: + err(_("file %s:%s unknown parent 1 %s") % + (f, short(n), short(p1))) + if p2 not in nodes: + err(_("file %s:%s unknown parent 2 %s") % + (f, short(n), short(p1))) + nodes[n] = 1 + + # cross-check + for node in filenodes[f]: + err(_("node %s in manifests not in %s") % (hex(node), f)) + + self.ui.status(_("%d files, %d changesets, %d total revisions\n") % + (files, changesets, revisions)) + + if warnings[0]: + self.ui.warn(_("%d warnings encountered!\n") % warnings[0]) + if errors[0]: + self.ui.warn(_("%d integrity errors encountered!\n") % errors[0]) + return 1 + +# used to avoid circular references so destructors work +def aftertrans(base): + p = base + def a(): + util.rename(os.path.join(p, "journal"), os.path.join(p, "undo")) + util.rename(os.path.join(p, "journal.dirstate"), + os.path.join(p, "undo.dirstate")) + return a +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/lock.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,118 @@ +# lock.py - simple locking scheme for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from demandload import * +demandload(globals(), 'errno os socket time util') + +class LockException(IOError): + def __init__(self, errno, strerror, filename, desc): + IOError.__init__(self, errno, strerror, filename) + self.desc = desc + +class LockHeld(LockException): + def __init__(self, errno, filename, desc, locker): + LockException.__init__(self, errno, 'Lock held', filename, desc) + self.locker = locker + +class LockUnavailable(LockException): + pass + +class lock(object): + # lock is symlink on platforms that support it, file on others. + + # symlink is used because create of directory entry and contents + # are atomic even over nfs. + + # old-style lock: symlink to pid + # new-style lock: symlink to hostname:pid + + def __init__(self, file, timeout=-1, releasefn=None, desc=None): + self.f = file + self.held = 0 + self.timeout = timeout + self.releasefn = releasefn + self.id = None + self.host = None + self.pid = None + self.desc = desc + self.lock() + + def __del__(self): + self.release() + + def lock(self): + timeout = self.timeout + while 1: + try: + self.trylock() + return 1 + except LockHeld, inst: + if timeout != 0: + time.sleep(1) + if timeout > 0: + timeout -= 1 + continue + raise LockHeld(errno.ETIMEDOUT, inst.filename, self.desc, + inst.locker) + + def trylock(self): + if self.id is None: + self.host = socket.gethostname() + self.pid = os.getpid() + self.id = '%s:%s' % (self.host, self.pid) + while not self.held: + try: + util.makelock(self.id, self.f) + self.held = 1 + except (OSError, IOError), why: + if why.errno == errno.EEXIST: + locker = self.testlock() + if locker: + raise LockHeld(errno.EAGAIN, self.f, self.desc, + locker) + else: + raise LockUnavailable(why.errno, why.strerror, + why.filename, self.desc) + + def testlock(self): + '''return id of locker if lock is valid, else None.''' + # if old-style lock, we cannot tell what machine locker is on. + # with new-style lock, if locker is on this machine, we can + # see if locker is alive. if locker is on this machine but + # not alive, we can safely break lock. + locker = util.readlock(self.f) + c = locker.find(':') + if c == -1: + return locker + host = locker[:c] + if host != self.host: + return locker + try: + pid = int(locker[c+1:]) + except: + return locker + if util.testpid(pid): + return locker + # if locker dead, break lock. must do this with another lock + # held, or can race and break valid lock. + try: + l = lock(self.f + '.break') + l.trylock() + os.unlink(self.f) + l.release() + except (LockHeld, LockUnavailable): + return locker + + def release(self): + if self.held: + self.held = 0 + if self.releasefn: + self.releasefn() + try: + os.unlink(self.f) + except: pass +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/manifest.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,189 @@ +# manifest.py - manifest revision class for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import struct +from revlog import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "bisect array") + +class manifest(revlog): + def __init__(self, opener, defversion=REVLOGV0): + self.mapcache = None + self.listcache = None + revlog.__init__(self, opener, "00manifest.i", "00manifest.d", + defversion) + + def read(self, node): + if node == nullid: return {} # don't upset local cache + if self.mapcache and self.mapcache[0] == node: + return self.mapcache[1] + text = self.revision(node) + map = {} + flag = {} + self.listcache = array.array('c', text) + lines = text.splitlines(1) + for l in lines: + (f, n) = l.split('\0') + map[f] = bin(n[:40]) + flag[f] = (n[40:-1] == "x") + self.mapcache = (node, map, flag) + return map + + def readflags(self, node): + if node == nullid: return {} # don't upset local cache + if not self.mapcache or self.mapcache[0] != node: + self.read(node) + return self.mapcache[2] + + def diff(self, a, b): + return mdiff.textdiff(str(a), str(b)) + + def _search(self, m, s, lo=0, hi=None): + '''return a tuple (start, end) that says where to find s within m. + + If the string is found m[start:end] are the line containing + that string. If start == end the string was not found and + they indicate the proper sorted insertion point. This was + taken from bisect_left, and modified to find line start/end as + it goes along. + + m should be a buffer or a string + s is a string''' + def advance(i, c): + while i < lenm and m[i] != c: + i += 1 + return i + lenm = len(m) + if not hi: + hi = lenm + while lo < hi: + mid = (lo + hi) // 2 + start = mid + while start > 0 and m[start-1] != '\n': + start -= 1 + end = advance(start, '\0') + if m[start:end] < s: + # we know that after the null there are 40 bytes of sha1 + # this translates to the bisect lo = mid + 1 + lo = advance(end + 40, '\n') + 1 + else: + # this translates to the bisect hi = mid + hi = start + end = advance(lo, '\0') + found = m[lo:end] + if cmp(s, found) == 0: + # we know that after the null there are 40 bytes of sha1 + end = advance(end + 40, '\n') + return (lo, end+1) + else: + return (lo, lo) + + def find(self, node, f): + '''look up entry for a single file efficiently. + return (node, flag) pair if found, (None, None) if not.''' + if self.mapcache and node == self.mapcache[0]: + return self.mapcache[1].get(f), self.mapcache[2].get(f) + text = self.revision(node) + start, end = self._search(text, f) + if start == end: + return None, None + l = text[start:end] + f, n = l.split('\0') + return bin(n[:40]), n[40:-1] == 'x' + + def add(self, map, flags, transaction, link, p1=None, p2=None, + changed=None): + # apply the changes collected during the bisect loop to our addlist + # return a delta suitable for addrevision + def addlistdelta(addlist, x): + # start from the bottom up + # so changes to the offsets don't mess things up. + i = len(x) + while i > 0: + i -= 1 + start = x[i][0] + end = x[i][1] + if x[i][2]: + addlist[start:end] = array.array('c', x[i][2]) + else: + del addlist[start:end] + return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \ + for d in x ]) + + # if we're using the listcache, make sure it is valid and + # parented by the same node we're diffing against + if not changed or not self.listcache or not p1 or \ + self.mapcache[0] != p1: + files = map.keys() + files.sort() + + # if this is changed to support newlines in filenames, + # be sure to check the templates/ dir again (especially *-raw.tmpl) + text = ["%s\000%s%s\n" % + (f, hex(map[f]), flags[f] and "x" or '') + for f in files] + self.listcache = array.array('c', "".join(text)) + cachedelta = None + else: + addlist = self.listcache + + # combine the changed lists into one list for sorting + work = [[x, 0] for x in changed[0]] + work[len(work):] = [[x, 1] for x in changed[1]] + work.sort() + + delta = [] + dstart = None + dend = None + dline = [""] + start = 0 + # zero copy representation of addlist as a buffer + addbuf = buffer(addlist) + + # start with a readonly loop that finds the offset of + # each line and creates the deltas + for w in work: + f = w[0] + # bs will either be the index of the item or the insert point + start, end = self._search(addbuf, f, start) + if w[1] == 0: + l = "%s\000%s%s\n" % (f, hex(map[f]), + flags[f] and "x" or '') + else: + l = "" + if start == end and w[1] == 1: + # item we want to delete was not found, error out + raise AssertionError( + _("failed to remove %s from manifest\n") % f) + if dstart != None and dstart <= start and dend >= start: + if dend < end: + dend = end + if l: + dline.append(l) + else: + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + dstart = start + dend = end + dline = [l] + + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + # apply the delta to the addlist, and get a delta for addrevision + cachedelta = addlistdelta(addlist, delta) + + # the delta is only valid if we've been processing the tip revision + if self.mapcache[0] != self.tip(): + cachedelta = None + self.listcache = addlist + + n = self.addrevision(buffer(self.listcache), transaction, link, p1, \ + p2, cachedelta) + self.mapcache = (n, map, flags) + + return n
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/mdiff.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,205 @@ +# mdiff.py - diff and patch routines for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from demandload import demandload +import struct, bdiff, util, mpatch +demandload(globals(), "re") + +def splitnewlines(text): + '''like str.splitlines, but only split on newlines.''' + lines = [l + '\n' for l in text.split('\n')] + if lines: + if lines[-1] == '\n': + lines.pop() + else: + lines[-1] = lines[-1][:-1] + return lines + +def unidiff(a, ad, b, bd, fn, r=None, text=False, + showfunc=False, ignorews=False): + + if not a and not b: return "" + epoch = util.datestr((0, 0)) + + if not text and (util.binary(a) or util.binary(b)): + l = ['Binary file %s has changed\n' % fn] + elif not a: + b = splitnewlines(b) + if a is None: + l1 = "--- %s\t%s\n" % ("/dev/null", epoch) + else: + l1 = "--- %s\t%s\n" % ("a/" + fn, ad) + l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) + l3 = "@@ -0,0 +1,%d @@\n" % len(b) + l = [l1, l2, l3] + ["+" + e for e in b] + elif not b: + a = splitnewlines(a) + l1 = "--- %s\t%s\n" % ("a/" + fn, ad) + if b is None: + l2 = "+++ %s\t%s\n" % ("/dev/null", epoch) + else: + l2 = "+++ %s\t%s\n" % ("b/" + fn, bd) + l3 = "@@ -1,%d +0,0 @@\n" % len(a) + l = [l1, l2, l3] + ["-" + e for e in a] + else: + al = splitnewlines(a) + bl = splitnewlines(b) + l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, + showfunc=showfunc, ignorews=ignorews)) + if not l: return "" + # difflib uses a space, rather than a tab + l[0] = "%s\t%s\n" % (l[0][:-2], ad) + l[1] = "%s\t%s\n" % (l[1][:-2], bd) + + for ln in xrange(len(l)): + if l[ln][-1] != '\n': + l[ln] += "\n\ No newline at end of file\n" + + if r: + l.insert(0, "diff %s %s\n" % + (' '.join(["-r %s" % rev for rev in r]), fn)) + + return "".join(l) + +# somewhat self contained replacement for difflib.unified_diff +# t1 and t2 are the text to be diffed +# l1 and l2 are the text broken up into lines +# header1 and header2 are the filenames for the diff output +# context is the number of context lines +# showfunc enables diff -p output +# ignorews ignores all whitespace changes in the diff +def bunidiff(t1, t2, l1, l2, header1, header2, context=3, showfunc=False, + ignorews=False): + def contextend(l, len): + ret = l + context + if ret > len: + ret = len + return ret + + def contextstart(l): + ret = l - context + if ret < 0: + return 0 + return ret + + def yieldhunk(hunk, header): + if header: + for x in header: + yield x + (astart, a2, bstart, b2, delta) = hunk + aend = contextend(a2, len(l1)) + alen = aend - astart + blen = b2 - bstart + aend - a2 + + func = "" + if showfunc: + # walk backwards from the start of the context + # to find a line starting with an alphanumeric char. + for x in xrange(astart, -1, -1): + t = l1[x].rstrip() + if funcre.match(t): + func = ' ' + t[:40] + break + + yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen, + bstart + 1, blen, func) + for x in delta: + yield x + for x in xrange(a2, aend): + yield ' ' + l1[x] + + header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ] + + if showfunc: + funcre = re.compile('\w') + if ignorews: + wsre = re.compile('[ \t]') + + # bdiff.blocks gives us the matching sequences in the files. The loop + # below finds the spaces between those matching sequences and translates + # them into diff output. + # + diff = bdiff.blocks(t1, t2) + hunk = None + for i in xrange(len(diff)): + # The first match is special. + # we've either found a match starting at line 0 or a match later + # in the file. If it starts later, old and new below will both be + # empty and we'll continue to the next match. + if i > 0: + s = diff[i-1] + else: + s = [0, 0, 0, 0] + delta = [] + s1 = diff[i] + a1 = s[1] + a2 = s1[0] + b1 = s[3] + b2 = s1[2] + + old = l1[a1:a2] + new = l2[b1:b2] + + # bdiff sometimes gives huge matches past eof, this check eats them, + # and deals with the special first match case described above + if not old and not new: + continue + + if ignorews: + wsold = wsre.sub('', "".join(old)) + wsnew = wsre.sub('', "".join(new)) + if wsold == wsnew: + continue + + astart = contextstart(a1) + bstart = contextstart(b1) + prev = None + if hunk: + # join with the previous hunk if it falls inside the context + if astart < hunk[1] + context + 1: + prev = hunk + astart = hunk[1] + bstart = hunk[3] + else: + for x in yieldhunk(hunk, header): + yield x + # we only want to yield the header if the files differ, and + # we only want to yield it once. + header = None + if prev: + # we've joined the previous hunk, record the new ending points. + hunk[1] = a2 + hunk[3] = b2 + delta = hunk[4] + else: + # create a new hunk + hunk = [ astart, a2, bstart, b2, delta ] + + delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ] + delta[len(delta):] = [ '-' + x for x in old ] + delta[len(delta):] = [ '+' + x for x in new ] + + if hunk: + for x in yieldhunk(hunk, header): + yield x + +def patchtext(bin): + pos = 0 + t = [] + while pos < len(bin): + p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12]) + pos += 12 + t.append(bin[pos:pos + l]) + pos += l + return "".join(t) + +def patch(a, bin): + return mpatch.patches(a, [bin]) + +patches = mpatch.patches +patchedsize = mpatch.patchedsize +textdiff = bdiff.bdiff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/mpatch.c Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,404 @@ +/* + mpatch.c - efficient binary patching for Mercurial + + This implements a patch algorithm that's O(m + nlog n) where m is the + size of the output and n is the number of patches. + + Given a list of binary patches, it unpacks each into a hunk list, + then combines the hunk lists with a treewise recursion to form a + single hunk list. This hunk list is then applied to the original + text. + + The text (or binary) fragments are copied directly from their source + Python objects into a preallocated output string to avoid the + allocation of intermediate Python objects. Working memory is about 2x + the total number of hunks. + + Copyright 2005 Matt Mackall <mpm@selenic.com> + + This software may be used and distributed according to the terms + of the GNU General Public License, incorporated herein by reference. +*/ + +#include <Python.h> +#include <stdlib.h> +#include <string.h> +#ifdef _WIN32 +#ifdef _MSC_VER +#define inline __inline +typedef unsigned long uint32_t; +#else +#include <stdint.h> +#endif +static uint32_t ntohl(uint32_t x) +{ + return ((x & 0x000000ffUL) << 24) | + ((x & 0x0000ff00UL) << 8) | + ((x & 0x00ff0000UL) >> 8) | + ((x & 0xff000000UL) >> 24); +} +#else +#include <sys/types.h> +#include <arpa/inet.h> +#endif + +static char mpatch_doc[] = "Efficient binary patching."; +static PyObject *mpatch_Error; + +struct frag { + int start, end, len; + char *data; +}; + +struct flist { + struct frag *base, *head, *tail; +}; + +static struct flist *lalloc(int size) +{ + struct flist *a = NULL; + + a = (struct flist *)malloc(sizeof(struct flist)); + if (a) { + a->base = (struct frag *)malloc(sizeof(struct frag) * size); + if (a->base) { + a->head = a->tail = a->base; + return a; + } + free(a); + a = NULL; + } + if (!PyErr_Occurred()) + PyErr_NoMemory(); + return NULL; +} + +static void lfree(struct flist *a) +{ + if (a) { + free(a->base); + free(a); + } +} + +static int lsize(struct flist *a) +{ + return a->tail - a->head; +} + +/* move hunks in source that are less cut to dest, compensating + for changes in offset. the last hunk may be split if necessary. +*/ +static int gather(struct flist *dest, struct flist *src, int cut, int offset) +{ + struct frag *d = dest->tail, *s = src->head; + int postend, c, l; + + while (s != src->tail) { + if (s->start + offset >= cut) + break; /* we've gone far enough */ + + postend = offset + s->start + s->len; + if (postend <= cut) { + /* save this hunk */ + offset += s->start + s->len - s->end; + *d++ = *s++; + } + else { + /* break up this hunk */ + c = cut - offset; + if (s->end < c) + c = s->end; + l = cut - offset - s->start; + if (s->len < l) + l = s->len; + + offset += s->start + l - c; + + d->start = s->start; + d->end = c; + d->len = l; + d->data = s->data; + d++; + s->start = c; + s->len = s->len - l; + s->data = s->data + l; + + break; + } + } + + dest->tail = d; + src->head = s; + return offset; +} + +/* like gather, but with no output list */ +static int discard(struct flist *src, int cut, int offset) +{ + struct frag *s = src->head; + int postend, c, l; + + while (s != src->tail) { + if (s->start + offset >= cut) + break; + + postend = offset + s->start + s->len; + if (postend <= cut) { + offset += s->start + s->len - s->end; + s++; + } + else { + c = cut - offset; + if (s->end < c) + c = s->end; + l = cut - offset - s->start; + if (s->len < l) + l = s->len; + + offset += s->start + l - c; + s->start = c; + s->len = s->len - l; + s->data = s->data + l; + + break; + } + } + + src->head = s; + return offset; +} + +/* combine hunk lists a and b, while adjusting b for offset changes in a/ + this deletes a and b and returns the resultant list. */ +static struct flist *combine(struct flist *a, struct flist *b) +{ + struct flist *c = NULL; + struct frag *bh, *ct; + int offset = 0, post; + + if (a && b) + c = lalloc((lsize(a) + lsize(b)) * 2); + + if (c) { + + for (bh = b->head; bh != b->tail; bh++) { + /* save old hunks */ + offset = gather(c, a, bh->start, offset); + + /* discard replaced hunks */ + post = discard(a, bh->end, offset); + + /* insert new hunk */ + ct = c->tail; + ct->start = bh->start - offset; + ct->end = bh->end - post; + ct->len = bh->len; + ct->data = bh->data; + c->tail++; + offset = post; + } + + /* hold on to tail from a */ + memcpy(c->tail, a->head, sizeof(struct frag) * lsize(a)); + c->tail += lsize(a); + } + + lfree(a); + lfree(b); + return c; +} + +/* decode a binary patch into a hunk list */ +static struct flist *decode(char *bin, int len) +{ + struct flist *l; + struct frag *lt; + char *end = bin + len; + char decode[12]; /* for dealing with alignment issues */ + + /* assume worst case size, we won't have many of these lists */ + l = lalloc(len / 12); + if (!l) + return NULL; + + lt = l->tail; + + while (bin < end) { + memcpy(decode, bin, 12); + lt->start = ntohl(*(uint32_t *)decode); + lt->end = ntohl(*(uint32_t *)(decode + 4)); + lt->len = ntohl(*(uint32_t *)(decode + 8)); + lt->data = bin + 12; + bin += 12 + lt->len; + lt++; + } + + if (bin != end) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, "patch cannot be decoded"); + lfree(l); + return NULL; + } + + l->tail = lt; + return l; +} + +/* calculate the size of resultant text */ +static int calcsize(int len, struct flist *l) +{ + int outlen = 0, last = 0; + struct frag *f = l->head; + + while (f != l->tail) { + if (f->start < last || f->end > len) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, + "invalid patch"); + return -1; + } + outlen += f->start - last; + last = f->end; + outlen += f->len; + f++; + } + + outlen += len - last; + return outlen; +} + +static int apply(char *buf, char *orig, int len, struct flist *l) +{ + struct frag *f = l->head; + int last = 0; + char *p = buf; + + while (f != l->tail) { + if (f->start < last || f->end > len) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, + "invalid patch"); + return 0; + } + memcpy(p, orig + last, f->start - last); + p += f->start - last; + memcpy(p, f->data, f->len); + last = f->end; + p += f->len; + f++; + } + memcpy(p, orig + last, len - last); + return 1; +} + +/* recursively generate a patch of all bins between start and end */ +static struct flist *fold(PyObject *bins, int start, int end) +{ + int len; + + if (start + 1 == end) { + /* trivial case, output a decoded list */ + PyObject *tmp = PyList_GetItem(bins, start); + if (!tmp) + return NULL; + return decode(PyString_AsString(tmp), PyString_Size(tmp)); + } + + /* divide and conquer, memory management is elsewhere */ + len = (end - start) / 2; + return combine(fold(bins, start, start + len), + fold(bins, start + len, end)); +} + +static PyObject * +patches(PyObject *self, PyObject *args) +{ + PyObject *text, *bins, *result; + struct flist *patch; + char *in, *out; + int len, outlen; + + if (!PyArg_ParseTuple(args, "SO:mpatch", &text, &bins)) + return NULL; + + len = PyList_Size(bins); + if (!len) { + /* nothing to do */ + Py_INCREF(text); + return text; + } + + patch = fold(bins, 0, len); + if (!patch) + return NULL; + + outlen = calcsize(PyString_Size(text), patch); + if (outlen < 0) { + result = NULL; + goto cleanup; + } + result = PyString_FromStringAndSize(NULL, outlen); + if (!result) { + result = NULL; + goto cleanup; + } + in = PyString_AsString(text); + out = PyString_AsString(result); + if (!apply(out, in, PyString_Size(text), patch)) { + Py_DECREF(result); + result = NULL; + } +cleanup: + lfree(patch); + return result; +} + +/* calculate size of a patched file directly */ +static PyObject * +patchedsize(PyObject *self, PyObject *args) +{ + long orig, start, end, len, outlen = 0, last = 0; + int patchlen; + char *bin, *binend; + char decode[12]; /* for dealing with alignment issues */ + + if (!PyArg_ParseTuple(args, "ls#", &orig, &bin, &patchlen)) + return NULL; + + binend = bin + patchlen; + + while (bin < binend) { + memcpy(decode, bin, 12); + start = ntohl(*(uint32_t *)decode); + end = ntohl(*(uint32_t *)(decode + 4)); + len = ntohl(*(uint32_t *)(decode + 8)); + bin += 12 + len; + outlen += start - last; + last = end; + outlen += len; + } + + if (bin != binend) { + if (!PyErr_Occurred()) + PyErr_SetString(mpatch_Error, "patch cannot be decoded"); + return NULL; + } + + outlen += orig - last; + return Py_BuildValue("l", outlen); +} + +static PyMethodDef methods[] = { + {"patches", patches, METH_VARARGS, "apply a series of patches\n"}, + {"patchedsize", patchedsize, METH_VARARGS, "calculed patched size\n"}, + {NULL, NULL} +}; + +PyMODINIT_FUNC +initmpatch(void) +{ + Py_InitModule3("mpatch", methods, mpatch_doc); + mpatch_Error = PyErr_NewException("mpatch.mpatchError", NULL, NULL); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/node.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +""" +node.py - basic nodeid manipulation for mercurial + +Copyright 2005 Matt Mackall <mpm@selenic.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" + +import binascii + +nullid = "\0" * 20 + +def hex(node): + return binascii.hexlify(node) + +def bin(node): + return binascii.unhexlify(node) + +def short(node): + return hex(node[:6])
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/packagescan.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,113 @@ +# packagescan.py - Helper module for identifing used modules. +# Used for the py2exe distutil. +# This module must be the first mercurial module imported in setup.py +# +# Copyright 2005 Volker Kleinfeld <Volker.Kleinfeld@gmx.de> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. +import glob +import os +import sys +import ihooks +import types +import string + +# Install this module as fake demandload module +sys.modules['mercurial.demandload'] = sys.modules[__name__] + +# Requiredmodules contains the modules imported by demandload. +# Please note that demandload can be invoked before the +# mercurial.packagescan.scan method is invoked in case a mercurial +# module is imported. +requiredmodules = {} +def demandload(scope, modules): + """ fake demandload function that collects the required modules + foo import foo + foo bar import foo, bar + foo.bar import foo.bar + foo:bar from foo import bar + foo:bar,quux from foo import bar, quux + foo.bar:quux from foo.bar import quux""" + + for m in modules.split(): + mod = None + try: + module, fromlist = m.split(':') + fromlist = fromlist.split(',') + except: + module = m + fromlist = [] + mod = __import__(module, scope, scope, fromlist) + if fromlist == []: + # mod is only the top package, but we need all packages + comp = module.split('.') + i = 1 + mn = comp[0] + while True: + # mn and mod.__name__ might not be the same + scope[mn] = mod + requiredmodules[mod.__name__] = 1 + if len(comp) == i: break + mod = getattr(mod,comp[i]) + mn = string.join(comp[:i+1],'.') + i += 1 + else: + # mod is the last package in the component list + requiredmodules[mod.__name__] = 1 + for f in fromlist: + scope[f] = getattr(mod,f) + if type(scope[f]) == types.ModuleType: + requiredmodules[scope[f].__name__] = 1 + +def scan(libpath,packagename): + """ helper for finding all required modules of package <packagename> """ + # Use the package in the build directory + libpath = os.path.abspath(libpath) + sys.path.insert(0,libpath) + packdir = os.path.join(libpath,packagename) + # A normal import would not find the package in + # the build directory. ihook is used to force the import. + # After the package is imported the import scope for + # the following imports is settled. + p = importfrom(packdir) + globals()[packagename] = p + sys.modules[packagename] = p + # Fetch the python modules in the package + cwd = os.getcwd() + os.chdir(packdir) + pymodulefiles = glob.glob('*.py') + extmodulefiles = glob.glob('*.pyd') + os.chdir(cwd) + # Import all python modules and by that run the fake demandload + for m in pymodulefiles: + if m == '__init__.py': continue + tmp = {} + mname,ext = os.path.splitext(m) + fullname = packagename+'.'+mname + __import__(fullname,tmp,tmp) + requiredmodules[fullname] = 1 + # Import all extension modules and by that run the fake demandload + for m in extmodulefiles: + tmp = {} + mname,ext = os.path.splitext(m) + fullname = packagename+'.'+mname + __import__(fullname,tmp,tmp) + requiredmodules[fullname] = 1 + +def getmodules(): + return requiredmodules.keys() + +def importfrom(filename): + """ + import module/package from a named file and returns the module. + It does not check on sys.modules or includes the module in the scope. + """ + loader = ihooks.BasicModuleLoader() + path, file = os.path.split(filename) + name, ext = os.path.splitext(file) + m = loader.find_module_in_dir(name, path) + if not m: + raise ImportError, name + m = loader.load_module(name, m) + return m
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/remoterepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +# remoterepo - remote repositort proxy classes for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +class remoterepository(object): + def local(self): + return False + +class remotelock(object): + def __init__(self, repo): + self.repo = repo + def release(self): + self.repo.unlock() + self.repo = None + def __del__(self): + if self.repo: + self.release()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/repo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +# repo.py - repository base classes for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +class RepoError(Exception): pass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/revlog.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1272 @@ +""" +revlog.py - storage back-end for mercurial + +This provides efficient delta storage with O(1) retrieve and append +and O(changes) merge between branches + +Copyright 2005 Matt Mackall <mpm@selenic.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. +""" + +from node import * +from i18n import gettext as _ +from demandload import demandload +demandload(globals(), "binascii changegroup errno heapq mdiff os") +demandload(globals(), "sha struct util zlib") + +# revlog version strings +REVLOGV0 = 0 +REVLOGNG = 1 + +# revlog flags +REVLOGNGINLINEDATA = (1 << 16) +REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA + +REVLOG_DEFAULT_FORMAT = REVLOGNG +REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS + +def flagstr(flag): + if flag == "inline": + return REVLOGNGINLINEDATA + raise RevlogError(_("unknown revlog flag %s" % flag)) + +def hash(text, p1, p2): + """generate a hash from the given text and its parent hashes + + This hash combines both the current file contents and its history + in a manner that makes it easy to distinguish nodes with the same + content in the revision graph. + """ + l = [p1, p2] + l.sort() + s = sha.new(l[0]) + s.update(l[1]) + s.update(text) + return s.digest() + +def compress(text): + """ generate a possibly-compressed representation of text """ + if not text: return ("", text) + if len(text) < 44: + if text[0] == '\0': return ("", text) + return ('u', text) + bin = zlib.compress(text) + if len(bin) > len(text): + if text[0] == '\0': return ("", text) + return ('u', text) + return ("", bin) + +def decompress(bin): + """ decompress the given input """ + if not bin: return bin + t = bin[0] + if t == '\0': return bin + if t == 'x': return zlib.decompress(bin) + if t == 'u': return bin[1:] + raise RevlogError(_("unknown compression type %r") % t) + +indexformatv0 = ">4l20s20s20s" +v0shaoffset = 56 +# index ng: +# 6 bytes offset +# 2 bytes flags +# 4 bytes compressed length +# 4 bytes uncompressed length +# 4 bytes: base rev +# 4 bytes link rev +# 4 bytes parent 1 rev +# 4 bytes parent 2 rev +# 32 bytes: nodeid +indexformatng = ">Qiiiiii20s12x" +ngshaoffset = 32 +versionformat = ">i" + +class lazyparser(object): + """ + this class avoids the need to parse the entirety of large indices + """ + + # lazyparser is not safe to use on windows if win32 extensions not + # available. it keeps file handle open, which make it not possible + # to break hardlinks on local cloned repos. + safe_to_use = os.name != 'nt' or (not util.is_win_9x() and + hasattr(util, 'win32api')) + + def __init__(self, dataf, size, indexformat, shaoffset): + self.dataf = dataf + self.format = indexformat + self.s = struct.calcsize(indexformat) + self.indexformat = indexformat + self.datasize = size + self.l = size/self.s + self.index = [None] * self.l + self.map = {nullid: -1} + self.allmap = 0 + self.all = 0 + self.mapfind_count = 0 + self.shaoffset = shaoffset + + def loadmap(self): + """ + during a commit, we need to make sure the rev being added is + not a duplicate. This requires loading the entire index, + which is fairly slow. loadmap can load up just the node map, + which takes much less time. + """ + if self.allmap: return + start = 0 + end = self.datasize + self.allmap = 1 + cur = 0 + count = 0 + blocksize = self.s * 256 + self.dataf.seek(0) + while cur < end: + data = self.dataf.read(blocksize) + off = 0 + for x in xrange(256): + n = data[off + self.shaoffset:off + self.shaoffset + 20] + self.map[n] = count + count += 1 + if count >= self.l: + break + off += self.s + cur += blocksize + + def loadblock(self, blockstart, blocksize, data=None): + if self.all: return + if data is None: + self.dataf.seek(blockstart) + data = self.dataf.read(blocksize) + lend = len(data) / self.s + i = blockstart / self.s + off = 0 + for x in xrange(lend): + if self.index[i + x] == None: + b = data[off : off + self.s] + self.index[i + x] = b + n = b[self.shaoffset:self.shaoffset + 20] + self.map[n] = i + x + off += self.s + + def findnode(self, node): + """search backwards through the index file for a specific node""" + if self.allmap: return None + + # hg log will cause many many searches for the manifest + # nodes. After we get called a few times, just load the whole + # thing. + if self.mapfind_count > 8: + self.loadmap() + if node in self.map: + return node + return None + self.mapfind_count += 1 + last = self.l - 1 + while self.index[last] != None: + if last == 0: + self.all = 1 + self.allmap = 1 + return None + last -= 1 + end = (last + 1) * self.s + blocksize = self.s * 256 + while end >= 0: + start = max(end - blocksize, 0) + self.dataf.seek(start) + data = self.dataf.read(end - start) + findend = end - start + while True: + # we're searching backwards, so weh have to make sure + # we don't find a changeset where this node is a parent + off = data.rfind(node, 0, findend) + findend = off + if off >= 0: + i = off / self.s + off = i * self.s + n = data[off + self.shaoffset:off + self.shaoffset + 20] + if n == node: + self.map[n] = i + start / self.s + return node + else: + break + end -= blocksize + return None + + def loadindex(self, i=None, end=None): + if self.all: return + all = False + if i == None: + blockstart = 0 + blocksize = (512 / self.s) * self.s + end = self.datasize + all = True + else: + if end: + blockstart = i * self.s + end = end * self.s + blocksize = end - blockstart + else: + blockstart = (i & ~(32)) * self.s + blocksize = self.s * 64 + end = blockstart + blocksize + while blockstart < end: + self.loadblock(blockstart, blocksize) + blockstart += blocksize + if all: self.all = True + +class lazyindex(object): + """a lazy version of the index array""" + def __init__(self, parser): + self.p = parser + def __len__(self): + return len(self.p.index) + def load(self, pos): + if pos < 0: + pos += len(self.p.index) + self.p.loadindex(pos) + return self.p.index[pos] + def __getitem__(self, pos): + ret = self.p.index[pos] or self.load(pos) + if isinstance(ret, str): + ret = struct.unpack(self.p.indexformat, ret) + return ret + def __setitem__(self, pos, item): + self.p.index[pos] = item + def __delitem__(self, pos): + del self.p.index[pos] + def append(self, e): + self.p.index.append(e) + +class lazymap(object): + """a lazy version of the node map""" + def __init__(self, parser): + self.p = parser + def load(self, key): + n = self.p.findnode(key) + if n == None: + raise KeyError(key) + def __contains__(self, key): + if key in self.p.map: + return True + self.p.loadmap() + return key in self.p.map + def __iter__(self): + yield nullid + for i in xrange(self.p.l): + ret = self.p.index[i] + if not ret: + self.p.loadindex(i) + ret = self.p.index[i] + if isinstance(ret, str): + ret = struct.unpack(self.p.indexformat, ret) + yield ret[-1] + def __getitem__(self, key): + try: + return self.p.map[key] + except KeyError: + try: + self.load(key) + return self.p.map[key] + except KeyError: + raise KeyError("node " + hex(key)) + def __setitem__(self, key, val): + self.p.map[key] = val + def __delitem__(self, key): + del self.p.map[key] + +class RevlogError(Exception): pass + +class revlog(object): + """ + the underlying revision storage object + + A revlog consists of two parts, an index and the revision data. + + The index is a file with a fixed record size containing + information on each revision, includings its nodeid (hash), the + nodeids of its parents, the position and offset of its data within + the data file, and the revision it's based on. Finally, each entry + contains a linkrev entry that can serve as a pointer to external + data. + + The revision data itself is a linear collection of data chunks. + Each chunk represents a revision and is usually represented as a + delta against the previous chunk. To bound lookup time, runs of + deltas are limited to about 2 times the length of the original + version data. This makes retrieval of a version proportional to + its size, or O(1) relative to the number of revisions. + + Both pieces of the revlog are written to in an append-only + fashion, which means we never need to rewrite a file to insert or + remove data, and can use some simple techniques to avoid the need + for locking while reading. + """ + def __init__(self, opener, indexfile, datafile, + defversion=REVLOG_DEFAULT_VERSION): + """ + create a revlog object + + opener is a function that abstracts the file opening operation + and can be used to implement COW semantics or the like. + """ + self.indexfile = indexfile + self.datafile = datafile + self.opener = opener + + self.indexstat = None + self.cache = None + self.chunkcache = None + self.defversion = defversion + self.load() + + def load(self): + v = self.defversion + try: + f = self.opener(self.indexfile) + i = f.read(4) + f.seek(0) + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + i = "" + else: + try: + st = util.fstat(f) + except AttributeError, inst: + st = None + else: + oldst = self.indexstat + if (oldst and st.st_dev == oldst.st_dev + and st.st_ino == oldst.st_ino + and st.st_mtime == oldst.st_mtime + and st.st_ctime == oldst.st_ctime): + return + self.indexstat = st + if len(i) > 0: + v = struct.unpack(versionformat, i)[0] + flags = v & ~0xFFFF + fmt = v & 0xFFFF + if fmt == REVLOGV0: + if flags: + raise RevlogError(_("index %s invalid flags %x for format v0" % + (self.indexfile, flags))) + elif fmt == REVLOGNG: + if flags & ~REVLOGNGINLINEDATA: + raise RevlogError(_("index %s invalid flags %x for revlogng" % + (self.indexfile, flags))) + else: + raise RevlogError(_("index %s invalid format %d" % + (self.indexfile, fmt))) + self.version = v + if v == REVLOGV0: + self.indexformat = indexformatv0 + shaoffset = v0shaoffset + else: + self.indexformat = indexformatng + shaoffset = ngshaoffset + + if i: + if (lazyparser.safe_to_use and not self.inlinedata() and + st and st.st_size > 10000): + # big index, let's parse it on demand + parser = lazyparser(f, st.st_size, self.indexformat, shaoffset) + self.index = lazyindex(parser) + self.nodemap = lazymap(parser) + else: + self.parseindex(f, st) + if self.version != REVLOGV0: + e = list(self.index[0]) + type = self.ngtype(e[0]) + e[0] = self.offset_type(0, type) + self.index[0] = e + else: + self.nodemap = { nullid: -1} + self.index = [] + + + def parseindex(self, fp, st): + s = struct.calcsize(self.indexformat) + self.index = [] + self.nodemap = {nullid: -1} + inline = self.inlinedata() + n = 0 + leftover = None + while True: + if st: + data = fp.read(65536) + else: + # hack for httprangereader, it doesn't do partial reads well + data = fp.read() + if not data: + break + if n == 0 and self.inlinedata(): + # cache the first chunk + self.chunkcache = (0, data) + if leftover: + data = leftover + data + leftover = None + off = 0 + l = len(data) + while off < l: + if l - off < s: + leftover = data[off:] + break + cur = data[off:off + s] + off += s + e = struct.unpack(self.indexformat, cur) + self.index.append(e) + self.nodemap[e[-1]] = n + n += 1 + if inline: + off += e[1] + if off > l: + # some things don't seek well, just read it + fp.read(off - l) + if not st: + break + + + def ngoffset(self, q): + if q & 0xFFFF: + raise RevlogError(_('%s: incompatible revision flag %x') % + (self.indexfile, q)) + return long(q >> 16) + + def ngtype(self, q): + return int(q & 0xFFFF) + + def offset_type(self, offset, type): + return long(long(offset) << 16 | type) + + def loadindex(self, start, end): + """load a block of indexes all at once from the lazy parser""" + if isinstance(self.index, lazyindex): + self.index.p.loadindex(start, end) + + def loadindexmap(self): + """loads both the map and the index from the lazy parser""" + if isinstance(self.index, lazyindex): + p = self.index.p + p.loadindex() + self.nodemap = p.map + + def loadmap(self): + """loads the map from the lazy parser""" + if isinstance(self.nodemap, lazymap): + self.nodemap.p.loadmap() + self.nodemap = self.nodemap.p.map + + def inlinedata(self): return self.version & REVLOGNGINLINEDATA + def tip(self): return self.node(len(self.index) - 1) + def count(self): return len(self.index) + def node(self, rev): + return (rev < 0) and nullid or self.index[rev][-1] + def rev(self, node): + try: + return self.nodemap[node] + except KeyError: + raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node))) + def linkrev(self, node): return self.index[self.rev(node)][-4] + def parents(self, node): + if node == nullid: return (nullid, nullid) + r = self.rev(node) + d = self.index[r][-3:-1] + if self.version == REVLOGV0: + return d + return [ self.node(x) for x in d ] + def start(self, rev): + if rev < 0: + return -1 + if self.version != REVLOGV0: + return self.ngoffset(self.index[rev][0]) + return self.index[rev][0] + + def end(self, rev): return self.start(rev) + self.length(rev) + + def size(self, rev): + """return the length of the uncompressed text for a given revision""" + l = -1 + if self.version != REVLOGV0: + l = self.index[rev][2] + if l >= 0: + return l + + t = self.revision(self.node(rev)) + return len(t) + + # alternate implementation, The advantage to this code is it + # will be faster for a single revision. But, the results are not + # cached, so finding the size of every revision will be slower. + """ + if self.cache and self.cache[1] == rev: + return len(self.cache[2]) + + base = self.base(rev) + if self.cache and self.cache[1] >= base and self.cache[1] < rev: + base = self.cache[1] + text = self.cache[2] + else: + text = self.revision(self.node(base)) + + l = len(text) + for x in xrange(base + 1, rev + 1): + l = mdiff.patchedsize(l, self.chunk(x)) + return l + """ + + def length(self, rev): + if rev < 0: + return 0 + else: + return self.index[rev][1] + def base(self, rev): return (rev < 0) and rev or self.index[rev][-5] + + def reachable(self, rev, stop=None): + reachable = {} + visit = [rev] + reachable[rev] = 1 + if stop: + stopn = self.rev(stop) + else: + stopn = 0 + while visit: + n = visit.pop(0) + if n == stop: + continue + if n == nullid: + continue + for p in self.parents(n): + if self.rev(p) < stopn: + continue + if p not in reachable: + reachable[p] = 1 + visit.append(p) + return reachable + + def nodesbetween(self, roots=None, heads=None): + """Return a tuple containing three elements. Elements 1 and 2 contain + a final list bases and heads after all the unreachable ones have been + pruned. Element 0 contains a topologically sorted list of all + + nodes that satisfy these constraints: + 1. All nodes must be descended from a node in roots (the nodes on + roots are considered descended from themselves). + 2. All nodes must also be ancestors of a node in heads (the nodes in + heads are considered to be their own ancestors). + + If roots is unspecified, nullid is assumed as the only root. + If heads is unspecified, it is taken to be the output of the + heads method (i.e. a list of all nodes in the repository that + have no children).""" + nonodes = ([], [], []) + if roots is not None: + roots = list(roots) + if not roots: + return nonodes + lowestrev = min([self.rev(n) for n in roots]) + else: + roots = [nullid] # Everybody's a descendent of nullid + lowestrev = -1 + if (lowestrev == -1) and (heads is None): + # We want _all_ the nodes! + return ([self.node(r) for r in xrange(0, self.count())], + [nullid], list(self.heads())) + if heads is None: + # All nodes are ancestors, so the latest ancestor is the last + # node. + highestrev = self.count() - 1 + # Set ancestors to None to signal that every node is an ancestor. + ancestors = None + # Set heads to an empty dictionary for later discovery of heads + heads = {} + else: + heads = list(heads) + if not heads: + return nonodes + ancestors = {} + # Start at the top and keep marking parents until we're done. + nodestotag = heads[:] + # Turn heads into a dictionary so we can remove 'fake' heads. + # Also, later we will be using it to filter out the heads we can't + # find from roots. + heads = dict.fromkeys(heads, 0) + # Remember where the top was so we can use it as a limit later. + highestrev = max([self.rev(n) for n in nodestotag]) + while nodestotag: + # grab a node to tag + n = nodestotag.pop() + # Never tag nullid + if n == nullid: + continue + # A node's revision number represents its place in a + # topologically sorted list of nodes. + r = self.rev(n) + if r >= lowestrev: + if n not in ancestors: + # If we are possibly a descendent of one of the roots + # and we haven't already been marked as an ancestor + ancestors[n] = 1 # Mark as ancestor + # Add non-nullid parents to list of nodes to tag. + nodestotag.extend([p for p in self.parents(n) if + p != nullid]) + elif n in heads: # We've seen it before, is it a fake head? + # So it is, real heads should not be the ancestors of + # any other heads. + heads.pop(n) + if not ancestors: + return nonodes + # Now that we have our set of ancestors, we want to remove any + # roots that are not ancestors. + + # If one of the roots was nullid, everything is included anyway. + if lowestrev > -1: + # But, since we weren't, let's recompute the lowest rev to not + # include roots that aren't ancestors. + + # Filter out roots that aren't ancestors of heads + roots = [n for n in roots if n in ancestors] + # Recompute the lowest revision + if roots: + lowestrev = min([self.rev(n) for n in roots]) + else: + # No more roots? Return empty list + return nonodes + else: + # We are descending from nullid, and don't need to care about + # any other roots. + lowestrev = -1 + roots = [nullid] + # Transform our roots list into a 'set' (i.e. a dictionary where the + # values don't matter. + descendents = dict.fromkeys(roots, 1) + # Also, keep the original roots so we can filter out roots that aren't + # 'real' roots (i.e. are descended from other roots). + roots = descendents.copy() + # Our topologically sorted list of output nodes. + orderedout = [] + # Don't start at nullid since we don't want nullid in our output list, + # and if nullid shows up in descedents, empty parents will look like + # they're descendents. + for r in xrange(max(lowestrev, 0), highestrev + 1): + n = self.node(r) + isdescendent = False + if lowestrev == -1: # Everybody is a descendent of nullid + isdescendent = True + elif n in descendents: + # n is already a descendent + isdescendent = True + # This check only needs to be done here because all the roots + # will start being marked is descendents before the loop. + if n in roots: + # If n was a root, check if it's a 'real' root. + p = tuple(self.parents(n)) + # If any of its parents are descendents, it's not a root. + if (p[0] in descendents) or (p[1] in descendents): + roots.pop(n) + else: + p = tuple(self.parents(n)) + # A node is a descendent if either of its parents are + # descendents. (We seeded the dependents list with the roots + # up there, remember?) + if (p[0] in descendents) or (p[1] in descendents): + descendents[n] = 1 + isdescendent = True + if isdescendent and ((ancestors is None) or (n in ancestors)): + # Only include nodes that are both descendents and ancestors. + orderedout.append(n) + if (ancestors is not None) and (n in heads): + # We're trying to figure out which heads are reachable + # from roots. + # Mark this head as having been reached + heads[n] = 1 + elif ancestors is None: + # Otherwise, we're trying to discover the heads. + # Assume this is a head because if it isn't, the next step + # will eventually remove it. + heads[n] = 1 + # But, obviously its parents aren't. + for p in self.parents(n): + heads.pop(p, None) + heads = [n for n in heads.iterkeys() if heads[n] != 0] + roots = roots.keys() + assert orderedout + assert roots + assert heads + return (orderedout, roots, heads) + + def heads(self, start=None): + """return the list of all nodes that have no children + + if start is specified, only heads that are descendants of + start will be returned + + """ + if start is None: + start = nullid + reachable = {start: 1} + heads = {start: 1} + startrev = self.rev(start) + + for r in xrange(startrev + 1, self.count()): + n = self.node(r) + for pn in self.parents(n): + if pn in reachable: + reachable[n] = 1 + heads[n] = 1 + if pn in heads: + del heads[pn] + return heads.keys() + + def children(self, node): + """find the children of a given node""" + c = [] + p = self.rev(node) + for r in range(p + 1, self.count()): + n = self.node(r) + for pn in self.parents(n): + if pn == node: + c.append(n) + continue + elif pn == nullid: + continue + return c + + def lookup(self, id): + """locate a node based on revision number or subset of hex nodeid""" + try: + rev = int(id) + if str(rev) != id: raise ValueError + if rev < 0: rev = self.count() + rev + if rev < 0 or rev >= self.count(): raise ValueError + return self.node(rev) + except (ValueError, OverflowError): + c = [] + for n in self.nodemap: + if hex(n).startswith(id): + c.append(n) + if len(c) > 1: raise RevlogError(_("Ambiguous identifier")) + if len(c) < 1: raise RevlogError(_("No match found")) + return c[0] + + return None + + def diff(self, a, b): + """return a delta between two revisions""" + return mdiff.textdiff(a, b) + + def patches(self, t, pl): + """apply a list of patches to a string""" + return mdiff.patches(t, pl) + + def chunk(self, rev, df=None, cachelen=4096): + start, length = self.start(rev), self.length(rev) + inline = self.inlinedata() + if inline: + start += (rev + 1) * struct.calcsize(self.indexformat) + end = start + length + def loadcache(df): + cache_length = max(cachelen, length) # 4k + if not df: + if inline: + df = self.opener(self.indexfile) + else: + df = self.opener(self.datafile) + df.seek(start) + self.chunkcache = (start, df.read(cache_length)) + + if not self.chunkcache: + loadcache(df) + + cache_start = self.chunkcache[0] + cache_end = cache_start + len(self.chunkcache[1]) + if start >= cache_start and end <= cache_end: + # it is cached + offset = start - cache_start + else: + loadcache(df) + offset = 0 + + #def checkchunk(): + # df = self.opener(self.datafile) + # df.seek(start) + # return df.read(length) + #assert s == checkchunk() + return decompress(self.chunkcache[1][offset:offset + length]) + + def delta(self, node): + """return or calculate a delta between a node and its predecessor""" + r = self.rev(node) + return self.revdiff(r - 1, r) + + def revdiff(self, rev1, rev2): + """return or calculate a delta between two revisions""" + b1 = self.base(rev1) + b2 = self.base(rev2) + if b1 == b2 and rev1 + 1 == rev2: + return self.chunk(rev2) + else: + return self.diff(self.revision(self.node(rev1)), + self.revision(self.node(rev2))) + + def revision(self, node): + """return an uncompressed revision of a given""" + if node == nullid: return "" + if self.cache and self.cache[0] == node: return self.cache[2] + + # look up what we need to read + text = None + rev = self.rev(node) + base = self.base(rev) + + if self.inlinedata(): + # we probably have the whole chunk cached + df = None + else: + df = self.opener(self.datafile) + + # do we have useful data cached? + if self.cache and self.cache[1] >= base and self.cache[1] < rev: + base = self.cache[1] + text = self.cache[2] + self.loadindex(base, rev + 1) + else: + self.loadindex(base, rev + 1) + text = self.chunk(base, df=df) + + bins = [] + for r in xrange(base + 1, rev + 1): + bins.append(self.chunk(r, df=df)) + + text = self.patches(text, bins) + + p1, p2 = self.parents(node) + if node != hash(text, p1, p2): + raise RevlogError(_("integrity check failed on %s:%d") + % (self.datafile, rev)) + + self.cache = (node, rev, text) + return text + + def checkinlinesize(self, tr, fp=None): + if not self.inlinedata(): + return + if not fp: + fp = self.opener(self.indexfile, 'r') + fp.seek(0, 2) + size = fp.tell() + if size < 131072: + return + trinfo = tr.find(self.indexfile) + if trinfo == None: + raise RevlogError(_("%s not found in the transaction" % + self.indexfile)) + + trindex = trinfo[2] + dataoff = self.start(trindex) + + tr.add(self.datafile, dataoff) + df = self.opener(self.datafile, 'w') + calc = struct.calcsize(self.indexformat) + for r in xrange(self.count()): + start = self.start(r) + (r + 1) * calc + length = self.length(r) + fp.seek(start) + d = fp.read(length) + df.write(d) + fp.close() + df.close() + fp = self.opener(self.indexfile, 'w', atomictemp=True) + self.version &= ~(REVLOGNGINLINEDATA) + if self.count(): + x = self.index[0] + e = struct.pack(self.indexformat, *x)[4:] + l = struct.pack(versionformat, self.version) + fp.write(l) + fp.write(e) + + for i in xrange(1, self.count()): + x = self.index[i] + e = struct.pack(self.indexformat, *x) + fp.write(e) + + # if we don't call rename, the temp file will never replace the + # real index + fp.rename() + + tr.replace(self.indexfile, trindex * calc) + self.chunkcache = None + + def addrevision(self, text, transaction, link, p1=None, p2=None, d=None): + """add a revision to the log + + text - the revision data to add + transaction - the transaction object used for rollback + link - the linkrev data to add + p1, p2 - the parent nodeids of the revision + d - an optional precomputed delta + """ + if text is None: text = "" + if p1 is None: p1 = self.tip() + if p2 is None: p2 = nullid + + node = hash(text, p1, p2) + + if node in self.nodemap: + return node + + n = self.count() + t = n - 1 + + if n: + base = self.base(t) + start = self.start(base) + end = self.end(t) + if not d: + prev = self.revision(self.tip()) + d = self.diff(prev, str(text)) + data = compress(d) + l = len(data[1]) + len(data[0]) + dist = end - start + l + + # full versions are inserted when the needed deltas + # become comparable to the uncompressed text + if not n or dist > len(text) * 2: + data = compress(text) + l = len(data[1]) + len(data[0]) + base = n + else: + base = self.base(t) + + offset = 0 + if t >= 0: + offset = self.end(t) + + if self.version == REVLOGV0: + e = (offset, l, base, link, p1, p2, node) + else: + e = (self.offset_type(offset, 0), l, len(text), + base, link, self.rev(p1), self.rev(p2), node) + + self.index.append(e) + self.nodemap[node] = n + entry = struct.pack(self.indexformat, *e) + + if not self.inlinedata(): + transaction.add(self.datafile, offset) + transaction.add(self.indexfile, n * len(entry)) + f = self.opener(self.datafile, "a") + if data[0]: + f.write(data[0]) + f.write(data[1]) + f.close() + f = self.opener(self.indexfile, "a") + else: + f = self.opener(self.indexfile, "a+") + f.seek(0, 2) + transaction.add(self.indexfile, f.tell(), self.count() - 1) + + if len(self.index) == 1 and self.version != REVLOGV0: + l = struct.pack(versionformat, self.version) + f.write(l) + entry = entry[4:] + + f.write(entry) + + if self.inlinedata(): + f.write(data[0]) + f.write(data[1]) + self.checkinlinesize(transaction, f) + + self.cache = (node, n, text) + return node + + def ancestor(self, a, b): + """calculate the least common ancestor of nodes a and b""" + + # start with some short cuts for the linear cases + if a == b: + return a + ra = self.rev(a) + rb = self.rev(b) + if ra < rb: + last = b + first = a + else: + last = a + first = b + + # reachable won't include stop in the list, so we have to use a parent + reachable = self.reachable(last, stop=self.parents(first)[0]) + if first in reachable: + return first + + # calculate the distance of every node from root + dist = {nullid: 0} + for i in xrange(self.count()): + n = self.node(i) + p1, p2 = self.parents(n) + dist[n] = max(dist[p1], dist[p2]) + 1 + + # traverse ancestors in order of decreasing distance from root + def ancestors(node): + # we store negative distances because heap returns smallest member + h = [(-dist[node], node)] + seen = {} + while h: + d, n = heapq.heappop(h) + if n not in seen: + seen[n] = 1 + yield (-d, n) + for p in self.parents(n): + heapq.heappush(h, (-dist[p], p)) + + def generations(node): + sg, s = None, {} + for g,n in ancestors(node): + if g != sg: + if sg: + yield sg, s + sg, s = g, {n:1} + else: + s[n] = 1 + yield sg, s + + x = generations(a) + y = generations(b) + gx = x.next() + gy = y.next() + + # increment each ancestor list until it is closer to root than + # the other, or they match + while 1: + #print "ancestor gen %s %s" % (gx[0], gy[0]) + if gx[0] == gy[0]: + # find the intersection + i = [ n for n in gx[1] if n in gy[1] ] + if i: + return i[0] + else: + #print "next" + gy = y.next() + gx = x.next() + elif gx[0] < gy[0]: + #print "next y" + gy = y.next() + else: + #print "next x" + gx = x.next() + + def group(self, nodelist, lookup, infocollect=None): + """calculate a delta group + + Given a list of changeset revs, return a set of deltas and + metadata corresponding to nodes. the first delta is + parent(nodes[0]) -> nodes[0] the receiver is guaranteed to + have this parent as it has all history before these + changesets. parent is parent[0] + """ + revs = [self.rev(n) for n in nodelist] + + # if we don't have any revisions touched by these changesets, bail + if not revs: + yield changegroup.closechunk() + return + + # add the parent of the first rev + p = self.parents(self.node(revs[0]))[0] + revs.insert(0, self.rev(p)) + + # build deltas + for d in xrange(0, len(revs) - 1): + a, b = revs[d], revs[d + 1] + nb = self.node(b) + + if infocollect is not None: + infocollect(nb) + + d = self.revdiff(a, b) + p = self.parents(nb) + meta = nb + p[0] + p[1] + lookup(nb) + yield changegroup.genchunk("%s%s" % (meta, d)) + + yield changegroup.closechunk() + + def addgroup(self, revs, linkmapper, transaction, unique=0): + """ + add a delta group + + given a set of deltas, add them to the revision log. the + first delta is against its parent, which should be in our + log, the rest are against the previous delta. + """ + + #track the base of the current delta log + r = self.count() + t = r - 1 + node = None + + base = prev = -1 + start = end = textlen = 0 + if r: + end = self.end(t) + + ifh = self.opener(self.indexfile, "a+") + ifh.seek(0, 2) + transaction.add(self.indexfile, ifh.tell(), self.count()) + if self.inlinedata(): + dfh = None + else: + transaction.add(self.datafile, end) + dfh = self.opener(self.datafile, "a") + + # loop through our set of deltas + chain = None + for chunk in revs: + node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80]) + link = linkmapper(cs) + if node in self.nodemap: + # this can happen if two branches make the same change + # if unique: + # raise RevlogError(_("already have %s") % hex(node[:4])) + chain = node + continue + delta = chunk[80:] + + for p in (p1, p2): + if not p in self.nodemap: + raise RevlogError(_("unknown parent %s") % short(p)) + + if not chain: + # retrieve the parent revision of the delta chain + chain = p1 + if not chain in self.nodemap: + raise RevlogError(_("unknown base %s") % short(chain[:4])) + + # full versions are inserted when the needed deltas become + # comparable to the uncompressed text or when the previous + # version is not the one we have a delta against. We use + # the size of the previous full rev as a proxy for the + # current size. + + if chain == prev: + tempd = compress(delta) + cdelta = tempd[0] + tempd[1] + textlen = mdiff.patchedsize(textlen, delta) + + if chain != prev or (end - start + len(cdelta)) > textlen * 2: + # flush our writes here so we can read it in revision + if dfh: + dfh.flush() + ifh.flush() + text = self.revision(chain) + text = self.patches(text, [delta]) + chk = self.addrevision(text, transaction, link, p1, p2) + if chk != node: + raise RevlogError(_("consistency error adding group")) + textlen = len(text) + else: + if self.version == REVLOGV0: + e = (end, len(cdelta), base, link, p1, p2, node) + else: + e = (self.offset_type(end, 0), len(cdelta), textlen, base, + link, self.rev(p1), self.rev(p2), node) + self.index.append(e) + self.nodemap[node] = r + if self.inlinedata(): + ifh.write(struct.pack(self.indexformat, *e)) + ifh.write(cdelta) + self.checkinlinesize(transaction, ifh) + if not self.inlinedata(): + dfh = self.opener(self.datafile, "a") + ifh = self.opener(self.indexfile, "a") + else: + if not dfh: + # addrevision switched from inline to conventional + # reopen the index + dfh = self.opener(self.datafile, "a") + ifh = self.opener(self.indexfile, "a") + dfh.write(cdelta) + ifh.write(struct.pack(self.indexformat, *e)) + + t, r, chain, prev = r, r + 1, node, node + base = self.base(t) + start = self.start(base) + end = self.end(t) + + return node + + def strip(self, rev, minlink): + if self.count() == 0 or rev >= self.count(): + return + + if isinstance(self.index, lazyindex): + self.loadindexmap() + + # When stripping away a revision, we need to make sure it + # does not actually belong to an older changeset. + # The minlink parameter defines the oldest revision + # we're allowed to strip away. + while minlink > self.index[rev][-4]: + rev += 1 + if rev >= self.count(): + return + + # first truncate the files on disk + end = self.start(rev) + if not self.inlinedata(): + df = self.opener(self.datafile, "a") + df.truncate(end) + end = rev * struct.calcsize(self.indexformat) + else: + end += rev * struct.calcsize(self.indexformat) + + indexf = self.opener(self.indexfile, "a") + indexf.truncate(end) + + # then reset internal state in memory to forget those revisions + self.cache = None + self.chunkcache = None + for x in xrange(rev, self.count()): + del self.nodemap[self.node(x)] + + del self.index[rev:] + + def checksize(self): + expected = 0 + if self.count(): + expected = self.end(self.count() - 1) + + try: + f = self.opener(self.datafile) + f.seek(0, 2) + actual = f.tell() + dd = actual - expected + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + dd = 0 + + try: + f = self.opener(self.indexfile) + f.seek(0, 2) + actual = f.tell() + s = struct.calcsize(self.indexformat) + i = actual / s + di = actual - (i * s) + if self.inlinedata(): + databytes = 0 + for r in xrange(self.count()): + databytes += self.length(r) + dd = 0 + di = actual - self.count() * s - databytes + except IOError, inst: + if inst.errno != errno.ENOENT: + raise + di = 0 + + return (dd, di) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/sshrepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,155 @@ +# sshrepo.py - ssh repository proxy class for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from node import * +from remoterepo import * +from i18n import gettext as _ +from demandload import * +demandload(globals(), "hg os re stat util") + +class sshrepository(remoterepository): + def __init__(self, ui, path): + self.url = path + self.ui = ui + + m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path) + if not m: + raise hg.RepoError(_("couldn't parse destination %s") % path) + + self.user = m.group(2) + self.host = m.group(3) + self.port = m.group(5) + self.path = m.group(7) or "." + + args = self.user and ("%s@%s" % (self.user, self.host)) or self.host + args = self.port and ("%s -p %s") % (args, self.port) or args + + sshcmd = self.ui.config("ui", "ssh", "ssh") + remotecmd = self.ui.config("ui", "remotecmd", "hg") + cmd = '%s %s "%s -R %s serve --stdio"' + cmd = cmd % (sshcmd, args, remotecmd, self.path) + + ui.note('running %s\n' % cmd) + self.pipeo, self.pipei, self.pipee = os.popen3(cmd, 'b') + + # skip any noise generated by remote shell + r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40))) + l1 = "" + l2 = "dummy" + max_noise = 500 + while l2 and max_noise: + l2 = r.readline() + self.readerr() + if l1 == "1\n" and l2 == "\n": + break + if l1: + ui.debug(_("remote: "), l1) + l1 = l2 + max_noise -= 1 + else: + if l1: + ui.debug(_("remote: "), l1) + raise hg.RepoError(_("no response from remote hg")) + + def readerr(self): + while 1: + size = util.fstat(self.pipee).st_size + if size == 0: break + l = self.pipee.readline() + if not l: break + self.ui.status(_("remote: "), l) + + def __del__(self): + try: + self.pipeo.close() + self.pipei.close() + # read the error descriptor until EOF + for l in self.pipee: + self.ui.status(_("remote: "), l) + self.pipee.close() + except: + pass + + def dev(self): + return -1 + + def do_cmd(self, cmd, **args): + self.ui.debug(_("sending %s command\n") % cmd) + self.pipeo.write("%s\n" % cmd) + for k, v in args.items(): + self.pipeo.write("%s %d\n" % (k, len(v))) + self.pipeo.write(v) + self.pipeo.flush() + + return self.pipei + + def call(self, cmd, **args): + r = self.do_cmd(cmd, **args) + l = r.readline() + self.readerr() + try: + l = int(l) + except: + raise hg.RepoError(_("unexpected response '%s'") % l) + return r.read(l) + + def lock(self): + self.call("lock") + return remotelock(self) + + def unlock(self): + self.call("unlock") + + def heads(self): + d = self.call("heads") + try: + return map(bin, d[:-1].split(" ")) + except: + raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "...")) + + def branches(self, nodes): + n = " ".join(map(hex, nodes)) + d = self.call("branches", nodes=n) + try: + br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ] + return br + except: + raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "...")) + + def between(self, pairs): + n = "\n".join(["-".join(map(hex, p)) for p in pairs]) + d = self.call("between", pairs=n) + try: + p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ] + return p + except: + raise hg.RepoError(_("unexpected response '%s'") % (d[:400] + "...")) + + def changegroup(self, nodes, kind): + n = " ".join(map(hex, nodes)) + f = self.do_cmd("changegroup", roots=n) + return self.pipei + + def addchangegroup(self, cg, source): + d = self.call("addchangegroup") + if d: + raise hg.RepoError(_("push refused: %s"), d) + + while 1: + d = cg.read(4096) + if not d: break + self.pipeo.write(d) + self.readerr() + + self.pipeo.flush() + + self.readerr() + l = int(self.pipei.readline()) + r = self.pipei.read(l) + if not r: + return 1 + return int(r)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/statichttprepo.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,48 @@ +# statichttprepo.py - simple http repository class for mercurial +# +# This provides read-only repo access to repositories exported via static http +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from demandload import demandload +demandload(globals(), "changelog filelog httprangereader") +demandload(globals(), "localrepo manifest os urllib urllib2") + +class rangereader(httprangereader.httprangereader): + def read(self, size=None): + try: + return httprangereader.httprangereader.read(self, size) + except urllib2.HTTPError, inst: + raise IOError(None, inst) + except urllib2.URLError, inst: + raise IOError(None, inst.reason[1]) + +def opener(base): + """return a function that opens files over http""" + p = base + def o(path, mode="r"): + f = os.path.join(p, urllib.quote(path)) + return rangereader(f) + return o + +class statichttprepository(localrepo.localrepository): + def __init__(self, ui, path): + self.path = (path + "/.hg") + self.ui = ui + self.revlogversion = 0 + self.opener = opener(self.path) + self.manifest = manifest.manifest(self.opener) + self.changelog = changelog.changelog(self.opener) + self.tagscache = None + self.nodetagscache = None + self.encodepats = None + self.decodepats = None + + def dev(self): + return -1 + + def local(self): + return False
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/templater.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,515 @@ +# templater.py - template expansion for output +# +# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import re +from demandload import demandload +from i18n import gettext as _ +from node import * +demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap") + +esctable = { + '\\': '\\', + 'r': '\r', + 't': '\t', + 'n': '\n', + 'v': '\v', + } + +def parsestring(s, quoted=True): + '''parse a string using simple c-like syntax. + string must be in quotes if quoted is True.''' + fp = cStringIO.StringIO() + if quoted: + first = s[0] + if len(s) < 2: raise SyntaxError(_('string too short')) + if first not in "'\"": raise SyntaxError(_('invalid quote')) + if s[-1] != first: raise SyntaxError(_('unmatched quotes')) + s = s[1:-1] + escape = False + for c in s: + if escape: + fp.write(esctable.get(c, c)) + escape = False + elif c == '\\': escape = True + elif quoted and c == first: raise SyntaxError(_('string ends early')) + else: fp.write(c) + if escape: raise SyntaxError(_('unterminated escape')) + return fp.getvalue() + +class templater(object): + '''template expansion engine. + + template expansion works like this. a map file contains key=value + pairs. if value is quoted, it is treated as string. otherwise, it + is treated as name of template file. + + templater is asked to expand a key in map. it looks up key, and + looks for atrings like this: {foo}. it expands {foo} by looking up + foo in map, and substituting it. expansion is recursive: it stops + when there is no more {foo} to replace. + + expansion also allows formatting and filtering. + + format uses key to expand each item in list. syntax is + {key%format}. + + filter uses function to transform value. syntax is + {key|filter1|filter2|...}.''' + + def __init__(self, mapfile, filters={}, defaults={}, cache={}): + '''set up template engine. + mapfile is name of file to read map definitions from. + filters is dict of functions. each transforms a value into another. + defaults is dict of default map definitions.''' + self.mapfile = mapfile or 'template' + self.cache = cache.copy() + self.map = {} + self.base = (mapfile and os.path.dirname(mapfile)) or '' + self.filters = filters + self.defaults = defaults + + if not mapfile: + return + i = 0 + for l in file(mapfile): + l = l.strip() + i += 1 + if not l or l[0] in '#;': continue + m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l) + if m: + key, val = m.groups() + if val[0] in "'\"": + try: + self.cache[key] = parsestring(val) + except SyntaxError, inst: + raise SyntaxError('%s:%s: %s' % + (mapfile, i, inst.args[0])) + else: + self.map[key] = os.path.join(self.base, val) + else: + raise SyntaxError(_("%s:%s: parse error") % (mapfile, i)) + + def __contains__(self, key): + return key in self.cache + + def __call__(self, t, **map): + '''perform expansion. + t is name of map element to expand. + map is added elements to use during expansion.''' + m = self.defaults.copy() + m.update(map) + try: + tmpl = self.cache[t] + except KeyError: + try: + tmpl = self.cache[t] = file(self.map[t]).read() + except IOError, inst: + raise IOError(inst.args[0], _('template file %s: %s') % + (self.map[t], inst.args[1])) + return self.template(tmpl, self.filters, **m) + + template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)" + r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)" + r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]") + + def template(self, tmpl, filters={}, **map): + lm = map.copy() + while tmpl: + m = self.template_re.search(tmpl) + if m: + start, end = m.span(0) + s, e = tmpl[start], tmpl[end - 1] + key = m.group(1) + if ((s == '#' and e != '#') or (s == '{' and e != '}')): + raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") % + (s, e, key)) + if start: + yield tmpl[:start] + v = map.get(key, "") + v = callable(v) and v(**map) or v + + format = m.group(2) + fl = m.group(4) + + if format: + q = v.__iter__ + for i in q(): + lm.update(i) + yield self(format[1:], **lm) + + v = "" + + elif fl: + for f in fl.split("|")[1:]: + v = filters[f](v) + + yield v + tmpl = tmpl[end:] + else: + yield tmpl + break + +agescales = [("second", 1), + ("minute", 60), + ("hour", 3600), + ("day", 3600 * 24), + ("week", 3600 * 24 * 7), + ("month", 3600 * 24 * 30), + ("year", 3600 * 24 * 365)] + +agescales.reverse() + +def age(date): + '''turn a (timestamp, tzoff) tuple into an age string.''' + + def plural(t, c): + if c == 1: + return t + return t + "s" + def fmt(t, c): + return "%d %s" % (c, plural(t, c)) + + now = time.time() + then = date[0] + delta = max(1, int(now - then)) + + for t, s in agescales: + n = delta / s + if n >= 2 or s == 1: + return fmt(t, n) + +def stringify(thing): + '''turn nested template iterator into string.''' + cs = cStringIO.StringIO() + def walk(things): + for t in things: + if hasattr(t, '__iter__'): + walk(t) + else: + cs.write(t) + walk(thing) + return cs.getvalue() + +para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M) +space_re = re.compile(r' +') + +def fill(text, width): + '''fill many paragraphs.''' + def findparas(): + start = 0 + while True: + m = para_re.search(text, start) + if not m: + w = len(text) + while w > start and text[w-1].isspace(): w -= 1 + yield text[start:w], text[w:] + break + yield text[start:m.start(0)], m.group(1) + start = m.end(1) + + fp = cStringIO.StringIO() + for para, rest in findparas(): + fp.write(space_re.sub(' ', textwrap.fill(para, width))) + fp.write(rest) + return fp.getvalue() + +def isodate(date): + '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.''' + return util.datestr(date, format='%Y-%m-%d %H:%M') + +def nl2br(text): + '''replace raw newlines with xhtml line breaks.''' + return text.replace('\n', '<br/>\n') + +def obfuscate(text): + return ''.join(['&#%d;' % ord(c) for c in text]) + +def domain(author): + '''get domain of author, or empty string if none.''' + f = author.find('@') + if f == -1: return '' + author = author[f+1:] + f = author.find('>') + if f >= 0: author = author[:f] + return author + +def email(author): + '''get email of author.''' + r = author.find('>') + if r == -1: r = None + return author[author.find('<')+1:r] + +def person(author): + '''get name of author, or else username.''' + f = author.find('<') + if f == -1: return util.shortuser(author) + return author[:f].rstrip() + +def shortdate(date): + '''turn (timestamp, tzoff) tuple into iso 8631 date.''' + return util.datestr(date, format='%Y-%m-%d', timezone=False) + +def indent(text, prefix): + '''indent each non-empty line of text after first with prefix.''' + fp = cStringIO.StringIO() + lines = text.splitlines() + num_lines = len(lines) + for i in xrange(num_lines): + l = lines[i] + if i and l.strip(): fp.write(prefix) + fp.write(l) + if i < num_lines - 1 or text.endswith('\n'): + fp.write('\n') + return fp.getvalue() + +common_filters = { + "addbreaks": nl2br, + "basename": os.path.basename, + "age": age, + "date": lambda x: util.datestr(x), + "domain": domain, + "email": email, + "escape": lambda x: cgi.escape(x, True), + "fill68": lambda x: fill(x, width=68), + "fill76": lambda x: fill(x, width=76), + "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'), + "tabindent": lambda x: indent(x, '\t'), + "isodate": isodate, + "obfuscate": obfuscate, + "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--", + "person": person, + "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), + "short": lambda x: x[:12], + "shortdate": shortdate, + "stringify": stringify, + "strip": lambda x: x.strip(), + "urlescape": lambda x: urllib.quote(x), + "user": lambda x: util.shortuser(x), + } + +def templatepath(name=None): + '''return location of template file or directory (if no name). + returns None if not found.''' + + # executable version (py2exe) doesn't support __file__ + if hasattr(sys, 'frozen'): + module = sys.executable + else: + module = __file__ + for f in 'templates', '../templates': + fl = f.split('/') + if name: fl.append(name) + p = os.path.join(os.path.dirname(module), *fl) + if (name and os.path.exists(p)) or os.path.isdir(p): + return os.path.normpath(p) + +class changeset_templater(object): + '''format changeset information.''' + + def __init__(self, ui, repo, mapfile, dest=None): + self.t = templater(mapfile, common_filters, + cache={'parent': '{rev}:{node|short} ', + 'manifest': '{rev}:{node|short}'}) + self.ui = ui + self.dest = dest + self.repo = repo + + def use_template(self, t): + '''set template string to use''' + self.t.cache['changeset'] = t + + def write(self, thing, header=False): + '''write expanded template. + uses in-order recursive traverse of iterators.''' + dest = self.dest or self.ui + for t in thing: + if hasattr(t, '__iter__'): + self.write(t, header=header) + elif header: + dest.write_header(t) + else: + dest.write(t) + + def write_header(self, thing): + self.write(thing, header=True) + + def show(self, rev=0, changenode=None, brinfo=None, changes=None, + **props): + '''show a single changeset or file revision''' + log = self.repo.changelog + if changenode is None: + changenode = log.node(rev) + elif not rev: + rev = log.rev(changenode) + if changes is None: + changes = log.read(changenode) + + def showlist(name, values, plural=None, **args): + '''expand set of values. + name is name of key in template map. + values is list of strings or dicts. + plural is plural of name, if not simply name + 's'. + + expansion works like this, given name 'foo'. + + if values is empty, expand 'no_foos'. + + if 'foo' not in template map, return values as a string, + joined by space. + + expand 'start_foos'. + + for each value, expand 'foo'. if 'last_foo' in template + map, expand it instead of 'foo' for last key. + + expand 'end_foos'. + ''' + if plural: names = plural + else: names = name + 's' + if not values: + noname = 'no_' + names + if noname in self.t: + yield self.t(noname, **args) + return + if name not in self.t: + if isinstance(values[0], str): + yield ' '.join(values) + else: + for v in values: + yield dict(v, **args) + return + startname = 'start_' + names + if startname in self.t: + yield self.t(startname, **args) + vargs = args.copy() + def one(v, tag=name): + try: + vargs.update(v) + except (AttributeError, ValueError): + try: + for a, b in v: + vargs[a] = b + except ValueError: + vargs[name] = v + return self.t(tag, **vargs) + lastname = 'last_' + name + if lastname in self.t: + last = values.pop() + else: + last = None + for v in values: + yield one(v) + if last is not None: + yield one(last, tag=lastname) + endname = 'end_' + names + if endname in self.t: + yield self.t(endname, **args) + + if brinfo: + def showbranches(**args): + if changenode in brinfo: + for x in showlist('branch', brinfo[changenode], + plural='branches', **args): + yield x + else: + showbranches = '' + + if self.ui.debugflag: + def showmanifest(**args): + args = args.copy() + args.update(dict(rev=self.repo.manifest.rev(changes[0]), + node=hex(changes[0]))) + yield self.t('manifest', **args) + else: + showmanifest = '' + + def showparents(**args): + parents = [[('rev', log.rev(p)), ('node', hex(p))] + for p in log.parents(changenode) + if self.ui.debugflag or p != nullid] + if (not self.ui.debugflag and len(parents) == 1 and + parents[0][0][1] == rev - 1): + return + for x in showlist('parent', parents, **args): + yield x + + def showtags(**args): + for x in showlist('tag', self.repo.nodetags(changenode), **args): + yield x + + if self.ui.debugflag: + files = self.repo.changes(log.parents(changenode)[0], changenode) + def showfiles(**args): + for x in showlist('file', files[0], **args): yield x + def showadds(**args): + for x in showlist('file_add', files[1], **args): yield x + def showdels(**args): + for x in showlist('file_del', files[2], **args): yield x + else: + def showfiles(**args): + for x in showlist('file', changes[3], **args): yield x + showadds = '' + showdels = '' + + defprops = { + 'author': changes[1], + 'branches': showbranches, + 'date': changes[2], + 'desc': changes[4], + 'file_adds': showadds, + 'file_dels': showdels, + 'files': showfiles, + 'manifest': showmanifest, + 'node': hex(changenode), + 'parents': showparents, + 'rev': rev, + 'tags': showtags, + } + props = props.copy() + props.update(defprops) + + try: + if self.ui.debugflag and 'header_debug' in self.t: + key = 'header_debug' + elif self.ui.quiet and 'header_quiet' in self.t: + key = 'header_quiet' + elif self.ui.verbose and 'header_verbose' in self.t: + key = 'header_verbose' + elif 'header' in self.t: + key = 'header' + else: + key = '' + if key: + self.write_header(self.t(key, **props)) + if self.ui.debugflag and 'changeset_debug' in self.t: + key = 'changeset_debug' + elif self.ui.quiet and 'changeset_quiet' in self.t: + key = 'changeset_quiet' + elif self.ui.verbose and 'changeset_verbose' in self.t: + key = 'changeset_verbose' + else: + key = 'changeset' + self.write(self.t(key, **props)) + except KeyError, inst: + raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile, + inst.args[0])) + except SyntaxError, inst: + raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0])) + +class stringio(object): + '''wrap cStringIO for use by changeset_templater.''' + def __init__(self): + self.fp = cStringIO.StringIO() + + def write(self, *args): + for a in args: + self.fp.write(a) + + write_header = write + + def __getattr__(self, key): + return getattr(self.fp, key)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/transaction.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,106 @@ +# transaction.py - simple journalling scheme for mercurial +# +# This transaction scheme is intended to gracefully handle program +# errors and interruptions. More serious failures like system crashes +# can be recovered with an fsck-like tool. As the whole repository is +# effectively log-structured, this should amount to simply truncating +# anything that isn't referenced in the changelog. +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os +from i18n import gettext as _ + +class transaction(object): + def __init__(self, report, opener, journal, after=None): + self.journal = None + + # abort here if the journal already exists + if os.path.exists(journal): + raise AssertionError(_("journal already exists - run hg recover")) + + self.count = 1 + self.report = report + self.opener = opener + self.after = after + self.entries = [] + self.map = {} + self.journal = journal + + self.file = open(self.journal, "w") + + def __del__(self): + if self.journal: + if self.entries: self.abort() + self.file.close() + try: os.unlink(self.journal) + except: pass + + def add(self, file, offset, data=None): + if file in self.map: return + self.entries.append((file, offset, data)) + self.map[file] = len(self.entries) - 1 + # add enough data to the journal to do the truncate + self.file.write("%s\0%d\n" % (file, offset)) + self.file.flush() + + def find(self, file): + if file in self.map: + return self.entries[self.map[file]] + return None + + def replace(self, file, offset, data=None): + if file not in self.map: + raise KeyError(file) + index = self.map[file] + self.entries[index] = (file, offset, data) + self.file.write("%s\0%d\n" % (file, offset)) + self.file.flush() + + def nest(self): + self.count += 1 + return self + + def running(self): + return self.count > 0 + + def close(self): + self.count -= 1 + if self.count != 0: + return + self.file.close() + self.entries = [] + if self.after: + self.after() + else: + os.unlink(self.journal) + self.journal = None + + def abort(self): + if not self.entries: return + + self.report(_("transaction abort!\n")) + + for f, o, ignore in self.entries: + try: + self.opener(f, "a").truncate(o) + except: + self.report(_("failed to truncate %s\n") % f) + + self.entries = [] + + self.report(_("rollback completed\n")) + +def rollback(opener, file): + files = {} + for l in open(file).readlines(): + f, o = l.split('\0') + files[f] = o + for f in files: + o = files[f] + opener(f, "a").truncate(int(o)) + os.unlink(file) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/ui.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,349 @@ +# ui.py - user interface bits for mercurial +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import ConfigParser +from i18n import gettext as _ +from demandload import * +demandload(globals(), "errno getpass os re smtplib socket sys tempfile") +demandload(globals(), "templater traceback util") + +class ui(object): + def __init__(self, verbose=False, debug=False, quiet=False, + interactive=True, traceback=False, parentui=None): + self.overlay = {} + if parentui is None: + # this is the parent of all ui children + self.parentui = None + self.cdata = ConfigParser.SafeConfigParser() + self.readconfig(util.rcpath()) + + self.quiet = self.configbool("ui", "quiet") + self.verbose = self.configbool("ui", "verbose") + self.debugflag = self.configbool("ui", "debug") + self.interactive = self.configbool("ui", "interactive", True) + self.traceback = traceback + + self.updateopts(verbose, debug, quiet, interactive) + self.diffcache = None + self.header = [] + self.prev_header = [] + self.revlogopts = self.configrevlog() + else: + # parentui may point to an ui object which is already a child + self.parentui = parentui.parentui or parentui + parent_cdata = self.parentui.cdata + self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults()) + # make interpolation work + for section in parent_cdata.sections(): + self.cdata.add_section(section) + for name, value in parent_cdata.items(section, raw=True): + self.cdata.set(section, name, value) + + def __getattr__(self, key): + return getattr(self.parentui, key) + + def updateopts(self, verbose=False, debug=False, quiet=False, + interactive=True, traceback=False, config=[]): + self.quiet = (self.quiet or quiet) and not verbose and not debug + self.verbose = (self.verbose or verbose) or debug + self.debugflag = (self.debugflag or debug) + self.interactive = (self.interactive and interactive) + self.traceback = self.traceback or traceback + for cfg in config: + try: + name, value = cfg.split('=', 1) + section, name = name.split('.', 1) + if not self.cdata.has_section(section): + self.cdata.add_section(section) + if not section or not name: + raise IndexError + self.cdata.set(section, name, value) + except (IndexError, ValueError): + raise util.Abort(_('malformed --config option: %s') % cfg) + + def readconfig(self, fn, root=None): + if isinstance(fn, basestring): + fn = [fn] + for f in fn: + try: + self.cdata.read(f) + except ConfigParser.ParsingError, inst: + raise util.Abort(_("Failed to parse %s\n%s") % (f, inst)) + # translate paths relative to root (or home) into absolute paths + if root is None: + root = os.path.expanduser('~') + for name, path in self.configitems("paths"): + if path and path.find("://") == -1 and not os.path.isabs(path): + self.cdata.set("paths", name, os.path.join(root, path)) + + def setconfig(self, section, name, val): + self.overlay[(section, name)] = val + + def config(self, section, name, default=None): + if self.overlay.has_key((section, name)): + return self.overlay[(section, name)] + if self.cdata.has_option(section, name): + try: + return self.cdata.get(section, name) + except ConfigParser.InterpolationError, inst: + raise util.Abort(_("Error in configuration:\n%s") % inst) + if self.parentui is None: + return default + else: + return self.parentui.config(section, name, default) + + def configbool(self, section, name, default=False): + if self.overlay.has_key((section, name)): + return self.overlay[(section, name)] + if self.cdata.has_option(section, name): + try: + return self.cdata.getboolean(section, name) + except ConfigParser.InterpolationError, inst: + raise util.Abort(_("Error in configuration:\n%s") % inst) + if self.parentui is None: + return default + else: + return self.parentui.configbool(section, name, default) + + def has_config(self, section): + '''tell whether section exists in config.''' + return self.cdata.has_section(section) + + def configitems(self, section): + items = {} + if self.parentui is not None: + items = dict(self.parentui.configitems(section)) + if self.cdata.has_section(section): + try: + items.update(dict(self.cdata.items(section))) + except ConfigParser.InterpolationError, inst: + raise util.Abort(_("Error in configuration:\n%s") % inst) + x = items.items() + x.sort() + return x + + def walkconfig(self, seen=None): + if seen is None: + seen = {} + for (section, name), value in self.overlay.iteritems(): + yield section, name, value + seen[section, name] = 1 + for section in self.cdata.sections(): + for name, value in self.cdata.items(section): + if (section, name) in seen: continue + yield section, name, value.replace('\n', '\\n') + seen[section, name] = 1 + if self.parentui is not None: + for parent in self.parentui.walkconfig(seen): + yield parent + + def extensions(self): + return self.configitems("extensions") + + def hgignorefiles(self): + result = [] + cfgitems = self.configitems("ui") + for key, value in cfgitems: + if key == 'ignore' or key.startswith('ignore.'): + path = os.path.expanduser(value) + result.append(path) + return result + + def configrevlog(self): + ret = {} + for x in self.configitems("revlog"): + k = x[0].lower() + ret[k] = x[1] + return ret + def diffopts(self): + if self.diffcache: + return self.diffcache + ret = { 'showfunc' : True, 'ignorews' : False} + for x in self.configitems("diff"): + k = x[0].lower() + v = x[1] + if v: + v = v.lower() + if v == 'true': + value = True + else: + value = False + ret[k] = value + self.diffcache = ret + return ret + + def username(self): + """Return default username to be used in commits. + + Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL + and stop searching if one of these is set. + Abort if found username is an empty string to force specifying + the commit user elsewhere, e.g. with line option or repo hgrc. + If not found, use ($LOGNAME or $USER or $LNAME or + $USERNAME) +"@full.hostname". + """ + user = os.environ.get("HGUSER") + if user is None: + user = self.config("ui", "username") + if user is None: + user = os.environ.get("EMAIL") + if user is None: + try: + user = '%s@%s' % (getpass.getuser(), socket.getfqdn()) + except KeyError: + raise util.Abort(_("Please specify a username.")) + return user + + def shortuser(self, user): + """Return a short representation of a user name or email address.""" + if not self.verbose: user = util.shortuser(user) + return user + + def expandpath(self, loc): + """Return repository location relative to cwd or from [paths]""" + if loc.find("://") != -1 or os.path.exists(loc): + return loc + + return self.config("paths", loc, loc) + + def write(self, *args): + if self.header: + if self.header != self.prev_header: + self.prev_header = self.header + self.write(*self.header) + self.header = [] + for a in args: + sys.stdout.write(str(a)) + + def write_header(self, *args): + for a in args: + self.header.append(str(a)) + + def write_err(self, *args): + try: + if not sys.stdout.closed: sys.stdout.flush() + for a in args: + sys.stderr.write(str(a)) + except IOError, inst: + if inst.errno != errno.EPIPE: + raise + + def flush(self): + try: sys.stdout.flush() + except: pass + try: sys.stderr.flush() + except: pass + + def readline(self): + return sys.stdin.readline()[:-1] + def prompt(self, msg, pat=None, default="y"): + if not self.interactive: return default + while 1: + self.write(msg, " ") + r = self.readline() + if not pat or re.match(pat, r): + return r + else: + self.write(_("unrecognized response\n")) + def getpass(self, prompt=None, default=None): + if not self.interactive: return default + return getpass.getpass(prompt or _('password: ')) + def status(self, *msg): + if not self.quiet: self.write(*msg) + def warn(self, *msg): + self.write_err(*msg) + def note(self, *msg): + if self.verbose: self.write(*msg) + def debug(self, *msg): + if self.debugflag: self.write(*msg) + def edit(self, text, user): + (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt", + text=True) + try: + f = os.fdopen(fd, "w") + f.write(text) + f.close() + + editor = (os.environ.get("HGEDITOR") or + self.config("ui", "editor") or + os.environ.get("EDITOR", "vi")) + + util.system("%s \"%s\"" % (editor, name), + environ={'HGUSER': user}, + onerr=util.Abort, errprefix=_("edit failed")) + + f = open(name) + t = f.read() + f.close() + t = re.sub("(?m)^HG:.*\n", "", t) + finally: + os.unlink(name) + + return t + + def sendmail(self): + '''send mail message. object returned has one method, sendmail. + call as sendmail(sender, list-of-recipients, msg).''' + + def smtp(): + '''send mail using smtp.''' + + s = smtplib.SMTP() + mailhost = self.config('smtp', 'host') + if not mailhost: + raise util.Abort(_('no [smtp]host in hgrc - cannot send mail')) + mailport = int(self.config('smtp', 'port', 25)) + self.note(_('sending mail: smtp host %s, port %s\n') % + (mailhost, mailport)) + s.connect(host=mailhost, port=mailport) + if self.configbool('smtp', 'tls'): + self.note(_('(using tls)\n')) + s.ehlo() + s.starttls() + s.ehlo() + username = self.config('smtp', 'username') + password = self.config('smtp', 'password') + if username and password: + self.note(_('(authenticating to mail server as %s)\n') % + (username)) + s.login(username, password) + return s + + class sendmail(object): + '''send mail using sendmail.''' + + def __init__(self, ui, program): + self.ui = ui + self.program = program + + def sendmail(self, sender, recipients, msg): + cmdline = '%s -f %s %s' % ( + self.program, templater.email(sender), + ' '.join(map(templater.email, recipients))) + self.ui.note(_('sending mail: %s\n') % cmdline) + fp = os.popen(cmdline, 'w') + fp.write(msg) + ret = fp.close() + if ret: + raise util.Abort('%s %s' % ( + os.path.basename(self.program.split(None, 1)[0]), + util.explain_exit(ret)[0])) + + method = self.config('email', 'method', 'smtp') + if method == 'smtp': + mail = smtp() + else: + mail = sendmail(self, method) + return mail + + def print_exc(self): + '''print exception traceback if traceback printing enabled. + only to call in exception handler. returns true if traceback + printed.''' + if self.traceback: + traceback.print_exc() + return self.traceback
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/util.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,900 @@ +""" +util.py - Mercurial utility functions and platform specfic implementations + + Copyright 2005 K. Thananchayan <thananck@yahoo.com> + +This software may be used and distributed according to the terms +of the GNU General Public License, incorporated herein by reference. + +This contains helper routines that are independent of the SCM core and hide +platform-specific details from the core. +""" + +import os, errno +from i18n import gettext as _ +from demandload import * +demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile") +demandload(globals(), "threading time") + +class SignalInterrupt(Exception): + """Exception raised on SIGTERM and SIGHUP.""" + +def pipefilter(s, cmd): + '''filter string S through command CMD, returning its output''' + (pout, pin) = popen2.popen2(cmd, -1, 'b') + def writer(): + try: + pin.write(s) + pin.close() + except IOError, inst: + if inst.errno != errno.EPIPE: + raise + + # we should use select instead on UNIX, but this will work on most + # systems, including Windows + w = threading.Thread(target=writer) + w.start() + f = pout.read() + pout.close() + w.join() + return f + +def tempfilter(s, cmd): + '''filter string S through a pair of temporary files with CMD. + CMD is used as a template to create the real command to be run, + with the strings INFILE and OUTFILE replaced by the real names of + the temporary files generated.''' + inname, outname = None, None + try: + infd, inname = tempfile.mkstemp(prefix='hg-filter-in-') + fp = os.fdopen(infd, 'wb') + fp.write(s) + fp.close() + outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-') + os.close(outfd) + cmd = cmd.replace('INFILE', inname) + cmd = cmd.replace('OUTFILE', outname) + code = os.system(cmd) + if code: raise Abort(_("command '%s' failed: %s") % + (cmd, explain_exit(code))) + return open(outname, 'rb').read() + finally: + try: + if inname: os.unlink(inname) + except: pass + try: + if outname: os.unlink(outname) + except: pass + +filtertable = { + 'tempfile:': tempfilter, + 'pipe:': pipefilter, + } + +def filter(s, cmd): + "filter a string through a command that transforms its input to its output" + for name, fn in filtertable.iteritems(): + if cmd.startswith(name): + return fn(s, cmd[len(name):].lstrip()) + return pipefilter(s, cmd) + +def find_in_path(name, path, default=None): + '''find name in search path. path can be string (will be split + with os.pathsep), or iterable thing that returns strings. if name + found, return path to name. else return default.''' + if isinstance(path, str): + path = path.split(os.pathsep) + for p in path: + p_name = os.path.join(p, name) + if os.path.exists(p_name): + return p_name + return default + +def patch(strip, patchname, ui): + """apply the patch <patchname> to the working directory. + a list of patched files is returned""" + patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') + fp = os.popen('%s -p%d < "%s"' % (patcher, strip, patchname)) + files = {} + for line in fp: + line = line.rstrip() + ui.status("%s\n" % line) + if line.startswith('patching file '): + pf = parse_patch_output(line) + files.setdefault(pf, 1) + code = fp.close() + if code: + raise Abort(_("patch command failed: %s") % explain_exit(code)[0]) + return files.keys() + +def binary(s): + """return true if a string is binary data using diff's heuristic""" + if s and '\0' in s[:4096]: + return True + return False + +def unique(g): + """return the uniq elements of iterable g""" + seen = {} + for f in g: + if f not in seen: + seen[f] = 1 + yield f + +class Abort(Exception): + """Raised if a command needs to print an error and exit.""" + +def always(fn): return True +def never(fn): return False + +def patkind(name, dflt_pat='glob'): + """Split a string into an optional pattern kind prefix and the + actual pattern.""" + for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': + if name.startswith(prefix + ':'): return name.split(':', 1) + return dflt_pat, name + +def globre(pat, head='^', tail='$'): + "convert a glob pattern into a regexp" + i, n = 0, len(pat) + res = '' + group = False + def peek(): return i < n and pat[i] + while i < n: + c = pat[i] + i = i+1 + if c == '*': + if peek() == '*': + i += 1 + res += '.*' + else: + res += '[^/]*' + elif c == '?': + res += '.' + elif c == '[': + j = i + if j < n and pat[j] in '!]': + j += 1 + while j < n and pat[j] != ']': + j += 1 + if j >= n: + res += '\\[' + else: + stuff = pat[i:j].replace('\\','\\\\') + i = j + 1 + if stuff[0] == '!': + stuff = '^' + stuff[1:] + elif stuff[0] == '^': + stuff = '\\' + stuff + res = '%s[%s]' % (res, stuff) + elif c == '{': + group = True + res += '(?:' + elif c == '}' and group: + res += ')' + group = False + elif c == ',' and group: + res += '|' + elif c == '\\': + p = peek() + if p: + i += 1 + res += re.escape(p) + else: + res += re.escape(c) + else: + res += re.escape(c) + return head + res + tail + +_globchars = {'[': 1, '{': 1, '*': 1, '?': 1} + +def pathto(n1, n2): + '''return the relative path from one place to another. + this returns a path in the form used by the local filesystem, not hg.''' + if not n1: return localpath(n2) + a, b = n1.split('/'), n2.split('/') + a.reverse() + b.reverse() + while a and b and a[-1] == b[-1]: + a.pop() + b.pop() + b.reverse() + return os.sep.join((['..'] * len(a)) + b) + +def canonpath(root, cwd, myname): + """return the canonical path of myname, given cwd and root""" + if root == os.sep: + rootsep = os.sep + elif root.endswith(os.sep): + rootsep = root + else: + rootsep = root + os.sep + name = myname + if not os.path.isabs(name): + name = os.path.join(root, cwd, name) + name = os.path.normpath(name) + if name != rootsep and name.startswith(rootsep): + name = name[len(rootsep):] + audit_path(name) + return pconvert(name) + elif name == root: + return '' + else: + # Determine whether `name' is in the hierarchy at or beneath `root', + # by iterating name=dirname(name) until that causes no change (can't + # check name == '/', because that doesn't work on windows). For each + # `name', compare dev/inode numbers. If they match, the list `rel' + # holds the reversed list of components making up the relative file + # name we want. + root_st = os.stat(root) + rel = [] + while True: + try: + name_st = os.stat(name) + except OSError: + break + if samestat(name_st, root_st): + rel.reverse() + name = os.path.join(*rel) + audit_path(name) + return pconvert(name) + dirname, basename = os.path.split(name) + rel.append(basename) + if dirname == name: + break + name = dirname + + raise Abort('%s not under root' % myname) + +def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None): + return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src) + +def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None): + if os.name == 'nt': + dflt_pat = 'glob' + else: + dflt_pat = 'relpath' + return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src) + +def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src): + """build a function to match a set of file patterns + + arguments: + canonroot - the canonical root of the tree you're matching against + cwd - the current working directory, if relevant + names - patterns to find + inc - patterns to include + exc - patterns to exclude + head - a regex to prepend to patterns to control whether a match is rooted + + a pattern is one of: + 'glob:<rooted glob>' + 're:<rooted regexp>' + 'path:<rooted path>' + 'relglob:<relative glob>' + 'relpath:<relative path>' + 'relre:<relative regexp>' + '<rooted path or regexp>' + + returns: + a 3-tuple containing + - list of explicit non-pattern names passed in + - a bool match(filename) function + - a bool indicating if any patterns were passed in + + todo: + make head regex a rooted bool + """ + + def contains_glob(name): + for c in name: + if c in _globchars: return True + return False + + def regex(kind, name, tail): + '''convert a pattern into a regular expression''' + if kind == 're': + return name + elif kind == 'path': + return '^' + re.escape(name) + '(?:/|$)' + elif kind == 'relglob': + return head + globre(name, '(?:|.*/)', tail) + elif kind == 'relpath': + return head + re.escape(name) + tail + elif kind == 'relre': + if name.startswith('^'): + return name + return '.*' + name + return head + globre(name, '', tail) + + def matchfn(pats, tail): + """build a matching function from a set of patterns""" + if not pats: + return + matches = [] + for k, p in pats: + try: + pat = '(?:%s)' % regex(k, p, tail) + matches.append(re.compile(pat).match) + except re.error: + if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p)) + else: raise Abort("invalid pattern (%s): %s" % (k, p)) + + def buildfn(text): + for m in matches: + r = m(text) + if r: + return r + + return buildfn + + def globprefix(pat): + '''return the non-glob prefix of a path, e.g. foo/* -> foo''' + root = [] + for p in pat.split(os.sep): + if contains_glob(p): break + root.append(p) + return '/'.join(root) + + pats = [] + files = [] + roots = [] + for kind, name in [patkind(p, dflt_pat) for p in names]: + if kind in ('glob', 'relpath'): + name = canonpath(canonroot, cwd, name) + if name == '': + kind, name = 'glob', '**' + if kind in ('glob', 'path', 're'): + pats.append((kind, name)) + if kind == 'glob': + root = globprefix(name) + if root: roots.append(root) + elif kind == 'relpath': + files.append((kind, name)) + roots.append(name) + + patmatch = matchfn(pats, '$') or always + filematch = matchfn(files, '(?:/|$)') or always + incmatch = always + if inc: + incmatch = matchfn(map(patkind, inc), '(?:/|$)') + excmatch = lambda fn: False + if exc: + excmatch = matchfn(map(patkind, exc), '(?:/|$)') + + return (roots, + lambda fn: (incmatch(fn) and not excmatch(fn) and + (fn.endswith('/') or + (not pats and not files) or + (pats and patmatch(fn)) or + (files and filematch(fn)))), + (inc or exc or (pats and pats != [('glob', '**')])) and True) + +def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None): + '''enhanced shell command execution. + run with environment maybe modified, maybe in different dir. + + if command fails and onerr is None, return status. if ui object, + print error message and return status, else raise onerr object as + exception.''' + oldenv = {} + for k in environ: + oldenv[k] = os.environ.get(k) + if cwd is not None: + oldcwd = os.getcwd() + try: + for k, v in environ.iteritems(): + os.environ[k] = str(v) + if cwd is not None and oldcwd != cwd: + os.chdir(cwd) + rc = os.system(cmd) + if rc and onerr: + errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]), + explain_exit(rc)[0]) + if errprefix: + errmsg = '%s: %s' % (errprefix, errmsg) + try: + onerr.warn(errmsg + '\n') + except AttributeError: + raise onerr(errmsg) + return rc + finally: + for k, v in oldenv.iteritems(): + if v is None: + del os.environ[k] + else: + os.environ[k] = v + if cwd is not None and oldcwd != cwd: + os.chdir(oldcwd) + +def rename(src, dst): + """forcibly rename a file""" + try: + os.rename(src, dst) + except OSError, err: + # on windows, rename to existing file is not allowed, so we + # must delete destination first. but if file is open, unlink + # schedules it for delete but does not delete it. rename + # happens immediately even for open files, so we create + # temporary file, delete it, rename destination to that name, + # then delete that. then rename is safe to do. + fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.') + os.close(fd) + os.unlink(temp) + os.rename(dst, temp) + os.unlink(temp) + os.rename(src, dst) + +def unlink(f): + """unlink and remove the directory if it is empty""" + os.unlink(f) + # try removing directories that might now be empty + try: + os.removedirs(os.path.dirname(f)) + except OSError: + pass + +def copyfiles(src, dst, hardlink=None): + """Copy a directory tree using hardlinks if possible""" + + if hardlink is None: + hardlink = (os.stat(src).st_dev == + os.stat(os.path.dirname(dst)).st_dev) + + if os.path.isdir(src): + os.mkdir(dst) + for name in os.listdir(src): + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + copyfiles(srcname, dstname, hardlink) + else: + if hardlink: + try: + os_link(src, dst) + except (IOError, OSError): + hardlink = False + shutil.copy(src, dst) + else: + shutil.copy(src, dst) + +def audit_path(path): + """Abort if path contains dangerous components""" + parts = os.path.normcase(path).split(os.sep) + if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '') + or os.pardir in parts): + raise Abort(_("path contains illegal component: %s\n") % path) + +def _makelock_file(info, pathname): + ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL) + os.write(ld, info) + os.close(ld) + +def _readlock_file(pathname): + return posixfile(pathname).read() + +def nlinks(pathname): + """Return number of hardlinks for the given file.""" + return os.stat(pathname).st_nlink + +if hasattr(os, 'link'): + os_link = os.link +else: + def os_link(src, dst): + raise OSError(0, _("Hardlinks not supported")) + +def fstat(fp): + '''stat file object that may not have fileno method.''' + try: + return os.fstat(fp.fileno()) + except AttributeError: + return os.stat(fp.name) + +posixfile = file + +def is_win_9x(): + '''return true if run on windows 95, 98 or me.''' + try: + return sys.getwindowsversion()[3] == 1 + except AttributeError: + return os.name == 'nt' and 'command' in os.environ.get('comspec', '') + +# Platform specific variants +if os.name == 'nt': + demandload(globals(), "msvcrt") + nulldev = 'NUL:' + + class winstdout: + '''stdout on windows misbehaves if sent through a pipe''' + + def __init__(self, fp): + self.fp = fp + + def __getattr__(self, key): + return getattr(self.fp, key) + + def close(self): + try: + self.fp.close() + except: pass + + def write(self, s): + try: + return self.fp.write(s) + except IOError, inst: + if inst.errno != 0: raise + self.close() + raise IOError(errno.EPIPE, 'Broken pipe') + + sys.stdout = winstdout(sys.stdout) + + def system_rcpath(): + try: + return system_rcpath_win32() + except: + return [r'c:\mercurial\mercurial.ini'] + + def os_rcpath(): + '''return default os-specific hgrc search path''' + path = system_rcpath() + path.append(user_rcpath()) + userprofile = os.environ.get('USERPROFILE') + if userprofile: + path.append(os.path.join(userprofile, 'mercurial.ini')) + return path + + def user_rcpath(): + '''return os-specific hgrc search path to the user dir''' + return os.path.join(os.path.expanduser('~'), 'mercurial.ini') + + def parse_patch_output(output_line): + """parses the output produced by patch and returns the file name""" + pf = output_line[14:] + if pf[0] == '`': + pf = pf[1:-1] # Remove the quotes + return pf + + def testpid(pid): + '''return False if pid dead, True if running or not known''' + return True + + def is_exec(f, last): + return last + + def set_exec(f, mode): + pass + + def set_binary(fd): + msvcrt.setmode(fd.fileno(), os.O_BINARY) + + def pconvert(path): + return path.replace("\\", "/") + + def localpath(path): + return path.replace('/', '\\') + + def normpath(path): + return pconvert(os.path.normpath(path)) + + makelock = _makelock_file + readlock = _readlock_file + + def samestat(s1, s2): + return False + + def explain_exit(code): + return _("exited with status %d") % code, code + + try: + # override functions with win32 versions if possible + from util_win32 import * + if not is_win_9x(): + posixfile = posixfile_nt + except ImportError: + pass + +else: + nulldev = '/dev/null' + + def rcfiles(path): + rcs = [os.path.join(path, 'hgrc')] + rcdir = os.path.join(path, 'hgrc.d') + try: + rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir) + if f.endswith(".rc")]) + except OSError, inst: pass + return rcs + + def os_rcpath(): + '''return default os-specific hgrc search path''' + path = [] + # old mod_python does not set sys.argv + if len(getattr(sys, 'argv', [])) > 0: + path.extend(rcfiles(os.path.dirname(sys.argv[0]) + + '/../etc/mercurial')) + path.extend(rcfiles('/etc/mercurial')) + path.append(os.path.expanduser('~/.hgrc')) + path = [os.path.normpath(f) for f in path] + return path + + def parse_patch_output(output_line): + """parses the output produced by patch and returns the file name""" + pf = output_line[14:] + if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0: + pf = pf[1:-1] # Remove the quotes + return pf + + def is_exec(f, last): + """check whether a file is executable""" + return (os.stat(f).st_mode & 0100 != 0) + + def set_exec(f, mode): + s = os.stat(f).st_mode + if (s & 0100 != 0) == mode: + return + if mode: + # Turn on +x for every +r bit when making a file executable + # and obey umask. + umask = os.umask(0) + os.umask(umask) + os.chmod(f, s | (s & 0444) >> 2 & ~umask) + else: + os.chmod(f, s & 0666) + + def set_binary(fd): + pass + + def pconvert(path): + return path + + def localpath(path): + return path + + normpath = os.path.normpath + samestat = os.path.samestat + + def makelock(info, pathname): + try: + os.symlink(info, pathname) + except OSError, why: + if why.errno == errno.EEXIST: + raise + else: + _makelock_file(info, pathname) + + def readlock(pathname): + try: + return os.readlink(pathname) + except OSError, why: + if why.errno == errno.EINVAL: + return _readlock_file(pathname) + else: + raise + + def testpid(pid): + '''return False if pid dead, True if running or not sure''' + try: + os.kill(pid, 0) + return True + except OSError, inst: + return inst.errno != errno.ESRCH + + def explain_exit(code): + """return a 2-tuple (desc, code) describing a process's status""" + if os.WIFEXITED(code): + val = os.WEXITSTATUS(code) + return _("exited with status %d") % val, val + elif os.WIFSIGNALED(code): + val = os.WTERMSIG(code) + return _("killed by signal %d") % val, val + elif os.WIFSTOPPED(code): + val = os.WSTOPSIG(code) + return _("stopped by signal %d") % val, val + raise ValueError(_("invalid exit code")) + +def opener(base, audit=True): + """ + return a function that opens files relative to base + + this function is used to hide the details of COW semantics and + remote file access from higher level code. + """ + p = base + audit_p = audit + + def mktempcopy(name): + d, fn = os.path.split(name) + fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d) + os.close(fd) + ofp = posixfile(temp, "wb") + try: + try: + ifp = posixfile(name, "rb") + except IOError, inst: + if not getattr(inst, 'filename', None): + inst.filename = name + raise + for chunk in filechunkiter(ifp): + ofp.write(chunk) + ifp.close() + ofp.close() + except: + try: os.unlink(temp) + except: pass + raise + st = os.lstat(name) + os.chmod(temp, st.st_mode) + return temp + + class atomictempfile(posixfile): + """the file will only be copied when rename is called""" + def __init__(self, name, mode): + self.__name = name + self.temp = mktempcopy(name) + posixfile.__init__(self, self.temp, mode) + def rename(self): + if not self.closed: + posixfile.close(self) + rename(self.temp, localpath(self.__name)) + def __del__(self): + if not self.closed: + try: + os.unlink(self.temp) + except: pass + posixfile.close(self) + + class atomicfile(atomictempfile): + """the file will only be copied on close""" + def __init__(self, name, mode): + atomictempfile.__init__(self, name, mode) + def close(self): + self.rename() + def __del__(self): + self.rename() + + def o(path, mode="r", text=False, atomic=False, atomictemp=False): + if audit_p: + audit_path(path) + f = os.path.join(p, path) + + if not text: + mode += "b" # for that other OS + + if mode[0] != "r": + try: + nlink = nlinks(f) + except OSError: + d = os.path.dirname(f) + if not os.path.isdir(d): + os.makedirs(d) + else: + if atomic: + return atomicfile(f, mode) + elif atomictemp: + return atomictempfile(f, mode) + if nlink > 1: + rename(mktempcopy(f), f) + return posixfile(f, mode) + + return o + +class chunkbuffer(object): + """Allow arbitrary sized chunks of data to be efficiently read from an + iterator over chunks of arbitrary size.""" + + def __init__(self, in_iter, targetsize = 2**16): + """in_iter is the iterator that's iterating over the input chunks. + targetsize is how big a buffer to try to maintain.""" + self.in_iter = iter(in_iter) + self.buf = '' + self.targetsize = int(targetsize) + if self.targetsize <= 0: + raise ValueError(_("targetsize must be greater than 0, was %d") % + targetsize) + self.iterempty = False + + def fillbuf(self): + """Ignore target size; read every chunk from iterator until empty.""" + if not self.iterempty: + collector = cStringIO.StringIO() + collector.write(self.buf) + for ch in self.in_iter: + collector.write(ch) + self.buf = collector.getvalue() + self.iterempty = True + + def read(self, l): + """Read L bytes of data from the iterator of chunks of data. + Returns less than L bytes if the iterator runs dry.""" + if l > len(self.buf) and not self.iterempty: + # Clamp to a multiple of self.targetsize + targetsize = self.targetsize * ((l // self.targetsize) + 1) + collector = cStringIO.StringIO() + collector.write(self.buf) + collected = len(self.buf) + for chunk in self.in_iter: + collector.write(chunk) + collected += len(chunk) + if collected >= targetsize: + break + if collected < targetsize: + self.iterempty = True + self.buf = collector.getvalue() + s, self.buf = self.buf[:l], buffer(self.buf, l) + return s + +def filechunkiter(f, size = 65536): + """Create a generator that produces all the data in the file size + (default 65536) bytes at a time. Chunks may be less than size + bytes if the chunk is the last chunk in the file, or the file is a + socket or some other type of file that sometimes reads less data + than is requested.""" + s = f.read(size) + while len(s) > 0: + yield s + s = f.read(size) + +def makedate(): + lt = time.localtime() + if lt[8] == 1 and time.daylight: + tz = time.altzone + else: + tz = time.timezone + return time.mktime(lt), tz + +def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True): + """represent a (unixtime, offset) tuple as a localized time. + unixtime is seconds since the epoch, and offset is the time zone's + number of seconds away from UTC. if timezone is false, do not + append time zone to string.""" + t, tz = date or makedate() + s = time.strftime(format, time.gmtime(float(t) - tz)) + if timezone: + s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60)) + return s + +def shortuser(user): + """Return a short representation of a user name or email address.""" + f = user.find('@') + if f >= 0: + user = user[:f] + f = user.find('<') + if f >= 0: + user = user[f+1:] + return user + +def walkrepos(path): + '''yield every hg repository under path, recursively.''' + def errhandler(err): + if err.filename == path: + raise err + + for root, dirs, files in os.walk(path, onerror=errhandler): + for d in dirs: + if d == '.hg': + yield root + dirs[:] = [] + break + +_rcpath = None + +def rcpath(): + '''return hgrc search path. if env var HGRCPATH is set, use it. + for each item in path, if directory, use files ending in .rc, + else use item. + make HGRCPATH empty to only look in .hg/hgrc of current repo. + if no HGRCPATH, use default os-specific path.''' + global _rcpath + if _rcpath is None: + if 'HGRCPATH' in os.environ: + _rcpath = [] + for p in os.environ['HGRCPATH'].split(os.pathsep): + if not p: continue + if os.path.isdir(p): + for f in os.listdir(p): + if f.endswith('.rc'): + _rcpath.append(os.path.join(p, f)) + else: + _rcpath.append(p) + else: + _rcpath = os_rcpath() + return _rcpath
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/util_win32.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,299 @@ +# util_win32.py - utility functions that use win32 API +# +# Copyright 2005 Matt Mackall <mpm@selenic.com> +# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com> +# +# This software may be used and distributed according to the terms of +# the GNU General Public License, incorporated herein by reference. + +# Mark Hammond's win32all package allows better functionality on +# Windows. this module overrides definitions in util.py. if not +# available, import of this module will fail, and generic code will be +# used. + +import win32api + +from demandload import * +from i18n import gettext as _ +demandload(globals(), 'errno os pywintypes win32con win32file win32process') +demandload(globals(), 'cStringIO win32com.shell:shell,shellcon winerror') + +class WinError: + winerror_map = { + winerror.ERROR_ACCESS_DENIED: errno.EACCES, + winerror.ERROR_ACCOUNT_DISABLED: errno.EACCES, + winerror.ERROR_ACCOUNT_RESTRICTION: errno.EACCES, + winerror.ERROR_ALREADY_ASSIGNED: errno.EBUSY, + winerror.ERROR_ALREADY_EXISTS: errno.EEXIST, + winerror.ERROR_ARITHMETIC_OVERFLOW: errno.ERANGE, + winerror.ERROR_BAD_COMMAND: errno.EIO, + winerror.ERROR_BAD_DEVICE: errno.ENODEV, + winerror.ERROR_BAD_DRIVER_LEVEL: errno.ENXIO, + winerror.ERROR_BAD_EXE_FORMAT: errno.ENOEXEC, + winerror.ERROR_BAD_FORMAT: errno.ENOEXEC, + winerror.ERROR_BAD_LENGTH: errno.EINVAL, + winerror.ERROR_BAD_PATHNAME: errno.ENOENT, + winerror.ERROR_BAD_PIPE: errno.EPIPE, + winerror.ERROR_BAD_UNIT: errno.ENODEV, + winerror.ERROR_BAD_USERNAME: errno.EINVAL, + winerror.ERROR_BROKEN_PIPE: errno.EPIPE, + winerror.ERROR_BUFFER_OVERFLOW: errno.ENAMETOOLONG, + winerror.ERROR_BUSY: errno.EBUSY, + winerror.ERROR_BUSY_DRIVE: errno.EBUSY, + winerror.ERROR_CALL_NOT_IMPLEMENTED: errno.ENOSYS, + winerror.ERROR_CANNOT_MAKE: errno.EACCES, + winerror.ERROR_CANTOPEN: errno.EIO, + winerror.ERROR_CANTREAD: errno.EIO, + winerror.ERROR_CANTWRITE: errno.EIO, + winerror.ERROR_CRC: errno.EIO, + winerror.ERROR_CURRENT_DIRECTORY: errno.EACCES, + winerror.ERROR_DEVICE_IN_USE: errno.EBUSY, + winerror.ERROR_DEV_NOT_EXIST: errno.ENODEV, + winerror.ERROR_DIRECTORY: errno.EINVAL, + winerror.ERROR_DIR_NOT_EMPTY: errno.ENOTEMPTY, + winerror.ERROR_DISK_CHANGE: errno.EIO, + winerror.ERROR_DISK_FULL: errno.ENOSPC, + winerror.ERROR_DRIVE_LOCKED: errno.EBUSY, + winerror.ERROR_ENVVAR_NOT_FOUND: errno.EINVAL, + winerror.ERROR_EXE_MARKED_INVALID: errno.ENOEXEC, + winerror.ERROR_FILENAME_EXCED_RANGE: errno.ENAMETOOLONG, + winerror.ERROR_FILE_EXISTS: errno.EEXIST, + winerror.ERROR_FILE_INVALID: errno.ENODEV, + winerror.ERROR_FILE_NOT_FOUND: errno.ENOENT, + winerror.ERROR_GEN_FAILURE: errno.EIO, + winerror.ERROR_HANDLE_DISK_FULL: errno.ENOSPC, + winerror.ERROR_INSUFFICIENT_BUFFER: errno.ENOMEM, + winerror.ERROR_INVALID_ACCESS: errno.EACCES, + winerror.ERROR_INVALID_ADDRESS: errno.EFAULT, + winerror.ERROR_INVALID_BLOCK: errno.EFAULT, + winerror.ERROR_INVALID_DATA: errno.EINVAL, + winerror.ERROR_INVALID_DRIVE: errno.ENODEV, + winerror.ERROR_INVALID_EXE_SIGNATURE: errno.ENOEXEC, + winerror.ERROR_INVALID_FLAGS: errno.EINVAL, + winerror.ERROR_INVALID_FUNCTION: errno.ENOSYS, + winerror.ERROR_INVALID_HANDLE: errno.EBADF, + winerror.ERROR_INVALID_LOGON_HOURS: errno.EACCES, + winerror.ERROR_INVALID_NAME: errno.EINVAL, + winerror.ERROR_INVALID_OWNER: errno.EINVAL, + winerror.ERROR_INVALID_PARAMETER: errno.EINVAL, + winerror.ERROR_INVALID_PASSWORD: errno.EPERM, + winerror.ERROR_INVALID_PRIMARY_GROUP: errno.EINVAL, + winerror.ERROR_INVALID_SIGNAL_NUMBER: errno.EINVAL, + winerror.ERROR_INVALID_TARGET_HANDLE: errno.EIO, + winerror.ERROR_INVALID_WORKSTATION: errno.EACCES, + winerror.ERROR_IO_DEVICE: errno.EIO, + winerror.ERROR_IO_INCOMPLETE: errno.EINTR, + winerror.ERROR_LOCKED: errno.EBUSY, + winerror.ERROR_LOCK_VIOLATION: errno.EACCES, + winerror.ERROR_LOGON_FAILURE: errno.EACCES, + winerror.ERROR_MAPPED_ALIGNMENT: errno.EINVAL, + winerror.ERROR_META_EXPANSION_TOO_LONG: errno.E2BIG, + winerror.ERROR_MORE_DATA: errno.EPIPE, + winerror.ERROR_NEGATIVE_SEEK: errno.ESPIPE, + winerror.ERROR_NOACCESS: errno.EFAULT, + winerror.ERROR_NONE_MAPPED: errno.EINVAL, + winerror.ERROR_NOT_ENOUGH_MEMORY: errno.ENOMEM, + winerror.ERROR_NOT_READY: errno.EAGAIN, + winerror.ERROR_NOT_SAME_DEVICE: errno.EXDEV, + winerror.ERROR_NO_DATA: errno.EPIPE, + winerror.ERROR_NO_MORE_SEARCH_HANDLES: errno.EIO, + winerror.ERROR_NO_PROC_SLOTS: errno.EAGAIN, + winerror.ERROR_NO_SUCH_PRIVILEGE: errno.EACCES, + winerror.ERROR_OPEN_FAILED: errno.EIO, + winerror.ERROR_OPEN_FILES: errno.EBUSY, + winerror.ERROR_OPERATION_ABORTED: errno.EINTR, + winerror.ERROR_OUTOFMEMORY: errno.ENOMEM, + winerror.ERROR_PASSWORD_EXPIRED: errno.EACCES, + winerror.ERROR_PATH_BUSY: errno.EBUSY, + winerror.ERROR_PATH_NOT_FOUND: errno.ENOENT, + winerror.ERROR_PIPE_BUSY: errno.EBUSY, + winerror.ERROR_PIPE_CONNECTED: errno.EPIPE, + winerror.ERROR_PIPE_LISTENING: errno.EPIPE, + winerror.ERROR_PIPE_NOT_CONNECTED: errno.EPIPE, + winerror.ERROR_PRIVILEGE_NOT_HELD: errno.EACCES, + winerror.ERROR_READ_FAULT: errno.EIO, + winerror.ERROR_SEEK: errno.EIO, + winerror.ERROR_SEEK_ON_DEVICE: errno.ESPIPE, + winerror.ERROR_SHARING_BUFFER_EXCEEDED: errno.ENFILE, + winerror.ERROR_SHARING_VIOLATION: errno.EACCES, + winerror.ERROR_STACK_OVERFLOW: errno.ENOMEM, + winerror.ERROR_SWAPERROR: errno.ENOENT, + winerror.ERROR_TOO_MANY_MODULES: errno.EMFILE, + winerror.ERROR_TOO_MANY_OPEN_FILES: errno.EMFILE, + winerror.ERROR_UNRECOGNIZED_MEDIA: errno.ENXIO, + winerror.ERROR_UNRECOGNIZED_VOLUME: errno.ENODEV, + winerror.ERROR_WAIT_NO_CHILDREN: errno.ECHILD, + winerror.ERROR_WRITE_FAULT: errno.EIO, + winerror.ERROR_WRITE_PROTECT: errno.EROFS, + } + + def __init__(self, err): + self.win_errno, self.win_function, self.win_strerror = err + if self.win_strerror.endswith('.'): + self.win_strerror = self.win_strerror[:-1] + +class WinIOError(WinError, IOError): + def __init__(self, err, filename=None): + WinError.__init__(self, err) + IOError.__init__(self, self.winerror_map.get(self.win_errno, 0), + self.win_strerror) + self.filename = filename + +class WinOSError(WinError, OSError): + def __init__(self, err): + WinError.__init__(self, err) + OSError.__init__(self, self.winerror_map.get(self.win_errno, 0), + self.win_strerror) + +def os_link(src, dst): + # NB will only succeed on NTFS + try: + win32file.CreateHardLink(dst, src) + except pywintypes.error, details: + raise WinOSError(details) + +def nlinks(pathname): + """Return number of hardlinks for the given file.""" + try: + fh = win32file.CreateFile(pathname, + win32file.GENERIC_READ, win32file.FILE_SHARE_READ, + None, win32file.OPEN_EXISTING, 0, None) + res = win32file.GetFileInformationByHandle(fh) + fh.Close() + return res[7] + except pywintypes.error: + return os.stat(pathname).st_nlink + +def testpid(pid): + '''return True if pid is still running or unable to + determine, False otherwise''' + try: + handle = win32api.OpenProcess( + win32con.PROCESS_QUERY_INFORMATION, False, pid) + if handle: + status = win32process.GetExitCodeProcess(handle) + return status == win32con.STILL_ACTIVE + except pywintypes.error, details: + return details[0] != winerror.ERROR_INVALID_PARAMETER + return True + +def system_rcpath_win32(): + '''return default os-specific hgrc search path''' + proc = win32api.GetCurrentProcess() + try: + # This will fail on windows < NT + filename = win32process.GetModuleFileNameEx(proc, 0) + except: + filename = win32api.GetModuleFileName(0) + return [os.path.join(os.path.dirname(filename), 'mercurial.ini')] + +def user_rcpath(): + '''return os-specific hgrc search path to the user dir''' + userdir = os.path.expanduser('~') + if userdir == '~': + # We are on win < nt: fetch the APPDATA directory location and use + # the parent directory as the user home dir. + appdir = shell.SHGetPathFromIDList( + shell.SHGetSpecialFolderLocation(0, shellcon.CSIDL_APPDATA)) + userdir = os.path.dirname(appdir) + return os.path.join(userdir, 'mercurial.ini') + +class posixfile_nt(object): + '''file object with posix-like semantics. on windows, normal + files can not be deleted or renamed if they are open. must open + with win32file.FILE_SHARE_DELETE. this flag does not exist on + windows < nt, so do not use this class there.''' + + # tried to use win32file._open_osfhandle to pass fd to os.fdopen, + # but does not work at all. wrap win32 file api instead. + + def __init__(self, name, mode='rb'): + access = 0 + if 'r' in mode or '+' in mode: + access |= win32file.GENERIC_READ + if 'w' in mode or 'a' in mode: + access |= win32file.GENERIC_WRITE + if 'r' in mode: + creation = win32file.OPEN_EXISTING + elif 'a' in mode: + creation = win32file.OPEN_ALWAYS + else: + creation = win32file.CREATE_ALWAYS + try: + self.handle = win32file.CreateFile(name, + access, + win32file.FILE_SHARE_READ | + win32file.FILE_SHARE_WRITE | + win32file.FILE_SHARE_DELETE, + None, + creation, + win32file.FILE_ATTRIBUTE_NORMAL, + 0) + except pywintypes.error, err: + raise WinIOError(err, name) + self.closed = False + self.name = name + self.mode = mode + + def __iter__(self): + for line in self.read().splitlines(True): + yield line + + def read(self, count=-1): + try: + cs = cStringIO.StringIO() + while count: + wincount = int(count) + if wincount == -1: + wincount = 1048576 + val, data = win32file.ReadFile(self.handle, wincount) + if not data: break + cs.write(data) + if count != -1: + count -= len(data) + return cs.getvalue() + except pywintypes.error, err: + raise WinIOError(err) + + def write(self, data): + try: + if 'a' in self.mode: + win32file.SetFilePointer(self.handle, 0, win32file.FILE_END) + nwrit = 0 + while nwrit < len(data): + val, nwrit = win32file.WriteFile(self.handle, data) + data = data[nwrit:] + except pywintypes.error, err: + raise WinIOError(err) + + def seek(self, pos, whence=0): + try: + win32file.SetFilePointer(self.handle, int(pos), whence) + except pywintypes.error, err: + raise WinIOError(err) + + def tell(self): + try: + return win32file.SetFilePointer(self.handle, 0, + win32file.FILE_CURRENT) + except pywintypes.error, err: + raise WinIOError(err) + + def close(self): + if not self.closed: + self.handle = None + self.closed = True + + def flush(self): + try: + win32file.FlushFileBuffers(self.handle) + except pywintypes.error, err: + raise WinIOError(err) + + def truncate(self, pos=0): + try: + win32file.SetFilePointer(self.handle, int(pos), + win32file.FILE_BEGIN) + win32file.SetEndOfFile(self.handle) + except pywintypes.error, err: + raise WinIOError(err)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mercurial/version.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,74 @@ +# Copyright (C) 2005 by Intevation GmbH +# Author(s): +# Thomas Arendsen Hein <thomas@intevation.de> +# +# This program is free software under the GNU GPL (>=v2) +# Read the file COPYING coming with the software for details. + +""" +Mercurial version +""" + +import os +import os.path +import re +import time +import util + +unknown_version = 'unknown' +remembered_version = False + +def get_version(): + """Return version information if available.""" + try: + from mercurial.__version__ import version + except ImportError: + version = unknown_version + return version + +def write_version(version): + """Overwrite version file.""" + if version == get_version(): + return + directory = os.path.dirname(__file__) + for suffix in ['py', 'pyc', 'pyo']: + try: + os.unlink(os.path.join(directory, '__version__.%s' % suffix)) + except OSError: + pass + f = open(os.path.join(directory, '__version__.py'), 'w') + f.write("# This file is auto-generated.\n") + f.write("version = %r\n" % version) + f.close() + +def remember_version(version=None): + """Store version information.""" + global remembered_version + if not version and os.path.isdir(".hg"): + f = os.popen("hg identify 2> %s" % util.nulldev) # use real hg installation + ident = f.read()[:-1] + if not f.close() and ident: + ids = ident.split(' ', 1) + version = ids.pop(0) + if version[-1] == '+': + version = version[:-1] + modified = True + else: + modified = False + if version.isalnum() and ids: + for tag in ids[0].split('/'): + # is a tag is suitable as a version number? + if re.match(r'^(\d+\.)+[\w.-]+$', tag): + version = tag + break + if modified: + version += time.strftime('+%Y%m%d') + if version: + remembered_version = True + write_version(version) + +def forget_version(): + """Remove version information.""" + if remembered_version: + write_version(unknown_version) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/notes.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,146 @@ +Some notes about Mercurial's design + +Revlogs: + +The fundamental storage type in Mercurial is a "revlog". A revlog is +the set of all revisions to a file. Each revision is either stored +compressed in its entirety or as a compressed binary delta against the +previous version. The decision of when to store a full version is made +based on how much data would be needed to reconstruct the file. This +lets us ensure that we never need to read huge amounts of data to +reconstruct a file, regardless of how many revisions of it we store. + +In fact, we should always be able to do it with a single read, +provided we know when and where to read. This is where the index comes +in. Each revlog has an index containing a special hash (nodeid) of the +text, hashes for its parents, and where and how much of the revlog +data we need to read to reconstruct it. Thus, with one read of the +index and one read of the data, we can reconstruct any version in time +proportional to the file size. + +Similarly, revlogs and their indices are append-only. This means that +adding a new version is also O(1) seeks. + +Generally revlogs are used to represent revisions of files, but they +also are used to represent manifests and changesets. + +Manifests: + +A manifest is simply a list of all files in a given revision of a +project along with the nodeids of the corresponding file revisions. So +grabbing a given version of the project means simply looking up its +manifest and reconstruction all the file revisions pointed to by it. + +Changesets: + +A changeset is a list of all files changed in a check-in along with a +change description and some metadata like user and date. It also +contains a nodeid to the relevent revision of the manifest. Changesets +and manifests are one-to-one, but contain different data for +convenience. + +Nodeids: + +Nodeids are unique ids that are used to represent the contents of a +file AND its position in the project history. That is, if you change a +file and then change it back, the result will have a different nodeid +because it has different history. This is accomplished by including +the parents of a given revision's nodeids with the revision's text +when calculating the hash. + +Graph merging: + +Nodeids are implemented as they are to simplify merging. Merging a +pair of directed acyclic graphs (aka "the family tree" of the file +history) requires some method of determining if nodes in different +graphs correspond. Simply comparing the contents of the node (by +comparing text of given revisions or their hashes) can get confused by +identical revisions in the tree. + +The nodeid approach makes it trivial - the hash uniquely describes a +revision's contents and its graph position relative to the root, so +merge is simply checking whether each nodeid in graph A is in the hash +table of graph B. If not, we pull them in, adding them sequentially to +the revlog. + +Branching and merging: + +Everything in Mercurial is potentially a branch and every user +effectively works in their own branch. When you do a checkout, +Mercurial remembers what the parent changeset was and uses it for the +next check in. + +To do a merge of branches in Mercurial, you check out the heads of the +two branches into the same working directory which causes a merge to +be performed, and then check in the result once you're happy with it. +The resulting checkin will have two parents. + +It decides when a merge is necessary by first determining if there are +any uncommitted changes in the working directory. This effectively +makes the working directory a branch off the checked in version it's +based on. Then it also determines if the working directory is a direct +ancestor or descendent of the second version we're attempting to +checkout. If neither is true, we simply replace the working directory +version with the new version. Otherwise we perform a merge between the +two versions. + +Merging files and manifests: + +We begin by comparing two versions manifests and deciding which files +need to be added, deleted, and merged. + +Then for each file, we perform a graph merge and resolve as above. +It's important to merge files using per-file DAGs rather than just +changeset level DAGs as this diagram illustrates: + +M M1 M2 + +AB + |`-------v M2 clones M +aB AB file A is change in mainline + |`---v AB' file B is changed in M2 + | aB / | M1 clones M + | ab/ | M1 changes B + | ab' | M1 merges from M2, changes to B conflict + | | A'B' M2 changes A + `---+--.| + | a'B' M2 merges from mainline, changes to A conflict + `--.| + ??? depending on which ancestor we choose, we will have + to redo A hand-merge, B hand-merge, or both + but if we look at the files independently, everything + is fine + +The result is a merged version in the working directory, waiting for +check-in. + +Rollback: + +When performing a commit or a merge, we order things so that the +changeset entry gets added last. We keep a transaction log of the name +of each file touched and its length prior to the transaction. On +abort, we simply truncate each file to its prior length. This is one +of the nice properties of the append-only structure of the revlogs. +We can also reuse this journal for "rollback". + +Merging between repositories: + +One of the key features of Mercurial is the ability to merge between +independent repositories in a decentralized fashion. Each repository +can act as a read-only server or a client. Clients operating by +pulling all branches that it hasn't seen from the server and adding +them into its graph. This is done in two steps: searching for new +"roots" and pulling a "changegroup" + +Searching for new "roots" begins by finding all new heads and +searching backwards from those heads to the first unknown nodes in +their respective branches. These nodes are the 'roots' that are used +to calculate the 'changegroup': the set of all changesets starting at +those roots. Mercurial takes pains to make this search efficient in +both bandwidth and round-trips. + +Once the roots are found, the changegroup can be transferred as a +single streaming transfer. This is organized as an ordered set of +deltas for changesets, manifests, and files. Large chunks of deltas +can be directly added to the repository without unpacking so it's +fairly fast.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rewrite-log Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,23 @@ +#!/usr/bin/env python +import sys, os +from mercurial import hg + +f = sys.argv[1] + +r1 = hg.revlog(open, f + ".i", f + ".d") +r2 = hg.revlog(open, f + ".i2", f + ".d2") + +tr = hg.transaction(open, "journal") + +for i in xrange(r1.count()): + n = r1.node(i) + p1, p2 = r1.parents(n) + l = r1.linkrev(n) + t = r1.revision(n) + n2 = r2.addrevision(t, tr, l, p1, p2) +tr.close() + +os.rename(f + ".i", f + ".i.old") +os.rename(f + ".d", f + ".d.old") +os.rename(f + ".i2", f + ".i") +os.rename(f + ".d2", f + ".d")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# This is the mercurial setup script. +# +# './setup.py install', or +# './setup.py --help' for more options + +import sys +if not hasattr(sys, 'version_info') or sys.version_info < (2, 3): + raise SystemExit, "Mercurial requires python 2.3 or later." + +import glob +from distutils.core import setup, Extension +from distutils.command.install_data import install_data + +# mercurial.packagescan must be the first mercurial module imported +import mercurial.packagescan +import mercurial.version + +# py2exe needs to be installed to work +try: + import py2exe + + # Help py2exe to find win32com.shell + try: + import modulefinder + import win32com + for p in win32com.__path__[1:]: # Take the path to win32comext + modulefinder.AddPackagePath("win32com", p) + pn = "win32com.shell" + __import__(pn) + m = sys.modules[pn] + for p in m.__path__[1:]: + modulefinder.AddPackagePath(pn, p) + except ImportError: + pass + + # Due to the use of demandload py2exe is not finding the modules. + # packagescan.getmodules creates a list of modules included in + # the mercurial package plus depdent modules. + from py2exe.build_exe import py2exe as build_exe + + class py2exe_for_demandload(build_exe): + """ overwrites the py2exe command class for getting the build + directory and for setting the 'includes' option.""" + def initialize_options(self): + self.build_lib = None + build_exe.initialize_options(self) + def finalize_options(self): + # Get the build directory, ie. where to search for modules. + self.set_undefined_options('build', + ('build_lib', 'build_lib')) + # Sets the 'includes' option with the list of needed modules + if not self.includes: + self.includes = [] + else: + self.includes = self.includes.split(',') + mercurial.packagescan.scan(self.build_lib,'mercurial') + mercurial.packagescan.scan(self.build_lib,'mercurial/hgweb') + mercurial.packagescan.scan(self.build_lib,'hgext') + self.includes += mercurial.packagescan.getmodules() + build_exe.finalize_options(self) +except ImportError: + py2exe_for_demandload = None + + +# specify version string, otherwise 'hg identify' will be used: +version = '' + +class install_package_data(install_data): + def finalize_options(self): + self.set_undefined_options('install', + ('install_lib', 'install_dir')) + install_data.finalize_options(self) + +mercurial.version.remember_version(version) +cmdclass = {'install_data': install_package_data} +py2exe_opts = {} +if py2exe_for_demandload is not None: + cmdclass['py2exe'] = py2exe_for_demandload + py2exe_opts['console'] = ['hg'] +setup(name='mercurial', + version=mercurial.version.get_version(), + author='Matt Mackall', + author_email='mpm@selenic.com', + url='http://selenic.com/mercurial', + description='Scalable distributed SCM', + license='GNU GPL', + packages=['mercurial', 'mercurial.hgweb', 'hgext'], + ext_modules=[Extension('mercurial.mpatch', ['mercurial/mpatch.c']), + Extension('mercurial.bdiff', ['mercurial/bdiff.c'])], + data_files=[('mercurial/templates', + ['templates/map'] + + glob.glob('templates/map-*') + + glob.glob('templates/*.tmpl')), + ('mercurial/templates/static', + glob.glob('templates/static/*'))], + cmdclass=cmdclass, + scripts=['hg', 'hgmerge'], + options=dict(bdist_mpkg=dict(zipdist=True, + license='COPYING', + readme='contrib/macosx/Readme.html', + welcome='contrib/macosx/Welcome.html')), + **py2exe_opts)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelog-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#header# +<title>#repo|escape#: Changelog</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / changelog +</div> + +<form action="#"> +<div class="search"> +<input type="hidden" name="repo" value="#repo|escape#" /> +<input type="hidden" name="style" value="gitweb" /> +<input type="hidden" name="cmd" value="changelog" /> +<input type="text" name="rev" /> +</div> +</form> +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | changelog | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/> +<br/> +#changenav%naventry#<br/> +</div> + +#entries%changelogentry# + +<div class="page_nav"> +#changenav%naventry#<br/> +</div> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelog-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +#header# + <title>#repo|escape# Changelog</title> + <description>#repo|escape# Changelog</description> + #entries%changelogentry# + </channel> +</rss> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelog.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,37 @@ +#header# +<title>#repo|escape#: changelog</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="buttons"> +<a href="?cmd=tags">tags</a> +<a href="?mf=#manifest|short#;path=/">manifest</a> +#archives%archiveentry# +<a type="application/rss+xml" href="?style=rss">rss</a> +</div> + +<h2>changelog for #repo|escape#</h2> + +<form action="#"> +<p> +<label for="search1">search:</label> +<input type="hidden" name="cmd" value="changelog"> +<input name="rev" id="search1" type="text" size="30"> +navigate: <small class="navigate">#changenav%naventry#</small> +</p> +</form> + +#entries%changelogentry# + +<form action="#"> +<p> +<label for="search2">search:</label> +<input type="hidden" name="cmd" value="changelog"> +<input name="rev" id="search2" type="text" size="30"> +navigate: <small class="navigate">#changenav%naventry#</small> +</p> +</form> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelogentry-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,14 @@ +<div> +<a class="title" href="?cmd=changeset;node=#node#;style=gitweb"><span class="age">#date|age# ago</span>#desc|strip|firstline|escape#</a> +</div> +<div class="title_text"> +<div class="log_link"> +<a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a><br/> +</div> +<i>#author|obfuscate# [#date|rfc822date#] rev #rev#</i><br/> +</div> +<div class="log_body"> +#desc|strip|escape|addbreaks# +<br/> +<br/> +</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelogentry-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,7 @@ +<item> + <title>#desc|strip|firstline|strip|escape#</title> + <link>#url#?cs=#node|short#</link> + <description><![CDATA[#desc|strip|escape|addbreaks#]]></description> + <author>#author|obfuscate#</author> + <pubDate>#date|rfc822date#</pubDate> +</item>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changelogentry.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +<table class="logEntry parity#parity#"> + <tr> + <th class="age">#date|age# ago:</th> + <th class="firstline">#desc|strip|firstline|escape#</th> + </tr> + <tr> + <th class="revision">changeset #rev#:</th> + <td class="node"><a href="?cs=#node|short#">#node|short#</a></td> + </tr> + #parent%changelogparent# + #child%changelogchild# + #changelogtag# + <tr> + <th class="author">author:</th> + <td class="author">#author|obfuscate#</td> + </tr> + <tr> + <th class="date">date:</th> + <td class="date">#date|date#</td> + </tr> + <tr> + <th class="files"><a href="?mf=#manifest|short#;path=/">files</a>:</th> + <td class="files">#files#</td> + </tr> +</table>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changeset-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +#header# +<title>#repo|escape#: Changeset</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / changeset +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;rev=#rev#;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a> | changeset | <a href="?cmd=changeset;node=#node#;style=raw">raw</a> #archives%archiveentry#<br/> +</div> + +<div> +<a class="title" href="?cmd=changeset;node=#node#;style=raw">#desc|strip|escape|firstline#</a> +</div> +<div class="title_text"> +<table cellspacing="0"> +<tr><td>author</td><td>#author|obfuscate#</td></tr> +<tr><td></td><td>#date|date# (#date|age# ago)</td></tr> +<tr><td>changeset</td><td style="font-family:monospace">#node|short#</td></tr> +<tr><td>manifest</td><td style="font-family:monospace"><a class="list" href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">#manifest|short#</a></td></tr> +#parent%changesetparent# +#child%changesetchild# +#changesettag# +</table></div> + +<div class="title_text"> +#desc|strip|escape|addbreaks# +</div> + +<div class="title_text"> +<table cellspacing="0"> +#files# +</table></div> + +<div class="page_body">#diff#</div> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changeset-raw.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +#header# +# HG changeset patch +# User #author# +# Date #date|date# +# Node ID #node# +#parent%changesetparent# +#desc# + +#diff#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/changeset.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,46 @@ +#header# +<title>#repo|escape#: changeset #node|short#</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=#rev#">changelog</a> +<a href="?cmd=tags">tags</a> +<a href="?mf=#manifest|short#;path=/">manifest</a> +<a href="?cs=#node|short#;style=raw">raw</a> +#archives%archiveentry# +</div> + +<h2>changeset: #desc|strip|escape|firstline#</h2> + +<table id="changesetEntry"> +<tr> + <th class="changeset">changeset #rev#:</th> + <td class="changeset"><a href="?cs=#node|short#">#node|short#</a></td> +</tr> +#parent%changesetparent# +#child%changesetchild# +#changesettag# +<tr> + <th class="author">author:</th> + <td class="author">#author|obfuscate#</td> +</tr> +<tr> + <th class="date">date:</th> + <td class="date">#date|date# (#date|age# ago)</td></tr> +<tr> + <th class="files">files:</th> + <td class="files">#files#</td></tr> +<tr> + <th class="description">description:</th> + <td class="description">#desc|strip|escape|addbreaks#</td> +</tr> +</table> + +<div id="changesetDiff"> +#diff# +</div> + +#footer# + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/error-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +#header# +<title>#repo|escape#: Error</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / error +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/> +</div> + +<div> +<br/> +<i>An error occured while processing your request</i><br/> +<br/> +</div> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/error.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +#header# +<title>Mercurial Error</title> +</head> +<body> + +<h2>Mercurial Error</h2> + +<p> +An error occured while processing your request: +</p> +<p> +#error|escape# +</p> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/fileannotate-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,45 @@ +#header# +<title>#repo|escape#: Annotate</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / annotate +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#;style=gitweb">manifest</a> | <a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a> | <a href="?cmd=file;file=#file|urlescape#;filenode=#filenode#;style=gitweb">file</a> | <a href="?cmd=filelog;file=#file|urlescape#;filenode=#filenode#;style=gitweb">revisions</a> | annotate | <a href="?cmd=annotate;file=#file|urlescape#;filenode=#filenode#;style=raw">raw</a><br/> +</div> + +<div class="title">#file|escape#</div> + +<table> +<tr> + <td class="metatag">changeset #rev#:</td> + <td><a href="?cs=#node|short#;style=gitweb">#node|short#</a></td></tr> +#rename%filerename# +#parent%fileannotateparent# +#child%fileannotatechild# +<tr> + <td class="metatag">manifest:</td> + <td><a href="?mf=#manifest|short#;path=/;style=gitweb">#manifest|short#</a></td></tr> +<tr> + <td class="metatag">author:</td> + <td>#author|obfuscate#</td></tr> +<tr> + <td class="metatag">date:</td> + <td>#date|date# (#date|age# ago)</td></tr> +<tr> + <td class="metatag">permissions:</td> + <td>#permissions|permissions#</td></tr> +</table> + +<div class="page_body"> +<table> +#annotate%annotateline# +</table> +</div> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/fileannotate-raw.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#header# +#annotate%annotateline# +#footer# + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/fileannotate.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,42 @@ +#header# +<title>#repo|escape#: #file|escape# annotate</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=#rev#">changelog</a> +<a href="?tags=">tags</a> +<a href="?cs=#node|short#">changeset</a> +<a href="?mf=#manifest|short#;path=#path|urlescape#">manifest</a> +<a href="?f=#filenode|short#;file=#file|urlescape#">file</a> +<a href="?fl=#filenode|short#;file=#file|urlescape#">revisions</a> +<a href="?fa=#filenode|short#;file=#file|urlescape#;style=raw">raw</a> +</div> + +<h2>Annotate #file|escape#</h2> + +<table> +<tr> + <td class="metatag">changeset #rev#:</td> + <td><a href="?cs=#node|short#">#node|short#</a></td></tr> +#rename%filerename# +#parent%fileannotateparent# +#child%fileannotatechild# +<tr> + <td class="metatag">author:</td> + <td>#author|obfuscate#</td></tr> +<tr> + <td class="metatag">date:</td> + <td>#date|date# (#date|age# ago)</td></tr> +<tr> + <td class="metatag">permissions:</td> + <td>#permissions|permissions#</td></tr> +</table> + +<br/> + +<table cellspacing="0" cellpadding="0"> +#annotate%annotateline# +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filediff-raw.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +#header# +#diff# +#footer# + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filediff.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,33 @@ +#header# +<title>#repo|escape#: #file|escape# diff</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=#rev#">changelog</a> +<a href="?tags=">tags</a> +<a href="?cs=#node|short#">changeset</a> +<a href="?f=#filenode|short#;file=#file|urlescape#">file</a> +<a href="?fl=#filenode|short#;file=#file|urlescape#">revisions</a> +<a href="?fa=#filenode|short#;file=#file|urlescape#">annotate</a> +<a href="?fd=#node|short#;file=#file|urlescape#;style=raw">raw</a> +</div> + +<h2>#file|escape#</h2> + +<table id="filediffEntry"> +<tr> + <th class="revision">revision #rev#:</th> + <td class="revision"><a href="?cs=#node|short#">#node|short#</a></td> +</tr> +#parent%filediffparent# +#child%filediffchild# +</table> + +<div id="fileDiff"> +#diff# +</div> + +#footer# + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filelog-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +#header# +<title>#repo|escape#: File revisions</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / file revisions +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=file;file=#file|urlescape#;filenode=#filenode#;style=gitweb">file</a> | revisions | <a href="?cmd=annotate;file=#file|urlescape#;filenode=#filenode#;style=gitweb">annotate</a> | <a href="?fl=#filenode|short#;file=#file|urlescape#;style=rss">rss</a><br/> +</div> + +<div class="title" >#file|urlescape#</div> + +<table> +#entries%filelogentry# +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filelog-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +#header# + <title>#repo|escape#: #file|escape# history</title> + <description>#file|escape# revision history</description> + #entries%filelogentry# + </channel> +</rss>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filelog.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +#header# +<title>#repo|escape#: #file|escape# history</title> +<link rel="alternate" type="application/rss+xml" + href="?fl=0;file=#file|urlescape#;style=rss" title="RSS feed for #repo|escape#:#file#"> +</head> +</head> +<body> + +<div class="buttons"> +<a href="?cl=tip">changelog</a> +<a href="?tags=">tags</a> +<a href="?f=#filenode|short#;file=#file|urlescape#">file</a> +<a href="?fa=#filenode|short#;file=#file|urlescape#">annotate</a> +<a type="application/rss+xml" href="?fl=0;file=#file|urlescape#;style=rss">rss</a> +</div> + +<h2>#file|escape# revision history</h2> + +#entries%filelogentry# + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filelogentry-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,7 @@ +<item> + <title>#desc|strip|firstline|strip|escape#</title> + <link>#url#?f=#filenode|short#;file=#file|urlescape#</link> + <description><![CDATA[#desc|strip|escape|addbreaks#]]></description> + <author>#author|obfuscate#</author> + <pubDate>#date|rfc822date#</pubDate>> +</item>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filelogentry.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +<table class="logEntry parity#parity#"> + <tr> + <th class="age">#date|age# ago:</th> + <th class="firstline"><a href="?cs=#node|short#">#desc|strip|firstline|escape#</a></th> + </tr> + <tr> + <th class="revision">revision #filerev#:</td> + <td class="node"> + <a href="?f=#filenode|short#;file=#file|urlescape#">#filenode|short#</a> + <a href="?fd=#node|short#;file=#file|urlescape#">(diff)</a> + <a href="?fa=#filenode|short#;file=#file|urlescape#">(annotate)</a> + </td> + </tr> + #rename%filelogrename# + <tr> + <th class="author">author:</th> + <td class="author">#author|obfuscate#</td> + </tr> + <tr> + <th class="date">date:</th> + <td class="date">#date|date#</td> + </tr> +</table> + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filerevision-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,43 @@ +#header# +<title>#repo|escape#: File revision</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / file revision +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">manifest</a> | <a href="?cmd=changeset;node=#node#;style=gitweb">changeset</a> | file | <a href="?cmd=filelog;file=#file|urlescape#;filenode=#filenode#;style=gitweb">revisions</a> | <a href="?cmd=annotate;file=#file|urlescape#;filenode=#filenode#;style=gitweb">annotate</a> | <a href="?cmd=file;file=#file|urlescape#;filenode=#filenode#;style=raw">raw</a><br/> +</div> + +<div class="title">#file|escape#</div> + +<table> +<tr> + <td class="metatag">changeset #rev#:</td> + <td><a href="?cs=#node|short#;style=gitweb">#node|short#</a></td></tr> +#rename%filerename# +#parent%fileannotateparent# +#child%fileannotatechild# +<tr> + <td class="metatag">manifest:</td> + <td><a href="?mf=#manifest|short#;path=/;style=gitweb">#manifest|short#</a></td></tr> +<tr> + <td class="metatag">author:</td> + <td>#author|obfuscate#</td></tr> +<tr> + <td class="metatag">date:</td> + <td>#date|date# (#date|age# ago)</td></tr> +<tr> + <td class="metatag">permissions:</td> + <td>#permissions|permissions#</td></tr> +</table> + +<div class="page_body"> +#text%fileline# +</div> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/filerevision.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,40 @@ +#header# +<title>#repo|escape#:#file|escape#</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=#rev#">changelog</a> +<a href="?tags=">tags</a> +<a href="?cs=#node|short#">changeset</a> +<a href="?mf=#manifest|short#;path=#path|urlescape#">manifest</a> +<a href="?fl=#filenode|short#;file=#file|urlescape#">revisions</a> +<a href="?fa=#filenode|short#;file=#file|urlescape#">annotate</a> +<a href="?f=#filenode|short#;file=#file|urlescape#;style=raw">raw</a> +</div> + +<h2>#file|escape#</h2> + +<table> +<tr> + <td class="metatag">changeset #rev#:</td> + <td><a href="?cs=#node|short#">#node|short#</a></td></tr> +#rename%filerename# +#parent%filerevparent# +#child%filerevchild# +<tr> + <td class="metatag">author:</td> + <td>#author|obfuscate#</td></tr> +<tr> + <td class="metatag">date:</td> + <td>#date|date# (#date|age# ago)</td></tr> +<tr> + <td class="metatag">permissions:</td> + <td>#permissions|permissions#</td></tr> +</table> + +<pre> +#text%fileline# +</pre> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/footer-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +<div class="page_footer"> +<div class="page_footer_text">#repo|escape#</div> +<a class="rss_logo" href="?cmd=changelog;style=rss">RSS</a> +</div> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/footer.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +#motd# +<div class="logo"> +powered by<br/> +<a href="http://www.selenic.com/mercurial/">mercurial</a> +</div> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/header-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +Content-type: text/html + +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> +<head> +<link rel="icon" href="?static=hgicon.png" type="image/png"> +<meta http-equiv="content-type" content="text/html; charset=utf-8"/> +<meta name="robots" content="index, nofollow"/> +<link rel="stylesheet" href="?static=style-gitweb.css" type="text/css" /> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/header-raw.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,1 @@ +Content-type: text/plain
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/header-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +Content-type: text/xml + +<rss version="2.0"> + <channel> + <link>#url#</link> + <language>en-us</language>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/header.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +Content-type: text/html + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<link rel="icon" href="?static=hgicon.png" type="image/png"> +<meta name="robots" content="index, nofollow" /> +<link rel="stylesheet" href="?static=style.css" type="text/css" />
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/index.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,19 @@ +#header# +<title>Mercurial repositories index</title> +</head> +<body> + +<h2>Mercurial Repositories</h2> + +<table> + <tr> + <td><a href="?sort=#sort_name#">Name</a></td> + <td><a href="?sort=#sort_description#">Description</a></td> + <td><a href="?sort=#sort_contact#">Contact</a></td> + <td><a href="?sort=#sort_lastchange#">Last change</a></td> + <td> </td> + <tr> + #entries%indexentry# +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/manifest-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,27 @@ +#header# +<title>#repo|escape#: Manifest</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / manifest +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | manifest | <a href="?cs=#node|short#;style=gitweb">changeset</a> #archives%archiveentry#<br/> +</div> + +<div class="title" >#path|escape#</div> +<div class="page_body"> +<table cellspacing="0"> +<tr class="light"> +<td style="font-family:monospace">drwxr-xr-x</td> +<td><a href="?cmd=manifest;manifest=#manifest#;path=#up|urlescape#;style=gitweb">[up]</a></td> +<td class="link"> </td> +</tr> +#dentries%manifestdirentry# +#fentries%manifestfileentry# +</table> +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/manifest.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +#header# +<title>#repo|escape#: manifest #manifest|short#</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=#rev#">changelog</a> +<a href="?tags=">tags</a> +<a href="?cs=#node|short#">changeset</a> +#archives%archiveentry# +</div> + +<h2>manifest for changeset #node|short#: #path|escape#</h2> + +<table cellpadding="0" cellspacing="0"> +<tr class="parity1"> + <td><tt>drwxr-xr-x</tt> + <td><a href="?mf=#manifest|short#;path=#up|urlescape#">[up]</a> +#dentries%manifestdirentry# +#fentries%manifestfileentry# +</table> +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,50 @@ +default = 'changelog' +header = header.tmpl +footer = footer.tmpl +search = search.tmpl +changelog = changelog.tmpl +naventry = '<a href="?cl=#rev#">#label|escape#</a> ' +filedifflink = '<a href="?fd=#node|short#;file=#file|urlescape#">#file|escape#</a> ' +filenodelink = '<a href="?f=#filenode|short#;file=#file|urlescape#">#file|escape#</a> ' +fileellipses = '...' +changelogentry = changelogentry.tmpl +searchentry = changelogentry.tmpl +changeset = changeset.tmpl +manifest = manifest.tmpl +manifestdirentry = '<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt> <td><a href="?cmd=manifest;manifest=#manifest#;path=#path|urlescape#">#basename|escape#/</a>' +manifestfileentry = '<tr class="parity#parity#"><td><tt>#permissions|permissions#</tt> <td><a href="?f=#filenode|short#;file=#file|urlescape#">#basename|escape#</a>' +filerevision = filerevision.tmpl +fileannotate = fileannotate.tmpl +filediff = filediff.tmpl +filelog = filelog.tmpl +fileline = '<div class="parity#parity#"><span class="lineno">#linenumber#</span>#line|escape#</div>' +filelogentry = filelogentry.tmpl +annotateline = '<tr class="parity#parity#"><td class="annotate"><a href="?cs=#node|short#">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>' +difflineplus = '<span class="plusline">#line|escape#</span>' +difflineminus = '<span class="minusline">#line|escape#</span>' +difflineat = '<span class="atline">#line|escape#</span>' +diffline = '#line|escape#' +changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +changesetparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>' +filelogrename = '<tr><th>base:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#file|escape#@#node|short#</a></td></tr>' +fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +changesetchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +filerevchild = '<tr><td class="metatag">child:</td><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?fa=#filenode|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +tags = tags.tmpl +tagentry = '<li class="tagEntry parity#parity#"><tt class="node">#node#</tt> <a href="?cs=#node|short#">#tag|escape#</a></li>' +diffblock = '<pre class="parity#parity#">#lines#</pre>' +changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>' +changesettag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>' +filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +filelogparent = '<tr><th>parent #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cs=#node|short#">#node|short#</a></td></tr>' +filelogchild = '<tr><th>child #rev#:</th><td><a href="?f=#node|short#;file=#file|urlescape#">#node|short#</a></td></tr>' +indexentry = '<tr class="parity#parity#"><td><a href="#url#">#name|escape#</a></td><td>#description#</td><td>#contact|obfuscate#</td><td class="age">#lastchange|age# ago</td><td class="indexlinks"><a href="#url#?cl=tip;style=rss">RSS</a> #archives%archiveentry#</td></tr>' +index = index.tmpl +archiveentry = '<a href="#url#?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> ' +notfound = notfound.tmpl +error = error.tmpl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-cmdline.changelog Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,14 @@ +header = '{date|shortdate} {author|person} <{author|email}>\n\n' +header_verbose = '' +changeset = '\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\t[{node|short}]{tags}\n\n' +changeset_quiet = '\t* {desc|firstline|fill68|tabindent|strip}\n\n' +changeset_verbose = '{date|isodate} {author|person} <{author|email}> ({node|short}{tags})\n\n\t* {file_adds|stringify|fill68|tabindent}{file_dels|stringify|fill68|tabindent}{files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\n' +start_tags = ' [' +tag = '{tag}, ' +last_tag = '{tag}]' +file = '{file}, ' +last_file = '{file}:\n\t' +file_add = '{file_add}, ' +last_file_add = '{file_add}: new file.\n* ' +file_del = '{file_del}, ' +last_file_del = '{file_del}: deleted file.\n* '
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-cmdline.compact Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +changeset = '{rev}{tags}{parents} {node|short} {date|isodate} {author|user}\n {desc|firstline|strip}\n\n' +changeset_quiet = '{rev}:{node|short}\n' +start_tags = '[' +tag = '{tag},' +last_tag = '{tag}]' +start_parents = ':' +parent = '{rev},' +last_parent = '{rev}'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-cmdline.default Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +changeset = 'changeset: {rev}:{node|short}\n{tags}{short_parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n' +changeset_quiet = '{rev}:{node|short}\n' +changeset_verbose = 'changeset: {rev}:{node}\n{tags}{parents}{manifest}user: {author}\ndate: {date|date}\nfiles: {files}\n{file_adds}{file_dels}description:\n{desc|strip}\n\n\n' +start_file_adds = 'files+: ' +file_add = ' {file_add}' +end_file_adds = '\n' +start_file_dels = 'files-: ' +file_del = ' {file_del}' +end_file_dels = '\n' +short_parent = 'parent: {rev}:{node|short}\n' +parent = 'parent: {rev}:{node}\n' +manifest = 'manifest: {rev}:{node}\n' +tag = 'tag: {tag}\n'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-gitweb Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,50 @@ +default = 'summary' +header = header-gitweb.tmpl +footer = footer-gitweb.tmpl +search = search-gitweb.tmpl +changelog = changelog-gitweb.tmpl +summary = summary-gitweb.tmpl +error = error-gitweb.tmpl +naventry = '<a href="?cmd=changelog;rev=#rev#;style=gitweb">#label|escape#</a> ' +navshortentry = '<a href="?cmd=shortlog;rev=#rev#;style=gitweb">#label|escape#</a> ' +filedifflink = '<a href="?cmd=filediff;node=#node#;file=#file|urlescape#;style=gitweb">#file|escape#</a> ' +filenodelink = '<tr class="light"><td><a class="list" href="">#file|escape#</a></td><td></td><td class="link"><a href="?cmd=file;filenode=#filenode#;file=#file|urlescape#;style=gitweb">file</a> | <!-- FIXME: <a href="?fd=#filenode|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?cmd=filelog;filenode=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a></td></tr>' +fileellipses = '...' +changelogentry = changelogentry-gitweb.tmpl +searchentry = changelogentry-gitweb.tmpl +changeset = changeset-gitweb.tmpl +manifest = manifest-gitweb.tmpl +manifestdirentry = '<tr class="parity#parity#"><td style="font-family:monospace">drwxr-xr-x</td><td><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">#basename|escape#/</a></td><td class="link"><a href="?mf=#manifest|short#;path=#path|urlescape#;style=gitweb">manifest</a></td></tr>' +manifestfileentry = '<tr class="parity#parity#"><td style="font-family:monospace">#permissions|permissions#</td><td class="list"><a class="list" href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">#basename|escape#</a></td><td class="link"><a href="?f=#filenode|short#;file=#file|urlescape#;style=gitweb">file</a> | <a href="?fl=#filenode|short#;file=#file|urlescape#;style=gitweb">revisions</a> | <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a></td></tr>' +filerevision = filerevision-gitweb.tmpl +fileannotate = fileannotate-gitweb.tmpl +filelog = filelog-gitweb.tmpl +fileline = '<div style="font-family:monospace; white-space: pre;" class="parity#parity#"><span class="linenr"> #linenumber#</span> #line|escape#</div>' +annotateline = '<tr style="font-family:monospace; white-space: pre;" class="parity#parity#"><td class="linenr" style="text-align: right;"><a href="?cs=#node|short#;style=gitweb">#author|obfuscate#@#rev#</a></td><td>#line|escape#</td></tr>' +difflineplus = '<div class="pre" style="color:#008800;">#line|escape#</div>' +difflineminus = '<div class="pre" style="color:#cc0000;">#line|escape#</div>' +difflineat = '<div class="pre" style="color:#990099;">#line|escape#</div>' +diffline = '<div class="pre">#line|escape#</div>' +changelogparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>' +changesetparent = '<tr><td>parent</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>' +filerevparent = '<tr><td class="metatag">parent:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +filerename = '<tr><td class="metatag">parent:</td><td><a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">#file|escape#@#node|short#</a></td></tr>' +filelogrename = '| <a href="?f=#node|short#;file=#file|urlescape#;style=gitweb">base</a>' +fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +changelogchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>' +changesetchild = '<tr><td>child</td><td style="font-family:monospace"><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb">#node|short#</a></td></tr>' +filerevchild = '<tr><td class="metatag">child:</td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="?cmd=annotate;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +tags = tags-gitweb.tmpl +tagentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#tag|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=changelog;rev=#node|short#;style=gitweb">changelog</a> | <a href="?mf=#tagmanifest|short#;path=/;style=gitweb">manifest</a></td></tr>' +diffblock = '#lines#' +changelogtag = '<tr><th class="tag">tag:</th><td class="tag">#tag|escape#</td></tr>' +changesettag = '<tr><td>tag</td><td>#tag|escape#</td></tr>' +filediffparent = '<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>' +filelogparent = '<tr><td align="right">parent #rev#: </td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +filediffchild = '<tr><th class="child">child #rev#:</th><td class="child"><a href="?cmd=changeset;node=#node#;style=gitweb">#node|short#</a></td></tr>' +filelogchild = '<tr><td align="right">child #rev#: </td><td><a href="?cmd=file;file=#file|urlescape#;filenode=#node#;style=gitweb">#node|short#</a></td></tr>' +shortlog = shortlog-gitweb.tmpl +shortlogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><i>#author#</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><a href="?cmd=changeset;node=#node|short#;style=gitweb">changeset</a> | <a href="?cmd=manifest;manifest=#manifest|short#;path=/;style=gitweb">manifest</a></td></tr>' +filelogentry = '<tr class="parity#parity#"><td class="age"><i>#date|age# ago</i></td><td><a class="list" href="?cmd=changeset;node=#node|short#;style=gitweb"><b>#desc|strip|firstline|escape#</b></a></td><td class="link"><!-- FIXME: <a href="?fd=#node|short#;file=#file|urlescape#;style=gitweb">diff</a> | --> <a href="?fa=#filenode|short#;file=#file|urlescape#;style=gitweb">annotate</a> #rename%filelogrename#</td></tr>' +archiveentry = ' | <a href="?ca=#node|short#;type=#type|urlescape#">#type|escape#</a> '
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-raw Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +header = header-raw.tmpl +footer = '' +changeset = changeset-raw.tmpl +difflineplus = '#line#' +difflineminus = '#line#' +difflineat = '#line#' +diffline = '#line#' +changesetparent = '# parent: #node#' +changesetchild = '# child: #node#' +filenodelink = '' +filerevision = 'Content-Type: #mimetype#\nContent-Disposition: filename=#file#\n\n#raw#' +fileline = '#line#' +diffblock = '#lines#' +filediff = filediff-raw.tmpl +fileannotate = fileannotate-raw.tmpl +annotateline = '#author#@#rev#: #line#'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/map-rss Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +default = 'changelog' +header = header-rss.tmpl +changelog = changelog-rss.tmpl +changelogentry = changelogentry-rss.tmpl +filelog = filelog-rss.tmpl +filelogentry = filelogentry-rss.tmpl +tags = tags-rss.tmpl +tagentry = tagentry-rss.tmpl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/notfound.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#header# +<title>Mercurial repositories index</title> +</head> +<body> + +<h2>Mercurial Repositories</h2> + +The specified repository "#repo|escape#" is unknown, sorry. + +Please go back to the main repository list page. + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/search-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +#header# +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | log | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/> +</div> + +<h2>searching for #query|escape#</h2> + +<form> +search: +<input type="hidden" name="cmd" value="changelog"> +<input type="hidden" name="style" value="gitweb"> +<input name="rev" type="text" width="30" value="#query|escape#"> +</form> + +#entries# + +<form> +search: +<input type="hidden" name="cmd" value="changelog"> +<input type="hidden" name="style" value="gitweb"> +<input name="rev" type="text" width="30"> +</form> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/search.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,32 @@ +#header# +<title>#repo|escape#: searching for #query|escape#</title> +</head> +<body> + +<div class="buttons"> +<a href="?cl=tip">changelog</a> +<a href="?tags=">tags</a> +<a href="?mf=#manifest|short#;path=/">manifest</a> +</div> + +<h2>searching for #query|escape#</h2> + +<form> +<p> +search: +<input type="hidden" name="cmd" value="changelog"> +<input name="rev" type="text" width="30" value="#query|escape#"> +</p> +</form> + +#entries# + +<form> +<p> +search: +<input type="hidden" name="cmd" value="changelog"> +<input name="rev" type="text" width="30" value="#query|escape#"> +</p> +</form> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/shortlog-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +#header# + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">log</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/> + +#changenav%naventry#<br/> +</div> + +<table cellspacing="0"> +#entries# +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/style-gitweb.css Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,49 @@ +body { font-family: sans-serif; font-size: 12px; margin:0px; border:solid #d9d8d1; border-width:1px; margin:10px; } +a { color:#0000cc; } +a:hover, a:visited, a:active { color:#880000; } +div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; } +div.page_header a:visited { color:#0000cc; } +div.page_header a:hover { color:#880000; } +div.page_nav { padding:8px; } +div.page_nav a:visited { color:#0000cc; } +div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px} +div.page_footer { height:17px; padding:4px 8px; background-color: #d9d8d1; } +div.page_footer_text { float:left; color:#555555; font-style:italic; } +div.page_body { padding:8px; } +div.title, a.title { + display:block; padding:6px 8px; + font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000; +} +a.title:hover { background-color: #d9d8d1; } +div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; } +div.log_body { padding:8px 8px 8px 150px; } +.age { white-space:nowrap; } +span.age { position:relative; float:left; width:142px; font-style:italic; } +div.log_link { + padding:0px 8px; + font-size:10px; font-family:sans-serif; font-style:normal; + position:relative; float:left; width:136px; +} +div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; } +a.list { text-decoration:none; color:#000000; } +a.list:hover { text-decoration:underline; color:#880000; } +table { padding:8px 4px; } +th { padding:2px 5px; font-size:12px; text-align:left; } +tr.light:hover, .parity0:hover { background-color:#edece6; } +tr.dark, .parity1 { background-color:#f6f6f0; } +tr.dark:hover, .parity1:hover { background-color:#edece6; } +td { padding:2px 5px; font-size:12px; vertical-align:top; } +td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; } +div.pre { font-family:monospace; font-size:12px; white-space:pre; } +div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; } +div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; } +div.search { margin:4px 8px; position:absolute; top:56px; right:12px } +.linenr { color:#999999; text-decoration:none } +a.rss_logo { + float:right; padding:3px 0px; width:35px; line-height:10px; + border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e; + color:#ffffff; background-color:#ff6600; + font-weight:bold; font-family:sans-serif; font-size:10px; + text-align:center; text-decoration:none; +} +a.rss_logo:hover { background-color:#ee5500; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/static/style.css Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,72 @@ +a { text-decoration:none; } +.age { white-space:nowrap; } +.indexlinks { white-space:nowrap; } +.parity0 { background-color: #dddddd; } +.parity1 { background-color: #eeeeee; } +.lineno { width: 60px; color: #aaaaaa; font-size: smaller; + text-align: right; padding-right:1em; } +.plusline { color: green; } +.minusline { color: red; } +.atline { color: purple; } +.annotate { font-size: smaller; text-align: right; padding-right: 1em; } +.buttons a { + background-color: #666666; + padding: 2pt; + color: white; + font-family: sans; + font-weight: bold; +} +.navigate a { + background-color: #ccc; + padding: 2pt; + font-family: sans; + color: black; +} + +.metatag { + background-color: #888888; + color: white; + text-align: right; +} + +/* Common */ +pre { margin: 0; } + +.logo { + background-color: #333; + padding: 4pt; + margin: 8pt 0 8pt 8pt; + font-family: sans; + font-size: 60%; + color: white; + float: right; + clear: right; + text-align: left; +} + +.logo a { + font-weight: bold; + font-size: 150%; + color: #999; +} + +/* Changelog/Filelog entries */ +.logEntry { width: 100%; } +.logEntry .age { width: 15%; } +.logEntry th { font-weight: normal; text-align: right; vertical-align: top; } +.logEntry th.age, .logEntry th.firstline { font-weight: bold; } +.logEntry th.firstline { text-align: left; width: inherit; } + +/* Tag entries */ +#tagEntries { list-style: none; margin: 0; padding: 0; } +#tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; } + +/* Changeset entry */ +#changesetEntry { } +#changesetEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; } +#changesetEntry th.files, #changesetEntry th.description { vertical-align: top; } + +/* File diff view */ +#filediffEntry { } +#filediffEntry th { font-weight: normal; background-color: #888; color: #fff; text-align: right; } +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/summary-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#header# +<title>#repo|escape#: Summary</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / summary +</div> +<div class="page_nav"> +summary | <a href="?cmd=changelog;style=gitweb">changelog</a> | <a href="?cmd=tags;style=gitweb">tags</a> | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a><br/> +</div> + +<div class="title"> </div> +<table cellspacing="0"> +<tr><td>description</td><td>#desc#</td></tr> +<tr><td>owner</td><td>#owner|escape#</td></tr> +<!-- <tr><td>last change</td><td>#lastchange|rfc822date#</td></tr> --> +</table> + +<div><a class="title" href="?cmd=changelog;style=gitweb">changes</a></div> +<table cellspacing="0"> +#shortlog# +<tr class="light"><td colspan="3"><a class="list" href="?cmd=changelog;style=gitweb">...</a></td></tr> +</table> + +<div><a class="title" href="?cmd=tags;style=gitweb">tags</a></div> +<table cellspacing="0"> +#tags# +<tr class="light"><td colspan="3"><a class="list" href="?cmd=tags;style=gitweb">...</a></td></tr> +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/tagentry-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +<item> + <title>#tag|escape#</title> + <link>#url#?cs=#node|short#</link> + <description><![CDATA[#tag|strip|escape|addbreaks#]]></description> + <pubDate>#date|rfc822date#</pubDate> +</item>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/tags-gitweb.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +#header# +<title>#repo|escape#: Tags</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#"> +</head> +<body> + +<div class="page_header"> +<a href="http://www.selenic.com/mercurial/" title="Mercurial"><div style="float:right;">Mercurial</div></a><a href="?cmd=summary;style=gitweb">#repo|escape#</a> / tags +</div> + +<div class="page_nav"> +<a href="?cmd=summary;style=gitweb">summary</a> | <a href="?cmd=changelog;style=gitweb">changelog</a> | tags | <a href="?cmd=manifest;manifest=#manifest#;path=/;style=gitweb">manifest</a> +<br/> +</div> + +<table cellspacing="0"> +#entries%tagentry# +</table> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/tags-rss.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +#header# + <title>#repo|escape#: tags </title> + <description>#repo|escape# tag history</description> + #entriesnotip%tagentry# + </channel> +</rss>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/tags.tmpl Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +#header# +<title>#repo|escape#: tags</title> +<link rel="alternate" type="application/rss+xml" + href="?cmd=tags;style=rss" title="RSS feed for #repo|escape#: tags"> +</head> +<body> + +<div class="buttons"> +<a href="?cl=tip">changelog</a> +<a href="?mf=#manifest|short#;path=/">manifest</a> +<a type="application/rss+xml" href="?cmd=tags;style=rss">rss</a> +</div> + +<h2>tags:</h2> + +<ul id="tagEntries"> +#entries%tagentry# +</ul> + +#footer#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/template-vars.txt Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,39 @@ +repo the name of the repo +rev a changeset.manifest revision +node a changeset node +changesets total number of changesets +file a filename +filenode a file node +filerev a file revision +filerevs total number of file revisions +up the directory of the relevant file +path a path in the manifest, starting with "/" +basename a short pathname +manifest a manifest node +manifestrev a manifest revision +date a date string +age age in hours, days, etc +line a line of text (escaped) +desc a description (escaped, with breaks) +shortdesc a short description (escaped) +author a name or email addressv(obfuscated) +parent a list of the parent +child a list of the children +tags a list of tag + +header the global page header +footer the global page footer + +files a list of file links +dirs a set of directory links +diff a diff of one or more files +annotate an annotated file +entries the entries relevant to the page + +Templates and commands: + changelog(rev) - a page for browsing changesets + naventry - a link for jumping to a changeset number + filenodelink - jump to file diff + fileellipses - printed after maxfiles + changelogentry - an entry in the log + manifest - browse a manifest as a directory tree \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/README Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,33 @@ +A simple testing framework + +To run the tests, do: + +cd tests/ +python run-tests.py + +This finds all scripts in the test directory named test-* and executes +them. The scripts can be either shell scripts or Python. Each test is +run in a temporary directory that is removed when the test is complete. + +A test-<x> succeeds if the script returns success and its output +matches test-<x>.out. If the new output doesn't match, it is stored in +test-<x>.err. + +There are some tricky points here that you should be aware of when +writing tests: + +- hg commit and hg merge want user interaction + + for commit use -m "text" + for hg merge, set HGMERGE to something noninteractive (like true or merge) + +- changeset hashes will change based on user and date which make + things like hg history output change + + use commit -m "test" -u test -d "1000000 0" + +- diff will show the current time + + use hg diff | sed "s/\(\(---\|+++\) [a-zA-Z0-9_/.-]*\).*/\1/" to strip + dates +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/coverage.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,891 @@ +#!/usr/bin/python +# +# Perforce Defect Tracking Integration Project +# <http://www.ravenbrook.com/project/p4dti/> +# +# COVERAGE.PY -- COVERAGE TESTING +# +# Gareth Rees, Ravenbrook Limited, 2001-12-04 +# Ned Batchelder, 2004-12-12 +# http://nedbatchelder.com/code/modules/coverage.html +# +# +# 1. INTRODUCTION +# +# This module provides coverage testing for Python code. +# +# The intended readership is all Python developers. +# +# This document is not confidential. +# +# See [GDR 2001-12-04a] for the command-line interface, programmatic +# interface and limitations. See [GDR 2001-12-04b] for requirements and +# design. + +"""Usage: + +coverage.py -x MODULE.py [ARG1 ARG2 ...] + Execute module, passing the given command-line arguments, collecting + coverage data. + +coverage.py -e + Erase collected coverage data. + +coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ... + Report on the statement coverage for the given files. With the -m + option, show line numbers of the statements that weren't executed. + +coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ... + Make annotated copies of the given files, marking statements that + are executed with > and statements that are missed with !. With + the -d option, make the copies in that directory. Without the -d + option, make each copy in the same directory as the original. + +-o dir,dir2,... + Omit reporting or annotating files when their filename path starts with + a directory listed in the omit list. + e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits + +Coverage data is saved in the file .coverage by default. Set the +COVERAGE_FILE environment variable to save it somewhere else.""" + +__version__ = "2.5.20051204" # see detailed history at the end of this file. + +import compiler +import compiler.visitor +import os +import re +import string +import sys +import threading +import types + +# 2. IMPLEMENTATION +# +# This uses the "singleton" pattern. +# +# The word "morf" means a module object (from which the source file can +# be deduced by suitable manipulation of the __file__ attribute) or a +# filename. +# +# When we generate a coverage report we have to canonicalize every +# filename in the coverage dictionary just in case it refers to the +# module we are reporting on. It seems a shame to throw away this +# information so the data in the coverage dictionary is transferred to +# the 'cexecuted' dictionary under the canonical filenames. +# +# The coverage dictionary is called "c" and the trace function "t". The +# reason for these short names is that Python looks up variables by name +# at runtime and so execution time depends on the length of variables! +# In the bottleneck of this application it's appropriate to abbreviate +# names to increase speed. + +class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): + def __init__(self, statements, excluded, suite_spots): + compiler.visitor.ASTVisitor.__init__(self) + self.statements = statements + self.excluded = excluded + self.suite_spots = suite_spots + self.excluding_suite = 0 + + def doRecursive(self, node): + self.recordNodeLine(node) + for n in node.getChildNodes(): + self.dispatch(n) + + visitStmt = visitModule = doRecursive + + def doCode(self, node): + if hasattr(node, 'decorators') and node.decorators: + self.dispatch(node.decorators) + self.doSuite(node, node.code) + + visitFunction = visitClass = doCode + + def getFirstLine(self, node): + # Find the first line in the tree node. + lineno = node.lineno + for n in node.getChildNodes(): + f = self.getFirstLine(n) + if lineno and f: + lineno = min(lineno, f) + else: + lineno = lineno or f + return lineno + + def getLastLine(self, node): + # Find the first line in the tree node. + lineno = node.lineno + for n in node.getChildNodes(): + lineno = max(lineno, self.getLastLine(n)) + return lineno + + def doStatement(self, node): + self.recordLine(self.getFirstLine(node)) + + visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ + visitPrintnl = visitRaise = visitSubscript = visitDecorators = \ + doStatement + + def recordNodeLine(self, node): + return self.recordLine(node.lineno) + + def recordLine(self, lineno): + # Returns a bool, whether the line is included or excluded. + if lineno: + # Multi-line tests introducing suites have to get charged to their + # keyword. + if lineno in self.suite_spots: + lineno = self.suite_spots[lineno][0] + # If we're inside an exluded suite, record that this line was + # excluded. + if self.excluding_suite: + self.excluded[lineno] = 1 + return 0 + # If this line is excluded, or suite_spots maps this line to + # another line that is exlcuded, then we're excluded. + elif self.excluded.has_key(lineno) or \ + self.suite_spots.has_key(lineno) and \ + self.excluded.has_key(self.suite_spots[lineno][1]): + return 0 + # Otherwise, this is an executable line. + else: + self.statements[lineno] = 1 + return 1 + return 0 + + default = recordNodeLine + + def recordAndDispatch(self, node): + self.recordNodeLine(node) + self.dispatch(node) + + def doSuite(self, intro, body, exclude=0): + exsuite = self.excluding_suite + if exclude or (intro and not self.recordNodeLine(intro)): + self.excluding_suite = 1 + self.recordAndDispatch(body) + self.excluding_suite = exsuite + + def doPlainWordSuite(self, prevsuite, suite): + # Finding the exclude lines for else's is tricky, because they aren't + # present in the compiler parse tree. Look at the previous suite, + # and find its last line. If any line between there and the else's + # first line are excluded, then we exclude the else. + lastprev = self.getLastLine(prevsuite) + firstelse = self.getFirstLine(suite) + for l in range(lastprev+1, firstelse): + if self.suite_spots.has_key(l): + self.doSuite(None, suite, exclude=self.excluded.has_key(l)) + break + else: + self.doSuite(None, suite) + + def doElse(self, prevsuite, node): + if node.else_: + self.doPlainWordSuite(prevsuite, node.else_) + + def visitFor(self, node): + self.doSuite(node, node.body) + self.doElse(node.body, node) + + def visitIf(self, node): + # The first test has to be handled separately from the rest. + # The first test is credited to the line with the "if", but the others + # are credited to the line with the test for the elif. + self.doSuite(node, node.tests[0][1]) + for t, n in node.tests[1:]: + self.doSuite(t, n) + self.doElse(node.tests[-1][1], node) + + def visitWhile(self, node): + self.doSuite(node, node.body) + self.doElse(node.body, node) + + def visitTryExcept(self, node): + self.doSuite(node, node.body) + for i in range(len(node.handlers)): + a, b, h = node.handlers[i] + if not a: + # It's a plain "except:". Find the previous suite. + if i > 0: + prev = node.handlers[i-1][2] + else: + prev = node.body + self.doPlainWordSuite(prev, h) + else: + self.doSuite(a, h) + self.doElse(node.handlers[-1][2], node) + + def visitTryFinally(self, node): + self.doSuite(node, node.body) + self.doPlainWordSuite(node.body, node.final) + + def visitGlobal(self, node): + # "global" statements don't execute like others (they don't call the + # trace function), so don't record their line numbers. + pass + +the_coverage = None + +class coverage: + error = "coverage error" + + # Name of the cache file (unless environment variable is set). + cache_default = ".coverage" + + # Environment variable naming the cache file. + cache_env = "COVERAGE_FILE" + + # A dictionary with an entry for (Python source file name, line number + # in that file) if that line has been executed. + c = {} + + # A map from canonical Python source file name to a dictionary in + # which there's an entry for each line number that has been + # executed. + cexecuted = {} + + # Cache of results of calling the analysis2() method, so that you can + # specify both -r and -a without doing double work. + analysis_cache = {} + + # Cache of results of calling the canonical_filename() method, to + # avoid duplicating work. + canonical_filename_cache = {} + + def __init__(self): + global the_coverage + if the_coverage: + raise self.error, "Only one coverage object allowed." + self.usecache = 1 + self.cache = None + self.exclude_re = '' + self.nesting = 0 + self.cstack = [] + self.xstack = [] + self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep) + + # t(f, x, y). This method is passed to sys.settrace as a trace function. + # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and + # the arguments and return value of the trace function. + # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code + # objects. + + def t(self, f, w, a): #pragma: no cover + #print w, f.f_code.co_filename, f.f_lineno + if w == 'line': + self.c[(f.f_code.co_filename, f.f_lineno)] = 1 + for c in self.cstack: + c[(f.f_code.co_filename, f.f_lineno)] = 1 + return self.t + + def help(self, error=None): + if error: + print error + print + print __doc__ + sys.exit(1) + + def command_line(self): + import getopt + settings = {} + optmap = { + '-a': 'annotate', + '-d:': 'directory=', + '-e': 'erase', + '-h': 'help', + '-i': 'ignore-errors', + '-m': 'show-missing', + '-r': 'report', + '-x': 'execute', + '-o': 'omit=', + } + short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') + long_opts = optmap.values() + options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) + for o, a in options: + if optmap.has_key(o): + settings[optmap[o]] = 1 + elif optmap.has_key(o + ':'): + settings[optmap[o + ':']] = a + elif o[2:] in long_opts: + settings[o[2:]] = 1 + elif o[2:] + '=' in long_opts: + settings[o[2:]] = a + else: + self.help("Unknown option: '%s'." % o) + if settings.get('help'): + self.help() + for i in ['erase', 'execute']: + for j in ['annotate', 'report']: + if settings.get(i) and settings.get(j): + self.help("You can't specify the '%s' and '%s' " + "options at the same time." % (i, j)) + args_needed = (settings.get('execute') + or settings.get('annotate') + or settings.get('report')) + action = settings.get('erase') or args_needed + if not action: + self.help("You must specify at least one of -e, -x, -r, or -a.") + if not args_needed and args: + self.help("Unexpected arguments %s." % args) + + self.get_ready() + self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') + + if settings.get('erase'): + self.erase() + if settings.get('execute'): + if not args: + self.help("Nothing to do.") + sys.argv = args + self.start() + import __main__ + sys.path[0] = os.path.dirname(sys.argv[0]) + execfile(sys.argv[0], __main__.__dict__) + if not args: + args = self.cexecuted.keys() + ignore_errors = settings.get('ignore-errors') + show_missing = settings.get('show-missing') + directory = settings.get('directory') + omit = filter(None, settings.get('omit', '').split(',')) + omit += ['/<'] # Always skip /<string> etc. + + if settings.get('report'): + self.report(args, show_missing, ignore_errors, omit_prefixes=omit) + if settings.get('annotate'): + self.annotate(args, directory, ignore_errors, omit_prefixes=omit) + + def use_cache(self, usecache): + self.usecache = usecache + + def get_ready(self): + if self.usecache and not self.cache: + self.cache = os.path.abspath(os.environ.get(self.cache_env, + self.cache_default)) + self.restore() + self.analysis_cache = {} + + def start(self): + self.get_ready() + if self.nesting == 0: #pragma: no cover + sys.settrace(self.t) + if hasattr(threading, 'settrace'): + threading.settrace(self.t) + self.nesting += 1 + + def stop(self): + self.nesting -= 1 + if self.nesting == 0: #pragma: no cover + sys.settrace(None) + if hasattr(threading, 'settrace'): + threading.settrace(None) + + def erase(self): + self.c = {} + self.analysis_cache = {} + self.cexecuted = {} + if self.cache and os.path.exists(self.cache): + os.remove(self.cache) + self.exclude_re = "" + + def exclude(self, re): + if self.exclude_re: + self.exclude_re += "|" + self.exclude_re += "(" + re + ")" + + def begin_recursive(self): + self.cstack.append(self.c) + self.xstack.append(self.exclude_re) + + def end_recursive(self): + self.c = self.cstack.pop() + self.exclude_re = self.xstack.pop() + + # save(). Save coverage data to the coverage cache. + + def save(self): + # move to directory that must exist. + os.chdir(os.sep) + if self.usecache and self.cache: + self.canonicalize_filenames() + cache = open(self.cache, 'wb') + import marshal + marshal.dump(self.cexecuted, cache) + cache.close() + + # restore(). Restore coverage data from the coverage cache (if it exists). + + def restore(self): + self.c = {} + self.cexecuted = {} + assert self.usecache + if not os.path.exists(self.cache): + return + try: + cache = open(self.cache, 'rb') + import marshal + cexecuted = marshal.load(cache) + cache.close() + if isinstance(cexecuted, types.DictType): + self.cexecuted = cexecuted + except: + pass + + # canonical_filename(filename). Return a canonical filename for the + # file (that is, an absolute path with no redundant components and + # normalized case). See [GDR 2001-12-04b, 3.3]. + + def canonical_filename(self, filename): + if not self.canonical_filename_cache.has_key(filename): + f = filename + if os.path.isabs(f) and not os.path.exists(f): + f = os.path.basename(f) + if not os.path.isabs(f): + for path in [os.curdir] + sys.path: + g = os.path.join(path, f) + if os.path.exists(g): + f = g + break + cf = os.path.normcase(os.path.abspath(f)) + self.canonical_filename_cache[filename] = cf + return self.canonical_filename_cache[filename] + + # canonicalize_filenames(). Copy results from "c" to "cexecuted", + # canonicalizing filenames on the way. Clear the "c" map. + + def canonicalize_filenames(self): + for filename, lineno in self.c.keys(): + f = self.canonical_filename(filename) + if not self.cexecuted.has_key(f): + self.cexecuted[f] = {} + self.cexecuted[f][lineno] = 1 + self.c = {} + + # morf_filename(morf). Return the filename for a module or file. + + def morf_filename(self, morf): + if isinstance(morf, types.ModuleType): + if not hasattr(morf, '__file__'): + raise self.error, "Module has no __file__ attribute." + file = morf.__file__ + else: + file = morf + return self.canonical_filename(file) + + # analyze_morf(morf). Analyze the module or filename passed as + # the argument. If the source code can't be found, raise an error. + # Otherwise, return a tuple of (1) the canonical filename of the + # source code for the module, (2) a list of lines of statements + # in the source code, and (3) a list of lines of excluded statements. + + def analyze_morf(self, morf): + if self.analysis_cache.has_key(morf): + return self.analysis_cache[morf] + filename = self.morf_filename(morf) + ext = os.path.splitext(filename)[1] + if ext == '.pyc': + if not os.path.exists(filename[0:-1]): + raise self.error, ("No source for compiled code '%s'." + % filename) + filename = filename[0:-1] + elif ext != '.py': + raise self.error, "File '%s' not Python source." % filename + source = open(filename, 'r') + lines, excluded_lines = self.find_executable_statements( + source.read(), exclude=self.exclude_re + ) + source.close() + result = filename, lines, excluded_lines + self.analysis_cache[morf] = result + return result + + def get_suite_spots(self, tree, spots): + import symbol, token + for i in range(1, len(tree)): + if type(tree[i]) == type(()): + if tree[i][0] == symbol.suite: + # Found a suite, look back for the colon and keyword. + lineno_colon = lineno_word = None + for j in range(i-1, 0, -1): + if tree[j][0] == token.COLON: + lineno_colon = tree[j][2] + elif tree[j][0] == token.NAME: + if tree[j][1] == 'elif': + # Find the line number of the first non-terminal + # after the keyword. + t = tree[j+1] + while t and token.ISNONTERMINAL(t[0]): + t = t[1] + if t: + lineno_word = t[2] + else: + lineno_word = tree[j][2] + break + elif tree[j][0] == symbol.except_clause: + # "except" clauses look like: + # ('except_clause', ('NAME', 'except', lineno), ...) + if tree[j][1][0] == token.NAME: + lineno_word = tree[j][1][2] + break + if lineno_colon and lineno_word: + # Found colon and keyword, mark all the lines + # between the two with the two line numbers. + for l in range(lineno_word, lineno_colon+1): + spots[l] = (lineno_word, lineno_colon) + self.get_suite_spots(tree[i], spots) + + def find_executable_statements(self, text, exclude=None): + # Find lines which match an exclusion pattern. + excluded = {} + suite_spots = {} + if exclude: + reExclude = re.compile(exclude) + lines = text.split('\n') + for i in range(len(lines)): + if reExclude.search(lines[i]): + excluded[i+1] = 1 + + import parser + tree = parser.suite(text+'\n\n').totuple(1) + self.get_suite_spots(tree, suite_spots) + + # Use the compiler module to parse the text and find the executable + # statements. We add newlines to be impervious to final partial lines. + statements = {} + ast = compiler.parse(text+'\n\n') + visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) + compiler.walk(ast, visitor, walker=visitor) + + lines = statements.keys() + lines.sort() + excluded_lines = excluded.keys() + excluded_lines.sort() + return lines, excluded_lines + + # format_lines(statements, lines). Format a list of line numbers + # for printing by coalescing groups of lines as long as the lines + # represent consecutive statements. This will coalesce even if + # there are gaps between statements, so if statements = + # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then + # format_lines will return "1-2, 5-11, 13-14". + + def format_lines(self, statements, lines): + pairs = [] + i = 0 + j = 0 + start = None + pairs = [] + while i < len(statements) and j < len(lines): + if statements[i] == lines[j]: + if start == None: + start = lines[j] + end = lines[j] + j = j + 1 + elif start: + pairs.append((start, end)) + start = None + i = i + 1 + if start: + pairs.append((start, end)) + def stringify(pair): + start, end = pair + if start == end: + return "%d" % start + else: + return "%d-%d" % (start, end) + return string.join(map(stringify, pairs), ", ") + + # Backward compatibility with version 1. + def analysis(self, morf): + f, s, _, m, mf = self.analysis2(morf) + return f, s, m, mf + + def analysis2(self, morf): + filename, statements, excluded = self.analyze_morf(morf) + self.canonicalize_filenames() + if not self.cexecuted.has_key(filename): + self.cexecuted[filename] = {} + missing = [] + for line in statements: + if not self.cexecuted[filename].has_key(line): + missing.append(line) + return (filename, statements, excluded, missing, + self.format_lines(statements, missing)) + + def relative_filename(self, filename): + """ Convert filename to relative filename from self.relative_dir. + """ + return filename.replace(self.relative_dir, "") + + def morf_name(self, morf): + """ Return the name of morf as used in report. + """ + if isinstance(morf, types.ModuleType): + return morf.__name__ + else: + return self.relative_filename(os.path.splitext(morf)[0]) + + def filter_by_prefix(self, morfs, omit_prefixes): + """ Return list of morfs where the morf name does not begin + with any one of the omit_prefixes. + """ + filtered_morfs = [] + for morf in morfs: + for prefix in omit_prefixes: + if self.morf_name(morf).startswith(prefix): + break + else: + filtered_morfs.append(morf) + + return filtered_morfs + + def morf_name_compare(self, x, y): + return cmp(self.morf_name(x), self.morf_name(y)) + + def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]): + if not isinstance(morfs, types.ListType): + morfs = [morfs] + morfs = self.filter_by_prefix(morfs, omit_prefixes) + morfs.sort(self.morf_name_compare) + + max_name = max([5,] + map(len, map(self.morf_name, morfs))) + fmt_name = "%%- %ds " % max_name + fmt_err = fmt_name + "%s: %s" + header = fmt_name % "Name" + " Stmts Exec Cover" + fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" + if show_missing: + header = header + " Missing" + fmt_coverage = fmt_coverage + " %s" + if not file: + file = sys.stdout + print >>file, header + print >>file, "-" * len(header) + total_statements = 0 + total_executed = 0 + for morf in morfs: + name = self.morf_name(morf) + try: + _, statements, _, missing, readable = self.analysis2(morf) + n = len(statements) + m = n - len(missing) + if n > 0: + pc = 100.0 * m / n + else: + pc = 100.0 + args = (name, n, m, pc) + if show_missing: + args = args + (readable,) + print >>file, fmt_coverage % args + total_statements = total_statements + n + total_executed = total_executed + m + except KeyboardInterrupt: #pragma: no cover + raise + except: + if not ignore_errors: + type, msg = sys.exc_info()[0:2] + print >>file, fmt_err % (name, type, msg) + if len(morfs) > 1: + print >>file, "-" * len(header) + if total_statements > 0: + pc = 100.0 * total_executed / total_statements + else: + pc = 100.0 + args = ("TOTAL", total_statements, total_executed, pc) + if show_missing: + args = args + ("",) + print >>file, fmt_coverage % args + + # annotate(morfs, ignore_errors). + + blank_re = re.compile(r"\s*(#|$)") + else_re = re.compile(r"\s*else\s*:\s*(#|$)") + + def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]): + morfs = self.filter_by_prefix(morfs, omit_prefixes) + for morf in morfs: + try: + filename, statements, excluded, missing, _ = self.analysis2(morf) + self.annotate_file(filename, statements, excluded, missing, directory) + except KeyboardInterrupt: + raise + except: + if not ignore_errors: + raise + + def annotate_file(self, filename, statements, excluded, missing, directory=None): + source = open(filename, 'r') + if directory: + dest_file = os.path.join(directory, + os.path.basename(filename) + + ',cover') + else: + dest_file = filename + ',cover' + dest = open(dest_file, 'w') + lineno = 0 + i = 0 + j = 0 + covered = 1 + while 1: + line = source.readline() + if line == '': + break + lineno = lineno + 1 + while i < len(statements) and statements[i] < lineno: + i = i + 1 + while j < len(missing) and missing[j] < lineno: + j = j + 1 + if i < len(statements) and statements[i] == lineno: + covered = j >= len(missing) or missing[j] > lineno + if self.blank_re.match(line): + dest.write(' ') + elif self.else_re.match(line): + # Special logic for lines containing only 'else:'. + # See [GDR 2001-12-04b, 3.2]. + if i >= len(statements) and j >= len(missing): + dest.write('! ') + elif i >= len(statements) or j >= len(missing): + dest.write('> ') + elif statements[i] == missing[j]: + dest.write('! ') + else: + dest.write('> ') + elif lineno in excluded: + dest.write('- ') + elif covered: + dest.write('> ') + else: + dest.write('! ') + dest.write(line) + source.close() + dest.close() + +# Singleton object. +the_coverage = coverage() + +# Module functions call methods in the singleton object. +def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw) +def start(*args, **kw): return the_coverage.start(*args, **kw) +def stop(*args, **kw): return the_coverage.stop(*args, **kw) +def erase(*args, **kw): return the_coverage.erase(*args, **kw) +def begin_recursive(*args, **kw): return the_coverage.begin_recursive(*args, **kw) +def end_recursive(*args, **kw): return the_coverage.end_recursive(*args, **kw) +def exclude(*args, **kw): return the_coverage.exclude(*args, **kw) +def analysis(*args, **kw): return the_coverage.analysis(*args, **kw) +def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw) +def report(*args, **kw): return the_coverage.report(*args, **kw) +def annotate(*args, **kw): return the_coverage.annotate(*args, **kw) +def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw) + +# Save coverage data when Python exits. (The atexit module wasn't +# introduced until Python 2.0, so use sys.exitfunc when it's not +# available.) +try: + import atexit + atexit.register(the_coverage.save) +except ImportError: + sys.exitfunc = the_coverage.save + +# Command-line interface. +if __name__ == '__main__': + the_coverage.command_line() + + +# A. REFERENCES +# +# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; +# Ravenbrook Limited; 2001-12-04; +# <http://www.nedbatchelder.com/code/modules/rees-coverage.html>. +# +# [GDR 2001-12-04b] "Statement coverage for Python: design and +# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; +# <http://www.nedbatchelder.com/code/modules/rees-design.html>. +# +# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; +# Guide van Rossum; 2001-07-20; +# <http://www.python.org/doc/2.1.1/ref/ref.html>. +# +# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; +# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>. +# +# +# B. DOCUMENT HISTORY +# +# 2001-12-04 GDR Created. +# +# 2001-12-06 GDR Added command-line interface and source code +# annotation. +# +# 2001-12-09 GDR Moved design and interface to separate documents. +# +# 2001-12-10 GDR Open cache file as binary on Windows. Allow +# simultaneous -e and -x, or -a and -r. +# +# 2001-12-12 GDR Added command-line help. Cache analysis so that it +# only needs to be done once when you specify -a and -r. +# +# 2001-12-13 GDR Improved speed while recording. Portable between +# Python 1.5.2 and 2.1.1. +# +# 2002-01-03 GDR Module-level functions work correctly. +# +# 2002-01-07 GDR Update sys.path when running a file with the -x option, +# so that it matches the value the program would get if it were run on +# its own. +# +# 2004-12-12 NMB Significant code changes. +# - Finding executable statements has been rewritten so that docstrings and +# other quirks of Python execution aren't mistakenly identified as missing +# lines. +# - Lines can be excluded from consideration, even entire suites of lines. +# - The filesystem cache of covered lines can be disabled programmatically. +# - Modernized the code. +# +# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior +# and add 'analysis2'. Add a global for 'annotate', and factor it, adding +# 'annotate_file'. +# +# 2004-12-31 NMB Allow for keyword arguments in the module global functions. +# Thanks, Allen. +# +# 2005-12-02 NMB Call threading.settrace so that all threads are measured. +# Thanks Martin Fuzzey. Add a file argument to report so that reports can be +# captured to a different destination. +# +# 2005-12-03 NMB coverage.py can now measure itself. +# +# 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames, +# and sorting and omitting files to report on. +# +# C. COPYRIGHT AND LICENCE +# +# Copyright 2001 Gareth Rees. All rights reserved. +# Copyright 2004-2005 Ned Batchelder. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +# DAMAGE. +# +# $Id: coverage.py 26 2005-12-04 18:42:44Z ned $
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/fish-merge Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,88 @@ +#!/bin/sh + +set -e +set -x + +# skip commit logs +HGMERGE=tkmerge; export HGMERGE +EDITOR=true; export EDITOR + +rm -rf m m1 m2 +mkdir m +cd m + +echo "m this that" +echo "this" > a +echo "that" > b +hg init +hg addremove +hg commit +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd .. +echo "m2 this that " +mkdir m2 +cd m2 +hg branch ../m +hg checkout +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m +echo "m this1 that " +echo "this1" > a +hg commit +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd .. +echo "m1 this1 that " +mkdir m1 +cd m1 +hg branch ../m +hg checkout +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m1 +echo "m1 this1 that1" +echo "that1" > b +hg commit +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m2 +echo "m2 this that2" +echo "that2" > b +hg commit +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m1 +echo "m1:m2 this1 that1 that2" +hg merge ../m2 # b should conflict, a should be fine +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m2 +echo "m2 this2 that2" +echo "this2" > a +hg commit +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +cd ../m2 +echo "m2:m this12 that2" +hg merge ../m # a should conflict, b should be fine +echo "a:" `hg dump a` "b:" `hg dump b` +echo + +# now here's the interesting bit +# if we choose ancestor by file, no conflicts +# otherwise we've got two equally close ancestors, each with a conflict +# if we go back to the root, we'll have both conflicts again +echo "m2:m1 this12 that12" +hg merge ../m1 # should be clean +echo "a:" `hg dump a` "b:" `hg dump b` +echo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/md5sum.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,32 @@ +#! /usr/bin/env python +# +# Based on python's Tools/scripts/md5sum.py +# +# This software may be used and distributed according to the terms +# of the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2, which is +# GPL-compatible. + +import sys +import os +import md5 + +for filename in sys.argv[1:]: + try: + fp = open(filename, 'rb') + except IOError, msg: + sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg)) + sys.exit(1) + + m = md5.new() + try: + while 1: + data = fp.read(8192) + if not data: + break + m.update(data) + except IOError, msg: + sys.stderr.write('%s: I/O error: %s\n' % (filename, msg)) + sys.exit(1) + sys.stdout.write('%s %s\n' % (m.hexdigest(), filename)) + +sys.exit(0)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/run-tests.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,271 @@ +#!/usr/bin/env python +# +# run-tests.py - Run a set of tests on Mercurial +# +# Copyright 2006 Matt Mackall <mpm@selenic.com> +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import os, sys, shutil, re +import tempfile +import difflib +import popen2 +from optparse import OptionParser + +required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] + +parser = OptionParser("%prog [options] [tests]") +parser.add_option("-v", "--verbose", action="store_true", + help="output verbose messages") +parser.add_option("-c", "--cover", action="store_true", + help="print a test coverage report") +parser.add_option("-s", "--cover_stdlib", action="store_true", + help="print a test coverage report inc. standard libraries") +parser.add_option("-C", "--annotate", action="store_true", + help="output files annotated with coverage") +(options, args) = parser.parse_args() +verbose = options.verbose +coverage = options.cover or options.cover_stdlib or options.annotate + +def vlog(*msg): + if verbose: + for m in msg: + print m, + print + +def splitnewlines(text): + '''like str.splitlines, but only split on newlines. + keep line endings.''' + i = 0 + lines = [] + while True: + n = text.find('\n', i) + if n == -1: + last = text[i:] + if last: + lines.append(last) + return lines + lines.append(text[i:n+1]) + i = n + 1 + +def show_diff(expected, output): + for line in difflib.unified_diff(expected, output, + "Expected output", "Test output", lineterm=''): + sys.stdout.write(line) + +def find_program(program): + """Search PATH for a executable program""" + for p in os.environ.get('PATH', os.defpath).split(os.pathsep): + name = os.path.join(p, program) + if os.access(name, os.X_OK): + return name + return None + +def check_required_tools(): + # Before we go any further, check for pre-requisite tools + # stuff from coreutils (cat, rm, etc) are not tested + for p in required_tools: + if os.name == 'nt': + p += '.exe' + found = find_program(p) + if found: + vlog("# Found prerequisite", p, "at", found) + else: + print "WARNING: Did not find prerequisite tool: "+p + +def cleanup_exit(): + if verbose: + print "# Cleaning up HGTMP", HGTMP + shutil.rmtree(HGTMP, True) + +def install_hg(): + vlog("# Performing temporary installation of HG") + installerrs = os.path.join("tests", "install.err") + + os.chdir("..") # Get back to hg root + cmd = ('%s setup.py clean --all' + ' install --force --home="%s" --install-lib="%s" >%s 2>&1' + % (sys.executable, INST, PYTHONDIR, installerrs)) + vlog("# Running", cmd) + if os.system(cmd) == 0: + if not verbose: + os.remove(installerrs) + else: + f = open(installerrs) + for line in f: + print line, + f.close() + sys.exit(1) + os.chdir(TESTDIR) + + os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"]) + os.environ["PYTHONPATH"] = PYTHONDIR + + if coverage: + vlog("# Installing coverage wrapper") + os.environ['COVERAGE_FILE'] = COVERAGE_FILE + if os.path.exists(COVERAGE_FILE): + os.unlink(COVERAGE_FILE) + # Create a wrapper script to invoke hg via coverage.py + os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py")) + f = open(os.path.join(BINDIR, 'hg'), 'w') + f.write('#!' + sys.executable + '\n') + f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \ + '"%s", "-x", "%s"] + sys.argv[1:])\n' % ( + os.path.join(TESTDIR, 'coverage.py'), + os.path.join(BINDIR, '_hg.py'))) + f.close() + os.chmod(os.path.join(BINDIR, 'hg'), 0700) + +def output_coverage(): + vlog("# Producing coverage report") + omit = [BINDIR, TESTDIR, PYTHONDIR] + if not options.cover_stdlib: + # Exclude as system paths (ignoring empty strings seen on win) + omit += [x for x in sys.path if x != ''] + omit = ','.join(omit) + os.chdir(PYTHONDIR) + cmd = '"%s" "%s" -r "--omit=%s"' % ( + sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit) + vlog("# Running: "+cmd) + os.system(cmd) + if options.annotate: + adir = os.path.join(TESTDIR, 'annotated') + if not os.path.isdir(adir): + os.mkdir(adir) + cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % ( + sys.executable, os.path.join(TESTDIR, 'coverage.py'), + adir, omit) + vlog("# Running: "+cmd) + os.system(cmd) + +def run(cmd): + """Run command in a sub-process, capturing the output (stdout and stderr). + Return the exist code, and output.""" + # TODO: Use subprocess.Popen if we're running on Python 2.4 + if os.name == 'nt': + tochild, fromchild = os.popen4(cmd) + tochild.close() + output = fromchild.read() + ret = fromchild.close() + if ret == None: + ret = 0 + else: + proc = popen2.Popen4(cmd) + proc.tochild.close() + output = proc.fromchild.read() + ret = proc.wait() + return ret, splitnewlines(output) + +def run_one(test): + vlog("# Test", test) + if not verbose: + sys.stdout.write('.') + sys.stdout.flush() + + err = os.path.join(TESTDIR, test+".err") + ref = os.path.join(TESTDIR, test+".out") + + if os.path.exists(err): + os.remove(err) # Remove any previous output files + + # Make a tmp subdirectory to work in + tmpd = os.path.join(HGTMP, test) + os.mkdir(tmpd) + os.chdir(tmpd) + + if test.endswith(".py"): + cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test)) + else: + cmd = '"%s"' % (os.path.join(TESTDIR, test)) + + # To reliably get the error code from batch files on WinXP, + # the "cmd /c call" prefix is needed. Grrr + if os.name == 'nt' and test.endswith(".bat"): + cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test)) + + vlog("# Running", cmd) + ret, out = run(cmd) + vlog("# Ret was:", ret) + + diffret = 0 + # If reference output file exists, check test output against it + if os.path.exists(ref): + f = open(ref, "r") + ref_out = splitnewlines(f.read()) + f.close() + else: + ref_out = [''] + if out != ref_out: + diffret = 1 + print "\nERROR: %s output changed" % (test) + show_diff(ref_out, out) + if ret: + print "\nERROR: %s failed with error code %d" % (test, ret) + elif diffret: + ret = diffret + + if ret != 0: # Save errors to a file for diagnosis + f = open(err, "wb") + for line in out: + f.write(line) + f.close() + + os.chdir(TESTDIR) + shutil.rmtree(tmpd, True) + return ret == 0 + + +os.umask(022) + +check_required_tools() + +# Reset some environment variables to well-known values so that +# the tests produce repeatable output. +os.environ['LANG'] = os.environ['LC_ALL'] = 'C' +os.environ['TZ'] = 'GMT' + +os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' +os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"' +os.environ["HGUSER"] = "test" +os.environ["HGRCPATH"] = "" + +TESTDIR = os.environ["TESTDIR"] = os.getcwd() +HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.") +vlog("# Using TESTDIR", TESTDIR) +vlog("# Using HGTMP", HGTMP) + +INST = os.path.join(HGTMP, "install") +BINDIR = os.path.join(INST, "bin") +PYTHONDIR = os.path.join(INST, "lib", "python") +COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") + +try: + try: + install_hg() + + tests = 0 + failed = 0 + + if len(args) == 0: + args = os.listdir(".") + for test in args: + if test.startswith("test-"): + if '~' in test or re.search(r'\.(out|err)$', test): + continue + if not run_one(test): + failed += 1 + tests += 1 + + print "\n# Ran %d tests, %d failed." % (tests, failed) + if coverage: + output_coverage() + except KeyboardInterrupt: + failed = True + print "\ninterrupted!" +finally: + cleanup_exit() + +if failed: + sys.exit(1)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-addremove Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/bin/sh + +hg init rep +cd rep +mkdir dir +touch foo dir/bar +hg -v addremove +hg -v commit -m "add 1" -d "1000000 0" +cd dir/ +touch ../foo_2 bar_2 +hg -v addremove +hg -v commit -m "add 2" -d "1000000 0"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-addremove.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,10 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding dir/bar +adding foo +dir/bar +foo +(the addremove command is deprecated; use add and remove --after instead) +adding dir/bar_2 +adding foo_2 +dir/bar_2 +foo_2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-archive Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,53 @@ +#!/bin/sh + +mkdir test +cd test +hg init +echo foo>foo +hg addremove +hg commit -m 1 +echo bar>bar +hg addremove +hg commit -m 2 +mkdir baz +echo bletch>baz/bletch +hg addremove +hg commit -m 3 +echo "[web]" >> .hg/hgrc +echo "name = test-archive" >> .hg/hgrc +echo "allowzip = true" >> .hg/hgrc +echo "allowgz = true" >> .hg/hgrc +echo "allowbz2 = true" >> .hg/hgrc +hg serve -p 20059 -d --pid-file=hg.pid + +TIP=`hg id -v | cut -f1 -d' '` +QTIP=`hg id -q` +cat > getarchive.py <<EOF +import sys, urllib2 +node, archive = sys.argv[1:] +f = urllib2.urlopen('http://127.0.0.1:20059/?cmd=archive;node=%s;type=%s' + % (node, archive)) +sys.stdout.write(f.read()) +EOF +http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - | sed "s/$QTIP/TIP/" +http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - | sed "s/$QTIP/TIP/" +http_proxy= python getarchive.py "$TIP" zip > archive.zip +unzip -t archive.zip | sed "s/$QTIP/TIP/" + +kill `cat hg.pid` +sleep 1 # wait for server to scream and die + +hg archive -t tar test.tar +tar tf test.tar + +hg archive -t tbz2 -X baz test.tar.bz2 +bunzip2 -dc test.tar.bz2 | tar tf - + +hg archive -t tgz -p %b-%h test-%h.tar.gz +gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/" + +hg archive -t zip -p /illegal test.zip +hg archive -t zip -p very/../bad test.zip + +hg archive -t zip -r 2 test.zip +unzip -t test.zip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-archive.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,38 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding foo +(the addremove command is deprecated; use add and remove --after instead) +adding bar +(the addremove command is deprecated; use add and remove --after instead) +adding baz/bletch +test-archive-TIP/.hg_archival.txt +test-archive-TIP/bar +test-archive-TIP/baz/bletch +test-archive-TIP/foo +test-archive-TIP/.hg_archival.txt +test-archive-TIP/bar +test-archive-TIP/baz/bletch +test-archive-TIP/foo +Archive: archive.zip + testing: test-archive-TIP/.hg_archival.txt OK + testing: test-archive-TIP/bar OK + testing: test-archive-TIP/baz/bletch OK + testing: test-archive-TIP/foo OK +No errors detected in compressed data of archive.zip. +test/.hg_archival.txt +test/bar +test/baz/bletch +test/foo +test/.hg_archival.txt +test/bar +test/foo +test-TIP/.hg_archival.txt +test-TIP/bar +test-TIP/baz/bletch +test-TIP/foo +abort: archive prefix contains illegal components +Archive: test.zip + testing: test/.hg_archival.txt OK + testing: test/bar OK + testing: test/baz/bletch OK + testing: test/foo OK +No errors detected in compressed data of test.zip.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-backout Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,51 @@ +#!/bin/sh + +echo '# basic operation' +hg init basic +cd basic +echo a > a +hg commit -d '0 0' -A -m a +echo b >> a +hg commit -d '1 0' -m b + +hg backout -d '2 0' tip +cat a + +echo '# file that was removed is recreated' +cd .. +hg init remove +cd remove + +echo content > a +hg commit -d '0 0' -A -m a + +hg rm a +hg commit -d '1 0' -m b + +hg backout -d '2 0' --merge tip +cat a + +echo '# backout of backout is as if nothing happened' + +hg backout -d '3 0' --merge tip +cat a 2>/dev/null || echo cat: a: No such file or directory + +echo '# backout with merge' +cd .. +hg init merge +cd merge + +echo line 1 > a +hg commit -d '0 0' -A -m a + +echo line 2 >> a +hg commit -d '1 0' -m b + +echo line 3 >> a +hg commit -d '2 0' -m c + +hg backout --merge -d '3 0' 1 +hg commit -d '4 0' -m d +cat a + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-backout.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +# basic operation +adding a +changeset 2:b38a34ddfd9f backs out changeset 1:a820f4f40a57 +a +# file that was removed is recreated +adding a +adding a +changeset 2:44cd84c7349a backs out changeset 1:76862dcce372 +content +# backout of backout is as if nothing happened +removing a +changeset 3:0dd8a0ed5e99 backs out changeset 2:44cd84c7349a +cat: a: No such file or directory +# backout with merge +adding a +changeset 3:6c77ecc28460 backs out changeset 1:314f55b1bf23 +merging with changeset 2:b66ea5b77abb +merging a +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +line 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-backwards-remove Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/bin/sh +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +ls +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +hg co 0 +# B should disappear +ls
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-backwards-remove.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,3 @@ +a +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bad-pull Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +#!/bin/sh + +hg clone http://localhost:20059/ copy +echo $? +ls copy 2>/dev/null || echo copy: No such file or directory + +cat > dumb.py <<EOF +import BaseHTTPServer, SimpleHTTPServer, signal + +def run(server_class=BaseHTTPServer.HTTPServer, + handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler): + server_address = ('localhost', 20059) + httpd = server_class(server_address, handler_class) + httpd.serve_forever() + +signal.signal(signal.SIGTERM, lambda x: sys.exit(0)) +run() +EOF + +python dumb.py 2>/dev/null & + +http_proxy= hg clone http://localhost:20059/foo copy2 +echo $? + +kill $!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bad-pull.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +abort: error: Connection refused +255 +copy: No such file or directory +abort: HTTP Error 404: File not found +255
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-basic Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo a > a +hg add a +hg commit -m test -d "1000000 0" +hg history +hg manifest +hg cat a +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-basic.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +changeset: 0:0acdaf898367 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + +b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a +a +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bdiff Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import sys +from mercurial import bdiff, mpatch + +def test1(a, b): + d = bdiff.bdiff(a, b) + c = a + if d: + c = mpatch.patches(a, [d]) + if c != b: + print "***", `a`, `b` + print "bad:" + print `c`[:200] + print `d` + +def test(a, b): + print "***", `a`, `b` + test1(a, b) + test1(b, a) + +test("a\nc\n\n\n\n", "a\nb\n\n\n") +test("a\nb\nc\n", "a\nc\n") +test("", "") +test("a\nb\nc", "a\nb\nc") +test("a\nb\nc\nd\n", "a\nd\n") +test("a\nb\nc\nd\n", "a\nc\ne\n") +test("a\nb\nc\n", "a\nc\n") +test("a\n", "c\na\nb\n") +test("a\n", "") +test("a\n", "b\nc\n") +test("a\n", "c\na\n") +test("", "adjfkjdjksdhfksj") +test("", "ab") +test("", "abc") +test("a", "a") +test("ab", "ab") +test("abc", "abc") +test("a\n", "a\n") +test("a\nb", "a\nb") + +print "done"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bdiff.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +*** 'a\nc\n\n\n\n' 'a\nb\n\n\n' +*** 'a\nb\nc\n' 'a\nc\n' +*** '' '' +*** 'a\nb\nc' 'a\nb\nc' +*** 'a\nb\nc\nd\n' 'a\nd\n' +*** 'a\nb\nc\nd\n' 'a\nc\ne\n' +*** 'a\nb\nc\n' 'a\nc\n' +*** 'a\n' 'c\na\nb\n' +*** 'a\n' '' +*** 'a\n' 'b\nc\n' +*** 'a\n' 'c\na\n' +*** '' 'adjfkjdjksdhfksj' +*** '' 'ab' +*** '' 'abc' +*** 'a' 'a' +*** 'ab' 'ab' +*** 'abc' 'abc' +*** 'a\n' 'a\n' +*** 'a\nb' 'a\nb' +done
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bundle Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,54 @@ +#!/bin/sh + +hg init test +cd test +echo 0 > afile +hg add afile +hg commit -m "0.0" -d "1000000 0" +echo 1 >> afile +hg commit -m "0.1" -d "1000000 0" +echo 2 >> afile +hg commit -m "0.2" -d "1000000 0" +echo 3 >> afile +hg commit -m "0.3" -d "1000000 0" +hg update -C 0 +echo 1 >> afile +hg commit -m "1.1" -d "1000000 0" +echo 2 >> afile +hg commit -m "1.2" -d "1000000 0" +echo "a line" > fred +echo 3 >> afile +hg add fred +hg commit -m "1.3" -d "1000000 0" +hg mv afile adifferentfile +hg commit -m "1.3m" -d "1000000 0" +hg update -C 3 +hg mv afile anotherfile +hg commit -m "0.3m" -d "1000000 0" +hg verify +cd .. +hg init empty +hg -R test bundle full.hg empty +hg -R test unbundle full.hg +hg -R empty unbundle full.hg +hg -R empty heads +hg -R empty verify + +rm -rf empty +hg init empty +cd empty +hg -R bundle://../full.hg log +#doesn't work (yet ?) +#hg -R bundle://../full.hg verify +hg pull bundle://../full.hg +cd .. + +rm -rf empty +hg init empty +hg clone -r 3 test partial +hg clone partial partial2 +cd partial +hg -R bundle://../full.hg log +hg incoming bundle://../full.hg +hg -R bundle://../full.hg outgoing ../partial2 +cd ..
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bundle.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,203 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 2 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions +searching for changes +adding changesets +adding manifests +adding file changes +added 0 changesets with 0 changes to 4 files +(run 'hg update' to get a working copy) +adding changesets +adding manifests +adding file changes +added 9 changesets with 7 changes to 4 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +changeset: 8:836ac62537ab +tag: tip +parent: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3m + +changeset: 7:80fe151401c2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3m + +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions +changeset: 8:836ac62537ab +tag: tip +parent: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3m + +changeset: 7:80fe151401c2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3m + +changeset: 6:1e3f6b843bd6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3 + +changeset: 5:024e4e7df376 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.2 + +changeset: 4:5f4f3ceb285e +parent: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.1 + +changeset: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3 + +changeset: 2:d62976ca1e50 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.2 + +changeset: 1:10b2180f755b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.1 + +changeset: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.0 + +pulling from bundle://../full.hg +requesting all changes +adding changesets +adding manifests +adding file changes +added 9 changesets with 7 changes to 4 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +requesting all changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 4 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +changeset: 8:836ac62537ab +tag: tip +parent: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3m + +changeset: 7:80fe151401c2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3m + +changeset: 6:1e3f6b843bd6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3 + +changeset: 5:024e4e7df376 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.2 + +changeset: 4:5f4f3ceb285e +parent: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.1 + +changeset: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3 + +changeset: 2:d62976ca1e50 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.2 + +changeset: 1:10b2180f755b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.1 + +changeset: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.0 + +searching for changes +changeset: 4:5f4f3ceb285e +parent: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.1 + +changeset: 5:024e4e7df376 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.2 + +changeset: 6:1e3f6b843bd6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3 + +changeset: 7:80fe151401c2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3m + +changeset: 8:836ac62537ab +tag: tip +parent: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3m + +searching for changes +changeset: 4:5f4f3ceb285e +parent: 0:5649c9d34dd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.1 + +changeset: 5:024e4e7df376 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.2 + +changeset: 6:1e3f6b843bd6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3 + +changeset: 7:80fe151401c2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1.3m + +changeset: 8:836ac62537ab +tag: tip +parent: 3:ac69c658229d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0.3m +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-cat Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,18 @@ +#!/bin/sh +# +mkdir t +cd t +hg init +echo 0 > a +echo 0 > b +hg ci -A -m m -d "1000000 0" +hg rm a +hg cat a +sleep 1 # make sure mtime is changed +echo 1 > b +hg ci -m m -d "1000000 0" +echo 2 > b +hg cat -r 0 a +hg cat -r 0 b +hg cat -r 1 a +hg cat -r 1 b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-cat.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,7 @@ +adding a +adding b +0 +0 +0 +a: No such file in rev 03f6b0774996 +1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,27 @@ +#!/bin/sh + +mkdir a +cd a +hg init +echo a > a +hg add a +hg commit -m test -d '0 0' + +# Default operation +hg clone . ../b +cd ../b +cat a +hg verify + +# No update +hg clone -U . ../c +cd ../c +cat a 2>/dev/null || echo "a not present" +hg verify + +# Default destination +mkdir ../d +cd ../d +hg clone ../a +cd a +hg cat a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-failure Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +#!/bin/sh + +# No local source +hg clone a b +echo $? + +# No remote source +hg clone http://127.0.0.1:3121/a b +echo $? +rm -rf b # work around bug with http clone + +# Inaccessible source +mkdir a +chmod 000 a +hg clone a b +echo $? + +# Inaccessible destination +mkdir b +cd b +hg init +hg clone . ../a +echo $? +cd .. +chmod 700 a +rm -rf a b + +# Source of wrong type +mkfifo a +hg clone a b +echo $? +rm a + +# Default destination, same directory +mkdir q +cd q +hg init +cd .. +hg clone q + +true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-failure.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +abort: repository a not found! +255 +abort: error: Connection refused +255 +abort: repository a not found! +255 +abort: destination '../a' already exists +255 +abort: repository a not found! +255 +abort: destination 'q' already exists
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-pull-corruption Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,32 @@ +#!/bin/sh +# +# Corrupt an hg repo with a pull started during an aborted commit +# + +# Create two repos, so that one of them can pull from the other one. +hg init source +cd source +touch foo +hg add foo +hg ci -m 'add foo' +hg clone . ../corrupted +echo >> foo +hg ci -m 'change foo' + +# Add a hook to wait 5 seconds and then abort the commit +cd ../corrupted +echo '[hooks]' >> .hg/hgrc +echo 'pretxncommit = sleep 5; exit 1' >> .hg/hgrc + +# start a commit... +touch bar +hg add bar +hg ci -m 'add bar' & + +# ... and start a pull while the commit is still running +sleep 1 +hg pull ../source 2>/dev/null + +# see what happened +wait +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-pull-corruption.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../source +abort: pretxncommit hook exited with status 1 +transaction abort! +rollback completed +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-r Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,59 @@ +#!/bin/sh + +hg init test +cd test +cat >>afile <<EOF +0 +EOF +hg add afile +hg commit -m "0.0" +cat >>afile <<EOF +1 +EOF +hg commit -m "0.1" +cat >>afile <<EOF +2 +EOF +hg commit -m "0.2" +cat >>afile <<EOF +3 +EOF +hg commit -m "0.3" +hg update -C 0 +cat >>afile <<EOF +1 +EOF +hg commit -m "1.1" +cat >>afile <<EOF +2 +EOF +hg commit -m "1.2" +cat >fred <<EOF +a line +EOF +cat >>afile <<EOF +3 +EOF +hg add fred +hg commit -m "1.3" +hg mv afile adifferentfile +hg commit -m "1.3m" +hg update -C 3 +hg mv afile anotherfile +hg commit -m "0.3m" +hg debugindex .hg/data/afile.i +hg debugindex .hg/data/adifferentfile.i +hg debugindex .hg/data/anotherfile.i +hg debugindex .hg/data/fred.i +hg debugindex .hg/00manifest.i +hg verify +cd .. +for i in 0 1 2 3 4 5 6 7 8; do + hg clone -r "$i" test test-"$i" + cd test-"$i" + hg verify + cd .. +done +cd test-8 +hg pull ../test-7 +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone-r.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,137 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 2 files removed, 0 files unresolved + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 362fef284ce2 000000000000 000000000000 + 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 + 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 + 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 75 0 7 905359268f77 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 75 0 8 905359268f77 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 + 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 + 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 + 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 + 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 + 5 250 68 3 7 3b45cc2ab868 de68e904d169 000000000000 + 6 318 54 6 8 24d86153a002 f54c32f13478 000000000000 +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 3 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 4 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 4 changesets, 4 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 3 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 5 changes to 2 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 4 changesets, 5 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 5 changesets with 6 changes to 3 files +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +3 files, 5 changesets, 6 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 5 changesets with 5 changes to 2 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 5 changesets, 5 total revisions +pulling from ../test-7 +searching for changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 2 changes to 3 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-clone.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +a +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +a not present +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-command-template Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,91 @@ +#!/bin/sh + +hg init a +cd a +echo a > a +hg add a +echo line 1 > b +echo line 2 >> b +hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>' +hg add b +echo other 1 > c +echo other 2 >> c +echo >> c +echo other 3 >> c +hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>' +hg add c +hg commit -m 'no person' -d '1200000 0' -u 'other@place' +echo c >> c +hg commit -m 'no user, no domain' -d '1300000 0' -u 'person' + +# make sure user/global hgrc does not affect tests +echo '[ui]' > .hg/hgrc +echo 'logtemplate =' >> .hg/hgrc +echo 'style =' >> .hg/hgrc + +echo '# default style is like normal output' +hg log > log.out +hg log --style default > style.out +diff log.out style.out +hg log -v > log.out +hg log -v --style default > style.out +diff log.out style.out +hg log --debug > log.out +hg log --debug --style default > style.out +diff log.out style.out + +echo '# compact style works' +hg log --style compact +hg log -v --style compact +hg log --debug --style compact + +echo '# error if style not readable' +touch q +chmod 0 q +hg log --style ./q + +echo '# error if no style' +hg log --style notexist + +echo '# error if style missing key' +echo 'q = q' > t +hg log --style ./t + +echo '# error if include fails' +echo 'changeset = q' >> t +hg log --style ./t + +echo '# include works' +rm -f q +echo '{rev}' > q +hg log --style ./t + +echo '# ui.style works' +echo '[ui]' > .hg/hgrc +echo 'style = t' >> .hg/hgrc +hg log + +echo "# keys work" +for key in author branches date desc file_adds file_dels files \ + manifest node parents rev tags; do + for mode in '' --verbose --debug; do + hg log $mode --template "$key$mode: {$key}\n" + done +done + +echo '# filters work' +hg log --template '{author|domain}\n' +hg log --template '{author|person}\n' +hg log --template '{author|user}\n' +hg log --template '{date|age}\n' > /dev/null || exit 1 +hg log --template '{date|date}\n' +hg log --template '{date|isodate}\n' +hg log --template '{date|rfc822date}\n' +hg log --template '{desc|firstline}\n' +hg log --template '{node|short}\n' + +echo '# error on syntax' +echo 'x = "f' >> t +hg log + +echo '# done'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-command-template.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,255 @@ +# default style is like normal output +18a19 +> files: +29a31 +> files: +43a46 +> files: +# compact style works +3[tip] 10e46f2dcbf4 1970-01-16 01:06 +0000 person + no user, no domain + +2 97054abb4ab8 1970-01-14 21:20 +0000 other + no person + +1 b608e9d1a3f0 1970-01-13 17:33 +0000 other + other 1 + +0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user + line 1 + +3[tip] 10e46f2dcbf4 1970-01-16 01:06 +0000 person + no user, no domain + +2 97054abb4ab8 1970-01-14 21:20 +0000 other + no person + +1 b608e9d1a3f0 1970-01-13 17:33 +0000 other + other 1 + +0 1e4e1b8f71e0 1970-01-12 13:46 +0000 user + line 1 + +3[tip]:2,-1 10e46f2dcbf4 1970-01-16 01:06 +0000 person + no user, no domain + +2:1,-1 97054abb4ab8 1970-01-14 21:20 +0000 other + no person + +1:0,-1 b608e9d1a3f0 1970-01-13 17:33 +0000 other + other 1 + +0:-1,-1 1e4e1b8f71e0 1970-01-12 13:46 +0000 user + line 1 + +# error if style not readable +abort: Permission denied - ./q +# error if no style +abort: No such file or directory - notexist +# error if style missing key +abort: ./t: no key named 'changeset' +# error if include fails +abort: template file ./q: Permission denied +# include works +3 +2 +1 +0 +# ui.style works +3 +2 +1 +0 +# keys work +author: person +author: other@place +author: A. N. Other <other@place> +author: User Name <user@hostname> +author--verbose: person +author--verbose: other@place +author--verbose: A. N. Other <other@place> +author--verbose: User Name <user@hostname> +author--debug: person +author--debug: other@place +author--debug: A. N. Other <other@place> +author--debug: User Name <user@hostname> +branches: +branches: +branches: +branches: +branches--verbose: +branches--verbose: +branches--verbose: +branches--verbose: +branches--debug: +branches--debug: +branches--debug: +branches--debug: +date: 1300000.00 +date: 1200000.00 +date: 1100000.00 +date: 1000000.00 +date--verbose: 1300000.00 +date--verbose: 1200000.00 +date--verbose: 1100000.00 +date--verbose: 1000000.00 +date--debug: 1300000.00 +date--debug: 1200000.00 +date--debug: 1100000.00 +date--debug: 1000000.00 +desc: no user, no domain +desc: no person +desc: other 1 +other 2 + +other 3 +desc: line 1 +line 2 +desc--verbose: no user, no domain +desc--verbose: no person +desc--verbose: other 1 +other 2 + +other 3 +desc--verbose: line 1 +line 2 +desc--debug: no user, no domain +desc--debug: no person +desc--debug: other 1 +other 2 + +other 3 +desc--debug: line 1 +line 2 +file_adds: +file_adds: +file_adds: +file_adds: +file_adds--verbose: +file_adds--verbose: +file_adds--verbose: +file_adds--verbose: +file_adds--debug: +file_adds--debug: c +file_adds--debug: b +file_adds--debug: a +file_dels: +file_dels: +file_dels: +file_dels: +file_dels--verbose: +file_dels--verbose: +file_dels--verbose: +file_dels--verbose: +file_dels--debug: +file_dels--debug: +file_dels--debug: +file_dels--debug: +files: c +files: c +files: b +files: a +files--verbose: c +files--verbose: c +files--verbose: b +files--verbose: a +files--debug: c +files--debug: +files--debug: +files--debug: +manifest: +manifest: +manifest: +manifest: +manifest--verbose: +manifest--verbose: +manifest--verbose: +manifest--verbose: +manifest--debug: 3:cb5a1327723b +manifest--debug: 2:6e0e82995c35 +manifest--debug: 1:4e8d705b1e53 +manifest--debug: 0:a0c8bcbbb45c +node: 10e46f2dcbf4823578cf180f33ecf0b957964c47 +node: 97054abb4ab824450e9164180baf491ae0078465 +node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 +node: 1e4e1b8f71e05681d422154f5421e385fec3454f +node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47 +node--verbose: 97054abb4ab824450e9164180baf491ae0078465 +node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 +node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f +node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47 +node--debug: 97054abb4ab824450e9164180baf491ae0078465 +node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965 +node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f +parents: +parents: +parents: +parents: +parents--verbose: +parents--verbose: +parents--verbose: +parents--verbose: +parents--debug: 2:97054abb4ab8 -1:000000000000 +parents--debug: 1:b608e9d1a3f0 -1:000000000000 +parents--debug: 0:1e4e1b8f71e0 -1:000000000000 +parents--debug: -1:000000000000 -1:000000000000 +rev: 3 +rev: 2 +rev: 1 +rev: 0 +rev--verbose: 3 +rev--verbose: 2 +rev--verbose: 1 +rev--verbose: 0 +rev--debug: 3 +rev--debug: 2 +rev--debug: 1 +rev--debug: 0 +tags: tip +tags: +tags: +tags: +tags--verbose: tip +tags--verbose: +tags--verbose: +tags--verbose: +tags--debug: tip +tags--debug: +tags--debug: +tags--debug: +# filters work + +place +place +hostname +person +other +A. N. Other +User Name +person +other +other +user +Fri Jan 16 01:06:40 1970 +0000 +Wed Jan 14 21:20:00 1970 +0000 +Tue Jan 13 17:33:20 1970 +0000 +Mon Jan 12 13:46:40 1970 +0000 +1970-01-16 01:06 +0000 +1970-01-14 21:20 +0000 +1970-01-13 17:33 +0000 +1970-01-12 13:46 +0000 +Fri, 16 Jan 1970 01:06:40 +0000 +Wed, 14 Jan 1970 21:20:00 +0000 +Tue, 13 Jan 1970 17:33:20 +0000 +Mon, 12 Jan 1970 13:46:40 +0000 +no user, no domain +no person +other 1 +line 1 +10e46f2dcbf4 +97054abb4ab8 +b608e9d1a3f0 +1e4e1b8f71e0 +# error on syntax +abort: t:3: unmatched quotes +# done
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-commit Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,19 @@ +#!/bin/sh + +hg init test +cd test +echo foo > foo +hg add foo +hg commit -d '0 0' -m commit-1 +echo foo >> foo +hg commit -d '1 4444444' -m commit-3 +hg commit -d '1 15.1' -m commit-4 +hg commit -d 'foo bar' -m commit-5 +hg commit -d ' 1 4444' -m commit-6 +hg commit -d '111111111111 0' -m commit-7 + +echo bar > bar +hg add bar +rm bar +hg commit -d "1000000 0" -m commit-8 2>&1 | sed -e "s:/.*\(/test/.*\):...\1:" +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-commit.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +abort: impossible time zone offset: 4444444 +transaction abort! +rollback completed +abort: invalid date: '1\t15.1' +transaction abort! +rollback completed +abort: invalid date: 'foo bar' +transaction abort! +rollback completed +abort: invalid date: ' 1 4444' +transaction abort! +rollback completed +abort: date exceeds 32 bits: 111111111111 +transaction abort! +rollback completed +abort: No such file or directory: .../test/bar
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-committer Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,12 @@ +#!/bin/sh + +unset HGUSER +EMAIL="My Name <myname@example.com>" +export EMAIL + +hg init test +cd test +touch asdf +hg add asdf +hg commit -d '1000000 0' -m commit-1 +hg tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-committer.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +changeset: 0:9426b370c206 +tag: tip +user: My Name <myname@example.com> +date: Mon Jan 12 13:46:40 1970 +0000 +summary: commit-1 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-conflict Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +#!/bin/sh + +hg init +echo "nothing" > a +hg add a +hg commit -m ancestor -d "1000000 0" +echo "something" > a +hg commit -m branch1 -d "1000000 0" +hg co 0 +echo "something else" > a +hg commit -m branch2 -d "1000000 0" +HGMERGE=merge; export HGMERGE +hg merge 1 +hg id +egrep -v ">>>|<<<" a +hg status
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-conflict.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merge: warning: conflicts during merge +merging a +merging a failed! +0 files updated, 0 files merged, 0 files removed, 1 files unresolved +There are unresolved merges, you can redo the full merge using: + hg update -C 2 + hg merge 1 +e7fe8eb3e180+0d24b7662d3e+ tip +something else +======= +something +M a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-confused-revert Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,55 @@ +#!/bin/sh + +hg init +echo foo > a +hg add a +hg commit -m "1" -d "1000000 0" + +echo bar > b +hg add b +hg remove a + +echo "%%% should show a removed and b added" +hg status + +echo "reverting..." +hg revert + +echo "%%% should show b unknown and a back to normal" +hg status + +rm b + +hg co -C 0 +echo foo-a > a +hg commit -m "2a" -d "1000000 0" + +hg co -C 0 +echo foo-b > a +hg commit -m "2b" -d "1000000 0" + +HGMERGE=true hg merge 1 + +echo "%%% should show foo-b" +cat a + +echo bar > b +hg add b +rm a +hg remove a + +echo "%%% should show a removed and b added" +hg status + +echo "%%% revert should fail" +hg revert + +echo "%%% revert should be ok now" +hg revert -r2 + +echo "%%% should show b unknown and a marked modified (merged)" +hg status + +echo "%%% should show foo-b" +cat a +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-confused-revert.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,27 @@ +%%% should show a removed and b added +A b +R a +reverting... +undeleting a +forgetting b +%%% should show b unknown and a back to normal +? b +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merging a +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +%%% should show foo-b +foo-b +%%% should show a removed and b added +A b +R a +%%% revert should fail +abort: working dir has two parents; you must specify the revision to revert to +%%% revert should be ok now +undeleting a +forgetting b +%%% should show b unknown and a marked modified (merged) +? b +%%% should show foo-b +foo-b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-copy Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,30 @@ +#!/bin/sh + +hg init +echo a > a +hg add a +hg commit -m "1" -d "1000000 0" +hg status +hg copy a b +hg status +hg --debug commit -m "2" -d "1000000 0" +echo "we should see two history entries" +hg history -v +echo "we should see one log entry for a" +hg log a +echo "this should show a revision linked to changeset 0" +hg debugindex .hg/data/a.i +echo "we should see one log entry for b" +hg log b +echo "this should show a revision linked to changeset 1" +hg debugindex .hg/data/b.i + +echo "this should show the rename information in the metadata" +hg debugdata .hg/data/b.d 0 | head -3 | tail -2 + +$TESTDIR/md5sum.py .hg/data/b.i +hg cat b > bsum +$TESTDIR/md5sum.py bsum +hg cat a > asum +$TESTDIR/md5sum.py asum +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-copy.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,51 @@ +A b +b + b: copy a:b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 +we should see two history entries +changeset: 1:386a3cc01532710ca78aed9a54fa2f459c04f29c +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +files: b +description: +2 + + +changeset: 0:33aaa84a386bd609094aeb21a97c09436c482ef1 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +files: a +description: +1 + + +we should see one log entry for a +changeset: 0:33aaa84a386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +this should show a revision linked to changeset 0 + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 b789fdd96dc2 000000000000 000000000000 +we should see one log entry for b +changeset: 1:386a3cc01532 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +this should show a revision linked to changeset 1 + rev offset length base linkrev nodeid p1 p2 + 0 0 65 0 1 9a263dd772e0 000000000000 000000000000 +this should show the rename information in the metadata +copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 +copy: a +ed156f22f0a6fde642de0b5eba0cbbb2 .hg/data/b.i +60b725f10c9c85c70d97880dfe8191b3 bsum +60b725f10c9c85c70d97880dfe8191b3 asum +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 2 changesets, 2 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-copy2 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +#!/bin/sh + +hg init +echo foo > foo +hg add foo +hg commit -m1 -d"0 0" + +echo "# should show copy" +hg copy foo bar +hg debugstate|grep '^copy' + +echo "# shouldn't show copy" +hg commit -m2 -d"0 0" +hg debugstate|grep '^copy' + +echo "# should match" +hg debugindex .hg/data/foo.i +hg debugrename bar + +echo bleah > foo +echo quux > bar +hg commit -m3 -d"0 0" + +echo "# should not be renamed" +hg debugrename bar + +hg copy -f foo bar +echo "# should show copy" +hg debugstate|grep '^copy' +hg commit -m3 -d"0 0" + +echo "# should show no parents for tip" +hg debugindex .hg/data/bar.i +echo "# should match" +hg debugindex .hg/data/foo.i +hg debugrename bar + +echo "# should show no copies" +hg debugstate|grep '^copy' + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-copy2.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +# should show copy +copy: foo -> bar +# shouldn't show copy +# should match + rev offset length base linkrev nodeid p1 p2 + 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 +renamed from foo:2ed2a3912a0b24502043eae84ee4b279c18b90dd +# should not be renamed +not renamed +# should show copy +copy: foo -> bar +# should show no parents for tip + rev offset length base linkrev nodeid p1 p2 + 0 0 69 0 1 6ca237634e1f 000000000000 000000000000 + 1 69 6 1 2 7a1ff8e75f5b 6ca237634e1f 000000000000 + 2 75 82 1 3 243dfe60f3d9 000000000000 000000000000 +# should match + rev offset length base linkrev nodeid p1 p2 + 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 + 1 5 7 1 2 dd12c926cf16 2ed2a3912a0b 000000000000 +renamed from foo:dd12c926cf165e3eb4cf87b084955cb617221c17 +# should show no copies
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-diff-newlines Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +#!/bin/sh + +hg init +python -c 'print "confuse str.splitlines\nembedded\rnewline"' > a +hg ci -Ama -d '1 0' +echo clean diff >> a +hg ci -mb -d '2 0' +hg diff -r0 -r1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-diff-newlines.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +adding a +diff -r 107ba6f817b5 -r 310ce7989cdc a +--- a/a Thu Jan 01 00:00:01 1970 +0000 ++++ b/a Thu Jan 01 00:00:02 1970 +0000 +@@ -1,2 +1,3 @@ confuse str.splitlines + confuse str.splitlines + embedded newline ++clean diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-diffdir Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,18 @@ +#!/bin/sh + +hg init +touch a +hg add a +hg ci -m "a" -d "1000000 0" + +echo 123 > b +hg add b +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +hg diff -r tip | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +echo foo > a +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-diffdir.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +diff -r acd8075edac9 b +--- /dev/null ++++ b/b +@@ -0,0 +1,1 @@ ++123 +diff -r acd8075edac9 b +--- /dev/null ++++ b/b +@@ -0,0 +1,1 @@ ++123 +diff -r acd8075edac9 a +--- a/a ++++ b/a +@@ -0,0 +1,1 @@ ++foo +diff -r acd8075edac9 b +--- /dev/null ++++ b/b +@@ -0,0 +1,1 @@ ++123
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,7 @@ +#!/bin/sh + +hg init +hg log +hg grep wah +hg manifest +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty-dir Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +#!/bin/sh + +hg init +echo 123 > a +hg add a +hg commit -m "first" -d "1000000 0" a +mkdir sub +echo 321 > sub/b +hg add sub/b +hg commit -m "second" -d "1000000 0" sub/b +cat sub/b +hg co 0 +cat sub/b 2>/dev/null || echo "sub/b not present" +ls sub 2>/dev/null || echo "sub not present" + +true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty-dir.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,4 @@ +321 +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +sub/b not present +sub not present
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty-group Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,49 @@ +#!/bin/sh +# +# A B +# +# 3 4 3 +# |\/| |\ +# |/\| | \ +# 1 2 1 2 +# \ / \ / +# 0 0 +# +# if the result of the merge of 1 and 2 +# is the same in 3 and 4, no new manifest +# will be created and the manifest group +# will be empty during the pull +# +# (plus we test a failure where outgoing +# wrongly reported the number of csets) +# + +hg init a +cd a +touch init +hg ci -A -m 0 -d "1000000 0" +touch x y +hg ci -A -m 1 -d "1000000 0" +hg update 0 +touch x y +hg ci -A -m 2 -d "1000000 0" +hg merge 1 +hg ci -A -m m1 -d "1000000 0" +#hg log +#hg debugindex .hg/00manifest.i +hg update -C 1 +hg merge 2 +hg ci -A -m m2 -d "1000000 0" +#hg log +#hg debugindex .hg/00manifest.i + +cd .. +hg clone -r 3 a b +hg clone -r 4 a c +hg -R a outgoing b +hg -R a outgoing c +hg -R b outgoing c +hg -R c outgoing b + +hg -R b pull a +hg -R c pull a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty-group.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,72 @@ +adding init +adding x +adding y +0 files updated, 0 files merged, 2 files removed, 0 files unresolved +adding x +adding y +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +requesting all changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 3 changes to 3 files +3 files updated, 0 files merged, 0 files removed, 0 files unresolved +requesting all changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 3 changes to 3 files +3 files updated, 0 files merged, 0 files removed, 0 files unresolved +searching for changes +changeset: 4:fdb3c546e859 +tag: tip +parent: 1:1f703b3fcbc6 +parent: 2:de997049e034 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: m2 + +searching for changes +changeset: 3:f40f830c0024 +parent: 2:de997049e034 +parent: 1:1f703b3fcbc6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: m1 + +searching for changes +changeset: 3:f40f830c0024 +tag: tip +parent: 2:de997049e034 +parent: 1:1f703b3fcbc6 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: m1 + +searching for changes +changeset: 3:fdb3c546e859 +tag: tip +parent: 1:1f703b3fcbc6 +parent: 2:de997049e034 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: m2 + +pulling from a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 0 changes to 0 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +pulling from a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 0 changes to 0 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-empty.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +0 files, 0 changesets, 0 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-encode Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#!/bin/sh + +hg init + +cat > .hg/hgrc <<EOF +[encode] +*.gz = gunzip + +[decode] +*.gz = gzip + +EOF + +echo "this is a test" | gzip > a.gz +hg add a.gz +hg ci -m "test" -d "1000000 0" +echo %% no changes +hg status +touch a.gz + +echo %% no changes +hg status + +echo %% uncompressed contents in repo +hg debugdata .hg/data/a.gz.d 0 + +echo %% uncompress our working dir copy +gunzip < a.gz + +rm a.gz +hg co + +echo %% uncompress our new working dir copy +gunzip < a.gz
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-encode.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +%% no changes +%% no changes +%% uncompressed contents in repo +this is a test +%% uncompress our working dir copy +this is a test +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% uncompress our new working dir copy +this is a test
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-excessive-merge Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,46 @@ +#!/bin/sh + +hg init + +echo foo > a +echo foo > b +hg add a b + +hg ci -m "test" -d "1000000 0" + +echo blah > a + +hg ci -m "branch a" -d "1000000 0" + +hg co 0 + +echo blah > b + +hg ci -m "branch b" -d "1000000 0" +HGMERGE=true hg merge 1 + +hg ci -m "merge b/a -> blah" -d "1000000 0" + +hg co 1 +HGMERGE=true hg merge 2 +hg ci -m "merge a/b -> blah" -d "1000000 0" + +hg log +hg debugindex .hg/00changelog.i + +echo + +echo 1 +hg manifest 1 +echo 2 +hg manifest 2 +echo 3 +hg manifest 3 +echo 4 +hg manifest 4 + +echo + +hg debugindex .hg/data/a.i + +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-excessive-merge.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,65 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +changeset: 4:f6c172c6198c +tag: tip +parent: 1:448a8c5e42f1 +parent: 2:7c5dc2e857f2 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: merge a/b -> blah + +changeset: 3:13d875a22764 +parent: 2:7c5dc2e857f2 +parent: 1:448a8c5e42f1 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: merge b/a -> blah + +changeset: 2:7c5dc2e857f2 +parent: 0:dc1751ec2e9d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: branch b + +changeset: 1:448a8c5e42f1 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: branch a + +changeset: 0:dc1751ec2e9d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + + rev offset length base linkrev nodeid p1 p2 + 0 0 64 0 0 dc1751ec2e9d 000000000000 000000000000 + 1 64 68 1 1 448a8c5e42f1 dc1751ec2e9d 000000000000 + 2 132 68 2 2 7c5dc2e857f2 dc1751ec2e9d 000000000000 + 3 200 75 3 3 13d875a22764 7c5dc2e857f2 448a8c5e42f1 + 4 275 29 3 4 f6c172c6198c 448a8c5e42f1 7c5dc2e857f2 + +1 +79d7492df40aa0fa093ec4209be78043c181f094 644 a +2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 b +2 +2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 a +79d7492df40aa0fa093ec4209be78043c181f094 644 b +3 +79d7492df40aa0fa093ec4209be78043c181f094 644 a +79d7492df40aa0fa093ec4209be78043c181f094 644 b +4 +79d7492df40aa0fa093ec4209be78043c181f094 644 a +79d7492df40aa0fa093ec4209be78043c181f094 644 b + + rev offset length base linkrev nodeid p1 p2 + 0 0 5 0 0 2ed2a3912a0b 000000000000 000000000000 + 1 5 6 1 1 79d7492df40a 2ed2a3912a0b 000000000000 +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 5 changesets, 4 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filebranch Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,79 @@ +#!/bin/sh + +# This test makes sure that we don't mark a file as merged with its ancestor +# when we do a merge. + +cat <<'EOF' > merge +#!/bin/sh +echo merging for `basename $1` +EOF +chmod +x merge + +echo creating base +hg init a +cd a +echo 1 > foo +echo 1 > bar +echo 1 > baz +echo 1 > quux +hg add foo bar baz quux +hg commit -m "base" -d "1000000 0" + +cd .. +hg clone a b + +echo creating branch a +cd a +echo 2a > foo +echo 2a > bar +hg commit -m "branch a" -d "1000000 0" + +echo creating branch b + +cd .. +cd b +echo 2b > foo +echo 2b > baz +hg commit -m "branch b" -d "1000000 0" + +echo "we shouldn't have anything but n state here" +hg debugstate | cut -b 1-16,35- + +echo merging +hg pull ../a +env HGMERGE=../merge hg merge -v + +echo 2m > foo +echo 2b > baz +echo new > quux + +echo "we shouldn't have anything but foo in merge state here" +hg debugstate | cut -b 1-16,35- | grep "^m" + +hg ci -m "merge" -d "1000000 0" + +echo "main: we should have a merge here" +hg debugindex .hg/00changelog.i + +echo "log should show foo and quux changed" +hg log -v -r tip + +echo "foo: we should have a merge here" +hg debugindex .hg/data/foo.i + +echo "bar: we shouldn't have a merge here" +hg debugindex .hg/data/bar.i + +echo "baz: we shouldn't have a merge here" +hg debugindex .hg/data/baz.i + +echo "quux: we shouldn't have a merge here" +hg debugindex .hg/data/quux.i + +echo "manifest entries should match tips of all files" +hg manifest + +echo "everything should be clean now" +hg status + +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-filebranch.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,73 @@ +creating base +4 files updated, 0 files merged, 0 files removed, 0 files unresolved +creating branch a +creating branch b +we shouldn't have anything but n state here +n 644 2 bar +n 644 3 baz +n 644 3 foo +n 644 2 quux +merging +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 2 changes to 2 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +merging for foo +resolving manifests +getting bar +merging foo +resolving foo +1 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +we shouldn't have anything but foo in merge state here +m 644 3 foo +main: we should have a merge here + rev offset length base linkrev nodeid p1 p2 + 0 0 77 0 0 c36078bec30d 000000000000 000000000000 + 1 77 73 1 1 182b283965f1 c36078bec30d 000000000000 + 2 150 71 2 2 a6aef98656b7 c36078bec30d 000000000000 + 3 221 72 3 3 0c2cc6fc80e2 182b283965f1 a6aef98656b7 +log should show foo and quux changed +changeset: 3:0c2cc6fc80e2d4ee289bb658dbbe9ad932380fe9 +tag: tip +parent: 1:182b283965f1069c0112784e30e7755ad1c0dd52 +parent: 2:a6aef98656b71154cae9d87408abe6d0218c8045 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +files: foo quux +description: +merge + + +foo: we should have a merge here + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 b8e02f643373 000000000000 000000000000 + 1 3 4 1 1 2ffeddde1b65 b8e02f643373 000000000000 + 2 7 4 2 2 33d1fb69067a b8e02f643373 000000000000 + 3 11 4 3 3 aa27919ee430 2ffeddde1b65 33d1fb69067a +bar: we shouldn't have a merge here + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 b8e02f643373 000000000000 000000000000 + 1 3 4 1 2 33d1fb69067a b8e02f643373 000000000000 +baz: we shouldn't have a merge here + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 b8e02f643373 000000000000 000000000000 + 1 3 4 1 1 2ffeddde1b65 b8e02f643373 000000000000 +quux: we shouldn't have a merge here + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 b8e02f643373 000000000000 000000000000 + 1 3 5 1 3 6128c0f33108 b8e02f643373 000000000000 +manifest entries should match tips of all files +33d1fb69067a0139622a3fa3b7ba1cdb1367972e 644 bar +2ffeddde1b65b4827f6746174a145474129fa2ce 644 baz +aa27919ee4303cfd575e1fb932dd64d75aa08be4 644 foo +6128c0f33108e8cfbb4e0824d13ae48b466d7280 644 quux +everything should be clean now +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 4 changesets, 10 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-flags Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#!/bin/sh -e + +umask 027 +mkdir test1 +cd test1 + +hg init +touch a b +hg add a b +hg ci -m "added a b" -d "1000000 0" + +cd .. +mkdir test2 +cd test2 + +hg init +hg pull ../test1 +hg co +chmod +x a +hg ci -m "chmod +x a" -d "1000000 0" + +cd ../test1 +echo 123 >>a +hg ci -m "a updated" -d "1000000 0" + +hg pull ../test2 +hg heads +hg history + +hg -v merge + +ls -l ../test[12]/a > foo +cut -b 1-10 < foo +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-flags.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,51 @@ +pulling from ../test1 +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 2 changes to 2 files +(run 'hg update' to get a working copy) +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../test2 +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +changeset: 2:b833d578451e +tag: tip +parent: 0:4536b1c2ca69 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: chmod +x a + +changeset: 1:a187cb361a5a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: a updated + +changeset: 2:b833d578451e +tag: tip +parent: 0:4536b1c2ca69 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: chmod +x a + +changeset: 1:a187cb361a5a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: a updated + +changeset: 0:4536b1c2ca69 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: added a b + +resolving manifests +merging a +resolving a +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +-rwxr-x--- +-rwxr-x---
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-globalopts Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,72 @@ +#!/bin/sh + +hg init a +cd a +echo a > a +hg ci -A -d'1 0' -m a + +cd .. + +hg init b +cd b +echo b > b +hg ci -A -d'1 0' -m b + +cd .. + +hg clone a c +cd c +hg pull -f ../b +HGMERGE=merge hg merge + +cd .. + +echo %% -R/--repository +hg -R a tip +hg --repository b tip + +echo %% abbrev of long option +hg --repo c tip + +echo %% --cwd +hg --cwd a parents + +echo %% -y/--noninteractive - just be sure it is parsed +hg --cwd a tip -q --noninteractive +hg --cwd a tip -q -y + +echo %% -q/--quiet +hg -R a -q tip +hg -R b -q tip +hg -R c --quiet parents + +echo %% -v/--verbose +hg --cwd c head -v +hg --cwd b tip --verbose + +echo %% --config +hg --cwd c --config paths.quuxfoo=bar paths | grep -q quuxfoo && echo quuxfoo +hg --cwd c --config '' tip -q +hg --cwd c --config a.b tip -q +hg --cwd c --config a tip -q +hg --cwd c --config a.= tip -q +hg --cwd c --config .b= tip -q + +echo %% --debug +hg --cwd c log --debug + +echo %% --traceback +hg --cwd c --config x --traceback tip 2>&1 | grep -i 'traceback' + +echo %% --time +hg --cwd a --time tip 2>&1 | grep '^Time:' | sed 's/[0-9][0-9]*/x/g' + +echo %% --version +hg --version -q | sed 's/version \([a-f0-9+]*\|unknown\)/version xxx/' + +echo %% -h/--help +hg -h +hg --help + +echo %% not tested: --debugger +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-globalopts.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,205 @@ +adding a +adding b +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../b +searching for changes +warning: repository is unrelated +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +%% -R/--repository +changeset: 0:8580ff50825a +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +summary: a + +changeset: 0:b6c483daf290 +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +summary: b + +%% abbrev of long option +changeset: 1:b6c483daf290 +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +summary: b + +%% --cwd +changeset: 0:8580ff50825a +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +summary: a + +%% -y/--noninteractive - just be sure it is parsed +0:8580ff50825a +0:8580ff50825a +%% -q/--quiet +0:8580ff50825a +0:b6c483daf290 +0:8580ff50825a +1:b6c483daf290 +%% -v/--verbose +changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +files: b +description: +b + + +changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +files: a +description: +a + + +changeset: 0:b6c483daf2907ce5825c0bb50f5716226281cc1a +tag: tip +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +files: b +description: +b + + +%% --config +quuxfoo +abort: malformed --config option: +abort: malformed --config option: a.b +abort: malformed --config option: a +abort: malformed --config option: a.= +abort: malformed --config option: .b= +%% --debug +changeset: 1:b6c483daf2907ce5825c0bb50f5716226281cc1a +tag: tip +parent: -1:0000000000000000000000000000000000000000 +parent: -1:0000000000000000000000000000000000000000 +manifest: 1:23226e7a252cacdc2d99e4fbdc3653441056de49 +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +files+: b +description: +b + + +changeset: 0:8580ff50825a50c8f716709acdf8de0deddcd6ab +parent: -1:0000000000000000000000000000000000000000 +parent: -1:0000000000000000000000000000000000000000 +manifest: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 +user: test +date: Thu Jan 01 00:00:01 1970 +0000 +files+: a +description: +a + + +%% --traceback +%% --time +Time: real x.x secs (user x.x+x.x sys x.x+x.x) +%% --version +Mercurial Distributed SCM (version xxx) +%% -h/--help +Mercurial Distributed SCM + +list of commands (use "hg help -v" to show aliases and global options): + + add add the specified files on the next commit + annotate show changeset information per file line + archive create unversioned archive of a repository revision + backout reverse effect of earlier changeset + bundle create a changegroup file + cat output the latest or given revisions of files + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + copy mark files as copied for the next commit + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + grep search for a pattern in specified files and revisions + heads show current repository heads + help show help for a given command or all commands + identify print information about the working copy + import import an ordered set of patches + incoming show new changesets found in source + init create a new repository in the given directory + locate locate files matching specific patterns + log show revision history of entire repository or files + manifest output the latest or given revision of the project manifest + merge Merge working directory with another revision + outgoing show changesets not found in destination + parents show the parents of the working dir or revision + paths show definition of symbolic path names + pull pull changes from the specified source + push push changes to the specified destination + recover roll back an interrupted transaction + remove remove the specified files on the next commit + rename rename files; equivalent of copy + remove + revert revert files or dirs to their states as of some revision + rollback roll back the last transaction in this repository + root print the root (top) of the current working dir + serve export the repository via HTTP + status show changed files in the working directory + tag add a tag for the current tip or a given revision + tags list repository tags + tip show the tip revision + unbundle apply a changegroup file + update update or merge working directory + verify verify the integrity of the repository + version output version and copyright information +Mercurial Distributed SCM + +list of commands (use "hg help -v" to show aliases and global options): + + add add the specified files on the next commit + annotate show changeset information per file line + archive create unversioned archive of a repository revision + backout reverse effect of earlier changeset + bundle create a changegroup file + cat output the latest or given revisions of files + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + copy mark files as copied for the next commit + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + grep search for a pattern in specified files and revisions + heads show current repository heads + help show help for a given command or all commands + identify print information about the working copy + import import an ordered set of patches + incoming show new changesets found in source + init create a new repository in the given directory + locate locate files matching specific patterns + log show revision history of entire repository or files + manifest output the latest or given revision of the project manifest + merge Merge working directory with another revision + outgoing show changesets not found in destination + parents show the parents of the working dir or revision + paths show definition of symbolic path names + pull pull changes from the specified source + push push changes to the specified destination + recover roll back an interrupted transaction + remove remove the specified files on the next commit + rename rename files; equivalent of copy + remove + revert revert files or dirs to their states as of some revision + rollback roll back the last transaction in this repository + root print the root (top) of the current working dir + serve export the repository via HTTP + status show changed files in the working directory + tag add a tag for the current tip or a given revision + tags list repository tags + tip show the tip revision + unbundle apply a changegroup file + update update or merge working directory + verify verify the integrity of the repository + version output version and copyright information +%% not tested: --debugger
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-grep Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,23 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo import > port +hg add port +hg commit -m 0 -u spam -d '0 0' +echo export >> port +hg commit -m 1 -u eggs -d '1 0' +echo export > port +echo vaportight >> port +echo 'import/export' >> port +hg commit -m 2 -u spam -d '2 0' +echo 'import/export' >> port +hg commit -m 3 -u eggs -d '3 0' +head -n 3 port > port1 +mv port1 port +hg commit -m 4 -u spam -d '4 0' +hg grep port port +echo 'FIXME: history is wrong here' +hg grep --all -nu port port +hg grep import port
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-grep.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,10 @@ +port:4:export +port:4:vaportight +port:4:import/export +FIXME: history is wrong here +port:1:1:-:eggs:import +port:1:2:+:eggs:vaportight +port:1:3:+:eggs:import/export +port:0:2:+:spam:export +port:0:1:+:spam:import +port:4:import/export
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-help Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +#!/bin/sh + +hg +hg -q +hg help +hg -q help +hg add -h +hg add --skjdfks +hg help diff +hg help status +hg -q help status +hg help foo +hg skjdfks + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-help.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,253 @@ +Mercurial Distributed SCM + +basic commands (use "hg help" for the full list or option "-v" for details): + + add add the specified files on the next commit + annotate show changeset information per file line + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + init create a new repository in the given directory + log show revision history of entire repository or files + parents show the parents of the working dir or revision + pull pull changes from the specified source + push push changes to the specified destination + remove remove the specified files on the next commit + revert revert files or dirs to their states as of some revision + serve export the repository via HTTP + status show changed files in the working directory + update update or merge working directory + add add the specified files on the next commit + annotate show changeset information per file line + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + init create a new repository in the given directory + log show revision history of entire repository or files + parents show the parents of the working dir or revision + pull pull changes from the specified source + push push changes to the specified destination + remove remove the specified files on the next commit + revert revert files or dirs to their states as of some revision + serve export the repository via HTTP + status show changed files in the working directory + update update or merge working directory +Mercurial Distributed SCM + +list of commands (use "hg help -v" to show aliases and global options): + + add add the specified files on the next commit + annotate show changeset information per file line + archive create unversioned archive of a repository revision + backout reverse effect of earlier changeset + bundle create a changegroup file + cat output the latest or given revisions of files + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + copy mark files as copied for the next commit + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + grep search for a pattern in specified files and revisions + heads show current repository heads + help show help for a given command or all commands + identify print information about the working copy + import import an ordered set of patches + incoming show new changesets found in source + init create a new repository in the given directory + locate locate files matching specific patterns + log show revision history of entire repository or files + manifest output the latest or given revision of the project manifest + merge Merge working directory with another revision + outgoing show changesets not found in destination + parents show the parents of the working dir or revision + paths show definition of symbolic path names + pull pull changes from the specified source + push push changes to the specified destination + recover roll back an interrupted transaction + remove remove the specified files on the next commit + rename rename files; equivalent of copy + remove + revert revert files or dirs to their states as of some revision + rollback roll back the last transaction in this repository + root print the root (top) of the current working dir + serve export the repository via HTTP + status show changed files in the working directory + tag add a tag for the current tip or a given revision + tags list repository tags + tip show the tip revision + unbundle apply a changegroup file + update update or merge working directory + verify verify the integrity of the repository + version output version and copyright information + add add the specified files on the next commit + annotate show changeset information per file line + archive create unversioned archive of a repository revision + backout reverse effect of earlier changeset + bundle create a changegroup file + cat output the latest or given revisions of files + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + copy mark files as copied for the next commit + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + grep search for a pattern in specified files and revisions + heads show current repository heads + help show help for a given command or all commands + identify print information about the working copy + import import an ordered set of patches + incoming show new changesets found in source + init create a new repository in the given directory + locate locate files matching specific patterns + log show revision history of entire repository or files + manifest output the latest or given revision of the project manifest + merge Merge working directory with another revision + outgoing show changesets not found in destination + parents show the parents of the working dir or revision + paths show definition of symbolic path names + pull pull changes from the specified source + push push changes to the specified destination + recover roll back an interrupted transaction + remove remove the specified files on the next commit + rename rename files; equivalent of copy + remove + revert revert files or dirs to their states as of some revision + rollback roll back the last transaction in this repository + root print the root (top) of the current working dir + serve export the repository via HTTP + status show changed files in the working directory + tag add a tag for the current tip or a given revision + tags list repository tags + tip show the tip revision + unbundle apply a changegroup file + update update or merge working directory + verify verify the integrity of the repository + version output version and copyright information +hg add [OPTION]... [FILE]... + +add the specified files on the next commit + + Schedule files to be version controlled and added to the repository. + + The files will be added to the repository at the next commit. + + If no names are given, add all files in the repository. + +options: + + -I --include include names matching the given patterns + -X --exclude exclude names matching the given patterns +hg add: option --skjdfks not recognized +hg add [OPTION]... [FILE]... + +add the specified files on the next commit + + Schedule files to be version controlled and added to the repository. + + The files will be added to the repository at the next commit. + + If no names are given, add all files in the repository. + +options: + + -I --include include names matching the given patterns + -X --exclude exclude names matching the given patterns +hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]... + +diff repository (or selected files) + + Show differences between revisions for the specified files. + + Differences between files are shown using the unified diff format. + + When two revision arguments are given, then changes are shown + between those revisions. If only one revision is specified then + that revision is compared to the working directory, and, when no + revisions are specified, the working directory files are compared + to its parent. + + Without the -a option, diff will avoid generating diffs of files + it detects as binary. With -a, diff will generate a diff anyway, + probably with undesirable results. + +options: + + -r --rev revision + -a --text treat all files as text + -p --show-function show which function each change is in + -w --ignore-all-space ignore white space when comparing lines + -I --include include names matching the given patterns + -X --exclude exclude names matching the given patterns +hg status [OPTION]... [FILE]... + +show changed files in the working directory + + Show changed files in the repository. If names are + given, only files that match are shown. + + The codes used to show the status of files are: + M = modified + A = added + R = removed + ! = deleted, but still tracked + ? = not tracked + I = ignored (not shown by default) + +aliases: st + +options: + + -m --modified show only modified files + -a --added show only added files + -r --removed show only removed files + -d --deleted show only deleted (but tracked) files + -u --unknown show only unknown (not tracked) files + -i --ignored show ignored files + -n --no-status hide status prefix + -0 --print0 end filenames with NUL, for use with xargs + -I --include include names matching the given patterns + -X --exclude exclude names matching the given patterns +hg status [OPTION]... [FILE]... + +show changed files in the working directory +hg: unknown command 'foo' +Mercurial Distributed SCM + +basic commands (use "hg help" for the full list or option "-v" for details): + + add add the specified files on the next commit + annotate show changeset information per file line + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + init create a new repository in the given directory + log show revision history of entire repository or files + parents show the parents of the working dir or revision + pull pull changes from the specified source + push push changes to the specified destination + remove remove the specified files on the next commit + revert revert files or dirs to their states as of some revision + serve export the repository via HTTP + status show changed files in the working directory + update update or merge working directory +hg: unknown command 'skjdfks' +Mercurial Distributed SCM + +basic commands (use "hg help" for the full list or option "-v" for details): + + add add the specified files on the next commit + annotate show changeset information per file line + clone make a copy of an existing repository + commit commit the specified files or all outstanding changes + diff diff repository (or selected files) + export dump the header and diffs for one or more changesets + init create a new repository in the given directory + log show revision history of entire repository or files + parents show the parents of the working dir or revision + pull pull changes from the specified source + push push changes to the specified destination + remove remove the specified files on the next commit + revert revert files or dirs to their states as of some revision + serve export the repository via HTTP + status show changed files in the working directory + update update or merge working directory
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hgignore Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,45 @@ +#!/bin/sh + +hg init +touch a.o +touch a.c +touch syntax +mkdir dir +touch dir/a.o +touch dir/b.o +touch dir/c.o + +hg add dir/a.o +hg commit -m 0 +hg add dir/b.o + +echo "--" ; hg status + +echo "*.o" > .hgignore +echo "--" ; hg status 2>&1 | sed -e 's/abort: .*\.hgignore:/abort: .hgignore:/' + +echo ".*\.o" > .hgignore +echo "--" ; hg status + +# XXX: broken +#echo "glob:**.o" > .hgignore +#echo "--" ; hg status +# +#echo "glob:*.o" > .hgignore +#echo "--" ; hg status + +echo "syntax: invalid" > .hgignore +echo "--" ; hg status 2>&1 | sed -e 's/.*\.hgignore:/.hgignore:/' + +echo "syntax: glob" > .hgignore +echo "*.o" >> .hgignore +echo "--" ; hg status + +echo "relglob:syntax*" > .hgignore +echo "--" ; hg status + +echo "relglob:*" > .hgignore +echo "--" ; hg status + +cd dir +echo "--" ; hg status .
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hgignore.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,36 @@ +-- +A dir/b.o +? a.c +? a.o +? dir/c.o +? syntax +-- +abort: .hgignore: invalid pattern (relre): *.o +-- +A dir/b.o +? .hgignore +? a.c +? syntax +-- +.hgignore: ignoring invalid syntax 'invalid' +A dir/b.o +? .hgignore +? a.c +? a.o +? dir/c.o +? syntax +-- +A dir/b.o +? .hgignore +? a.c +? syntax +-- +A dir/b.o +? .hgignore +? a.c +? a.o +? dir/c.o +-- +A dir/b.o +-- +A b.o
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hgrc Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,7 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo "invalid" > .hg/hgrc +hg status 2>&1 |sed -e "s:/.*\(/t/.*\):...\1:"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hgrc.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,4 @@ +abort: Failed to parse .../t/.hg/hgrc +File contains no section headers. +file: .../t/.hg/hgrc, line: 1 +'invalid\n'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hook Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,186 @@ +#!/bin/sh + +# commit hooks can see env vars +hg init a +cd a +echo "[hooks]" > .hg/hgrc +echo 'commit = echo commit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc +echo 'commit.b = echo commit hook b' >> .hg/hgrc +echo 'precommit = echo precommit hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc +echo 'pretxncommit = echo pretxncommit hook: n=$HG_NODE p1=$HG_PARENT1 p2=$HG_PARENT2; hg -q tip' >> .hg/hgrc +echo a > a +hg add a +hg commit -m a -d "1000000 0" + +hg clone . ../b +cd ../b + +# changegroup hooks can see env vars +echo '[hooks]' > .hg/hgrc +echo 'prechangegroup = echo prechangegroup hook' >> .hg/hgrc +echo 'changegroup = echo changegroup hook: n=$HG_NODE' >> .hg/hgrc +echo 'incoming = echo incoming hook: n=$HG_NODE' >> .hg/hgrc + +# pretxncommit and commit hooks can see both parents of merge +cd ../a +echo b >> a +hg commit -m a1 -d "1 0" +hg update -C 0 +echo b > b +hg add b +hg commit -m b -d '1 0' +hg merge 1 +hg commit -m merge -d '2 0' + +cd ../b +hg pull ../a + +# tag hooks can see env vars +cd ../a +echo 'pretag = echo pretag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc +echo 'tag = echo tag hook: t=$HG_TAG n=$HG_NODE l=$HG_LOCAL' >> .hg/hgrc +hg tag -d '3 0' a +hg tag -l la + +# pretag hook can forbid tagging +echo 'pretag.forbid = echo pretag.forbid hook; exit 1' >> .hg/hgrc +hg tag -d '4 0' fa +hg tag -l fla + +# pretxncommit hook can see changeset, can roll back txn, changeset +# no more there after +echo 'pretxncommit.forbid = echo pretxncommit.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc +echo z > z +hg add z +hg -q tip +hg commit -m 'fail' -d '4 0' +hg -q tip + +# precommit hook can prevent commit +echo 'precommit.forbid = echo precommit.forbid hook; exit 1' >> .hg/hgrc +hg commit -m 'fail' -d '4 0' +hg -q tip + +# preupdate hook can prevent update +echo 'preupdate = echo preupdate hook: p1=$HG_PARENT1 p2=$HG_PARENT2' >> .hg/hgrc +hg update 1 + +# update hook +echo 'update = echo update hook: p1=$HG_PARENT1 p2=$HG_PARENT2 err=$HG_ERROR' >> .hg/hgrc +hg update + +# prechangegroup hook can prevent incoming changes +cd ../b +hg -q tip +echo '[hooks]' > .hg/hgrc +echo 'prechangegroup.forbid = echo prechangegroup.forbid hook; exit 1' >> .hg/hgrc +hg pull ../a + +# pretxnchangegroup hook can see incoming changes, can roll back txn, +# incoming changes no longer there after +echo '[hooks]' > .hg/hgrc +echo 'pretxnchangegroup.forbid = echo pretxnchangegroup.forbid hook: tip=`hg -q tip`; exit 1' >> .hg/hgrc +hg pull ../a +hg -q tip + +# outgoing hooks can see env vars +rm .hg/hgrc +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing = echo preoutgoing hook: s=$HG_SOURCE' >> ../a/.hg/hgrc +echo 'outgoing = echo outgoing hook: n=$HG_NODE s=$HG_SOURCE' >> ../a/.hg/hgrc +hg pull ../a +hg rollback + +# preoutgoing hook can prevent outgoing changes +echo 'preoutgoing.forbid = echo preoutgoing.forbid hook; exit 1' >> ../a/.hg/hgrc +hg pull ../a + +cat > hooktests.py <<EOF +from mercurial import util + +uncallable = 0 + +def printargs(args): + args.pop('ui', None) + args.pop('repo', None) + a = list(args.items()) + a.sort() + print 'hook args:' + for k, v in a: + print ' ', k, v + +def passhook(**args): + printargs(args) + +def failhook(**args): + printargs(args) + return True + +class LocalException(Exception): + pass + +def raisehook(**args): + raise LocalException('exception from hook') + +def aborthook(**args): + raise util.Abort('raise abort from hook') + +def brokenhook(**args): + return 1 + {} + +class container: + unreachable = 1 +EOF + +echo '# test python hooks' +PYTHONPATH="`pwd`:$PYTHONPATH" +export PYTHONPATH + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc +hg pull ../a 2>&1 | grep 'raised an exception' + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc +hg pull ../a 2>&1 | grep 'raised an exception' + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc +hg pull ../a + +echo '[hooks]' > ../a/.hg/hgrc +echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc +hg pull ../a + +echo '# make sure --traceback works' +echo '[hooks]' > .hg/hgrc +echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc + +echo a >> a +hg --traceback commit -A -m a 2>&1 | grep '^Traceback' + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hook.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,140 @@ +precommit hook: p1=0000000000000000000000000000000000000000 p2= +pretxncommit hook: n=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p1=0000000000000000000000000000000000000000 p2= +0:29b62aeb769f +commit hook: n=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p1=0000000000000000000000000000000000000000 p2= +commit hook b +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +precommit hook: p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +pretxncommit hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +1:b702efe96888 +commit hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +commit hook b +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +precommit hook: p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +pretxncommit hook: n=1324a5531bac09b329c3845d35ae6a7526874edb p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +2:1324a5531bac +commit hook: n=1324a5531bac09b329c3845d35ae6a7526874edb p1=29b62aeb769fdf78d8d9c5f28b017f76d7ef824b p2= +commit hook b +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +precommit hook: p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2 +pretxncommit hook: n=4c52fb2e402287dd5dc052090682536c8406c321 p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2 +3:4c52fb2e4022 +commit hook: n=4c52fb2e402287dd5dc052090682536c8406c321 p1=1324a5531bac09b329c3845d35ae6a7526874edb p2=b702efe9688826e3a91283852b328b84dbf37bc2 +commit hook b +prechangegroup hook +changegroup hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 +incoming hook: n=b702efe9688826e3a91283852b328b84dbf37bc2 +incoming hook: n=1324a5531bac09b329c3845d35ae6a7526874edb +incoming hook: n=4c52fb2e402287dd5dc052090682536c8406c321 +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 2 changes to 2 files +(run 'hg update' to get a working copy) +pretag hook: t=a n=4c52fb2e402287dd5dc052090682536c8406c321 l=0 +precommit hook: p1=4c52fb2e402287dd5dc052090682536c8406c321 p2= +pretxncommit hook: n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p1=4c52fb2e402287dd5dc052090682536c8406c321 p2= +4:4f92e785b90a +commit hook: n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p1=4c52fb2e402287dd5dc052090682536c8406c321 p2= +commit hook b +tag hook: t=a n=4c52fb2e402287dd5dc052090682536c8406c321 l=0 +pretag hook: t=la n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 l=1 +tag hook: t=la n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 l=1 +pretag hook: t=fa n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 l=0 +pretag.forbid hook +abort: pretag.forbid hook exited with status 1 +pretag hook: t=fla n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 l=1 +pretag.forbid hook +abort: pretag.forbid hook exited with status 1 +4:4f92e785b90a +precommit hook: p1=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p2= +pretxncommit hook: n=7792358308a2026661cea44f9d47c072813004cb p1=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p2= +5:7792358308a2 +pretxncommit.forbid hook: tip=5:7792358308a2 +abort: pretxncommit.forbid hook exited with status 1 +transaction abort! +rollback completed +4:4f92e785b90a +precommit hook: p1=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p2= +precommit.forbid hook +abort: precommit.forbid hook exited with status 1 +4:4f92e785b90a +preupdate hook: p1=b702efe9688826e3a91283852b328b84dbf37bc2 p2= +0 files updated, 0 files merged, 2 files removed, 0 files unresolved +preupdate hook: p1=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p2= +update hook: p1=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 p2= err=0 +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +3:4c52fb2e4022 +prechangegroup.forbid hook +pulling from ../a +searching for changes +abort: prechangegroup.forbid hook exited with status 1 +pretxnchangegroup.forbid hook: tip=4:4f92e785b90a +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +abort: pretxnchangegroup.forbid hook exited with status 1 +transaction abort! +rollback completed +3:4c52fb2e4022 +preoutgoing hook: s=pull +outgoing hook: n=4f92e785b90ae8995dfe156e39dd4fbc3b346a24 s=pull +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +rolling back last transaction +preoutgoing hook: s=pull +preoutgoing.forbid hook +pulling from ../a +searching for changes +abort: preoutgoing.forbid hook exited with status 1 +# test python hooks +error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict' +error: preoutgoing.raise hook raised an exception: exception from hook +pulling from ../a +searching for changes +error: preoutgoing.abort hook failed: raise abort from hook +abort: raise abort from hook +pulling from ../a +searching for changes +hook args: + hooktype preoutgoing + source pull +abort: preoutgoing.fail hook failed +pulling from ../a +searching for changes +abort: preoutgoing.uncallable hook is invalid ("hooktests.uncallable" is not callable) +pulling from ../a +searching for changes +abort: preoutgoing.nohook hook is invalid ("hooktests.nohook" is not defined) +pulling from ../a +searching for changes +abort: preoutgoing.nomodule hook is invalid ("nomodule" not in a module) +pulling from ../a +searching for changes +abort: preoutgoing.badmodule hook is invalid (import of "nomodule" failed) +pulling from ../a +searching for changes +abort: preoutgoing.unreachable hook is invalid (import of "hooktests.container" failed) +pulling from ../a +searching for changes +hook args: + hooktype preoutgoing + source pull +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +# make sure --traceback works +Traceback (most recent call last):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-http-proxy Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,30 @@ +#!/bin/sh + +hg init a +cd a +echo a > a +hg ci -Ama -d '1123456789 0' +hg serve -p 20059 -d --pid-file=hg.pid + +cd .. +("$TESTDIR/tinyproxy.py" 20060 localhost >/dev/null 2>&1 </dev/null & +echo $! > proxy.pid) +sleep 2 + +echo %% url for proxy +http_proxy=http://localhost:20060/ hg --config http_proxy.always=True clone http://localhost:20059/ b + +echo %% host:port for proxy +http_proxy=localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ c + +echo %% proxy url with user name and password +http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://localhost:20059/ d + +echo %% url with user name and password +http_proxy=http://user:passwd@localhost:20060 hg clone --config http_proxy.always=True http://user:passwd@localhost:20059/ e + +echo %% bad host:port for proxy +http_proxy=localhost:20061 hg clone --config http_proxy.always=True http://localhost:20059/ f + +kill $(cat proxy.pid a/hg.pid) +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-http-proxy.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,31 @@ +adding a +%% url for proxy +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% host:port for proxy +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% proxy url with user name and password +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% url with user name and password +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% bad host:port for proxy +abort: error: Connection refused
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hup Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +#!/bin/sh + +hg init +mkfifo p + +hg serve --stdio < p & +P=$! +(echo lock; echo addchangegroup; sleep 5) > p & +Q=$! +sleep 1 +kill -HUP $P +wait +ls .hg + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-hup.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +0 +0 +adding changesets +killed! +transaction abort! +rollback completed +00changelog.i +data +journal.dirstate
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-incoming-outgoing Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,46 @@ +#!/bin/sh + +mkdir test +cd test +hg init +for i in 0 1 2 3 4 5 6 7 8; do + echo $i >> foo + hg commit -A -m $i -d "1000000 0" +done +hg verify +hg serve -p 20059 -d --pid-file=hg.pid +cd .. + +hg init new +# http incoming +http_proxy= hg -R new incoming http://localhost:20059/ +# local incoming +hg -R new incoming test + +# test with --bundle +http_proxy= hg -R new incoming --bundle test.hg http://localhost:20059/ +hg -R new incoming --bundle test2.hg test + +# test the resulting bundles +hg init temp +hg init temp2 +hg -R temp unbundle test.hg +hg -R temp2 unbundle test2.hg +hg -R temp tip +hg -R temp2 tip + +rm -rf temp temp2 new + +# test outgoing +hg clone test test-dev +cd test-dev +for i in 9 10 11 12 13; do + echo $i >> foo + hg commit -A -m $i -d "1000000 0" +done +hg verify +cd .. +hg -R test-dev outgoing test +http_proxy= hg -R test-dev outgoing http://localhost:20059/ + +kill `cat test/hg.pid`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-incoming-outgoing.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,272 @@ +adding foo +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 9 changesets, 9 total revisions +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +changeset: 5:c028bcc7a28a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 5 + +changeset: 6:a0c0095f3389 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 6 + +changeset: 7:d4be65f4e891 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 7 + +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +changeset: 5:c028bcc7a28a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 5 + +changeset: 6:a0c0095f3389 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 6 + +changeset: 7:d4be65f4e891 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 7 + +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +changeset: 5:c028bcc7a28a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 5 + +changeset: 6:a0c0095f3389 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 6 + +changeset: 7:d4be65f4e891 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 7 + +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +changeset: 0:9cb21d99fe27 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 0 + +changeset: 1:d717f5dfad6a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +changeset: 2:c0d6b86da426 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 3:dfacbd43b3fe +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +changeset: 4:1f3a964b6022 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +changeset: 5:c028bcc7a28a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 5 + +changeset: 6:a0c0095f3389 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 6 + +changeset: 7:d4be65f4e891 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 7 + +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +adding changesets +adding manifests +adding file changes +added 9 changesets with 9 changes to 1 files +(run 'hg update' to get a working copy) +adding changesets +adding manifests +adding file changes +added 9 changesets with 9 changes to 1 files +(run 'hg update' to get a working copy) +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +changeset: 8:92b83e334ef8 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 8 + +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 14 changesets, 14 total revisions +searching for changes +changeset: 9:3741c3ad1096 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 9 + +changeset: 10:de4143c8d9a5 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 10 + +changeset: 11:0e1c188b9a7a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 11 + +changeset: 12:251354d0fdd3 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 12 + +changeset: 13:bdaadd969642 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 13 + +searching for changes +changeset: 9:3741c3ad1096 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 9 + +changeset: 10:de4143c8d9a5 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 10 + +changeset: 11:0e1c188b9a7a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 11 + +changeset: 12:251354d0fdd3 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 12 + +changeset: 13:bdaadd969642 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 13 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-locate Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh +# +mkdir t +cd t +hg init +echo 0 > a +echo 0 > b +hg ci -A -m m -d "1000000 0" +touch nottracked +hg locate a +hg locate NONEXISTENT +hg locate +hg rm a +hg ci -m m -d "1000000 0" +hg locate a +hg locate NONEXISTENT +hg locate +hg locate -r 0 a +hg locate -r 0 NONEXISTENT +hg locate -r 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-locate.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +adding a +adding b +a +NONEXISTENT: No such file or directory +a +b +a: No such file or directory +NONEXISTENT: No such file or directory +b +a +NONEXISTENT: No such file in rev 14467d15ef43 +a +b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-lock-badness Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +#!/bin/sh + +hg init a +echo a > a/a +hg --cwd a ci -A -m a +hg clone a b +echo b > b/b +hg --cwd b ci -A -m b +chmod 100 a/.hg +hg --cwd b push ../a +chmod 700 a/.hg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-lock-badness.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +adding a +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +adding b +pushing to ../a +abort: could not lock repository ../a: Permission denied
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge-revert Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,45 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo "added file1" > file1 +echo "added file2" > file2 +hg add file1 file2 +hg commit -m "added file1 and file2" -d "1000000 0" -u user +echo "changed file1" >> file1 +hg commit -m "changed file1" -d "1000000 0" -u user +hg -q log +hg id +hg update -C 0 +hg id +echo "changed file1" >> file1 +hg id +hg revert +hg diff +hg status +hg id +hg update +hg diff +hg status +hg id +hg update -C 0 +echo "changed file1" >> file1 +HGMERGE=merge hg update +hg diff +hg status +hg id +hg revert +hg diff +hg status +hg id +hg revert -r tip +hg diff +hg status +hg id +hg update -C +hg diff +hg status +hg id +cd ..; /bin/rm -rf t +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge-revert.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +1:016807e6fdaf +0:eb43f19ff115 +016807e6fdaf tip +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +eb43f19ff115 +eb43f19ff115+ +reverting file1 +? file1.orig +eb43f19ff115 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +? file1.orig +016807e6fdaf tip +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merging file1 +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +? file1.orig +016807e6fdaf tip +? file1.orig +016807e6fdaf tip +? file1.orig +016807e6fdaf tip +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +? file1.orig +016807e6fdaf tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge-revert2 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,47 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo "added file1" > file1 +echo "another line of text" >> file1 +echo "added file2" > file2 +hg add file1 file2 +hg commit -m "added file1 and file2" -d "1000000 0" -u user +echo "changed file1" >> file1 +hg commit -m "changed file1" -d "1000000 0" -u user +hg -q log +hg id +hg update -C 0 +hg id +echo "changed file1" >> file1 +hg id +hg revert --no-backup +hg diff +hg status +hg id +hg update +hg diff +hg status +hg id +hg update -C 0 +echo "changed file1 different" >> file1 +HGMERGE=merge hg update +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" -e "s/\(<<<<<<<\) .*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" -e "s/\(>>>>>>>\) .*/\1/" +hg status +hg id +hg revert --no-backup +hg diff +hg status +hg id +hg revert -r tip --no-backup +hg diff +hg status +hg id +hg update -C +hg diff +hg status +hg id +cd ..; /bin/rm -rf t +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge-revert2.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +1:f248da0d4c3e +0:9eca13a34789 +f248da0d4c3e tip +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +9eca13a34789 +9eca13a34789+ +reverting file1 +9eca13a34789 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +f248da0d4c3e tip +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merge: warning: conflicts during merge +merging file1 +merging file1 failed! +0 files updated, 0 files merged, 0 files removed, 1 files unresolved +There are unresolved merges with locally modified files. +diff -r f248da0d4c3e file1 +--- a/file1 ++++ b/file1 +@@ -1,3 +1,7 @@ added file1 + added file1 + another line of text ++<<<<<<< ++changed file1 different ++======= + changed file1 ++>>>>>>> +M file1 +f248da0d4c3e+ tip +reverting file1 +f248da0d4c3e tip +f248da0d4c3e tip +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +f248da0d4c3e tip
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge1 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,96 @@ +#!/bin/sh + +cat <<'EOF' > merge +#!/bin/sh +echo merging for `basename $1` +EOF +chmod +x merge + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" + +hg update 0 +echo This is file c1 > c +hg add c +hg commit -m "commit #2" -d "1000000 0" +echo This is file b1 > b +echo %% no merges expected +env HGMERGE=../merge hg merge 1 +cd ..; /bin/rm -rf t + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" + +hg update 0 +echo This is file c1 > c +hg add c +hg commit -m "commit #2" -d "1000000 0" +echo This is file b2 > b +echo %% merge should fail +env HGMERGE=../merge hg merge 1 +echo %% merge of b expected +env HGMERGE=../merge hg merge -f 1 +cd ..; /bin/rm -rf t +echo %% + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +echo This is file b22 > b +hg commit -m "commit #2" -d "1000000 0" +hg update 1 +echo This is file c1 > c +hg add c +hg commit -m "commit #3" -d "1000000 0" + +echo 'Contents of b should be "this is file b1"' +cat b + +echo This is file b22 > b +echo %% merge fails +env HGMERGE=../merge hg merge 2 +echo %% merge expected! +env HGMERGE=../merge hg merge -f 2 +cd ..; /bin/rm -rf t + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +echo This is file b22 > b +hg commit -m "commit #2" -d "1000000 0" +hg update 1 +echo This is file c1 > c +hg add c +hg commit -m "commit #3" -d "1000000 0" +echo This is file b33 > b +echo %% merge of b should fail +env HGMERGE=../merge hg merge 2 +echo %% merge of b expected +env HGMERGE=../merge hg merge -f 2 +cd ..; /bin/rm -rf t
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge1.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,31 @@ +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +%% no merges expected +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +%% merge should fail +abort: 'b' already exists in the working dir and differs from remote +%% merge of b expected +merging for b +merging b +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +%% +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +Contents of b should be "this is file b1" +This is file b1 +%% merge fails +abort: outstanding uncommitted changes +%% merge expected! +merging for b +merging b +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +%% merge of b should fail +abort: outstanding uncommitted changes +%% merge of b expected +merging for b +merging b +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge2 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,48 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +rm b +hg update 0 +echo This is file b2 > b +hg add b +hg commit -m "commit #2" -d "1000000 0" +cd ..; /bin/rm -rf t + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +rm b +hg update 0 +echo This is file b2 > b +hg commit -A -m "commit #2" -d "1000000 0" +cd ..; /bin/rm -rf t + +mkdir t +cd t +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +rm b +hg remove b +hg update 0 +echo This is file b2 > b +hg commit -A -m "commit #2" -d "1000000 0" +cd ..; /bin/rm -rf t
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge2.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,5 @@ +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +adding b +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +adding b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge3 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,10 @@ +#!/bin/sh + +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +touch b +hg add b +rm b +hg commit -A -m"comment #1" -d "1000000 0"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge3.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,2 @@ +removing b +nothing changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge4 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,17 @@ +#!/bin/sh + +hg init +echo This is file a1 > a +hg add a +hg commit -m "commit #0" -d "1000000 0" +echo This is file b1 > b +hg add b +hg commit -m "commit #1" -d "1000000 0" +hg update 0 +echo This is file c1 > c +hg add c +hg commit -m "commit #2" -d "1000000 0" +hg merge 1 +rm b +echo This is file c22 > c +hg commit -m "commit #3" -d "1000000 0"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge4.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,3 @@ +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge5 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo This is file a1 > a +echo This is file b1 > b +hg add a b +hg commit -m "commit #0" -d "1000000 0" +echo This is file b22 > b +hg commit -m"comment #1" -d "1000000 0" +hg update 0 +rm b +hg commit -A -m"comment #2" -d "1000000 0" +# in theory, we shouldn't need the "-y" below, but it prevents +# this test from hanging when "hg update" erroneously prompts the +# user for "keep or delete" +hg update -y 1 + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge5.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +removing b +this update spans a branch affecting the following files: + b +aborting update spanning branches! +(use 'hg merge' to merge across branches or 'hg update -C' to lose changes)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge6 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,46 @@ +#!/bin/sh + +cat <<'EOF' > merge +#!/bin/sh +echo merging for `basename $1` +EOF +chmod +x merge +HGMERGE=./merge; export HGMERGE + +mkdir A1 +cd A1 +hg init +echo This is file foo1 > foo +echo This is file bar1 > bar +hg add foo bar +hg commit -m "commit text" -d "1000000 0" + +cd .. +hg clone A1 B1 + +cd A1 +rm bar +hg remove bar +hg commit -m "commit test" -d "1000000 0" + +cd ../B1 +echo This is file foo22 > foo +hg commit -m "commit test" -d "1000000 0" + +cd .. +hg clone A1 A2 +hg clone B1 B2 + +cd A1 +hg pull ../B1 +hg merge +hg commit -m "commit test" -d "1000000 0" +echo bar should remain deleted. +hg manifest + +cd ../B2 +hg pull ../A2 +hg merge +hg commit -m "commit test" -d "1000000 0" +echo bar should remain deleted. +hg manifest
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge6.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../B1 +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +bar should remain deleted. +f9b0e817f6a48de3564c6b2957687c5e7297c5a0 644 foo +pulling from ../A2 +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 0 changes to 0 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +(branch merge, don't forget to commit) +bar should remain deleted. +f9b0e817f6a48de3564c6b2957687c5e7297c5a0 644 foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge7 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,66 @@ +#!/bin/sh + +# initial +hg init test-a +cd test-a +cat >test.txt <<"EOF" +1 +2 +3 +EOF +hg add test.txt +hg commit -m "Initial" -d "1000000 0" + +# clone +cd .. +hg clone test-a test-b + +# change test-a +cd test-a +cat >test.txt <<"EOF" +one +two +three +EOF +hg commit -m "Numbers as words" -d "1000000 0" + +# change test-b +cd ../test-b +cat >test.txt <<"EOF" +1 +2.5 +3 +EOF +hg commit -m "2 -> 2.5" -d "1000000 0" + +# now pull and merge from test-a +hg pull ../test-a +HGMERGE=merge hg merge +# resolve conflict +cat >test.txt <<"EOF" +one +two-point-five +three +EOF +rm -f *.orig +hg commit -m "Merge 1" -d "1000000 0" + +# change test-a again +cd ../test-a +cat >test.txt <<"EOF" +one +two-point-one +three +EOF +hg commit -m "two -> two-point-one" -d "1000000 0" + +# pull and merge from test-a again +cd ../test-b +hg pull ../test-a +HGMERGE=merge hg merge --debug + +cat test.txt | sed "s% .*%%" + +hg debugindex .hg/data/test.txt.i + +hg log
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-merge7.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,78 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../test-a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +merge: warning: conflicts during merge +merging test.txt +merging test.txt failed! +0 files updated, 0 files merged, 0 files removed, 1 files unresolved +There are unresolved merges, you can redo the full merge using: + hg update -C 1 + hg merge 2 +pulling from ../test-a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +merge: warning: conflicts during merge +resolving manifests + force False allow True moddirstate True linear False + ancestor 055d847dd401 local 2eded9ab0a5c remote 84cf5750dd20 + test.txt versions differ, resolve +merging test.txt +resolving test.txt +file test.txt: my fc3148072371 other d40249267ae3 ancestor 8fe46a3eb557 +merging test.txt failed! +0 files updated, 0 files merged, 0 files removed, 1 files unresolved +There are unresolved merges, you can redo the full merge using: + hg update -C 3 + hg merge 4 +one +<<<<<<< +two-point-five +======= +two-point-one +>>>>>>> +three + rev offset length base linkrev nodeid p1 p2 + 0 0 7 0 0 01365c4cca56 000000000000 000000000000 + 1 7 9 1 1 7b013192566a 01365c4cca56 000000000000 + 2 16 15 2 2 8fe46a3eb557 01365c4cca56 000000000000 + 3 31 27 2 3 fc3148072371 7b013192566a 8fe46a3eb557 + 4 58 25 4 4 d40249267ae3 8fe46a3eb557 000000000000 +changeset: 4:a070d41e8360 +tag: tip +parent: 2:faaea63e63a9 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: two -> two-point-one + +changeset: 3:451c744aabcc +parent: 1:e409be6afcc0 +parent: 2:faaea63e63a9 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Merge 1 + +changeset: 2:faaea63e63a9 +parent: 0:095c92b91f1a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Numbers as words + +changeset: 1:e409be6afcc0 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 -> 2.5 + +changeset: 0:095c92b91f1a +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Initial +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-nested-repo Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,19 @@ +#!/bin/sh + +hg init a +cd a +hg init b +echo x > b/x +echo '# should print nothing' +hg st +echo '# should print ? b/x' +hg st b/x + +hg add b/x + +echo '# should print A b/x' +hg st +echo '# should forget b/x' +hg revert +echo '# should print nothing' +hg st b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-nested-repo.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,8 @@ +# should print nothing +# should print ? b/x +? b/x +# should print A b/x +A b/x +# should forget b/x +forgetting b/x +# should print nothing
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-notfound Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +#!/bin/sh + +hg init + +echo "Is there an error message when trying to diff non-existing files?" +hg diff not found + +echo "Is there an error message when trying to add non-existing files?" +hg add not found
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-notfound.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,6 @@ +Is there an error message when trying to diff non-existing files? +found: No such file or directory +not: No such file or directory +Is there an error message when trying to add non-existing files? +found: No such file or directory +not: No such file or directory
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-parseindex Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,52 @@ +#!/bin/sh +# +# revlog.parseindex must be able to parse the index file even if +# an index entry is split between two 64k blocks. The ideal test +# would be to create an index file with inline data where +# 64k < size < 64k + 64 (64k is the size of the read buffer, 64 is +# the size of an index entry) and with an index entry starting right +# before the 64k block boundary, and try to read it. +# +# We approximate that by reducing the read buffer to 1 byte. +# + +hg init a +cd a +echo abc > foo +hg add foo +hg commit -m 'add foo' -d '1000000 0' + +echo >> foo +hg commit -m 'change foo' -d '1000001 0' +hg log -r 0: + +cat >> test.py << EOF +from mercurial import changelog, util +from mercurial.node import * + +class singlebyteread(object): + def __init__(self, real): + self.real = real + + def read(self, size=-1): + if size == 65536: + size = 1 + return self.real.read(size) + + def __getattr__(self, key): + return getattr(self.real, key) + +def opener(*args): + o = util.opener(*args) + def wrapper(*a): + f = o(*a) + return singlebyteread(f) + return wrapper + +cl = changelog.changelog(opener('.hg')) +print cl.count(), 'revisions:' +for r in xrange(cl.count()): + print short(cl.node(r)) +EOF + +python test.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-parseindex.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,14 @@ +changeset: 0:9c2cf2b35aa7 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: add foo + +changeset: 1:3756a9556b89 +tag: tip +user: test +date: Mon Jan 12 13:46:41 1970 +0000 +summary: change foo + +2 revisions: +9c2cf2b35aa7 +3756a9556b89
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-permissions Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +#!/bin/sh + +hg init +echo foo > a +hg add a +hg commit -m "1" -d "1000000 0" +hg verify +chmod -r .hg/data/a.i +hg verify 2>/dev/null || echo verify failed +chmod +r .hg/data/a.i +hg verify 2>/dev/null || echo verify failed +chmod -w .hg/data/a.i +echo barber > a +hg commit -m "2" -d "1000000 0" 2>/dev/null || echo commit failed +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-permissions.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,16 @@ +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +verify failed +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +commit failed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,21 @@ +#!/bin/sh + +mkdir test +cd test +echo foo>foo +hg init +hg addremove +hg commit -m 1 +hg verify +hg serve -p 20059 -d --pid-file=hg.pid +cd .. + +http_proxy= hg clone http://localhost:20059/ copy +cd copy +hg verify +hg co +cat foo +hg manifest +hg pull + +kill `cat ../test/hg.pid`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-permission Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,19 @@ +#!/bin/sh + +mkdir a +cd a +hg init +echo foo > b +hg add b +hg ci -m "b" -d "1000000 0" + +chmod -w .hg + +cd .. + +hg clone a b + +chmod +w a/.hg # let test clean up + +cd b +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-permission.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,11 @@ +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-pull-corruption Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Corrupt an hg repo with two pulls. +# + +# create one repo with a long history +hg init source1 +cd source1 +touch foo +hg add foo +for i in 1 2 3 4 5 6 7 8 9 10; do + echo $i >> foo + hg ci -m $i +done +cd .. + +# create one repo with a shorter history +hg clone -r 0 source1 source2 +cd source2 +echo a >> foo +hg ci -m a +cd .. + +# create a third repo to pull both other repos into it +hg init corrupted +cd corrupted +# use a hook to make the second pull start while the first one is still running +echo '[hooks]' >> .hg/hgrc +echo 'prechangegroup = sleep 5' >> .hg/hgrc + +# start a pull... +hg pull ../source1 & + +# ... and start another pull before the first one has finished +sleep 1 +hg pull ../source2 2>/dev/null + +# see the result +wait +hg verify +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-pull-corruption.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../source2 +pulling from ../source1 +requesting all changes +adding changesets +adding manifests +adding file changes +added 10 changesets with 10 changes to 1 files +(run 'hg update' to get a working copy) +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 11 changesets, 11 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-pull-corruption2 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Corrupt an hg repo with two pulls. +# + +# create one repo with a long history +hg init source1 +cd source1 +touch foo +hg add foo +for i in 1 2 3 4 5 6 7 8 9 10; do + echo $i >> foo + hg ci -m $i +done +cd .. + +# create a third repo to pull both other repos into it +hg init version2 +hg -R version2 pull source1 & +sleep 1 + +hg clone --pull -U version2 corrupted +hg -R corrupted verify +hg -R version2 verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull-pull-corruption2.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +pulling from source1 +requesting all changes +adding changesets +adding manifests +adding file changes +added 10 changesets with 10 changes to 1 files +(run 'hg update' to get a working copy) +requesting all changes +adding changesets +adding manifests +adding file changes +added 10 changesets with 10 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 10 changesets, 10 total revisions +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 10 changesets, 10 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-pull.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding foo +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +foo +2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo +pulling from http://localhost:20059/ +searching for changes +no changes found
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-hook-lock Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,13 @@ +#!/bin/sh +hg init 1 +echo '[ui]' >> 1/.hg/hgrc +echo 'timeout = 10' >> 1/.hg/hgrc +echo foo > 1/foo +hg --cwd 1 ci -A -m foo +hg clone 1 2 +hg clone 2 3 +echo '[hooks]' >> 2/.hg/hgrc +echo 'changegroup.push = hg push -qf ../1' >> 2/.hg/hgrc +echo bar >> 3/foo +hg --cwd 3 ci -m bar +hg --cwd 3 push ../2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-hook-lock.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +adding foo +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pushing to ../2 +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-r Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,61 @@ +#!/bin/sh + +hg init test +cd test +cat >>afile <<EOF +0 +EOF +hg add afile +hg commit -m "0.0" +cat >>afile <<EOF +1 +EOF +hg commit -m "0.1" +cat >>afile <<EOF +2 +EOF +hg commit -m "0.2" +cat >>afile <<EOF +3 +EOF +hg commit -m "0.3" +hg update -C 0 +cat >>afile <<EOF +1 +EOF +hg commit -m "1.1" +cat >>afile <<EOF +2 +EOF +hg commit -m "1.2" +cat >fred <<EOF +a line +EOF +cat >>afile <<EOF +3 +EOF +hg add fred +hg commit -m "1.3" +hg mv afile adifferentfile +hg commit -m "1.3m" +hg update -C 3 +hg mv afile anotherfile +hg commit -m "0.3m" +hg debugindex .hg/data/afile.i +hg debugindex .hg/data/adifferentfile.i +hg debugindex .hg/data/anotherfile.i +hg debugindex .hg/data/fred.i +hg debugindex .hg/00manifest.i +hg verify +cd .. +for i in 0 1 2 3 4 5 6 7 8; do + mkdir test-"$i" + hg --cwd test-"$i" init + hg -R test push -r "$i" test-"$i" + cd test-"$i" + hg verify + cd .. +done +cd test-8 +hg pull ../test-7 +hg verify
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-r.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,137 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 2 files removed, 0 files unresolved + rev offset length base linkrev nodeid p1 p2 + 0 0 3 0 0 362fef284ce2 000000000000 000000000000 + 1 3 5 1 1 125144f7e028 362fef284ce2 000000000000 + 2 8 7 2 2 4c982badb186 125144f7e028 000000000000 + 3 15 9 3 3 19b1fc555737 4c982badb186 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 75 0 7 905359268f77 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 75 0 8 905359268f77 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 8 0 6 12ab3bcc5ea4 000000000000 000000000000 + rev offset length base linkrev nodeid p1 p2 + 0 0 48 0 0 43eadb1d2d06 000000000000 000000000000 + 1 48 48 1 1 8b89697eba2c 43eadb1d2d06 000000000000 + 2 96 48 2 2 626a32663c2f 8b89697eba2c 000000000000 + 3 144 48 3 3 f54c32f13478 626a32663c2f 000000000000 + 4 192 58 3 6 de68e904d169 626a32663c2f 000000000000 + 5 250 68 3 7 3b45cc2ab868 de68e904d169 000000000000 + 6 318 54 6 8 24d86153a002 f54c32f13478 000000000000 +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions +pushing to test-0 +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +pushing to test-1 +searching for changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +pushing to test-2 +searching for changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 3 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +pushing to test-3 +searching for changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 4 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 4 changesets, 4 total revisions +pushing to test-4 +searching for changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +pushing to test-5 +searching for changes +adding changesets +adding manifests +adding file changes +added 3 changesets with 3 changes to 1 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 3 changesets, 3 total revisions +pushing to test-6 +searching for changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 5 changes to 2 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 4 changesets, 5 total revisions +pushing to test-7 +searching for changes +adding changesets +adding manifests +adding file changes +added 5 changesets with 6 changes to 3 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +3 files, 5 changesets, 6 total revisions +pushing to test-8 +searching for changes +adding changesets +adding manifests +adding file changes +added 5 changesets with 5 changes to 2 files +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +2 files, 5 changesets, 5 total revisions +pulling from ../test-7 +searching for changes +adding changesets +adding manifests +adding file changes +added 4 changesets with 2 changes to 3 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +4 files, 9 changesets, 7 total revisions
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-warn Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,55 @@ +#!/bin/sh + +mkdir a +cd a +hg init +echo foo > t1 +hg add t1 +hg commit -m "1" -d "1000000 0" + +cd .. +hg clone a b + +cd a +echo foo > t2 +hg add t2 +hg commit -m "2" -d "1000000 0" + +cd ../b +echo foo > t3 +hg add t3 +hg commit -m "3" -d "1000000 0" + +hg push ../a +hg pull ../a +hg push ../a +hg merge +hg commit -m "4" -d "1000000 0" +hg push ../a +cd .. + +hg init c +cd c +for i in 0 1 2; do + echo $i >> foo + hg ci -Am $i -d "1000000 0" +done +cd .. + +hg clone c d +cd d +for i in 0 1; do + hg co -C $i + echo d-$i >> foo + hg ci -m d-$i -d "1000000 0" +done + +HGMERGE=true hg merge 3 +hg ci -m c-d -d "1000000 0" + +hg push ../c +hg push -r 2 ../c +hg push -r 3 -r 4 ../c +hg push -r 5 ../c + +exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-push-warn.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,50 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +pushing to ../a +searching for changes +abort: unsynced remote changes! +(did you forget to sync? use push -f to force) +pulling from ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +pushing to ../a +searching for changes +abort: push creates new remote branches! +(did you forget to merge? use push -f to force) +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +pushing to ../a +searching for changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 1 changes to 1 files +adding foo +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merging foo +0 files updated, 1 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +pushing to ../c +searching for changes +abort: push creates new remote branches! +(did you forget to merge? use push -f to force) +pushing to ../c +searching for changes +no changes found +pushing to ../c +searching for changes +adding changesets +adding manifests +adding file changes +added 2 changesets with 2 changes to 1 files (+2 heads) +pushing to ../c +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rawcommit1 Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,33 @@ +#!/bin/sh +hg --debug init +echo this is a1 > a +hg add a +hg commit -m0 -d "1000000 0" +echo this is b1 > b +hg add b +hg commit -m1 -d "1000000 0" +hg manifest 1 +echo this is c1 > c +hg rawcommit -p 1 -d "1000000 0" -m2 c +hg manifest 2 +hg parents +rm b +hg rawcommit -p 2 -d "1000000 0" -m3 b +hg manifest 3 +hg parents +echo this is a22 > a +hg rawcommit -p 3 -d "1000000 0" -m4 a +hg manifest 4 +hg parents +echo this is c22 > c +hg rawcommit -p 1 -d "1000000 0" -m5 c +hg manifest 5 +hg parents +# merge, but no files changed +hg rawcommit -p 4 -p 5 -d "1000000 0" -m6 +hg manifest 6 +hg parents +# no changes what-so-ever +hg rawcommit -p 6 -d "1000000 0" -m7 +hg manifest 7 +hg parents
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rawcommit1.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,59 @@ +05f9e54f4c9b86b09099803d8b49a50edcb4eaab 644 a +54837d97f2932a8194e69745a280a2c11e61ff9c 644 b +(the rawcommit command is deprecated) +05f9e54f4c9b86b09099803d8b49a50edcb4eaab 644 a +54837d97f2932a8194e69745a280a2c11e61ff9c 644 b +76d5e637cbec1bcc04a5a3fa4bcc7d13f6847c00 644 c +changeset: 2:e110db3db549 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +(the rawcommit command is deprecated) +05f9e54f4c9b86b09099803d8b49a50edcb4eaab 644 a +76d5e637cbec1bcc04a5a3fa4bcc7d13f6847c00 644 c +changeset: 3:0f9843914735 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +(the rawcommit command is deprecated) +d6e3c4976c13feb1728cd3ac851abaf7256a5c23 644 a +76d5e637cbec1bcc04a5a3fa4bcc7d13f6847c00 644 c +changeset: 4:909a3d1d3ee1 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +(the rawcommit command is deprecated) +05f9e54f4c9b86b09099803d8b49a50edcb4eaab 644 a +54837d97f2932a8194e69745a280a2c11e61ff9c 644 b +3570202ceac2b52517df64ebd0a062cb0d8fe33a 644 c +changeset: 4:909a3d1d3ee1 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 4 + +(the rawcommit command is deprecated) +d6e3c4976c13feb1728cd3ac851abaf7256a5c23 644 a +76d5e637cbec1bcc04a5a3fa4bcc7d13f6847c00 644 c +changeset: 6:725fdd0728db +tag: tip +parent: 4:909a3d1d3ee1 +parent: 5:f56d4c64ab98 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 6 + +(the rawcommit command is deprecated) +d6e3c4976c13feb1728cd3ac851abaf7256a5c23 644 a +76d5e637cbec1bcc04a5a3fa4bcc7d13f6847c00 644 c +changeset: 7:2c11b55105cb +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 7 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-remove Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,32 @@ +#!/bin/sh + +hg init a +cd a +echo a > foo +hg rm foo +hg add foo +hg commit -m 1 -d "1000000 0" +hg remove +rm foo +hg remove foo +hg revert +rm foo +hg remove --after +hg commit -m 2 -d "1000000 0" +hg export 0 +hg export 1 +hg log -p -r 0 +hg log -p -r 1 + +echo a > a +hg add a +hg rm a +hg rm -f a +echo b > b +hg ci -A -m 3 -d "1000001 0" +echo c >> b +hg rm b +hg rm -f b + +cd .. +hg clone a b
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-remove.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,58 @@ +not removing foo: file is not managed +abort: no files specified +undeleting foo +removing foo +# HG changeset patch +# User test +# Date 1000000 0 +# Node ID 8ba83d44753d6259db5ce6524974dd1174e90f47 +# Parent 0000000000000000000000000000000000000000 +1 + +diff -r 000000000000 -r 8ba83d44753d foo +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/foo Mon Jan 12 13:46:40 1970 +0000 +@@ -0,0 +1,1 @@ ++a +# HG changeset patch +# User test +# Date 1000000 0 +# Node ID a1fce69c50d97881c5c014ab23f580f720c78678 +# Parent 8ba83d44753d6259db5ce6524974dd1174e90f47 +2 + +diff -r 8ba83d44753d -r a1fce69c50d9 foo +--- a/foo Mon Jan 12 13:46:40 1970 +0000 ++++ /dev/null Thu Jan 01 00:00:00 1970 +0000 +@@ -1,1 +0,0 @@ +-a +changeset: 0:8ba83d44753d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +diff -r 000000000000 -r 8ba83d44753d foo +--- /dev/null Thu Jan 01 00:00:00 1970 +0000 ++++ b/foo Mon Jan 12 13:46:40 1970 +0000 +@@ -0,0 +1,1 @@ ++a + + +changeset: 1:a1fce69c50d9 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +diff -r 8ba83d44753d -r a1fce69c50d9 foo +--- a/foo Mon Jan 12 13:46:40 1970 +0000 ++++ /dev/null Thu Jan 01 00:00:00 1970 +0000 +@@ -1,1 +0,0 @@ +-a + + +not removing a: file has been marked for add (use -f to force removal) +adding a +adding b +not removing b: file is modified (use -f to force removal) +2 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rename Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,211 @@ +#!/bin/sh + +hg init +mkdir d1 d1/d11 d2 +echo d1/a > d1/a +echo d1/ba > d1/ba +echo d1/a1 > d1/d11/a1 +echo d1/b > d1/b +echo d2/b > d2/b +hg add d1/a d1/b d1/ba d1/d11/a1 d2/b +hg commit -m "1" -d "1000000 0" + +echo "# rename a single file" +hg rename d1/d11/a1 d2/c +hg status +hg update -C + +echo "# rename --after a single file" +mv d1/d11/a1 d2/c +hg rename --after d1/d11/a1 d2/c +hg status +hg update -C + +echo "# move a single file to an existing directory" +hg rename d1/d11/a1 d2 +hg status +hg update -C + +echo "# move --after a single file to an existing directory" +mv d1/d11/a1 d2 +hg rename --after d1/d11/a1 d2 +hg status +hg update -C + +echo "# rename a file using a relative path" +(cd d1/d11; hg rename ../../d2/b e) +hg status +hg update -C + +echo "# rename --after a file using a relative path" +(cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e) +hg status +hg update -C + +echo "# rename directory d1 as d3" +hg rename d1/ d3 +hg status +hg update -C + +echo "# rename --after directory d1 as d3" +mv d1 d3 +hg rename --after d1 d3 +hg status +hg update -C + +echo "# move a directory using a relative path" +(cd d2; mkdir d3; hg rename ../d1/d11 d3) +hg status +hg update -C + +echo "# move --after a directory using a relative path" +(cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3) +hg status +hg update -C + +echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)" +hg rename d1/d11/ d2 +hg status +hg update -C + +echo "# move directories d1 and d2 to a new directory d3" +mkdir d3 +hg rename d1 d2 d3 +hg status +hg update -C + +echo "# move --after directories d1 and d2 to a new directory d3" +mkdir d3 +mv d1 d2 d3 +hg rename --after d1 d2 d3 +hg status +hg update -C + +echo "# move everything under directory d1 to existing directory d2, do not" +echo "# overwrite existing files (d2/b)" +hg rename d1/* d2 +hg status +diff d1/b d2/b +hg update -C + +echo "# attempt to move potentially more than one file into a non-existent" +echo "# directory" +hg rename 'glob:d1/**' dx + +echo "# move every file under d1 to d2/d21 (glob)" +mkdir d2/d21 +hg rename 'glob:d1/**' d2/d21 +hg status +hg update -C + +echo "# move --after some files under d1 to d2/d21 (glob)" +mkdir d2/d21 +mv d1/a d1/d11/a1 d2/d21 +hg rename --after 'glob:d1/**' d2/d21 +hg status +hg update -C + +echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)" +mkdir d2/d21 +hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21 +hg status +hg update -C + +echo "# attempt to overwrite an existing file" +echo "ca" > d1/ca +hg rename d1/ba d1/ca +hg status +hg update -C + +echo "# forced overwrite of an existing file" +echo "ca" > d1/ca +hg rename --force d1/ba d1/ca +hg status +hg update -C + +echo "# replace a symlink with a file" +ln -s ba d1/ca +hg rename --force d1/ba d1/ca +hg status +hg update -C + +echo "# do not copy more than one source file to the same destination file" +mkdir d3 +hg rename d1/* d2/* d3 +hg status +hg update -C + +echo "# move a whole subtree with \"hg rename .\"" +mkdir d3 +(cd d1; hg rename . ../d3) +hg status +hg update -C + +echo "# move a whole subtree with \"hg rename --after .\"" +mkdir d3 +mv d1/* d3 +(cd d1; hg rename --after . ../d3) +hg status +hg update -C + +echo "# move the parent tree with \"hg rename ..\"" +(cd d1/d11; hg rename .. ../../d3) +hg status +hg update -C + +echo "# skip removed files" +hg remove d1/b +hg rename d1 d3 +hg status +hg update -C + +echo "# transitive rename" +hg rename d1/b d1/bb +hg rename d1/bb d1/bc +hg status +hg update -C + +echo "# transitive rename --after" +hg rename d1/b d1/bb +mv d1/bb d1/bc +hg rename --after d1/bb d1/bc +hg status +hg update -C + +echo "# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b)" +hg rename d1/b d1/bb +echo "some stuff added to d1/bb" >> d1/bb +hg rename d1/bb d1/b +hg status +hg debugstate | grep copy +hg update -C + +echo "# check illegal path components" + +hg rename d1/d11/a1 .hg/foo +hg status +hg rename d1/d11/a1 ../foo +hg status + +mv d1/d11/a1 .hg/foo +hg rename --after d1/d11/a1 .hg/foo +hg status +hg update -C +rm .hg/foo + +hg rename d1/d11/a1 .hg +hg status +hg rename d1/d11/a1 .. +hg status + +mv d1/d11/a1 .hg +hg rename --after d1/d11/a1 .hg +hg status +hg update -C +rm .hg/a1 + +(cd d1/d11; hg rename ../../d2/b ../../.hg/foo) +hg status +(cd d1/d11; hg rename ../../d2/b ../../../foo) +hg status +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rename.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,302 @@ +# rename a single file +A d2/c +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# rename --after a single file +A d2/c +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# move a single file to an existing directory +A d2/a1 +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# move --after a single file to an existing directory +A d2/a1 +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# rename a file using a relative path +A d1/d11/e +R d2/b +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# rename --after a file using a relative path +A d1/d11/e +R d2/b +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# rename directory d1 as d3 +copying d1/a to d3/a +copying d1/b to d3/b +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# rename --after directory d1 as d3 +copying d1/a to d3/a +copying d1/b to d3/b +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# move a directory using a relative path +copying ../d1/d11/a1 to d3/d11/a1 +removing ../d1/d11/a1 +A d2/d3/d11/a1 +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# move --after a directory using a relative path +copying ../d1/d11/a1 to d3/d11/a1 +removing ../d1/d11/a1 +A d2/d3/d11/a1 +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# move directory d1/d11 to an existing directory d2 (removes empty d1) +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/d11/a1 +R d1/d11/a1 +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# move directories d1 and d2 to a new directory d3 +copying d1/a to d3/d1/a +copying d1/b to d3/d1/b +copying d1/ba to d3/d1/ba +copying d1/d11/a1 to d3/d1/d11/a1 +copying d2/b to d3/d2/b +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +removing d2/b +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +A d3/d2/b +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +R d2/b +5 files updated, 0 files merged, 5 files removed, 0 files unresolved +# move --after directories d1 and d2 to a new directory d3 +copying d1/a to d3/d1/a +copying d1/b to d3/d1/b +copying d1/ba to d3/d1/ba +copying d1/d11/a1 to d3/d1/d11/a1 +copying d2/b to d3/d2/b +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +removing d2/b +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +A d3/d2/b +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +R d2/b +5 files updated, 0 files merged, 5 files removed, 0 files unresolved +# move everything under directory d1 to existing directory d2, do not +# overwrite existing files (d2/b) +d2/b: not overwriting - file exists +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/a +A d2/ba +A d2/d11/a1 +R d1/a +R d1/ba +R d1/d11/a1 +1c1 +< d1/b +--- +> d2/b +3 files updated, 0 files merged, 3 files removed, 0 files unresolved +# attempt to move potentially more than one file into a non-existent +# directory +abort: with multiple sources, destination must be an existing directory +# move every file under d1 to d2/d21 (glob) +copying d1/a to d2/d21/a +copying d1/b to d2/d21/b +copying d1/ba to d2/d21/ba +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +A d2/d21/b +A d2/d21/ba +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# move --after some files under d1 to d2/d21 (glob) +copying d1/a to d2/d21/a +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +R d1/a +R d1/d11/a1 +2 files updated, 0 files merged, 2 files removed, 0 files unresolved +# move every file under d1 starting with an 'a' to d2/d21 (regexp) +copying d1/a to d2/d21/a +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +R d1/a +R d1/d11/a1 +2 files updated, 0 files merged, 2 files removed, 0 files unresolved +# attempt to overwrite an existing file +d1/ca: not overwriting - file exists +? d1/ca +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +# forced overwrite of an existing file +A d1/ca +R d1/ba +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# replace a symlink with a file +A d1/ca +R d1/ba +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# do not copy more than one source file to the same destination file +copying d1/d11/a1 to d3/d11/a1 +d3/b: not overwriting - d2/b collides with d1/b +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# move a whole subtree with "hg rename ." +copying a to ../d3/d1/a +copying b to ../d3/d1/b +copying ba to ../d3/d1/ba +copying d11/a1 to ../d3/d1/d11/a1 +removing a +removing b +removing ba +removing d11/a1 +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# move a whole subtree with "hg rename --after ." +copying a to ../d3/a +copying b to ../d3/b +copying ba to ../d3/ba +copying d11/a1 to ../d3/d11/a1 +removing a +removing b +removing ba +removing d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# move the parent tree with "hg rename .." +copying ../a to ../../d3/a +copying ../b to ../../d3/b +copying ../ba to ../../d3/ba +copying a1 to ../../d3/d11/a1 +removing ../a +removing ../b +removing ../ba +removing a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 4 files removed, 0 files unresolved +# skip removed files +copying d1/a to d3/a +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +4 files updated, 0 files merged, 3 files removed, 0 files unresolved +# transitive rename +A d1/bc +R d1/b +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# transitive rename --after +A d1/bc +R d1/b +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +# idempotent renames (d1/b -> d1/bb followed by d1/bb -> d1/b) +M d1/b +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +# check illegal path components +abort: path contains illegal component: .hg/foo + +abort: ../foo not under root +abort: path contains illegal component: .hg/foo + +! d1/d11/a1 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +abort: path contains illegal component: .hg/a1 + +abort: ../a1 not under root +abort: path contains illegal component: .hg/a1 + +! d1/d11/a1 +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +abort: path contains illegal component: .hg/foo + +abort: ../../../foo not under root
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-revert Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,83 @@ +#!/bin/sh + +hg init +echo 123 > a +echo 123 > c +echo 123 > e +hg add a c e +hg commit -m "first" -d "1000000 0" a c e +echo 123 > b +echo %% should show b unknown +hg status +echo 12 > c +echo %% should show b unknown and c modified +hg status +hg add b +echo %% should show b added and c modified +hg status +hg rm a +echo %% should show a removed, b added and c modified +hg status +hg revert a +echo %% should show b added, copy saved, and c modified +hg status +hg revert b +echo %% should show b unknown, and c modified +hg status +hg revert --no-backup c +echo %% should show unknown: b +hg status +echo %% should show a b c e +ls +echo %% should verbosely save backup to e.orig +echo z > e +hg revert -v +echo %% should say no changes needed +hg revert a +echo %% should say file not managed +echo q > q +hg revert q +rm q +echo %% should say file not found +hg revert notfound +hg rm a +hg commit -m "second" -d "1000000 0" +echo z > z +hg add z +hg st +echo %% should add a, forget z +hg revert -r0 +echo %% should forget a +hg revert -rtip +rm -f a *.orig +echo %% should silently add a +hg revert -r0 a +hg st a + +hg update -C +chmod +x c +hg revert +echo %% should print non-executable +test -x c || echo non-executable + +chmod +x c +hg commit -d '1000001 0' -m exe + +chmod -x c +hg revert +echo %% should print executable +test -x c && echo executable + +echo %% issue 241 +hg init a +cd a +echo a >> a +hg commit -A -d '1 0' -m a +echo a >> a +hg commit -d '2 0' -m a +hg update 0 +mkdir b +echo b > b/b +hg revert -rtip + +true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-revert-unknown Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh + +hg init +touch unknown + +touch a +hg add a +hg ci -m "1" -d "1000000 0" + +touch b +hg add b +hg ci -m "2" -d "1000000 0" + +echo %% Should show unknown +hg status +hg revert -r 0 +echo %% Should show unknown and b removed +hg status +echo %% Should show a and unknown +ls
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-revert-unknown.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,9 @@ +%% Should show unknown +? unknown +removing b +%% Should show unknown and b removed +R b +? unknown +%% Should show a and unknown +a +unknown
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-revert.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,56 @@ +%% should show b unknown +? b +%% should show b unknown and c modified +M c +? b +%% should show b added and c modified +M c +A b +%% should show a removed, b added and c modified +M c +A b +R a +%% should show b added, copy saved, and c modified +M c +A b +%% should show b unknown, and c modified +M c +? b +%% should show unknown: b +? b +%% should show a b c e +a +b +c +e +%% should verbosely save backup to e.orig +saving current version of e as e.orig +reverting e +resolving manifests +getting e +%% should say no changes needed +no changes needed to a +%% should say file not managed +file not managed: q +%% should say file not found +notfound: No such file in rev 095eacd0c0d7 +A z +? b +? e.orig +%% should add a, forget z +adding a +forgetting z +%% should forget a +forgetting a +%% should silently add a +A a +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +reverting c +%% should print non-executable +non-executable +reverting c +%% should print executable +executable +%% issue 241 +adding a +1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ro-message Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,17 @@ +#!/bin/sh +HG=hg +"$HG" init +mkdir b +echo 'Bouncy' >b/bouncy +echo 'tricycle' >b/vehicle +"$HG" add b/bouncy +"$HG" add b/vehicle +"$HG" commit -m 'Adding bouncy' +echo 'bouncy' >>b/bouncy +"$HG" commit -m 'Making it bouncier' +"$HG" update -C 0 +echo 'stationary' >>b/vehicle +"$HG" commit -m 'Clarifying the vehicle.' +"$HG" update -C 1 +chmod a-w b/vehicle +"$HG" merge 2 2>&1 | sed 's|^\(.*[ ]\).*/\([^/]*/[^/]*/[^/]*\)$|\1\2|g'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ro-message.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,3 @@ +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +2 files updated, 0 files merged, 0 files removed, 0 files unresolved +abort: Permission denied - test-ro-message/b/vehicle
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rollback Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,15 @@ +#!/bin/sh + +mkdir t +cd t +hg init +echo a > a +hg add a +hg commit -m "test" -d "1000000 0" +hg verify +hg parents +hg status +hg rollback +hg verify +hg parents +hg status
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rollback.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,18 @@ +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +changeset: 0:0acdaf898367 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + +rolling back last transaction +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +0 files, 0 changesets, 0 total revisions +A a
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-simple-update Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +#!/bin/sh + +set -e + +mkdir test +cd test +echo foo>foo +hg init +hg addremove +hg commit -m "1" +hg verify + +hg clone . ../branch +cd ../branch +hg co +echo bar>>foo +hg commit -m "2" + +cd ../test +hg pull ../branch +hg verify +hg co +cat foo +hg manifest
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-simple-update.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding foo +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +pulling from ../branch +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +(run 'hg update' to get a working copy) +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +foo +bar +6f4310b00b9a147241b071a60c28a650827fb03d 644 foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ssh Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,70 @@ +#!/bin/sh + +# This test tries to exercise the ssh functionality with a dummy script + +cat <<'EOF' > dummyssh +#!/bin/sh +# this attempts to deal with relative pathnames +cd `dirname $0` + +# check for proper args +if [ $1 != "user@dummy" ] ; then + exit -1 +fi + +# check that we're in the right directory +if [ ! -x dummyssh ] ; then + exit -1 +fi + +echo Got arguments 1:$1 2:$2 3:$3 4:$4 5:$5 >> dummylog +$2 +EOF +chmod +x dummyssh + +echo "# creating 'remote'" +hg init remote +cd remote +echo this > foo +hg ci -A -m "init" -d "1000000 0" foo + +cd .. + +echo "# clone remote" +hg clone -e ./dummyssh ssh://user@dummy/remote local + +echo "# verify" +cd local +hg verify + +echo "# empty default pull" +hg paths +hg pull -e ../dummyssh + +echo "# local change" +echo bleah > foo +hg ci -m "add" -d "1000000 0" + +echo "# updating rc" +echo "default-push = ssh://user@dummy/remote" >> .hg/hgrc +echo "[ui]" >> .hg/hgrc +echo "ssh = ../dummyssh" >> .hg/hgrc + +echo "# find outgoing" +hg out ssh://user@dummy/remote + +echo "# find incoming on the remote side" +hg incoming -R ../remote -e ../dummyssh ssh://user@dummy/local + +echo "# push" +hg push + +cd ../remote + +echo "# check remote tip" +hg tip +hg verify +hg cat foo + +cd .. +cat dummylog
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-ssh.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,62 @@ +# creating 'remote' +# clone remote +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +# verify +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +# empty default pull +default = ssh://user@dummy/remote +pulling from ssh://user@dummy/remote +searching for changes +no changes found +# local change +# updating rc +# find outgoing +searching for changes +changeset: 1:c54836a570be +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: add + +# find incoming on the remote side +searching for changes +changeset: 1:c54836a570be +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: add + +# push +pushing to ssh://user@dummy/remote +searching for changes +remote: adding changesets +remote: adding manifests +remote: adding file changes +remote: added 1 changesets with 1 changes to 1 files +# check remote tip +changeset: 1:c54836a570be +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: add + +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 2 changesets, 2 total revisions +bleah +Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5: +Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5: +Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5: +Got arguments 1:user@dummy 2:hg -R local serve --stdio 3: 4: 5: +Got arguments 1:user@dummy 2:hg -R remote serve --stdio 3: 4: 5:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-static-http Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +#!/bin/sh + +http_proxy= hg clone old-http://localhost:20059/ copy +echo $? +ls copy 2>/dev/null || echo copy: No such file or directory + +# This server doesn't do range requests so it's basically only good for +# one pull +cat > dumb.py <<EOF +import BaseHTTPServer, SimpleHTTPServer, signal + +def run(server_class=BaseHTTPServer.HTTPServer, + handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler): + server_address = ('localhost', 20059) + httpd = server_class(server_address, handler_class) + httpd.serve_forever() + +signal.signal(signal.SIGTERM, lambda x: sys.exit(0)) +run() +EOF + +python dumb.py 2>/dev/null & + +mkdir remote +cd remote +hg init +echo foo > bar +hg add bar +hg commit -m"test" -d "1000000 0" +hg tip + +cd .. + +http_proxy= hg clone old-http://localhost:20059/remote local + +cd local +hg verify +cat bar +http_proxy= hg pull + +kill $!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-static-http.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,24 @@ +abort: Connection refused +255 +copy: No such file or directory +changeset: 0:53e17d176ae6 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +checking changesets +checking manifests +crosschecking files in changesets and manifests +checking files +1 files, 1 changesets, 1 total revisions +foo +pulling from old-http://localhost:20059/remote +searching for changes +no changes found
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-status Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#!/bin/sh + +hg init repo1 +cd repo1 +mkdir a b a/1 b/1 b/2 +touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2 +echo "hg status in repo root:" +hg status +echo "hg status . in repo root:" +hg status . +for dir in a b a/1 b/1 b/2; do + echo "hg status in $dir:" + hg status --cwd "$dir" + echo "hg status . in $dir:" + hg status --cwd "$dir" . + echo "hg status .. in $dir:" + hg status --cwd "$dir" .. +done +cd .. + +hg init repo2 +cd repo2 +touch modified removed deleted ignored +echo "ignored" > .hgignore +hg ci -A -m 'initial checkin' -d "1000000 0" +sleep 1 # make sure mtime is changed +touch modified added unknown ignored +hg add added +hg remove removed +rm deleted +echo "hg status:" +hg status +echo "hg status modified added removed deleted unknown never-existed ignored:" +hg status modified added removed deleted unknown never-existed ignored
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-status.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,103 @@ +hg status in repo root: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in repo root: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status in a: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in a: +? 1/in_a_1 +? in_a +hg status .. in a: +? 1/in_a_1 +? in_a +? ../b/1/in_b_1 +? ../b/2/in_b_2 +? ../b/in_b +? ../in_root +hg status in b: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in b: +? 1/in_b_1 +? 2/in_b_2 +? in_b +hg status .. in b: +? ../a/1/in_a_1 +? ../a/in_a +? 1/in_b_1 +? 2/in_b_2 +? in_b +? ../in_root +hg status in a/1: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in a/1: +? in_a_1 +hg status .. in a/1: +? in_a_1 +? ../in_a +hg status in b/1: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in b/1: +? in_b_1 +hg status .. in b/1: +? in_b_1 +? ../2/in_b_2 +? ../in_b +hg status in b/2: +? a/1/in_a_1 +? a/in_a +? b/1/in_b_1 +? b/2/in_b_2 +? b/in_b +? in_root +hg status . in b/2: +? in_b_2 +hg status .. in b/2: +? ../1/in_b_1 +? in_b_2 +? ../in_b +adding .hgignore +adding deleted +adding modified +adding removed +hg status: +A added +R removed +! deleted +? unknown +hg status modified added removed deleted unknown never-existed ignored: +never-existed: No such file or directory +A added +R removed +! deleted +? ignored +? unknown
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-symlinks Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,57 @@ +#!/bin/sh +#Test bug regarding symlinks that showed up in hg 0.7 +#Author: Matthew Elder <sseses@gmail.com> + +#make and initialize repo +hg init test; cd test; + +#make a file and a symlink +touch foo; ln -s foo bar; + +#import with addremove -- symlink walking should _not_ screwup. +hg addremove + +#commit -- the symlink should _not_ appear added to dir state +hg commit -m 'initial' + +#add a new file so hg will let me commit again +touch bomb + +#again, symlink should _not_ show up on dir state +hg addremove + +#Assert screamed here before, should go by without consequence +hg commit -m 'is there a bug?' + +cd .. ; rm -rf test +hg init test; cd test; + +mkdir dir +touch a.c dir/a.o dir/b.o +# test what happens if we want to trick hg +hg commit -A -m 0 +echo "relglob:*.o" > .hgignore +rm a.c +rm dir/a.o +rm dir/b.o +mkdir dir/a.o +ln -sf nonexist dir/b.o +mkfifo a.c +# it should show a.c, dir/a.o and dir/b.o deleted +hg status +hg status a.c + +echo '# test absolute path through symlink outside repo' +cd .. +p=`pwd` +hg init x +ln -s x y +cd x +touch f +hg add f +hg status $p/y/f + +echo '# try symlink outside repo to file inside' +ln -s x/f ../z +# this should fail +hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || :
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-symlinks.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,17 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding foo +(the addremove command is deprecated; use add and remove --after instead) +adding bomb +adding a.c +adding dir/a.o +adding dir/b.o +! a.c +! dir/a.o +! dir/b.o +? .hgignore +a.c: unsupported file type (type is fifo) +! a.c +# test absolute path through symlink outside repo +A f +# try symlink outside repo to file inside +abort: ../z not under root
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-tag Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,25 @@ +#!/bin/sh + +hg init +echo a > a +hg add a +hg commit -m "test" -d "1000000 0" +hg history +hg tag -d "1000000 0" "bleah" +hg history + +echo foo >> .hgtags +hg tag -d "1000000 0" "bleah2" || echo "failed" +hg tag -d "1000000 0" -r 0 "bleah2" 1 || echo "failed" + +hg revert .hgtags +hg tag -d "1000000 0" -r 0 "bleah0" +hg tag -l -d "1000000 0" "bleah1" 1 + +cat .hgtags +cat .hg/localtags + +hg tag -l 'xx +newline' +hg tag -l 'xx:xx' +true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-tag.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,29 @@ +changeset: 0:0acdaf898367 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + +changeset: 1:c5c60883086f +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added tag bleah for changeset 0acdaf8983679e0aac16e811534eb49d7ee1f2b4 + +changeset: 0:0acdaf898367 +tag: bleah +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: test + +abort: working copy of .hgtags is changed (please commit .hgtags manually) +failed +use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead +abort: use only one form to specify the revision +failed +use of 'hg tag NAME [REV]' is deprecated, please use 'hg tag [-r REV] NAME' instead +0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah +0acdaf8983679e0aac16e811534eb49d7ee1f2b4 bleah0 +c5c60883086f5526bd3e36814b94a73a4e75e172 bleah1 +abort: '\n' cannot be used in a tag name +abort: ':' cannot be used in a tag name
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-tags Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,62 @@ +#!/bin/sh + +mkdir t +cd t +hg init +hg id +echo a > a +hg add a +hg commit -m "test" -d "1000000 0" +hg co +hg identify +T=`hg tip -v | head -n 1 | cut -d : -f 3` +echo "$T first" > .hgtags +cat .hgtags +hg add .hgtags +hg commit -m "add tags" -d "1000000 0" +hg tags +hg identify +echo bb > a +hg status +hg identify +hg co first +hg id +hg -v id +hg status +echo 1 > b +hg add b +hg commit -m "branch" -d "1000000 0" +hg id +hg merge 1 +hg id +hg status + +hg commit -m "merge" -d "1000000 0" + +# create fake head, make sure tag not visible afterwards +cp .hgtags tags +hg tag -d "1000000 0" last +hg rm .hgtags +hg commit -m "remove" -d "1000000 0" + +mv tags .hgtags +hg add .hgtags +hg commit -m "readd" -d "1000000 0" + +hg tags + +# invalid tags +echo "spam" >> .hgtags +echo >> .hgtags +echo "foo bar" >> .hgtags +echo "$T invalid" | sed "s/..../a5a5/" >> .hg/localtags +hg commit -m "tags" -d "1000000 0" + +# report tag parse error on other head +hg up 3 +echo 'x y' >> .hgtags +hg commit -m "head" -d "1000000 0" + +hg tags +hg tip +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-tags.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,41 @@ +unknown +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +0acdaf898367 tip +0acdaf8983679e0aac16e811534eb49d7ee1f2b4 first +tip 1:8a3ca90d111dc784e6575d373105be12570e8776 +first 0:0acdaf8983679e0aac16e811534eb49d7ee1f2b4 +8a3ca90d111d tip +M a +8a3ca90d111d+ tip +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +0acdaf898367+ first +0acdaf8983679e0aac16e811534eb49d7ee1f2b4+ first +M a +8216907a933d tip +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +8216907a933d+8a3ca90d111d+ tip +M .hgtags +tip 6:c6af9d771a81bb9c7f267ec03491224a9f8ba1cd +first 0:0acdaf8983679e0aac16e811534eb49d7ee1f2b4 +.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry +.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed +localtags, line 1: tag 'invalid' refers to unknown node +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry +.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed +.hgtags (rev 8:4ca6f1b1a68c), line 2: node 'x' is not well formed +localtags, line 1: tag 'invalid' refers to unknown node +tip 8:4ca6f1b1a68c77be687a03aaeb1614671ba59b20 +first 0:0acdaf8983679e0aac16e811534eb49d7ee1f2b4 +changeset: 8:4ca6f1b1a68c +.hgtags (rev 7:39bba1bbbc4c), line 2: cannot parse entry +.hgtags (rev 7:39bba1bbbc4c), line 4: node 'foo' is not well formed +.hgtags (rev 8:4ca6f1b1a68c), line 2: node 'x' is not well formed +localtags, line 1: tag 'invalid' refers to unknown node +tag: tip +parent: 3:b2ef3841386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: head +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-unrelated-pull Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,20 @@ +#!/bin/sh + +mkdir a +cd a +hg init +echo 123 > a +hg add a +hg commit -m "a" -u a -d "1000000 0" + +cd .. +mkdir b +cd b +hg init +echo 321 > b +hg add b +hg commit -m "b" -u b -d "1000000 0" + +hg pull ../a +hg pull -f ../a +hg heads
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-unrelated-pull.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,22 @@ +pulling from ../a +searching for changes +abort: repository is unrelated +pulling from ../a +searching for changes +warning: repository is unrelated +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files (+1 heads) +(run 'hg heads' to see heads, 'hg merge' to merge) +changeset: 1:bdcee5d51fa6 +tag: tip +user: a +date: Mon Jan 12 13:46:40 1970 +0000 +summary: a + +changeset: 0:f155ba1aa5ba +user: b +date: Mon Jan 12 13:46:40 1970 +0000 +summary: b +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-up-local-change Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,69 @@ +#!/bin/sh + +set -e +mkdir r1 +cd r1 +hg init +echo a > a +hg addremove +hg commit -m "1" -d "1000000 0" + +hg clone . ../r2 +cd ../r2 +hg up +echo abc > a +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +cd ../r1 +echo b > b +echo a2 > a +hg addremove +hg commit -m "2" -d "1000000 0" + +cd ../r2 +hg -q pull ../r1 +hg status +hg parents +hg --debug up +hg parents +hg --debug up 0 +hg parents +hg --debug merge || echo failed +hg parents +hg --debug up +hg parents +hg -v history +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +# create a second head +cd ../r1 +hg up 0 +echo b2 > b +echo a3 > a +hg addremove +hg commit -m "3" -d "1000000 0" + +cd ../r2 +hg -q pull ../r1 +hg status +hg parents +hg --debug up || echo failed +hg --debug merge || echo failed +hg --debug merge -f +hg parents +hg diff | sed -e "s/\(+++ [a-zA-Z0-9_/.-]*\).*/\1/" \ + -e "s/\(--- [a-zA-Z0-9_/.-]*\).*/\1/" + +# test a local add +cd .. +hg init a +hg init b +echo a > a/a +echo a > b/a +hg --cwd a commit -A -m a +cd b +hg add a +hg pull -u ../a +hg st
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-up-local-change.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,152 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding a +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +diff -r 33aaa84a386b a +--- a/a ++++ b/a +@@ -1,1 +1,1 @@ a +-a ++abc +(the addremove command is deprecated; use add and remove --after instead) +adding b +M a +changeset: 0:33aaa84a386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +resolving manifests + force None allow None moddirstate True linear True + ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e + a versions differ, resolve +remote created b +getting b +merging a +resolving a +file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +1 files updated, 1 files merged, 0 files removed, 0 files unresolved +changeset: 1:802f095af299 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +resolving manifests + force None allow None moddirstate True linear True + ancestor a0c8bcbbb45c local 1165e8bd193e remote a0c8bcbbb45c +remote deleted b +removing b +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +changeset: 0:33aaa84a386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +abort: there is nothing to merge, just use 'hg update' +failed +changeset: 0:33aaa84a386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 1 + +resolving manifests + force None allow None moddirstate True linear True + ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e + a versions differ, resolve +remote created b +getting b +merging a +resolving a +file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +1 files updated, 1 files merged, 0 files removed, 0 files unresolved +changeset: 1:802f095af299 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 1:802f095af299cde27a85b2f056aef3829870956c +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +files: a b +description: +2 + + +changeset: 0:33aaa84a386bd609094aeb21a97c09436c482ef1 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +files: a +description: +1 + + +diff -r 802f095af299 a +--- a/a ++++ b/a +@@ -1,1 +1,1 @@ a2 +-a2 ++abc +1 files updated, 0 files merged, 1 files removed, 0 files unresolved +(the addremove command is deprecated; use add and remove --after instead) +adding b +M a +changeset: 1:802f095af299 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +resolving manifests + force None allow None moddirstate True linear False + ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392 + a versions differ, resolve + b versions differ, resolve +this update spans a branch affecting the following files: + a (resolve) + b (resolve) +aborting update spanning branches! +(use 'hg merge' to merge across branches or 'hg update -C' to lose changes) +failed +abort: outstanding uncommitted changes +failed +resolving manifests + force False allow True moddirstate True linear False + ancestor a0c8bcbbb45c local 1165e8bd193e remote 4096f2872392 + a versions differ, resolve + b versions differ, resolve +merging a +resolving a +file a: my d730145abbf9 other 13e0d5f949fa ancestor b789fdd96dc2 +merging b +resolving b +file b: my 1e88685f5dde other 61de8c7723ca ancestor 000000000000 +0 files updated, 2 files merged, 0 files removed, 0 files unresolved +(branch merge, don't forget to commit) +changeset: 1:802f095af299 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 2 + +changeset: 2:030602aee63d +tag: tip +parent: 0:33aaa84a386b +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: 3 + +diff -r 802f095af299 a +--- a/a ++++ b/a +@@ -1,1 +1,1 @@ a2 +-a2 ++abc +adding a +pulling from ../a +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-update-reverse Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,34 @@ +#!/bin/sh + +hg init +touch a +hg add a +hg commit -m "Added a" -d "1000000 0" + +touch main +hg add main +hg commit -m "Added main" -d "1000000 0" +hg checkout 0 + +echo Main should be gone +ls + +touch side1 +hg add side1 +hg commit -m "Added side1" -d "1000000 0" +touch side2 +hg add side2 +hg commit -m "Added side2" -d "1000000 0" + +hg log + +echo Should have two heads, side2 and main +hg heads + +echo Should show "a side1 side2" +ls + +hg update --debug -C 1 +echo Should only show "a main" +ls +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-update-reverse.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,54 @@ +0 files updated, 0 files merged, 1 files removed, 0 files unresolved +Main should be gone +a +changeset: 3:ded32b0db104 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added side2 + +changeset: 2:92a816cea698 +parent: 0:537353581d3d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added side1 + +changeset: 1:221226fb2bd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added main + +changeset: 0:537353581d3d +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added a + +Should have two heads, side2 and main +changeset: 3:ded32b0db104 +tag: tip +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added side2 + +changeset: 1:221226fb2bd8 +user: test +date: Mon Jan 12 13:46:40 1970 +0000 +summary: Added main + +Should show a side1 side2 +a +side1 +side2 +resolving manifests + force 1 allow None moddirstate True linear False + ancestor 8515d4bfda76 local 1c0f48f8ece6 remote 0594b9004bae +remote deleted side2, clobbering +remote deleted side1, clobbering +remote created main +getting main +removing side1 +removing side2 +1 files updated, 0 files merged, 2 files removed, 0 files unresolved +Should only show a main +a +main
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-walk Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,61 @@ +#!/bin/sh + +mkdir t +cd t +hg init +mkdir -p beans +for b in kidney navy turtle borlotti black pinto; do + echo $b > beans/$b +done +mkdir -p mammals/Procyonidae +for m in cacomistle coatimundi raccoon; do + echo $m > mammals/Procyonidae/$m +done +echo skunk > mammals/skunk +echo fennel > fennel +echo fenugreek > fenugreek +echo fiddlehead > fiddlehead +echo glob:glob > glob:glob +hg addremove +hg commit -m "commit #0" -d "1000000 0" +hg debugwalk +cd mammals +hg debugwalk . +hg debugwalk Procyonidae +cd Procyonidae +hg debugwalk . +hg debugwalk .. +cd .. +hg debugwalk ../beans +hg debugwalk . +hg debugwalk .hg +hg debugwalk ../.hg +cd .. +hg debugwalk -Ibeans +hg debugwalk 'glob:mammals/../beans/b*' +hg debugwalk '-X*/Procyonidae' mammals +hg debugwalk path:mammals +hg debugwalk .. +hg debugwalk beans/../.. +hg debugwalk .hg +hg debugwalk beans/../.hg +hg debugwalk beans/../.hg/data +hg debugwalk beans/.hg +# Don't know how to test absolute paths without always getting a false +# error. +#hg debugwalk `pwd`/beans +#hg debugwalk `pwd`/.. +hg debugwalk glob:\* +hg debugwalk 're:.*[kb]$' +hg debugwalk path:beans/black +hg debugwalk beans 'glob:beans/*' +hg debugwalk 'glob:j*' +hg debugwalk NOEXIST +mkfifo fifo +hg debugwalk fifo +rm fenugreek +hg debugwalk fenugreek +hg rm fenugreek +hg debugwalk fenugreek +touch new +hg debugwalk new
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-walk.out Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,98 @@ +(the addremove command is deprecated; use add and remove --after instead) +adding beans/black +adding beans/borlotti +adding beans/kidney +adding beans/navy +adding beans/pinto +adding beans/turtle +adding fennel +adding fenugreek +adding fiddlehead +adding glob:glob +adding mammals/Procyonidae/cacomistle +adding mammals/Procyonidae/coatimundi +adding mammals/Procyonidae/raccoon +adding mammals/skunk +f beans/black beans/black +f beans/borlotti beans/borlotti +f beans/kidney beans/kidney +f beans/navy beans/navy +f beans/pinto beans/pinto +f beans/turtle beans/turtle +f fennel fennel +f fenugreek fenugreek +f fiddlehead fiddlehead +f glob:glob glob:glob +f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle +f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi +f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon +f mammals/skunk mammals/skunk +f mammals/Procyonidae/cacomistle Procyonidae/cacomistle +f mammals/Procyonidae/coatimundi Procyonidae/coatimundi +f mammals/Procyonidae/raccoon Procyonidae/raccoon +f mammals/skunk skunk +f mammals/Procyonidae/cacomistle Procyonidae/cacomistle +f mammals/Procyonidae/coatimundi Procyonidae/coatimundi +f mammals/Procyonidae/raccoon Procyonidae/raccoon +f mammals/Procyonidae/cacomistle cacomistle +f mammals/Procyonidae/coatimundi coatimundi +f mammals/Procyonidae/raccoon raccoon +f mammals/Procyonidae/cacomistle cacomistle +f mammals/Procyonidae/coatimundi coatimundi +f mammals/Procyonidae/raccoon raccoon +f mammals/skunk ../skunk +f beans/black ../beans/black +f beans/borlotti ../beans/borlotti +f beans/kidney ../beans/kidney +f beans/navy ../beans/navy +f beans/pinto ../beans/pinto +f beans/turtle ../beans/turtle +f mammals/Procyonidae/cacomistle Procyonidae/cacomistle +f mammals/Procyonidae/coatimundi Procyonidae/coatimundi +f mammals/Procyonidae/raccoon Procyonidae/raccoon +f mammals/skunk skunk +.hg: No such file or directory +abort: path contains illegal component: .hg + +f beans/black beans/black +f beans/borlotti beans/borlotti +f beans/kidney beans/kidney +f beans/navy beans/navy +f beans/pinto beans/pinto +f beans/turtle beans/turtle +f beans/black beans/black +f beans/borlotti beans/borlotti +f mammals/skunk mammals/skunk +f mammals/Procyonidae/cacomistle mammals/Procyonidae/cacomistle +f mammals/Procyonidae/coatimundi mammals/Procyonidae/coatimundi +f mammals/Procyonidae/raccoon mammals/Procyonidae/raccoon +f mammals/skunk mammals/skunk +abort: .. not under root +abort: beans/../.. not under root +abort: path contains illegal component: .hg + +abort: path contains illegal component: .hg + +abort: path contains illegal component: .hg/data + +beans/.hg: No such file or directory +f fennel fennel +f fenugreek fenugreek +f fiddlehead fiddlehead +f glob:glob glob:glob +f beans/black beans/black +f fenugreek fenugreek +f glob:glob glob:glob +f mammals/skunk mammals/skunk +f beans/black beans/black +f beans/black beans/black +f beans/borlotti beans/borlotti +f beans/kidney beans/kidney +f beans/navy beans/navy +f beans/pinto beans/pinto +f beans/turtle beans/turtle +NOEXIST: No such file or directory +fifo: unsupported file type (type is fifo) +m fenugreek fenugreek exact +m fenugreek fenugreek exact +f new new exact
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/tinyproxy.py Thu Jun 01 23:58:43 2006 +0200 @@ -0,0 +1,132 @@ +#!/usr/bin/env python + +__doc__ = """Tiny HTTP Proxy. + +This module implements GET, HEAD, POST, PUT and DELETE methods +on BaseHTTPServer, and behaves as an HTTP proxy. The CONNECT +method is also implemented experimentally, but has not been +tested yet. + +Any help will be greatly appreciated. SUZUKI Hisao +""" + +__version__ = "0.2.1" + +import BaseHTTPServer, select, socket, SocketServer, urlparse + +class ProxyHandler (BaseHTTPServer.BaseHTTPRequestHandler): + __base = BaseHTTPServer.BaseHTTPRequestHandler + __base_handle = __base.handle + + server_version = "TinyHTTPProxy/" + __version__ + rbufsize = 0 # self.rfile Be unbuffered + + def handle(self): + (ip, port) = self.client_address + if hasattr(self, 'allowed_clients') and ip not in self.allowed_clients: + self.raw_requestline = self.rfile.readline() + if self.parse_request(): self.send_error(403) + else: + self.__base_handle() + + def _connect_to(self, netloc, soc): + i = netloc.find(':') + if i >= 0: + host_port = netloc[:i], int(netloc[i+1:]) + else: + host_port = netloc, 80 + print "\t" "connect to %s:%d" % host_port + try: soc.connect(host_port) + except socket.error, arg: + try: msg = arg[1] + except: msg = arg + self.send_error(404, msg) + return 0 + return 1 + + def do_CONNECT(self): + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + if self._connect_to(self.path, soc): + self.log_request(200) + self.wfile.write(self.protocol_version + + " 200 Connection established\r\n") + self.wfile.write("Proxy-agent: %s\r\n" % self.version_string()) + self.wfile.write("\r\n") + self._read_write(soc, 300) + finally: + print "\t" "bye" + soc.close() + self.connection.close() + + def do_GET(self): + (scm, netloc, path, params, query, fragment) = urlparse.urlparse( + self.path, 'http') + if scm != 'http' or fragment or not netloc: + self.send_error(400, "bad url %s" % self.path) + return + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + if self._connect_to(netloc, soc): + self.log_request() + soc.send("%s %s %s\r\n" % ( + self.command, + urlparse.urlunparse(('', '', path, params, query, '')), + self.request_version)) + self.headers['Connection'] = 'close' + del self.headers['Proxy-Connection'] + for key_val in self.headers.items(): + soc.send("%s: %s\r\n" % key_val) + soc.send("\r\n") + self._read_write(soc) + finally: + print "\t" "bye" + soc.close() + self.connection.close() + + def _read_write(self, soc, max_idling=20): + iw = [self.connection, soc] + ow = [] + count = 0 + while 1: + count += 1 + (ins, _, exs) = select.select(iw, ow, iw, 3) + if exs: break + if ins: + for i in ins: + if i is soc: + out = self.connection + else: + out = soc + data = i.recv(8192) + if data: + out.send(data) + count = 0 + else: + print "\t" "idle", count + if count == max_idling: break + + do_HEAD = do_GET + do_POST = do_GET + do_PUT = do_GET + do_DELETE=do_GET + +class ThreadingHTTPServer (SocketServer.ThreadingMixIn, + BaseHTTPServer.HTTPServer): pass + +if __name__ == '__main__': + from sys import argv + if argv[1:] and argv[1] in ('-h', '--help'): + print argv[0], "[port [allowed_client_name ...]]" + else: + if argv[2:]: + allowed = [] + for name in argv[2:]: + client = socket.gethostbyname(name) + allowed.append(client) + print "Accept: %s (%s)" % (client, name) + ProxyHandler.allowed_clients = allowed + del argv[2:] + else: + print "Any clients will be served..." + BaseHTTPServer.test(ProxyHandler, ThreadingHTTPServer)