durin42-hg-git-53d514c9c7e6/.hg_archival.txt0000644000000000000000000000017112653777310016514 0ustar 00000000000000repo: 06366111af3c6a2ffa06333ed60d3ed3b9ec0763 node: 53d514c9c7e6ac99e2c4d6df6c520a7db1e63d7f branch: default tag: 0.8.5 durin42-hg-git-53d514c9c7e6/.gitignore0000644000000000000000000000005012653777310015412 0ustar 00000000000000*.pyc tests/*.err build dist *.egg-info durin42-hg-git-53d514c9c7e6/.hgignore0000644000000000000000000000011612653777310015230 0ustar 00000000000000syntax: glob *.pyc tests/*.err tests/.testtimes build dist *.egg-info *.orig durin42-hg-git-53d514c9c7e6/.hgsigs0000644000000000000000000000600012653777310014710 0ustar 000000000000002f7bd8db709f8cf27107839951e88a38f915709d 0 iQIcBAABAgAGBQJSrI/JAAoJELnJ3IJKpb3VTroP/iK6eHVLtwDlsOqyEHCGP9CYw0Gd8bCq46YWZsVmz3bj01dKFwWSPKtFv+PNKFMGa8UV5VsEKCMoby2hzX6+beKs7oNLMU2YUGhP9A1QvQtpB5MArEMXWrQXeJTJZoZfA+2K2AgPJdeo1cLWKHIBc8IcG6yl9K55ummHwyg96KDna3fWrARxgLMNOBAva7otCSW/nQ0raABXi+JklBtB2C+Glxc3amnQQaWO659/DK0V2YS/54tiHJ73iJDlvm0bk+cgMUwtsgGJHFkLtCkbVA0tmDmsZBPaokRkgiJTEnqBLjKXoOqeoktAxdxSVHSEbIxx6guPhOtV0/cZsseShWQ7+3L1rCw6ujvqm9aBithb04LgfkJRZyXuV6NdbYaS+Y1guLJAEbkjmJplswxMI/laEFV5yTyPTQ2a4pTYuOx9Ix4jtDgpqzKztEc1hVk+dX/B2WWOjH+eLOYriAJSIUC+57GLA6WpeKMYYRZoIUhwM0TUjD3ePX9bIkUp4vIFUTdZV45TCsanJ+3rTIXiZh/C/O/jauNQ6wa2rkpmD4YRKb89YSbst6pdIaHDNBrnQppxD9DvYAO9ToFLCp+IfPxBdM5jvb9Wsmcw9p/3awfdII3W9mk/MOAJzEfPJLjWZbWaGHKrnPlQdBl/BP03ueCqZ/pw4Chm20WSAAFfqWam 29f51877a61078371d428a424799272e22d00810 0 iQIcBAABAgAGBQJTcpG4AAoJELnJ3IJKpb3VuEgP/RKdNxF5qRaHMtW3AEboeHamVSuAqaro+2W2RYjEuawK97agByGUcjxU4FuvzuRTvVUIrcim1PsXOw5pr30AA9alyQ7zAzs0KOx/lUkQ0cXJ4a0kKF/Um2Angx4uVausloa7A21fgB5GBdDP/47pN5G7ycAoi9rDkRNk9xvHnVXeAmIrg9IWrMoj31f/wQal7mmX0GS5SNAsWCjEfnUe5UYIsxTcWkAzyCggv/fSJdOfZ3qExeBACHr5Bo2twtY5GWlUWKYA3Pk9FthC4sQ1uPSghaHhTuXS38ZxSouU9WSq+hBkmv6dg8DFKlqHMiuet80ex1EWU6VHANmNnndHi33fsKJnojMzVmBJ28u2TLKoZLhIn46OdLs9hhj4+aPACPI1I0+3CIeQ57MNWstTL3CU9uiTgZ89akbEHax8EtsEElB83f5aXWQ7jerLPr1XGyv788NTeuTjBvYdAJz0ymkmhm4867TPAYU4A9rwEpgrlksVXmZD3aK0M2yQovlgfYmQaQ34DnzPTQNNKrd1fzp6PG4MTqecHz0O0S/ie+9AJSN4dht3rnsHyoIP20V4I1cjqqfQVEVXfKv+hPbdw2TDleRUr6EaPVcViM1LlZFezE0UUKRhz3n+PFkrC3Ssf9EfHAkjH439GULe3ezgfXCzsGubtaRnIuF7IB/XJ/gS 4e418736814f33e3351dd0a63628d06347c75082 0 iQIcBAABAgAGBQJT255gAAoJELnJ3IJKpb3VO0oQAMPpsoslxXiTpM+Q/bU56dwNb5OQoFcj5EHXfFDu4Lx1RKGjG2OtbhGLj7GIAJw3Yz64glC91i6NJNGkRQODrb4gJUsCM0NBaT+Zy9gq/udNLqYBDnpxRQExYO/ZVd0kWXIWqfAiAJGy+Ut5osoCgqaom1/IbQ5T3fbFFYmYLDVvw4TgsulM+iILstu4JS8/Rd0sBz7wofKU0d02+o5b1cyCyYJHjIGjkst/22Z+bOg8qHY8p9ZYM+rktHatN+bYY5OxYQYRGhPDXbx6fkTHPSo4MfKz+nH3oEWxV892+jHvTsiJSxDQXGFMm/1Pnn4+IvYw3v3Ohw0jj9/ndnktRK44sa/aEKwZO+VGUejkIXGSr6COmf1unNPqME1W6LFekYA+Ou4nFcYGTNmD/sV20tZTXkNL2nWX2gR2qGmsI8szdm5smQTFctmJvUiI+3QhASjXo8kty/JShvfhn7aUpqV+E/ddDecZx66I6JdCpsqIKZG6uRyMRHgYBwl6rC0lX9RVWLthr9Ed/XGnhxn1wClNO/mqZj3Ja34AB2ZCEdgse11w0+mnUxCn3MPeDAKz5tJaH68UnfG+79FyDJJtSZJb3xI4aC1Z3vWQWD/KeanTqjphkx4cy0t38+D5Q4SbHyiL5/hXQYBPnpm1DuHke1iJU6tVGVUvRzjxlKCM81u2 aedd9b232115b6fa5f7e894a1921bb45ac5b447b 0 iQIcBAABAgAGBQJUy6+GAAoJELnJ3IJKpb3VF2EP/0M467IBUGRWbJaQT/SghmYrsxypIUJ4Wq3KPgzb50RV/fd76Bb2fiM9rL0vWLe0fGK9iGkMKrCx4a+XVGkw5HV1mqyFEBo0ubwNJXPObohyhADaOChtEYi0o0vInuTA7cHgIYpLlmTEPArNlX/tDJanznehgu+VjaPfv5nKwj7mtm0kOItQta5lD7LUU8HH3JXkeUrDVovQyC+F8woKU8oF4fed8l3fXv6vjFckY2gcsdXwx1C8tHjHqE51k0+rxFpXtQAoeI6LwaqReqdUP3/GK9tTl42ywcl/KrA+UmdSehTNReG6SL1ZWsnikU7wWdNn8/ZKRkJZJ8KaI1bNxK0KiWe+BcVie8BI/18xTKtJ0p5sOX1gAcveDC0sy7ajOqJmlieu1HCXQtcs03WKoyOqYE7X1EcNf2ZKbyeP/jFgg6hgTHQt96JfCT0c+WLdpKz4H0gBfiPOnaXjHgJ/hi9Deu0cehsR1AnEI+ou6r/+wGA3mipQX7ooh4L80ONJMJNe0JIvnGkFWxbWRzXY70IGVF1NtwmJQ1HLB85hSj39RrKuRcza5aajMLLvQWEPd5wfPiM9ZnJoEeUvBtdtzVPowXMqQI0vz5vMDTbrku772kGlu+iHtESc+qcyDyxC8Vd5VorqX1brZLbvwWmlPymqI5aqe6mj4pJr3p+zH8Rp durin42-hg-git-53d514c9c7e6/.hgtags0000644000000000000000000000215012653777310014703 0ustar 00000000000000505d7cdca19838bfb270193e0709154a2dad5c19 0.1.0 77d6c9eb02fb96abb6a933170a912e94e3141f22 0.2.0 21ead8190d9c5f2a5a7500bab1d89b5373eda040 0.2.1 a222399a59d7a960f9ac41b89d3d0041bdf3e78c 0.2.2 5d39b98e5083a0897328f97c4c9795e64d45aac8 0.2.3 b53421918a89a20f35dc6b6c3974234fe45a5512 0.2.4 bc72dd89c2c911b3b844bd6a1e3841ca16cec59c 0.2.5 46d390f404da3add53c1f8de91216388f791cd82 0.2.6 fa3edeec7ed16dec6a16bd3e99bc3feba93115c3 0.3.0 556c3c586c4aa52f587ccc4d2d65b370a7e9037f 0.3.1 a9c0b93488d4d082f813c6d91c8e473505a026c4 0.3.2 9d44dafbb31c14126be151b78c7a41b3c110fd97 0.3.3 586b7aa9646641b3b1083ab349bb186c79aa646b 0.3.4 a3c3b8077cbeec7c381a4b312d722d575a738610 0.4.0 ef41e87ea11afdabfa6583e2c86dec375c67b2a4 0.5.0 9f20b66027c259e498a96454cf4c380e9440d769 0.6.0 f67747f18f20e97eff459aa24a70a23a2b3730a9 0.6.1 fc63d0e2653d3601dcc6e1adb99c13813cefae98 0.7.0 cf3dafce0611cf940945fdedb4cf5cb1091bc30c 0.8.0 e6489cf3fe8c204ad62846ba7755445755814330 0.8.1 e183fdc198f01affa094d15e6587a74de4eb063a 0.8.2 93d70993447c49d180a9c5d0ef130620fc956e33 0.8.3 d7ad67f850b279c1c3caa8349d4a47cf626e6f9a 0.8.3 22a12bf143a386f0a0be2bceb9ce1e42b1b27dd7 0.8.4 durin42-hg-git-53d514c9c7e6/CONTRIBUTING0000644000000000000000000000200212653777310015253 0ustar 00000000000000The short version: * Patches should have a good summary line for first line of commit message * Patches should be sent to the Google Group[0] * Patch needs to do exactly one thing * testsuite passes The longer version: We use a variant of Mercurial's own contribution system. http://mercurial.selenic.com/wiki/ContributingChanges contains their full guidelines. Key differences are (by rule number): 1. For hg-git, I'm not strict about the "topic: a few words" format for the first line, but I do insist on a sensible summary as the first line of your commit 2. We don't require use of issueNNN for bug fixes (we have no robot) 3. Use the hg-git google group 10. We use mostly pep8 style. The current codebase is a mess, but new code should be basically pep8. 0: Pull requests are generally not noticed more than once every few months. If you do a pull request, I'm still going to expect you to have a clean history, and to be willing to rework history so it's clean before I push the "merge" button. durin42-hg-git-53d514c9c7e6/COPYING0000644000000000000000000004325412653777310014472 0ustar 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. durin42-hg-git-53d514c9c7e6/DESIGN.txt0000644000000000000000000000752512653777310015152 0ustar 00000000000000Hg-Git Plugin Design Notes ========================== This plugin is designed to allow you to push to a Git server over the Git protocol and to pull from a Git based project. All data is stored in Hg native format with a mapping table. People collaborating in Git should not even be able to tell that you're using Hg to collaborate on their project (except for the '--HG--' section added to commit message). Nothing should need to be kept in the Git format - you should be able to run 'hg gclear' at any time to wipe out the Git directory and everything can be rebuilt losslessly from the existing Hg data - it is a cache only. We are using the Dulwich library, which I've modified quite a bit - I'll have to get these changes back upstream at some point. I've been adding 'TODO' comments all over the place where I only partially implemented something so I could get the entire first draft of functionality completed. The TODO areas should be mostly edge cases (executable bits, file rename info, tags, submodules, etc). Lossless Two Way ================ We need to store data that Git records that Merc does not in a git/extra_data file. This would be parents over two and committer information (author will be mapped to Hg committer). This way two Hg developers can collaborate without the Git transport messing up the local commits. Each Git commit should be reproducible as a Merc ID and vice versa on any system without losing data (ie changing the SHA). Branch Translation Policy ========================= Branches in Hg and Git are pretty different. This is meant to provide a clear policy on how one is converted to the other. * Without Bookmarks: * If you don't have bookmarks enabled, Git simply maps your 'tip' to the 'master' branch of the repository you're pushing to, since that is the most commonly used default branch name. Actually, pulling will map whatever the server points to as HEAD, but when pushing it will assume 'master' is your tip. $ hg gpush origin # will push tip to remote 'master' $ hg gpush origin master # same as above $ hg gpush origin --all # same as above If the remote server has divergent branches (branches with commits not reachable from HEAD) it will basically ignore them, not convert them into Hg changesets. It will tell you this (and why) when fetched. Conversely, on pushing, Hg named branches are ignored if they are not reachable from traversing the parents of tip. (SC: is this best?) * With Bookmarks: * If you have bookmarks enabled, it will treat your bookmarks like Git branches and will only push up references if you specify them. hg gpush origin # will error, you have to specify a branch hg gpush origin master # pushes the master branch hg gpush origin --all # pushes all local branches If a fetch gets branches, it _will_ import them and will create bookmarks that point to them, even if they have no common ancestors with the mainline (HEAD). * Other points * If you do not specify a remote name, it will assume 'origin'. This is helpful if you do not have bookmarks enabled as it will push tip automatically. If you have bookmarks enabled this is not helpful because you have to specify a branch name after. Eventually, I would like to setup tracking branch refspecs much like Git - say 'this local branch pushes and pulls to/from this remote and branch', but that will be one of the last things done. Testing Hg-Git ============== Tests are implemented in the Mercurial-standard way of small shell scripts. The scripts are located in the tests directory, and to run them you should change to that directory and then run tests/run-tests.py from the Mercurial sources. For example, if you have a copy of the Mercurial source code at /Stuff/hg-crew, you would do something like this: cd tests ; /Stuff/hg-crew/tests/run-tests.py And you should see output like this: . # Ran 1 tests, 0 skipped, 0 failed. durin42-hg-git-53d514c9c7e6/MANIFEST.in0000644000000000000000000000024512653777310015166 0ustar 00000000000000include COPYING include MANIFEST.in include README.md recursive-include hggit *.py *.rst recursive-include tests *.t *.py *.py.out hghave latin-1-encoding testutil durin42-hg-git-53d514c9c7e6/Makefile0000644000000000000000000000256612653777310015100 0ustar 00000000000000PYTHON=python TESTFLAGS ?= $(shell echo $$HGTESTFLAGS) help: @echo 'Commonly used make targets:' @echo ' tests - run all tests in the automatic test suite' @echo ' all-version-tests - run all tests against many hg versions' @echo ' tests-%s - run all tests in the specified hg version' all: help tests: cd tests && $(PYTHON) run-tests.py --with-hg=`which hg` $(TESTFLAGS) test-%: python -m doctest hggit/hg2git.py cd tests && $(PYTHON) run-tests.py --with-hg=`which hg` $(TESTFLAGS) $@ tests-%: @echo "Path to crew repo is $(CREW) - set this with CREW= if needed." hg -R $(CREW) checkout $$(echo $@ | sed s/tests-//) && \ (cd $(CREW) ; $(MAKE) clean ) && \ cd tests && $(PYTHON) $(CREW)/tests/run-tests.py $(TESTFLAGS) # This is intended to be the authoritative list of Hg versions that this # extension is tested with. Versions prior to the version that ships in the # latest Ubuntu LTS release (2.8.2 for 14.04 LTS) may be dropped if they # interfere with new development. The latest released minor version should be # listed for each major version; earlier minor versions are not needed. # Mercurial 3.4 had a core bug that caused a harmless test failure -- 3.4.1 # fixes that bug. all-version-tests: tests-2.8.2 tests-3.0.1 tests-3.1 tests-3.2.2 tests-3.3 \ tests-3.4.1 tests-3.5.2 tests-3.6.3 tests-3.7 tests-@ .PHONY: tests all-version-tests durin42-hg-git-53d514c9c7e6/README.md0000644000000000000000000002300212653777310014703 0ustar 00000000000000Hg-Git Mercurial Plugin ======================= * Homepage: http://hg-git.github.com/ * https://bitbucket.org/durin42/hg-git (primary) * https://github.com/schacon/hg-git (mirror) This is the Hg-Git plugin for Mercurial, adding the ability to push and pull to/from a Git server repository from Hg. This means you can collaborate on Git based projects from Hg, or use a Git server as a collaboration point for a team with developers using both Git and Hg. The Hg-Git plugin can convert commits/changesets losslessly from one system to another, so you can push via an Hg repository and another Hg client can pull it and their changeset node ids will be identical - Mercurial data does not get lost in translation. It is intended that Hg users may wish to use this to collaborate even if no Git users are involved in the project, and it may even provide some advantages if you're using Bookmarks (see below). Dependencies ============ This plugin is implemented entirely in Python - there are no Git binary dependencies, you do not need to have Git installed on your system. The only dependencies are Mercurial and Dulwich. See the Makefile for information about which versions of Mercurial are known to work, and setup.py for which versions of Dulwich are required. Usage ===== You can clone a Git repository from Hg by running `hg clone [dest]`. For example, if you were to run $ hg clone git://github.com/schacon/hg-git.git Hg-Git would clone the repository and convert it to an Hg repository for you. If you want to clone a github repository for later pushing (or any other repository you access via ssh), you need to convert the ssh url to a format with an explicit protocol prefix. For example, the git url with push access git@github.com:schacon/hg-git.git would read git+ssh://git@github.com/schacon/hg-git.git (Mind the switch from colon to slash after the host!) Your clone command would thus look like this: $ hg clone git+ssh://git@github.com/schacon/hg-git.git If you are starting from an existing Hg repository, you have to set up a Git repository somewhere that you have push access to, add a path entry for it in your .hg/hgrc file, and then run `hg push [name]` from within your repository. For example: $ cd hg-git # (an Hg repository) $ # edit .hg/hgrc and add the target git url in the paths section $ hg push This will convert all your Hg data into Git objects and push them to the Git server. Now that you have an Hg repository that can push/pull to/from a Git repository, you can fetch updates with `hg pull`. $ hg pull That will pull down any commits that have been pushed to the server in the meantime and give you a new head that you can merge in. Hg-Git can also be used to convert a Mercurial repository to Git. You can use a local repository or a remote repository accessed via SSH, HTTP or HTTPS. Use the following commands to convert the repository (it assumes you're running this in $HOME). $ mkdir git-repo; cd git-repo; git init; cd .. $ cd hg-repo $ hg bookmarks hg $ hg push ../git-repo The hg bookmark is necessary to prevent problems as otherwise hg-git pushes to the currently checked out branch confusing Git. This will create a branch named hg in the Git repository. To get the changes in master use the following command (only necessary in the first run, later just use git merge or rebase). $ cd git-repo $ git checkout -b master hg To import new changesets into the Git repository just rerun the hg push command and then use git merge or git rebase in your Git repository. Commands ======== gclear ------ TODO gimport ------- TODO gexport ------- TODO git-cleanup ----------- TODO Hg Bookmarks Integration ======================== Hg-Git pushes your bookmarks up to the Git server as branches and will pull Git branches down and set them up as bookmarks. Installing ========== Clone this repository somewhere and make the 'extensions' section in your `~/.hgrc` file look something like this: [extensions] hggit = [path-to]/hg-git/hggit That will enable the Hg-Git extension for you. See the Makefile for a list of compatible Mercurial versions. Configuration ============= git.intree ---------- hg-git keeps a git repository clone for reading and updating. By default, the git clone is the subdirectory `git` in your local Mercurial repository. If you would like this git clone to be at the same level of your Mercurial repository instead (named `.git`), add the following to your `hgrc`: [git] intree = True git.authors ----------- Git uses a strict convention for "author names" when representing changesets, using the form `[realname] [email address]`. Mercurial encourages this convention as well but is not as strict, so it's not uncommon for a Mercurial repo to have authors listed as, for example, simple usernames. hg-git by default will attempt to translate Mercurial usernames using the following rules: * If the Mercurial username fits the pattern `NAME `, the git name will be set to NAME and the email to EMAIL. * If the Mercurial username looks like an email (if it contains an `@`), the git name and email will both be set to that email. * If the Mercurial username consists of only a name, the email will be set to `none@none`. * Illegal characters (stray `<`s or `>`s) will be stripped out, and for `NAME ` usernames, any content after the right-bracket (for example, a second `>`) will be turned into a url-encoded sigil like `ext:(%3E)` in the git author name. Since these default behaviors may not be what you want (`none@none`, for example, shows up unpleasantly on Github as "illegal email address"), the `git.authors` option provides for an "authors translation file" that will be used during outgoing transfers from mercurial to git only, by modifying `hgrc` as such: [git] authors = authors.txt Where `authors.txt` is the name of a text file containing author name translations, one per each line, using the following format: johnny = John Smith dougie = Doug Johnson Empty lines and lines starting with a "#" are ignored. It should be noted that **this translation is on the hg->git side only**. Changesets coming from Git back to Mercurial will not translate back into hg usernames, so it's best that the same username/email combination be used on both the hg and git sides; the author file is mostly useful for translating legacy changesets. git.branch_bookmark_suffix --------------------------- hg-git does not convert between Mercurial named branches and git branches as the two are conceptually different; instead, it uses Mercurial bookmarks to represent the concept of a git branch. Therefore, when translating an hg repo over to git, you typically need to create bookmarks to mirror all the named branches that you'd like to see transferred over to git. The major caveat with this is that you can't use the same name for your bookmark as that of the named branch, and furthermore there's no feasible way to rename a branch in Mercurial. For the use case where one would like to transfer an hg repo over to git, and maintain the same named branches as are present on the hg side, the `branch_bookmark_suffix` might be all that's needed. This presents a string "suffix" that will be recognized on each bookmark name, and stripped off as the bookmark is translated to a git branch: [git] branch_bookmark_suffix=_bookmark Above, if an hg repo had a named branch called `release_6_maintenance`, you could then link it to a bookmark called `release_6_maintenance_bookmark`. hg-git will then strip off the `_bookmark` suffix from this bookmark name, and create a git branch called `release_6_maintenance`. When pulling back from git to hg, the `_bookmark` suffix is then applied back, if and only if an hg named branch of that name exists. E.g., when changes to the `release_6_maintenance` branch are checked into git, these will be placed into the `release_6_maintenance_bookmark` bookmark on hg. But if a new branch called `release_7_maintenance` were pulled over to hg, and there was not a `release_7_maintenance` named branch already, the bookmark will be named `release_7_maintenance` with no usage of the suffix. The `branch_bookmark_suffix` option is, like the `authors` option, intended for migrating legacy hg named branches. Going forward, an hg repo that is to be linked with a git repo should only use bookmarks for named branching. git.mindate ----------- If set, branches where the latest commit's commit time is older than this will not be imported. Accepts any date formats that Mercurial does -- see `hg help dates` for more. git.similarity -------------- Specify how similar files modified in a Git commit must be to be imported as Mercurial renames or copies, as a percentage between "0" (disabled) and "100" (files must be identical). For example, "90" means that a delete/add pair will be imported as a rename if more than 90% of the file has stayed the same. The default is "0" (disabled). git.renamelimit --------------- The number of files to consider when performing the copy/rename detection. Detection is disabled if the number of files modified in a commit is above the limit. Detection is O(N^2) in the number of files modified, so be sure not to set the limit too high. Similar to Git's `diff.renameLimit` config. The default is "400", the same as Git. git.findcopiesharder -------------------- Whether to consider unmodified files as copy sources. This is a very expensive operation for large projects, so use it with caution. Similar to `git diff`'s --find-copies-harder option. durin42-hg-git-53d514c9c7e6/TODO.txt0000644000000000000000000000146712653777310014745 0ustar 00000000000000GENERAL ========== * work fine with eclipse plugin or tortoise-hg MAPPING ISSUES ============== * work in Git on a named branch created in Hg is forward-ported to be named branch commits in Hg and stripped back out if re-exported REMOTE/BRANCH STUFF ===================== * explain what branch mapping policy determined when updating refs * error nicer if pushing to remote without push access (over git://) WEBSITE =========== * more usage documentation * screencast SPEED/EFFICIENCY ================ * dulwich improvements - don't send blobs/trees already on server - thin packs - packfile creation benchmarking (seems to take a while sometimes) - at least provide status output MAYBE ========== * submodules? * .gitignore, etc - try to convert? - (probably not automatically, but perhaps a generator?) durin42-hg-git-53d514c9c7e6/contrib/hggitperf.py0000644000000000000000000000441412653777310017423 0ustar 00000000000000# hggitperf.py - performance test routines '''helper extension to measure performance of hg-git operations This requires both the hggit and hggitperf extensions to be enabled and available. ''' from mercurial import cmdutil import time, os, tempfile import functools cmdtable = {} command = cmdutil.command(cmdtable) # the timer functions are copied from mercurial/contrib/perf.py def gettimer(ui, opts=None): """return a timer function and formatter: (timer, formatter) This functions exist to gather the creation of formatter in a single place instead of duplicating it in all performance command.""" # enforce an idle period before execution to counteract power management time.sleep(ui.configint("perf", "presleep", 1)) if opts is None: opts = {} # redirect all to stderr ui = ui.copy() ui.fout = ui.ferr # get a formatter fm = ui.formatter('perf', opts) return functools.partial(_timer, fm), fm def _timer(fm, func, title=None): results = [] begin = time.time() count = 0 while True: ostart = os.times() cstart = time.time() r = func() cstop = time.time() ostop = os.times() count += 1 a, b = ostart, ostop results.append((cstop - cstart, b[0] - a[0], b[1]-a[1])) if cstop - begin > 3 and count >= 100: break if cstop - begin > 10 and count >= 3: break fm.startitem() if title: fm.write('title', '! %s\n', title) if r: fm.write('result', '! result: %s\n', r) m = min(results) fm.plain('!') fm.write('wall', ' wall %f', m[0]) fm.write('comb', ' comb %f', m[1] + m[2]) fm.write('user', ' user %f', m[1]) fm.write('sys', ' sys %f', m[2]) fm.write('count', ' (best of %d)', count) fm.plain('\n') @command('perfgitloadmap') def perfgitloadmap(ui, repo): timer, fm = gettimer(ui) timer(repo.githandler.load_map) fm.end() @command('perfgitsavemap') def perfgitsavemap(ui, repo): timer, fm = gettimer(ui) repo.githandler.load_map() fd, f = tempfile.mkstemp(prefix='.git-mapfile-', dir=repo.path) basename = os.path.basename(f) try: timer(lambda: repo.githandler.save_map(basename)) finally: os.unlink(f) fm.end() durin42-hg-git-53d514c9c7e6/hggit/__init__.py0000644000000000000000000003164312653777310016651 0ustar 00000000000000# git.py - git server bridge # # Copyright 2008 Scott Chacon # also some code (and help) borrowed from durin42 # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. '''push and pull from a Git server This extension lets you communicate (push and pull) with a Git server. This way you can use Git hosting for your project or collaborate with a project that is in Git. A bridger of worlds, this plugin be. Try hg clone git:// or hg clone git+ssh:// For more information and instructions, see :hg:`help git` ''' # global modules import os # local modules import gitrepo import hgrepo import overlay import verify import util from bisect import insort from git_handler import GitHandler from mercurial.node import hex from mercurial.i18n import _ from mercurial import ( bundlerepo, cmdutil, demandimport, dirstate, discovery, extensions, help, hg, ui as hgui, util as hgutil, localrepo, manifest, revset, scmutil, templatekw, ) try: from mercurial import exchange exchange.push # existed in first iteration of this file except (AttributeError, ImportError): # We only *use* the exchange module in hg 3.2+, so this is safe pass try: from mercurial import ignore ignore.readpats ignoremod = True except (AttributeError, ImportError): # The ignore module disappeared in Mercurial 3.5 ignoremod = False baseset = set try: baseset = revset.baseset except AttributeError: # baseset was added in hg 3.0 pass demandimport.ignore.extend([ 'collections', ]) __version__ = '0.8.5' testedwith = '2.8.2 3.0.1 3.1 3.2.2 3.3 3.4 3.5 3.6 3.7' buglink = 'https://bitbucket.org/durin42/hg-git/issues' cmdtable = {} command = cmdutil.command(cmdtable) # support for `hg clone git://github.com/defunkt/facebox.git` # also hg clone git+ssh://git@github.com/schacon/simplegit.git for _scheme in util.gitschemes: hg.schemes[_scheme] = gitrepo # support for `hg clone localgitrepo` _oldlocal = hg.schemes['file'] try: urlcls = hgutil.url except AttributeError: class urlcls(object): def __init__(self, path): self.p = hgutil.drop_scheme('file', path) def localpath(self): return self.p def _isgitdir(path): """True if the given file path is a git repo.""" if os.path.exists(os.path.join(path, '.hg')): return False if os.path.exists(os.path.join(path, '.git')): # is full git repo return True if (os.path.exists(os.path.join(path, 'HEAD')) and os.path.exists(os.path.join(path, 'objects')) and os.path.exists(os.path.join(path, 'refs'))): # is bare git repo return True return False def _local(path): p = urlcls(path).localpath() if _isgitdir(p): return gitrepo # detect git ssh urls (which mercurial thinks is a file-like path) if util.isgitsshuri(p): return gitrepo return _oldlocal(path) hg.schemes['file'] = _local # we need to wrap this so that git-like ssh paths are not prepended with a # local filesystem path. ugh. def _url(orig, path, **kwargs): # we'll test for 'git@' then use our heuristic method to determine if it's # a git uri if not (path.startswith(os.sep) and ':' in path): return orig(path, **kwargs) # the file path will be everything up until the last slash right before the # ':' lastsep = path.rindex(os.sep, None, path.index(':')) + 1 gituri = path[lastsep:] if util.isgitsshuri(gituri): return orig(gituri, **kwargs) return orig(path, **kwargs) extensions.wrapfunction(hgutil, 'url', _url) def _httpgitwrapper(orig): # we should probably test the connection but for now, we just keep it # simple and check for a url ending in '.git' def httpgitscheme(uri): if uri.endswith('.git'): return gitrepo # the http(s) scheme just returns the _peerlookup return orig return httpgitscheme hg.schemes['https'] = _httpgitwrapper(hg.schemes['https']) hg.schemes['http'] = _httpgitwrapper(hg.schemes['http']) hgdefaultdest = hg.defaultdest def defaultdest(source): for scheme in util.gitschemes: if source.startswith('%s://' % scheme) and source.endswith('.git'): return hgdefaultdest(source[:-4]) if source.endswith('.git'): return hgdefaultdest(source[:-4]) return hgdefaultdest(source) hg.defaultdest = defaultdest def getversion(): """return version with dependencies for hg --version -v""" import dulwich dulver = '.'.join(str(i) for i in dulwich.__version__) return __version__ + (" (dulwich %s)" % dulver) # defend against tracebacks if we specify -r in 'hg pull' def safebranchrevs(orig, lrepo, repo, branches, revs): revs, co = orig(lrepo, repo, branches, revs) if hgutil.safehasattr(lrepo, 'changelog') and co not in lrepo.changelog: co = None return revs, co if getattr(hg, 'addbranchrevs', False): extensions.wrapfunction(hg, 'addbranchrevs', safebranchrevs) def extsetup(ui): templatekw.keywords.update({'gitnode': gitnodekw}) revset.symbols.update({ 'fromgit': revset_fromgit, 'gitnode': revset_gitnode }) helpdir = os.path.join(os.path.dirname(__file__), 'help') entry = (['git'], _("Working with Git Repositories"), # Mercurial >= 3.6: doc(ui) lambda *args: open(os.path.join(helpdir, 'git.rst')).read()) insort(help.helptable, entry) def reposetup(ui, repo): if not isinstance(repo, gitrepo.gitrepo): if (getattr(dirstate, 'rootcache', False) and (not ignoremod or getattr(ignore, 'readpats', False))): # only install our dirstate wrapper if it has a hope of working import gitdirstate if ignoremod: def ignorewrap(orig, *args, **kwargs): return gitdirstate.gignore(*args, **kwargs) extensions.wrapfunction(ignore, 'ignore', ignorewrap) dirstate.dirstate = gitdirstate.gitdirstate klass = hgrepo.generate_repo_subclass(repo.__class__) repo.__class__ = klass if hgutil.safehasattr(manifest, '_lazymanifest'): # Mercurial >= 3.4 extensions.wrapfunction(manifest.manifestdict, 'diff', overlay.wrapmanifestdictdiff) @command('gimport') def gimport(ui, repo, remote_name=None): '''import commits from Git to Mercurial''' repo.githandler.import_commits(remote_name) @command('gexport') def gexport(ui, repo): '''export commits from Mercurial to Git''' repo.githandler.export_commits() @command('gclear') def gclear(ui, repo): '''clear out the Git cached data''' repo.ui.status(_("clearing out the git cache data\n")) repo.githandler.clear() @command('gverify', [('r', 'rev', '', _('revision to verify'), _('REV'))], _('[-r REV]')) def gverify(ui, repo, **opts): '''verify that a Mercurial rev matches the corresponding Git rev Given a Mercurial revision that has a corresponding Git revision in the map, this attempts to answer whether that revision has the same contents as the corresponding Git revision. ''' ctx = scmutil.revsingle(repo, opts.get('rev'), '.') return verify.verify(ui, repo, ctx) @command('git-cleanup') def git_cleanup(ui, repo): '''clean up Git commit map after history editing''' new_map = [] for line in repo.opener(GitHandler.map_file): gitsha, hgsha = line.strip().split(' ', 1) if hgsha in repo: new_map.append('%s %s\n' % (gitsha, hgsha)) f = repo.opener(GitHandler.map_file, 'wb') map(f.write, new_map) ui.status(_('git commit map cleaned\n')) def findcommonoutgoing(orig, repo, other, *args, **kwargs): if isinstance(other, gitrepo.gitrepo): heads = repo.githandler.get_refs(other.path)[0] kw = {} kw.update(kwargs) for val, k in zip(args, ('onlyheads', 'force', 'commoninc', 'portable')): kw[k] = val force = kw.get('force', False) commoninc = kw.get('commoninc', None) if commoninc is None: commoninc = discovery.findcommonincoming(repo, other, heads=heads, force=force) kw['commoninc'] = commoninc return orig(repo, other, **kw) return orig(repo, other, *args, **kwargs) extensions.wrapfunction(discovery, 'findcommonoutgoing', findcommonoutgoing) def getremotechanges(orig, ui, repo, other, *args, **opts): if isinstance(other, gitrepo.gitrepo): if args: revs = args[0] else: revs = opts.get('onlyheads', opts.get('revs')) r, c, cleanup = repo.githandler.getremotechanges(other, revs) # ugh. This is ugly even by mercurial API compatibility standards if 'onlyheads' not in orig.func_code.co_varnames: cleanup = None return r, c, cleanup return orig(ui, repo, other, *args, **opts) extensions.wrapfunction(bundlerepo, 'getremotechanges', getremotechanges) def peer(orig, uiorrepo, *args, **opts): newpeer = orig(uiorrepo, *args, **opts) if isinstance(newpeer, gitrepo.gitrepo): if isinstance(uiorrepo, localrepo.localrepository): newpeer.localrepo = uiorrepo return newpeer extensions.wrapfunction(hg, 'peer', peer) def isvalidlocalpath(orig, self, path): return orig(self, path) or _isgitdir(path) if (hgutil.safehasattr(hgui, 'path') and hgutil.safehasattr(hgui.path, '_isvalidlocalpath')): extensions.wrapfunction(hgui.path, '_isvalidlocalpath', isvalidlocalpath) @util.transform_notgit def exchangepull(orig, repo, remote, heads=None, force=False, bookmarks=(), **kwargs): if isinstance(remote, gitrepo.gitrepo): # transaction manager is present in Mercurial >= 3.3 try: trmanager = getattr(exchange, 'transactionmanager') except AttributeError: trmanager = None pullop = exchange.pulloperation(repo, remote, heads, force, bookmarks=bookmarks) if trmanager: pullop.trmanager = trmanager(repo, 'pull', remote.url()) wlock = repo.wlock() lock = repo.lock() try: pullop.cgresult = repo.githandler.fetch(remote.path, heads) if trmanager: pullop.trmanager.close() else: pullop.closetransaction() return pullop finally: if trmanager: pullop.trmanager.release() else: pullop.releasetransaction() lock.release() wlock.release() else: return orig(repo, remote, heads, force, bookmarks=bookmarks, **kwargs) if not hgutil.safehasattr(localrepo.localrepository, 'pull'): # Mercurial >= 3.2 extensions.wrapfunction(exchange, 'pull', exchangepull) # TODO figure out something useful to do with the newbranch param @util.transform_notgit def exchangepush(orig, repo, remote, force=False, revs=None, newbranch=False, bookmarks=(), **kwargs): if isinstance(remote, gitrepo.gitrepo): # opargs is in Mercurial >= 3.6 opargs = kwargs.get('opargs') if opargs is None: opargs = {} pushop = exchange.pushoperation(repo, remote, force, revs, newbranch, bookmarks, **opargs) pushop.cgresult = repo.githandler.push(remote.path, revs, force) return pushop else: return orig(repo, remote, force, revs, newbranch, bookmarks=bookmarks, **kwargs) if not hgutil.safehasattr(localrepo.localrepository, 'push'): # Mercurial >= 3.2 extensions.wrapfunction(exchange, 'push', exchangepush) def revset_fromgit(repo, subset, x): '''``fromgit()`` Select changesets that originate from Git. ''' revset.getargs(x, 0, 0, "fromgit takes no arguments") git = repo.githandler node = repo.changelog.node return baseset(r for r in subset if git.map_git_get(hex(node(r))) is not None) def revset_gitnode(repo, subset, x): '''``gitnode(hash)`` Select changesets that originate in the given Git revision. ''' args = revset.getargs(x, 1, 1, "gitnode takes one argument") rev = revset.getstring(args[0], "the argument to gitnode() must be a hash") git = repo.githandler node = repo.changelog.node def matches(r): gitnode = git.map_git_get(hex(node(r))) if gitnode is None: return False return rev in [gitnode, gitnode[:12]] return baseset(r for r in subset if matches(r)) def gitnodekw(**args): """:gitnode: String. The Git changeset identification hash, as a 40 hexadecimal digit string.""" node = args['ctx'] repo = args['repo'] gitnode = repo.githandler.map_git_get(node.hex()) if gitnode is None: gitnode = '' return gitnode durin42-hg-git-53d514c9c7e6/hggit/_ssh.py0000644000000000000000000000264212653777310016043 0ustar 00000000000000from dulwich.client import SubprocessWrapper import subprocess from mercurial import ( util, ) class SSHVendor(object): """Parent class for ui-linked Vendor classes.""" def generate_ssh_vendor(ui): """ Allows dulwich to use hg's ui.ssh config. The dulwich.client.get_ssh_vendor property should point to the return value. """ class _Vendor(SSHVendor): def run_command(self, host, command, username=None, port=None): if isinstance(command, basestring): # 0.12.x dulwich sends the raw string command = [command] elif len(command) > 1: # 0.11.x dulwich sends an array of [command arg1 arg2 ...], so # we detect that here and reformat it back to what hg-git # expects (e.g. "command 'arg1 arg2'") command = ["%s '%s'" % (command[0], ' '.join(command[1:]))] sshcmd = ui.config("ui", "ssh", "ssh") args = util.sshargs(sshcmd, host, username, port) cmd = '%s %s %s' % (sshcmd, args, util.shellquote(' '.join(command))) ui.debug('calling ssh: %s\n' % cmd) proc = subprocess.Popen(util.quotecommand(cmd), shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) return SubprocessWrapper(proc) return _Vendor durin42-hg-git-53d514c9c7e6/hggit/compat.py0000644000000000000000000000156112653777310016371 0ustar 00000000000000try: from mercurial import encoding hfsignoreclean = encoding.hfsignoreclean except AttributeError: # compat with hg 3.2.1 and earlier, which doesn't have # hfsignoreclean (This was borrowed wholesale from hg 3.2.2.) _ignore = [unichr(int(x, 16)).encode("utf-8") for x in "200c 200d 200e 200f 202a 202b 202c 202d 202e " "206a 206b 206c 206d 206e 206f feff".split()] # verify the next function will work assert set([i[0] for i in _ignore]) == set(["\xe2", "\xef"]) def hfsignoreclean(s): """Remove codepoints ignored by HFS+ from s. >>> hfsignoreclean(u'.h\u200cg'.encode('utf-8')) '.hg' >>> hfsignoreclean(u'.h\ufeffg'.encode('utf-8')) '.hg' """ if "\xe2" in s or "\xef" in s: for c in _ignore: s = s.replace(c, '') return s durin42-hg-git-53d514c9c7e6/hggit/git2hg.py0000644000000000000000000001221612653777310016271 0ustar 00000000000000# git2hg.py - convert Git repositories and commits to Mercurial ones import urllib from dulwich.objects import Commit, Tag def find_incoming(git_object_store, git_map, refs): '''find what commits need to be imported git_object_store is a dulwich object store. git_map is a map with keys being Git commits that have already been imported refs is a map of refs to SHAs that we're interested in.''' done = set() commit_cache = {} # sort by commit date def commitdate(sha): obj = git_object_store[sha] return obj.commit_time-obj.commit_timezone # get a list of all the head shas def get_heads(refs): todo = [] seenheads = set() for sha in refs.itervalues(): # refs could contain refs on the server that we haven't pulled down # the objects for if sha in git_object_store: obj = git_object_store[sha] while isinstance(obj, Tag): obj_type, sha = obj.object obj = git_object_store[sha] if isinstance(obj, Commit) and sha not in seenheads: seenheads.add(sha) todo.append(sha) todo.sort(key=commitdate, reverse=True) return todo def get_unseen_commits(todo): '''get all unseen commits reachable from todo in topological order 'unseen' means not reachable from the done set and not in the git map. Mutates todo and the done set in the process.''' commits = [] while todo: sha = todo[-1] if sha in done or sha in git_map: todo.pop() continue assert isinstance(sha, str) if sha in commit_cache: obj = commit_cache[sha] else: obj = git_object_store[sha] commit_cache[sha] = obj assert isinstance(obj, Commit) for p in obj.parents: if p not in done and p not in git_map: todo.append(p) # process parents of a commit before processing the # commit itself, and come back to this commit later break else: commits.append(sha) done.add(sha) todo.pop() return commits todo = get_heads(refs) commits = get_unseen_commits(todo) return GitIncomingResult(commits, commit_cache) class GitIncomingResult(object): '''struct to store result from find_incoming''' def __init__(self, commits, commit_cache): self.commits = commits self.commit_cache = commit_cache def extract_hg_metadata(message, git_extra): split = message.split("\n--HG--\n", 1) # Renames are explicitly stored in Mercurial but inferred in Git. For # commits that originated in Git we'd like to optionally infer rename # information to store in Mercurial, but for commits that originated in # Mercurial we'd like to disable this. How do we tell whether the commit # originated in Mercurial or in Git? We rely on the presence of extra hg-git # fields in the Git commit. # - Commits exported by hg-git versions past 0.7.0 always store at least one # hg-git field. # - For commits exported by hg-git versions before 0.7.0, this becomes a # heuristic: if the commit has any extra hg fields, it definitely originated # in Mercurial. If the commit doesn't, we aren't really sure. # If we think the commit originated in Mercurial, we set renames to a # dict. If we don't, we set renames to None. Callers can then determine # whether to infer rename information. renames = None extra = {} branch = None if len(split) == 2: renames = {} message, meta = split lines = meta.split("\n") for line in lines: if line == '': continue if ' : ' not in line: break command, data = line.split(" : ", 1) if command == 'rename': before, after = data.split(" => ", 1) renames[after] = before if command == 'branch': branch = data if command == 'extra': k, v = data.split(" : ", 1) extra[k] = urllib.unquote(v) git_fn = 0 for field, data in git_extra: if field.startswith('HG:'): if renames is None: renames = {} command = field[3:] if command == 'rename': before, after = data.split(':', 1) renames[urllib.unquote(after)] = urllib.unquote(before) elif command == 'extra': k, v = data.split(':', 1) extra[urllib.unquote(k)] = urllib.unquote(v) else: # preserve ordering in Git by using an incrementing integer for # each field. Note that extra metadata in Git is an ordered list # of pairs. hg_field = 'GIT%d-%s' % (git_fn, field) git_fn += 1 extra[urllib.quote(hg_field)] = urllib.quote(data) return (message, renames, branch, extra) durin42-hg-git-53d514c9c7e6/hggit/git_handler.py0000644000000000000000000017507012653777310017375 0ustar 00000000000000import collections import itertools import os import urllib import urllib2 import re import cStringIO import StringIO from dulwich.errors import HangupException, GitProtocolError from dulwich.objects import Blob, Commit, Tag, Tree, parse_timezone from dulwich.pack import create_delta, apply_delta from dulwich.repo import Repo, check_ref_format from dulwich import client from dulwich import config as dul_config from dulwich import diff_tree from mercurial.i18n import _ from mercurial.node import hex, bin, nullid from mercurial import ( bookmarks, commands, context, encoding, util as hgutil, url, ) import _ssh import git2hg import hg2git import util from overlay import overlayrepo RE_GIT_AUTHOR = re.compile('^(.*?) ?\<(.*?)(?:\>(.*))?$') RE_GIT_SANITIZE_AUTHOR = re.compile('[<>\n]') RE_GIT_AUTHOR_EXTRA = re.compile('^(.*?)\ ext:\((.*)\) <(.*)\>$') RE_GIT_EXTRA_KEY = re.compile('GIT([0-9]*)-(.*)') # Test for git:// and git+ssh:// URI. # Support several URL forms, including separating the # host and path with either a / or : (sepr) RE_GIT_URI = re.compile( r'^(?Pgit([+]ssh)?://)(?P.*?)(:(?P\d+))?' r'(?P[:/])(?P.*)$') RE_NEWLINES = re.compile('[\r\n]') RE_GIT_PROGRESS = re.compile('\((\d+)/(\d+)\)') RE_AUTHOR_FILE = re.compile('\s*=\s*') CALLBACK_BUFFER = '' class GitProgress(object): """convert git server progress strings into mercurial progress""" def __init__(self, ui): self.ui = ui self.lasttopic = None self.msgbuf = '' def progress(self, msg): # 'Counting objects: 33640, done.\n' # 'Compressing objects: 0% (1/9955) \r msgs = RE_NEWLINES.split(self.msgbuf + msg) self.msgbuf = msgs.pop() for msg in msgs: td = msg.split(':', 1) data = td.pop() if not td: self.flush(data) continue topic = td[0] m = RE_GIT_PROGRESS.search(data) if m: if self.lasttopic and self.lasttopic != topic: self.flush() self.lasttopic = topic pos, total = map(int, m.group(1, 2)) self.ui.progress(topic, pos, total=total) else: self.flush(msg) def flush(self, msg=None): if self.lasttopic: self.ui.progress(self.lasttopic, None) self.lasttopic = None if msg: self.ui.note(msg + '\n') class GitHandler(object): map_file = 'git-mapfile' remote_refs_file = 'git-remote-refs' tags_file = 'git-tags' def __init__(self, dest_repo, ui): self.repo = dest_repo self.ui = ui if ui.configbool('git', 'intree'): self.gitdir = self.repo.wjoin('.git') else: self.gitdir = self.repo.join('git') self.init_author_file() self.paths = ui.configitems('paths') self.branch_bookmark_suffix = ui.config('git', 'branch_bookmark_suffix') self._map_git_real = None self._map_hg_real = None self.load_tags() self._remote_refs = None @property def _map_git(self): if self._map_git_real is None: self.load_map() return self._map_git_real @property def _map_hg(self): if self._map_hg_real is None: self.load_map() return self._map_hg_real @property def remote_refs(self): if self._remote_refs is None: self.load_remote_refs() return self._remote_refs @hgutil.propertycache def git(self): # Dulwich is going to try and join unicode ref names against # the repository path to try and read unpacked refs. This # doesn't match hg's bytes-only view of filesystems, we just # have to cope with that. As a workaround, try decoding our # (bytes) path to the repo in hg's active encoding and hope # for the best. gitpath = self.gitdir.decode(encoding.encoding, encoding.encodingmode) # make the git data directory if os.path.exists(self.gitdir): return Repo(gitpath) else: os.mkdir(self.gitdir) return Repo.init_bare(gitpath) def init_author_file(self): self.author_map = {} if self.ui.config('git', 'authors'): with open(self.repo.wjoin(self.ui.config('git', 'authors'))) as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue from_, to = RE_AUTHOR_FILE.split(line, 2) self.author_map[from_] = to # FILE LOAD AND SAVE METHODS def map_set(self, gitsha, hgsha): self._map_git[gitsha] = hgsha self._map_hg[hgsha] = gitsha def map_hg_get(self, gitsha): return self._map_git.get(gitsha) def map_git_get(self, hgsha): return self._map_hg.get(hgsha) def load_map(self): map_git_real = {} map_hg_real = {} if os.path.exists(self.repo.join(self.map_file)): for line in self.repo.opener(self.map_file): # format is <40 hex digits> <40 hex digits>\n if len(line) != 82: raise ValueError( _('corrupt mapfile: incorrect line length %d') % len(line)) gitsha = line[:40] hgsha = line[41:81] map_git_real[gitsha] = hgsha map_hg_real[hgsha] = gitsha self._map_git_real = map_git_real self._map_hg_real = map_hg_real def save_map(self, map_file): file = self.repo.opener(map_file, 'w+', atomictemp=True) map_hg = self._map_hg buf = cStringIO.StringIO() bwrite = buf.write for hgsha, gitsha in map_hg.iteritems(): bwrite("%s %s\n" % (gitsha, hgsha)) file.write(buf.getvalue()) buf.close() # If this complains, atomictempfile no longer has close file.close() def load_tags(self): self.tags = {} if os.path.exists(self.repo.join(self.tags_file)): for line in self.repo.opener(self.tags_file): sha, name = line.strip().split(' ', 1) self.tags[name] = sha def save_tags(self): file = self.repo.opener(self.tags_file, 'w+', atomictemp=True) for name, sha in sorted(self.tags.iteritems()): if not self.repo.tagtype(name) == 'global': file.write("%s %s\n" % (sha, name)) # If this complains, atomictempfile no longer has close file.close() def load_remote_refs(self): self._remote_refs = {} refdir = os.path.join(self.git.path, 'refs', 'remotes') paths = self.paths # if paths are set, we should still check 'default' if not paths: paths = [('default', None),] # we avoid using dulwich's refs method because it is incredibly slow; # on a repo with a few hundred branches and a few thousand tags, # dulwich took about 200ms for p in paths: remotedir = os.path.join(refdir, p[0]) for root, dirs, files in os.walk(remotedir): for f in files: try: ref = root.replace(refdir + os.sep, '') + '/' node = open(os.path.join(root, f)).read().strip() self._remote_refs[ref + f] = bin(self._map_git[node]) except (KeyError, IOError): pass # END FILE LOAD AND SAVE METHODS # COMMANDS METHODS def import_commits(self, remote_name): refs = self.git.refs.as_dict() filteredrefs = self.filter_min_date(refs) self.import_git_objects(remote_name, filteredrefs) self.update_hg_bookmarks(refs) self.save_map(self.map_file) def fetch(self, remote, heads): refs = self.fetch_pack(remote, heads) remote_name = self.remote_name(remote) oldheads = self.repo.changelog.heads() imported = 0 if refs: filteredrefs = self.filter_min_date(self.filter_refs(refs, heads)) imported = self.import_git_objects(remote_name, filteredrefs) self.import_tags(refs) self.update_hg_bookmarks(refs) if remote_name: self.update_remote_branches(remote_name, refs) elif not self.paths: # intial cloning self.update_remote_branches('default', refs) # "Activate" a tipmost bookmark. bms = self.repo['tip'].bookmarks() if bms: try: bookmarks.activate(self.repo, bms[0]) except AttributeError: # hg < 3.5 bookmarks.setcurrent(self.repo, bms[0]) self.save_map(self.map_file) if imported == 0: return 0 # code taken from localrepo.py:addchangegroup dh = 0 if oldheads: heads = self.repo.changelog.heads() dh = len(heads) - len(oldheads) for h in heads: if h not in oldheads and self.repo[h].closesbranch(): dh -= 1 if dh < 0: return dh - 1 else: return dh + 1 def export_commits(self): try: self.export_git_objects() self.export_hg_tags() self.update_references() finally: self.save_map(self.map_file) def get_refs(self, remote): self.export_commits() client, path = self.get_transport_and_path(remote) old_refs = {} new_refs = {} def changed(refs): old_refs.update(refs) exportable = self.get_exportable() new_refs.update(self.get_changed_refs(refs, exportable, True)) return refs # always return the same refs to make the send a no-op try: client.send_pack(path, changed, lambda have, want: []) changed_refs = [ref for ref, sha in new_refs.iteritems() if sha != old_refs.get(ref)] new = [bin(self.map_hg_get(new_refs[ref])) for ref in changed_refs] old = {} for r in old_refs: old_ref = self.map_hg_get(old_refs[r]) if old_ref: old[bin(old_ref)] = 1 return old, new except (HangupException, GitProtocolError), e: raise hgutil.Abort(_("git remote error: ") + str(e)) def push(self, remote, revs, force): self.export_commits() old_refs, new_refs = self.upload_pack(remote, revs, force) remote_name = self.remote_name(remote) if remote_name and new_refs: for ref, new_sha in sorted(new_refs.iteritems()): old_sha = old_refs.get(ref) if old_sha is None: if self.ui.verbose: self.ui.note("adding reference %s::%s => GIT:%s\n" % (remote_name, ref, new_sha[0:8])) else: self.ui.status("adding reference %s\n" % ref) elif new_sha != old_sha: if self.ui.verbose: self.ui.note("updating reference %s::%s => GIT:%s\n" % (remote_name, ref, new_sha[0:8])) else: self.ui.status("updating reference %s\n" % ref) else: self.ui.debug("unchanged reference %s::%s => GIT:%s\n" % (remote_name, ref, new_sha[0:8])) self.update_remote_branches(remote_name, new_refs) if old_refs == new_refs: self.ui.status(_("no changes found\n")) ret = None elif len(new_refs) > len(old_refs): ret = 1 + (len(new_refs) - len(old_refs)) elif len(old_refs) > len(new_refs): ret = -1 - (len(new_refs) - len(old_refs)) else: ret = 1 return ret def clear(self): mapfile = self.repo.join(self.map_file) if os.path.exists(self.gitdir): for root, dirs, files in os.walk(self.gitdir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) os.rmdir(self.gitdir) if os.path.exists(mapfile): os.remove(mapfile) # incoming support def getremotechanges(self, remote, revs): self.export_commits() refs = self.fetch_pack(remote.path, revs) # refs contains all remote refs. Prune to only those requested. if revs: reqrefs = {} for rev in revs: for n in ('refs/heads/' + rev, 'refs/tags/' + rev): if n in refs: reqrefs[n] = refs[n] else: reqrefs = refs commits = [bin(c) for c in self.get_git_incoming(reqrefs).commits] b = overlayrepo(self, commits, refs) return (b, commits, lambda: None) # CHANGESET CONVERSION METHODS def export_git_objects(self): repo = self.repo clnode = repo.changelog.node nodes = (clnode(n) for n in repo) export = (repo[node] for node in nodes if not hex(node) in self._map_hg) export = [ctx for ctx in export if ctx.extra().get('hg-git', None) != 'octopus'] total = len(export) if not total: return self.ui.note(_("exporting hg objects to git\n")) # By only exporting deltas, the assertion is that all previous objects # for all other changesets are already present in the Git repository. # This assertion is necessary to prevent redundant work. Here, nodes, # and therefore export, is in topological order. By definition, # export[0]'s parents must be present in Git, so we start the # incremental exporter from there. pctx = export[0].p1() pnode = pctx.node() if pnode == nullid: gitcommit = None else: gitsha = self._map_hg[hex(pnode)] try: gitcommit = self.git[gitsha] except KeyError: raise hgutil.Abort(_('Parent SHA-1 not present in Git' 'repo: %s' % gitsha)) exporter = hg2git.IncrementalChangesetExporter( self.repo, pctx, self.git.object_store, gitcommit) for i, ctx in enumerate(export): self.ui.progress('exporting', i, total=total) self.export_hg_commit(ctx.node(), exporter) self.ui.progress('exporting', None, total=total) # convert this commit into git objects # go through the manifest, convert all blobs/trees we don't have # write the commit object (with metadata info) def export_hg_commit(self, rev, exporter): self.ui.note(_("converting revision %s\n") % hex(rev)) oldenc = self.swap_out_encoding() ctx = self.repo.changectx(rev) extra = ctx.extra() commit = Commit() (time, timezone) = ctx.date() # work around to bad timezone offets - dulwich does not handle # sub minute based timezones. In the one known case, it was a # manual edit that led to the unusual value. Based on that, # there is no reason to round one way or the other, so do the # simplest and round down. timezone -= (timezone % 60) commit.author = self.get_git_author(ctx) commit.author_time = int(time) commit.author_timezone = -timezone if 'committer' in extra: # fixup timezone (name, timestamp, timezone) = extra['committer'].rsplit(' ', 2) commit.committer = name commit.commit_time = timestamp # work around a timezone format change if int(timezone) % 60 != 0: # pragma: no cover timezone = parse_timezone(timezone) # Newer versions of Dulwich return a tuple here if isinstance(timezone, tuple): timezone, neg_utc = timezone commit._commit_timezone_neg_utc = neg_utc else: timezone = -int(timezone) commit.commit_timezone = timezone else: commit.committer = commit.author commit.commit_time = commit.author_time commit.commit_timezone = commit.author_timezone commit.parents = [] for parent in self.get_git_parents(ctx): hgsha = hex(parent.node()) git_sha = self.map_git_get(hgsha) if git_sha: if git_sha not in self.git.object_store: raise hgutil.Abort(_('Parent SHA-1 not present in Git' 'repo: %s' % git_sha)) commit.parents.append(git_sha) commit.message, extra = self.get_git_message_and_extra(ctx) commit.extra.extend(extra) if 'encoding' in extra: commit.encoding = extra['encoding'] for obj, nodeid in exporter.update_changeset(ctx): if obj.id not in self.git.object_store: self.git.object_store.add_object(obj) tree_sha = exporter.root_tree_sha if tree_sha not in self.git.object_store: raise hgutil.Abort(_('Tree SHA-1 not present in Git repo: %s' % tree_sha)) commit.tree = tree_sha if commit.id not in self.git.object_store: self.git.object_store.add_object(commit) self.map_set(commit.id, ctx.hex()) self.swap_out_encoding(oldenc) return commit.id def get_valid_git_username_email(self, name): r"""Sanitize usernames and emails to fit git's restrictions. The following is taken from the man page of git's fast-import command: [...] Likewise LF means one (and only one) linefeed [...] committer The committer command indicates who made this commit, and when they made it. Here is the person's display name (for example "Com M Itter") and is the person's email address ("cm@example.com[1]"). LT and GT are the literal less-than (\x3c) and greater-than (\x3e) symbols. These are required to delimit the email address from the other fields in the line. Note that and are free-form and may contain any sequence of bytes, except LT, GT and LF. is typically UTF-8 encoded. Accordingly, this function makes sure that there are none of the characters <, >, or \n in any string which will be used for a git username or email. Before this, it first removes left angle brackets and spaces from the beginning, and right angle brackets and spaces from the end, of this string, to convert such things as " " to "john@doe.com" for convenience. TESTS: >>> from mercurial.ui import ui >>> g = GitHandler('', ui()).get_valid_git_username_email >>> g('John Doe') 'John Doe' >>> g('john@doe.com') 'john@doe.com' >>> g(' ') 'john@doe.com' >>> g(' > > ') 'random???garbage?' >>> g('Typo in hgrc >but.hg-git@handles.it.gracefully>') 'Typo in hgrc ?but.hg-git@handles.it.gracefully' """ return RE_GIT_SANITIZE_AUTHOR.sub('?', name.lstrip('< ').rstrip('> ')) def get_git_author(self, ctx): # hg authors might not have emails author = ctx.user() # see if a translation exists author = self.author_map.get(author, author) # check for git author pattern compliance a = RE_GIT_AUTHOR.match(author) if a: name = self.get_valid_git_username_email(a.group(1)) email = self.get_valid_git_username_email(a.group(2)) if a.group(3) != None and len(a.group(3)) != 0: name += ' ext:(' + urllib.quote(a.group(3)) + ')' author = '%s <%s>' \ % (self.get_valid_git_username_email(name), self.get_valid_git_username_email(email)) elif '@' in author: author = '%s <%s>' \ % (self.get_valid_git_username_email(author), self.get_valid_git_username_email(author)) else: author = self.get_valid_git_username_email(author) + ' ' if 'author' in ctx.extra(): author = "".join(apply_delta(author, ctx.extra()['author'])) return author def get_git_parents(self, ctx): def is_octopus_part(ctx): olist = ('octopus', 'octopus-done') return ctx.extra().get('hg-git', None) in olist parents = [] if ctx.extra().get('hg-git', None) == 'octopus-done': # implode octopus parents part = ctx while is_octopus_part(part): (p1, p2) = part.parents() assert ctx.extra().get('hg-git', None) != 'octopus' parents.append(p1) part = p2 parents.append(p2) else: parents = ctx.parents() return parents def get_git_message_and_extra(self, ctx): extra = ctx.extra() message = ctx.description() + "\n" if 'message' in extra: message = "".join(apply_delta(message, extra['message'])) # HG EXTRA INFORMATION # test only -- do not document this! extra_in_message = self.ui.configbool('git', 'debugextrainmessage', False) extra_message = '' git_extra = [] if ctx.branch() != 'default': # we always store the branch in the extra message extra_message += "branch : " + ctx.branch() + "\n" # Git native extra items always come first, followed by hg renames, # followed by hg extra keys git_extraitems = [] for key, value in extra.items(): m = RE_GIT_EXTRA_KEY.match(key) if m is not None: git_extraitems.append((int(m.group(1)), m.group(2), value)) del extra[key] git_extraitems.sort() for i, field, value in git_extraitems: git_extra.append((urllib.unquote(field), urllib.unquote(value))) if extra.get('hg-git-rename-source', None) != 'git': renames = [] for f in ctx.files(): if f not in ctx.manifest(): continue rename = ctx.filectx(f).renamed() if rename: renames.append((rename[0], f)) if renames: for oldfile, newfile in renames: if extra_in_message: extra_message += ("rename : " + oldfile + " => " + newfile + "\n") else: spec = '%s:%s' % (urllib.quote(oldfile), urllib.quote(newfile)) git_extra.append(('HG:rename', spec)) # hg extra items always go at the end extraitems = extra.items() extraitems.sort() for key, value in extraitems: if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git', 'hg-git-rename-source'): continue else: if extra_in_message: extra_message += ("extra : " + key + " : " + urllib.quote(value) + "\n") else: spec = '%s:%s' % (urllib.quote(key), urllib.quote(value)) git_extra.append(('HG:extra', spec)) if extra_message: message += "\n--HG--\n" + extra_message if (extra.get('hg-git-rename-source', None) != 'git' and not extra_in_message and not git_extra and extra_message == ''): # We need to store this if no other metadata is stored. This # indicates that when reimporting the commit into Mercurial we'll # know not to detect renames. git_extra.append(('HG:rename-source', 'hg')) return message, git_extra def get_git_incoming(self, refs): return git2hg.find_incoming(self.git.object_store, self._map_git, refs) def import_git_objects(self, remote_name, refs): result = self.get_git_incoming(refs) commits = result.commits commit_cache = result.commit_cache # import each of the commits, oldest first total = len(commits) if total: self.ui.status(_("importing git objects into hg\n")) else: self.ui.status(_("no changes found\n")) mapsavefreq = self.ui.configint('hggit', 'mapsavefrequency', 0) for i, csha in enumerate(commits): self.ui.progress('importing', i, total=total, unit='commits') commit = commit_cache[csha] self.import_git_commit(commit) if mapsavefreq and i % mapsavefreq == 0: self.ui.debug(_("saving mapfile\n")) self.save_map(self.map_file) self.ui.progress('importing', None, total=total, unit='commits') # TODO if the tags cache is used, remove any dangling tag references return total def import_git_commit(self, commit): self.ui.debug(_("importing: %s\n") % commit.id) detect_renames = False (strip_message, hg_renames, hg_branch, extra) = git2hg.extract_hg_metadata( commit.message, commit.extra) if hg_renames is None: detect_renames = True # We have to store this unconditionally, even if there are no # renames detected from Git. This is because we export an extra # 'HG:rename-source' Git parameter when this isn't set, which will # break bidirectionality. extra['hg-git-rename-source'] = 'git' else: renames = hg_renames gparents = map(self.map_hg_get, commit.parents) for parent in gparents: if parent not in self.repo: raise hgutil.Abort(_('you appear to have run strip - ' 'please run hg git-cleanup')) # get a list of the changed, added, removed files and gitlinks files, gitlinks, git_renames = self.get_files_changed(commit, detect_renames) if detect_renames: renames = git_renames git_commit_tree = self.git[commit.tree] # Analyze hgsubstate and build an updated version using SHAs from # gitlinks. Order of application: # - preexisting .hgsubstate in git tree # - .hgsubstate from hg parent # - changes in gitlinks hgsubstate = util.parse_hgsubstate( self.git_file_readlines(git_commit_tree, '.hgsubstate')) parentsubdata = '' if gparents: p1ctx = self.repo.changectx(gparents[0]) if '.hgsubstate' in p1ctx: parentsubdata = p1ctx.filectx('.hgsubstate').data() parentsubdata = parentsubdata.splitlines() parentsubstate = util.parse_hgsubstate(parentsubdata) for path, sha in parentsubstate.iteritems(): hgsubstate[path] = sha for path, sha in gitlinks.iteritems(): if sha is None: hgsubstate.pop(path, None) else: hgsubstate[path] = sha # in case .hgsubstate wasn't among changed files # force its inclusion if it wasn't already deleted hgsubdeleted = files.get('.hgsubstate') if hgsubdeleted: hgsubdeleted = hgsubdeleted[0] if hgsubdeleted or (not hgsubstate and parentsubdata): files['.hgsubstate'] = True, None, None elif util.serialize_hgsubstate(hgsubstate) != parentsubdata: files['.hgsubstate'] = False, 0100644, None # Analyze .hgsub and merge with .gitmodules hgsub = None gitmodules = self.parse_gitmodules(git_commit_tree) if gitmodules: hgsub = util.parse_hgsub(self.git_file_readlines(git_commit_tree, '.hgsub')) for (sm_path, sm_url, sm_name) in gitmodules: hgsub[sm_path] = '[git]' + sm_url files['.hgsub'] = (False, 0100644, None) elif (commit.parents and '.gitmodules' in self.git[self.git[commit.parents[0]].tree]): # no .gitmodules in this commit, however present in the parent # mark its hg counterpart as deleted (assuming .hgsub is there # due to the same import_git_commit process files['.hgsub'] = (True, 0100644, None) date = (commit.author_time, -commit.author_timezone) text = strip_message origtext = text try: text.decode('utf-8') except UnicodeDecodeError: text = self.decode_guess(text, commit.encoding) text = '\n'.join([l.rstrip() for l in text.splitlines()]).strip('\n') if text + '\n' != origtext: extra['message'] = create_delta(text + '\n', origtext) author = commit.author # convert extra data back to the end if ' ext:' in commit.author: m = RE_GIT_AUTHOR_EXTRA.match(commit.author) if m: name = m.group(1) ex = urllib.unquote(m.group(2)) email = m.group(3) author = name + ' <' + email + '>' + ex if ' ' in commit.author: author = commit.author[:-12] try: author.decode('utf-8') except UnicodeDecodeError: origauthor = author author = self.decode_guess(author, commit.encoding) extra['author'] = create_delta(author, origauthor) oldenc = self.swap_out_encoding() def findconvergedfiles(p1, p2): # If any files have the same contents in both parents of a merge # (and are therefore not reported as changed by Git) but are at # different file revisions in Mercurial (because they arrived at # those contents in different ways), we need to include them in # the list of changed files so that Mercurial can join up their # filelog histories (same as if the merge was done in Mercurial to # begin with). if p2 == nullid: return [] manifest1 = self.repo.changectx(p1).manifest() manifest2 = self.repo.changectx(p2).manifest() return [path for path, node1 in manifest1.iteritems() if path not in files and manifest2.get(path, node1) != node1] def getfilectx(repo, memctx, f): info = files.get(f) if info is not None: # it's a file reported as modified from Git delete, mode, sha = info if delete: if getattr(memctx, '_returnnoneformissingfiles', False): return None else: # Mercurial < 3.2 raise IOError if not sha: # indicates there's no git counterpart e = '' copied_path = None if '.hgsubstate' == f: data = util.serialize_hgsubstate(hgsubstate) elif '.hgsub' == f: data = util.serialize_hgsub(hgsub) else: data = self.git[sha].data copied_path = renames.get(f) e = self.convert_git_int_mode(mode) else: # it's a converged file fc = context.filectx(self.repo, f, changeid=memctx.p1().rev()) data = fc.data() e = fc.flags() copied_path = None copied = fc.renamed() if copied: copied_path = copied[0] try: return context.memfilectx(self.repo, f, data, islink='l' in e, isexec='x' in e, copied=copied_path) except TypeError: return context.memfilectx(f, data, islink='l' in e, isexec='x' in e, copied=copied_path) p1, p2 = (nullid, nullid) octopus = False if len(gparents) > 1: # merge, possibly octopus def commit_octopus(p1, p2): ctx = context.memctx(self.repo, (p1, p2), text, list(files) + findconvergedfiles(p1, p2), getfilectx, author, date, {'hg-git': 'octopus'}) # See comment below about setting substate to None. ctx.substate = None return hex(self.repo.commitctx(ctx)) octopus = len(gparents) > 2 p2 = gparents.pop() p1 = gparents.pop() while len(gparents) > 0: p2 = commit_octopus(p1, p2) p1 = gparents.pop() else: if gparents: p1 = gparents.pop() # if named branch, add to extra if hg_branch: extra['branch'] = hg_branch # if committer is different than author, add it to extra if commit.author != commit.committer \ or commit.author_time != commit.commit_time \ or commit.author_timezone != commit.commit_timezone: extra['committer'] = "%s %d %d" % ( commit.committer, commit.commit_time, -commit.commit_timezone) if commit.encoding: extra['encoding'] = commit.encoding if octopus: extra['hg-git'] = 'octopus-done' ctx = context.memctx(self.repo, (p1, p2), text, list(files) + findconvergedfiles(p1, p2), getfilectx, author, date, extra) # Starting Mercurial commit d2743be1bb06, memctx imports from # committablectx. This means that it has a 'substate' property that # contains the subrepo state. Ordinarily, Mercurial expects the subrepo # to be present while making a new commit -- since hg-git is importing # purely in-memory commits without backing stores for the subrepos, # that won't work. Forcibly set the substate to None so that there's no # attempt to read subrepos. ctx.substate = None node = self.repo.commitctx(ctx) self.swap_out_encoding(oldenc) # save changeset to mapping file cs = hex(node) self.map_set(commit.id, cs) # PACK UPLOADING AND FETCHING def upload_pack(self, remote, revs, force): client, path = self.get_transport_and_path(remote) old_refs = {} change_totals = {} def changed(refs): self.ui.status(_("searching for changes\n")) old_refs.update(refs) all_exportable = self.get_exportable() if revs is None: exportable = all_exportable else: exportable = {} for rev in (hex(r) for r in revs): if rev not in all_exportable: raise hgutil.Abort("revision %s cannot be pushed since" " it doesn't have a ref" % self.repo[rev]) exportable[rev] = all_exportable[rev] return self.get_changed_refs(refs, exportable, force) def genpack(have, want): commits = [] for mo in self.git.object_store.find_missing_objects(have, want): (sha, name) = mo o = self.git.object_store[sha] t = type(o) change_totals[t] = change_totals.get(t, 0) + 1 if isinstance(o, Commit): commits.append(sha) commit_count = len(commits) self.ui.note(_("%d commits found\n") % commit_count) if commit_count > 0: self.ui.debug(_("list of commits:\n")) for commit in commits: self.ui.debug("%s\n" % commit) self.ui.status(_("adding objects\n")) return self.git.object_store.generate_pack_contents(have, want) def callback(remote_info): # dulwich (perhaps git?) wraps remote output at a fixed width but # signifies the end of transmission with a double new line global CALLBACK_BUFFER if remote_info and not remote_info.endswith('\n\n'): CALLBACK_BUFFER += remote_info return remote_info = CALLBACK_BUFFER + remote_info CALLBACK_BUFFER = '' if not remote_info: remote_info = '\n' for line in remote_info[:-1].split('\n'): self.ui.status(_("remote: %s\n") % line) try: new_refs = client.send_pack(path, changed, genpack, progress=callback) if len(change_totals) > 0: self.ui.status(_("added %d commits with %d trees" " and %d blobs\n") % (change_totals.get(Commit, 0), change_totals.get(Tree, 0), change_totals.get(Blob, 0))) return old_refs, new_refs except (HangupException, GitProtocolError), e: raise hgutil.Abort(_("git remote error: ") + str(e)) def get_changed_refs(self, refs, exportable, force): new_refs = refs.copy() # The remote repo is empty and the local one doesn't have # bookmarks/tags # # (older dulwich versions return the proto-level # capabilities^{} key when the dict should have been # empty. That check can probably be removed at some point in # the future.) if not refs or refs.keys()[0] == 'capabilities^{}': if not exportable: tip = self.repo.lookup('tip') if tip != nullid: if 'capabilities^{}' in new_refs: del new_refs['capabilities^{}'] tip = hex(tip) try: commands.bookmark(self.ui, self.repo, 'master', rev=tip, force=True) except NameError: bookmarks.bookmark(self.ui, self.repo, 'master', rev=tip, force=True) try: bookmarks.activate(self.repo, 'master') except AttributeError: # hg < 3.5 bookmarks.setcurrent(self.repo, 'master') new_refs['refs/heads/master'] = self.map_git_get(tip) # mapped nodes might be hidden unfiltered = self.repo.unfiltered() for rev, rev_refs in exportable.iteritems(): ctx = self.repo[rev] if not rev_refs: raise hgutil.Abort("revision %s cannot be pushed since" " it doesn't have a ref" % ctx) # Check if the tags the server is advertising are annotated tags, # by attempting to retrieve it from the our git repo, and building # a list of these tags. # # This is possible, even though (currently) annotated tags are # dereferenced and stored as lightweight ones, as the annotated tag # is still stored in the git repo. uptodate_annotated_tags = [] for ref in rev_refs.tags: # Check tag. if ref not in refs: continue try: # We're not using Repo.tag(), as it's deprecated. tag = self.git.get_object(refs[ref]) if not isinstance(tag, Tag): continue except KeyError: continue # If we've reached here, the tag's good. uptodate_annotated_tags.append(ref) for ref in rev_refs: if ref not in refs: new_refs[ref] = self.map_git_get(ctx.hex()) elif new_refs[ref] in self._map_git: rctx = unfiltered[self.map_hg_get(new_refs[ref])] if rctx.ancestor(ctx) == rctx or force: new_refs[ref] = self.map_git_get(ctx.hex()) else: raise hgutil.Abort("pushing %s overwrites %s" % (ref, ctx)) elif ref in uptodate_annotated_tags: # we already have the annotated tag. pass else: raise hgutil.Abort( "branch '%s' changed on the server, " "please pull and merge before pushing" % ref) return new_refs def fetch_pack(self, remote_name, heads=None): localclient, path = self.get_transport_and_path(remote_name) # The dulwich default walk only checks refs/heads/. We also want to # consider remotes when doing discovery, so we build our own list. We # can't just do 'refs/' here because the tag class doesn't have a # parents function for walking, and older versions of dulwich don't like # that. haveheads = self.git.refs.as_dict('refs/remotes/').values() haveheads.extend(self.git.refs.as_dict('refs/heads/').values()) graphwalker = self.git.get_graph_walker(heads=haveheads) def determine_wants(refs): if refs is None: return None filteredrefs = self.filter_refs(refs, heads) return [x for x in filteredrefs.itervalues() if x not in self.git] try: progress = GitProgress(self.ui) f = StringIO.StringIO() ret = localclient.fetch_pack(path, determine_wants, graphwalker, f.write, progress.progress) if(f.pos != 0): f.seek(0) self.git.object_store.add_thin_pack(f.read, None) progress.flush() # For empty repos dulwich gives us None, but since later # we want to iterate over this, we really want an empty # iterable return ret if ret else {} except (HangupException, GitProtocolError), e: raise hgutil.Abort(_("git remote error: ") + str(e)) # REFERENCES HANDLING def filter_refs(self, refs, heads): '''For a dictionary of refs: shas, if heads is None then return refs that match the heads. Otherwise, return refs that are heads or tags. ''' filteredrefs = [] if heads is not None: # contains pairs of ('refs/(heads|tags|...)/foo', 'foo') # if ref is just '', then we get ('foo', 'foo') stripped_refs = [(r, r[r.find('/', r.find('/') + 1) + 1:]) for r in refs] for h in heads: if h.endswith('/*'): prefix = h[:-1] # include the / but not the * r = [pair[0] for pair in stripped_refs if pair[1].startswith(prefix)] r.sort() filteredrefs.extend(r) else: r = [pair[0] for pair in stripped_refs if pair[1] == h] if not r: raise hgutil.Abort("ref %s not found on remote server" % h) elif len(r) == 1: filteredrefs.append(r[0]) else: raise hgutil.Abort("ambiguous reference %s: %r" % (h, r)) else: for ref, sha in refs.iteritems(): if (not ref.endswith('^{}') and (ref.startswith('refs/heads/') or ref.startswith('refs/tags/'))): filteredrefs.append(ref) filteredrefs.sort() # the choice of OrderedDict vs plain dict has no impact on stock # hg-git, but allows extensions to customize the order in which refs # are returned return util.OrderedDict((r, refs[r]) for r in filteredrefs) def filter_min_date(self, refs): '''filter refs by minimum date This only works for refs that are available locally.''' min_date = self.ui.config('git', 'mindate') if min_date is None: return refs # filter refs older than min_timestamp min_timestamp, min_offset = hgutil.parsedate(min_date) def check_min_time(obj): if isinstance(obj, Tag): return obj.tag_time >= min_timestamp else: return obj.commit_time >= min_timestamp return util.OrderedDict((ref, sha) for ref, sha in refs.iteritems() if check_min_time(self.git[sha])) def update_references(self): exportable = self.get_exportable() # Create a local Git branch name for each # Mercurial bookmark. for hg_sha, refs in exportable.iteritems(): for git_ref in refs.heads: git_sha = self.map_git_get(hg_sha) if git_sha: self.git.refs[git_ref] = git_sha def export_hg_tags(self): for tag, sha in self.repo.tags().iteritems(): if self.repo.tagtype(tag) in ('global', 'git'): tag = tag.replace(' ', '_') target = self.map_git_get(hex(sha)) if target is not None: tag_refname = 'refs/tags/' + tag if(check_ref_format(tag_refname)): self.git.refs[tag_refname] = target self.tags[tag] = hex(sha) else: self.repo.ui.warn('Skipping export of tag %s because ' 'it has invalid name as a git ' 'refname.\n' % tag) else: self.repo.ui.warn( 'Skipping export of tag %s because it ' 'has no matching git revision.\n' % tag) def _filter_for_bookmarks(self, bms): if not self.branch_bookmark_suffix: return [(bm, bm) for bm in bms] else: def _filter_bm(bm): if bm.endswith(self.branch_bookmark_suffix): return bm[0:-(len(self.branch_bookmark_suffix))] else: return bm return [(_filter_bm(bm), bm) for bm in bms] def get_exportable(self): class heads_tags(object): def __init__(self): self.heads = set() self.tags = set() def __iter__(self): return itertools.chain(self.heads, self.tags) def __nonzero__(self): return bool(self.heads) or bool(self.tags) res = collections.defaultdict(heads_tags) bms = self.repo._bookmarks for filtered_bm, bm in self._filter_for_bookmarks(bms): res[hex(bms[bm])].heads.add('refs/heads/' + filtered_bm) for tag, sha in self.tags.iteritems(): res[sha].tags.add('refs/tags/' + tag) return res def import_tags(self, refs): keys = refs.keys() if not keys: return repotags = self.repo.tags() for k in keys[:]: ref_name = k parts = k.split('/') if parts[0] == 'refs' and parts[1] == 'tags': ref_name = "/".join([v for v in parts[2:]]) # refs contains all the refs in the server, not just # the ones we are pulling if refs[k] not in self.git.object_store: continue if ref_name[-3:] == '^{}': ref_name = ref_name[:-3] if ref_name not in repotags: obj = self.git.get_object(refs[k]) sha = None if isinstance(obj, Commit): # lightweight sha = self.map_hg_get(refs[k]) if sha is not None: self.tags[ref_name] = sha elif isinstance(obj, Tag): # annotated (obj_type, obj_sha) = obj.object obj = self.git.get_object(obj_sha) if isinstance(obj, Commit): sha = self.map_hg_get(obj_sha) # TODO: better handling for annotated tags if sha is not None: self.tags[ref_name] = sha self.save_tags() def update_hg_bookmarks(self, refs): try: bms = self.repo._bookmarks heads = dict([(ref[11:], refs[ref]) for ref in refs if ref.startswith('refs/heads/')]) suffix = self.branch_bookmark_suffix or '' for head, sha in heads.iteritems(): # refs contains all the refs in the server, not just # the ones we are pulling hgsha = self.map_hg_get(sha) if hgsha is None: continue hgsha = bin(hgsha) if head not in bms: # new branch bms[head + suffix] = hgsha else: bm = self.repo[bms[head]] if bm.ancestor(self.repo[hgsha]) == bm: # fast forward bms[head + suffix] = hgsha if heads: util.recordbookmarks(self.repo, bms) except AttributeError: self.ui.warn(_('creating bookmarks failed, do you have' ' bookmarks enabled?\n')) def update_remote_branches(self, remote_name, refs): remote_refs = self.remote_refs # since we re-write all refs for this remote each time, prune # all entries matching this remote from our refs list now so # that we avoid any stale refs hanging around forever for t in list(remote_refs): if t.startswith(remote_name + '/'): del remote_refs[t] for ref_name, sha in refs.iteritems(): if ref_name.startswith('refs/heads'): hgsha = self.map_hg_get(sha) if hgsha is None or hgsha not in self.repo: continue head = ref_name[11:] remote_refs['/'.join((remote_name, head))] = bin(hgsha) # TODO(durin42): what is this doing? new_ref = 'refs/remotes/%s/%s' % (remote_name, head) self.git.refs[new_ref] = sha elif (ref_name.startswith('refs/tags') and not ref_name.endswith('^{}')): self.git.refs[ref_name] = sha # UTILITY FUNCTIONS def convert_git_int_mode(self, mode): # TODO: make these into constants convert = { 0100644: '', 0100755: 'x', 0120000: 'l' } if mode in convert: return convert[mode] return '' def get_file(self, commit, f): otree = self.git.tree(commit.tree) parts = f.split('/') for part in parts: (mode, sha) = otree[part] obj = self.git.get_object(sha) if isinstance(obj, Blob): return (mode, sha, obj._text) elif isinstance(obj, Tree): otree = obj def get_files_changed(self, commit, detect_renames): tree = commit.tree btree = None if commit.parents: btree = self.git[commit.parents[0]].tree files = {} gitlinks = {} renames = None rename_detector = None if detect_renames: renames = {} rename_detector = self._rename_detector # this set is unused if rename detection isn't enabled -- that makes # the code below simpler renamed_out = set() changes = diff_tree.tree_changes(self.git.object_store, btree, tree, rename_detector=rename_detector) for change in changes: oldfile, oldmode, oldsha = change.old newfile, newmode, newsha = change.new # actions are described by the following table ('no' means 'does # not exist'): # old new | action # no file | record file # no gitlink | record gitlink # file no | delete file # file file | record file # file gitlink | delete file and record gitlink # gitlink no | delete gitlink # gitlink file | delete gitlink and record file # gitlink gitlink | record gitlink # # There's an edge case here -- symlink <-> regular file transitions # are returned by dulwich as separate deletes and adds, not # modifications. The order of those results is unspecified and # could be either way round. Handle both cases: delete first, then # add -- delete stored in 'old = file' case, then overwritten by # 'new = file' case. add first, then delete -- record stored in # 'new = file' case, then membership check fails in 'old = file' # case so is not overwritten there. This is not an issue for # gitlink <-> {symlink, regular file} transitions because they # write to separate dictionaries. # # There's a similar edge case when rename detection is enabled: if # a file is renamed and then replaced by a symlink (typically to # the new location), it is returned by dulwich as an add and a # rename. The order of those results is unspecified. Handle both # cases: rename first, then add -- delete stored in 'new = file' # case with renamed_out, then renamed_out check passes in 'old = # file' case so is overwritten. add first, then rename -- add # stored in 'old = file' case, then membership check fails in 'new # = file' case so is overwritten. if newmode == 0160000: # new = gitlink gitlinks[newfile] = newsha if change.type == diff_tree.CHANGE_RENAME: # don't record the rename because only file -> file renames # make sense in Mercurial gitlinks[oldfile] = None if oldmode is not None and oldmode != 0160000: # file -> gitlink files[oldfile] = True, None, None continue if oldmode == 0160000 and newmode != 0160000: # gitlink -> no/file (gitlink -> gitlink is covered above) gitlinks[oldfile] = None continue if newfile is not None: # new = file files[newfile] = False, newmode, newsha if renames is not None and newfile != oldfile: renames[newfile] = oldfile renamed_out.add(oldfile) # the membership check is explained in a comment above if (change.type == diff_tree.CHANGE_RENAME and oldfile not in files): files[oldfile] = True, None, None else: # old = file # files renamed_out | action # no * | write # yes no | ignore # yes yes | write if oldfile not in files or oldfile in renamed_out: files[oldfile] = True, None, None return files, gitlinks, renames @hgutil.propertycache def _rename_detector(self): # disabled by default to avoid surprises similarity = self.ui.configint('git', 'similarity', default=0) if similarity < 0 or similarity > 100: raise util.Abort(_('git.similarity must be between 0 and 100')) if similarity == 0: return None # default is borrowed from Git max_files = self.ui.configint('git', 'renamelimit', default=400) if similarity < 0: raise util.Abort(_('git.renamelimit must be non-negative')) if max_files == 0: max_files = None find_copies_harder = self.ui.configbool('git', 'findcopiesharder', default=False) return diff_tree.RenameDetector(self.git.object_store, rename_threshold=similarity, max_files=max_files, find_copies_harder=find_copies_harder) def parse_gitmodules(self, tree_obj): """Parse .gitmodules from a git tree specified by tree_obj :return: list of tuples (submodule path, url, name), where name is quoted part of the section's name, or empty list if nothing found """ rv = [] try: unused_mode, gitmodules_sha = tree_obj['.gitmodules'] except KeyError: return rv gitmodules_content = self.git[gitmodules_sha].data fo = StringIO.StringIO(gitmodules_content) tt = dul_config.ConfigFile.from_file(fo) for section in tt.keys(): section_kind, section_name = section if section_kind == 'submodule': sm_path = tt.get(section, 'path') sm_url = tt.get(section, 'url') rv.append((sm_path, sm_url, section_name)) return rv def git_file_readlines(self, tree_obj, fname): """Read content of a named entry from the git commit tree :return: list of lines """ if fname in tree_obj: unused_mode, sha = tree_obj[fname] content = self.git[sha].data return content.splitlines() return [] def remote_name(self, remote): names = [name for name, path in self.paths if path == remote] if names: return names[0] # Stolen from hgsubversion def swap_out_encoding(self, new_encoding='UTF-8'): try: from mercurial import encoding old = encoding.encoding encoding.encoding = new_encoding except (AttributeError, ImportError): old = hgutil._encoding hgutil._encoding = new_encoding return old def decode_guess(self, string, encoding): # text is not valid utf-8, try to make sense of it if encoding: try: return string.decode(encoding).encode('utf-8') except UnicodeDecodeError: pass try: return string.decode('latin-1').encode('utf-8') except UnicodeDecodeError: return string.decode('ascii', 'replace').encode('utf-8') def get_transport_and_path(self, uri): """Method that sets up the transport (either ssh or http(s)) Tests: >>> from dulwich.client import HttpGitClient, SSHGitClient >>> from mercurial.ui import ui >>> g = GitHandler('', ui()) >>> client, url = g.get_transport_and_path('http://fqdn.com/test.git') >>> print isinstance(client, HttpGitClient) True >>> print url http://fqdn.com/test.git >>> client, url = g.get_transport_and_path('git@fqdn.com:user/repo.git') >>> print isinstance(client, SSHGitClient) True >>> print url user/repo.git >>> print client.host git@fqdn.com """ # pass hg's ui.ssh config to dulwich if not issubclass(client.get_ssh_vendor, _ssh.SSHVendor): client.get_ssh_vendor = _ssh.generate_ssh_vendor(self.ui) # test for raw git ssh uri here so that we can reuse the logic below if util.isgitsshuri(uri): uri = "git+ssh://" + uri git_match = RE_GIT_URI.match(uri) if git_match: res = git_match.groupdict() transport = client.TCPGitClient if 'ssh' in res['scheme']: transport = client.SSHGitClient host, port, sepr = res['host'], res['port'], res['sepr'] path = res['path'] if sepr == '/' and not path.startswith('~'): path = '/' + path # strip trailing slash for heroku-style URLs # ssh+git://git@heroku.com:project.git/ if sepr == ':' and path.endswith('.git/'): path = path.rstrip('/') if port: client.port = port return transport(host, port=port), path if uri.startswith('git+http://') or uri.startswith('git+https://'): uri = uri[4:] if uri.startswith('http://') or uri.startswith('https://'): auth = urllib2.HTTPBasicAuthHandler(url.passwordmgr(self.ui)) opener = urllib2.build_opener(auth) ua = 'git/20x6 (hg-git ; uses dulwich and hg ; like git-core)' opener.addheaders = [('User-Agent', ua)] try: return client.HttpGitClient(uri, opener=opener), uri except TypeError as e: if e.message.find("unexpected keyword argument 'opener'") >= 0: # Dulwich 0.9.4, which is the latest version that ships # with Ubuntu 14.04, doesn't support the 'opener' keyword. # Try without authentication. return client.HttpGitClient(uri), uri else: raise # if its not git or git+ssh, try a local url.. return client.SubprocessGitClient(), uri durin42-hg-git-53d514c9c7e6/hggit/gitdirstate.py0000644000000000000000000002215312653777310017431 0ustar 00000000000000import os import stat import re import errno from mercurial import ( dirstate, match as matchmod, osutil, scmutil, util, ) try: from mercurial import ignore ignore.readpats ignoremod = True except (AttributeError, ImportError): # ignore module was removed in Mercurial 3.5 ignoremod = False # pathauditor moved to pathutil in 2.9 try: from mercurial import pathutil pathutil.pathauditor except (AttributeError, ImportError): pathutil = scmutil from mercurial.i18n import _ def gignorepats(orig, lines, root=None): '''parse lines (iterable) of .gitignore text, returning a tuple of (patterns, parse errors). These patterns should be given to compile() to be validated and converted into a match function.''' syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'} syntax = 'glob:' patterns = [] warnings = [] for line in lines: if "#" in line: _commentre = re.compile(r'((^|[^\\])(\\\\)*)#.*') # remove comments prefixed by an even number of escapes line = _commentre.sub(r'\1', line) # fixup properly escaped comments that survived the above line = line.replace("\\#", "#") line = line.rstrip() if not line: continue if line.startswith('!'): warnings.append(_("unsupported ignore pattern '%s'") % line) continue if re.match(r'(:?.*/)?\.hg(:?/|$)', line): continue rootprefix = '%s/' % root if root else '' if line.startswith('/'): line = line[1:] rootsuffixes = [''] else: rootsuffixes = ['', '**/'] for rootsuffix in rootsuffixes: pat = syntax + rootprefix + rootsuffix + line for s, rels in syntaxes.iteritems(): if line.startswith(rels): pat = line break elif line.startswith(s + ':'): pat = rels + line[len(s) + 1:] break patterns.append(pat) return patterns, warnings def gignore(root, files, warn, extrapatterns=None): allpats = [] pats = [] if ignoremod: pats = ignore.readpats(root, files, warn) for f, patlist in pats: allpats.extend(patlist) else: allpats.extend(['include:%s' % f for f in files]) if extrapatterns: allpats.extend(extrapatterns) if not allpats: return util.never try: ignorefunc = matchmod.match(root, '', [], allpats) except util.Abort: for f, patlist in pats: try: matchmod.match(root, '', [], patlist) except util.Abort, inst: raise util.Abort('%s: %s' % (f, inst[0])) if extrapatterns: try: matchmod.match(root, '', [], extrapatterns) except util.Abort, inst: raise util.Abort('%s: %s' % ('extra patterns', inst[0])) return ignorefunc class gitdirstate(dirstate.dirstate): @dirstate.rootcache('.hgignore') def _ignore(self): files = [self._join('.hgignore')] for name, path in self._ui.configitems("ui"): if name == 'ignore' or name.startswith('ignore.'): files.append(util.expandpath(path)) patterns = [] # Only use .gitignore if there's no .hgignore try: fp = open(files[0]) fp.close() except: fns = self._finddotgitignores() for fn in fns: d = os.path.dirname(fn) fn = self.pathto(fn) if not os.path.exists(fn): continue fp = open(fn) pats, warnings = gignorepats(None, fp, root=d) for warning in warnings: self._ui.warn("%s: %s\n" % (fn, warning)) patterns.extend(pats) return gignore(self._root, files, self._ui.warn, extrapatterns=patterns) def _finddotgitignores(self): """A copy of dirstate.walk. This is called from the new _ignore method, which is called by dirstate.walk, which would cause infinite recursion, except _finddotgitignores calls the superclass _ignore directly.""" match = matchmod.match(self._root, self.getcwd(), ['relglob:.gitignore']) # TODO: need subrepos? subrepos = [] unknown = True ignored = False def fwarn(f, msg): self._ui.warn('%s: %s\n' % (self.pathto(f), msg)) return False ignore = super(gitdirstate, self)._ignore dirignore = self._dirignore if ignored: ignore = util.never dirignore = util.never elif not unknown: # if unknown and ignored are False, skip step 2 ignore = util.always dirignore = util.always matchfn = match.matchfn matchalways = match.always() matchtdir = match.traversedir dmap = self._map listdir = osutil.listdir lstat = os.lstat dirkind = stat.S_IFDIR regkind = stat.S_IFREG lnkkind = stat.S_IFLNK join = self._join exact = skipstep3 = False if matchfn == match.exact: # match.exact exact = True dirignore = util.always # skip step 2 elif match.files() and not match.anypats(): # match.match, no patterns skipstep3 = True if not exact and self._checkcase: normalize = self._normalize skipstep3 = False else: normalize = None # step 1: find all explicit files results, work, dirsnotfound = self._walkexplicit(match, subrepos) skipstep3 = skipstep3 and not (work or dirsnotfound) if work and isinstance(work[0], tuple): # Mercurial >= 3.3.3 work = [nd for nd, d in work if not dirignore(d)] else: work = [d for d in work if not dirignore(d)] wadd = work.append # step 2: visit subdirectories while work: nd = work.pop() skip = None if nd == '.': nd = '' else: skip = '.hg' try: entries = listdir(join(nd), stat=True, skip=skip) except OSError, inst: if inst.errno in (errno.EACCES, errno.ENOENT): fwarn(nd, inst.strerror) continue raise for f, kind, st in entries: if normalize: nf = normalize(nd and (nd + "/" + f) or f, True, True) else: nf = nd and (nd + "/" + f) or f if nf not in results: if kind == dirkind: if not ignore(nf): if matchtdir: matchtdir(nf) wadd(nf) if nf in dmap and (matchalways or matchfn(nf)): results[nf] = None elif kind == regkind or kind == lnkkind: if nf in dmap: if matchalways or matchfn(nf): results[nf] = st elif (matchalways or matchfn(nf)) and not ignore(nf): results[nf] = st elif nf in dmap and (matchalways or matchfn(nf)): results[nf] = None for s in subrepos: del results[s] del results['.hg'] # step 3: report unseen items in the dmap hash if not skipstep3 and not exact: if not results and matchalways: visit = dmap.keys() else: visit = [f for f in dmap if f not in results and matchfn(f)] visit.sort() if unknown: # unknown == True means we walked the full directory tree # above. So if a file is not seen it was either a) not matching # matchfn b) ignored, c) missing, or d) under a symlink # directory. audit_path = pathutil.pathauditor(self._root) for nf in iter(visit): # Report ignored items in the dmap as long as they are not # under a symlink directory. if audit_path.check(nf): try: results[nf] = lstat(join(nf)) except OSError: # file doesn't exist results[nf] = None else: # It's either missing or under a symlink directory results[nf] = None else: # We may not have walked the full directory tree above, # so stat everything we missed. nf = iter(visit).next for st in util.statfiles([join(i) for i in visit]): results[nf()] = st return results.keys() durin42-hg-git-53d514c9c7e6/hggit/gitrepo.py0000644000000000000000000000306612653777310016561 0ustar 00000000000000from util import isgitsshuri from mercurial import util from mercurial.error import RepoError from mercurial.peer import peerrepository class gitrepo(peerrepository): capabilities = ['lookup'] def _capabilities(self): return self.capabilities def __init__(self, ui, path, create): if create: # pragma: no cover raise util.Abort('Cannot create a git repository.') self.ui = ui self.path = path self.localrepo = None def url(self): return self.path def lookup(self, key): if isinstance(key, str): return key def local(self): if not self.path: raise RepoError def heads(self): return [] def listkeys(self, namespace): if namespace == 'namespaces': return {'bookmarks': ''} elif namespace == 'bookmarks': if self.localrepo is not None: handler = self.localrepo.githandler refs = handler.fetch_pack(self.path, heads=[]) # map any git shas that exist in hg to hg shas stripped_refs = dict([ (ref[11:], handler.map_hg_get(refs[ref]) or refs[ref]) for ref in refs.keys() if ref.startswith('refs/heads/') ]) return stripped_refs return {} def pushkey(self, namespace, key, old, new): return False instance = gitrepo def islocal(path): if isgitsshuri(path): return True u = util.url(path) return not u.scheme or u.scheme == 'file' durin42-hg-git-53d514c9c7e6/hggit/help/git.rst0000644000000000000000000000540412653777310017001 0ustar 00000000000000Basic Use --------- You can clone a Git repository from Hg by running `hg clone [dest]`. For example, if you were to run:: $ hg clone git://github.com/schacon/hg-git.git Hg-Git would clone the repository and convert it to an Hg repository for you. There are a number of different protocols that can be used for Git repositories. Examples of Git repository URLs include:: https://github.com/schacon/hg-git.git http://code.google.com/p/guava-libraries ssh://git@github.com:schacon/hg-git.git git://github.com/schacon/hg-git.git ../hg-git (local file path) For the HTTP, HTTPS, and SSH protocols, it isn't clear based solely on the URL whether the remote repository should be treated as a Mercurial repository or a Git repository. Thus, to specify that a URL should use Git, prepend the URL with "git+". For example, an HTTPS URL would start with "git+https://". Also, note that Git doesn't require the specification of the protocol for SSH, but Mercurial does. Hg-Git automatically detects whether file paths should be treated as Git repositories by their contents. If you are starting from an existing Hg repository, you have to set up a Git repository somewhere that you have push access to, add a path entry for it in your .hg/hgrc file, and then run `hg push [name]` from within your repository. For example:: $ cd hg-git # (an Hg repository) $ # edit .hg/hgrc and add the target Git URL in the paths section $ hg push This will convert all your Hg data into Git objects and push them to the Git server. Pulling new revisions into a repository is the same as from any other Mercurial source. Within the earlier examples, the following commands are all equivalent:: $ hg pull $ hg pull default $ hg pull git://github.com/schacon/hg-git.git Git branches are exposed in Hg as bookmarks, while Git remotes are exposed as Hg local tags. See `hg help bookmarks` and `hg help tags` for further information. Finding and displaying Git revisions ------------------------------------ For displaying the Git revision ID, Hg-Git provides a template keyword: :gitnode: String. The Git changeset identification hash, as a 40 hexadecimal digit string. For example:: $ hg log --template='{rev}:{node|short}:{gitnode|short} {desc}\n' $ hg log --template='hg: {node}\ngit: {gitnode}\n{date|isodate} {author}\n{desc}\n\n' For finding changesets from Git, Hg-Git extends revsets to provide two new selectors: :fromgit: Select changesets that originate from Git. Takes no arguments. :gitnode: Select changesets that originate in a specific Git revision. Takes a revision argument. For example:: $ hg log -r 'fromgit()' $ hg log -r 'gitnode(84f75b909fc3)' Revsets are accepted by several Mercurial commands for specifying revisions. See ``hg help revsets`` for details. durin42-hg-git-53d514c9c7e6/hggit/hg2git.py0000644000000000000000000003762612653777310016305 0ustar 00000000000000# This file contains code dealing specifically with converting Mercurial # repositories to Git repositories. Code in this file is meant to be a generic # library and should be usable outside the context of hg-git or an hg command. import os import stat import dulwich.objects as dulobjs from mercurial import ( util as hgutil, ) import compat import util def parse_subrepos(ctx): sub = util.OrderedDict() if '.hgsub' in ctx: sub = util.parse_hgsub(ctx['.hgsub'].data().splitlines()) substate = util.OrderedDict() if '.hgsubstate' in ctx: substate = util.parse_hgsubstate( ctx['.hgsubstate'].data().splitlines()) return sub, substate def audit_git_path(ui, path): r"""Check for path components that case-fold to .git. >>> class fakeui(object): ... def configbool(*args): ... return False ... def warn(self, s): ... print s >>> u = fakeui() >>> audit_git_path(u, 'foo/git~100/wat') warning: path 'foo/git~100/wat' contains a potentially dangerous path component. It may not be legal to check out in Git. It may also be rejected by some git server configurations. >>> audit_git_path(u, u'foo/.gi\u200ct'.encode('utf-8')) warning: path 'foo/.gi\xe2\x80\x8ct' contains a potentially dangerous path component. It may not be legal to check out in Git. It may also be rejected by some git server configurations. >>> audit_git_path(u, 'this/is/safe') """ dangerous = False for c in path.split(os.path.sep): if compat.hfsignoreclean(c) == '.git': dangerous = True break elif '~' in c: base, tail = c.split('~', 1) if tail.isdigit() and base.upper().startswith('GIT'): dangerous = True break if dangerous: if ui.configbool('git', 'blockdotgit', True): raise hgutil.Abort( ('Refusing to export likely-dangerous path %r' % path), hint=("If you need to continue, read about CVE-2014-9390 and " "then set '[git] blockdotgit = false' in your hgrc.")) ui.warn('warning: path %r contains a potentially dangerous path ' 'component.\n' 'It may not be legal to check out in Git.\n' 'It may also be rejected by some git server configurations.\n' % path) class IncrementalChangesetExporter(object): """Incrementally export Mercurial changesets to Git trees. The purpose of this class is to facilitate Git tree export that is more optimal than brute force. A "dumb" implementations of Mercurial to Git export would iterate over every file present in a Mercurial changeset and would convert each to a Git blob and then conditionally add it to a Git repository if it didn't yet exist. This is suboptimal because the overhead associated with obtaining every file's raw content and converting it to a Git blob is not trivial! This class works around the suboptimality of brute force export by leveraging the information stored in Mercurial - the knowledge of what changed between changesets - to only export Git objects corresponding to changes in Mercurial. In the context of converting Mercurial repositories to Git repositories, we only export objects Git (possibly) hasn't seen yet. This prevents a lot of redundant work and is thus faster. Callers instantiate an instance of this class against a mercurial.localrepo instance. They then associate it with a specific changesets by calling update_changeset(). On each call to update_changeset(), the instance computes the difference between the current and new changesets and emits Git objects that haven't yet been encountered during the lifetime of the class instance. In other words, it expresses Mercurial changeset deltas in terms of Git objects. Callers then (usually) take this set of Git objects and add them to the Git repository. This class only emits Git blobs and trees, not commits. The tree calculation part of this class is essentially a reimplementation of dulwich.index.commit_tree. However, since our implementation reuses Tree instances and only recalculates SHA-1 when things change, we are more efficient. """ def __init__(self, hg_repo, start_ctx, git_store, git_commit): """Create an instance against a mercurial.localrepo. start_ctx: the context for a Mercurial commit that has a Git equivalent, passed in as git_commit. The incremental computation will be started from this commit. git_store: the Git object store the commit comes from. start_ctx can be repo[nullid], in which case git_commit should be None. """ self._hg = hg_repo # Our current revision's context. self._ctx = start_ctx # Path to dulwich.objects.Tree. self._init_dirs(git_store, git_commit) # Mercurial file nodeid to Git blob SHA-1. Used to prevent redundant # blob calculation. self._blob_cache = {} def _init_dirs(self, store, commit): """Initialize self._dirs for a Git object store and commit.""" self._dirs = {} if commit is None: return dirkind = stat.S_IFDIR # depth-first order, chosen arbitrarily todo = [('', store[commit.tree])] while todo: path, tree = todo.pop() self._dirs[path] = tree for entry in tree.iteritems(): if entry.mode == dirkind: if path == '': newpath = entry.path else: newpath = path + '/' + entry.path todo.append((newpath, store[entry.sha])) @property def root_tree_sha(self): """The SHA-1 of the root Git tree. This is needed to construct a Git commit object. """ return self._dirs[''].id def update_changeset(self, newctx): """Set the tree to track a new Mercurial changeset. This is a generator of 2-tuples. The first item in each tuple is a dulwich object, either a Blob or a Tree. The second item is the corresponding Mercurial nodeid for the item, if any. Only blobs will have nodeids. Trees do not correspond to a specific nodeid, so it does not make sense to emit a nodeid for them. When exporting trees from Mercurial, callers typically write the returned dulwich object to the Git repo via the store's add_object(). Some emitted objects may already exist in the Git repository. This class does not know about the Git repository, so it's up to the caller to conditionally add the object, etc. Emitted objects are those that have changed since the last call to update_changeset. If this is the first call to update_chanageset, all objects in the tree are emitted. """ # Our general strategy is to accumulate dulwich.objects.Blob and # dulwich.objects.Tree instances for the current Mercurial changeset. # We do this incremental by iterating over the Mercurial-reported # changeset delta. We rely on the behavior of Mercurial to lazy # calculate a Tree's SHA-1 when we modify it. This is critical to # performance. # In theory we should be able to look at changectx.files(). This is # *much* faster. However, it may not be accurate, especially with older # repositories, which may not record things like deleted files # explicitly in the manifest (which is where files() gets its data). # The only reliable way to get the full set of changes is by looking at # the full manifest. And, the easy way to compare two manifests is # localrepo.status(). modified, added, removed = self._hg.status(self._ctx, newctx)[0:3] # We track which directories/trees have modified in this update and we # only export those. dirty_trees = set() subadded, subremoved = [], [] for s in modified, added, removed: if '.hgsub' in s or '.hgsubstate' in s: subadded, subremoved = self._handle_subrepos(newctx) break # We first process subrepo and file removals so we can prune dead # trees. for path in subremoved: self._remove_path(path, dirty_trees) for path in removed: if path == '.hgsubstate' or path == '.hgsub': continue self._remove_path(path, dirty_trees) for path, sha in subadded: d = os.path.dirname(path) tree = self._dirs.setdefault(d, dulobjs.Tree()) dirty_trees.add(d) tree.add(os.path.basename(path), dulobjs.S_IFGITLINK, sha) # For every file that changed or was added, we need to calculate the # corresponding Git blob and its tree entry. We emit the blob # immediately and update trees to be aware of its presence. for path in set(modified) | set(added): audit_git_path(self._hg.ui, path) if path == '.hgsubstate' or path == '.hgsub': continue d = os.path.dirname(path) tree = self._dirs.setdefault(d, dulobjs.Tree()) dirty_trees.add(d) fctx = newctx[path] func = IncrementalChangesetExporter.tree_entry entry, blob = func(fctx, self._blob_cache) if blob is not None: yield (blob, fctx.filenode()) tree.add(*entry) # Now that all the trees represent the current changeset, recalculate # the tree IDs and emit them. Note that we wait until now to calculate # tree SHA-1s. This is an important difference between us and # dulwich.index.commit_tree(), which builds new Tree instances for each # series of blobs. for obj in self._populate_tree_entries(dirty_trees): yield (obj, None) self._ctx = newctx def _remove_path(self, path, dirty_trees): """Remove a path (file or git link) from the current changeset. If the tree containing this path is empty, it might be removed.""" d = os.path.dirname(path) tree = self._dirs.get(d, dulobjs.Tree()) del tree[os.path.basename(path)] dirty_trees.add(d) # If removing this file made the tree empty, we should delete this # tree. This could result in parent trees losing their only child # and so on. if not len(tree): self._remove_tree(d) else: self._dirs[d] = tree def _remove_tree(self, path): """Remove a (presumably empty) tree from the current changeset. A now-empty tree may be the only child of its parent. So, we traverse up the chain to the root tree, deleting any empty trees along the way. """ try: del self._dirs[path] except KeyError: return # Now we traverse up to the parent and delete any references. if path == '': return basename = os.path.basename(path) parent = os.path.dirname(path) while True: tree = self._dirs.get(parent, None) # No parent entry. Nothing to remove or update. if tree is None: return try: del tree[basename] except KeyError: return if len(tree): return # The parent tree is empty. Se, we can delete it. del self._dirs[parent] if parent == '': return basename = os.path.basename(parent) parent = os.path.dirname(parent) def _populate_tree_entries(self, dirty_trees): self._dirs.setdefault('', dulobjs.Tree()) # Fill in missing directories. for path in self._dirs.keys(): parent = os.path.dirname(path) while parent != '': parent_tree = self._dirs.get(parent, None) if parent_tree is not None: break self._dirs[parent] = dulobjs.Tree() parent = os.path.dirname(parent) for dirty in list(dirty_trees): parent = os.path.dirname(dirty) while parent != '': if parent in dirty_trees: break dirty_trees.add(parent) parent = os.path.dirname(parent) # The root tree is always dirty but doesn't always get updated. dirty_trees.add('') # We only need to recalculate and export dirty trees. for d in sorted(dirty_trees, key=len, reverse=True): # Only happens for deleted directories. try: tree = self._dirs[d] except KeyError: continue yield tree if d == '': continue parent_tree = self._dirs[os.path.dirname(d)] # Accessing the tree's ID is what triggers SHA-1 calculation and is # the expensive part (at least if the tree has been modified since # the last time we retrieved its ID). Also, assigning an entry to a # tree (even if it already exists) invalidates the existing tree # and incurs SHA-1 recalculation. So, it's in our interest to avoid # invalidating trees. Since we only update the entries of dirty # trees, this should hold true. parent_tree[os.path.basename(d)] = (stat.S_IFDIR, tree.id) def _handle_subrepos(self, newctx): sub, substate = parse_subrepos(self._ctx) newsub, newsubstate = parse_subrepos(newctx) # For each path, the logic is described by the following table. 'no' # stands for 'the subrepo doesn't exist', 'git' stands for 'git # subrepo', and 'hg' stands for 'hg or other subrepo'. # # old new | action # * git | link (1) # git hg | delete (2) # git no | delete (3) # # All other combinations are 'do nothing'. # # git links without corresponding submodule paths are stored as # subrepos with a substate but without an entry in .hgsub. # 'added' is both modified and added added, removed = [], [] def isgit(sub, path): return path not in sub or sub[path].startswith('[git]') for path, sha in substate.iteritems(): if not isgit(sub, path): # old = hg -- will be handled in next loop continue # old = git if path not in newsubstate or not isgit(newsub, path): # new = hg or no, case (2) or (3) removed.append(path) for path, sha in newsubstate.iteritems(): if not isgit(newsub, path): # new = hg or no; the only cases we care about are handled # above continue # case (1) added.append((path, sha)) return added, removed @staticmethod def tree_entry(fctx, blob_cache): """Compute a dulwich TreeEntry from a filectx. A side effect is the TreeEntry is stored in the passed cache. Returns a 2-tuple of (dulwich.objects.TreeEntry, dulwich.objects.Blob). """ blob_id = blob_cache.get(fctx.filenode(), None) blob = None if blob_id is None: blob = dulobjs.Blob.from_string(fctx.data()) blob_id = blob.id blob_cache[fctx.filenode()] = blob_id flags = fctx.flags() if 'l' in flags: mode = 0120000 elif 'x' in flags: mode = 0100755 else: mode = 0100644 return (dulobjs.TreeEntry(os.path.basename(fctx.path()), mode, blob_id), blob) durin42-hg-git-53d514c9c7e6/hggit/hgrepo.py0000644000000000000000000000606212653777310016373 0ustar 00000000000000from mercurial import ( util as hgutil, localrepo, ) from mercurial.node import bin from git_handler import GitHandler from gitrepo import gitrepo import util def generate_repo_subclass(baseclass): class hgrepo(baseclass): if hgutil.safehasattr(localrepo.localrepository, 'pull'): # Mercurial < 3.2 @util.transform_notgit def pull(self, remote, heads=None, force=False): if isinstance(remote, gitrepo): return self.githandler.fetch(remote.path, heads) else: # pragma: no cover return super(hgrepo, self).pull(remote, heads, force) if hgutil.safehasattr(localrepo.localrepository, 'push'): # Mercurial < 3.2 # TODO figure out something useful to do with the newbranch param @util.transform_notgit def push(self, remote, force=False, revs=None, newbranch=False): if isinstance(remote, gitrepo): return self.githandler.push(remote.path, revs, force) else: # pragma: no cover return super(hgrepo, self).push(remote, force, revs, newbranch) @util.transform_notgit def findoutgoing(self, remote, base=None, heads=None, force=False): if isinstance(remote, gitrepo): base, heads = self.githandler.get_refs(remote.path) out, h = super(hgrepo, self).findoutgoing(remote, base, heads, force) return out else: # pragma: no cover return super(hgrepo, self).findoutgoing(remote, base, heads, force) def _findtags(self): (tags, tagtypes) = super(hgrepo, self)._findtags() for tag, rev in self.githandler.tags.iteritems(): if isinstance(tag, unicode): tag = tag.encode('utf-8') tags[tag] = bin(rev) tagtypes[tag] = 'git' for tag, rev in self.githandler.remote_refs.iteritems(): if isinstance(tag, unicode): tag = tag.encode('utf-8') tags[tag] = rev tagtypes[tag] = 'git-remote' tags.update(self.githandler.remote_refs) return (tags, tagtypes) @hgutil.propertycache def githandler(self): '''get the GitHandler for an hg repo This only makes sense if the repo talks to at least one git remote. ''' return GitHandler(self, self.ui) def tags(self): # TODO consider using self._tagscache tagscache = super(hgrepo, self).tags() tagscache.update(self.githandler.remote_refs) for tag, rev in self.githandler.tags.iteritems(): if tag in tagscache: continue tagscache[tag] = bin(rev) return tagscache return hgrepo durin42-hg-git-53d514c9c7e6/hggit/overlay.py0000644000000000000000000002616712653777310016600 0ustar 00000000000000# overlay classes for repositories # unifies access to unimported git objects and committed hg objects # designed to support incoming # # incomplete, implemented on demand from mercurial import ( ancestor, manifest, context, ) from mercurial.node import bin, hex, nullid def _maybehex(n): if len(n) == 20: return hex(n) return n class overlaymanifest(object): def __init__(self, repo, sha): self.repo = repo self.tree = repo.handler.git.get_object(sha) self._map = None self._flags = None def withflags(self): self.load() return set([path for path, flag in self._flags.iteritems() if flag != '']) def copy(self): return overlaymanifest(self.repo, self.tree.id) def keys(self): self.load() return self._map.keys() def iterkeys(self): return iter(self.keys()) def load(self): if self._map is not None: return self._map = {} self._flags = {} def hgflag(gitflag): if gitflag & 0100: return 'x' elif gitflag & 020000: return 'l' else: return '' def addtree(tree, dirname): for entry in tree.iteritems(): if entry.mode & 040000: # expand directory subtree = self.repo.handler.git.get_object(entry.sha) addtree(subtree, dirname + entry.path + '/') else: path = dirname + entry.path self._map[path] = bin(entry.sha) self._flags[path] = hgflag(entry.mode) addtree(self.tree, '') def matches(self, match): '''generate a new manifest filtered by the match argument''' if match.always(): return self.copy() mf = self.copy() for fn in mf.keys(): if not match(fn): del mf[fn] return mf def iteritems(self): self.load() return self._map.iteritems() def __iter__(self): self.load() return self._map.__iter__() def __getitem__(self, path): self.load() return self._map[path] def __contains__(self, path): self.load() return path in self._map def get(self, path, default=None): self.load() return self._map.get(path, default) def diff(self, m2, clean=False): self.load() if isinstance(m2, overlaymanifest): m2.load() # below code copied from manifest.py:manifestdict.diff diff = {} try: m2flagget = m2.flags except AttributeError: # Mercurial <= 3.3 m2flagget = m2._flags.get for fn, n1 in self.iteritems(): fl1 = self._flags.get(fn, '') n2 = m2.get(fn, None) fl2 = m2flagget(fn, '') if n2 is None: fl2 = '' if n1 != n2 or fl1 != fl2: diff[fn] = ((n1, fl1), (n2, fl2)) elif clean: diff[fn] = None for fn, n2 in m2.iteritems(): if fn not in self: fl2 = m2flagget(fn, '') diff[fn] = ((None, ''), (n2, fl2)) return diff def __delitem__(self, path): del self._map[path] def wrapmanifestdictdiff(orig, self, m2, clean=False): '''avoid calling into lazymanifest code if m2 is an overlaymanifest''' if isinstance(m2, overlaymanifest): diff = m2.diff(self, clean=clean) # since we calculated the diff with m2 vs m1, flip it around for fn in diff: c1, c2 = diff[fn] diff[fn] = c2, c1 return diff else: return orig(self, m2, clean=clean) class overlayfilectx(object): def __init__(self, repo, path, fileid=None): self.repo = repo self._path = path self.fileid = fileid # this is a hack to skip copy detection def ancestors(self): return [self, self] def filenode(self): return nullid def rev(self): return -1 def path(self): return self._path def filelog(self): return self.fileid def data(self): blob = self.repo.handler.git.get_object(_maybehex(self.fileid)) return blob.data class overlaychangectx(context.changectx): def __init__(self, repo, sha): self.repo = repo if not isinstance(sha, basestring): sha = sha.hex() self.commit = repo.handler.git.get_object(_maybehex(sha)) self._overlay = getattr(repo, 'gitoverlay', repo) self._rev = self._overlay.rev(bin(self.commit.id)) def node(self): return bin(self.commit.id) def rev(self): return self._rev def date(self): return self.commit.author_time, self.commit.author_timezone def branch(self): return 'default' def user(self): return self.commit.author def files(self): return [] def extra(self): return {} def description(self): return self.commit.message def parents(self): cl = self.repo.changelog parents = cl.parents(cl.node(self._rev)) if not parents: return [self.repo['null']] if parents[1] == nullid: parents = parents[:-1] return [self.repo[sha] for sha in parents] def manifestnode(self): return bin(self.commit.tree) def hex(self): return self.commit.id def tags(self): return [] def bookmarks(self): return [] def manifest(self): return overlaymanifest(self._overlay, self.commit.tree) def filectx(self, path, filelog=None): mf = self.manifest() return overlayfilectx(self._overlay, path, mf[path]) def flags(self, path): mf = self.manifest() return mf.flags(path) def __nonzero__(self): return True def phase(self): try: from mercurial import phases return phases.draft except (AttributeError, ImportError): return 1 def totuple(self): return (self.commit.tree, self.user(), self.date(), self.files(), self.description(), self.extra()) class overlayrevlog(object): def __init__(self, repo, base): self.repo = repo self.base = base def parents(self, n): gitrev = self.repo.revmap.get(n) if gitrev is None: # we've reached a revision we have return self.base.parents(n) commit = self.repo.handler.git.get_object(_maybehex(n)) if not commit.parents: return [nullid, nullid] def gitorhg(n): hn = self.repo.handler.map_hg_get(hex(n)) if hn is not None: return bin(hn) return n # currently ignores the octopus p1 = gitorhg(bin(commit.parents[0])) if len(commit.parents) > 1: p2 = gitorhg(bin(commit.parents[1])) else: p2 = nullid return [p1, p2] def ancestor(self, a, b): anode = self.repo.nodemap.get(a) bnode = self.repo.nodemap.get(b) if anode is None and bnode is None: return self.base.ancestor(a, b) ancs = ancestor.ancestors(self.parentrevs, a, b) if ancs: return min(map(self.node, ancs)) return nullid def parentrevs(self, rev): return [self.rev(p) for p in self.parents(self.node(rev))] def node(self, rev): gitnode = self.repo.nodemap.get(rev) if gitnode is None: return self.base.node(rev) return gitnode def rev(self, n): gitrev = self.repo.revmap.get(n) if gitrev is None: return self.base.rev(n) return gitrev def __len__(self): return len(self.repo.handler.repo) + len(self.repo.revmap) class overlaymanifestlog(overlayrevlog): def read(self, sha): if sha == nullid: return manifest.manifestdict() return overlaymanifest(self.repo, sha) class overlaychangelog(overlayrevlog): def read(self, sha): if isinstance(sha, int): sha = self.node(sha) if sha == nullid: return (nullid, "", (0, 0), [], "", {}) try: return self.base.read(sha) except LookupError: return overlaychangectx(self.repo, sha).totuple() class overlayrepo(object): def __init__(self, handler, commits, refs): self.handler = handler self.changelog = overlaychangelog(self, handler.repo.changelog) self.manifest = overlaymanifestlog(self, handler.repo.manifest) # for incoming -p self.root = handler.repo.root self.getcwd = handler.repo.getcwd # self.status = handler.repo.status self.ui = handler.repo.ui self.revmap = None self.nodemap = None self.refmap = None self.tagmap = None self._makemaps(commits, refs) try: # Mercurial >= 3.3 from mercurial import namespaces self.names = namespaces.namespaces() except (AttributeError, ImportError): pass def __getitem__(self, n): if n not in self.revmap: return self.handler.repo[n] return overlaychangectx(self, n) def _handlerhack(self, method, *args, **kwargs): nothing = object() r = self.handler.repo oldhandler = getattr(r, 'handler', nothing) oldoverlay = getattr(r, 'gitoverlay', nothing) r.handler = self.handler r.gitoverlay = self try: return getattr(r, method)(*args, **kwargs) finally: if oldhandler is nothing: del r.handler else: r.handler = oldhandler if oldoverlay is nothing: del r.gitoverlay else: r.gitoverlay = oldoverlay def status(self, *args, **kwargs): return self._handlerhack('status', *args, **kwargs) def node(self, n): """Returns an Hg or Git hash for the specified Git hash""" if bin(n) in self.revmap: return n return self.handler.map_hg_get(n) def nodebookmarks(self, n): return self.refmap.get(n, []) def nodetags(self, n): return self.tagmap.get(n, []) def rev(self, n): return self.revmap[n] def filectx(self, path, fileid=None): return overlayfilectx(self, path, fileid=fileid) def _makemaps(self, commits, refs): baserev = self.handler.repo['tip'].rev() self.revmap = {} self.nodemap = {} for i, n in enumerate(commits): rev = baserev + i + 1 self.revmap[n] = rev self.nodemap[rev] = n self.refmap = {} self.tagmap = {} for ref in refs: if ref.startswith('refs/heads/'): refname = ref[11:] self.refmap.setdefault(bin(refs[ref]), []).append(refname) elif ref.startswith('refs/tags/'): tagname = ref[10:] self.tagmap.setdefault(bin(refs[ref]), []).append(tagname) durin42-hg-git-53d514c9c7e6/hggit/util.py0000644000000000000000000000642012653777310016062 0ustar 00000000000000"""Compatibility functions for old Mercurial versions and other utility functions.""" import re try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict from dulwich import errors from mercurial import ( lock as lockmod, util as hgutil, ) gitschemes = ('git', 'git+ssh', 'git+http', 'git+https') def parse_hgsub(lines): """Fills OrderedDict with hgsub file content passed as list of lines""" rv = OrderedDict() for l in lines: ls = l.strip() if not ls or ls[0] == '#': continue name, value = l.split('=', 1) rv[name.strip()] = value.strip() return rv def serialize_hgsub(data): """Produces a string from OrderedDict hgsub content""" return ''.join(['%s = %s\n' % (n, v) for n, v in data.iteritems()]) def parse_hgsubstate(lines): """Fills OrderedDict with hgsubtate file content passed as list of lines""" rv = OrderedDict() for l in lines: ls = l.strip() if not ls or ls[0] == '#': continue value, name = l.split(' ', 1) rv[name.strip()] = value.strip() return rv def serialize_hgsubstate(data): """Produces a string from OrderedDict hgsubstate content""" return ''.join(['%s %s\n' % (data[n], n) for n in sorted(data)]) def transform_notgit(f): '''use as a decorator around functions that call into dulwich''' def inner(*args, **kwargs): try: return f(*args, **kwargs) except errors.NotGitRepository: raise hgutil.Abort('not a git repository') return inner def isgitsshuri(uri): """Method that returns True if a uri looks like git-style uri Tests: >>> print isgitsshuri('http://fqdn.com/hg') False >>> print isgitsshuri('http://fqdn.com/test.git') False >>> print isgitsshuri('git@github.com:user/repo.git') True >>> print isgitsshuri('github-123.com:user/repo.git') True >>> print isgitsshuri('git@127.0.0.1:repo.git') True >>> print isgitsshuri('git@[2001:db8::1]:repository.git') True """ for scheme in gitschemes: if uri.startswith('%s://' % scheme): return False if uri.startswith('http:') or uri.startswith('https:'): return False m = re.match(r'(?:.+@)*([\[]?[\w\d\.\:\-]+[\]]?):(.*)', uri) if m: # here we're being fairly conservative about what we consider to be git # urls giturl, repopath = m.groups() # definitely a git repo if repopath.endswith('.git'): return True # use a simple regex to check if it is a fqdn regex fqdn_re = (r'(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{1,63}' r'(?=1.1'] from os.path import dirname, join def get_version(relpath): root = dirname(__file__) for line in open(join(root, relpath), 'rb'): line = line.decode('utf-8') if '__version__' in line: return line.split("'")[1] setup( name='hg-git', version=get_version('hggit/__init__.py'), author='The hg-git Authors', maintainer='Augie Fackler', maintainer_email='durin42@gmail.com', url='http://hg-git.github.com/', description='push to and pull from a Git repository using Mercurial', long_description=""" This extension lets you communicate (push and pull) with a Git server. This way you can use Git hosting for your project or collaborate with a project that is in Git. A bridger of worlds, this plugin be. """.strip(), keywords='hg git mercurial', license='GPLv2', packages=['hggit'], package_data={ 'hggit': ['help/git.rst'] }, include_package_data=True, install_requires=['dulwich>=0.9.7'] + extra_req, ) durin42-hg-git-53d514c9c7e6/tests/commitextra.py0000644000000000000000000000142312653777310017477 0ustar 00000000000000'''test helper extension to create commits with multiple extra fields''' from mercurial import cmdutil, commands, scmutil cmdtable = {} command = cmdutil.command(cmdtable) testedwith = 'internal' @command('commitextra', [('', 'field', [], 'extra data to store', 'FIELD=VALUE'), ] + commands.commitopts + commands.commitopts2, 'commitextra') def commitextra(ui, repo, *pats, **opts): '''make a commit with extra fields''' fields = opts.get('field') extras = {} for field in fields: k, v = field.split('=', 1) extras[k] = v message = cmdutil.logmessage(ui, opts) repo.commit(message, opts.get('user'), opts.get('date'), match=scmutil.match(repo[None], pats, opts), extra=extras) return 0 durin42-hg-git-53d514c9c7e6/tests/heredoctest.py0000644000000000000000000000076412653777310017463 0ustar 00000000000000import sys globalvars = {} localvars = {} lines = sys.stdin.readlines() while lines: l = lines.pop(0) if l.startswith('SALT'): print l[:-1] elif l.startswith('>>> '): snippet = l[4:] while lines and lines[0].startswith('... '): l = lines.pop(0) snippet += "\n" + l[4:] c = compile(snippet, '', 'single') try: exec c in globalvars, localvars except Exception, inst: print repr(inst) durin42-hg-git-53d514c9c7e6/tests/hghave0000755000000000000000000000405712653777310015767 0ustar 00000000000000#!/usr/bin/env python """Test the running system for features availability. Exit with zero if all features are there, non-zero otherwise. If a feature name is prefixed with "no-", the absence of feature is tested. """ import optparse import sys import hghave checks = hghave.checks def list_features(): for name, feature in checks.iteritems(): desc = feature[1] print name + ':', desc def test_features(): failed = 0 for name, feature in checks.iteritems(): check, _ = feature try: check() except Exception, e: print "feature %s failed: %s" % (name, e) failed += 1 return failed parser = optparse.OptionParser("%prog [options] [features]") parser.add_option("--test-features", action="store_true", help="test available features") parser.add_option("--list-features", action="store_true", help="list available features") parser.add_option("-q", "--quiet", action="store_true", help="check features silently") if __name__ == '__main__': options, args = parser.parse_args() if options.list_features: list_features() sys.exit(0) if options.test_features: sys.exit(test_features()) quiet = options.quiet failures = 0 def error(msg): global failures if not quiet: sys.stderr.write(msg + '\n') failures += 1 for feature in args: negate = feature.startswith('no-') if negate: feature = feature[3:] if feature not in checks: error('skipped: unknown feature: ' + feature) continue check, desc = checks[feature] try: available = check() except Exception, e: error('hghave check failed: ' + feature) continue if not negate and not available: error('skipped: missing feature: ' + desc) elif negate and available: error('skipped: system supports %s' % desc) if failures != 0: sys.exit(1) durin42-hg-git-53d514c9c7e6/tests/hghave.py0000755000000000000000000002130012653777310016404 0ustar 00000000000000import os, stat, socket import re import sys import tempfile tempprefix = 'hg-hghave-' def matchoutput(cmd, regexp, ignorestatus=False): """Return True if cmd executes successfully and its output is matched by the supplied regular expression. """ r = re.compile(regexp) fh = os.popen(cmd) s = fh.read() try: ret = fh.close() except IOError: # Happen in Windows test environment ret = 1 return (ignorestatus or ret is None) and r.search(s) def has_baz(): return matchoutput('baz --version 2>&1', r'baz Bazaar version') def has_bzr(): try: import bzrlib return bzrlib.__doc__ is not None except ImportError: return False def has_bzr114(): try: import bzrlib return (bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= (1, 14)) except ImportError: return False def has_cvs(): re = r'Concurrent Versions System.*?server' return matchoutput('cvs --version 2>&1', re) and not has_msys() def has_darcs(): return matchoutput('darcs --version', r'2\.[2-9]', True) def has_mtn(): return matchoutput('mtn --version', r'monotone', True) and not matchoutput( 'mtn --version', r'monotone 0\.', True) def has_eol_in_paths(): try: fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') os.close(fd) os.remove(path) return True except (IOError, OSError): return False def has_executablebit(): try: EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) try: os.close(fh) m = os.stat(fn).st_mode & 0777 new_file_has_exec = m & EXECFLAGS os.chmod(fn, m ^ EXECFLAGS) exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m) finally: os.unlink(fn) except (IOError, OSError): # we don't care, the user probably won't be able to commit anyway return False return not (new_file_has_exec or exec_flags_cannot_flip) def has_icasefs(): # Stolen from mercurial.util fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) os.close(fd) try: s1 = os.stat(path) d, b = os.path.split(path) p2 = os.path.join(d, b.upper()) if path == p2: p2 = os.path.join(d, b.lower()) try: s2 = os.stat(p2) return s2 == s1 except OSError: return False finally: os.remove(path) def has_inotify(): try: import hgext.inotify.linux.watcher except ImportError: return False name = tempfile.mktemp(dir='.', prefix=tempprefix) sock = socket.socket(socket.AF_UNIX) try: sock.bind(name) except socket.error, err: return False sock.close() os.unlink(name) return True def has_fifo(): if getattr(os, "mkfifo", None) is None: return False name = tempfile.mktemp(dir='.', prefix=tempprefix) try: os.mkfifo(name) os.unlink(name) return True except OSError: return False def has_cacheable_fs(): from mercurial import util fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix) os.close(fd) try: return util.cachestat(path).cacheable() finally: os.remove(path) def has_lsprof(): try: import _lsprof return True except ImportError: return False def has_gettext(): return matchoutput('msgfmt --version', 'GNU gettext-tools') def has_git(): return matchoutput('git --version 2>&1', r'^git version') def has_docutils(): try: from docutils.core import publish_cmdline return True except ImportError: return False def getsvnversion(): m = matchoutput('svn --version 2>&1', r'^svn,\s+version\s+(\d+)\.(\d+)') if not m: return (0, 0) return (int(m.group(1)), int(m.group(2))) def has_svn15(): return getsvnversion() >= (1, 5) def has_svn13(): return getsvnversion() >= (1, 3) def has_svn(): return matchoutput('svn --version 2>&1', r'^svn, version') and \ matchoutput('svnadmin --version 2>&1', r'^svnadmin, version') def has_svn_bindings(): try: import svn.core version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR if version < (1, 4): return False return True except ImportError: return False def has_p4(): return (matchoutput('p4 -V', r'Rev\. P4/') and matchoutput('p4d -V', r'Rev\. P4D/')) def has_symlink(): if getattr(os, "symlink", None) is None: return False name = tempfile.mktemp(dir='.', prefix=tempprefix) try: os.symlink(".", name) os.unlink(name) return True except (OSError, AttributeError): return False def has_hardlink(): from mercurial import util fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix) os.close(fh) name = tempfile.mktemp(dir='.', prefix=tempprefix) try: try: util.oslink(fn, name) os.unlink(name) return True except OSError: return False finally: os.unlink(fn) def has_tla(): return matchoutput('tla --version 2>&1', r'The GNU Arch Revision') def has_gpg(): return matchoutput('gpg --version 2>&1', r'GnuPG') def has_unix_permissions(): d = tempfile.mkdtemp(dir='.', prefix=tempprefix) try: fname = os.path.join(d, 'foo') for umask in (077, 007, 022): os.umask(umask) f = open(fname, 'w') f.close() mode = os.stat(fname).st_mode os.unlink(fname) if mode & 0777 != ~umask & 0666: return False return True finally: os.rmdir(d) def has_pyflakes(): return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"", r":1: 're' imported but unused", True) def has_pygments(): try: import pygments return True except ImportError: return False def has_outer_repo(): # failing for other reasons than 'no repo' imply that there is a repo return not matchoutput('hg root 2>&1', r'abort: no repository found', True) def has_ssl(): try: import ssl import OpenSSL OpenSSL.SSL.Context return True except ImportError: return False def has_windows(): return os.name == 'nt' def has_system_sh(): return os.name != 'nt' def has_serve(): return os.name != 'nt' # gross approximation def has_tic(): return matchoutput('test -x "`which tic`"', '') def has_msys(): return os.getenv('MSYSTEM') checks = { "true": (lambda: True, "yak shaving"), "false": (lambda: False, "nail clipper"), "baz": (has_baz, "GNU Arch baz client"), "bzr": (has_bzr, "Canonical's Bazaar client"), "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"), "cacheable": (has_cacheable_fs, "cacheable filesystem"), "cvs": (has_cvs, "cvs client/server"), "darcs": (has_darcs, "darcs client"), "docutils": (has_docutils, "Docutils text processing library"), "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"), "execbit": (has_executablebit, "executable bit"), "fifo": (has_fifo, "named pipes"), "gettext": (has_gettext, "GNU Gettext (msgfmt)"), "git": (has_git, "git command line client"), "gpg": (has_gpg, "gpg client"), "hardlink": (has_hardlink, "hardlinks"), "icasefs": (has_icasefs, "case insensitive file system"), "inotify": (has_inotify, "inotify extension support"), "lsprof": (has_lsprof, "python lsprof module"), "mtn": (has_mtn, "monotone client (>= 1.0)"), "outer-repo": (has_outer_repo, "outer repo"), "p4": (has_p4, "Perforce server and client"), "pyflakes": (has_pyflakes, "Pyflakes python linter"), "pygments": (has_pygments, "Pygments source highlighting library"), "serve": (has_serve, "platform and python can manage 'hg serve -d'"), "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"), "svn": (has_svn, "subversion client and admin tools"), "svn13": (has_svn13, "subversion client and admin tools >= 1.3"), "svn15": (has_svn15, "subversion client and admin tools >= 1.5"), "svn-bindings": (has_svn_bindings, "subversion python bindings"), "symlink": (has_symlink, "symbolic links"), "system-sh": (has_system_sh, "system() uses sh"), "tic": (has_tic, "terminfo compiler"), "tla": (has_tla, "GNU Arch tla client"), "unix-permissions": (has_unix_permissions, "unix-style permissions"), "windows": (has_windows, "Windows"), "msys": (has_msys, "Windows with MSYS"), } durin42-hg-git-53d514c9c7e6/tests/killdaemons.py0000755000000000000000000000572012653777310017454 0ustar 00000000000000#!/usr/bin/env python import os, sys, time, errno, signal if os.name =='nt': import ctypes def _check(ret, expectederr=None): if ret == 0: winerrno = ctypes.GetLastError() if winerrno == expectederr: return True raise ctypes.WinError(winerrno) def kill(pid, logfn, tryhard=True): logfn('# Killing daemon process %d' % pid) PROCESS_TERMINATE = 1 PROCESS_QUERY_INFORMATION = 0x400 SYNCHRONIZE = 0x00100000L WAIT_OBJECT_0 = 0 WAIT_TIMEOUT = 258 handle = ctypes.windll.kernel32.OpenProcess( PROCESS_TERMINATE|SYNCHRONIZE|PROCESS_QUERY_INFORMATION, False, pid) if handle == 0: _check(0, 87) # err 87 when process not found return # process not found, already finished try: r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100) if r == WAIT_OBJECT_0: pass # terminated, but process handle still available elif r == WAIT_TIMEOUT: _check(ctypes.windll.kernel32.TerminateProcess(handle, -1)) else: _check(r) # TODO?: forcefully kill when timeout # and ?shorter waiting time? when tryhard==True r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100) # timeout = 100 ms if r == WAIT_OBJECT_0: pass # process is terminated elif r == WAIT_TIMEOUT: logfn('# Daemon process %d is stuck') else: check(r) # any error except: #re-raises ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error raise _check(ctypes.windll.kernel32.CloseHandle(handle)) else: def kill(pid, logfn, tryhard=True): try: os.kill(pid, 0) logfn('# Killing daemon process %d' % pid) os.kill(pid, signal.SIGTERM) if tryhard: for i in range(10): time.sleep(0.05) os.kill(pid, 0) else: time.sleep(0.1) os.kill(pid, 0) logfn('# Daemon process %d is stuck - really killing it' % pid) os.kill(pid, signal.SIGKILL) except OSError, err: if err.errno != errno.ESRCH: raise def killdaemons(pidfile, tryhard=True, remove=False, logfn=None): if not logfn: logfn = lambda s: s # Kill off any leftover daemon processes try: fp = open(pidfile) for line in fp: try: pid = int(line) except ValueError: continue kill(pid, logfn, tryhard) fp.close() if remove: os.unlink(pidfile) except IOError: pass if __name__ == '__main__': path, = sys.argv[1:] killdaemons(path) durin42-hg-git-53d514c9c7e6/tests/latin-1-encoding0000644000000000000000000000062112653777310017544 0ustar 00000000000000# -*- coding: latin-1 -*- # this file contains some latin-1 messages for test-encoding GIT_AUTHOR_NAME='tést èncödîng'; export GIT_AUTHOR_NAME echo beta > beta git add beta fn_git_commit -m 'add beta' echo gamma > gamma git add gamma fn_git_commit -m 'add gämmâ' # test the commit encoding field git config i18n.commitencoding latin-1 echo delta > delta git add delta fn_git_commit -m 'add déltà' durin42-hg-git-53d514c9c7e6/tests/run-tests.py0000755000000000000000000012716112653777310017122 0ustar 00000000000000#!/usr/bin/env python # # run-tests.py - Run a set of tests on Mercurial # # Copyright 2006 Matt Mackall # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. # Modifying this script is tricky because it has many modes: # - serial (default) vs parallel (-jN, N > 1) # - no coverage (default) vs coverage (-c, -C, -s) # - temp install (default) vs specific hg script (--with-hg, --local) # - tests are a mix of shell scripts and Python scripts # # If you change this script, it is recommended that you ensure you # haven't broken it by running it in various modes with a representative # sample of test scripts. For example: # # 1) serial, no coverage, temp install: # ./run-tests.py test-s* # 2) serial, no coverage, local hg: # ./run-tests.py --local test-s* # 3) serial, coverage, temp install: # ./run-tests.py -c test-s* # 4) serial, coverage, local hg: # ./run-tests.py -c --local test-s* # unsupported # 5) parallel, no coverage, temp install: # ./run-tests.py -j2 test-s* # 6) parallel, no coverage, local hg: # ./run-tests.py -j2 --local test-s* # 7) parallel, coverage, temp install: # ./run-tests.py -j2 -c test-s* # currently broken # 8) parallel, coverage, local install: # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) # 9) parallel, custom tmp dir: # ./run-tests.py -j2 --tmpdir /tmp/myhgtests # # (You could use any subset of the tests: test-s* happens to match # enough that it's worth doing parallel runs, few enough that it # completes fairly quickly, includes both shell and Python scripts, and # includes some scripts that run daemon processes.) from distutils import version import difflib import errno import optparse import os import shutil import subprocess import signal import sys import tempfile import time import random import re import threading import killdaemons as killmod import Queue as queue processlock = threading.Lock() # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing # zombies but it's pretty harmless even if we do. if sys.version_info < (2, 5): subprocess._cleanup = lambda: None closefds = os.name == 'posix' def Popen4(cmd, wd, timeout, env=None): processlock.acquire() p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env, close_fds=closefds, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) processlock.release() p.fromchild = p.stdout p.tochild = p.stdin p.childerr = p.stderr p.timeout = False if timeout: def t(): start = time.time() while time.time() - start < timeout and p.returncode is None: time.sleep(.1) p.timeout = True if p.returncode is None: terminate(p) threading.Thread(target=t).start() return p # reserved exit code to skip test (used by hghave) SKIPPED_STATUS = 80 SKIPPED_PREFIX = 'skipped: ' FAILED_PREFIX = 'hghave check failed: ' PYTHON = sys.executable.replace('\\', '/') IMPL_PATH = 'PYTHONPATH' if 'java' in sys.platform: IMPL_PATH = 'JYTHONPATH' requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"] createdfiles = [] defaults = { 'jobs': ('HGTEST_JOBS', 1), 'timeout': ('HGTEST_TIMEOUT', 180), 'port': ('HGTEST_PORT', 20059), 'shell': ('HGTEST_SHELL', 'sh'), } def parselistfiles(files, listtype, warn=True): entries = dict() for filename in files: try: path = os.path.expanduser(os.path.expandvars(filename)) f = open(path, "r") except IOError, err: if err.errno != errno.ENOENT: raise if warn: print "warning: no such %s file: %s" % (listtype, filename) continue for line in f.readlines(): line = line.split('#', 1)[0].strip() if line: entries[line] = filename f.close() return entries def parseargs(): parser = optparse.OptionParser("%prog [options] [tests]") # keep these sorted parser.add_option("--blacklist", action="append", help="skip tests listed in the specified blacklist file") parser.add_option("--whitelist", action="append", help="always run tests listed in the specified whitelist file") parser.add_option("-C", "--annotate", action="store_true", help="output files annotated with coverage") parser.add_option("-c", "--cover", action="store_true", help="print a test coverage report") parser.add_option("-d", "--debug", action="store_true", help="debug mode: write output of test scripts to console" " rather than capturing and diff'ing it (disables timeout)") parser.add_option("-f", "--first", action="store_true", help="exit on the first test failure") parser.add_option("-H", "--htmlcov", action="store_true", help="create an HTML report of the coverage of the files") parser.add_option("--inotify", action="store_true", help="enable inotify extension when running tests") parser.add_option("-i", "--interactive", action="store_true", help="prompt to accept changed output") parser.add_option("-j", "--jobs", type="int", help="number of jobs to run in parallel" " (default: $%s or %d)" % defaults['jobs']) parser.add_option("--keep-tmpdir", action="store_true", help="keep temporary directory after running tests") parser.add_option("-k", "--keywords", help="run tests matching keywords") parser.add_option("-l", "--local", action="store_true", help="shortcut for --with-hg=/../hg") parser.add_option("--loop", action="store_true", help="loop tests repeatedly") parser.add_option("-n", "--nodiff", action="store_true", help="skip showing test changes") parser.add_option("-p", "--port", type="int", help="port on which servers should listen" " (default: $%s or %d)" % defaults['port']) parser.add_option("--compiler", type="string", help="compiler to build with") parser.add_option("--pure", action="store_true", help="use pure Python code instead of C extensions") parser.add_option("-R", "--restart", action="store_true", help="restart at last error") parser.add_option("-r", "--retest", action="store_true", help="retest failed tests") parser.add_option("-S", "--noskips", action="store_true", help="don't report skip tests verbosely") parser.add_option("--shell", type="string", help="shell to use (default: $%s or %s)" % defaults['shell']) parser.add_option("-t", "--timeout", type="int", help="kill errant tests after TIMEOUT seconds" " (default: $%s or %d)" % defaults['timeout']) parser.add_option("--time", action="store_true", help="time how long each test takes") parser.add_option("--tmpdir", type="string", help="run tests in the given temporary directory" " (implies --keep-tmpdir)") parser.add_option("-v", "--verbose", action="store_true", help="output verbose messages") parser.add_option("--view", type="string", help="external diff viewer") parser.add_option("--with-hg", type="string", metavar="HG", help="test using specified hg script rather than a " "temporary installation") parser.add_option("-3", "--py3k-warnings", action="store_true", help="enable Py3k warnings on Python 2.6+") parser.add_option('--extra-config-opt', action="append", help='set the given config opt in the test hgrc') parser.add_option('--random', action="store_true", help='run tests in random order') for option, (envvar, default) in defaults.items(): defaults[option] = type(default)(os.environ.get(envvar, default)) parser.set_defaults(**defaults) (options, args) = parser.parse_args() # jython is always pure if 'java' in sys.platform or '__pypy__' in sys.modules: options.pure = True if options.with_hg: options.with_hg = os.path.expanduser(options.with_hg) if not (os.path.isfile(options.with_hg) and os.access(options.with_hg, os.X_OK)): parser.error('--with-hg must specify an executable hg script') if not os.path.basename(options.with_hg) == 'hg': sys.stderr.write('warning: --with-hg should specify an hg script\n') if options.local: testdir = os.path.dirname(os.path.realpath(sys.argv[0])) hgbin = os.path.join(os.path.dirname(testdir), 'hg') if os.name != 'nt' and not os.access(hgbin, os.X_OK): parser.error('--local specified, but %r not found or not executable' % hgbin) options.with_hg = hgbin options.anycoverage = options.cover or options.annotate or options.htmlcov if options.anycoverage: try: import coverage covver = version.StrictVersion(coverage.__version__).version if covver < (3, 3): parser.error('coverage options require coverage 3.3 or later') except ImportError: parser.error('coverage options now require the coverage package') if options.anycoverage and options.local: # this needs some path mangling somewhere, I guess parser.error("sorry, coverage options do not work when --local " "is specified") global verbose if options.verbose: verbose = '' if options.tmpdir: options.tmpdir = os.path.expanduser(options.tmpdir) if options.jobs < 1: parser.error('--jobs must be positive') if options.interactive and options.debug: parser.error("-i/--interactive and -d/--debug are incompatible") if options.debug: if options.timeout != defaults['timeout']: sys.stderr.write( 'warning: --timeout option ignored with --debug\n') options.timeout = 0 if options.py3k_warnings: if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): parser.error('--py3k-warnings can only be used on Python 2.6+') if options.blacklist: options.blacklist = parselistfiles(options.blacklist, 'blacklist') if options.whitelist: options.whitelisted = parselistfiles(options.whitelist, 'whitelist') else: options.whitelisted = {} return (options, args) def rename(src, dst): """Like os.rename(), trade atomicity and opened files friendliness for existing destination support. """ shutil.copy(src, dst) os.remove(src) def parsehghaveoutput(lines): '''Parse hghave log lines. Return tuple of lists (missing, failed): * the missing/unknown features * the features for which existence check failed''' missing = [] failed = [] for line in lines: if line.startswith(SKIPPED_PREFIX): line = line.splitlines()[0] missing.append(line[len(SKIPPED_PREFIX):]) elif line.startswith(FAILED_PREFIX): line = line.splitlines()[0] failed.append(line[len(FAILED_PREFIX):]) return missing, failed def showdiff(expected, output, ref, err): print for line in difflib.unified_diff(expected, output, ref, err): sys.stdout.write(line) verbose = False def vlog(*msg): if verbose is not False: iolock.acquire() if verbose: print verbose, for m in msg: print m, print sys.stdout.flush() iolock.release() def log(*msg): iolock.acquire() if verbose: print verbose, for m in msg: print m, print sys.stdout.flush() iolock.release() def findprogram(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.name == 'nt' or os.access(name, os.X_OK): return name return None def createhgrc(path, options): # create a fresh hgrc hgrc = open(path, 'w') hgrc.write('[ui]\n') hgrc.write('slash = True\n') hgrc.write('interactive = False\n') hgrc.write('[defaults]\n') hgrc.write('backout = -d "0 0"\n') hgrc.write('commit = -d "0 0"\n') hgrc.write('shelve = --date "0 0"\n') hgrc.write('tag = -d "0 0"\n') if options.inotify: hgrc.write('[extensions]\n') hgrc.write('inotify=\n') hgrc.write('[inotify]\n') hgrc.write('pidfile=daemon.pids') hgrc.write('appendpid=True\n') if options.extra_config_opt: for opt in options.extra_config_opt: section, key = opt.split('.', 1) assert '=' in key, ('extra config opt %s must ' 'have an = for assignment' % opt) hgrc.write('[%s]\n%s\n' % (section, key)) hgrc.close() def createenv(options, testtmp, threadtmp, port): env = os.environ.copy() env['TESTTMP'] = testtmp env['HOME'] = testtmp env["HGPORT"] = str(port) env["HGPORT1"] = str(port + 1) env["HGPORT2"] = str(port + 2) env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc') env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids') env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"' env["HGMERGE"] = "internal:merge" env["HGUSER"] = "test" env["HGENCODING"] = "ascii" env["HGENCODINGMODE"] = "strict" # Reset some environment variables to well-known values so that # the tests produce repeatable output. env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C' env['TZ'] = 'GMT' env["EMAIL"] = "Foo Bar " env['COLUMNS'] = '80' env['TERM'] = 'xterm' for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' + 'NO_PROXY').split(): if k in env: del env[k] # unset env related to hooks for k in env.keys(): if k.startswith('HG_'): del env[k] return env def checktools(): # Before we go any further, check for pre-requisite tools # stuff from coreutils (cat, rm, etc) are not tested for p in requiredtools: if os.name == 'nt' and not p.endswith('.exe'): p += '.exe' found = findprogram(p) if found: vlog("# Found prerequisite", p, "at", found) else: print "WARNING: Did not find prerequisite tool: "+p def terminate(proc): """Terminate subprocess (with fallback for Python versions < 2.6)""" vlog('# Terminating process %d' % proc.pid) try: getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))() except OSError: pass def killdaemons(pidfile): return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog) def cleanup(options): if not options.keep_tmpdir: vlog("# Cleaning up HGTMP", HGTMP) shutil.rmtree(HGTMP, True) for f in createdfiles: try: os.remove(f) except OSError: pass def usecorrectpython(): # some tests run python interpreter. they must use same # interpreter we use or bad things will happen. pyexename = sys.platform == 'win32' and 'python.exe' or 'python' if getattr(os, 'symlink', None): vlog("# Making python executable in test path a symlink to '%s'" % sys.executable) mypython = os.path.join(TMPBINDIR, pyexename) try: if os.readlink(mypython) == sys.executable: return os.unlink(mypython) except OSError, err: if err.errno != errno.ENOENT: raise if findprogram(pyexename) != sys.executable: try: os.symlink(sys.executable, mypython) createdfiles.append(mypython) except OSError, err: # child processes may race, which is harmless if err.errno != errno.EEXIST: raise else: exedir, exename = os.path.split(sys.executable) vlog("# Modifying search path to find %s as %s in '%s'" % (exename, pyexename, exedir)) path = os.environ['PATH'].split(os.pathsep) while exedir in path: path.remove(exedir) os.environ['PATH'] = os.pathsep.join([exedir] + path) if not findprogram(pyexename): print "WARNING: Cannot find %s in search path" % pyexename def installhg(options): vlog("# Performing temporary installation of HG") installerrs = os.path.join("tests", "install.err") compiler = '' if options.compiler: compiler = '--compiler ' + options.compiler pure = options.pure and "--pure" or "" py3 = '' if sys.version_info[0] == 3: py3 = '--c2to3' # Run installer in hg root script = os.path.realpath(sys.argv[0]) hgroot = os.path.dirname(os.path.dirname(script)) os.chdir(hgroot) nohome = '--home=""' if os.name == 'nt': # The --home="" trick works only on OS where os.sep == '/' # because of a distutils convert_path() fast-path. Avoid it at # least on Windows for now, deal with .pydistutils.cfg bugs # when they happen. nohome = '' cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all' ' build %(compiler)s --build-base="%(base)s"' ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"' ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' % dict(exe=sys.executable, py3=py3, pure=pure, compiler=compiler, base=os.path.join(HGTMP, "build"), prefix=INST, libdir=PYTHONDIR, bindir=BINDIR, nohome=nohome, logfile=installerrs)) vlog("# Running", cmd) if os.system(cmd) == 0: if not options.verbose: os.remove(installerrs) else: f = open(installerrs) for line in f: print line, f.close() sys.exit(1) os.chdir(TESTDIR) usecorrectpython() if options.py3k_warnings and not options.anycoverage: vlog("# Updating hg command to enable Py3k Warnings switch") f = open(os.path.join(BINDIR, 'hg'), 'r') lines = [line.rstrip() for line in f] lines[0] += ' -3' f.close() f = open(os.path.join(BINDIR, 'hg'), 'w') for line in lines: f.write(line + '\n') f.close() hgbat = os.path.join(BINDIR, 'hg.bat') if os.path.isfile(hgbat): # hg.bat expects to be put in bin/scripts while run-tests.py # installation layout put it in bin/ directly. Fix it f = open(hgbat, 'rb') data = f.read() f.close() if '"%~dp0..\python" "%~dp0hg" %*' in data: data = data.replace('"%~dp0..\python" "%~dp0hg" %*', '"%~dp0python" "%~dp0hg" %*') f = open(hgbat, 'wb') f.write(data) f.close() else: print 'WARNING: cannot fix hg.bat reference to python.exe' if options.anycoverage: custom = os.path.join(TESTDIR, 'sitecustomize.py') target = os.path.join(PYTHONDIR, 'sitecustomize.py') vlog('# Installing coverage trigger to %s' % target) shutil.copyfile(custom, target) rc = os.path.join(TESTDIR, '.coveragerc') vlog('# Installing coverage rc to %s' % rc) os.environ['COVERAGE_PROCESS_START'] = rc fn = os.path.join(INST, '..', '.coverage') os.environ['COVERAGE_FILE'] = fn def outputtimes(options): vlog('# Producing time report') times.sort(key=lambda t: (t[1], t[0]), reverse=True) cols = '%7.3f %s' print '\n%-7s %s' % ('Time', 'Test') for test, timetaken in times: print cols % (timetaken, test) def outputcoverage(options): vlog('# Producing coverage report') os.chdir(PYTHONDIR) def covrun(*args): cmd = 'coverage %s' % ' '.join(args) vlog('# Running: %s' % cmd) os.system(cmd) covrun('-c') omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR]) covrun('-i', '-r', '"--omit=%s"' % omit) # report if options.htmlcov: htmldir = os.path.join(TESTDIR, 'htmlcov') covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit) if options.annotate: adir = os.path.join(TESTDIR, 'annotated') if not os.path.isdir(adir): os.mkdir(adir) covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit) def pytest(test, wd, options, replacements, env): py3kswitch = options.py3k_warnings and ' -3' or '' cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test) vlog("# Running", cmd) if os.name == 'nt': replacements.append((r'\r\n', '\n')) return run(cmd, wd, options, replacements, env) needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256)) escapemap.update({'\\': '\\\\', '\r': r'\r'}) def escapef(m): return escapemap[m.group(0)] def stringescape(s): return escapesub(escapef, s) def rematch(el, l): try: # use \Z to ensure that the regex matches to the end of the string if os.name == 'nt': return re.match(el + r'\r?\n\Z', l) return re.match(el + r'\n\Z', l) except re.error: # el is an invalid regex return False def globmatch(el, l): # The only supported special characters are * and ? plus / which also # matches \ on windows. Escaping of these caracters is supported. if el + '\n' == l: if os.altsep: # matching on "/" is not needed for this line return '-glob' return True i, n = 0, len(el) res = '' while i < n: c = el[i] i += 1 if c == '\\' and el[i] in '*?\\/': res += el[i - 1:i + 1] i += 1 elif c == '*': res += '.*' elif c == '?': res += '.' elif c == '/' and os.altsep: res += '[/\\\\]' else: res += re.escape(c) return rematch(res, l) def linematch(el, l): if el == l: # perfect match (fast) return True if el: if el.endswith(" (esc)\n"): el = el[:-7].decode('string-escape') + '\n' if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l: return True if el.endswith(" (re)\n"): return rematch(el[:-6], l) if el.endswith(" (glob)\n"): return globmatch(el[:-8], l) if os.altsep and l.replace('\\', '/') == el: return '+glob' return False def tsttest(test, wd, options, replacements, env): # We generate a shell script which outputs unique markers to line # up script results with our source. These markers include input # line number and the last return code salt = "SALT" + str(time.time()) def addsalt(line, inpython): if inpython: script.append('%s %d 0\n' % (salt, line)) else: script.append('echo %s %s $?\n' % (salt, line)) # After we run the shell script, we re-unify the script output # with non-active parts of the source, with synchronization by our # SALT line number markers. The after table contains the # non-active components, ordered by line number after = {} pos = prepos = -1 # Expected shellscript output expected = {} # We keep track of whether or not we're in a Python block so we # can generate the surrounding doctest magic inpython = False # True or False when in a true or false conditional section skipping = None def hghave(reqs): # TODO: do something smarter when all other uses of hghave is gone tdir = TESTDIR.replace('\\', '/') proc = Popen4('%s -c "%s/hghave %s"' % (options.shell, tdir, ' '.join(reqs)), wd, 0) stdout, stderr = proc.communicate() ret = proc.wait() if wifexited(ret): ret = os.WEXITSTATUS(ret) if ret == 2: print stdout sys.exit(1) return ret == 0 f = open(test) t = f.readlines() f.close() script = [] if options.debug: script.append('set -x\n') if os.getenv('MSYSTEM'): script.append('alias pwd="pwd -W"\n') n = 0 for n, l in enumerate(t): if not l.endswith('\n'): l += '\n' if l.startswith('#if'): if skipping is not None: after.setdefault(pos, []).append(' !!! nested #if\n') skipping = not hghave(l.split()[1:]) after.setdefault(pos, []).append(l) elif l.startswith('#else'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = not skipping after.setdefault(pos, []).append(l) elif l.startswith('#endif'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = None after.setdefault(pos, []).append(l) elif skipping: after.setdefault(pos, []).append(l) elif l.startswith(' >>> '): # python inlines after.setdefault(pos, []).append(l) prepos = pos pos = n if not inpython: # we've just entered a Python block, add the header inpython = True addsalt(prepos, False) # make sure we report the exit code script.append('%s -m heredoctest < '): # continuations after.setdefault(prepos, []).append(l) script.append(l[4:]) elif l.startswith(' '): # results # queue up a list of expected results expected.setdefault(pos, []).append(l[2:]) else: if inpython: script.append("EOF\n") inpython = False # non-command/result - queue up for merged output after.setdefault(pos, []).append(l) if inpython: script.append("EOF\n") if skipping is not None: after.setdefault(pos, []).append(' !!! missing #endif\n') addsalt(n + 1, False) # Write out the script and execute it name = wd + '.sh' f = open(name, 'w') for l in script: f.write(l) f.close() cmd = '%s "%s"' % (options.shell, name) vlog("# Running", cmd) exitcode, output = run(cmd, wd, options, replacements, env) # do not merge output if skipped, return hghave message instead # similarly, with --debug, output is None if exitcode == SKIPPED_STATUS or output is None: return exitcode, output # Merge the script output back into a unified test warnonly = True pos = -1 postout = [] for l in output: lout, lcmd = l, None if salt in l: lout, lcmd = l.split(salt, 1) if lout: if not lout.endswith('\n'): lout += ' (no-eol)\n' # find the expected output at the current position el = None if pos in expected and expected[pos]: el = expected[pos].pop(0) r = linematch(el, lout) if isinstance(r, str): if r == '+glob': lout = el[:-1] + ' (glob)\n' r = 0 # warn only elif r == '-glob': lout = ''.join(el.rsplit(' (glob)', 1)) r = 0 # warn only else: log('\ninfo, unknown linematch result: %r\n' % r) r = False if r: postout.append(" " + el) else: if needescape(lout): lout = stringescape(lout.rstrip('\n')) + " (esc)\n" postout.append(" " + lout) # let diff deal with it if r != 0: # != warn only warnonly = False if lcmd: # add on last return code ret = int(lcmd.split()[1]) if ret != 0: postout.append(" [%s]\n" % ret) if pos in after: # merge in non-active test bits postout += after.pop(pos) pos = int(lcmd.split()[0]) if pos in after: postout += after.pop(pos) if warnonly and exitcode == 0: exitcode = False return exitcode, postout wifexited = getattr(os, "WIFEXITED", lambda x: False) def run(cmd, wd, options, replacements, env): """Run command in a sub-process, capturing the output (stdout and stderr). Return a tuple (exitcode, output). output is None in debug mode.""" # TODO: Use subprocess.Popen if we're running on Python 2.4 if options.debug: proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env) ret = proc.wait() return (ret, None) proc = Popen4(cmd, wd, options.timeout, env) def cleanup(): terminate(proc) ret = proc.wait() if ret == 0: ret = signal.SIGTERM << 8 killdaemons(env['DAEMON_PIDS']) return ret output = '' proc.tochild.close() try: output = proc.fromchild.read() except KeyboardInterrupt: vlog('# Handling keyboard interrupt') cleanup() raise ret = proc.wait() if wifexited(ret): ret = os.WEXITSTATUS(ret) if proc.timeout: ret = 'timeout' if ret: killdaemons(env['DAEMON_PIDS']) if abort: raise KeyboardInterrupt() for s, r in replacements: output = re.sub(s, r, output) return ret, output.splitlines(True) def runone(options, test, count): '''returns a result element: (code, test, msg)''' def skip(msg): if options.verbose: log("\nSkipping %s: %s" % (testpath, msg)) return 's', test, msg def fail(msg, ret): warned = ret is False if not options.nodiff: log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg)) if (not ret and options.interactive and os.path.exists(testpath + ".err")): iolock.acquire() print "Accept this change? [n] ", answer = sys.stdin.readline().strip() iolock.release() if answer.lower() in "y yes".split(): if test.endswith(".t"): rename(testpath + ".err", testpath) else: rename(testpath + ".err", testpath + ".out") return '.', test, '' return warned and '~' or '!', test, msg def success(): return '.', test, '' def ignore(msg): return 'i', test, msg def describe(ret): if ret < 0: return 'killed by signal %d' % -ret return 'returned error code %d' % ret testpath = os.path.join(TESTDIR, test) err = os.path.join(TESTDIR, test + ".err") lctest = test.lower() if not os.path.exists(testpath): return skip("doesn't exist") if not (options.whitelisted and test in options.whitelisted): if options.blacklist and test in options.blacklist: return skip("blacklisted") if options.retest and not os.path.exists(test + ".err"): return ignore("not retesting") if options.keywords: fp = open(test) t = fp.read().lower() + test.lower() fp.close() for k in options.keywords.lower().split(): if k in t: break else: return ignore("doesn't match keyword") if not lctest.startswith("test-"): return skip("not a test file") for ext, func, out in testtypes: if lctest.endswith(ext): runner = func ref = os.path.join(TESTDIR, test + out) break else: return skip("unknown test type") vlog("# Test", test) if os.path.exists(err): os.remove(err) # Remove any previous output files # Make a tmp subdirectory to work in threadtmp = os.path.join(HGTMP, "child%d" % count) testtmp = os.path.join(threadtmp, os.path.basename(test)) os.mkdir(threadtmp) os.mkdir(testtmp) port = options.port + count * 3 replacements = [ (r':%s\b' % port, ':$HGPORT'), (r':%s\b' % (port + 1), ':$HGPORT1'), (r':%s\b' % (port + 2), ':$HGPORT2'), ] if os.name == 'nt': replacements.append( (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c for c in testtmp), '$TESTTMP')) else: replacements.append((re.escape(testtmp), '$TESTTMP')) env = createenv(options, testtmp, threadtmp, port) createhgrc(env['HGRCPATH'], options) starttime = time.time() try: ret, out = runner(testpath, testtmp, options, replacements, env) except KeyboardInterrupt: endtime = time.time() log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime)) raise endtime = time.time() times.append((test, endtime - starttime)) vlog("# Ret was:", ret) killdaemons(env['DAEMON_PIDS']) skipped = (ret == SKIPPED_STATUS) # If we're not in --debug mode and reference output file exists, # check test output against it. if options.debug: refout = None # to match "out is None" elif os.path.exists(ref): f = open(ref, "r") refout = f.read().splitlines(True) f.close() else: refout = [] if (ret != 0 or out != refout) and not skipped and not options.debug: # Save errors to a file for diagnosis f = open(err, "wb") for line in out: f.write(line) f.close() if skipped: if out is None: # debug mode: nothing to parse missing = ['unknown'] failed = None else: missing, failed = parsehghaveoutput(out) if not missing: missing = ['irrelevant'] if failed: result = fail("hghave failed checking for %s" % failed[-1], ret) skipped = False else: result = skip(missing[-1]) elif ret == 'timeout': result = fail("timed out", ret) elif out != refout: if not options.nodiff: iolock.acquire() if options.view: os.system("%s %s %s" % (options.view, ref, err)) else: showdiff(refout, out, ref, err) iolock.release() if ret: result = fail("output changed and " + describe(ret), ret) else: result = fail("output changed", ret) elif ret: result = fail(describe(ret), ret) else: result = success() if not options.verbose: iolock.acquire() sys.stdout.write(result[0]) sys.stdout.flush() iolock.release() if not options.keep_tmpdir: shutil.rmtree(threadtmp, True) return result _hgpath = None def _gethgpath(): """Return the path to the mercurial package that is actually found by the current Python interpreter.""" global _hgpath if _hgpath is not None: return _hgpath cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"' pipe = os.popen(cmd % PYTHON) try: _hgpath = pipe.read().strip() finally: pipe.close() return _hgpath def _checkhglib(verb): """Ensure that the 'mercurial' package imported by python is the one we expect it to be. If not, print a warning to stderr.""" expecthg = os.path.join(PYTHONDIR, 'mercurial') actualhg = _gethgpath() if os.path.abspath(actualhg) != os.path.abspath(expecthg): sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n' ' (expected %s)\n' % (verb, actualhg, expecthg)) results = {'.':[], '!':[], '~': [], 's':[], 'i':[]} times = [] iolock = threading.Lock() abort = False def scheduletests(options, tests): jobs = options.jobs done = queue.Queue() running = 0 count = 0 global abort def job(test, count): try: done.put(runone(options, test, count)) except KeyboardInterrupt: pass except: # re-raises done.put(('!', test, 'run-test raised an error, see traceback')) raise try: while tests or running: if not done.empty() or running == jobs or not tests: try: code, test, msg = done.get(True, 1) results[code].append((test, msg)) if options.first and code not in '.si': break except queue.Empty: continue running -= 1 if tests and not running == jobs: test = tests.pop(0) if options.loop: tests.append(test) t = threading.Thread(target=job, name=test, args=(test, count)) t.start() running += 1 count += 1 except KeyboardInterrupt: abort = True def runtests(options, tests): try: if INST: installhg(options) _checkhglib("Testing") else: usecorrectpython() if options.restart: orig = list(tests) while tests: if os.path.exists(tests[0] + ".err"): break tests.pop(0) if not tests: print "running all tests" tests = orig scheduletests(options, tests) failed = len(results['!']) warned = len(results['~']) tested = len(results['.']) + failed + warned skipped = len(results['s']) ignored = len(results['i']) print if not options.noskips: for s in results['s']: print "Skipped %s: %s" % s for s in results['~']: print "Warned %s: %s" % s for s in results['!']: print "Failed %s: %s" % s _checkhglib("Tested") print "# Ran %d tests, %d skipped, %d warned, %d failed." % ( tested, skipped + ignored, warned, failed) if results['!']: print 'python hash seed:', os.environ['PYTHONHASHSEED'] if options.time: outputtimes(options) if options.anycoverage: outputcoverage(options) except KeyboardInterrupt: failed = True print "\ninterrupted!" if failed: return 1 if warned: return 80 testtypes = [('.py', pytest, '.out'), ('.t', tsttest, '')] def main(): (options, args) = parseargs() os.umask(022) checktools() if len(args) == 0: args = [t for t in os.listdir(".") if t.startswith("test-") and (t.endswith(".py") or t.endswith(".t"))] tests = args if options.random: random.shuffle(tests) else: # keywords for slow tests slow = 'svn gendoc check-code-hg'.split() def sortkey(f): # run largest tests first, as they tend to take the longest try: val = -os.stat(f).st_size except OSError, e: if e.errno != errno.ENOENT: raise return -1e9 # file does not exist, tell early for kw in slow: if kw in f: val *= 10 return val tests.sort(key=sortkey) if 'PYTHONHASHSEED' not in os.environ: # use a random python hash seed all the time # we do the randomness ourself to know what seed is used os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32)) global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE TESTDIR = os.environ["TESTDIR"] = os.getcwd() if options.tmpdir: options.keep_tmpdir = True tmpdir = options.tmpdir if os.path.exists(tmpdir): # Meaning of tmpdir has changed since 1.3: we used to create # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if # tmpdir already exists. sys.exit("error: temp dir %r already exists" % tmpdir) # Automatically removing tmpdir sounds convenient, but could # really annoy anyone in the habit of using "--tmpdir=/tmp" # or "--tmpdir=$HOME". #vlog("# Removing temp dir", tmpdir) #shutil.rmtree(tmpdir) os.makedirs(tmpdir) else: d = None if os.name == 'nt': # without this, we get the default temp dir location, but # in all lowercase, which causes troubles with paths (issue3490) d = os.getenv('TMP') tmpdir = tempfile.mkdtemp('', 'hgtests.', d) HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir) if options.with_hg: INST = None BINDIR = os.path.dirname(os.path.realpath(options.with_hg)) TMPBINDIR = os.path.join(HGTMP, 'install', 'bin') os.makedirs(TMPBINDIR) # This looks redundant with how Python initializes sys.path from # the location of the script being executed. Needed because the # "hg" specified by --with-hg is not the only Python script # executed in the test suite that needs to import 'mercurial' # ... which means it's not really redundant at all. PYTHONDIR = BINDIR else: INST = os.path.join(HGTMP, "install") BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin") TMPBINDIR = BINDIR PYTHONDIR = os.path.join(INST, "lib", "python") os.environ["BINDIR"] = BINDIR os.environ["PYTHON"] = PYTHON path = [BINDIR] + os.environ["PATH"].split(os.pathsep) if TMPBINDIR != BINDIR: path = [TMPBINDIR] + path os.environ["PATH"] = os.pathsep.join(path) # Include TESTDIR in PYTHONPATH so that out-of-tree extensions # can run .../tests/run-tests.py test-foo where test-foo # adds an extension to HGRC. Also include run-test.py directory to import # modules like heredoctest. pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))] # We have to augment PYTHONPATH, rather than simply replacing # it, in case external libraries are only available via current # PYTHONPATH. (In particular, the Subversion bindings on OS X # are in /opt/subversion.) oldpypath = os.environ.get(IMPL_PATH) if oldpypath: pypath.append(oldpypath) os.environ[IMPL_PATH] = os.pathsep.join(pypath) COVERAGE_FILE = os.path.join(TESTDIR, ".coverage") vlog("# Using TESTDIR", TESTDIR) vlog("# Using HGTMP", HGTMP) vlog("# Using PATH", os.environ["PATH"]) vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH]) try: sys.exit(runtests(options, tests) or 0) finally: time.sleep(.1) cleanup(options) if __name__ == '__main__': main() durin42-hg-git-53d514c9c7e6/tests/test-bookmark-workflow.t0000644000000000000000000001525312653777310021416 0ustar 00000000000000This test demonstrates how Hg works with remote Hg bookmarks compared with remote branches via Hg-Git. Ideally, they would behave identically. In practice, some differences are unavoidable, but we should try to minimize them. This test should not bother testing the behavior of bookmark creation, deletion, activation, deactivation, etc. These behaviors, while important to the end user, don't vary at all when Hg-Git is in use. Only the synchonization of bookmarks should be considered "under test", and mutation of bookmarks locally is only to provide a test fixture. Load commonly used test logic $ . "$TESTDIR/testutil" $ gitcount=10 $ gitcommit() > { > GIT_AUTHOR_DATE="2007-01-01 00:00:$gitcount +0000" > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error" > gitcount=`expr $gitcount + 1` > } $ hgcount=10 $ hgcommit() > { > HGDATE="2007-01-01 00:00:$hgcount +0000" > hg commit -u "test " -d "$HGDATE" "$@" >/dev/null 2>/dev/null || echo "hg commit error" > hgcount=`expr $hgcount + 1` > } $ gitstate() > { > git log --format=" %h \"%s\" refs:%d" $@ | sed 's/HEAD, //' > } $ hgstate() > { > hg log --template " {rev} {node|short} \"{desc}\" bookmarks: [{bookmarks}]\n" $@ > } $ hggitstate() > { > hg log --template " {rev} {node|short} {gitnode|short} \"{desc}\" bookmarks: [{bookmarks}]\n" $@ > } Initialize remote hg and git repos with equivalent initial contents $ hg init hgremoterepo $ cd hgremoterepo $ hg bookmark master $ for f in alpha beta gamma delta; do > echo $f > $f; hg add $f; hgcommit -m "add $f" > done $ hg bookmark -r 1 b1 $ hgstate 3 fc2664cac217 "add delta" bookmarks: [master] 2 d85ced7ae9d6 "add gamma" bookmarks: [] 1 7bcd915dc873 "add beta" bookmarks: [b1] 0 3442585be8a6 "add alpha" bookmarks: [] $ cd .. $ git init -q gitremoterepo $ cd gitremoterepo $ for f in alpha beta gamma delta; do > echo $f > $f; git add $f; gitcommit -m "add $f" > done $ git branch b1 9497a4e $ gitstate 55b133e "add delta" refs: (*master) (glob) d338971 "add gamma" refs: 9497a4e "add beta" refs: (b1) 7eeab2e "add alpha" refs: $ cd .. Cloning transfers all bookmarks from remote to local $ hg clone -q hgremoterepo purehglocalrepo $ cd purehglocalrepo $ hgstate 3 fc2664cac217 "add delta" bookmarks: [master] 2 d85ced7ae9d6 "add gamma" bookmarks: [] 1 7bcd915dc873 "add beta" bookmarks: [b1] 0 3442585be8a6 "add alpha" bookmarks: [] $ cd .. $ hg clone -q gitremoterepo hggitlocalrepo $ cd hggitlocalrepo $ hggitstate 3 03769a650ded 55b133e1d558 "add delta" bookmarks: [master] 2 ca33a262eb46 d338971a96e2 "add gamma" bookmarks: [] 1 7fe02317c63d 9497a4ee62e1 "add beta" bookmarks: [b1] 0 ff7a2f2d8d70 7eeab2ea75ec "add alpha" bookmarks: [] $ cd .. No changes $ cd purehglocalrepo $ hg incoming -B comparing with $TESTTMP/hgremoterepo searching for changed bookmarks no changed bookmarks found [1] $ hg outgoing comparing with $TESTTMP/hgremoterepo searching for changes no changes found [1] $ hg outgoing -B comparing with $TESTTMP/hgremoterepo searching for changed bookmarks no changed bookmarks found [1] $ hg push pushing to $TESTTMP/hgremoterepo searching for changes no changes found [1] $ cd .. $ cd hggitlocalrepo $ hg incoming -B comparing with $TESTTMP/gitremoterepo searching for changed bookmarks no changed bookmarks found [1] $ hg outgoing comparing with $TESTTMP/gitremoterepo searching for changes no changes found [1] $ hg outgoing -B comparing with $TESTTMP/gitremoterepo searching for changed bookmarks no changed bookmarks found [1] $ hg push pushing to $TESTTMP/gitremoterepo searching for changes no changes found [1] $ cd .. Bookmarks on existing revs: - change b1 on local repo - introduce b2 on local repo - introduce b3 on remote repo Bookmarks on new revs - introduce b4 on a new rev on the remote $ cd hgremoterepo $ hg bookmark -r master b3 $ hg bookmark -r master b4 $ hg update -q b4 $ echo epsilon > epsilon; hg add epsilon; hgcommit -m 'add epsilon' $ hgstate 4 d979bb8e0fbb "add epsilon" bookmarks: [b4] 3 fc2664cac217 "add delta" bookmarks: [b3 master] 2 d85ced7ae9d6 "add gamma" bookmarks: [] 1 7bcd915dc873 "add beta" bookmarks: [b1] 0 3442585be8a6 "add alpha" bookmarks: [] $ cd .. $ cd purehglocalrepo $ hg bookmark -fr 2 b1 $ hg bookmark -r 0 b2 $ hgstate 3 fc2664cac217 "add delta" bookmarks: [master] 2 d85ced7ae9d6 "add gamma" bookmarks: [b1] 1 7bcd915dc873 "add beta" bookmarks: [] 0 3442585be8a6 "add alpha" bookmarks: [b2] $ hg incoming -B comparing with $TESTTMP/hgremoterepo searching for changed bookmarks b3 fc2664cac217 b4 d979bb8e0fbb $ hg outgoing comparing with $TESTTMP/hgremoterepo searching for changes no changes found [1] As of 2.3, Mercurial's outgoing -B doesn't actually show changed bookmarks It only shows "new" bookmarks. Thus, b1 doesn't show up. This changed in 3.4 to start showing changed and deleted bookmarks again. $ hg outgoing -B | grep -v -E -w 'b1|b3|b4' comparing with $TESTTMP/hgremoterepo searching for changed bookmarks b2 3442585be8a6 $ cd .. $ cd gitremoterepo $ git branch b3 master $ git checkout -b b4 master Switched to a new branch 'b4' $ echo epsilon > epsilon $ git add epsilon $ gitcommit -m 'add epsilon' $ gitstate fcfd2c0 "add epsilon" refs: (*b4) (glob) 55b133e "add delta" refs: (master, b3) d338971 "add gamma" refs: 9497a4e "add beta" refs: (b1) 7eeab2e "add alpha" refs: $ cd .. $ cd hggitlocalrepo $ hg bookmark -fr 2 b1 $ hg bookmark -r 0 b2 $ hgstate 3 03769a650ded "add delta" bookmarks: [master] 2 ca33a262eb46 "add gamma" bookmarks: [b1] 1 7fe02317c63d "add beta" bookmarks: [] 0 ff7a2f2d8d70 "add alpha" bookmarks: [b2] $ hg incoming -B comparing with $TESTTMP/gitremoterepo searching for changed bookmarks b3 03769a650ded b4 fcfd2c0262db $ hg outgoing comparing with $TESTTMP/gitremoterepo searching for changes no changes found [1] As of 2.3, Mercurial's outgoing -B doesn't actually show changed bookmarks It only shows "new" bookmarks. Thus, b1 doesn't show up. This changed in 3.4 to start showing changed and deleted bookmarks again. $ hg outgoing -B | grep -v -E -w 'b1|b3|b4' comparing with $TESTTMP/gitremoterepo searching for changed bookmarks b2 ff7a2f2d8d70 $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-branch-bookmark-suffix.t0000644000000000000000000001006412653777310022276 0ustar 00000000000000bail if the user does not have dulwich $ python -c 'import dulwich, dulwich.repo' || exit 80 $ echo "[extensions]" >> $HGRCPATH $ echo "hggit=$(echo $(dirname $TESTDIR))/hggit" >> $HGRCPATH $ echo 'hgext.graphlog =' >> $HGRCPATH $ echo "[git]" >> $HGRCPATH $ echo "branch_bookmark_suffix=_bookmark" >> $HGRCPATH $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE $ count=10 $ commit() > { > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000" > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error" > count=`expr $count + 1` > } $ hgcommit() > { > HGDATE="2007-01-01 00:00:$count +0000" > hg commit -d "$HGDATE" "$@" >/dev/null 2>/dev/null || echo "hg commit error" > count=`expr $count + 1` > } $ git config --global push.default matching $ git init --bare gitrepo1 Initialized empty Git repository in $TESTTMP/gitrepo1/ $ hg init hgrepo $ cd hgrepo $ hg branch -q branch1 $ hg bookmark branch1_bookmark $ echo f1 > f1 $ hg add f1 $ hgcommit -m "add f1" $ hg branch -q branch2 $ hg bookmark branch2_bookmark $ echo f2 > f2 $ hg add f2 $ hgcommit -m "add f2" $ hg log --graph @ changeset: 1:600de9b6d498 | branch: branch2 | bookmark: branch2_bookmark | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add f2 | o changeset: 0:40a840c1f8ae branch: branch1 bookmark: branch1_bookmark user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add f1 $ hg push ../gitrepo1 pushing to ../gitrepo1 searching for changes adding objects added 2 commits with 2 trees and 2 blobs $ cd .. $ cd gitrepo1 $ git symbolic-ref HEAD refs/heads/branch1 $ git branch * branch1 branch2 $ cd .. $ git clone gitrepo1 gitrepo2 Cloning into 'gitrepo2'... done. $ cd gitrepo2 $ git checkout branch1 2>&1 | grep -v 'up-to-date' Already on 'branch1' $ echo g1 >> f1 $ git add f1 $ commit -m "append f1" $ git checkout branch2 Switched to a new branch 'branch2' Branch branch2 set up to track remote branch branch2 from origin. $ echo g2 >> f2 $ git add f2 $ commit -m "append f2" $ git push origin To $TESTTMP/gitrepo1 bbfe79a..d8aef79 branch1 -> branch1 288e92b..f8f8de5 branch2 -> branch2 make sure the commit doesn't have an HG:rename-source annotation $ git cat-file commit d8aef79 tree b5644d8071b8a5963b8d1fd089fb3fdfb14b1203 parent bbfe79acf62dcd6a97763e2a67424a6de8a96941 author test 1167609612 +0000 committer test 1167609612 +0000 append f1 $ cd .. $ cd hgrepo $ hg pull ../gitrepo1 pulling from ../gitrepo1 importing git objects into hg (run 'hg heads' to see heads) $ hg log --graph o changeset: 3:ae8eb55f7090 | bookmark: branch2_bookmark | tag: default/branch2 | tag: tip | parent: 1:600de9b6d498 | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: append f2 | | o changeset: 2:8211cade99e4 | | bookmark: branch1_bookmark | | tag: default/branch1 | | parent: 0:40a840c1f8ae | | user: test | | date: Mon Jan 01 00:00:12 2007 +0000 | | summary: append f1 | | @ | changeset: 1:600de9b6d498 |/ branch: branch2 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add f2 | o changeset: 0:40a840c1f8ae branch: branch1 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add f1 $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-clone.t0000644000000000000000000000456412653777310017044 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git tag alpha $ git checkout -b beta 2>&1 | sed s/\'/\"/g Switched to a new branch "beta" $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. clone a tag $ hg clone -r alpha gitrepo hgrepo-a | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-a log --graph @ changeset: 0:ff7a2f2d8d70 bookmark: master tag: alpha tag: default/master tag: tip user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha clone a branch $ hg clone -r beta gitrepo hgrepo-b | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-b log --graph @ changeset: 1:7fe02317c63d | bookmark: beta | tag: default/beta | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 bookmark: master tag: alpha tag: default/master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha clone with mapsavefreq set $ rm -rf hgrepo-b $ hg clone -r beta gitrepo hgrepo-b --config hggit.mapsavefrequency=1 --debug | grep saving saving mapfile saving mapfile Make sure that a deleted .hgsubstate does not confuse hg-git $ cd gitrepo $ echo 'HASH random' > .hgsubstate $ git add .hgsubstate $ fn_git_commit -m 'add bogus .hgsubstate' $ git rm -q .hgsubstate $ fn_git_commit -m 'remove bogus .hgsubstate' $ cd .. $ hg clone -r beta gitrepo hgrepo-c importing git objects into hg updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --cwd hgrepo-c status clone empty repo $ git init empty Initialized empty Git repository in $TESTTMP/empty/.git/ $ hg clone empty emptyhg updating to branch default 0 files updated, 0 files merged, 0 files removed, 0 files unresolved durin42-hg-git-53d514c9c7e6/tests/test-conflict-1.t0000644000000000000000000000367712653777310017707 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hgrepo1 $ cd hgrepo1 $ echo A > afile $ hg add afile $ hg ci -m "origin" $ echo B > afile $ hg ci -m "A->B" $ hg up -r0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo C > afile $ hg ci -m "A->C" created new head $ hg merge -r1 2>&1 | sed 's/-C ./-C/' | egrep -v '^merging afile' | sed 's/incomplete.*/failed!/' warning: conflicts.* (re) 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon resolve using first parent $ echo C > afile $ hg resolve -m afile | egrep -v 'no more unresolved files' || true $ hg ci -m "merge to C" $ hg log --graph --style compact | sed 's/\[.*\]//g' @ 3:2,1 6c53bc0f062f 1970-01-01 00:00 +0000 test |\ merge to C | | | o 2:0 ea82b67264a1 1970-01-01 00:00 +0000 test | | A->C | | o | 1 7205e83b5a3f 1970-01-01 00:00 +0000 test |/ A->B | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin $ cd .. $ git init --bare gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/ $ cd hgrepo1 $ hg bookmark -r tip master $ hg push -r master ../gitrepo pushing to ../gitrepo searching for changes adding objects added 4 commits with 3 trees and 3 blobs $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved expect the same revision ids as above $ hg -R hgrepo2 log --graph --style compact | sed 's/\[.*\]//g' @ 3:1,2 6c53bc0f062f 1970-01-01 00:00 +0000 test |\ merge to C | | | o 2:0 7205e83b5a3f 1970-01-01 00:00 +0000 test | | A->B | | o | 1 ea82b67264a1 1970-01-01 00:00 +0000 test |/ A->C | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin durin42-hg-git-53d514c9c7e6/tests/test-conflict-2.t0000644000000000000000000000370012653777310017673 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hgrepo1 $ cd hgrepo1 $ echo A > afile $ hg add afile $ hg ci -m "origin" $ echo B > afile $ hg ci -m "A->B" $ hg up -r0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo C > afile $ hg ci -m "A->C" created new head $ hg merge -r1 2>&1 | sed 's/-C ./-C/' | egrep -v '^merging afile' | sed 's/incomplete.*/failed!/' warning: conflicts.* (re) 0 files updated, 0 files merged, 0 files removed, 1 files unresolved use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon resolve using second parent $ echo B > afile $ hg resolve -m afile | egrep -v 'no more unresolved files' || true $ hg ci -m "merge to B" $ hg log --graph --style compact | sed 's/\[.*\]//g' @ 3:2,1 120385945d08 1970-01-01 00:00 +0000 test |\ merge to B | | | o 2:0 ea82b67264a1 1970-01-01 00:00 +0000 test | | A->C | | o | 1 7205e83b5a3f 1970-01-01 00:00 +0000 test |/ A->B | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin $ cd .. $ git init --bare gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/ $ cd hgrepo1 $ hg bookmark -r tip master $ hg push -r master ../gitrepo pushing to ../gitrepo searching for changes adding objects added 4 commits with 3 trees and 3 blobs $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved expect the same revision ids as above $ hg -R hgrepo2 log --graph --style compact | sed 's/\[.*\]//g' @ 3:1,2 120385945d08 1970-01-01 00:00 +0000 test |\ merge to B | | | o 2:0 7205e83b5a3f 1970-01-01 00:00 +0000 test | | A->B | | o | 1 ea82b67264a1 1970-01-01 00:00 +0000 test |/ A->C | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin durin42-hg-git-53d514c9c7e6/tests/test-convergedmerge.t0000644000000000000000000000372612653777310020737 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hgrepo1 $ cd hgrepo1 $ echo A > afile $ hg add afile $ hg ci -m "origin" $ echo B > afile $ hg ci -m "A->B" $ echo C > afile $ hg ci -m "B->C" $ hg up -r0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo C > afile $ hg ci -m "A->C" created new head $ hg merge -r2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m "merge" $ hg log --graph --style compact | sed 's/\[.*\]//g' @ 4:3,2 eaa21d002113 1970-01-01 00:00 +0000 test |\ merge | | | o 3:0 ea82b67264a1 1970-01-01 00:00 +0000 test | | A->C | | o | 2 0dbe4ac1a758 1970-01-01 00:00 +0000 test | | B->C | | o | 1 7205e83b5a3f 1970-01-01 00:00 +0000 test |/ A->B | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin $ cd .. $ git init --bare gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/ $ cd hgrepo1 $ hg bookmark -r4 master $ hg push -r master ../gitrepo pushing to ../gitrepo searching for changes adding objects added 5 commits with 3 trees and 3 blobs $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved expect the same revision ids as above $ hg -R hgrepo2 log --graph --style compact | sed 's/\[.*\]//g' @ 4:1,3 eaa21d002113 1970-01-01 00:00 +0000 test |\ merge | | | o 3 0dbe4ac1a758 1970-01-01 00:00 +0000 test | | B->C | | | o 2:0 7205e83b5a3f 1970-01-01 00:00 +0000 test | | A->B | | o | 1 ea82b67264a1 1970-01-01 00:00 +0000 test |/ A->C | o 0 5d1a6b64f9d0 1970-01-01 00:00 +0000 test origin $ hg -R hgrepo2 gverify verifying rev eaa21d002113 against git commit fb8c9e2afe5418cfff337eeed79fad5dd58826f0 durin42-hg-git-53d514c9c7e6/tests/test-empty-working-tree.t0000644000000000000000000000202712653777310021505 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ git commit --allow-empty -m empty >/dev/null 2>/dev/null || echo "git commit error" $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log -r tip --template 'files: {files}\n' files: $ hg gverify verifying rev 01708ca54a8f against git commit 678256865a8c85ae925bf834369264193c88f8de $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 1 commits with 1 trees and 0 blobs $ cd .. $ git --git-dir=gitrepo2 log --pretty=medium commit 678256865a8c85ae925bf834369264193c88f8de Author: test Date: Mon Jan 1 00:00:00 2007 +0000 empty durin42-hg-git-53d514c9c7e6/tests/test-encoding.t0000644000000000000000000001057412653777310017530 0ustar 00000000000000# -*- coding: utf-8 -*- Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo utf-8 encoded commit message $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add älphà' Create some commits using latin1 encoding The warning message changed in Git 1.8.0 $ . $TESTDIR/latin-1-encoding Warning: commit message (did|does) not conform to UTF-8. (re) You may want to amend it after fixing the message, or set the config variable i18n.commitencoding to the encoding your project uses. Warning: commit message (did|does) not conform to UTF-8. (re) You may want to amend it after fixing the message, or set the config variable i18n.commitencoding to the encoding your project uses. $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 4 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ HGENCODING=utf-8 hg log --graph --debug | grep -v 'phase:' | grep -v ': *author=' | grep -v ': *message=' @ changeset: 3:b8a0ac52f339ccd6d5729508bac4aee6e8b489d8 | bookmark: master | tag: default/master | tag: tip | parent: 2:8bc4d64940260d4a1e70b54c099d3a76c83ff41e | parent: -1:0000000000000000000000000000000000000000 | manifest: 3:ea49f93388380ead5601c8fcbfa187516e7c2ed8 | user: tést èncödîng | date: Mon Jan 01 00:00:13 2007 +0000 | files+: delta | extra: branch=default | extra: committer=test 1167609613 0 | extra: encoding=latin-1 | extra: hg-git-rename-source=git | description: | add d\xc3\xa9lt\xc3\xa0 (esc) | | o changeset: 2:8bc4d64940260d4a1e70b54c099d3a76c83ff41e | parent: 1:f35a3100b78e57a0f5e4589a438f952a14b26ade | parent: 1:(9f6268bfc9eb3956c5ab8752d7b983b0ffe57115|955b24cf6f8f293741d3f39110c6fe554c292533) (re) | manifest: 2:f580e7da3673c137370da2b931a1dee83590d7b4 | user: t\xc3\xa9st \xc3\xa8nc\xc3\xb6d\xc3\xaeng (esc) | date: Mon Jan 01 00:00:12 2007 +0000 | files+: gamma | extra: branch=default | extra: committer=test 1167609612 0 | extra: hg-git-rename-source=git | description: | add g\xc3\xa4mm\xc3\xa2 (esc) | | o changeset: 1:f35a3100b78e57a0f5e4589a438f952a14b26ade | parent: 0:87cd29b67a9159eec3b5495b0496ef717b2769f5 | parent: -1:0000000000000000000000000000000000000000 | manifest: 1:f0bd6fbafbaebe4bb59c35108428f6fce152431d | user: t\xc3\xa9st \xc3\xa8nc\xc3\xb6d\xc3\xaeng (esc) | date: Mon Jan 01 00:00:11 2007 +0000 | files+: beta | extra: branch=default | extra: committer=test 1167609611 0 | extra: hg-git-rename-source=git | description: | add beta | | o changeset: 0:87cd29b67a9159eec3b5495b0496ef717b2769f5 parent: -1:0000000000000000000000000000000000000000 parent: -1:0000000000000000000000000000000000000000 manifest: 0:8b8a0e87dfd7a0706c0524afa8ba67e20544cbf0 user: test date: Mon Jan 01 00:00:10 2007 +0000 files+: alpha extra: branch=default extra: hg-git-rename-source=git description: add \xc3\xa4lph\xc3\xa0 (esc) $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 4 commits with 4 trees and 4 blobs $ cd .. $ git --git-dir=gitrepo2 log --pretty=medium commit e85fef6b515d555e45124a5dc39a019cf8db9ff0 Author: t\xe9st \xe8nc\xf6d\xeeng (esc) Date: Mon Jan 1 00:00:13 2007 +0000 add d\xe9lt\xe0 (esc) commit bd576458238cbda49ffcfbafef5242e103f1bc24 Author: * (glob) Date: Mon Jan 1 00:00:12 2007 +0000 add g*mm* (glob) commit 7a7e86fc1b24db03109c9fe5da28b352de59ce90 Author: * (glob) Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 0530b75d8c203e10dc934292a6a4032c6e958a83 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add älphà durin42-hg-git-53d514c9c7e6/tests/test-extra.t0000644000000000000000000001364712653777310017071 0ustar 00000000000000Test that extra metadata (renames, copies, and other extra metadata) roundtrips across from hg to git $ . "$TESTDIR/testutil" $ git init -q gitrepo $ cd gitrepo $ touch a $ git add a $ fn_git_commit -ma $ git checkout -b not-master 2>&1 | sed s/\'/\"/g Switched to a new branch "not-master" $ cd .. $ hg clone -q gitrepo hgrepo $ cd hgrepo $ hg mv a b $ fn_hg_commit -mb $ hg up 0 | egrep -v '^\(leaving bookmark master\)$' 1 files updated, 0 files merged, 1 files removed, 0 files unresolved $ touch c $ hg add c $ fn_hg_commit -mc Rebase will add a rebase_source (The 'rebasing' is extra output in Mercurial 3.3+) $ hg --config extensions.rebase= rebase -s 1 -d 2 | grep -v '^rebasing ' saved backup bundle to $TESTTMP/*.hg (glob) $ hg up 2 1 files updated, 0 files merged, 1 files removed, 0 files unresolved Add a commit with multiple extra fields $ hg bookmark b1 $ touch d $ hg add d $ fn_hg_commitextra --field zzzzzzz=datazzz --field aaaaaaa=dataaaa $ hg log --graph --template "{rev} {node} {desc|firstline}\n{join(extras, ' ')}\n\n" @ 3 f01651cfcc9337fbd9700d5018ca637a2911ed28 | aaaaaaa=dataaaa branch=default zzzzzzz=datazzz | o 2 03f4cf3c429050e2204fb2bda3a0f93329bdf4fd b | branch=default rebase_source=4c7da7adf18b785726a7421ef0d585bb5762990d | o 1 a735dc0cd7cc0ccdbc16cfa4326b19c707c360f4 c | branch=default | o 0 aa9eb6424386df2b0638fe6f480c3767fdd0e6fd a branch=default hg-git-rename-source=git Make sure legacy extra (in commit message, after '--HG--') doesn't break $ hg push -r b1 --config git.debugextrainmessage=1 pushing to $TESTTMP/gitrepo searching for changes adding objects added 3 commits with 3 trees and 0 blobs adding reference refs/heads/b1 $ hg bookmark b2 $ hg mv c c2 $ hg mv d d2 $ fn_hg_commitextra --field yyyyyyy=datayyy --field bbbbbbb=databbb Test some nutty filenames $ hg book b3 $ hg mv c2 'c2 => c3' warning: filename contains '>', which is reserved on Windows: 'c2 => c3' $ fn_hg_commit -m 'test filename with arrow' $ hg mv 'c2 => c3' 'c3 => c4' warning: filename contains '>', which is reserved on Windows: 'c3 => c4' $ fn_hg_commit -m 'test filename with arrow 2' $ hg log --graph --template "{rev} {node} {desc|firstline}\n{join(extras, ' ')}\n\n" -l 3 @ 6 bca4ba69a6844c133b069e227dfa043d41e3c197 test filename with arrow 2 | branch=default | o 5 864caad1f3493032f8d06f44a89dc9f1c039b09f test filename with arrow | branch=default | o 4 58f855ae26f4930ce857e648d3dd949901cce817 | bbbbbbb=databbb branch=default yyyyyyy=datayyy | $ hg push -r b2 -r b3 pushing to $TESTTMP/gitrepo searching for changes adding objects added 3 commits with 3 trees and 0 blobs adding reference refs/heads/b2 adding reference refs/heads/b3 $ cd ../gitrepo $ git cat-file commit b1 tree 1b773a2eb70f29397356f8069c285394835ff85a parent 202f271eb3dcb7b767ce2af6cdad4114df62ff3f author test 1167609613 +0000 committer test 1167609613 +0000 --HG-- extra : aaaaaaa : dataaaa extra : zzzzzzz : datazzz $ git cat-file commit b2 tree 34ad62c6d6ad9464bfe62db5b3d2fa16aaa9fa9e parent 66fe706f6f4f08f0020323e6c49548d41bb00ff6 author test 1167609614 +0000 committer test 1167609614 +0000 HG:rename c:c2 HG:rename d:d2 HG:extra bbbbbbb:databbb HG:extra yyyyyyy:datayyy $ git cat-file commit b3 tree e63df52695f9b06e54b37e7ef60d0c43994de620 parent 6a66c937dea689a8bb2aa053bd91667fe4a7bfe8 author test 1167609616 +0000 committer test 1167609616 +0000 HG:rename c2%20%3D%3E%20c3:c3%20%3D%3E%20c4 test filename with arrow 2 $ cd ../gitrepo $ git checkout b1 Switched to branch 'b1' $ commit_sha=$(git rev-parse HEAD) $ tree_sha=$(git rev-parse HEAD^{tree}) There's no way to create a Git repo with extra metadata via the CLI. Dulwich lets you do that, though. >>> from dulwich.objects import Commit >>> from dulwich.porcelain import open_repo >>> repo = open_repo('.') >>> c = Commit() >>> c.author = 'test ' >>> c.author_time = 0 >>> c.author_timezone = 0 >>> c.committer = c.author >>> c.commit_time = 0 >>> c.commit_timezone = 0 >>> c.parents = ['$commit_sha'] >>> c.tree = '$tree_sha' >>> c.message = 'extra commit\n' >>> c.extra.extend([('zzz:zzz', 'data:zzz'), ('aaa:aaa', 'data:aaa'), ... ('HG:extra', 'hgaaa:dataaaa'), ... ('HG:extra', 'hgzzz:datazzz')]) >>> repo.object_store.add_object(c) >>> repo.refs.set_if_equals('refs/heads/master', None, c.id) True $ git cat-file commit master tree 1b773a2eb70f29397356f8069c285394835ff85a parent 66fe706f6f4f08f0020323e6c49548d41bb00ff6 author test 0 +0000 committer test 0 +0000 zzz:zzz data:zzz aaa:aaa data:aaa HG:extra hgaaa:dataaaa HG:extra hgzzz:datazzz extra commit $ cd .. $ hg clone -q gitrepo hgrepo2 $ cd hgrepo2 $ hg log --graph --template "{rev} {node} {desc|firstline}\n{join(extras, ' ')}\n\n" @ 7 e003ec989aaae23b3eb30d4423419fb4dc346089 test filename with arrow 2 | branch=default | o 6 a2e276bd9458cb7dc309230ec8064d544e4f0c68 test filename with arrow | branch=default | o 5 524e82e66b589f8b56bdd0679ad457a162ba16cd | bbbbbbb=databbb branch=default yyyyyyy=datayyy | | o 4 741081daa02c9023c8c5117771f59ef2308a575c extra commit |/ GIT0-zzz%3Azzz=data%3Azzz GIT1-aaa%3Aaaa=data%3Aaaa branch=default hgaaa=dataaaa hgzzz=datazzz | o 3 73fa4063c4b0f386fd6b59da693617dedb340b02 | aaaaaaa=dataaaa branch=default zzzzzzz=datazzz | o 2 98337758089f6efd29f48bcaf00d14184ed0771b b | branch=default rebase_source=4c7da7adf18b785726a7421ef0d585bb5762990d | o 1 92a46c8588a7cd504c369259ef631b2c14ef4e91 c | branch=default hg-git-rename-source=git | o 0 aa9eb6424386df2b0638fe6f480c3767fdd0e6fd a branch=default hg-git-rename-source=git durin42-hg-git-53d514c9c7e6/tests/test-file-removal.t0000644000000000000000000001715012653777310020321 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ mkdir foo $ echo blah > foo/bar $ git add foo $ fn_git_commit -m 'add foo' $ git rm alpha rm 'alpha' $ fn_git_commit -m 'remove alpha' $ git rm foo/bar rm 'foo/bar' $ fn_git_commit -m 'remove foo/bar' $ ln -s beta betalink $ git add betalink $ fn_git_commit -m 'add symlink to beta' replace symlink with file $ rm betalink $ echo betalink > betalink $ git add betalink $ fn_git_commit -m 'replace symlink with file' replace file with symlink $ rm betalink $ ln -s beta betalink $ git add betalink $ fn_git_commit -m 'replace file with symlink' $ git rm betalink rm 'betalink' $ fn_git_commit -m 'remove betalink' final manifest in git is just beta $ git ls-files beta $ git log --pretty=medium commit 5ee11eeae239d6a99df5a99901ec00ffafbcc46b Author: test Date: Mon Jan 1 00:00:18 2007 +0000 remove betalink commit 2c7b324faeccb1acf89c35b7ad38e7956f5705fa Author: test Date: Mon Jan 1 00:00:17 2007 +0000 replace file with symlink commit ff0478d2ecc2571d01eb6d406ac29e4e63e5d3d5 Author: test Date: Mon Jan 1 00:00:16 2007 +0000 replace symlink with file commit 5492e6e410e42df527956be945286cd1ae45acb8 Author: test Date: Mon Jan 1 00:00:15 2007 +0000 add symlink to beta commit b991de8952c482a7cd51162674ffff8474862218 Author: test Date: Mon Jan 1 00:00:14 2007 +0000 remove foo/bar commit b0edaf0adac19392cf2867498b983bc5192b41dd Author: test Date: Mon Jan 1 00:00:13 2007 +0000 remove alpha commit f2d0d5bfa905e12dee728b509b96cf265bb6ee43 Author: test Date: Mon Jan 1 00:00:12 2007 +0000 add foo commit 9497a4ee62e16ee641860d7677cdb2589ea15554 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log --graph @ changeset: 8:0995b8a0a943 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:18 2007 +0000 | summary: remove betalink | o changeset: 7:a316d3a96c89 | user: test | date: Mon Jan 01 00:00:17 2007 +0000 | summary: replace file with symlink | o changeset: 6:1804acb71f3e | user: test | date: Mon Jan 01 00:00:16 2007 +0000 | summary: replace symlink with file | o changeset: 5:e19c85becc87 | user: test | date: Mon Jan 01 00:00:15 2007 +0000 | summary: add symlink to beta | o changeset: 4:0d3086c3f8c3 | user: test | date: Mon Jan 01 00:00:14 2007 +0000 | summary: remove foo/bar | o changeset: 3:b2406125ef5c | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: remove alpha | o changeset: 2:8b3b2f4b4158 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add foo | o changeset: 1:7fe02317c63d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha make sure alpha is not in this manifest $ hg manifest -r 3 beta foo/bar make sure that only beta is in the manifest $ hg manifest beta $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 9 commits with 8 trees and 5 blobs $ cd .. $ git --git-dir=gitrepo2 log --pretty=medium commit 5ee11eeae239d6a99df5a99901ec00ffafbcc46b Author: test Date: Mon Jan 1 00:00:18 2007 +0000 remove betalink commit 2c7b324faeccb1acf89c35b7ad38e7956f5705fa Author: test Date: Mon Jan 1 00:00:17 2007 +0000 replace file with symlink commit ff0478d2ecc2571d01eb6d406ac29e4e63e5d3d5 Author: test Date: Mon Jan 1 00:00:16 2007 +0000 replace symlink with file commit 5492e6e410e42df527956be945286cd1ae45acb8 Author: test Date: Mon Jan 1 00:00:15 2007 +0000 add symlink to beta commit b991de8952c482a7cd51162674ffff8474862218 Author: test Date: Mon Jan 1 00:00:14 2007 +0000 remove foo/bar commit b0edaf0adac19392cf2867498b983bc5192b41dd Author: test Date: Mon Jan 1 00:00:13 2007 +0000 remove alpha commit f2d0d5bfa905e12dee728b509b96cf265bb6ee43 Author: test Date: Mon Jan 1 00:00:12 2007 +0000 add foo commit 9497a4ee62e16ee641860d7677cdb2589ea15554 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha test with rename detection enabled $ hg --config git.similarity=100 clone gitrepo hgreporenames | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgreporenames $ hg log --graph @ changeset: 8:0995b8a0a943 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:18 2007 +0000 | summary: remove betalink | o changeset: 7:a316d3a96c89 | user: test | date: Mon Jan 01 00:00:17 2007 +0000 | summary: replace file with symlink | o changeset: 6:1804acb71f3e | user: test | date: Mon Jan 01 00:00:16 2007 +0000 | summary: replace symlink with file | o changeset: 5:e19c85becc87 | user: test | date: Mon Jan 01 00:00:15 2007 +0000 | summary: add symlink to beta | o changeset: 4:0d3086c3f8c3 | user: test | date: Mon Jan 01 00:00:14 2007 +0000 | summary: remove foo/bar | o changeset: 3:b2406125ef5c | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: remove alpha | o changeset: 2:8b3b2f4b4158 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add foo | o changeset: 1:7fe02317c63d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha durin42-hg-git-53d514c9c7e6/tests/test-git-clone.t0000644000000000000000000000210712653777310017614 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo log --graph @ changeset: 1:7fe02317c63d | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha we should have some bookmarks $ hg -R hgrepo book * master 1:7fe02317c63d $ hg -R hgrepo gverify verifying rev 7fe02317c63d against git commit 9497a4ee62e16ee641860d7677cdb2589ea15554 durin42-hg-git-53d514c9c7e6/tests/test-git-submodules.t0000644000000000000000000003277412653777310020713 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo1 Initialized empty Git repository in $TESTTMP/gitrepo1/.git/ $ cd gitrepo1 $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ cd .. $ git init gitsubrepo Initialized empty Git repository in $TESTTMP/gitsubrepo/.git/ $ cd gitsubrepo $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. $ mkdir gitrepo2 $ cd gitrepo2 $ rmpwd="import sys; print sys.stdin.read().replace('$(dirname $(pwd))/', '')" different versions of git spell the dir differently. Older versions use the full path to the directory all the time, whereas newer version spell it sanely as it was given (eg . in a newer version, while older git will use the full normalized path for .) $ clonefilt='s/Cloning into/Initialized empty Git repository in/;s/in .*/in .../' $ git clone ../gitrepo1 . 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ git submodule add ../gitsubrepo subrepo 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ git commit -m 'add subrepo' | sed 's/, 0 deletions(-)//' [master e42b08b] add subrepo 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 subrepo $ cd subrepo $ echo gamma > gamma $ git add gamma $ fn_git_commit -m 'add gamma' $ cd .. $ git add subrepo $ git commit -m 'change subrepo commit' [master a000567] change subrepo commit 1 file changed, 1 insertion(+), 1 deletion(-) $ git submodule add ../gitsubrepo subrepo2 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ git commit -m 'add another subrepo' | sed 's/, 0 deletions(-)//' [master 6e21952] add another subrepo 2 files changed, 4 insertions(+) create mode 160000 subrepo2 remove one subrepo, replace with file $ git rm --cached subrepo rm 'subrepo' we'd ordinarily use sed here, but BSD sed doesn't support two-address formats like +2 -- so use grep with the stuff we want to keep $ grep 'submodule "subrepo2"' -A2 .gitmodules > .gitmodules-new $ mv .gitmodules-new .gitmodules $ git add .gitmodules $ git config --unset submodule.subrepo.url $ rm -rf subrepo $ echo subrepo > subrepo $ git add subrepo $ git commit -m 'replace subrepo with file' | sed 's/, 0 deletions(-)//' | sed 's/, 0 insertions(+)//' [master f6436a4] replace subrepo with file 2 files changed, 1 insertion(+), 4 deletions(-) mode change 160000 => 100644 subrepo replace file with subrepo -- apparently, git complains about the subrepo if the same name has existed at any point historically, so use alpha instead of subrepo $ git rm alpha rm 'alpha' $ git submodule add ../gitsubrepo alpha 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ git commit -m 'replace file with subrepo' | sed 's/, 0 deletions(-)//' | sed 's/, 0 insertions(+)//' [master 8817116] replace file with subrepo 2 files changed, 4 insertions(+), 1 deletion(-) mode change 100644 => 160000 alpha $ ln -s foo foolink $ git add foolink $ git commit -m 'add symlink' [master 2d1c135] add symlink 1 file changed, 1 insertion(+) create mode 120000 foolink replace symlink with subrepo $ git rm foolink rm 'foolink' $ git submodule add ../gitsubrepo foolink 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ git commit -m 'replace symlink with subrepo' | sed 's/, 0 deletions(-)//' | sed 's/, 0 insertions(+)//' [master e3288fa] replace symlink with subrepo 2 files changed, 4 insertions(+), 1 deletion(-) mode change 120000 => 160000 foolink replace subrepo with symlink $ cat > .gitmodules < [submodule "subrepo2"] > path = subrepo2 > url = ../gitsubrepo > [submodule "alpha"] > path = alpha > url = ../gitsubrepo > EOF $ git add .gitmodules $ git rm --cached foolink rm 'foolink' $ rm -rf foolink $ ln -s foo foolink $ git add foolink $ git commit -m 'replace subrepo with symlink' | sed 's/, 0 deletions(-)//' | sed 's/, 0 insertions(+)//' [master d283640] replace subrepo with symlink 2 files changed, 1 insertion(+), 4 deletions(-) mode change 160000 => 120000 foolink $ git show commit d28364013fe1a0fde56c0e1921e49ecdeee8571d Author: test Date: Mon Jan 1 00:00:12 2007 +0000 replace subrepo with symlink diff --git a/.gitmodules b/.gitmodules index b511494..813e20b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "alpha"] path = alpha url = ../gitsubrepo -[submodule "foolink"] - path = foolink - url = ../gitsubrepo diff --git a/foolink b/foolink deleted file mode 160000 index 6e4ad8d..0000000 --- a/foolink +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6e4ad8da50204560c00fa25e4987eb2e239029ba diff --git a/foolink b/foolink new file mode 120000 index 0000000..1910281 --- /dev/null +++ b/foolink @@ -0,0 +1 @@ +foo \ No newline at end of file $ git rm --cached subrepo2 rm 'subrepo2' $ git rm --cached alpha rm 'alpha' $ git rm .gitmodules rm '.gitmodules' $ git commit -m 'remove all subrepos' | sed 's/, 0 deletions(-)//' | sed 's/, 0 insertions(+)//' [master 15ba949] remove all subrepos 3 files changed, 8 deletions(-) delete mode 100644 .gitmodules delete mode 160000 alpha delete mode 160000 subrepo2 $ git log --pretty=oneline 15ba94929481c654814178aac1dbca06ae688718 remove all subrepos d28364013fe1a0fde56c0e1921e49ecdeee8571d replace subrepo with symlink e3288fa737d429a60637b3b6782cb25b8298bc00 replace symlink with subrepo 2d1c135447d11df4dfe96dd5d4f37926dc5c821d add symlink 88171163bf4795b5570924e51d5f8ede33f8bc28 replace file with subrepo f6436a472da00f581d8d257e9bbaf3c358a5e88c replace subrepo with file 6e219527869fa40eb6ffbdd013cd86d576b26b01 add another subrepo a000567ceefbd9a2ce364e0dea6e298010b02b6d change subrepo commit e42b08b3cb7069b4594a4ee1d9cb641ee47b2355 add subrepo 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 add alpha $ cd .. $ hg clone gitrepo2 hgrepo | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log --graph @ changeset: 9:9c3929c04f22 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: remove all subrepos | o changeset: 8:1b71dd3e6033 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace subrepo with symlink | o changeset: 7:e338dc0b9f64 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace symlink with subrepo | o changeset: 6:db94aa767571 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add symlink | o changeset: 5:87bae50d72cb | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace file with subrepo | o changeset: 4:33729ae46d57 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace subrepo with file | o changeset: 3:4d2f0f4fb53d | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add another subrepo | o changeset: 2:620c9d5e9a98 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: change subrepo commit | o changeset: 1:f20b40ad6da1 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add subrepo | o changeset: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ hg book * master 9:9c3929c04f22 (add subrepo) $ hg cat -r 1 .hgsubstate 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo $ hg cat -r 1 .hgsub subrepo = [git]../gitsubrepo $ hg gverify -r 1 verifying rev f20b40ad6da1 against git commit e42b08b3cb7069b4594a4ee1d9cb641ee47b2355 (change subrepo commit) $ hg cat -r 2 .hgsubstate aa2ead20c29b5cc6256408e1d9ef704870033afb subrepo $ hg cat -r 2 .hgsub subrepo = [git]../gitsubrepo $ hg gverify -r 2 verifying rev 620c9d5e9a98 against git commit a000567ceefbd9a2ce364e0dea6e298010b02b6d (add another subrepo) $ hg cat -r 3 .hgsubstate aa2ead20c29b5cc6256408e1d9ef704870033afb subrepo 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo2 $ hg cat -r 3 .hgsub subrepo = [git]../gitsubrepo subrepo2 = [git]../gitsubrepo $ hg gverify -r 3 verifying rev 4d2f0f4fb53d against git commit 6e219527869fa40eb6ffbdd013cd86d576b26b01 (replace subrepo with file) $ hg cat -r 4 .hgsubstate 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo2 $ hg cat -r 4 .hgsub subrepo2 = [git]../gitsubrepo $ hg manifest -r 4 .gitmodules .hgsub .hgsubstate alpha subrepo $ hg gverify -r 4 verifying rev 33729ae46d57 against git commit f6436a472da00f581d8d257e9bbaf3c358a5e88c (replace file with subrepo) $ hg cat -r 5 .hgsubstate 6e4ad8da50204560c00fa25e4987eb2e239029ba alpha 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo2 $ hg cat -r 5 .hgsub subrepo2 = [git]../gitsubrepo alpha = [git]../gitsubrepo $ hg manifest -r 5 .gitmodules .hgsub .hgsubstate subrepo $ hg gverify -r 5 verifying rev 87bae50d72cb against git commit 88171163bf4795b5570924e51d5f8ede33f8bc28 (replace symlink with subrepo) $ hg cat -r 7 .hgsub .hgsubstate subrepo2 = [git]../gitsubrepo alpha = [git]../gitsubrepo foolink = [git]../gitsubrepo 6e4ad8da50204560c00fa25e4987eb2e239029ba alpha 6e4ad8da50204560c00fa25e4987eb2e239029ba foolink 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo2 $ hg gverify -r 7 verifying rev e338dc0b9f64 against git commit e3288fa737d429a60637b3b6782cb25b8298bc00 (replace subrepo with symlink) $ hg cat -r 8 .hgsub .hgsubstate subrepo2 = [git]../gitsubrepo alpha = [git]../gitsubrepo 6e4ad8da50204560c00fa25e4987eb2e239029ba alpha 6e4ad8da50204560c00fa25e4987eb2e239029ba subrepo2 $ hg gverify -r 8 verifying rev 1b71dd3e6033 against git commit d28364013fe1a0fde56c0e1921e49ecdeee8571d (remove all subrepos) $ hg cat -r 9 .hgsub .hgsubstate .hgsub: no such file in rev 9c3929c04f22 .hgsubstate: no such file in rev 9c3929c04f22 [1] $ hg gverify -r 9 verifying rev 9c3929c04f22 against git commit 15ba94929481c654814178aac1dbca06ae688718 $ hg gclear clearing out the git cache data $ hg gexport $ cd .hg/git $ git log --pretty=oneline 15ba94929481c654814178aac1dbca06ae688718 remove all subrepos d28364013fe1a0fde56c0e1921e49ecdeee8571d replace subrepo with symlink e3288fa737d429a60637b3b6782cb25b8298bc00 replace symlink with subrepo 2d1c135447d11df4dfe96dd5d4f37926dc5c821d add symlink 88171163bf4795b5570924e51d5f8ede33f8bc28 replace file with subrepo f6436a472da00f581d8d257e9bbaf3c358a5e88c replace subrepo with file 6e219527869fa40eb6ffbdd013cd86d576b26b01 add another subrepo a000567ceefbd9a2ce364e0dea6e298010b02b6d change subrepo commit e42b08b3cb7069b4594a4ee1d9cb641ee47b2355 add subrepo 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 add alpha test with rename detection enabled -- simply checking that the Mercurial hashes are the same is enough $ cd ../../.. $ hg --config git.similarity=100 clone gitrepo2 hgreporenames | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgreporenames $ hg log --graph @ changeset: 9:9c3929c04f22 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: remove all subrepos | o changeset: 8:1b71dd3e6033 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace subrepo with symlink | o changeset: 7:e338dc0b9f64 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace symlink with subrepo | o changeset: 6:db94aa767571 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add symlink | o changeset: 5:87bae50d72cb | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace file with subrepo | o changeset: 4:33729ae46d57 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: replace subrepo with file | o changeset: 3:4d2f0f4fb53d | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add another subrepo | o changeset: 2:620c9d5e9a98 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: change subrepo commit | o changeset: 1:f20b40ad6da1 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add subrepo | o changeset: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha durin42-hg-git-53d514c9c7e6/tests/test-git-tags.t0000644000000000000000000000240012653777310017446 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ git config receive.denyCurrentBranch ignore $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ fn_git_tag alpha $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ fn_git_tag -a -m 'added tag beta' beta $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log --graph @ changeset: 1:5403d6137622 | bookmark: master | tag: beta | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 tag: alpha user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ echo beta-fix >> beta $ hg commit -m 'fix for beta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-git-workflow.t0000644000000000000000000000774612653777310020404 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hgrepo $ cd hgrepo $ echo alpha > alpha $ hg add alpha $ fn_hg_commit -m "add alpha" $ hg log --graph --debug | grep -v phase: @ changeset: 0:0221c246a56712c6aa64e5ee382244d8a471b1e2 tag: tip parent: -1:0000000000000000000000000000000000000000 parent: -1:0000000000000000000000000000000000000000 manifest: 0:8b8a0e87dfd7a0706c0524afa8ba67e20544cbf0 user: test date: Mon Jan 01 00:00:10 2007 +0000 files+: alpha extra: branch=default description: add alpha $ cd .. configure for use from git $ hg clone hgrepo gitrepo updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd gitrepo $ hg book master $ hg up null | egrep -v '^\(leaving bookmark master\)$' 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ echo "[git]" >> .hg/hgrc $ echo "intree = True" >> .hg/hgrc $ hg gexport do some work $ git config core.bare false $ git checkout master 2>&1 | sed s/\'/\"/g Already on "master" $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' get things back to hg $ hg gimport importing git objects into hg $ hg log --graph --debug | grep -v phase: o changeset: 1:9f124f3c1fc29a14f5eb027c24811b0ac9d5ff10 | bookmark: master | tag: tip | parent: 0:0221c246a56712c6aa64e5ee382244d8a471b1e2 | parent: -1:0000000000000000000000000000000000000000 | manifest: 1:f0bd6fbafbaebe4bb59c35108428f6fce152431d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | files+: beta | extra: branch=default | extra: hg-git-rename-source=git | description: | add beta | | o changeset: 0:0221c246a56712c6aa64e5ee382244d8a471b1e2 parent: -1:0000000000000000000000000000000000000000 parent: -1:0000000000000000000000000000000000000000 manifest: 0:8b8a0e87dfd7a0706c0524afa8ba67e20544cbf0 user: test date: Mon Jan 01 00:00:10 2007 +0000 files+: alpha extra: branch=default description: add alpha gimport should have updated the bookmarks as well $ hg bookmarks master 1:9f124f3c1fc2 gimport support for git.mindate $ cat >> .hg/hgrc << EOF > [git] > mindate = 2014-01-02 00:00:00 +0000 > EOF $ echo oldcommit > oldcommit $ git add oldcommit $ GIT_AUTHOR_DATE="2014-03-01 00:00:00 +0000" \ > GIT_COMMITTER_DATE="2009-01-01 00:00:00 +0000" \ > git commit -m oldcommit > /dev/null || echo "git commit error" $ hg gimport no changes found $ hg log --graph o changeset: 1:9f124f3c1fc2 | bookmark: master | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:0221c246a567 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ echo newcommit > newcommit $ git add newcommit $ GIT_AUTHOR_DATE="2014-01-01 00:00:00 +0000" \ > GIT_COMMITTER_DATE="2014-01-02 00:00:00 +0000" \ > git commit -m newcommit > /dev/null || echo "git commit error" $ hg gimport importing git objects into hg $ hg log --graph o changeset: 3:3d10b7289d79 | bookmark: master | tag: tip | user: test | date: Wed Jan 01 00:00:00 2014 +0000 | summary: newcommit | o changeset: 2:befdecd14df5 | user: test | date: Sat Mar 01 00:00:00 2014 +0000 | summary: oldcommit | o changeset: 1:9f124f3c1fc2 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:0221c246a567 user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha durin42-hg-git-53d514c9c7e6/tests/test-gitignore.t0000644000000000000000000000401312653777310017720 0ustar 00000000000000 $ python -c 'from mercurial.dirstate import rootcache' || exit 80 Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init $ touch foo $ touch foobar $ touch bar $ echo 'foo*' > .gitignore $ hg status ? .gitignore ? bar $ echo '*bar' > .gitignore $ hg status ? .gitignore ? foo $ mkdir dir $ touch dir/foo $ echo 'foo' > .gitignore $ hg status ? .gitignore ? bar ? foobar $ echo '/foo' > .gitignore $ hg status ? .gitignore ? bar ? dir/foo ? foobar $ rm .gitignore $ echo 'foo' > dir/.gitignore $ hg status ? bar ? dir/.gitignore ? foo ? foobar $ touch dir/bar $ echo 'bar' > .gitignore $ hg status ? .gitignore ? dir/.gitignore ? foo ? foobar $ echo '/bar' > .gitignore $ hg status ? .gitignore ? dir/.gitignore ? dir/bar ? foo ? foobar $ echo 'foo*' > .gitignore $ echo '!*bar' >> .gitignore $ hg status .gitignore: unsupported ignore pattern '!*bar' ? .gitignore ? bar ? dir/.gitignore ? dir/bar $ echo '.hg/' > .gitignore $ hg status ? .gitignore ? bar ? dir/.gitignore ? dir/bar ? foo ? foobar $ echo 'dir/.hg/' > .gitignore $ hg status ? .gitignore ? bar ? dir/.gitignore ? dir/bar ? foo ? foobar $ echo '.hg/foo' > .gitignore $ hg status ? .gitignore ? bar ? dir/.gitignore ? dir/bar ? foo ? foobar $ touch foo.hg $ echo 'foo.hg' > .gitignore $ hg status ? .gitignore ? bar ? dir/.gitignore ? dir/bar ? foo ? foobar $ rm foo.hg $ touch .hgignore $ hg status ? .gitignore ? .hgignore ? bar ? dir/.gitignore ? dir/bar ? dir/foo ? foo ? foobar $ echo 'syntax: re' > .hgignore $ echo 'foo.*$(?> .hgignore $ echo 'dir/foo' >> .hgignore $ hg status ? .gitignore ? .hgignore ? bar ? dir/.gitignore ? dir/bar ? foobar $ hg add .gitignore $ hg commit -m "add and commit .gitignore" $ rm .gitignore $ rm .hgignore $ hg status ! .gitignore ? bar ? dir/.gitignore ? dir/bar ? foo ? foobar durin42-hg-git-53d514c9c7e6/tests/test-help.t0000644000000000000000000000072712653777310016671 0ustar 00000000000000Tests that the various help files are properly registered Load commonly used test logic $ . "$TESTDIR/testutil" $ hg help | grep 'git' | sed 's/ */ /g' hggit push and pull from a Git server git Working with Git Repositories Mercurial 3.7+ uses single quotes $ hg help hggit | grep 'help git' | sed "s/'/\"/g" For more information and instructions, see "hg help git" $ hg help git | grep 'Working with Git Repositories' Working with Git Repositories durin42-hg-git-53d514c9c7e6/tests/test-hg-author.t0000644000000000000000000001630212653777310017633 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git checkout -b not-master Switched to a new branch 'not-master' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg co master | egrep -v '^\(activating bookmark master\)$' 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ echo beta > beta $ hg add beta $ fn_hg_commit -u "test" -m 'add beta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo gamma >> beta $ fn_hg_commit -u "test (comment)" -m 'modify beta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo gamma > gamma $ hg add gamma $ fn_hg_commit -u "" -m 'add gamma' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo delta > delta $ hg add delta $ fn_hg_commit -u "name" -m 'add delta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo epsilon > epsilon $ hg add epsilon $ fn_hg_commit -u "name zeta $ hg add zeta $ fn_hg_commit -u " test " -m 'add zeta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo eta > eta $ hg add eta $ fn_hg_commit -u "test < test@example.com >" -m 'add eta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ echo theta > theta $ hg add theta $ fn_hg_commit -u "test >test@example.com>" -m 'add theta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master $ hg log --graph @ changeset: 8:c5d1976ab12c | bookmark: master | tag: default/master | tag: tip | user: test >test@example.com> | date: Mon Jan 01 00:00:18 2007 +0000 | summary: add theta | o changeset: 7:0e2fb4d21667 | user: test < test@example.com > | date: Mon Jan 01 00:00:17 2007 +0000 | summary: add eta | o changeset: 6:faa3aae96199 | user: test | date: Mon Jan 01 00:00:16 2007 +0000 | summary: add zeta | o changeset: 5:2cf6ad5a1afc | user: name | date: Mon Jan 01 00:00:14 2007 +0000 | summary: add delta | o changeset: 3:6b854d65d0d6 | user: | date: Mon Jan 01 00:00:13 2007 +0000 | summary: add gamma | o changeset: 2:46303c652e79 | user: test (comment) | date: Mon Jan 01 00:00:12 2007 +0000 | summary: modify beta | o changeset: 1:47580592d3d6 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 8 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo2 log --graph @ changeset: 8:1fbf3aa91221 | bookmark: master | tag: default/master | tag: tip | user: test ?test@example.com | date: Mon Jan 01 00:00:18 2007 +0000 | summary: add theta | o changeset: 7:20310508f06d | user: test | date: Mon Jan 01 00:00:17 2007 +0000 | summary: add eta | o changeset: 6:e3d81af8a8c1 | user: test | date: Mon Jan 01 00:00:16 2007 +0000 | summary: add zeta | o changeset: 5:78f609fd208f | user: name | date: Mon Jan 01 00:00:15 2007 +0000 | summary: add epsilon | o changeset: 4:42fa61d57718 | user: name | date: Mon Jan 01 00:00:14 2007 +0000 | summary: add delta | o changeset: 3:6b854d65d0d6 | user: | date: Mon Jan 01 00:00:13 2007 +0000 | summary: add gamma | o changeset: 2:46303c652e79 | user: test (comment) | date: Mon Jan 01 00:00:12 2007 +0000 | summary: modify beta | o changeset: 1:47580592d3d6 | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ git --git-dir=gitrepo/.git log --pretty=medium master commit 2fe60ba69727981e6ede78be70354c3a9e30e21d Author: test ?test@example.com Date: Mon Jan 1 00:00:18 2007 +0000 add theta commit 9f2f7cafdbf2e467928db98de8275141001d3081 Author: test Date: Mon Jan 1 00:00:17 2007 +0000 add eta commit 172a6f8d8064d73dff7013e395a9fe3cfc3ff807 Author: test Date: Mon Jan 1 00:00:16 2007 +0000 add zeta commit 71badb8e343a7da391a9b5d98909fbd2ca7d78f2 Author: name Date: Mon Jan 1 00:00:15 2007 +0000 add epsilon commit 9a9ae7b7f310d4a1a3e732a747ca26f06934f8d8 Author: name Date: Mon Jan 1 00:00:14 2007 +0000 add delta commit e4149a32e81e380193f59aa8773349201b8ed7f7 Author: Date: Mon Jan 1 00:00:13 2007 +0000 add gamma commit fae95aef5889a80103c2fbd5d14ff6eb8c9daf93 Author: test ext:(%20%28comment%29) Date: Mon Jan 1 00:00:12 2007 +0000 modify beta commit 0f378ab6c2c6b5514bd873d3faf8ac4b8095b001 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha durin42-hg-git-53d514c9c7e6/tests/test-hg-branch.t0000644000000000000000000000505312653777310017567 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git checkout -b not-master Switched to a new branch 'not-master' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg co master | egrep -v '^\(activating bookmark master\)$' 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg mv alpha beta $ fn_hg_commit -m 'rename alpha to beta' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 0 blobs updating reference refs/heads/master $ hg branch gamma | grep -v 'permanent and global' marked working directory as branch gamma $ fn_hg_commit -m 'started branch gamma' $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 0 blobs updating reference refs/heads/master $ hg log --graph @ changeset: 2:400db38f4f64 | branch: gamma | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: started branch gamma | o changeset: 1:3baa67317a4d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: rename alpha to beta | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo2 log --graph o changeset: 2:400db38f4f64 | branch: gamma | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: started branch gamma | @ changeset: 1:3baa67317a4d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: rename alpha to beta | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha durin42-hg-git-53d514c9c7e6/tests/test-hg-tags-invalid.t0000644000000000000000000000646412653777310020723 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git checkout -b not-master Switched to a new branch 'not-master' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg co master | egrep -v '^\(activating bookmark master\)$' 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ fn_hg_tag alph#a $ fn_hg_tag bet*a $ fn_hg_tag 'gamm a' $ hg push pushing to $TESTTMP/gitrepo Skipping export of tag bet*a because it has invalid name as a git refname. searching for changes adding objects added 3 commits with 3 trees and 3 blobs updating reference refs/heads/master adding reference refs/tags/alph#a adding reference refs/tags/gamm_a $ hg log --graph @ changeset: 3:0950ab44ea23 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: Added tag gamm a for changeset 0b27ab2b3df6 | o changeset: 2:0b27ab2b3df6 | tag: gamm a | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: Added tag bet*a for changeset 491ceeb1b0f1 | o changeset: 1:491ceeb1b0f1 | tag: bet*a | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: Added tag alph#a for changeset ff7a2f2d8d70 | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: alph#a tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd .. $ cd gitrepo git should have only the valid tag alph#a but have full commit log including the missing invalid bet*a tag commit $ git tag -l alph#a gamm_a $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo2 log --graph @ changeset: 3:0950ab44ea23 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: Added tag gamm a for changeset 0b27ab2b3df6 | o changeset: 2:0b27ab2b3df6 | tag: gamm a | tag: gamm_a | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: Added tag bet*a for changeset 491ceeb1b0f1 | o changeset: 1:491ceeb1b0f1 | tag: bet*a | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: Added tag alph#a for changeset ff7a2f2d8d70 | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: alph#a tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha the tag should be in .hgtags $ cat hgrepo2/.hgtags ff7a2f2d8d7099694ae1e8b03838d40575bebb63 alph#a 491ceeb1b0f10d65d956dfcdd3470ac2bc2c96a8 bet*a 0b27ab2b3df69c6f7defd7040b93e539136db5be gamm a durin42-hg-git-53d514c9c7e6/tests/test-hg-tags.t0000644000000000000000000000425312653777310017271 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git checkout -b not-master Switched to a new branch 'not-master' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg co master | egrep -v '^\(activating bookmark master\)$' 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ fn_hg_tag alpha $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master adding reference refs/tags/alpha $ hg log --graph @ changeset: 1:e8b150f84560 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: Added tag alpha for changeset ff7a2f2d8d70 | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: alpha tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd .. $ cd gitrepo git should have the tag alpha $ git tag -l alpha $ cd .. $ hg clone gitrepo hgrepo2 | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo2 log --graph @ changeset: 1:e8b150f84560 | bookmark: master | tag: default/master | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: Added tag alpha for changeset ff7a2f2d8d70 | o changeset: 0:ff7a2f2d8d70 bookmark: not-master tag: alpha tag: default/not-master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha the tag should be in .hgtags $ cat hgrepo2/.hgtags ff7a2f2d8d7099694ae1e8b03838d40575bebb63 alpha durin42-hg-git-53d514c9c7e6/tests/test-http.t0000644000000000000000000000223112653777310016710 0ustar 00000000000000#require serve $ hg init test $ cd test $ echo foo>foo $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg $ echo foo>foo.d/foo $ echo bar>foo.d/bAr.hg.d/BaR $ echo bar>foo.d/baR.d.hg/bAR $ hg commit -A -m 1 adding foo adding foo.d/bAr.hg.d/BaR adding foo.d/baR.d.hg/bAR adding foo.d/foo $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log $ hg --config server.uncompressed=False serve -p $HGPORT1 -d --pid-file=../hg2.pid Test server address cannot be reused #if windows $ hg serve -p $HGPORT1 2>&1 abort: cannot start server at ':$HGPORT1': * (glob) [255] #else $ hg serve -p $HGPORT1 2>&1 abort: cannot start server at ':$HGPORT1': Address already in use [255] #endif $ cd .. $ cat hg1.pid hg2.pid >> $DAEMON_PIDS Load commonly used test logic $ . "$TESTDIR/testutil" Make sure that clone regular mercurial repos over http doesn't break $ hg clone http://localhost:$HGPORT/ copy 2>&1 requesting all changes adding changesets adding manifests adding file changes added 1 changesets with 4 changes to 4 files updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved durin42-hg-git-53d514c9c7e6/tests/test-illegal-contents.t0000644000000000000000000000466212653777310021207 0ustar 00000000000000Check for contents we should refuse to export to git repositories (or at least warn). Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hg $ cd hg $ mkdir -p .git/hooks $ cat > .git/hooks/post-update < #!/bin/sh > echo pwned > EOF $ hg addremove adding .git/hooks/post-update $ hg ci -m "we should refuse to export this" $ hg book master $ hg gexport abort: Refusing to export likely-dangerous path '.git/hooks/post-update' (If you need to continue, read about CVE-2014-9390 and then set '[git] blockdotgit = false' in your hgrc.) [255] $ cd .. $ rm -rf hg $ hg init hg $ cd hg $ mkdir -p nested/.git/hooks/ $ cat > nested/.git/hooks/post-update < #!/bin/sh > echo pwnd > EOF $ chmod +x nested/.git/hooks/post-update $ hg addremove adding nested/.git/hooks/post-update $ hg ci -m "also refuse to export this" $ hg book master $ hg gexport abort: Refusing to export likely-dangerous path 'nested/.git/hooks/post-update' (If you need to continue, read about CVE-2014-9390 and then set '[git] blockdotgit = false' in your hgrc.) [255] We can override if needed: $ hg --config git.blockdotgit=false gexport warning: path 'nested/.git/hooks/post-update' contains a potentially dangerous path component. It may not be legal to check out in Git. It may also be rejected by some git server configurations. $ cd .. $ git clone hg/.hg/git git Cloning into 'git'... done. error: Invalid path 'nested/.git/hooks/post-update' Now check something that case-folds to .git, which might let you own Mac users: $ cd .. $ rm -rf hg $ hg init hg $ cd hg $ mkdir -p .GIT/hooks/ $ cat > .GIT/hooks/post-checkout < #!/bin/sh > echo pwnd > EOF $ chmod +x .GIT/hooks/post-checkout $ hg addremove adding .GIT/hooks/post-checkout $ hg ci -m "also refuse to export this" $ hg book master $ hg gexport $ cd .. And the NTFS case: $ cd .. $ rm -rf hg $ hg init hg $ cd hg $ mkdir -p GIT~1/hooks/ $ cat > GIT~1/hooks/post-checkout < #!/bin/sh > echo pwnd > EOF $ chmod +x GIT~1/hooks/post-checkout $ hg addremove adding GIT~1/hooks/post-checkout $ hg ci -m "also refuse to export this" $ hg book master $ hg gexport abort: Refusing to export likely-dangerous path 'GIT~1/hooks/post-checkout' (If you need to continue, read about CVE-2014-9390 and then set '[git] blockdotgit = false' in your hgrc.) [255] $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-incoming.t0000644000000000000000000000732512653777310017545 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ cd .. $ hg init hgrepo-empty $ hg -R hgrepo-empty incoming gitrepo | grep -v 'no changes found' comparing with gitrepo changeset: 0:7eeab2ea75ec bookmark: master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo incoming | grep -v 'no changes found' comparing with $TESTTMP/gitrepo $ cd gitrepo $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. $ hg -R hgrepo incoming | grep -v 'no changes found' comparing with $TESTTMP/gitrepo changeset: 1:9497a4ee62e1 bookmark: master user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta $ cd gitrepo $ git checkout -b b1 HEAD^ Switched to a new branch 'b1' $ mkdir d $ echo gamma > d/gamma $ git add d/gamma $ fn_git_commit -m'add d/gamma' $ git tag t1 $ echo gamma 2 >> d/gamma $ git add d/gamma $ fn_git_commit -m'add d/gamma line 2' $ cd ../hgrepo $ hg incoming -p | grep -v 'no changes found' comparing with $TESTTMP/gitrepo changeset: 1:9497a4ee62e1 bookmark: master user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta diff -r ff7a2f2d8d70 -r 9497a4ee62e1 beta --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/beta Mon Jan 01 00:00:11 2007 +0000 @@ -0,0 +1,1 @@ +beta changeset: 2:9865e289be73 tag: t1 parent: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add d/gamma diff -r ff7a2f2d8d70 -r 9865e289be73 d/gamma --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/d/gamma Mon Jan 01 00:00:12 2007 +0000 @@ -0,0 +1,1 @@ +gamma changeset: 3:5202f48c20c9 bookmark: b1 user: test date: Mon Jan 01 00:00:13 2007 +0000 summary: add d/gamma line 2 diff -r 9865e289be73 -r 5202f48c20c9 d/gamma --- a/d/gamma Mon Jan 01 00:00:12 2007 +0000 +++ b/d/gamma Mon Jan 01 00:00:13 2007 +0000 @@ -1,1 +1,2 @@ gamma +gamma 2 incoming -r $ hg incoming -r master | grep -v 'no changes found' comparing with $TESTTMP/gitrepo changeset: 1:9497a4ee62e1 bookmark: master user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta $ hg incoming -r b1 | grep -v 'no changes found' comparing with $TESTTMP/gitrepo changeset: 1:9865e289be73 tag: t1 user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add d/gamma changeset: 2:5202f48c20c9 bookmark: b1 user: test date: Mon Jan 01 00:00:13 2007 +0000 summary: add d/gamma line 2 $ hg incoming -r t1 | grep -v 'no changes found' comparing with $TESTTMP/gitrepo changeset: 1:9865e289be73 tag: t1 user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add d/gamma nothing incoming after pull "adding remote bookmark" message was added in Mercurial 2.3 $ hg pull | grep -v "adding remote bookmark" pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg heads' to see heads, 'hg merge' to merge) $ hg incoming | grep -v 'no changes found' comparing with $TESTTMP/gitrepo durin42-hg-git-53d514c9c7e6/tests/test-keywords.t0000644000000000000000000000241712653777310017606 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ echo gamma > gamma $ hg add gamma $ hg commit -m 'add gamma' $ hg log --template "{rev} {node} {node|short} {gitnode} {gitnode|short}\n" 2 168eb1ee8b3c04e6723c9330327b0eec1e36577f 168eb1ee8b3c 1 7fe02317c63d9ee324d4b5df7c9296085162da1b 7fe02317c63d 9497a4ee62e16ee641860d7677cdb2589ea15554 9497a4ee62e1 0 ff7a2f2d8d7099694ae1e8b03838d40575bebb63 ff7a2f2d8d70 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 7eeab2ea75ec $ hg log --template "fromgit {rev}\n" --rev "fromgit()" fromgit 0 fromgit 1 $ hg log --template "gitnode_existsA {rev}\n" --rev "gitnode(9497a4ee62e16ee641860d7677cdb2589ea15554)" gitnode_existsA 1 $ hg log --template "gitnode_existsB {rev}\n" --rev "gitnode(7eeab2ea75ec)" gitnode_existsB 0 $ hg log --template "gitnode_notexists {rev}\n" --rev "gitnode(1234567890ab)" durin42-hg-git-53d514c9c7e6/tests/test-merge.t0000644000000000000000000000455712653777310017045 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git checkout -b beta 2>&1 | sed s/\'/\"/g Switched to a new branch "beta" $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ git checkout master 2>&1 | sed s/\'/\"/g Switched to branch "master" $ echo gamma > gamma $ git add gamma $ fn_git_commit -m 'add gamma' clean merge $ git merge beta | sed "s/the '//;s/' strategy//" | sed 's/^Merge.*recursive.*$/Merge successful/' | sed 's/files/file/;s/insertions/insertion/;s/, 0 deletions.*//' | sed 's/| */| /' Merge successful beta | 1 + 1 file changed, 1 insertion(+) create mode 100644 beta $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo clear the cache to be sure it is regenerated correctly $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 4 commits with 4 trees and 3 blobs $ cd .. git log in repo pushed from hg $ git --git-dir=gitrepo2 log --pretty=medium master | sed 's/\.\.\.//g' commit 5806851511aaf3bfe813ae3a86c5027165fa9b96 Merge: e5023f9 9497a4e Author: test Date: Mon Jan 1 00:00:12 2007 +0000 Merge branch 'beta' commit e5023f9e5cb24fdcec7b6c127cec45d8888e35a9 Author: test Date: Mon Jan 1 00:00:12 2007 +0000 add gamma commit 9497a4ee62e16ee641860d7677cdb2589ea15554 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha $ git --git-dir=gitrepo2 log --pretty=medium beta | sed 's/\.\.\.//g' commit 9497a4ee62e16ee641860d7677cdb2589ea15554 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha durin42-hg-git-53d514c9c7e6/tests/test-octopus.t0000644000000000000000000001146212653777310017433 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git checkout -b branch1 2>&1 | sed s/\'/\"/g Switched to a new branch "branch1" $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ git checkout -b branch2 master 2>&1 | sed s/\'/\"/g Switched to a new branch "branch2" $ echo gamma > gamma $ git add gamma $ fn_git_commit -m 'add gamma' $ git checkout -b branch3 master 2>&1 | sed s/\'/\"/g Switched to a new branch "branch3" $ echo epsilon > epsilon $ git add epsilon $ fn_git_commit -m 'add epsilon' $ git checkout -b branch4 master 2>&1 | sed s/\'/\"/g Switched to a new branch "branch4" $ echo zeta > zeta $ git add zeta $ fn_git_commit -m 'add zeta' $ git checkout master 2>&1 | sed s/\'/\"/g Switched to branch "master" $ echo delta > delta $ git add delta $ fn_git_commit -m 'add delta' $ git merge branch1 branch2 | sed "s/the '//;s/' strategy//" | sed 's/^Merge.*octopus.*$/Merge successful/;s/, 0 deletions.*//' | sed 's/| */| /' Trying simple merge with branch1 Trying simple merge with branch2 Merge successful beta | 1 + gamma | 1 + 2 files changed, 2 insertions(+) create mode 100644 beta create mode 100644 gamma $ git merge branch3 branch4 | sed "s/the '//;s/' strategy//" | sed 's/^Merge.*octopus.*$/Merge successful/;s/, 0 deletions.*//' | sed 's/| */| /' Trying simple merge with branch3 Trying simple merge with branch4 Merge successful epsilon | 1 + zeta | 1 + 2 files changed, 2 insertions(+) create mode 100644 epsilon create mode 100644 zeta $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 6 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log --graph --style compact | sed 's/\[.*\]//g' @ 9:7,8 307506d6ae8a 2007-01-01 00:00 +0000 test |\ Merge branches 'branch3' and 'branch4' | | | o 8:3,4 2b07220e422e 2007-01-01 00:00 +0000 test | |\ Merge branches 'branch3' and 'branch4' | | | o | | 7:5,6 ccf2d65d982c 2007-01-01 00:00 +0000 test |\ \ \ Merge branches 'branch1' and 'branch2' | | | | | o | | 6:1,2 690b40256117 2007-01-01 00:00 +0000 test | |\ \ \ Merge branches 'branch1' and 'branch2' | | | | | o | | | | 5:0 e459c0629ca4 2007-01-01 00:00 +0000 test | | | | | add delta | | | | | +-------o 4:0 e857c9a04474 2007-01-01 00:00 +0000 test | | | | add zeta | | | | +-----o 3:0 0071dec0de0e 2007-01-01 00:00 +0000 test | | | add epsilon | | | +---o 2:0 205a004356ef 2007-01-01 00:00 +0000 test | | add gamma | | | o 1 7fe02317c63d 2007-01-01 00:00 +0000 test |/ add beta | o 0 ff7a2f2d8d70 2007-01-01 00:00 +0000 test add alpha $ hg gverify -r 9 verifying rev 307506d6ae8a against git commit b32ff845df61df998206b630e4370a44f9b36845 $ hg gverify -r 8 abort: no git commit found for rev 2b07220e422e (if this is an octopus merge, verify against the last rev) [255] $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 8 commits with 8 trees and 6 blobs $ cd .. $ git --git-dir=gitrepo2 log --pretty=medium | sed s/\\.\\.\\.//g commit b32ff845df61df998206b630e4370a44f9b36845 Merge: 9ac68f9 7e9cd9f e695849 Author: test Date: Mon Jan 1 00:00:15 2007 +0000 Merge branches 'branch3' and 'branch4' commit 9ac68f982ae7426d9597ff16c74afb4e6053c582 Merge: d40f375 9497a4e e5023f9 Author: test Date: Mon Jan 1 00:00:15 2007 +0000 Merge branches 'branch1' and 'branch2' commit d40f375a81b7d033e92cbad89487115fe2dd472f Author: test Date: Mon Jan 1 00:00:15 2007 +0000 add delta commit e695849087f6c320c1a447620492b29a82ca41b1 Author: test Date: Mon Jan 1 00:00:14 2007 +0000 add zeta commit 7e9cd9f90b6d2c60579375eb796ce706d2d8bbe6 Author: test Date: Mon Jan 1 00:00:13 2007 +0000 add epsilon commit e5023f9e5cb24fdcec7b6c127cec45d8888e35a9 Author: test Date: Mon Jan 1 00:00:12 2007 +0000 add gamma commit 9497a4ee62e16ee641860d7677cdb2589ea15554 Author: test Date: Mon Jan 1 00:00:11 2007 +0000 add beta commit 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 Author: test Date: Mon Jan 1 00:00:10 2007 +0000 add alpha durin42-hg-git-53d514c9c7e6/tests/test-outgoing.t0000644000000000000000000000700412653777310017567 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git branch alpha $ git show-ref 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 refs/heads/alpha 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 refs/heads/master $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg update -q master $ echo beta > beta $ hg add beta $ fn_hg_commit -m 'add beta' $ echo gamma > gamma $ hg add gamma $ fn_hg_commit -m 'add gamma' $ hg book -r 1 beta $ hg outgoing | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta changeset: 2:953796e1cfd8 bookmark: master tag: tip user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add gamma $ hg outgoing -r beta | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta $ hg outgoing -r master | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta changeset: 2:953796e1cfd8 bookmark: master tag: tip user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add gamma $ cd .. some more work on master from git $ cd gitrepo Check state of refs after outgoing $ git show-ref 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 refs/heads/alpha 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 refs/heads/master $ git checkout master 2>&1 | sed s/\'/\"/g Already on "master" $ echo delta > delta $ git add delta $ fn_git_commit -m "add delta" $ cd .. $ cd hgrepo this will fail # maybe we should try to make it work $ hg outgoing comparing with */gitrepo (glob) abort: branch 'refs/heads/master' changed on the server, please pull and merge before pushing [255] let's pull and try again $ hg pull 2>&1 | grep -v 'divergent bookmark' pulling from */gitrepo (glob) importing git objects into hg (run 'hg heads' to see heads, 'hg merge' to merge) $ hg outgoing | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta changeset: 2:953796e1cfd8 bookmark: master user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add gamma $ hg outgoing -r beta | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta $ hg outgoing -r master | grep -v 'searching for changes' comparing with */gitrepo (glob) changeset: 1:47580592d3d6 bookmark: beta user: test date: Mon Jan 01 00:00:11 2007 +0000 summary: add beta changeset: 2:953796e1cfd8 bookmark: master user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add gamma $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-pull-after-strip.t0000644000000000000000000000554412653777310021155 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git tag alpha $ git checkout -b beta 2>&1 | sed s/\'/\"/g Switched to a new branch "beta" $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. clone a tag $ hg clone -r alpha gitrepo hgrepo-a | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-a log --graph @ changeset: 0:ff7a2f2d8d70 bookmark: master tag: alpha tag: default/master tag: tip user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha clone a branch $ hg clone -r beta gitrepo hgrepo-b | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-b log --graph @ changeset: 1:7fe02317c63d | bookmark: beta | tag: default/beta | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | o changeset: 0:ff7a2f2d8d70 bookmark: master tag: alpha tag: default/master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd gitrepo $ echo beta line 2 >> beta $ git add beta $ fn_git_commit -m 'add to beta' $ cd .. $ cd hgrepo-b $ hg strip tip 2>&1 | grep -v saving | grep -v backup 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg pull -r beta pulling from $TESTTMP/gitrepo importing git objects into hg abort: you appear to have run strip - please run hg git-cleanup [255] $ hg git-cleanup git commit map cleaned pull works after 'hg git-cleanup' "adding remote bookmark" message was added in Mercurial 2.3 $ hg pull -r beta | grep -v "adding remote bookmark" pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg update' to get a working copy) $ hg log --graph o changeset: 2:cc1e605d90db | bookmark: beta | tag: default/beta | tag: tip | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add to beta | o changeset: 1:7fe02317c63d | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | @ changeset: 0:ff7a2f2d8d70 bookmark: master tag: alpha tag: default/master user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha $ cd .. durin42-hg-git-53d514c9c7e6/tests/test-pull.t0000644000000000000000000002435412653777310016717 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" set up a git repo with some commits, branches and a tag $ git init -q gitrepo $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git tag t_alpha $ git checkout -qb beta $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ git checkout -qb delta master $ echo delta > delta $ git add delta $ fn_git_commit -m 'add delta' $ cd .. pull a tag $ hg init hgrepo $ echo "[paths]" >> hgrepo/.hg/hgrc $ echo "default=$TESTTMP/gitrepo" >> hgrepo/.hg/hgrc $ hg -R hgrepo pull -r t_alpha pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg update' to get a working copy) $ hg -R hgrepo update t_alpha 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo log --graph @ changeset: 0:ff7a2f2d8d70 bookmark: master tag: default/master tag: t_alpha tag: tip user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha no-op pull $ hg -R hgrepo pull -r t_alpha pulling from $TESTTMP/gitrepo no changes found no-op pull with added bookmark $ cd gitrepo $ git checkout -qb epsilon t_alpha $ cd .. $ hg -R hgrepo pull -r epsilon pulling from $TESTTMP/gitrepo no changes found pull a branch $ hg -R hgrepo pull -r beta pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg update' to get a working copy) $ hg -R hgrepo log --graph o changeset: 1:7fe02317c63d | bookmark: beta | tag: default/beta | tag: tip | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | @ changeset: 0:ff7a2f2d8d70 bookmark: epsilon bookmark: master tag: default/epsilon tag: default/master tag: t_alpha user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha add another commit and tag to the git repo $ cd gitrepo $ git checkout -q beta $ git tag t_beta $ git checkout -q master $ echo gamma > gamma $ git add gamma $ fn_git_commit -m 'add gamma' $ cd .. pull everything else $ hg -R hgrepo pull pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg heads' to see heads, 'hg merge' to merge) $ hg -R hgrepo log --graph o changeset: 3:6f898ad1f3e1 | bookmark: master | tag: default/master | tag: tip | parent: 0:ff7a2f2d8d70 | user: test | date: Mon Jan 01 00:00:13 2007 +0000 | summary: add gamma | | o changeset: 2:678ebee93e38 |/ bookmark: delta | tag: default/delta | parent: 0:ff7a2f2d8d70 | user: test | date: Mon Jan 01 00:00:12 2007 +0000 | summary: add delta | | o changeset: 1:7fe02317c63d |/ bookmark: beta | tag: default/beta | tag: t_beta | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | @ changeset: 0:ff7a2f2d8d70 bookmark: epsilon tag: default/epsilon tag: t_alpha user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha add a merge to the git repo $ cd gitrepo $ git merge beta | sed 's/| */| /' Merge made by the 'recursive' strategy. beta | 1 + 1 file changed, 1 insertion(+) create mode 100644 beta $ cd .. pull the merge $ hg -R hgrepo pull pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg update' to get a working copy) $ hg -R hgrepo log --graph o changeset: 4:a02330f767a4 |\ bookmark: master | | tag: default/master | | tag: tip | | parent: 3:6f898ad1f3e1 | | parent: 1:7fe02317c63d | | user: test | | date: Mon Jan 01 00:00:13 2007 +0000 | | summary: Merge branch 'beta' | | | o changeset: 3:6f898ad1f3e1 | | parent: 0:ff7a2f2d8d70 | | user: test | | date: Mon Jan 01 00:00:13 2007 +0000 | | summary: add gamma | | | | o changeset: 2:678ebee93e38 | |/ bookmark: delta | | tag: default/delta | | parent: 0:ff7a2f2d8d70 | | user: test | | date: Mon Jan 01 00:00:12 2007 +0000 | | summary: add delta | | o | changeset: 1:7fe02317c63d |/ bookmark: beta | tag: default/beta | tag: t_beta | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | @ changeset: 0:ff7a2f2d8d70 bookmark: epsilon tag: default/epsilon tag: t_alpha user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha pull with wildcards $ cd gitrepo $ git checkout -qb releases/v1 master $ echo zeta > zeta $ git add zeta $ fn_git_commit -m 'add zeta' $ git checkout -qb releases/v2 master $ echo eta > eta $ git add eta $ fn_git_commit -m 'add eta' $ git checkout -qb notreleases/v1 master $ echo theta > theta $ git add theta $ fn_git_commit -m 'add theta' ensure that releases/v1 and releases/v2 are pulled but not notreleases/v1 $ cd .. $ hg -R hgrepo pull -r 'releases/*' pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg heads .' to see heads, 'hg merge' to merge) $ hg -R hgrepo log --graph o changeset: 6:a3f95e150b0a | bookmark: releases/v2 | tag: default/releases/v2 | tag: tip | parent: 4:a02330f767a4 | user: test | date: Mon Jan 01 00:00:15 2007 +0000 | summary: add eta | | o changeset: 5:218b2d0660d3 |/ bookmark: releases/v1 | tag: default/releases/v1 | user: test | date: Mon Jan 01 00:00:14 2007 +0000 | summary: add zeta | o changeset: 4:a02330f767a4 |\ bookmark: master | | tag: default/master | | parent: 3:6f898ad1f3e1 | | parent: 1:7fe02317c63d | | user: test | | date: Mon Jan 01 00:00:13 2007 +0000 | | summary: Merge branch 'beta' | | | o changeset: 3:6f898ad1f3e1 | | parent: 0:ff7a2f2d8d70 | | user: test | | date: Mon Jan 01 00:00:13 2007 +0000 | | summary: add gamma | | | | o changeset: 2:678ebee93e38 | |/ bookmark: delta | | tag: default/delta | | parent: 0:ff7a2f2d8d70 | | user: test | | date: Mon Jan 01 00:00:12 2007 +0000 | | summary: add delta | | o | changeset: 1:7fe02317c63d |/ bookmark: beta | tag: default/beta | tag: t_beta | user: test | date: Mon Jan 01 00:00:11 2007 +0000 | summary: add beta | @ changeset: 0:ff7a2f2d8d70 bookmark: epsilon tag: default/epsilon tag: t_alpha user: test date: Mon Jan 01 00:00:10 2007 +0000 summary: add alpha add old and new commits to the git repo -- make sure we're using the commit date and not the author date $ cat >> $HGRCPATH < [git] > mindate = 2014-01-02 00:00:00 +0000 > EOF $ cd gitrepo $ git checkout -q master $ echo oldcommit > oldcommit $ git add oldcommit $ GIT_AUTHOR_DATE="2014-03-01 00:00:00 +0000" \ > GIT_COMMITTER_DATE="2009-01-01 00:00:00 +0000" \ > git commit -m oldcommit > /dev/null || echo "git commit error" also add an annotated tag $ git checkout -q master^ $ echo oldtag > oldtag $ git add oldtag $ GIT_AUTHOR_DATE="2014-03-01 00:00:00 +0000" \ > GIT_COMMITTER_DATE="2009-01-01 00:00:00 +0000" \ > git commit -m oldtag > /dev/null || echo "git commit error" $ GIT_COMMITTER_DATE="2009-02-01 00:00:00 +0000" \ > git tag -a -m 'tagging oldtag' oldtag $ cd .. $ hg -R hgrepo pull pulling from $TESTTMP/gitrepo no changes found $ hg -R hgrepo log -r master changeset: 4:a02330f767a4 bookmark: master tag: default/master parent: 3:6f898ad1f3e1 parent: 1:7fe02317c63d user: test date: Mon Jan 01 00:00:13 2007 +0000 summary: Merge branch 'beta' $ cd gitrepo $ git checkout -q master $ echo newcommit > newcommit $ git add newcommit $ GIT_AUTHOR_DATE="2014-01-01 00:00:00 +0000" \ > GIT_COMMITTER_DATE="2014-01-02 00:00:00 +0000" \ > git commit -m newcommit > /dev/null || echo "git commit error" $ git checkout -q refs/tags/oldtag $ GIT_COMMITTER_DATE="2014-01-02 00:00:00 +0000" \ > git tag -a -m 'tagging newtag' newtag $ cd .. $ hg -R hgrepo pull pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg heads .' to see heads, 'hg merge' to merge) $ hg -R hgrepo heads changeset: 9:e103a73f33be bookmark: master tag: default/master tag: tip user: test date: Wed Jan 01 00:00:00 2014 +0000 summary: newcommit changeset: 7:49713da8f665 tag: newtag tag: oldtag parent: 4:a02330f767a4 user: test date: Sat Mar 01 00:00:00 2014 +0000 summary: oldtag changeset: 6:a3f95e150b0a bookmark: releases/v2 tag: default/releases/v2 parent: 4:a02330f767a4 user: test date: Mon Jan 01 00:00:15 2007 +0000 summary: add eta changeset: 5:218b2d0660d3 bookmark: releases/v1 tag: default/releases/v1 user: test date: Mon Jan 01 00:00:14 2007 +0000 summary: add zeta changeset: 2:678ebee93e38 bookmark: delta tag: default/delta parent: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add delta durin42-hg-git-53d514c9c7e6/tests/test-push-r.t0000644000000000000000000001022512653777310017151 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init test $ cd test $ cat >>afile < 0 > EOF $ hg add afile $ hg commit -m "0.0" $ cat >>afile < 1 > EOF $ hg commit -m "0.1" $ cat >>afile < 2 > EOF $ hg commit -m "0.2" $ cat >>afile < 3 > EOF $ hg commit -m "0.3" $ hg update -C 0 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat >>afile < 1 > EOF $ hg commit -m "1.1" created new head $ cat >>afile < 2 > EOF $ hg commit -m "1.2" $ cat >fred < a line > EOF $ cat >>afile < 3 > EOF $ hg add fred $ hg commit -m "1.3" $ hg mv afile adifferentfile $ hg commit -m "1.3m" $ hg update -C 3 1 files updated, 0 files merged, 2 files removed, 0 files unresolved $ hg mv afile anotherfile $ hg commit -m "0.3m" $ 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 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 $ cd test-8 $ hg pull ../test-7 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) $ hg verify checking changesets checking manifests crosschecking files in changesets and manifests checking files 4 files, 9 changesets, 7 total revisions durin42-hg-git-53d514c9c7e6/tests/test-push.t0000644000000000000000000001231612653777310016715 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo alpha > alpha $ git add alpha $ fn_git_commit -m "add alpha" $ git checkout -b not-master 2>&1 | sed s/\'/\"/g Switched to a new branch "not-master" $ cd .. $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ echo beta > beta $ hg add beta $ fn_hg_commit -m 'add beta' $ echo gamma > gamma $ hg add gamma $ fn_hg_commit -m 'add gamma' $ hg book -r 1 beta $ hg push -r beta pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs adding reference refs/heads/beta $ cd .. should have two different branches $ cd gitrepo $ git branch -v beta 0f378ab add beta master 7eeab2e add alpha * not-master 7eeab2e add alpha some more work on master from git $ git checkout master 2>&1 | sed s/\'/\"/g Switched to branch "master" $ echo delta > delta $ git add delta $ fn_git_commit -m "add delta" $ git checkout not-master 2>&1 | sed s/\'/\"/g Switched to branch "not-master" $ cd .. $ cd hgrepo this should fail $ hg push -r master pushing to $TESTTMP/gitrepo searching for changes abort: branch 'refs/heads/master' changed on the server, please pull and merge before pushing [255] ... even with -f $ hg push -fr master pushing to $TESTTMP/gitrepo searching for changes abort: branch 'refs/heads/master' changed on the server, please pull and merge before pushing [255] $ hg pull 2>&1 | grep -v 'divergent bookmark' pulling from $TESTTMP/gitrepo importing git objects into hg (run 'hg heads' to see heads, 'hg merge' to merge) TODO shouldn't need to do this since we're (in theory) pushing master explicitly, which should not implicitly also push the not-master ref. $ hg book not-master -r default/not-master --force master and default/master should be diferent $ hg log -r master changeset: 2:953796e1cfd8 bookmark: master user: test date: Mon Jan 01 00:00:12 2007 +0000 summary: add gamma $ hg log -r default/master changeset: 3:25eed24f5e8f tag: default/master tag: tip parent: 0:ff7a2f2d8d70 user: test date: Mon Jan 01 00:00:13 2007 +0000 summary: add delta this should also fail $ hg push -r master pushing to $TESTTMP/gitrepo searching for changes abort: pushing refs/heads/master overwrites 953796e1cfd8 [255] ... but succeed with -f $ hg push -fr master pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 1 trees and 1 blobs updating reference refs/heads/master this should fail, no changes to push $ hg push -r master pushing to $TESTTMP/gitrepo searching for changes no changes found [1] hg-git issue103 -- directories can lose information at hg-git export time $ hg up master | egrep -v '^\(activating bookmark master\)$' 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ mkdir dir1 $ echo alpha > dir1/alpha $ hg add dir1/alpha $ fn_hg_commit -m 'add dir1/alpha' $ hg push -r master pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 2 trees and 0 blobs updating reference refs/heads/master $ echo beta > dir1/beta $ hg add dir1/beta $ fn_hg_commit -m 'add dir1/beta' $ hg push -r master pushing to $TESTTMP/gitrepo searching for changes adding objects added 1 commits with 2 trees and 0 blobs updating reference refs/heads/master $ hg log -r master changeset: 5:ba0476ff1899 bookmark: master tag: default/master tag: tip user: test date: Mon Jan 01 00:00:15 2007 +0000 summary: add dir1/beta $ cd .. $ hg clone gitrepo hgrepo-test importing git objects into hg updating to branch default 5 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-test log -r master changeset: 4:ba0476ff1899 bookmark: master tag: default/master tag: tip user: test date: Mon Jan 01 00:00:15 2007 +0000 summary: add dir1/beta Push empty Hg repo to empty Git repo (issue #58) $ hg init hgrepo2 $ git init -q --bare gitrepo2 $ hg -R hgrepo2 push gitrepo2 pushing to gitrepo2 searching for changes no changes found [1] The remote repo is empty and the local one doesn't have any bookmarks/tags $ cd hgrepo2 $ echo init >> test.txt $ hg addremove adding test.txt $ fn_hg_commit -m init $ hg update null 0 files updated, 0 files merged, 1 files removed, 0 files unresolved $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 1 commits with 1 trees and 1 blobs (the phases line was added in Mercurial 3.5) $ hg summary | grep -Ev '^phases:' parent: -1:000000000000 (no revision checked out) branch: default commit: (clean) update: 1 new changesets (update) Only one bookmark 'master' should be created $ hg bookmarks * master 0:8aded40be5af durin42-hg-git-53d514c9c7e6/tests/test-renames.t0000644000000000000000000002607612653777310017400 0ustar 00000000000000Test that rename detection works $ . "$TESTDIR/testutil" $ cat >> $HGRCPATH < [diff] > git = True > [git] > similarity = 50 > EOF $ git init -q gitrepo $ cd gitrepo $ for i in 1 2 3 4 5 6 7 8 9 10; do echo $i >> alpha; done $ git add alpha $ fn_git_commit -malpha Rename a file $ git mv alpha beta $ echo 11 >> beta $ git add beta $ fn_git_commit -mbeta Copy a file $ cp beta gamma $ echo 12 >> beta $ echo 13 >> gamma $ git add beta gamma $ fn_git_commit -mgamma Add a submodule (gitlink) and move it to a different spot: $ cd .. $ git init -q gitsubmodule $ cd gitsubmodule $ touch subalpha $ git add subalpha $ fn_git_commit -msubalpha $ cd ../gitrepo $ rmpwd="import sys; print sys.stdin.read().replace('$(dirname $(pwd))/', '')" $ clonefilt='s/Cloning into/Initialized empty Git repository in/;s/in .*/in .../' $ git submodule add ../gitsubmodule 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ fn_git_commit -m 'add submodule' $ sed -e 's/path = gitsubmodule/path = gitsubmodule2/' .gitmodules > .gitmodules-new $ mv .gitmodules-new .gitmodules $ mv gitsubmodule gitsubmodule2 $ git add .gitmodules gitsubmodule2 $ git rm --cached gitsubmodule rm 'gitsubmodule' $ fn_git_commit -m 'move submodule' Rename a file elsewhere and replace it with a symlink: $ git mv beta beta-new $ ln -s beta-new beta $ git add beta $ fn_git_commit -m 'beta renamed' Rename the file back: $ git rm beta rm 'beta' $ git mv beta-new beta $ fn_git_commit -m 'beta renamed back' Rename a file elsewhere and replace it with a submodule: $ git mv gamma gamma-new $ git submodule add ../gitsubmodule gamma 2>&1 | python -c "$rmpwd" | sed "$clonefilt" | egrep -v '^done\.$' Initialized empty Git repository in ... $ fn_git_commit -m 'rename and add submodule' Remove the submodule and rename the file back: $ grep 'submodule "gitsubmodule"' -A2 .gitmodules > .gitmodules-new $ mv .gitmodules-new .gitmodules $ git add .gitmodules $ git rm --cached gamma rm 'gamma' $ rm -rf gamma $ git mv gamma-new gamma $ fn_git_commit -m 'remove submodule and rename back' $ git checkout -f -b not-master 2>&1 | sed s/\'/\"/g Switched to a new branch "not-master" $ cd .. $ hg clone -q gitrepo hgrepo $ cd hgrepo $ hg log -p --graph --template "{rev} {node} {desc|firstline}\n{join(extras, ' ')}\n\n" @ 8 a26f94023ab2ea40c9e4c4dbb753c9a4e572fefe remove submodule and rename back | branch=default hg-git-rename-source=git | | diff --git a/.gitmodules b/.gitmodules | --- a/.gitmodules | +++ b/.gitmodules | @@ -1,6 +1,3 @@ | [submodule "gitsubmodule"] | path = gitsubmodule2 | url = ../gitsubmodule | -[submodule "gamma"] | - path = gamma | - url = ../gitsubmodule | diff --git a/.hgsub b/.hgsub | --- a/.hgsub | +++ b/.hgsub | @@ -1,2 +1,1 @@ | gitsubmodule2 = [git]../gitsubmodule | -gamma = [git]../gitsubmodule | diff --git a/.hgsubstate b/.hgsubstate | --- a/.hgsubstate | +++ b/.hgsubstate | @@ -1,2 +1,1 @@ | -5944b31ff85b415573d1a43eb942e2dea30ab8be gamma | 5944b31ff85b415573d1a43eb942e2dea30ab8be gitsubmodule2 | diff --git a/gamma-new b/gamma | rename from gamma-new | rename to gamma | o 7 a7c65d466ee1f33540695666c2e65dcbe3fbe388 rename and add submodule | branch=default hg-git-rename-source=git | | diff --git a/.gitmodules b/.gitmodules | --- a/.gitmodules | +++ b/.gitmodules | @@ -1,3 +1,6 @@ | [submodule "gitsubmodule"] | path = gitsubmodule2 | url = ../gitsubmodule | +[submodule "gamma"] | + path = gamma | + url = ../gitsubmodule | diff --git a/.hgsub b/.hgsub | --- a/.hgsub | +++ b/.hgsub | @@ -1,1 +1,2 @@ | gitsubmodule2 = [git]../gitsubmodule | +gamma = [git]../gitsubmodule | diff --git a/.hgsubstate b/.hgsubstate | --- a/.hgsubstate | +++ b/.hgsubstate | @@ -1,1 +1,2 @@ | +5944b31ff85b415573d1a43eb942e2dea30ab8be gamma | 5944b31ff85b415573d1a43eb942e2dea30ab8be gitsubmodule2 | diff --git a/gamma b/gamma-new | rename from gamma | rename to gamma-new | o 6 10614bb16f4d240ba81b6a71d76a7aa160621a29 beta renamed back | branch=default hg-git-rename-source=git | | diff --git a/beta b/beta | old mode 120000 | new mode 100644 | --- a/beta | +++ b/beta | @@ -1,1 +1,12 @@ | -beta-new | \ No newline at end of file | +1 | +2 | +3 | +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 | diff --git a/beta-new b/beta-new | deleted file mode 100644 | --- a/beta-new | +++ /dev/null | @@ -1,12 +0,0 @@ | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | o 5 96ad24db491a180ccd330556129d75377e201f63 beta renamed | branch=default hg-git-rename-source=git | | diff --git a/beta b/beta | old mode 100644 | new mode 120000 | --- a/beta | +++ b/beta | @@ -1,12 +1,1 @@ | -1 | -2 | -3 | -4 | -5 | -6 | -7 | -8 | -9 | -10 | -11 | -12 | +beta-new | \ No newline at end of file | diff --git a/beta b/beta-new | copy from beta | copy to beta-new | o 4 d22608e850ea875936802e119831f1789f5d98bd move submodule | branch=default hg-git-rename-source=git | | diff --git a/.gitmodules b/.gitmodules | --- a/.gitmodules | +++ b/.gitmodules | @@ -1,3 +1,3 @@ | [submodule "gitsubmodule"] | - path = gitsubmodule | + path = gitsubmodule2 | url = ../gitsubmodule | diff --git a/.hgsub b/.hgsub | --- a/.hgsub | +++ b/.hgsub | @@ -1,1 +1,1 @@ | -gitsubmodule = [git]../gitsubmodule | +gitsubmodule2 = [git]../gitsubmodule | diff --git a/.hgsubstate b/.hgsubstate | --- a/.hgsubstate | +++ b/.hgsubstate | @@ -1,1 +1,1 @@ | -5944b31ff85b415573d1a43eb942e2dea30ab8be gitsubmodule | +5944b31ff85b415573d1a43eb942e2dea30ab8be gitsubmodule2 | o 3 db55fd0e7083555ec886f6175fa0a42a711c6592 add submodule | branch=default hg-git-rename-source=git | | diff --git a/.gitmodules b/.gitmodules | new file mode 100644 | --- /dev/null | +++ b/.gitmodules | @@ -0,0 +1,3 @@ | +[submodule "gitsubmodule"] | + path = gitsubmodule | + url = ../gitsubmodule | diff --git a/.hgsub b/.hgsub | new file mode 100644 | --- /dev/null | +++ b/.hgsub | @@ -0,0 +1,1 @@ | +gitsubmodule = [git]../gitsubmodule | diff --git a/.hgsubstate b/.hgsubstate | new file mode 100644 | --- /dev/null | +++ b/.hgsubstate | @@ -0,0 +1,1 @@ | +5944b31ff85b415573d1a43eb942e2dea30ab8be gitsubmodule | o 2 20f9e56b6d006d0403f853245e483d0892b8ac48 gamma | branch=default hg-git-rename-source=git | | diff --git a/beta b/beta | --- a/beta | +++ b/beta | @@ -9,3 +9,4 @@ | 9 | 10 | 11 | +12 | diff --git a/beta b/gamma | copy from beta | copy to gamma | --- a/beta | +++ b/gamma | @@ -9,3 +9,4 @@ | 9 | 10 | 11 | +13 | o 1 9f7744e68def81da3b394f11352f602ca9c8ab68 beta | branch=default hg-git-rename-source=git | | diff --git a/alpha b/beta | rename from alpha | rename to beta | --- a/alpha | +++ b/beta | @@ -8,3 +8,4 @@ | 8 | 9 | 10 | +11 | o 0 7bc844166f76e49562f81eacd54ea954d01a9e42 alpha branch=default hg-git-rename-source=git diff --git a/alpha b/alpha new file mode 100644 --- /dev/null +++ b/alpha @@ -0,0 +1,10 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 Make a new ordinary commit in Mercurial (no extra metadata) $ echo 14 >> gamma $ hg ci -m "gamma2" Make a new commit with a copy and a rename in Mercurial $ hg cp gamma delta $ echo 15 >> delta $ hg mv beta epsilon $ echo 16 >> epsilon $ hg ci -m "delta/epsilon" $ hg export . # HG changeset patch # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 # Node ID a725f9bdefe29f1a1db89ad030d9f1ee36b3a440 # Parent 814ed6aa5bd2867ce5ab80f3c2602155d89027b1 delta/epsilon diff --git a/gamma b/delta copy from gamma copy to delta --- a/gamma +++ b/delta @@ -11,3 +11,4 @@ 11 13 14 +15 diff --git a/beta b/epsilon rename from beta rename to epsilon --- a/beta +++ b/epsilon @@ -10,3 +10,4 @@ 10 11 12 +16 $ hg push pushing to $TESTTMP/gitrepo searching for changes adding objects added 2 commits with 2 trees and 3 blobs updating reference refs/heads/master $ cd ../gitrepo $ git log master --pretty=oneline 5f2948d029693346043f320620af99a615930dc4 delta/epsilon bbd2ec050f7fbc64f772009844f7d58a556ec036 gamma2 50d116676a308b7c22935137d944e725d2296f2a remove submodule and rename back 59fb8e82ea18f79eab99196f588e8948089c134f rename and add submodule f95497455dfa891b4cd9b524007eb9514c3ab654 beta renamed back 055f482277da6cd3dd37c7093d06983bad68f782 beta renamed d7f31298f27df8a9226eddb1e4feb96922c46fa5 move submodule c610256cb6959852d9e70d01902a06726317affc add submodule e1348449e0c3a417b086ed60fc13f068d4aa8b26 gamma cc83241f39927232f690d370894960b0d1943a0e beta 938bb65bb322eb4a3558bec4cdc8a680c4d1794c alpha Make sure the right metadata is stored $ git cat-file commit master^ tree 0adbde18545845f3b42ad1a18939ed60a9dec7a8 parent 50d116676a308b7c22935137d944e725d2296f2a author test 0 +0000 committer test 0 +0000 HG:rename-source hg gamma2 $ git cat-file commit master tree f8f32f4e20b56a5a74582c6a5952c175bf9ec155 parent bbd2ec050f7fbc64f772009844f7d58a556ec036 author test 0 +0000 committer test 0 +0000 HG:rename gamma:delta HG:rename beta:epsilon delta/epsilon Now make another clone and compare the hashes $ cd .. $ hg clone -q gitrepo hgrepo2 $ cd hgrepo2 $ hg export master # HG changeset patch # User test # Date 0 0 # Thu Jan 01 00:00:00 1970 +0000 # Node ID a725f9bdefe29f1a1db89ad030d9f1ee36b3a440 # Parent 814ed6aa5bd2867ce5ab80f3c2602155d89027b1 delta/epsilon diff --git a/gamma b/delta copy from gamma copy to delta --- a/gamma +++ b/delta @@ -11,3 +11,4 @@ 11 13 14 +15 diff --git a/beta b/epsilon rename from beta rename to epsilon --- a/beta +++ b/epsilon @@ -10,3 +10,4 @@ 10 11 12 +16 Regenerate the Git metadata and compare the hashes $ hg gclear clearing out the git cache data $ hg gexport $ cd .hg/git $ git log master --pretty=oneline 5f2948d029693346043f320620af99a615930dc4 delta/epsilon bbd2ec050f7fbc64f772009844f7d58a556ec036 gamma2 50d116676a308b7c22935137d944e725d2296f2a remove submodule and rename back 59fb8e82ea18f79eab99196f588e8948089c134f rename and add submodule f95497455dfa891b4cd9b524007eb9514c3ab654 beta renamed back 055f482277da6cd3dd37c7093d06983bad68f782 beta renamed d7f31298f27df8a9226eddb1e4feb96922c46fa5 move submodule c610256cb6959852d9e70d01902a06726317affc add submodule e1348449e0c3a417b086ed60fc13f068d4aa8b26 gamma cc83241f39927232f690d370894960b0d1943a0e beta 938bb65bb322eb4a3558bec4cdc8a680c4d1794c alpha durin42-hg-git-53d514c9c7e6/tests/test-subrepos.t0000644000000000000000000001020212653777310017570 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitsubrepo Initialized empty Git repository in $TESTTMP/gitsubrepo/.git/ $ cd gitsubrepo $ echo beta > beta $ git add beta $ fn_git_commit -m 'add beta' $ cd .. $ git init gitrepo1 Initialized empty Git repository in $TESTTMP/gitrepo1/.git/ $ cd gitrepo1 $ echo alpha > alpha $ git add alpha $ fn_git_commit -m 'add alpha' $ git submodule add ../gitsubrepo subrepo1 Cloning into 'subrepo1'... done. $ fn_git_commit -m 'add subrepo1' $ git submodule add ../gitsubrepo xyz/subrepo2 Cloning into 'xyz/subrepo2'... done. $ fn_git_commit -m 'add subrepo2' we are going to push to this repo from our hg clone, allow commits despite working copy presense $ git config receive.denyCurrentBranch ignore $ cd .. Ensure gitlinks are transformed to .hgsubstate on hg pull from git $ hg clone gitrepo1 hgrepo 2>&1 | egrep -v '^(Cloning into|done)' importing git objects into hg updating to branch default cloning subrepo subrepo1 from $TESTTMP/gitsubrepo cloning subrepo xyz/subrepo2 from $TESTTMP/gitsubrepo 4 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg bookmarks -f -r default master 1. Ensure gitlinks are transformed to .hgsubstate on hg <- git pull .hgsub shall list two [git] subrepos $ cat .hgsub | sort subrepo1 = [git]../gitsubrepo xyz/subrepo2 = [git]../gitsubrepo .hgsubstate shall list two idenitcal revisions $ cat .hgsubstate 56f0304c5250308f14cfbafdc27bd12d40154d17 subrepo1 56f0304c5250308f14cfbafdc27bd12d40154d17 xyz/subrepo2 hg status shall NOT report .hgsub and .hgsubstate as untracked - either ignored or unmodified $ hg status --unknown .hgsub .hgsubstate $ hg status --modified .hgsub .hgsubstate $ cd .. 2. Check gitmodules are preserved during hg -> git push $ cd gitsubrepo $ echo gamma > gamma $ git add gamma $ fn_git_commit -m 'add gamma' $ cd .. $ cd hgrepo $ cd xyz/subrepo2 $ git pull | sed 's/files/file/;s/insertions/insertion/;s/, 0 deletions.*//' | sed 's/| */| /' From $TESTTMP/gitsubrepo 56f0304..aabf7cd master -> origin/master Updating 56f0304..aabf7cd Fast-forward gamma | 1 + 1 file changed, 1 insertion(+) create mode 100644 gamma $ cd ../.. $ echo xxx >> alpha $ hg commit -m 'Update subrepo2 from hg' | grep -v "committing subrepository" || true $ hg push pushing to $TESTTMP/gitrepo1 searching for changes adding objects added 1 commits with 2 trees and 1 blobs updating reference refs/heads/master $ cd .. $ cd gitrepo1 there shall be two gitlink entries, with values matching that in .hgsubstate $ git ls-tree -r HEAD^{tree} | grep 'commit' 160000 commit 56f0304c5250308f14cfbafdc27bd12d40154d17 subrepo1 160000 commit aabf7cd015089aff0b84596e69aa37b24a3d090a xyz/subrepo2 bring working copy to HEAD state (it's not bare repo) $ git reset --hard HEAD is now at ae335e3 Update subrepo2 from hg $ cd .. 3. Check .hgsub and .hgsubstate from git repository are merged, not overwritten $ hg init hgsub $ cd hgsub $ echo delta > delta $ hg add delta $ fn_hg_commit -m "add delta" $ echo "`hg tip --template '{node}'` hgsub" > ../gitrepo1/.hgsubstate $ echo "hgsub = $(pwd)" > ../gitrepo1/.hgsub $ cd ../gitrepo1 $ git add .hgsubstate .hgsub $ fn_git_commit -m "Test3. Prepare .hgsub and .hgsubstate sources" $ cd ../hgrepo $ hg pull pulling from $TESTTMP/gitrepo1 importing git objects into hg (run 'hg update' to get a working copy) $ hg checkout -C | egrep -v '^\(activating bookmark master\)$' updating to active bookmark master cloning subrepo hgsub from $TESTTMP/hgsub 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd .. pull shall bring .hgsub entry which was added to the git repo $ cat hgrepo/.hgsub | sort hgsub = $TESTTMP/hgsub subrepo1 = [git]../gitsubrepo xyz/subrepo2 = [git]../gitsubrepo .hgsubstate shall list revision of the subrepo added through git repo $ cat hgrepo/.hgsubstate 481ec30d580f333ae3a77f94c973ce37b69d5bda hgsub 56f0304c5250308f14cfbafdc27bd12d40154d17 subrepo1 aabf7cd015089aff0b84596e69aa37b24a3d090a xyz/subrepo2 durin42-hg-git-53d514c9c7e6/tests/test-timezone.t0000644000000000000000000000152312653777310017566 0ustar 00000000000000This test shows how dulwich fails to convert a commit accepted by hg. In the real world case, it was a hand edit by the user to change the timezone field in an export. However, if it is good enough for hg, we have to make it good enough for git. Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init hgrepo $ cd hgrepo $ touch beta $ hg add beta $ fn_hg_commit -m "test commit" $ cat >patch2 < # HG changeset patch > # User J. User > # Date 1337962044 25201 > # Node ID 1111111111111111111111111111111111111111 > # Parent 0000000000000000000000000000000000000000 > > Patch with sub-minute time zone > diff --git a/alpha b/alpha > new file mode 100644 > --- /dev/null > +++ b/alpha > @@ -0,0 +1,1 @@ > +alpha > EOF $ hg import patch2 applying patch2 $ hg gexport durin42-hg-git-53d514c9c7e6/tests/test-tree-decomposition.t0000644000000000000000000000276212653777310021553 0ustar 00000000000000Load commonly used test logic $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ mkdir d1 $ echo a > d1/f1 $ echo b > d1/f2 $ git add d1/f1 d1/f2 $ fn_git_commit -m initial $ mkdir d2 $ git mv d1/f2 d2/f2 $ fn_git_commit -m 'rename' $ rm -r d1 $ echo c > d1 $ git add --all d1 $ fn_git_commit -m 'replace a dir with a file' $ cd .. $ git init --bare gitrepo2 Initialized empty Git repository in $TESTTMP/gitrepo2/ $ hg clone gitrepo hgrepo | grep -v '^updating' importing git objects into hg 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cd hgrepo $ hg log --template 'adds: {file_adds}\ndels: {file_dels}\n' adds: d1 dels: d1/f1 adds: d2/f2 dels: d1/f2 adds: d1/f1 d1/f2 dels: $ hg gclear clearing out the git cache data $ hg push ../gitrepo2 pushing to ../gitrepo2 searching for changes adding objects added 3 commits with 6 trees and 3 blobs $ cd .. $ git --git-dir=gitrepo2 log --pretty=medium commit 6e0dbd8cd92ed4823c69cb48d8a2b81f904e6e69 Author: test Date: Mon Jan 1 00:00:12 2007 +0000 replace a dir with a file commit a1874d5cd0b1549ed729e36f0da4a93ed36259ee Author: test Date: Mon Jan 1 00:00:11 2007 +0000 rename commit 102c17a5deda49db3f10ec5573f9378867098b7c Author: test Date: Mon Jan 1 00:00:10 2007 +0000 initial durin42-hg-git-53d514c9c7e6/tests/test-url-parsing.py0000755000000000000000000001021112653777310020361 0ustar 00000000000000import sys try: import dulwich except ImportError: print "skipped: missing feature: dulwich" sys.exit(80) import os, tempfile, unittest, shutil from mercurial import ui, hg, commands sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir)) from hggit.git_handler import GitHandler class TestUrlParsing(object): def setUp(self): # create a test repo location. self.tmpdir = tempfile.mkdtemp('hg-git_url-test') commands.init(ui.ui(), self.tmpdir) repo = hg.repository(ui.ui(), self.tmpdir) self.handler = GitHandler(repo, ui.ui()) def tearDown(self): # remove the temp repo shutil.rmtree(self.tmpdir) def assertEquals(self, l, r): print '%% expect %r' % (r, ) print l assert l == r def test_ssh_github_style_slash(self): url = "git+ssh://git@github.com/webjam/webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, '/webjam/webjam.git') self.assertEquals(client.host, 'git@github.com') def test_ssh_github_style_colon_number_starting_username(self): url = "git+ssh://git@github.com:42qu/vps.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, '42qu/vps.git') self.assertEquals(client.host, 'git@github.com') def test_ssh_github_style_colon(self): url = "git+ssh://git@github.com:webjam/webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, 'webjam/webjam.git') self.assertEquals(client.host, 'git@github.com') def test_ssh_heroku_style(self): url = "git+ssh://git@heroku.com:webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, 'webjam.git') self.assertEquals(client.host, 'git@heroku.com') # also test that it works even if heroku isn't in the name url = "git+ssh://git@compatible.com:webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, 'webjam.git') self.assertEquals(client.host, 'git@compatible.com') def test_ssh_heroku_style_with_trailing_slash(self): # some versions of mercurial add a trailing slash even if # the user didn't supply one. url = "git+ssh://git@heroku.com:webjam.git/" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, 'webjam.git') self.assertEquals(client.host, 'git@heroku.com') def test_heroku_style_with_port(self): url = "git+ssh://git@heroku.com:999:webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, 'webjam.git') self.assertEquals(client.host, 'git@heroku.com') self.assertEquals(client.port, '999') def test_gitdaemon_style(self): url = "git://github.com/webjam/webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, '/webjam/webjam.git') try: self.assertEquals(client._host, 'github.com') except AttributeError: self.assertEquals(client.host, 'github.com') def test_ssh_github_style_slash_with_port(self): url = "git+ssh://git@github.com:10022/webjam/webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, '/webjam/webjam.git') self.assertEquals(client.host, 'git@github.com') self.assertEquals(client.port, '10022') def test_gitdaemon_style_with_port(self): url = "git://github.com:19418/webjam/webjam.git" client, path = self.handler.get_transport_and_path(url) self.assertEquals(path, '/webjam/webjam.git') try: self.assertEquals(client._host, 'github.com') except AttributeError: self.assertEquals(client.host, 'github.com') self.assertEquals(client._port, '19418') if __name__ == '__main__': tc = TestUrlParsing() for test in sorted([t for t in dir(tc) if t.startswith('test_')]): tc.setUp() getattr(tc, test)() tc.tearDown() durin42-hg-git-53d514c9c7e6/tests/test-url-parsing.py.out0000644000000000000000000000155712653777310021201 0ustar 00000000000000% expect '/webjam/webjam.git' /webjam/webjam.git % expect 'github.com' github.com % expect '/webjam/webjam.git' /webjam/webjam.git % expect 'github.com' github.com % expect '19418' 19418 % expect 'webjam.git' webjam.git % expect 'git@heroku.com' git@heroku.com % expect '999' 999 % expect 'webjam/webjam.git' webjam/webjam.git % expect 'git@github.com' git@github.com % expect '42qu/vps.git' 42qu/vps.git % expect 'git@github.com' git@github.com % expect '/webjam/webjam.git' /webjam/webjam.git % expect 'git@github.com' git@github.com % expect '/webjam/webjam.git' /webjam/webjam.git % expect 'git@github.com' git@github.com % expect '10022' 10022 % expect 'webjam.git' webjam.git % expect 'git@heroku.com' git@heroku.com % expect 'webjam.git' webjam.git % expect 'git@compatible.com' git@compatible.com % expect 'webjam.git' webjam.git % expect 'git@heroku.com' git@heroku.com durin42-hg-git-53d514c9c7e6/tests/test-verify-fail.t0000644000000000000000000000447712653777310020164 0ustar 00000000000000Other tests make sure that gverify passes. This makes sure that gverify detects inconsistencies. Since hg-git is ostensibly correct, we artificially create inconsistencies by placing different Mercurial and Git repos in the right spots. $ . "$TESTDIR/testutil" $ git init gitrepo Initialized empty Git repository in $TESTTMP/gitrepo/.git/ $ cd gitrepo $ echo normalf > normalf $ echo missingf > missingf $ echo differentf > differentf (executable in git, non-executable in hg) $ echo exef > exef $ chmod +x exef (symlink in hg, regular file in git) equivalent to 'echo -n foo > linkf', but that doesn't work on OS X $ printf foo > linkf $ git add normalf missingf differentf exef linkf $ fn_git_commit -m 'add files' $ cd .. $ hg init hgrepo $ cd hgrepo $ echo normalf > normalf $ echo differentf2 > differentf $ echo unexpectedf > unexpectedf $ echo exef > exef $ ln -s foo linkf $ hg add normalf differentf unexpectedf exef linkf $ fn_hg_commit -m 'add files' $ git clone --mirror ../gitrepo .hg/git Cloning into bare repository '.hg/git'... done. $ echo "$(cd ../gitrepo && git rev-parse HEAD) $(hg log -r . --template '{node}')" >> .hg/git-mapfile $ hg gverify verifying rev 3f1601c3cf54 against git commit 039c1cd9fdda382c9d1e8ec85de6b5b59518ca80 difference in: differentf file has different flags: exef (hg '', git 'x') file has different flags: linkf (hg 'l', git '') file found in git but not hg: missingf file found in hg but not git: unexpectedf [1] $ echo newf > newf $ hg add newf $ fn_hg_commit -m 'new hg commit' $ hg gverify abort: no git commit found for rev 4e582b4eb862 (if this is an octopus merge, verify against the last rev) [255] invalid git SHA $ echo "ffffffffffffffffffffffffffffffffffffffff $(hg log -r . --template '{node}')" >> .hg/git-mapfile $ hg gverify abort: git equivalent ffffffffffffffffffffffffffffffffffffffff for rev 4e582b4eb862 not found! [255] git SHA is not a commit $ echo new2 >> newf $ fn_hg_commit -m 'new hg commit 2' this gets the tree pointed to by the commit at HEAD $ echo "$(cd ../gitrepo && git show --format=%T HEAD | head -n 1) $(hg log -r . --template '{node}')" >> .hg/git-mapfile $ hg gverify abort: git equivalent f477b00e4a9907617f346a529cc0fe9ba5d6f6d3 for rev 5c2eb98af3e2 is not a commit! [255] durin42-hg-git-53d514c9c7e6/tests/testutil0000755000000000000000000000401612653777310016375 0ustar 00000000000000#!/bin/sh # This file holds logic that is used in many tests. # It can be called in a test like this: # $ . "$TESTDIR/testutil" # Activate extensions echo "[extensions]" >> $HGRCPATH echo "hggit=$(echo $(dirname $TESTDIR))/hggit" >> $HGRCPATH echo 'mq=' >> $HGRCPATH # Standard checks for external dependencies # We use the git command-line client and dulwich in pretty much all the tests. # Thus, to avoid repetitively declaring that requirement in almost every test, # we just call the checks in all tests that include this library. python -c 'import dulwich' || { echo "skipped: missing feature: dulwich" && exit 80 } "$TESTDIR/hghave" git || exit 80 GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE # Functions to commit and tag in Mercurial and Git in a predictable manner count=10 fn_git_commit() { GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000" GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit "$@" >/dev/null || echo "git commit error" count=`expr $count + 1` } fn_hg_commit() { HGDATE="2007-01-01 00:00:$count +0000" hg commit -d "$HGDATE" "$@" >/dev/null || echo "hg commit error" count=`expr $count + 1` } fn_hg_commitextra() { HGDATE="2007-01-01 00:00:$count +0000" hg --config extensions.commitextra=$TESTDIR/commitextra.py commitextra \ -d "$HGDATE" "$@" >/dev/null || echo "hg commit error" count=`expr $count + 1` } fn_git_tag() { GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000" GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git tag "$@" >/dev/null || echo "git tag error" count=`expr $count + 1` } fn_hg_tag() { HGDATE="2007-01-01 00:00:$count +0000" hg tag -d "$HGDATE" "$@" >/dev/null || echo "hg tag error" count=`expr $count + 1` }