pax_global_header00006660000000000000000000000064132447374130014522gustar00rootroot0000000000000052 comment=d033a5cd467546a19490ad7cdb645813a6da0afe hg-git-0.8.11/000077500000000000000000000000001324473741300127705ustar00rootroot00000000000000hg-git-0.8.11/.gitignore000066400000000000000000000000501324473741300147530ustar00rootroot00000000000000*.pyc tests/*.err build dist *.egg-info hg-git-0.8.11/.hg_archival.txt000066400000000000000000000001721324473741300160560ustar00rootroot00000000000000repo: 06366111af3c6a2ffa06333ed60d3ed3b9ec0763 node: b90c69681d2d97aca47299d653c738ed688b60d7 branch: default tag: 0.8.11 hg-git-0.8.11/.hgignore000066400000000000000000000001161324473741300145710ustar00rootroot00000000000000syntax: glob *.pyc tests/*.err tests/.testtimes build dist *.egg-info *.orig hg-git-0.8.11/.hgsigs000066400000000000000000000154001324473741300142550ustar00rootroot000000000000002f7bd8db709f8cf27107839951e88a38f915709d 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 1bd9d9a0201c15c7233ece182d98087e4915805a 0 iQIVAwUAWPbquUemf/qjRqrOAQhP0RAAphf232y3ozyXhZAbFyF+EaJHpvN0UqHF7otPp/5eoobO0liD3ffLCcCY018bs/LixWY5fkdbXsd7SZI8uQKfzitdosnbAUJoL2+Sw0H+E8MUQp1bCbh9Gn1oFHgtDkYzKaFYSWwM0KyLaFQx+UC8ffyVv4GhTRSfg/rYvgBOgorRjQbkns9bvQRxv3q9Zme6EQnM7yoKLh+shqRf7XjONZ+Yp/P7beQTot4oBGUOP9PZw+t2fJwE+yP9NcamM7mRAtYDH6+yINed/R7JMdpNyWhCpa38R6/t5mOJbDR/1qEGGUZn9Kb4CE5CvmyVuUJC9w0Y+7PtrPC5q9s7k3tUZ3RD2/GDi7yv7cS688Uw3H3ZuRpk5s+HkPIyiYm3blcipR9uFItpXX6ANm9/2tnzcxrP4mzB3UCygL3cwIibG3xyRiN4iUZa+QbUJmy5rRL/yrYU7tDKfUL+dvq5Qvx774XMDm5VZZTUwA3tEXACIXkUH8MUUjDllqK9f7FJ/CRSYyUnANFle7X5XfuszQ9l0CIXM5AVUosiEix0XnJr+RodBb0Kq9AUCzXTEX5cCxI2AhnWl7+Kg+Cc9/V8dF56EvLjH4H583e7tmI3S5oc4fA2d1C90/Gq/UnIJnV2Z+EkN2j7OabZ2xPnMezpyo1NKoRS45YeV1HaxApcamtkXB4= 620a1095f3a92c81d4087ab264501e37743c6e84 0 iQIVAwUAWXZUFUemf/qjRqrOAQhz5g/+N1gGY+7Awd5ko74kY1si+asBX6viR2lz/UTCXdAbyKI9DygFhGhMQz1cd2XVj0VJ4UZJnqimwMic3sIXjaQbMeDhP5PuptygGipvx4S6kRzvfJOArEj6y5337zJZ4ARF0imIN5zQdERJtfjECZd4l2JP95gj8tfxI88SH+57wCTO0MlEpXTjRL2ZJ+KlfQ54cAziCU3qraahLd2rkBhWYkFI4ZRr8GdzP3pDk6yIUUBcWUP8ePSQyiB16qkTjpjENMkDerRsJimclXzq7rplCOiKxNl9llyNdy22gPZbJOGjgF/463j3QrHUTKnakXJKztjF3Q7ICea9Las9L9SZCZ9Jqrykmgk1kCbwE4vOWsUx+ov/gyS49LCYg6f605e3SA8M3/7NXw0+TToPDLbZPlcc3vVEXNgPU5xGKcHPnoTtdGkcP1vexWP4e+AmhmYFU4i/qvagXbSXlaP67tpUnVk3LEqa0bWPVQzwuRNQpXCoj5zYYouweGPDvS4n/B5jtXz6P2WNyatkyK6gpGRk8nLu7W2Go+/nCdB60jfHxWpsVNcSM6uZouEvw4l7Vkg2peZsG5+96TJ+tGf6zG8KmvAkixbhACPjkFM2ZSKP3WH3ALrixGV2apCo7IXF+6vIacsBY+9CVkrRZdZ2dPAJrxAEdf2cFI971CjVv+iBr3I= 631d42fbf8a27cfaa25b04914f2671351b2ea6ac 0 iQIVAwUAWXZhUUemf/qjRqrOAQjPyA//Xd5TUkiEumFMe3bgddFdwyyu15RQV3bYiNP4/yhICfL8+Tq8oz8z8fwvp+i8T548m24y3LfZp+v5mZLv32zn/nKFL+lm3glVBApYI51tgjL0zFdl6CeCqkRqKKdVOXDp9rfeDGZfs64esUHWSnOn2tWVVxqN6dyAeSv2pwl95+HI4nO3f+jTKcMpriQRyrKHG1zdCj7xxfVPDzOscmK3XlHsloWgnKmCL/KoLjc/ngKz6ZDv2YS7TMqFGS8JDomfjyGG1aN2AYcs9D5SIELyWbwzBvEYGD66uUkKUeE0cf2cK5xkx5HFuTjqkxnhfWJeedFMvsudh/hIhlrQkT28D+SJAIQvsYL6xq8RC0E2Pp9WjWLzsI3XNnfzO7cW+lgZPp+f4qvecw+QcBex4ET1FDG+JtlR4ZceSz/SVvdrAZxhoHKMYU4sCyiA7IiqbfC8axH5ag18HxhK21qh083axBCPnZ4RWY+EDWdcXB/ifqQahA36KcIXrWLonq20t4Hv6osNiwnvpbz2F8iuWZj4RSerBm/XwgZ4v7UkonERVvv09C/UqaxYAbniNyYNxl5sDoFO2NWKvkkqLwVtQSBemMkK21kl4nT8pDvRuvB4wkJmiCHVjQKo5d1Fz7/cI58SlXaOMlrb5zXhyh8ZTcRgmgaG7SqsJbhgvUt9VRjRe9E= 88de3215e1389966053b6b10e93e5a3132625060 0 iQIVAwUAWYy2tkemf/qjRqrOAQjoVhAAh3aYB7e1I4lIFAd7sCemnHpjSRmHi3Lf2Ge0c/9nquNbWmkYjNtFcJsnZgd90YYvKladHJ+qCGg/a++OPVxqt/39SZduML1U7eGPpkQ7M9WAHWxDWcU9EZJZkOUKxF2X81T3thcL77zMtxRyVgTPzc8jy5FYfx7ILg3ooecSQxSoVwRWhizp8GdBnWhCTIW/XegjSYQRE5QvfpS1z/Uy5qHvCuWkvyLYiLO3V5kCjzSMG4B027iC+41Ofz7/THureOHQ1Ask4QIi81lIcBa+VS5XrGufyMf5AoPD13txQjodI1ZePtOi+mUB4JagTvk/n/b2maRKs/NFB+mDUjRQY95a6KbipdTAbsxSdJEDBBbSKXiYIXuG/pQLR3nMxa9MoEHJZVqCfJoHUUIKZQHgVKpffIArxecWhxD+Cpw09I/N6EN1/5+NR2keTSZloYvY73iIXfENMIf1RAoJGAIkNRSKdCHOKg9oNG+ItZyRssMXVZ4Q13EDtIIV7r+uwP+2ek6nXTrsAr/m5g3P6oFLBHNV+FXbbTbpTUIVjfrNjxvgq4hZoSU5+x6h83jXWr+4PeSBNjK7FousCEyvxSogxRl3VpshQfAIxV1yq1Hyxq3YIHLi6YPTiGNPDstwZODszMzp3Grh410xkij6bmx8+45BA63MpL3NkdrD0utqTNw= 6ef27582bfa5952e2c98ae8d9331c76e899fc353 0 iQIzBAABCAAdFiEEOoFVFj0OIKUw/LeGR6Z/+qNGqs4FAln6c7wACgkQR6Z/+qNGqs69xA//UuZ1vG3nOJUfsyiUyQT3xhg8YddpeGAU3RUPdXCQ84bMwapOaVAWrcfY5nZhQbfpkPWMPxZINuw1/DxTKbXFcunGeWrpwP+WBRMLy/KEXNjcRL1g9uAZyaQdo6N1Ly0za8W7hcwk8Tz1LG2mTvIXGhIvux5ExtpUEq2BSzs7pIGHz+PhTko/6BpK94V/Muo0rMs7oKEHmTWnWkFO9Yuq56MJAPFIOzpw+4fPid1NHOJm5gaGBVVodwzGF7LWJzqHLSRjYAkY9vQW0Bf3zQ6TXu41HYHDYYrIavFbtqUsCHynNoP8+WOhAenDzSVuEPvwaPT8jcN5DLM9Wnr5TRjapOZX0c/hsgELKFi+Mss6j5/54/C8GOrFZDmE++vaLTLDkGcp9FJByFCtkABpVUiETQ3cdd5etwUDFtmjo/y1NJgi0arC0nmKWyetdwr3LXZdazy7GmPF3HIHBKsiotbptMalTfwt4h52jsvIIUfYGPyOBZ5m6uRypfi3SJlSldYsWpEYfRZEbHbMXMq+vCyC9pqlE+qZ788QVEYNtFd0E65Q8dumzkIwuiBXG6n9zjo4pmbu2wp2dfJ7q4Ae8uZCfU8z9lPU8oKqrdmWXexMJH44RnF4e6tHZghoPp/iB4zg++AG6Dbgy7pwyPzG1hEK/hLqKt6ESPpbV7fbXmCIr2E= hg-git-0.8.11/.hgtags000066400000000000000000000026031324473741300142470ustar00rootroot00000000000000505d7cdca19838bfb270193e0709154a2dad5c19 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 53d514c9c7e6ac99e2c4d6df6c520a7db1e63d7f 0.8.5 1bd9d9a0201c15c7233ece182d98087e4915805a 0.8.6 620a1095f3a92c81d4087ab264501e37743c6e84 0.8.7 631d42fbf8a27cfaa25b04914f2671351b2ea6ac 0.8.8 88de3215e1389966053b6b10e93e5a3132625060 0.8.9 6ef27582bfa5952e2c98ae8d9331c76e899fc353 0.8.10 hg-git-0.8.11/CONTRIBUTING000066400000000000000000000021431324473741300146220ustar00rootroot00000000000000The 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. https://www.mercurial-scm.org/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: Mailing list: hg-git@googlegroups.com Archives: https://groups.google.com/forum/#!forum/hg-git 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. hg-git-0.8.11/COPYING000066400000000000000000000432541324473741300140330ustar00rootroot00000000000000 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. hg-git-0.8.11/DESIGN.txt000066400000000000000000000075251324473741300145130ustar00rootroot00000000000000Hg-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. hg-git-0.8.11/MANIFEST.in000066400000000000000000000002451324473741300145270ustar00rootroot00000000000000include COPYING include MANIFEST.in include README.md recursive-include hggit *.py *.rst recursive-include tests *.t *.py *.py.out hghave latin-1-encoding testutil hg-git-0.8.11/Makefile000066400000000000000000000027271324473741300144400ustar00rootroot00000000000000PYTHON=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-%: 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 local) && \ cd tests && $(PYTHON) run-tests.py --with-hg=$(CREW)/hg $(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-2.9.2 tests-3.0.2 tests-3.1.2 tests-3.2.4 \ tests-3.3.3 tests-3.4.2 tests-3.5.2 tests-3.6.3 tests-3.7.3 tests-3.8.4 \ tests-3.9.2 tests-4.0.2 tests-4.1.3 tests-4.2.3 tests-4.3.3 tests-4.4.2 \ tests-4.5 tests-@ .PHONY: tests all-version-tests hg-git-0.8.11/README.md000066400000000000000000000254471324473741300142630ustar00rootroot00000000000000Hg-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 ======== See `hg help -e hggit`. 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.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.blockdotgit --------------- Blocks exporting revisions to Git that contain a directory named .git or any letter-case variation thereof. This prevents creating repositories that newer versions of Git and many Git hosting services block due to security concerns. Defaults to True. git.blockdothg -------------- Blocks importing revisions from Git that contain a directory named .hg. Defaults to True. 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.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. 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.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.public ---------- A list of Git branches that should be considered "published", and therefore converted to Mercurial in the 'public' phase. This is only used if hggit.usephases is set. 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.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). hggit.mapsavefrequency ---------------------- Controls how often the mapping between Git and Mercurial commit hashes gets saved when importing or exporting changesets. Set this to a number greater than 0 to save the mapping after converting that many commits. This can help when the conversion encounters an error partway through a large batch of changes. Defaults to 0, so that the mapping is saved once at the end. hggit.usephases --------------- When converting Git revisions to Mercurial, place them in the 'public' phase as appropriate. Namely, revisions that are reachable from the remote Git repository's HEAD will be marked 'public'. For most repositories, this means the remote 'master' will be converted as public. This speeds up some local Mercurial operations including `hg shelve`. hg-git-0.8.11/TODO.txt000066400000000000000000000014611324473741300143000ustar00rootroot00000000000000GENERAL ======= * 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?) hg-git-0.8.11/contrib/000077500000000000000000000000001324473741300144305ustar00rootroot00000000000000hg-git-0.8.11/contrib/hggitperf.py000066400000000000000000000044141324473741300167640ustar00rootroot00000000000000# 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() hg-git-0.8.11/hggit/000077500000000000000000000000001324473741300140725ustar00rootroot00000000000000hg-git-0.8.11/hggit/__init__.py000066400000000000000000000337371324473741300162200ustar00rootroot00000000000000# 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 compat 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.error import LookupError 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.11' testedwith = ('2.8.2 2.9.2 3.0.2 3.1.2 3.2.4 3.3.3 3.4.2 3.5.2 3.6.3 3.7.3' '3.8.4 3.9.2 4.0.2 4.1.3 4.2.3 4.3.3 4.4.2 4.5') buglink = 'https://bitbucket.org/durin42/hg-git/issues' cmdtable = {} configtable = {} try: from mercurial import registrar command = registrar.command(cmdtable) configitem = registrar.configitem(configtable) compat.registerconfigs(configitem) except (ImportError, AttributeError): 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)) and hgutil.safehasattr(repo, 'vfs') and os.path.exists(compat.gitvfs(repo).join('git'))): # 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 Strips all Git-related metadata from the repo, including the mapping between Git and Mercurial changesets. This is an irreversible destructive operation that may prevent further interaction with other clones. ''' 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 = [] vfs = compat.gitvfs(repo) for line in vfs(GitHandler.map_file): gitsha, hgsha = line.strip().split(' ', 1) if hgsha in repo: new_map.append('%s %s\n' % (gitsha, hgsha)) wlock = repo.wlock() try: f = vfs(GitHandler.map_file, 'wb') map(f.write, new_map) finally: wlock.release() 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 the changeset that originates in the given Git revision. The hash may be abbreviated: `gitnode(a5b)` selects the revision whose Git hash starts with `a5b`. Aborts if multiple changesets match the abbreviation. ''' 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 gitnode.startswith(rev) result = baseset(r for r in subset if matches(r)) if 0 <= len(result) < 2: return result raise LookupError(rev, git.map_file, _('ambiguous identifier')) 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 hg-git-0.8.11/hggit/_ssh.py000066400000000000000000000026421324473741300154040ustar00rootroot00000000000000from 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 hg-git-0.8.11/hggit/compat.py000066400000000000000000000113441324473741300157320ustar00rootroot00000000000000from mercurial import ( context, url, util as hgutil, ) try: 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 try: from mercurial import vfs as vfsmod hgvfs = vfsmod.vfs except ImportError: # vfsmod was extracted in hg 4.2 from mercurial import scmutil hgvfs = scmutil.vfs def gitvfs(repo): """return a vfs suitable to read git related data""" # Mercurial >= 3.3: repo.shared() if repo.sharedpath != repo.path: return hgvfs(repo.sharedpath) else: return repo.vfs def passwordmgr(ui): try: realm = hgutil.urlreq.httppasswordmgrwithdefaultrealm() return url.passwordmgr(ui, realm) except (TypeError, AttributeError): # compat with hg < 3.9 return url.passwordmgr(ui) try: import dulwich.client FetchPackResult = dulwich.client.FetchPackResult read_pkt_refs = dulwich.client.read_pkt_refs except (AttributeError, ImportError): # older dulwich doesn't return the symref where remote HEAD points, so we # monkey patch it here from dulwich.errors import GitProtocolError from dulwich.protocol import extract_capabilities class FetchPackResult(object): """Result of a fetch-pack operation. :var refs: Dictionary with all remote refs :var symrefs: Dictionary with remote symrefs :var agent: User agent string """ def __init__(self, refs, symrefs, agent): self.refs = refs self.symrefs = symrefs self.agent = agent def read_pkt_refs(proto): server_capabilities = None refs = {} # Receive refs from server for pkt in proto.read_pkt_seq(): (sha, ref) = pkt.rstrip('\n').split(None, 1) if sha == 'ERR': raise GitProtocolError(ref) if server_capabilities is None: (ref, server_capabilities) = extract_capabilities(ref) symref = 'symref=HEAD:' for cap in server_capabilities: if cap.startswith(symref): sha = cap.replace(symref, '') refs[ref] = sha if len(refs) == 0: return None, set([]) return refs, set(server_capabilities) def memfilectx(repo, changectx, path, data, islink=False, isexec=False, copied=None): # Different versions of mercurial have different parameters to # memfilectx. Try them from newest to oldest. args_to_try = ( (repo, changectx, path, data), # hg 4.5+ (repo, path, data), # hg 3.1 - 4.5 (path, data), # hg < 3.1 ) for args in args_to_try: try: return context.memfilectx(*args, islink=islink, isexec=isexec, copied=copied) except TypeError as ex: last_ex = ex raise last_ex CONFIG_DEFAULTS = { 'git': { 'authors': None, 'blockdotgit': True, 'blockdothg': True, 'branch_bookmark_suffix': None, 'debugextrainmessage': False, # test only -- do not document this! 'findcopiesharder': False, 'intree': None, 'mindate': None, 'public': list, 'renamelimit': 400, 'similarity': 0, }, 'hggit': { 'mapsavefrequency': 0, 'usephases': False, } } hasconfigitems = False def registerconfigs(configitem): global hasconfigitems hasconfigitems = True for section, items in CONFIG_DEFAULTS.iteritems(): for item, default in items.iteritems(): configitem(section, item, default=default) def config(ui, subtype, section, item): if subtype == 'string': subtype = '' getconfig = getattr(ui, 'config' + subtype) if hasconfigitems: return getconfig(section, item) return getconfig(section, item, CONFIG_DEFAULTS[section][item]) hg-git-0.8.11/hggit/git2hg.py000066400000000000000000000123201324473741300156260ustar00rootroot00000000000000# 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 ref, sha in refs.iteritems(): # refs could contain refs on the server that we haven't pulled down # the objects for; also make sure it's a sha and not a symref if ref != 'HEAD' and 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) hg-git-0.8.11/hggit/git_handler.py000066400000000000000000002073131324473741300167320ustar00rootroot00000000000000import 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, phases, util as hgutil, url, ) import _ssh import compat 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 self.vfs = self.repo.vfs # Mercurial >= 3.3: repo.shared() if dest_repo.sharedpath != dest_repo.path: self.vfs = compat.hgvfs(dest_repo.sharedpath) if compat.config(ui, 'bool', 'git', 'intree'): self.gitdir = self.repo.wvfs.join('.git') else: self.gitdir = self.vfs.join('git') self.init_author_file() self.paths = ui.configitems('paths') self.branch_bookmark_suffix = compat.config( ui, 'string', '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 = {} authors_path = compat.config(self.ui, 'string', 'git', 'authors') if authors_path: with open(self.repo.wvfs.join(authors_path)) 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.vfs.join(self.map_file)): for line in self.vfs(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): wlock = self.repo.wlock() try: file = self.vfs(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() finally: wlock.release() def load_tags(self): self.tags = {} if os.path.exists(self.vfs.join(self.tags_file)): for line in self.vfs(self.tags_file): sha, name = line.strip().split(' ', 1) self.tags[name] = sha def save_tags(self): file = self.vfs(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): result = self.fetch_pack(remote, heads) remote_name = self.remote_name(remote) # if remote returns a symref for HEAD, then let's store that rhead = None rnode = None oldheads = self.repo.changelog.heads() imported = 0 if result.refs: filteredrefs = self.filter_min_date(self.filter_refs(result.refs, heads)) imported = self.import_git_objects(remote_name, filteredrefs) self.import_tags(result.refs) self.update_hg_bookmarks(result.refs) try: symref = result.symrefs['HEAD'] if symref.startswith('refs/heads'): rhead = symref.replace('refs/heads/', '') rnode = result.refs['refs/heads/%s' % rhead] rnode = self._map_git[rnode] rnode = self.repo[rnode].node() except KeyError: # if there is any error make sure to clear the variables rhead = None rnode = None if remote_name: self.update_remote_branches(remote_name, result.refs) elif not self.git.refs.as_dict('refs/remotes/'): # intial cloning self.update_remote_branches('default', result.refs) # "Activate" a tipmost bookmark. bms = self.repo['tip'].bookmarks() # override the 'tipmost' behavior if we know the remote HEAD if rnode: # make sure the bookmark exists; at the point the remote # branches has already been set up suffix = self.branch_bookmark_suffix or '' changes = [(rhead + suffix, rnode)] util.updatebookmarks(self.repo, changes) bms = [rhead + suffix] 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) # also mark public any branches the user specified blist = [self.repo[branch].node() for branch in self.ui.configlist('git', 'public')] if rnode and self.ui.configbool('hggit', 'usephases'): blist.append(rnode) if blist: lock = self.repo.lock() try: tr = self.repo.transaction("phase") try: phases.advanceboundary(self.repo, tr, phases.public, blist) except TypeError: # hg < 3.2 phases.advanceboundary(self.repo, phases.public, blist) tr.close() finally: if tr is not None: tr.release() lock.release() 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.vfs.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() result = 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 result.refs: reqrefs[n] = result.refs[n] else: reqrefs = result.refs commits = [bin(c) for c in self.get_git_incoming(reqrefs).commits] b = overlayrepo(self, commits, result.refs) return (b, commits, lambda: None) # CHANGESET CONVERSION METHODS def export_git_objects(self): self.ui.note(_("finding hg commits to export\n")) repo = self.repo clnode = repo.changelog.node nodes = (clnode(n) for n in repo) to_export = (repo[node] for node in nodes if not hex(node) in self._map_hg) todo_total = len(repo) - len(self._map_hg) topic = 'find commits to export' pos = 0 unit = 'commits' export = [] for ctx in to_export: item = hex(ctx.node()) pos += 1 repo.ui.progress(topic, pos, item, unit, todo_total) if ctx.extra().get('hg-git', None) != 'octopus': export.append(ctx) 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) mapsavefreq = compat.config(self.ui, 'int', 'hggit', 'mapsavefrequency') for i, ctx in enumerate(export): self.ui.progress('exporting', i, total=total) self.export_hg_commit(ctx.node(), exporter) if mapsavefreq and i % mapsavefreq == 0: self.ui.debug(_("saving mapfile\n")) self.save_map(self.map_file) self.ui.progress('exporting', None, total=total) def set_commiter_from_author(self, commit): commit.committer = commit.author commit.commit_time = commit.author_time commit.commit_timezone = commit.author_timezone # 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: try: # 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 except: # extra is essentially user-supplied, we must be careful self.set_commiter_from_author(commit) else: self.set_commiter_from_author(commit) 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 collections import namedtuple >>> from mercurial.ui import ui >>> mockrepo = namedtuple('localrepo', ['vfs', 'sharedpath', 'path']) >>> mockrepo.vfs = '' >>> mockrepo.sharedpath = '' >>> mockrepo.path = '' >>> g = GitHandler(mockrepo, 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 = compat.config(self.ui, 'bool', 'git', 'debugextrainmessage') 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 = compat.config(self.ui, 'int', 'hggit', 'mapsavefrequency') 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] return compat.memfilectx(self.repo, memctx, 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 else: extra['branch'] = 'default' # 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 bookmark" % 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 bookmark" % 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() # only newer versions (0.18) of dulwich have symref support client.read_pkt_refs = compat.read_pkt_refs 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 if ret is None: ret = {} # for older dulwich, the return type was a dict, meaning we're # running our monkey patch read_pkt_refs if isinstance(ret, dict): head = ret.get("HEAD") symrefs = {} # we want to raise a KeyError if HEAD is not a key if head: symrefs["HEAD"] = head # use FetchPackResult as upstream now does ret = compat.FetchPackResult(ret, symrefs, client.default_user_agent_string()) return ret 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 = compat.config(self.ui, 'string', '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 '' changes = [] 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 changes.append((head + suffix, hgsha)) else: bm = self.repo[bms[head]] if bm.ancestor(self.repo[hgsha]) == bm: # fast forward changes.append((head + suffix, hgsha)) if heads: util.updatebookmarks(self.repo, changes) 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: self.audit_hg_path(newfile) # 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 = compat.config(self.ui, 'int', 'git', 'similarity') if similarity < 0 or similarity > 100: raise hgutil.Abort(_('git.similarity must be between 0 and 100')) if similarity == 0: return None # default is borrowed from Git max_files = compat.config(self.ui, 'int', 'git', 'renamelimit') if max_files < 0: raise hgutil.Abort(_('git.renamelimit must be non-negative')) if max_files == 0: max_files = None find_copies_harder = compat.config(self.ui, 'bool', 'git', 'findcopiesharder') 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] def audit_hg_path(self, path): if '.hg' in path.split(os.path.sep): if compat.config(self.ui, 'bool', 'git', 'blockdothg'): raise hgutil.Abort( ('Refusing to import problematic path %r' % path), hint=("Mercurial cannot check out paths inside nested " + "repositories; if you need to continue, then set " + "'[git] blockdothg = false' in your hgrc.")) self.ui.warn(('warning: path %r is within a nested repository, ' + 'which Mercurial cannot check out.\n') % path) # 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 collections import namedtuple >>> from dulwich.client import HttpGitClient, SSHGitClient >>> from mercurial.ui import ui >>> mockrepo = namedtuple('localrepo', ['vfs', 'sharedpath', 'path']) >>> mockrepo.vfs = '' >>> mockrepo.sharedpath = '' >>> mockrepo.path = '' >>> g = GitHandler(mockrepo, 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() host, port, sepr = res['host'], res['port'], res['sepr'] transport = client.TCPGitClient if 'ssh' in res['scheme']: util.checksafessh(host) transport = client.SSHGitClient 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://'): pmgr = compat.passwordmgr(self.ui) auth = urllib2.HTTPBasicAuthHandler(pmgr) 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 hg-git-0.8.11/hggit/gitdirstate.py000066400000000000000000000227131324473741300167740ustar00rootroot00000000000000import os import stat import re import errno from mercurial import ( dirstate, match as matchmod, 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) else: pats = [(f, ['include:%s' % f]) for f in files] for f, patlist in pats: allpats.extend(patlist) 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: if not ignoremod: # in this case, patlist is ['include: FILE'], and # inst[0] should already include FILE raise 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 # osutil moved in hg 4.3, but util re-exports listdir try: listdir = util.listdir except AttributeError: from mercurial import osutil 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() hg-git-0.8.11/hggit/gitrepo.py000066400000000000000000000047111324473741300161200ustar00rootroot00000000000000from util import isgitsshuri from mercurial import util from mercurial.error import RepoError peerapi = False try: from mercurial.repository import peer as peerrepository peerapi = True except ImportError: from mercurial.peer import peerrepository class gitrepo(peerrepository): 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 _peercapabilities = ['lookup'] def _capabilities(self): return self._peercapabilities def capabilities(self): return self._peercapabilities @property def ui(self): return self._ui 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 result = handler.fetch_pack(self.path, heads=[]) # map any git shas that exist in hg to hg shas stripped_refs = { ref[11:]: handler.map_hg_get(val) or val for ref, val in result.refs.iteritems() if ref.startswith('refs/heads/') } return stripped_refs return {} def pushkey(self, namespace, key, old, new): return False if peerapi: def branchmap(self): raise NotImplementedError def canpush(self): return True def close(self): pass def debugwireargs(self): raise NotImplementedError def getbundle(self): raise NotImplementedError def iterbatch(self): raise NotImplementedError def known(self): raise NotImplementedError def peer(self): return self def stream_out(self): raise NotImplementedError def unbundle(self): raise NotImplementedError instance = gitrepo def islocal(path): if isgitsshuri(path): return True u = util.url(path) return not u.scheme or u.scheme == 'file' hg-git-0.8.11/hggit/help/000077500000000000000000000000001324473741300150225ustar00rootroot00000000000000hg-git-0.8.11/hggit/help/git.rst000066400000000000000000000054041324473741300163420ustar00rootroot00000000000000Basic 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. hg-git-0.8.11/hggit/hg2git.py000066400000000000000000000376341324473741300156450ustar00rootroot00000000000000# 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 compat.config(ui, 'bool', 'git', 'blockdotgit'): 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) hg-git-0.8.11/hggit/hgrepo.py000066400000000000000000000060621324473741300157340ustar00rootroot00000000000000from 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 hg-git-0.8.11/hggit/overlay.py000066400000000000000000000337711324473741300161400ustar00rootroot00000000000000# 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, changelog, manifest, match as matchmod, context, util, ) 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, match=None, clean=False): # Older mercurial clients used diff(m2, clean=False). If a caller failed # to specify clean as a keyword arg, it might get passed as match here. if isinstance(match, bool): clean = match match = None 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 if match is None: match = matchmod.always('', '') for fn, n1 in self.iteritems(): if not match(fn): continue 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: if not match(fn): continue fl2 = m2flagget(fn, '') diff[fn] = ((None, ''), (n2, fl2)) return diff def __delitem__(self, path): del self._map[path] def wrapmanifestdictdiff(orig, self, m2, match=None, clean=False): '''avoid calling into lazymanifest code if m2 is an overlaymanifest''' # Older mercurial clients used diff(m2, clean=False). If a caller failed # to specify clean as a keyword arg, it might get passed as match here. if isinstance(match, bool): clean = match match = None kwargs = { 'clean' : clean } # Older versions of mercurial don't support the match arg, so only add it if # it exists. if match is not None: kwargs['match'] = match if isinstance(m2, overlaymanifest): diff = m2.diff(self, **kwargs) # 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, **kwargs) class overlayfilectx(object): def __init__(self, repo, path, fileid=None): self._repo = repo self._path = path self.fileid = fileid def repo(self): return self._repo # 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 def isbinary(self): return util.binary(self.data()) class overlaychangectx(context.changectx): def __init__(self, repo, sha): # Can't store this in self._repo because the base class uses that field self._hgrepo = 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 repo(self): return self._hgrepo 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._hgrepo.changelog parents = cl.parents(cl.node(self._rev)) if not parents: return [self._hgrepo['null']] if parents[1] == nullid: parents = parents[:-1] return [self._hgrepo[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 overlayoldmanifestlog(overlayrevlog): def read(self, sha): if sha == nullid: return manifest.manifestdict() return overlaymanifest(self.repo, sha) def __getitem__(self, sha): return overlaymanifestctx(self.repo, sha) class overlaymanifestrevlog(overlayrevlog): pass class overlaymanifestctx(object): def __init__(self, repo, node): self._repo = repo self._node = node def read(self): return overlaymanifest(self._repo, self._node) try: class overlaymanifestlog(manifest.manifestlog): def __init__(self, repo): self._repo = repo # Needed for 4.0, since __getitem__ did not redirect to get() in that # release. def __getitem__(self, node): return self.get('', node) def get(self, dir, node): if dir: raise RuntimeError("hggit doesn't support treemanifests") if node == nullid: return manifest.manifestctx() return overlaymanifestctx(self._repo, node) except AttributeError: # manifestlog did not exist prior to 4.0 pass 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() def changelogrevision(self, noderev): values = self.read(noderev) return changelog._changelogrevision( manifest=values[0], user=values[1], date=values[2], files=values[3], description=values[4], extra=values[5], ) class overlayrepo(object): def __init__(self, handler, commits, refs): self.handler = handler self.changelog = overlaychangelog(self, handler.repo.changelog) if util.safehasattr(handler.repo, 'manifest'): self.manifest = overlayoldmanifestlog(self, handler.repo.manifest) # new as of mercurial 3.9+ self.manifestlog = self.manifest else: # no more manifest class as of 4.1 self.manifestlog = overlaymanifestlog(self) # 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 _constructmanifest(self): return overlaymanifestrevlog(self, self.handler.repo._constructmanifest()) 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 unfiltered(self): return self.handler.repo.unfiltered() 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) hg-git-0.8.11/hggit/util.py000066400000000000000000000103621324473741300154230ustar00rootroot00000000000000"""Compatibility functions for old Mercurial versions and other utility functions.""" import re import urllib try: from collections import OrderedDict except ImportError: from ordereddict import OrderedDict from dulwich import errors from mercurial.i18n import _ from mercurial import ( encoding, error, 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, ) hg-git-0.8.11/tests/000077500000000000000000000000001324473741300141325ustar00rootroot00000000000000hg-git-0.8.11/tests/commitextra.py000066400000000000000000000016201324473741300170370ustar00rootroot00000000000000'''test helper extension to create commits with multiple extra fields''' from mercurial import cmdutil, commands, scmutil cmdtable = {} try: from mercurial import registrar command = registrar.command(cmdtable) except (ImportError, AttributeError): 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 hg-git-0.8.11/tests/heredoctest.py000066400000000000000000000007641324473741300170240ustar00rootroot00000000000000import 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) hg-git-0.8.11/tests/hghave000077500000000000000000000040571324473741300153300ustar00rootroot00000000000000#!/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) hg-git-0.8.11/tests/hghave.py000077500000000000000000000213001324473741300157450ustar00rootroot00000000000000import 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"), } hg-git-0.8.11/tests/killdaemons.py000077500000000000000000000057201324473741300170150ustar00rootroot00000000000000#!/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) hg-git-0.8.11/tests/latin-1-encoding000066400000000000000000000006211324473741300171050ustar00rootroot00000000000000# -*- 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à' hg-git-0.8.11/tests/run-tests.py000077500000000000000000003266131324473741300164660ustar00rootroot00000000000000#!/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 # 10) parallel, pure, tests that call run-tests: # ./run-tests.py --pure `grep -l run-tests.py *.t` # # (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 __future__ import absolute_import, print_function import difflib import distutils.version as version import errno import json import optparse import os import random import re import shutil import signal import socket import subprocess import sys import sysconfig import tempfile import threading import time import unittest import xml.dom.minidom as minidom try: import Queue as queue except ImportError: import queue try: import shlex shellquote = shlex.quote except (ImportError, AttributeError): import pipes shellquote = pipes.quote if os.environ.get('RTUNICODEPEDANTRY', False): try: reload(sys) sys.setdefaultencoding("undefined") except NameError: pass origenviron = os.environ.copy() osenvironb = getattr(os, 'environb', os.environ) processlock = threading.Lock() pygmentspresent = False # ANSI color is unsupported prior to Windows 10 if os.name != 'nt': try: # is pygments installed import pygments import pygments.lexers as lexers import pygments.lexer as lexer import pygments.formatters as formatters import pygments.token as token import pygments.style as style pygmentspresent = True difflexer = lexers.DiffLexer() terminal256formatter = formatters.Terminal256Formatter() except ImportError: pass if pygmentspresent: class TestRunnerStyle(style.Style): default_style = "" skipped = token.string_to_tokentype("Token.Generic.Skipped") failed = token.string_to_tokentype("Token.Generic.Failed") skippedname = token.string_to_tokentype("Token.Generic.SName") failedname = token.string_to_tokentype("Token.Generic.FName") styles = { skipped: '#e5e5e5', skippedname: '#00ffff', failed: '#7f0000', failedname: '#ff0000', } class TestRunnerLexer(lexer.RegexLexer): tokens = { 'root': [ (r'^Skipped', token.Generic.Skipped, 'skipped'), (r'^Failed ', token.Generic.Failed, 'failed'), (r'^ERROR: ', token.Generic.Failed, 'failed'), ], 'skipped': [ (r'[\w-]+\.(t|py)', token.Generic.SName), (r':.*', token.Generic.Skipped), ], 'failed': [ (r'[\w-]+\.(t|py)', token.Generic.FName), (r'(:| ).*', token.Generic.Failed), ] } runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle) runnerlexer = TestRunnerLexer() if sys.version_info > (3, 5, 0): PYTHON3 = True xrange = range # we use xrange in one place, and we'd rather not use range def _bytespath(p): if p is None: return p return p.encode('utf-8') def _strpath(p): if p is None: return p return p.decode('utf-8') elif sys.version_info >= (3, 0, 0): print('%s is only supported on Python 3.5+ and 2.7, not %s' % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))) sys.exit(70) # EX_SOFTWARE from `man 3 sysexit` else: PYTHON3 = False # In python 2.x, path operations are generally done using # bytestrings by default, so we don't have to do any extra # fiddling there. We define the wrapper functions anyway just to # help keep code consistent between platforms. def _bytespath(p): return p _strpath = _bytespath # For Windows support wifexited = getattr(os, "WIFEXITED", lambda x: False) # Whether to use IPv6 def checksocketfamily(name, port=20058): """return true if we can listen on localhost using family=name name should be either 'AF_INET', or 'AF_INET6'. port being used is okay - EADDRINUSE is considered as successful. """ family = getattr(socket, name, None) if family is None: return False try: s = socket.socket(family, socket.SOCK_STREAM) s.bind(('localhost', port)) s.close() return True except socket.error as exc: if exc.errno == errno.EADDRINUSE: return True elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT): return False else: raise else: return False # useipv6 will be set by parseargs useipv6 = None def checkportisavailable(port): """return true if a port seems free to bind on localhost""" if useipv6: family = socket.AF_INET6 else: family = socket.AF_INET try: s = socket.socket(family, socket.SOCK_STREAM) s.bind(('localhost', port)) s.close() return True except socket.error as exc: if exc.errno not in (errno.EADDRINUSE, errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT): raise return False 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 PYTHON = _bytespath(sys.executable.replace('\\', '/')) IMPL_PATH = b'PYTHONPATH' if 'java' in sys.platform: IMPL_PATH = b'JYTHONPATH' defaults = { 'jobs': ('HGTEST_JOBS', 1), 'timeout': ('HGTEST_TIMEOUT', 180), 'slowtimeout': ('HGTEST_SLOWTIMEOUT', 500), 'port': ('HGTEST_PORT', 20059), 'shell': ('HGTEST_SHELL', 'sh'), } def canonpath(path): return os.path.realpath(os.path.expanduser(path)) def parselistfiles(files, listtype, warn=True): entries = dict() for filename in files: try: path = os.path.expanduser(os.path.expandvars(filename)) f = open(path, "rb") except IOError as 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(b'#', 1)[0].strip() if line: entries[line] = filename f.close() return entries def parsettestcases(path): """read a .t test file, return a set of test case names If path does not exist, return an empty set. """ cases = set() try: with open(path, 'rb') as f: for l in f: if l.startswith(b'#testcases '): cases.update(l[11:].split()) except IOError as ex: if ex.errno != errno.ENOENT: raise return cases def getparser(): """Obtain the OptionParser used by the CLI.""" 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("--test-list", action="append", help="read tests to run from the specified file") parser.add_option("--changed", type="string", help="run tests that are changed in parent rev or working directory") 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("--color", choices=["always", "auto", "never"], default=os.environ.get('HGRUNTESTSCOLOR', 'auto'), help="colorisation: always|auto|never (default: auto)") parser.add_option("-d", "--debug", action="store_true", help="debug mode: write output of test scripts to console" " rather than capturing and diffing 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("-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("--list-tests", action="store_true", help="list tests instead of running them") parser.add_option("-l", "--local", action="store_true", help="shortcut for --with-hg=/../hg, " "and --with-chg=/../contrib/chg/chg if --chg is set") parser.add_option("--loop", action="store_true", help="loop tests repeatedly") parser.add_option("--runs-per-test", type="int", dest="runs_per_test", help="run each test N times (default=1)", default=1) parser.add_option("-n", "--nodiff", action="store_true", help="skip showing test changes") parser.add_option("--outputdir", type="string", help="directory to write error logs to (default=test directory)") 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("--slowtimeout", type="int", help="kill errant slow tests after SLOWTIMEOUT seconds" " (default: $%s or %d)" % defaults['slowtimeout']) parser.add_option("--time", action="store_true", help="time how long each test takes") parser.add_option("--json", action="store_true", help="store test result data in 'report.json' file") 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("--xunit", type="string", help="record xunit results at specified path") 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("--chg", action="store_true", help="install and use chg wrapper in place of hg") parser.add_option("--with-chg", metavar="CHG", help="use specified chg wrapper in place of hg") parser.add_option("--ipv6", action="store_true", help="prefer IPv6 to IPv4 for network related tests") parser.add_option("-3", "--py3k-warnings", action="store_true", help="enable Py3k warnings on Python 2.7+") # This option should be deleted once test-check-py3-compat.t and other # Python 3 tests run with Python 3. parser.add_option("--with-python3", metavar="PYTHON3", help="Python 3 interpreter (if running under Python 2)" " (TEMPORARY)") 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') parser.add_option('--profile-runner', action='store_true', help='run statprof on run-tests') parser.add_option('--allow-slow-tests', action='store_true', help='allow extremely slow tests') parser.add_option('--showchannels', action='store_true', help='show scheduling channels') parser.add_option('--known-good-rev', type="string", metavar="known_good_rev", help=("Automatically bisect any failures using this " "revision as a known-good revision.")) parser.add_option('--bisect-repo', type="string", metavar='bisect_repo', help=("Path of a repo to bisect. Use together with " "--known-good-rev")) for option, (envvar, default) in defaults.items(): defaults[option] = type(default)(os.environ.get(envvar, default)) parser.set_defaults(**defaults) return parser def parseargs(args, parser): """Parse arguments with our OptionParser and validate results.""" (options, args) = parser.parse_args(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 = canonpath(_bytespath(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 os.path.basename(options.with_hg) not in [b'hg', b'hg.exe']: sys.stderr.write('warning: --with-hg should specify an hg script\n') if options.local: testdir = os.path.dirname(_bytespath(canonpath(sys.argv[0]))) reporootdir = os.path.dirname(testdir) pathandattrs = [(b'hg', 'with_hg')] if options.chg: pathandattrs.append((b'contrib/chg/chg', 'with_chg')) for relpath, attr in pathandattrs: binpath = os.path.join(reporootdir, relpath) if os.name != 'nt' and not os.access(binpath, os.X_OK): parser.error('--local specified, but %r not found or ' 'not executable' % binpath) setattr(options, attr, binpath) if (options.chg or options.with_chg) and os.name == 'nt': parser.error('chg does not work on %s' % os.name) if options.with_chg: options.chg = False # no installation to temporary location options.with_chg = canonpath(_bytespath(options.with_chg)) if not (os.path.isfile(options.with_chg) and os.access(options.with_chg, os.X_OK)): parser.error('--with-chg must specify a chg executable') if options.chg and options.with_hg: # chg shares installation location with hg parser.error('--chg does not work when --with-hg is specified ' '(use --with-chg instead)') if options.color == 'always' and not pygmentspresent: sys.stderr.write('warning: --color=always ignored because ' 'pygments is not installed\n') if options.bisect_repo and not options.known_good_rev: parser.error("--bisect-repo cannot be used without --known-good-rev") global useipv6 if options.ipv6: useipv6 = checksocketfamily('AF_INET6') else: # only use IPv6 if IPv4 is unavailable and IPv6 is available useipv6 = ((not checksocketfamily('AF_INET')) and checksocketfamily('AF_INET6')) 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") if options.anycoverage and options.with_hg: parser.error("sorry, coverage options do not work when --with-hg " "is specified") global verbose if options.verbose: verbose = '' if options.tmpdir: options.tmpdir = canonpath(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') if options.slowtimeout != defaults['slowtimeout']: sys.stderr.write( 'warning: --slowtimeout option ignored with --debug\n') options.timeout = 0 options.slowtimeout = 0 if options.py3k_warnings: if PYTHON3: parser.error( '--py3k-warnings can only be used on Python 2.7') if options.with_python3: if PYTHON3: parser.error('--with-python3 cannot be used when executing with ' 'Python 3') options.with_python3 = canonpath(options.with_python3) # Verify Python3 executable is acceptable. proc = subprocess.Popen([options.with_python3, b'--version'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out, _err = proc.communicate() ret = proc.wait() if ret != 0: parser.error('could not determine version of python 3') if not out.startswith('Python '): parser.error('unexpected output from python3 --version: %s' % out) vers = version.LooseVersion(out[len('Python '):]) if vers < version.LooseVersion('3.5.0'): parser.error('--with-python3 version must be 3.5.0 or greater; ' 'got %s' % out) if options.blacklist: options.blacklist = parselistfiles(options.blacklist, 'blacklist') if options.whitelist: options.whitelisted = parselistfiles(options.whitelist, 'whitelist') else: options.whitelisted = {} if options.showchannels: options.nodiff = True 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) _unified_diff = difflib.unified_diff if PYTHON3: import functools _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff) def getdiff(expected, output, ref, err): servefail = False lines = [] for line in _unified_diff(expected, output, ref, err): if line.startswith(b'+++') or line.startswith(b'---'): line = line.replace(b'\\', b'/') if line.endswith(b' \n'): line = line[:-2] + b'\n' lines.append(line) if not servefail and line.startswith( b'+ abort: child process failed to start'): servefail = True return servefail, lines verbose = False def vlog(*msg): """Log only when in verbose mode.""" if verbose is False: return return log(*msg) # Bytes that break XML even in a CDATA block: control characters 0-31 # sans \t, \n and \r CDATA_EVIL = re.compile(br"[\000-\010\013\014\016-\037]") # Match feature conditionalized output lines in the form, capturing the feature # list in group 2, and the preceeding line output in group 1: # # output..output (feature !)\n optline = re.compile(b'(.*) \((.+?) !\)\n$') def cdatasafe(data): """Make a string safe to include in a CDATA block. Certain control characters are illegal in a CDATA block, and there's no way to include a ]]> in a CDATA either. This function replaces illegal bytes with ? and adds a space between the ]] so that it won't break the CDATA block. """ return CDATA_EVIL.sub(b'?', data).replace(b']]>', b'] ]>') def log(*msg): """Log something to stdout. Arguments are strings to print. """ with iolock: if verbose: print(verbose, end=' ') for m in msg: print(m, end=' ') print() sys.stdout.flush() def highlightdiff(line, color): if not color: return line assert pygmentspresent return pygments.highlight(line.decode('latin1'), difflexer, terminal256formatter).encode('latin1') def highlightmsg(msg, color): if not color: return msg assert pygmentspresent return pygments.highlight(msg, runnerlexer, runnerformatter) def terminate(proc): """Terminate subprocess""" vlog('# Terminating process %d' % proc.pid) try: proc.terminate() except OSError: pass def killdaemons(pidfile): import killdaemons as killmod return killmod.killdaemons(pidfile, tryhard=False, remove=True, logfn=vlog) class Test(unittest.TestCase): """Encapsulates a single, runnable test. While this class conforms to the unittest.TestCase API, it differs in that instances need to be instantiated manually. (Typically, unittest.TestCase classes are instantiated automatically by scanning modules.) """ # Status code reserved for skipped tests (used by hghave). SKIPPED_STATUS = 80 def __init__(self, path, outputdir, tmpdir, keeptmpdir=False, debug=False, timeout=None, startport=None, extraconfigopts=None, py3kwarnings=False, shell=None, hgcommand=None, slowtimeout=None, usechg=False, useipv6=False): """Create a test from parameters. path is the full path to the file defining the test. tmpdir is the main temporary directory to use for this test. keeptmpdir determines whether to keep the test's temporary directory after execution. It defaults to removal (False). debug mode will make the test execute verbosely, with unfiltered output. timeout controls the maximum run time of the test. It is ignored when debug is True. See slowtimeout for tests with #require slow. slowtimeout overrides timeout if the test has #require slow. startport controls the starting port number to use for this test. Each test will reserve 3 port numbers for execution. It is the caller's responsibility to allocate a non-overlapping port range to Test instances. extraconfigopts is an iterable of extra hgrc config options. Values must have the form "key=value" (something understood by hgrc). Values of the form "foo.key=value" will result in "[foo] key=value". py3kwarnings enables Py3k warnings. shell is the shell to execute tests in. """ if timeout is None: timeout = defaults['timeout'] if startport is None: startport = defaults['port'] if slowtimeout is None: slowtimeout = defaults['slowtimeout'] self.path = path self.bname = os.path.basename(path) self.name = _strpath(self.bname) self._testdir = os.path.dirname(path) self._outputdir = outputdir self._tmpname = os.path.basename(path) self.errpath = os.path.join(self._outputdir, b'%s.err' % self.bname) self._threadtmp = tmpdir self._keeptmpdir = keeptmpdir self._debug = debug self._timeout = timeout self._slowtimeout = slowtimeout self._startport = startport self._extraconfigopts = extraconfigopts or [] self._py3kwarnings = py3kwarnings self._shell = _bytespath(shell) self._hgcommand = hgcommand or b'hg' self._usechg = usechg self._useipv6 = useipv6 self._aborted = False self._daemonpids = [] self._finished = None self._ret = None self._out = None self._skipped = None self._testtmp = None self._chgsockdir = None self._refout = self.readrefout() def readrefout(self): """read reference output""" # If we're not in --debug mode and reference output file exists, # check test output against it. if self._debug: return None # to match "out is None" elif os.path.exists(self.refpath): with open(self.refpath, 'rb') as f: return f.read().splitlines(True) else: return [] # needed to get base class __repr__ running @property def _testMethodName(self): return self.name def __str__(self): return self.name def shortDescription(self): return self.name def setUp(self): """Tasks to perform before run().""" self._finished = False self._ret = None self._out = None self._skipped = None try: os.mkdir(self._threadtmp) except OSError as e: if e.errno != errno.EEXIST: raise name = self._tmpname self._testtmp = os.path.join(self._threadtmp, name) os.mkdir(self._testtmp) # Remove any previous output files. if os.path.exists(self.errpath): try: os.remove(self.errpath) except OSError as e: # We might have raced another test to clean up a .err # file, so ignore ENOENT when removing a previous .err # file. if e.errno != errno.ENOENT: raise if self._usechg: self._chgsockdir = os.path.join(self._threadtmp, b'%s.chgsock' % name) os.mkdir(self._chgsockdir) def run(self, result): """Run this test and report results against a TestResult instance.""" # This function is extremely similar to unittest.TestCase.run(). Once # we require Python 2.7 (or at least its version of unittest), this # function can largely go away. self._result = result result.startTest(self) try: try: self.setUp() except (KeyboardInterrupt, SystemExit): self._aborted = True raise except Exception: result.addError(self, sys.exc_info()) return success = False try: self.runTest() except KeyboardInterrupt: self._aborted = True raise except unittest.SkipTest as e: result.addSkip(self, str(e)) # The base class will have already counted this as a # test we "ran", but we want to exclude skipped tests # from those we count towards those run. result.testsRun -= 1 except self.failureException as e: # This differs from unittest in that we don't capture # the stack trace. This is for historical reasons and # this decision could be revisited in the future, # especially for PythonTest instances. if result.addFailure(self, str(e)): success = True except Exception: result.addError(self, sys.exc_info()) else: success = True try: self.tearDown() except (KeyboardInterrupt, SystemExit): self._aborted = True raise except Exception: result.addError(self, sys.exc_info()) success = False if success: result.addSuccess(self) finally: result.stopTest(self, interrupted=self._aborted) def runTest(self): """Run this test instance. This will return a tuple describing the result of the test. """ env = self._getenv() self._genrestoreenv(env) self._daemonpids.append(env['DAEMON_PIDS']) self._createhgrc(env['HGRCPATH']) vlog('# Test', self.name) ret, out = self._run(env) self._finished = True self._ret = ret self._out = out def describe(ret): if ret < 0: return 'killed by signal: %d' % -ret return 'returned error code %d' % ret self._skipped = False if ret == self.SKIPPED_STATUS: if out is None: # Debug mode, nothing to parse. missing = ['unknown'] failed = None else: missing, failed = TTest.parsehghaveoutput(out) if not missing: missing = ['skipped'] if failed: self.fail('hg have failed checking for %s' % failed[-1]) else: self._skipped = True raise unittest.SkipTest(missing[-1]) elif ret == 'timeout': self.fail('timed out') elif ret is False: self.fail('no result code from test') elif out != self._refout: # Diff generation may rely on written .err file. if (ret != 0 or out != self._refout) and not self._skipped \ and not self._debug: f = open(self.errpath, 'wb') for line in out: f.write(line) f.close() # The result object handles diff calculation for us. if self._result.addOutputMismatch(self, ret, out, self._refout): # change was accepted, skip failing return if ret: msg = 'output changed and ' + describe(ret) else: msg = 'output changed' self.fail(msg) elif ret: self.fail(describe(ret)) def tearDown(self): """Tasks to perform after run().""" for entry in self._daemonpids: killdaemons(entry) self._daemonpids = [] if self._keeptmpdir: log('\nKeeping testtmp dir: %s\nKeeping threadtmp dir: %s' % (self._testtmp.decode('utf-8'), self._threadtmp.decode('utf-8'))) else: shutil.rmtree(self._testtmp, True) shutil.rmtree(self._threadtmp, True) if self._usechg: # chgservers will stop automatically after they find the socket # files are deleted shutil.rmtree(self._chgsockdir, True) if (self._ret != 0 or self._out != self._refout) and not self._skipped \ and not self._debug and self._out: f = open(self.errpath, 'wb') for line in self._out: f.write(line) f.close() vlog("# Ret was:", self._ret, '(%s)' % self.name) def _run(self, env): # This should be implemented in child classes to run tests. raise unittest.SkipTest('unknown test type') def abort(self): """Terminate execution of this test.""" self._aborted = True def _portmap(self, i): offset = b'' if i == 0 else b'%d' % i return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset) def _getreplacements(self): """Obtain a mapping of text replacements to apply to test output. Test output needs to be normalized so it can be compared to expected output. This function defines how some of that normalization will occur. """ r = [ # This list should be parallel to defineport in _getenv self._portmap(0), self._portmap(1), self._portmap(2), (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$', br'\1 (glob)'), (br'([^0-9])%s' % re.escape(self._localip()), br'\1$LOCALIP'), (br'\bHG_TXNID=TXN:[a-f0-9]{40}\b', br'HG_TXNID=TXN:$ID$'), ] r.append((self._escapepath(self._testtmp), b'$TESTTMP')) return r def _escapepath(self, p): if os.name == 'nt': return ( (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c for c in p)) ) else: return re.escape(p) def _localip(self): if self._useipv6: return b'::1' else: return b'127.0.0.1' def _genrestoreenv(self, testenv): """Generate a script that can be used by tests to restore the original environment.""" # Put the restoreenv script inside self._threadtmp scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh') testenv['HGTEST_RESTOREENV'] = scriptpath # Only restore environment variable names that the shell allows # us to export. name_regex = re.compile('^[a-zA-Z][a-zA-Z0-9_]*$') # Do not restore these variables; otherwise tests would fail. reqnames = {'PYTHON', 'TESTDIR', 'TESTTMP'} with open(scriptpath, 'w') as envf: for name, value in origenviron.items(): if not name_regex.match(name): # Skip environment variables with unusual names not # allowed by most shells. continue if name in reqnames: continue envf.write('%s=%s\n' % (name, shellquote(value))) for name in testenv: if name in origenviron or name in reqnames: continue envf.write('unset %s\n' % (name,)) def _getenv(self): """Obtain environment variables to use during test execution.""" def defineport(i): offset = '' if i == 0 else '%s' % i env["HGPORT%s" % offset] = '%s' % (self._startport + i) env = os.environ.copy() env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') env['HGEMITWARNINGS'] = '1' env['TESTTMP'] = self._testtmp env['HOME'] = self._testtmp # This number should match portneeded in _getport for port in xrange(3): # This list should be parallel to _portmap in _getreplacements defineport(port) env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc') env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'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" env['HGIPV6'] = str(int(self._useipv6)) # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw # IP addresses. env['LOCALIP'] = self._localip() # 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 ' + 'HGPLAIN HGPLAINEXCEPT EDITOR VISUAL PAGER ' + 'NO_PROXY CHGDEBUG').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] if self._usechg: env['CHGSOCKNAME'] = os.path.join(self._chgsockdir, b'server') return env def _createhgrc(self, path): """Create an hgrc file for this test.""" hgrc = open(path, 'wb') hgrc.write(b'[ui]\n') hgrc.write(b'slash = True\n') hgrc.write(b'interactive = False\n') hgrc.write(b'mergemarkers = detailed\n') hgrc.write(b'promptecho = True\n') hgrc.write(b'[defaults]\n') hgrc.write(b'[devel]\n') hgrc.write(b'all-warnings = true\n') hgrc.write(b'default-date = 0 0\n') hgrc.write(b'[largefiles]\n') hgrc.write(b'usercache = %s\n' % (os.path.join(self._testtmp, b'.cache/largefiles'))) hgrc.write(b'[web]\n') hgrc.write(b'address = localhost\n') hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii')) for opt in self._extraconfigopts: section, key = opt.split('.', 1) assert '=' in key, ('extra config opt %s must ' 'have an = for assignment' % opt) hgrc.write(b'[%s]\n%s\n' % (section, key)) hgrc.close() def fail(self, msg): # unittest differentiates between errored and failed. # Failed is denoted by AssertionError (by default at least). raise AssertionError(msg) def _runcommand(self, cmd, env, normalizenewlines=False): """Run command in a sub-process, capturing the output (stdout and stderr). Return a tuple (exitcode, output). output is None in debug mode. """ if self._debug: proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp, env=env) ret = proc.wait() return (ret, None) proc = Popen4(cmd, self._testtmp, self._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']) for s, r in self._getreplacements(): output = re.sub(s, r, output) if normalizenewlines: output = output.replace('\r\n', '\n') return ret, output.splitlines(True) class PythonTest(Test): """A Python-based test.""" @property def refpath(self): return os.path.join(self._testdir, b'%s.out' % self.bname) def _run(self, env): py3kswitch = self._py3kwarnings and b' -3' or b'' cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path) vlog("# Running", cmd) normalizenewlines = os.name == 'nt' result = self._runcommand(cmd, env, normalizenewlines=normalizenewlines) if self._aborted: raise KeyboardInterrupt() return result # Some glob patterns apply only in some circumstances, so the script # might want to remove (glob) annotations that otherwise should be # retained. checkcodeglobpats = [ # On Windows it looks like \ doesn't require a (glob), but we know # better. re.compile(br'^pushing to \$TESTTMP/.*[^)]$'), re.compile(br'^moving \S+/.*[^)]$'), re.compile(br'^pulling from \$TESTTMP/.*[^)]$'), # Not all platforms have 127.0.0.1 as loopback (though most do), # so we always glob that too. re.compile(br'.*\$LOCALIP.*$'), ] bchr = chr if PYTHON3: bchr = lambda x: bytes([x]) class TTest(Test): """A "t test" is a test backed by a .t file.""" SKIPPED_PREFIX = b'skipped: ' FAILED_PREFIX = b'hghave check failed: ' NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256)) ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'}) def __init__(self, path, *args, **kwds): # accept an extra "case" parameter case = None if 'case' in kwds: case = kwds.pop('case') self._case = case self._allcases = parsettestcases(path) super(TTest, self).__init__(path, *args, **kwds) if case: self.name = '%s (case %s)' % (self.name, _strpath(case)) self.errpath = b'%s.%s.err' % (self.errpath[:-4], case) self._tmpname += b'-%s' % case @property def refpath(self): return os.path.join(self._testdir, self.bname) def _run(self, env): f = open(self.path, 'rb') lines = f.readlines() f.close() # .t file is both reference output and the test input, keep reference # output updated with the the test input. This avoids some race # conditions where the reference output does not match the actual test. if self._refout is not None: self._refout = lines salt, script, after, expected = self._parsetest(lines) # Write out the generated script. fname = b'%s.sh' % self._testtmp f = open(fname, 'wb') for l in script: f.write(l) f.close() cmd = b'%s "%s"' % (self._shell, fname) vlog("# Running", cmd) exitcode, output = self._runcommand(cmd, env) if self._aborted: raise KeyboardInterrupt() # Do not merge output if skipped. Return hghave message instead. # Similarly, with --debug, output is None. if exitcode == self.SKIPPED_STATUS or output is None: return exitcode, output return self._processoutput(exitcode, output, salt, after, expected) def _hghave(self, reqs): # TODO do something smarter when all other uses of hghave are gone. runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__))) tdir = runtestdir.replace(b'\\', b'/') proc = Popen4(b'%s -c "%s/hghave %s"' % (self._shell, tdir, b' '.join(reqs)), self._testtmp, 0, self._getenv()) stdout, stderr = proc.communicate() ret = proc.wait() if wifexited(ret): ret = os.WEXITSTATUS(ret) if ret == 2: print(stdout.decode('utf-8')) sys.exit(1) if ret != 0: return False, stdout if b'slow' in reqs: self._timeout = self._slowtimeout return True, None def _iftest(self, args): # implements "#if" reqs = [] for arg in args: if arg.startswith(b'no-') and arg[3:] in self._allcases: if arg[3:] == self._case: return False elif arg in self._allcases: if arg != self._case: return False else: reqs.append(arg) return self._hghave(reqs)[0] def _parsetest(self, lines): # 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 = b"SALT%d" % time.time() def addsalt(line, inpython): if inpython: script.append(b'%s %d 0\n' % (salt, line)) else: script.append(b'echo %s %d $?\n' % (salt, line)) script = [] # 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 = {} # Expected shell script output. expected = {} pos = prepos = -1 # True or False when in a true or false conditional section skipping = None # We keep track of whether or not we're in a Python block so we # can generate the surrounding doctest magic. inpython = False if self._debug: script.append(b'set -x\n') if self._hgcommand != b'hg': script.append(b'alias hg="%s"\n' % self._hgcommand) if os.getenv('MSYSTEM'): script.append(b'alias pwd="pwd -W"\n') n = 0 for n, l in enumerate(lines): if not l.endswith(b'\n'): l += b'\n' if l.startswith(b'#require'): lsplit = l.split() if len(lsplit) < 2 or lsplit[0] != b'#require': after.setdefault(pos, []).append(' !!! invalid #require\n') haveresult, message = self._hghave(lsplit[1:]) if not haveresult: script = [b'echo "%s"\nexit 80\n' % message] break after.setdefault(pos, []).append(l) elif l.startswith(b'#if'): lsplit = l.split() if len(lsplit) < 2 or lsplit[0] != b'#if': after.setdefault(pos, []).append(' !!! invalid #if\n') if skipping is not None: after.setdefault(pos, []).append(' !!! nested #if\n') skipping = not self._iftest(lsplit[1:]) after.setdefault(pos, []).append(l) elif l.startswith(b'#else'): if skipping is None: after.setdefault(pos, []).append(' !!! missing #if\n') skipping = not skipping after.setdefault(pos, []).append(l) elif l.startswith(b'#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(b' >>> '): # 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(b'%s -m heredoctest < '): # continuations after.setdefault(prepos, []).append(l) script.append(l[4:]) elif l.startswith(b' '): # results # Queue up a list of expected results. expected.setdefault(pos, []).append(l[2:]) else: if inpython: script.append(b'EOF\n') inpython = False # Non-command/result. Queue up for merged output. after.setdefault(pos, []).append(l) if inpython: script.append(b'EOF\n') if skipping is not None: after.setdefault(pos, []).append(' !!! missing #endif\n') addsalt(n + 1, False) return salt, script, after, expected def _processoutput(self, exitcode, output, salt, after, expected): # Merge the script output back into a unified test. warnonly = 1 # 1: not yet; 2: yes; 3: for sure not if exitcode != 0: warnonly = 3 pos = -1 postout = [] for l in output: lout, lcmd = l, None if salt in l: lout, lcmd = l.split(salt, 1) while lout: if not lout.endswith(b'\n'): lout += b' (no-eol)\n' # Find the expected output at the current position. els = [None] if expected.get(pos, None): els = expected[pos] i = 0 optional = [] while i < len(els): el = els[i] r = self.linematch(el, lout) if isinstance(r, str): if r == '+glob': lout = el[:-1] + ' (glob)\n' r = '' # Warn only this line. elif r == '-glob': lout = ''.join(el.rsplit(' (glob)', 1)) r = '' # Warn only this line. elif r == "retry": postout.append(b' ' + el) els.pop(i) break else: log('\ninfo, unknown linematch result: %r\n' % r) r = False if r: els.pop(i) break if el: if el.endswith(b" (?)\n"): optional.append(i) else: m = optline.match(el) if m: conditions = [ c for c in m.group(2).split(b' ')] if not self._iftest(conditions): optional.append(i) i += 1 if r: if r == "retry": continue # clean up any optional leftovers for i in optional: postout.append(b' ' + els[i]) for i in reversed(optional): del els[i] postout.append(b' ' + el) else: if self.NEEDESCAPE(lout): lout = TTest._stringescape(b'%s (esc)\n' % lout.rstrip(b'\n')) postout.append(b' ' + lout) # Let diff deal with it. if r != '': # If line failed. warnonly = 3 # for sure not elif warnonly == 1: # Is "not yet" and line is warn only. warnonly = 2 # Yes do warn. break else: # clean up any optional leftovers while expected.get(pos, None): el = expected[pos].pop(0) if el: if not el.endswith(b" (?)\n"): m = optline.match(el) if m: conditions = [c for c in m.group(2).split(b' ')] if self._iftest(conditions): # Don't append as optional line continue else: continue postout.append(b' ' + el) if lcmd: # Add on last return code. ret = int(lcmd.split()[1]) if ret != 0: postout.append(b' [%d]\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 == 2: exitcode = False # Set exitcode to warned. return exitcode, postout @staticmethod 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 + br'\r?\n\Z', l) return re.match(el + br'\n\Z', l) except re.error: # el is an invalid regex return False @staticmethod def globmatch(el, l): # The only supported special characters are * and ? plus / which also # matches \ on windows. Escaping of these characters is supported. if el + b'\n' == l: if os.altsep: # matching on "/" is not needed for this line for pat in checkcodeglobpats: if pat.match(el): return True return b'-glob' return True el = el.replace(b'$LOCALIP', b'*') i, n = 0, len(el) res = b'' while i < n: c = el[i:i + 1] i += 1 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/': res += el[i - 1:i + 1] i += 1 elif c == b'*': res += b'.*' elif c == b'?': res += b'.' elif c == b'/' and os.altsep: res += b'[/\\\\]' else: res += re.escape(c) return TTest.rematch(res, l) def linematch(self, el, l): retry = False if el == l: # perfect match (fast) return True if el: if el.endswith(b" (?)\n"): retry = "retry" el = el[:-5] + b"\n" else: m = optline.match(el) if m: conditions = [c for c in m.group(2).split(b' ')] el = m.group(1) + b"\n" if not self._iftest(conditions): retry = "retry" # Not required by listed features if el.endswith(b" (esc)\n"): if PYTHON3: el = el[:-7].decode('unicode_escape') + '\n' el = el.encode('utf-8') else: el = el[:-7].decode('string-escape') + '\n' if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l: return True if el.endswith(b" (re)\n"): return TTest.rematch(el[:-6], l) or retry if el.endswith(b" (glob)\n"): # ignore '(glob)' added to l by 'replacements' if l.endswith(b" (glob)\n"): l = l[:-8] + b"\n" return TTest.globmatch(el[:-8], l) or retry if os.altsep and l.replace(b'\\', b'/') == el: return b'+glob' return retry @staticmethod 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(TTest.SKIPPED_PREFIX): line = line.splitlines()[0] missing.append(line[len(TTest.SKIPPED_PREFIX):].decode('utf-8')) elif line.startswith(TTest.FAILED_PREFIX): line = line.splitlines()[0] failed.append(line[len(TTest.FAILED_PREFIX):].decode('utf-8')) return missing, failed @staticmethod def _escapef(m): return TTest.ESCAPEMAP[m.group(0)] @staticmethod def _stringescape(s): return TTest.ESCAPESUB(TTest._escapef, s) iolock = threading.RLock() class TestResult(unittest._TextTestResult): """Holds results when executing via unittest.""" # Don't worry too much about accessing the non-public _TextTestResult. # It is relatively common in Python testing tools. def __init__(self, options, *args, **kwargs): super(TestResult, self).__init__(*args, **kwargs) self._options = options # unittest.TestResult didn't have skipped until 2.7. We need to # polyfill it. self.skipped = [] # We have a custom "ignored" result that isn't present in any Python # unittest implementation. It is very similar to skipped. It may make # sense to map it into skip some day. self.ignored = [] self.times = [] self._firststarttime = None # Data stored for the benefit of generating xunit reports. self.successes = [] self.faildata = {} if options.color == 'auto': self.color = pygmentspresent and self.stream.isatty() elif options.color == 'never': self.color = False else: # 'always', for testing purposes self.color = pygmentspresent def addFailure(self, test, reason): self.failures.append((test, reason)) if self._options.first: self.stop() else: with iolock: if reason == "timed out": self.stream.write('t') else: if not self._options.nodiff: self.stream.write('\n') # Exclude the '\n' from highlighting to lex correctly formatted = 'ERROR: %s output changed\n' % test self.stream.write(highlightmsg(formatted, self.color)) self.stream.write('!') self.stream.flush() def addSuccess(self, test): with iolock: super(TestResult, self).addSuccess(test) self.successes.append(test) def addError(self, test, err): super(TestResult, self).addError(test, err) if self._options.first: self.stop() # Polyfill. def addSkip(self, test, reason): self.skipped.append((test, reason)) with iolock: if self.showAll: self.stream.writeln('skipped %s' % reason) else: self.stream.write('s') self.stream.flush() def addIgnore(self, test, reason): self.ignored.append((test, reason)) with iolock: if self.showAll: self.stream.writeln('ignored %s' % reason) else: if reason not in ('not retesting', "doesn't match keyword"): self.stream.write('i') else: self.testsRun += 1 self.stream.flush() def addOutputMismatch(self, test, ret, got, expected): """Record a mismatch in test output for a particular test.""" if self.shouldStop: # don't print, some other test case already failed and # printed, we're just stale and probably failed due to our # temp dir getting cleaned up. return accepted = False lines = [] with iolock: if self._options.nodiff: pass elif self._options.view: v = self._options.view if PYTHON3: v = _bytespath(v) os.system(b"%s %s %s" % (v, test.refpath, test.errpath)) else: servefail, lines = getdiff(expected, got, test.refpath, test.errpath) if servefail: raise test.failureException( 'server failed to start (HGPORT=%s)' % test._startport) else: self.stream.write('\n') for line in lines: line = highlightdiff(line, self.color) if PYTHON3: self.stream.flush() self.stream.buffer.write(line) self.stream.buffer.flush() else: self.stream.write(line) self.stream.flush() # handle interactive prompt without releasing iolock if self._options.interactive: if test.readrefout() != expected: self.stream.write( 'Reference output has changed (run again to prompt ' 'changes)') else: self.stream.write('Accept this change? [n] ') answer = sys.stdin.readline().strip() if answer.lower() in ('y', 'yes'): if test.path.endswith(b'.t'): rename(test.errpath, test.path) else: rename(test.errpath, '%s.out' % test.path) accepted = True if not accepted: self.faildata[test.name] = b''.join(lines) return accepted def startTest(self, test): super(TestResult, self).startTest(test) # os.times module computes the user time and system time spent by # child's processes along with real elapsed time taken by a process. # This module has one limitation. It can only work for Linux user # and not for Windows. test.started = os.times() if self._firststarttime is None: # thread racy but irrelevant self._firststarttime = test.started[4] def stopTest(self, test, interrupted=False): super(TestResult, self).stopTest(test) test.stopped = os.times() starttime = test.started endtime = test.stopped origin = self._firststarttime self.times.append((test.name, endtime[2] - starttime[2], # user space CPU time endtime[3] - starttime[3], # sys space CPU time endtime[4] - starttime[4], # real time starttime[4] - origin, # start date in run context endtime[4] - origin, # end date in run context )) if interrupted: with iolock: self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % ( test.name, self.times[-1][3])) class TestSuite(unittest.TestSuite): """Custom unittest TestSuite that knows how to execute Mercurial tests.""" def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None, retest=False, keywords=None, loop=False, runs_per_test=1, loadtest=None, showchannels=False, *args, **kwargs): """Create a new instance that can run tests with a configuration. testdir specifies the directory where tests are executed from. This is typically the ``tests`` directory from Mercurial's source repository. jobs specifies the number of jobs to run concurrently. Each test executes on its own thread. Tests actually spawn new processes, so state mutation should not be an issue. If there is only one job, it will use the main thread. whitelist and blacklist denote tests that have been whitelisted and blacklisted, respectively. These arguments don't belong in TestSuite. Instead, whitelist and blacklist should be handled by the thing that populates the TestSuite with tests. They are present to preserve backwards compatible behavior which reports skipped tests as part of the results. retest denotes whether to retest failed tests. This arguably belongs outside of TestSuite. keywords denotes key words that will be used to filter which tests to execute. This arguably belongs outside of TestSuite. loop denotes whether to loop over tests forever. """ super(TestSuite, self).__init__(*args, **kwargs) self._jobs = jobs self._whitelist = whitelist self._blacklist = blacklist self._retest = retest self._keywords = keywords self._loop = loop self._runs_per_test = runs_per_test self._loadtest = loadtest self._showchannels = showchannels def run(self, result): # We have a number of filters that need to be applied. We do this # here instead of inside Test because it makes the running logic for # Test simpler. tests = [] num_tests = [0] for test in self._tests: def get(): num_tests[0] += 1 if getattr(test, 'should_reload', False): return self._loadtest(test, num_tests[0]) return test if not os.path.exists(test.path): result.addSkip(test, "Doesn't exist") continue if not (self._whitelist and test.bname in self._whitelist): if self._blacklist and test.bname in self._blacklist: result.addSkip(test, 'blacklisted') continue if self._retest and not os.path.exists(test.errpath): result.addIgnore(test, 'not retesting') continue if self._keywords: f = open(test.path, 'rb') t = f.read().lower() + test.bname.lower() f.close() ignored = False for k in self._keywords.lower().split(): if k not in t: result.addIgnore(test, "doesn't match keyword") ignored = True break if ignored: continue for _ in xrange(self._runs_per_test): tests.append(get()) runtests = list(tests) done = queue.Queue() running = 0 channels = [""] * self._jobs def job(test, result): for n, v in enumerate(channels): if not v: channel = n break else: raise ValueError('Could not find output channel') channels[channel] = "=" + test.name[5:].split(".")[0] try: test(result) done.put(None) except KeyboardInterrupt: pass except: # re-raises done.put(('!', test, 'run-test raised an error, see traceback')) raise finally: try: channels[channel] = '' except IndexError: pass def stat(): count = 0 while channels: d = '\n%03s ' % count for n, v in enumerate(channels): if v: d += v[0] channels[n] = v[1:] or '.' else: d += ' ' d += ' ' with iolock: sys.stdout.write(d + ' ') sys.stdout.flush() for x in xrange(10): if channels: time.sleep(.1) count += 1 stoppedearly = False if self._showchannels: statthread = threading.Thread(target=stat, name="stat") statthread.start() try: while tests or running: if not done.empty() or running == self._jobs or not tests: try: done.get(True, 1) running -= 1 if result and result.shouldStop: stoppedearly = True break except queue.Empty: continue if tests and not running == self._jobs: test = tests.pop(0) if self._loop: if getattr(test, 'should_reload', False): num_tests[0] += 1 tests.append( self._loadtest(test, num_tests[0])) else: tests.append(test) if self._jobs == 1: job(test, result) else: t = threading.Thread(target=job, name=test.name, args=(test, result)) t.start() running += 1 # If we stop early we still need to wait on started tests to # finish. Otherwise, there is a race between the test completing # and the test's cleanup code running. This could result in the # test reporting incorrect. if stoppedearly: while running: try: done.get(True, 1) running -= 1 except queue.Empty: continue except KeyboardInterrupt: for test in runtests: test.abort() channels = [] return result # Save the most recent 5 wall-clock runtimes of each test to a # human-readable text file named .testtimes. Tests are sorted # alphabetically, while times for each test are listed from oldest to # newest. def loadtimes(outputdir): times = [] try: with open(os.path.join(outputdir, b'.testtimes-')) as fp: for line in fp: ts = line.split() times.append((ts[0], [float(t) for t in ts[1:]])) except IOError as err: if err.errno != errno.ENOENT: raise return times def savetimes(outputdir, result): saved = dict(loadtimes(outputdir)) maxruns = 5 skipped = set([str(t[0]) for t in result.skipped]) for tdata in result.times: test, real = tdata[0], tdata[3] if test not in skipped: ts = saved.setdefault(test, []) ts.append(real) ts[:] = ts[-maxruns:] fd, tmpname = tempfile.mkstemp(prefix=b'.testtimes', dir=outputdir, text=True) with os.fdopen(fd, 'w') as fp: for name, ts in sorted(saved.items()): fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts]))) timepath = os.path.join(outputdir, b'.testtimes') try: os.unlink(timepath) except OSError: pass try: os.rename(tmpname, timepath) except OSError: pass class TextTestRunner(unittest.TextTestRunner): """Custom unittest test runner that uses appropriate settings.""" def __init__(self, runner, *args, **kwargs): super(TextTestRunner, self).__init__(*args, **kwargs) self._runner = runner def listtests(self, test): result = TestResult(self._runner.options, self.stream, self.descriptions, 0) test = sorted(test, key=lambda t: t.name) for t in test: print(t.name) result.addSuccess(t) if self._runner.options.xunit: with open(self._runner.options.xunit, "wb") as xuf: self._writexunit(result, xuf) if self._runner.options.json: jsonpath = os.path.join(self._runner._outputdir, b'report.json') with open(jsonpath, 'w') as fp: self._writejson(result, fp) return result def run(self, test): result = TestResult(self._runner.options, self.stream, self.descriptions, self.verbosity) test(result) failed = len(result.failures) skipped = len(result.skipped) ignored = len(result.ignored) with iolock: self.stream.writeln('') if not self._runner.options.noskips: for test, msg in result.skipped: formatted = 'Skipped %s: %s\n' % (test.name, msg) self.stream.write(highlightmsg(formatted, result.color)) for test, msg in result.failures: formatted = 'Failed %s: %s\n' % (test.name, msg) self.stream.write(highlightmsg(formatted, result.color)) for test, msg in result.errors: self.stream.writeln('Errored %s: %s' % (test.name, msg)) if self._runner.options.xunit: with open(self._runner.options.xunit, "wb") as xuf: self._writexunit(result, xuf) if self._runner.options.json: jsonpath = os.path.join(self._runner._outputdir, b'report.json') with open(jsonpath, 'w') as fp: self._writejson(result, fp) self._runner._checkhglib('Tested') savetimes(self._runner._outputdir, result) if failed and self._runner.options.known_good_rev: self._bisecttests(t for t, m in result.failures) self.stream.writeln( '# Ran %d tests, %d skipped, %d failed.' % (result.testsRun, skipped + ignored, failed)) if failed: self.stream.writeln('python hash seed: %s' % os.environ['PYTHONHASHSEED']) if self._runner.options.time: self.printtimes(result.times) self.stream.flush() return result def _bisecttests(self, tests): bisectcmd = ['hg', 'bisect'] bisectrepo = self._runner.options.bisect_repo if bisectrepo: bisectcmd.extend(['-R', os.path.abspath(bisectrepo)]) def pread(args): env = os.environ.copy() env['HGPLAIN'] = '1' p = subprocess.Popen(args, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=env) data = p.stdout.read() p.wait() return data for test in tests: pread(bisectcmd + ['--reset']), pread(bisectcmd + ['--bad', '.']) pread(bisectcmd + ['--good', self._runner.options.known_good_rev]) # TODO: we probably need to forward more options # that alter hg's behavior inside the tests. opts = '' withhg = self._runner.options.with_hg if withhg: opts += ' --with-hg=%s ' % shellquote(_strpath(withhg)) rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts, test) data = pread(bisectcmd + ['--command', rtc]) m = re.search( (br'\nThe first (?Pbad|good) revision ' br'is:\nchangeset: +\d+:(?P[a-f0-9]+)\n.*\n' br'summary: +(?P[^\n]+)\n'), data, (re.MULTILINE | re.DOTALL)) if m is None: self.stream.writeln( 'Failed to identify failure point for %s' % test) continue dat = m.groupdict() verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed' self.stream.writeln( '%s %s by %s (%s)' % ( test, verb, dat['node'], dat['summary'])) def printtimes(self, times): # iolock held by run self.stream.writeln('# Producing time report') times.sort(key=lambda t: (t[3])) cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s' self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' % ('start', 'end', 'cuser', 'csys', 'real', 'Test')) for tdata in times: test = tdata[0] cuser, csys, real, start, end = tdata[1:6] self.stream.writeln(cols % (start, end, cuser, csys, real, test)) @staticmethod def _writexunit(result, outf): # See http://llg.cubic.org/docs/junit/ for a reference. timesd = dict((t[0], t[3]) for t in result.times) doc = minidom.Document() s = doc.createElement('testsuite') s.setAttribute('name', 'run-tests') s.setAttribute('tests', str(result.testsRun)) s.setAttribute('errors', "0") # TODO s.setAttribute('failures', str(len(result.failures))) s.setAttribute('skipped', str(len(result.skipped) + len(result.ignored))) doc.appendChild(s) for tc in result.successes: t = doc.createElement('testcase') t.setAttribute('name', tc.name) tctime = timesd.get(tc.name) if tctime is not None: t.setAttribute('time', '%.3f' % tctime) s.appendChild(t) for tc, err in sorted(result.faildata.items()): t = doc.createElement('testcase') t.setAttribute('name', tc) tctime = timesd.get(tc) if tctime is not None: t.setAttribute('time', '%.3f' % tctime) # createCDATASection expects a unicode or it will # convert using default conversion rules, which will # fail if string isn't ASCII. err = cdatasafe(err).decode('utf-8', 'replace') cd = doc.createCDATASection(err) # Use 'failure' here instead of 'error' to match errors = 0, # failures = len(result.failures) in the testsuite element. failelem = doc.createElement('failure') failelem.setAttribute('message', 'output changed') failelem.setAttribute('type', 'output-mismatch') failelem.appendChild(cd) t.appendChild(failelem) s.appendChild(t) for tc, message in result.skipped: # According to the schema, 'skipped' has no attributes. So store # the skip message as a text node instead. t = doc.createElement('testcase') t.setAttribute('name', tc.name) binmessage = message.encode('utf-8') message = cdatasafe(binmessage).decode('utf-8', 'replace') cd = doc.createCDATASection(message) skipelem = doc.createElement('skipped') skipelem.appendChild(cd) t.appendChild(skipelem) s.appendChild(t) outf.write(doc.toprettyxml(indent=' ', encoding='utf-8')) @staticmethod def _writejson(result, outf): timesd = {} for tdata in result.times: test = tdata[0] timesd[test] = tdata[1:] outcome = {} groups = [('success', ((tc, None) for tc in result.successes)), ('failure', result.failures), ('skip', result.skipped)] for res, testcases in groups: for tc, __ in testcases: if tc.name in timesd: diff = result.faildata.get(tc.name, b'') try: diff = diff.decode('unicode_escape') except UnicodeDecodeError as e: diff = '%r decoding diff, sorry' % e tres = {'result': res, 'time': ('%0.3f' % timesd[tc.name][2]), 'cuser': ('%0.3f' % timesd[tc.name][0]), 'csys': ('%0.3f' % timesd[tc.name][1]), 'start': ('%0.3f' % timesd[tc.name][3]), 'end': ('%0.3f' % timesd[tc.name][4]), 'diff': diff, } else: # blacklisted test tres = {'result': res} outcome[tc.name] = tres jsonout = json.dumps(outcome, sort_keys=True, indent=4, separators=(',', ': ')) outf.writelines(("testreport =", jsonout)) class TestRunner(object): """Holds context for executing tests. Tests rely on a lot of state. This object holds it for them. """ # Programs required to run tests. REQUIREDTOOLS = [ b'diff', b'grep', b'unzip', b'gunzip', b'bunzip2', b'sed', ] # Maps file extensions to test class. TESTTYPES = [ (b'.py', PythonTest), (b'.t', TTest), ] def __init__(self): self.options = None self._hgroot = None self._testdir = None self._outputdir = None self._hgtmp = None self._installdir = None self._bindir = None self._tmpbinddir = None self._pythondir = None self._coveragefile = None self._createdfiles = [] self._hgcommand = None self._hgpath = None self._portoffset = 0 self._ports = {} def run(self, args, parser=None): """Run the test suite.""" oldmask = os.umask(0o22) try: parser = parser or getparser() options, args = parseargs(args, parser) # positional arguments are paths to test files to run, so # we make sure they're all bytestrings args = [_bytespath(a) for a in args] if options.test_list is not None: for listfile in options.test_list: with open(listfile, 'rb') as f: args.extend(t for t in f.read().splitlines() if t) self.options = options self._checktools() testdescs = self.findtests(args) if options.profile_runner: import statprof statprof.start() result = self._run(testdescs) if options.profile_runner: statprof.stop() statprof.display() return result finally: os.umask(oldmask) def _run(self, testdescs): if self.options.random: random.shuffle(testdescs) else: # keywords for slow tests slow = {b'svn': 10, b'cvs': 10, b'hghave': 10, b'largefiles-update': 10, b'run-tests': 10, b'corruption': 10, b'race': 10, b'i18n': 10, b'check': 100, b'gendoc': 100, b'contrib-perf': 200, } perf = {} def sortkey(f): # run largest tests first, as they tend to take the longest f = f['path'] try: return perf[f] except KeyError: try: val = -os.stat(f).st_size except OSError as e: if e.errno != errno.ENOENT: raise perf[f] = -1e9 # file does not exist, tell early return -1e9 for kw, mul in slow.items(): if kw in f: val *= mul if f.endswith(b'.py'): val /= 10.0 perf[f] = val / 1000.0 return perf[f] testdescs.sort(key=sortkey) self._testdir = osenvironb[b'TESTDIR'] = getattr( os, 'getcwdb', os.getcwd)() if self.options.outputdir: self._outputdir = canonpath(_bytespath(self.options.outputdir)) else: self._outputdir = self._testdir 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)) if self.options.tmpdir: self.options.keep_tmpdir = True tmpdir = _bytespath(self.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. print("error: temp dir %r already exists" % tmpdir) return 1 # 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 = osenvironb.get(b'TMP', None) tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d) self._hgtmp = osenvironb[b'HGTMP'] = ( os.path.realpath(tmpdir)) if self.options.with_hg: self._installdir = None whg = self.options.with_hg self._bindir = os.path.dirname(os.path.realpath(whg)) assert isinstance(self._bindir, bytes) self._hgcommand = os.path.basename(whg) self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') os.makedirs(self._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. self._pythondir = self._bindir else: self._installdir = os.path.join(self._hgtmp, b"install") self._bindir = os.path.join(self._installdir, b"bin") self._hgcommand = b'hg' self._tmpbindir = self._bindir self._pythondir = os.path.join(self._installdir, b"lib", b"python") # set CHGHG, then replace "hg" command by "chg" chgbindir = self._bindir if self.options.chg or self.options.with_chg: osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand) else: osenvironb.pop(b'CHGHG', None) # drop flag for hghave if self.options.chg: self._hgcommand = b'chg' elif self.options.with_chg: chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg)) self._hgcommand = os.path.basename(self.options.with_chg) osenvironb[b"BINDIR"] = self._bindir osenvironb[b"PYTHON"] = PYTHON if self.options.with_python3: osenvironb[b'PYTHON3'] = self.options.with_python3 fileb = _bytespath(__file__) runtestdir = os.path.abspath(os.path.dirname(fileb)) osenvironb[b'RUNTESTDIR'] = runtestdir if PYTHON3: sepb = _bytespath(os.pathsep) else: sepb = os.pathsep path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb) if os.path.islink(__file__): # test helper will likely be at the end of the symlink realfile = os.path.realpath(fileb) realdir = os.path.abspath(os.path.dirname(realfile)) path.insert(2, realdir) if chgbindir != self._bindir: path.insert(1, chgbindir) if self._testdir != runtestdir: path = [self._testdir] + path if self._tmpbindir != self._bindir: path = [self._tmpbindir] + path osenvironb[b"PATH"] = sepb.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 = [self._pythondir, self._testdir, runtestdir] # 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 = osenvironb.get(IMPL_PATH) if oldpypath: pypath.append(oldpypath) osenvironb[IMPL_PATH] = sepb.join(pypath) if self.options.pure: os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure" os.environ["HGMODULEPOLICY"] = "py" if self.options.allow_slow_tests: os.environ["HGTEST_SLOW"] = "slow" elif 'HGTEST_SLOW' in os.environ: del os.environ['HGTEST_SLOW'] self._coveragefile = os.path.join(self._testdir, b'.coverage') vlog("# Using TESTDIR", self._testdir) vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR']) vlog("# Using HGTMP", self._hgtmp) vlog("# Using PATH", os.environ["PATH"]) vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH]) vlog("# Writing to directory", self._outputdir) try: return self._runtests(testdescs) or 0 finally: time.sleep(.1) self._cleanup() def findtests(self, args): """Finds possible test files from arguments. If you wish to inject custom tests into the test harness, this would be a good function to monkeypatch or override in a derived class. """ if not args: if self.options.changed: proc = Popen4('hg st --rev "%s" -man0 .' % self.options.changed, None, 0) stdout, stderr = proc.communicate() args = stdout.strip(b'\0').split(b'\0') else: args = os.listdir(b'.') tests = [] for t in args: if not (os.path.basename(t).startswith(b'test-') and (t.endswith(b'.py') or t.endswith(b'.t'))): continue if t.endswith(b'.t'): # .t file may contain multiple test cases cases = sorted(parsettestcases(t)) if cases: tests += [{'path': t, 'case': c} for c in sorted(cases)] else: tests.append({'path': t}) else: tests.append({'path': t}) return tests def _runtests(self, testdescs): def _reloadtest(test, i): # convert a test back to its description dict desc = {'path': test.path} case = getattr(test, '_case', None) if case: desc['case'] = case return self._gettest(desc, i) try: if self.options.restart: orig = list(testdescs) while testdescs: desc = testdescs[0] # desc['path'] is a relative path if 'case' in desc: errpath = b'%s.%s.err' % (desc['path'], desc['case']) else: errpath = b'%s.err' % desc['path'] errpath = os.path.join(self._outputdir, errpath) if os.path.exists(errpath): break testdescs.pop(0) if not testdescs: print("running all tests") testdescs = orig tests = [self._gettest(d, i) for i, d in enumerate(testdescs)] failed = False kws = self.options.keywords if kws is not None and PYTHON3: kws = kws.encode('utf-8') suite = TestSuite(self._testdir, jobs=self.options.jobs, whitelist=self.options.whitelisted, blacklist=self.options.blacklist, retest=self.options.retest, keywords=kws, loop=self.options.loop, runs_per_test=self.options.runs_per_test, showchannels=self.options.showchannels, tests=tests, loadtest=_reloadtest) verbosity = 1 if self.options.verbose: verbosity = 2 runner = TextTestRunner(self, verbosity=verbosity) if self.options.list_tests: result = runner.listtests(suite) else: if self._installdir: self._installhg() self._checkhglib("Testing") else: self._usecorrectpython() if self.options.chg: assert self._installdir self._installchg() result = runner.run(suite) if result.failures: failed = True if self.options.anycoverage: self._outputcoverage() except KeyboardInterrupt: failed = True print("\ninterrupted!") if failed: return 1 def _getport(self, count): port = self._ports.get(count) # do we have a cached entry? if port is None: portneeded = 3 # above 100 tries we just give up and let test reports failure for tries in xrange(100): allfree = True port = self.options.port + self._portoffset for idx in xrange(portneeded): if not checkportisavailable(port + idx): allfree = False break self._portoffset += portneeded if allfree: break self._ports[count] = port return port def _gettest(self, testdesc, count): """Obtain a Test by looking at its filename. Returns a Test instance. The Test may not be runnable if it doesn't map to a known type. """ path = testdesc['path'] lctest = path.lower() testcls = Test for ext, cls in self.TESTTYPES: if lctest.endswith(ext): testcls = cls break refpath = os.path.join(self._testdir, path) tmpdir = os.path.join(self._hgtmp, b'child%d' % count) # extra keyword parameters. 'case' is used by .t tests kwds = dict((k, testdesc[k]) for k in ['case'] if k in testdesc) t = testcls(refpath, self._outputdir, tmpdir, keeptmpdir=self.options.keep_tmpdir, debug=self.options.debug, timeout=self.options.timeout, startport=self._getport(count), extraconfigopts=self.options.extra_config_opt, py3kwarnings=self.options.py3k_warnings, shell=self.options.shell, hgcommand=self._hgcommand, usechg=bool(self.options.with_chg or self.options.chg), useipv6=useipv6, **kwds) t.should_reload = True return t def _cleanup(self): """Clean up state from this test invocation.""" if self.options.keep_tmpdir: return vlog("# Cleaning up HGTMP", self._hgtmp) shutil.rmtree(self._hgtmp, True) for f in self._createdfiles: try: os.remove(f) except OSError: pass def _usecorrectpython(self): """Configure the environment to use the appropriate Python in tests.""" # Tests must use the same interpreter as us or bad things will happen. pyexename = sys.platform == 'win32' and b'python.exe' or b'python' if getattr(os, 'symlink', None): vlog("# Making python executable in test path a symlink to '%s'" % sys.executable) mypython = os.path.join(self._tmpbindir, pyexename) try: if os.readlink(mypython) == sys.executable: return os.unlink(mypython) except OSError as err: if err.errno != errno.ENOENT: raise if self._findprogram(pyexename) != sys.executable: try: os.symlink(sys.executable, mypython) self._createdfiles.append(mypython) except OSError as 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 self._findprogram(pyexename): print("WARNING: Cannot find %s in search path" % pyexename) def _installhg(self): """Install hg into the test environment. This will also configure hg with the appropriate testing settings. """ vlog("# Performing temporary installation of HG") installerrs = os.path.join(self._hgtmp, b"install.err") compiler = '' if self.options.compiler: compiler = '--compiler ' + self.options.compiler if self.options.pure: pure = b"--pure" else: pure = b"" # Run installer in hg root script = os.path.realpath(sys.argv[0]) exe = sys.executable if PYTHON3: compiler = _bytespath(compiler) script = _bytespath(script) exe = _bytespath(exe) hgroot = os.path.dirname(os.path.dirname(script)) self._hgroot = hgroot os.chdir(hgroot) nohome = b'--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 = b'' cmd = (b'%(exe)s setup.py %(pure)s clean --all' b' build %(compiler)s --build-base="%(base)s"' b' install --force --prefix="%(prefix)s"' b' --install-lib="%(libdir)s"' b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1' % {b'exe': exe, b'pure': pure, b'compiler': compiler, b'base': os.path.join(self._hgtmp, b"build"), b'prefix': self._installdir, b'libdir': self._pythondir, b'bindir': self._bindir, b'nohome': nohome, b'logfile': installerrs}) # setuptools requires install directories to exist. def makedirs(p): try: os.makedirs(p) except OSError as e: if e.errno != errno.EEXIST: raise makedirs(self._pythondir) makedirs(self._bindir) vlog("# Running", cmd) if os.system(cmd) == 0: if not self.options.verbose: try: os.remove(installerrs) except OSError as e: if e.errno != errno.ENOENT: raise else: f = open(installerrs, 'rb') for line in f: if PYTHON3: sys.stdout.buffer.write(line) else: sys.stdout.write(line) f.close() sys.exit(1) os.chdir(self._testdir) self._usecorrectpython() if self.options.py3k_warnings and not self.options.anycoverage: vlog("# Updating hg command to enable Py3k Warnings switch") f = open(os.path.join(self._bindir, 'hg'), 'rb') lines = [line.rstrip() for line in f] lines[0] += ' -3' f.close() f = open(os.path.join(self._bindir, 'hg'), 'wb') for line in lines: f.write(line + '\n') f.close() hgbat = os.path.join(self._bindir, b'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 b'"%~dp0..\python" "%~dp0hg" %*' in data: data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*', b'"%~dp0python" "%~dp0hg" %*') f = open(hgbat, 'wb') f.write(data) f.close() else: print('WARNING: cannot fix hg.bat reference to python.exe') if self.options.anycoverage: custom = os.path.join(self._testdir, 'sitecustomize.py') target = os.path.join(self._pythondir, 'sitecustomize.py') vlog('# Installing coverage trigger to %s' % target) shutil.copyfile(custom, target) rc = os.path.join(self._testdir, '.coveragerc') vlog('# Installing coverage rc to %s' % rc) os.environ['COVERAGE_PROCESS_START'] = rc covdir = os.path.join(self._installdir, '..', 'coverage') try: os.mkdir(covdir) except OSError as e: if e.errno != errno.EEXIST: raise os.environ['COVERAGE_DIR'] = covdir def _checkhglib(self, verb): """Ensure that the 'mercurial' package imported by python is the one we expect it to be. If not, print a warning to stderr.""" if ((self._bindir == self._pythondir) and (self._bindir != self._tmpbindir)): # The pythondir has been inferred from --with-hg flag. # We cannot expect anything sensible here. return expecthg = os.path.join(self._pythondir, b'mercurial') actualhg = self._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)) def _gethgpath(self): """Return the path to the mercurial package that is actually found by the current Python interpreter.""" if self._hgpath is not None: return self._hgpath cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"' cmd = cmd % PYTHON if PYTHON3: cmd = _strpath(cmd) pipe = os.popen(cmd) try: self._hgpath = _bytespath(pipe.read().strip()) finally: pipe.close() return self._hgpath def _installchg(self): """Install chg into the test environment""" vlog('# Performing temporary installation of CHG') assert os.path.dirname(self._bindir) == self._installdir assert self._hgroot, 'must be called after _installhg()' cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"' % {b'make': 'make', # TODO: switch by option or environment? b'prefix': self._installdir}) cwd = os.path.join(self._hgroot, b'contrib', b'chg') vlog("# Running", cmd) proc = subprocess.Popen(cmd, shell=True, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) out, _err = proc.communicate() if proc.returncode != 0: if PYTHON3: sys.stdout.buffer.write(out) else: sys.stdout.write(out) sys.exit(1) def _outputcoverage(self): """Produce code coverage output.""" import coverage coverage = coverage.coverage vlog('# Producing coverage report') # chdir is the easiest way to get short, relative paths in the # output. os.chdir(self._hgroot) covdir = os.path.join(self._installdir, '..', 'coverage') cov = coverage(data_file=os.path.join(covdir, 'cov')) # Map install directory paths back to source directory. cov.config.paths['srcdir'] = ['.', self._pythondir] cov.combine() omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]] cov.report(ignore_errors=True, omit=omit) if self.options.htmlcov: htmldir = os.path.join(self._outputdir, 'htmlcov') cov.html_report(directory=htmldir, omit=omit) if self.options.annotate: adir = os.path.join(self._outputdir, 'annotated') if not os.path.isdir(adir): os.mkdir(adir) cov.annotate(directory=adir, omit=omit) def _findprogram(self, program): """Search PATH for a executable program""" dpb = _bytespath(os.defpath) sepb = _bytespath(os.pathsep) for p in osenvironb.get(b'PATH', dpb).split(sepb): name = os.path.join(p, program) if os.name == 'nt' or os.access(name, os.X_OK): return name return None def _checktools(self): """Ensure tools required to run tests are present.""" for p in self.REQUIREDTOOLS: if os.name == 'nt' and not p.endswith('.exe'): p += '.exe' found = self._findprogram(p) if found: vlog("# Found prerequisite", p, "at", found) else: print("WARNING: Did not find prerequisite tool: %s " % p.decode("utf-8")) if __name__ == '__main__': runner = TestRunner() try: import msvcrt msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) except ImportError: pass sys.exit(runner.run(sys.argv[1:])) hg-git-0.8.11/tests/test-bookmark-workflow.t000066400000000000000000000154151324473741300207570ustar00rootroot00000000000000This 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 --config hggit.usephases=True $ 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: [] Make sure that master is public $ hg phase -r master 3: public $ 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 .. hg-git-0.8.11/tests/test-branch-bookmark-suffix.t000066400000000000000000000101741324473741300216410ustar00rootroot00000000000000bail 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' Your branch is up to date with 'origin/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'?. (re) $ 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 .. hg-git-0.8.11/tests/test-clone.t000066400000000000000000000061231324473741300163760ustar00rootroot00000000000000Load 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 Make sure this is still draft since we didn't pull remote's HEAD $ hg -R hgrepo-a phase -r alpha 0: draft 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 test shared repositories $ hg clone gitrepo hgrepo-base importing git objects into hg updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg --config extensions.share= share hgrepo-base hgrepo-shared updating working directory 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgrepo-shared pull gitrepo pulling from gitrepo no changes found $ hg -R hgrepo-shared push gitrepo pushing to gitrepo searching for changes no changes found [1] $ ls hgrepo-shared/.hg | grep git [1] $ rm -rf hgrepo-base hgrepo-shared 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 hg-git-0.8.11/tests/test-conflict-1.t000066400000000000000000000036761324473741300172470ustar00rootroot00000000000000Load 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 *' to abandon (glob) 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 hg-git-0.8.11/tests/test-conflict-2.t000066400000000000000000000036771324473741300172510ustar00rootroot00000000000000Load 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 *' to abandon (glob) 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 hg-git-0.8.11/tests/test-convergedmerge.t000066400000000000000000000037261324473741300203000ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-doctest.py000066400000000000000000000031161324473741300171270ustar00rootroot00000000000000# this is hack to make sure no escape characters are inserted into the output from __future__ import absolute_import import doctest import os import re import sys ispy3 = (sys.version_info[0] >= 3) # add hggit/ to sys.path sys.path.insert(0, os.path.join(os.environ["TESTDIR"], "..")) if 'TERM' in os.environ: del os.environ['TERM'] class py3docchecker(doctest.OutputChecker): def check_output(self, want, got, optionflags): want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want) # py2: u'' got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got) # py3: b'' # py3: : b'' -> : # : -> : got2 = re.sub(r'''^hggit\.\w+\.(\w+): (['"])(.*?)\2''', r'\1: \3', got2, re.MULTILINE) got2 = re.sub(r'^hggit\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE) return any(doctest.OutputChecker.check_output(self, w, g, optionflags) for w, g in [(want, got), (want2, got2)]) def testmod(name, optionflags=0, testtarget=None): __import__(name) mod = sys.modules[name] if testtarget is not None: mod = getattr(mod, testtarget) # minimal copy of doctest.testmod() finder = doctest.DocTestFinder() checker = None if ispy3: checker = py3docchecker() runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags) for test in finder.find(mod, name): runner.run(test) runner.summarize() testmod('hggit.compat') testmod('hggit.hg2git') testmod('hggit.util') testmod('hggit.git_handler') hg-git-0.8.11/tests/test-empty-working-tree.t000066400000000000000000000020271324473741300210460ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-encoding.t000066400000000000000000000105151324473741300170640ustar00rootroot00000000000000# -*- 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:0000000000000000000000000000000000000000 | 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à hg-git-0.8.11/tests/test-extra.t000066400000000000000000000137121324473741300164230ustar00rootroot00000000000000Test 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' 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 --config "experimental.graphstyle.missing=|" @ 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 hg-git-0.8.11/tests/test-file-removal.t000066400000000000000000000171501324473741300176620ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-git-clone.t000066400000000000000000000037221324473741300171610ustar00rootroot00000000000000Load 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 test for ssh vulnerability $ cat >> $HGRCPATH << EOF > [ui] > ssh = ssh -o ConnectTimeout=1 > EOF $ hg clone 'git+ssh://-oProxyCommand=rm${IFS}nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm${IFS}nonexistent' [255] $ hg clone 'git+ssh://%2DoProxyCommand=rm${IFS}nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm${IFS}nonexistent' [255] $ hg clone 'git+ssh://fakehost|rm${IFS}nonexistent/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7crm%24%7bifs%7dnonexistent* (glob) abort: git remote error: The remote server unexpectedly closed the connection. $ hg clone 'git+ssh://fakehost%7Crm${IFS}nonexistent/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7crm%24%7bifs%7dnonexistent* (glob) abort: git remote error: The remote server unexpectedly closed the connection. hg-git-0.8.11/tests/test-git-submodules.t000066400000000000000000000330141324473741300202400ustar00rootroot00000000000000Load 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 subrepo2 = [git]../gitsubrepo alpha = [git]../gitsubrepo foolink = [git]../gitsubrepo $ hg cat -r 7 .hgsubstate 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 hg-git-0.8.11/tests/test-git-tags.t000066400000000000000000000024001324473741300170070ustar00rootroot00000000000000Load 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 .. hg-git-0.8.11/tests/test-git-workflow.t000066400000000000000000000077461324473741300177450ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-gitignore.t000066400000000000000000000052441324473741300172700ustar00rootroot00000000000000 $ python -c 'from mercurial.dirstate import rootcache' || exit 80 Load commonly used test logic $ . "$TESTDIR/testutil" $ hg init We should only read .gitignore files in a hg-git repo (i.e. one with .hg/git directory) otherwise, a rogue .gitignore could slow down a hg-only repo $ mkdir .hg/git $ 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 show pattern error in hgignore file as expected (issue197) ---------------------------------------------------------- $ cat > $TESTTMP/invalidhgignore < # invalid syntax in regexp > foo( > EOF $ hg status --config ui.ignore=$TESTTMP/invalidhgignore abort: $TESTTMP/invalidhgignore: invalid pattern (relre): foo( [255] $ cat > .hgignore < # invalid syntax in regexp > foo( > EOF $ hg status abort: $TESTTMP/.hgignore: invalid pattern (relre): foo( [255] hg-git-0.8.11/tests/test-help.t000066400000000000000000000007271324473741300162320ustar00rootroot00000000000000Tests 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 hg-git-0.8.11/tests/test-hg-author.t000066400000000000000000000161171324473741300172000ustar00rootroot00000000000000Load 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 book master $ 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 hg-git-0.8.11/tests/test-hg-branch.t000066400000000000000000000050531324473741300171300ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-hg-tags-invalid.t000066400000000000000000000064641324473741300202640ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-hg-tags.t000066400000000000000000000042531324473741300166320ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-http.t000066400000000000000000000023041324473741300162520ustar00rootroot00000000000000#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 (glob) [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 new changesets 8b6053c928fe (?) updating to branch default 4 files updated, 0 files merged, 0 files removed, 0 files unresolved hg-git-0.8.11/tests/test-illegal-contents.t000066400000000000000000000065451324473741300205520ustar00rootroot00000000000000Check 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 .. Now check a Git repository containing a Mercurial repository, which you can't check out. $ rm -rf hg git nested $ git init -q git $ hg init nested $ mv nested git $ cd git $ git add nested $ fn_git_commit -m 'add a Mercurial repository' $ cd .. $ hg clone git hg importing git objects into hg abort: Refusing to import problematic path 'nested/.hg/00changelog.i' (Mercurial cannot check out paths inside nested repositories; if you need to continue, then set '[git] blockdothg = false' in your hgrc.) [255] $ hg clone --config git.blockdothg=false git hg importing git objects into hg warning: path 'nested/.hg/00changelog.i' is within a nested repository, which Mercurial cannot check out. warning: path 'nested/.hg/requires' is within a nested repository, which Mercurial cannot check out. updating to branch default abort: path 'nested/.hg/00changelog.i' is inside nested repo 'nested' [255] $ cd .. hg-git-0.8.11/tests/test-incoming.t000066400000000000000000000073251324473741300171060ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-keywords.t000066400000000000000000000033041324473741300171430ustar00rootroot00000000000000Load 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' This commit is called gamma10 so that its hash will have the same initial digit as commit alpha. This lets us test ambiguous abbreviated identifiers. $ echo gamma10 > gamma10 $ git add gamma10 $ fn_git_commit -m 'add gamma10' $ cd .. $ 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 $ echo gamma > gamma $ hg add gamma $ hg commit -m 'add gamma' $ hg log --template "{rev} {node} {node|short} {gitnode} {gitnode|short}\n" 3 965bf7d08d3ac847dd8eb9e72ee0bf547d1a65d9 965bf7d08d3a 2 8e3f0ecc9aefd4ea2fdf8e2d5299cac548762a1c 8e3f0ecc9aef 7e2a5465ff4e3b992c429bb87a392620a0ac97b7 7e2a5465ff4e 1 7fe02317c63d9ee324d4b5df7c9296085162da1b 7fe02317c63d 9497a4ee62e16ee641860d7677cdb2589ea15554 9497a4ee62e1 0 ff7a2f2d8d7099694ae1e8b03838d40575bebb63 ff7a2f2d8d70 7eeab2ea75ec1ac0ff3d500b5b6f8a3447dd7c03 7eeab2ea75ec $ hg log --template "fromgit {rev}\n" --rev "fromgit()" fromgit 0 fromgit 1 fromgit 2 $ hg log --template "gitnode_existsA {rev}\n" --rev "gitnode(9497a4ee62e16ee641860d7677cdb2589ea15554)" gitnode_existsA 1 $ hg log --template "gitnode_existsB {rev}\n" --rev "gitnode(7eeab)" gitnode_existsB 0 $ hg log --rev "gitnode(7e)" abort: git-mapfile@7e: ambiguous identifier! [255] $ hg log --template "gitnode_notexists {rev}\n" --rev "gitnode(1234567890ab)" hg-git-0.8.11/tests/test-merge.t000066400000000000000000000045571324473741300164060ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-octopus.t000066400000000000000000000114621324473741300167740ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-outgoing.t000066400000000000000000000070041324473741300171300ustar00rootroot00000000000000Load 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 .. hg-git-0.8.11/tests/test-pull-after-strip.t000066400000000000000000000055441324473741300205160ustar00rootroot00000000000000Load 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 .. hg-git-0.8.11/tests/test-pull.t000066400000000000000000000261301324473741300162520ustar00rootroot00000000000000Load 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 test for ssh vulnerability $ cat >> $HGRCPATH << EOF > [ui] > ssh = ssh -o ConnectTimeout=1 > EOF $ hg init a $ cd a $ hg pull 'git+ssh://-oProxyCommand=rm${IFS}nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm${IFS}nonexistent' [255] $ hg pull 'git+ssh://-oProxyCommand=rm%20nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm nonexistent' [255] $ hg pull 'git+ssh://fakehost|shellcommand/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7cshellcommand* (glob) abort: git remote error: The remote server unexpectedly closed the connection. $ hg pull 'git+ssh://fakehost%7Cshellcommand/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7cshellcommand* (glob) abort: git remote error: The remote server unexpectedly closed the connection. hg-git-0.8.11/tests/test-push-r.t000066400000000000000000000103041324473741300165100ustar00rootroot00000000000000Load 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) new changesets 095197eb4973:a6a34bfa0076 (?) (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 hg-git-0.8.11/tests/test-push.t000066400000000000000000000141141324473741300162540ustar00rootroot00000000000000Load 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 $ hg bookmark -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 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 test for ssh vulnerability $ cat >> $HGRCPATH << EOF > [ui] > ssh = ssh -o ConnectTimeout=1 > EOF $ hg push 'git+ssh://-oProxyCommand=rm${IFS}nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm${IFS}nonexistent' [255] $ hg push 'git+ssh://-oProxyCommand=rm%20nonexistent/path' 2>&1 >/dev/null abort: potentially unsafe hostname: '-oProxyCommand=rm nonexistent' [255] $ hg push 'git+ssh://fakehost|rm%20nonexistent/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7crm%20nonexistent* (glob) abort: git remote error: The remote server unexpectedly closed the connection. $ hg push 'git+ssh://fakehost%7Crm%20nonexistent/path' 2>&1 >/dev/null | grep -v ^devel-warn: ssh: * fakehost%7crm%20nonexistent* (glob) abort: git remote error: The remote server unexpectedly closed the connection. hg-git-0.8.11/tests/test-renames.t000066400000000000000000000354261324473741300167400ustar00rootroot00000000000000Test 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 Previous versions of git did not produce any output but 2.14 changed the output to warn the user about submodules $ git add .gitmodules gitsubmodule2 2>/dev/null $ 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 book master -q $ 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 book master -qf $ 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 Test findcopiesharder $ cd $TESTTMP $ git init -q gitcopyharder $ cd gitcopyharder $ cat >> file0 << EOF > 1 > 2 > 3 > 4 > 5 > EOF $ git add file0 $ fn_git_commit -m file0 $ cp file0 file1 $ git add file1 $ fn_git_commit -m file1 $ cp file0 file2 $ echo 6 >> file2 $ git add file2 $ fn_git_commit -m file2 $ cd .. Clone without findcopiesharder does not find copies from unmodified files $ hg clone gitcopyharder hgnocopyharder importing git objects into hg updating to branch default 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgnocopyharder export 1::2 # HG changeset patch # User test # Date 1167609621 0 # Mon Jan 01 00:00:21 2007 +0000 # Node ID 555831c93e2a250e5ba42efad45bf7ba71da13e4 # Parent b45d023c6842337ffe694663a44aa672d311081c file1 diff --git a/file1 b/file1 new file mode 100644 --- /dev/null +++ b/file1 @@ -0,0 +1,5 @@ +1 +2 +3 +4 +5 # HG changeset patch # User test # Date 1167609622 0 # Mon Jan 01 00:00:22 2007 +0000 # Node ID ec77ccdbefe023eb9898b0399f84f670c8c0f5fc # Parent 555831c93e2a250e5ba42efad45bf7ba71da13e4 file2 diff --git a/file2 b/file2 new file mode 100644 --- /dev/null +++ b/file2 @@ -0,0 +1,6 @@ +1 +2 +3 +4 +5 +6 findcopiesharder finds copies from unmodified files if similarity is met $ hg --config git.findcopiesharder=true clone gitcopyharder hgcopyharder0 importing git objects into hg updating to branch default 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgcopyharder0 export 1::2 # HG changeset patch # User test # Date 1167609621 0 # Mon Jan 01 00:00:21 2007 +0000 # Node ID cd05a87103eed9d270fc05b62b00f48e174ab960 # Parent b45d023c6842337ffe694663a44aa672d311081c file1 diff --git a/file0 b/file1 copy from file0 copy to file1 # HG changeset patch # User test # Date 1167609622 0 # Mon Jan 01 00:00:22 2007 +0000 # Node ID 9b30998342729c7357d418bebed7399986cfe643 # Parent cd05a87103eed9d270fc05b62b00f48e174ab960 file2 diff --git a/file0 b/file2 copy from file0 copy to file2 --- a/file0 +++ b/file2 @@ -3,3 +3,4 @@ 3 4 5 +6 $ hg --config git.findcopiesharder=true --config git.similarity=95 clone gitcopyharder hgcopyharder1 importing git objects into hg updating to branch default 3 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg -R hgcopyharder1 export 1::2 # HG changeset patch # User test # Date 1167609621 0 # Mon Jan 01 00:00:21 2007 +0000 # Node ID cd05a87103eed9d270fc05b62b00f48e174ab960 # Parent b45d023c6842337ffe694663a44aa672d311081c file1 diff --git a/file0 b/file1 copy from file0 copy to file1 # HG changeset patch # User test # Date 1167609622 0 # Mon Jan 01 00:00:22 2007 +0000 # Node ID d9d2e8cbf050772be31dccf78851f71dc547d139 # Parent cd05a87103eed9d270fc05b62b00f48e174ab960 file2 diff --git a/file2 b/file2 new file mode 100644 --- /dev/null +++ b/file2 @@ -0,0 +1,6 @@ +1 +2 +3 +4 +5 +6 Config values out of range $ hg --config git.similarity=999 clone gitcopyharder hgcopyharder2 importing git objects into hg abort: git.similarity must be between 0 and 100 [255] $ hg --config git.renamelimit=-5 clone gitcopyharder hgcopyharder2 importing git objects into hg abort: git.renamelimit must be non-negative [255] hg-git-0.8.11/tests/test-subrepos.t000066400000000000000000000102221324473741300171330ustar00rootroot00000000000000Load 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'... (glob) done. $ fn_git_commit -m 'add subrepo1' $ git submodule add ../gitsubrepo xyz/subrepo2 Cloning into '*xyz/subrepo2'... (glob) 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 hg-git-0.8.11/tests/test-timezone.t000066400000000000000000000015231324473741300171270ustar00rootroot00000000000000This 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 hg-git-0.8.11/tests/test-tree-decomposition.t000066400000000000000000000027621324473741300211140ustar00rootroot00000000000000Load 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 hg-git-0.8.11/tests/test-url-parsing.py000077500000000000000000000102111324473741300177220ustar00rootroot00000000000000import 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() hg-git-0.8.11/tests/test-url-parsing.py.out000066400000000000000000000015571324473741300205420ustar00rootroot00000000000000% 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 hg-git-0.8.11/tests/test-verify-fail.t000066400000000000000000000044771324473741300175250ustar00rootroot00000000000000Other 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] hg-git-0.8.11/tests/testutil000077500000000000000000000045451324473741300157450ustar00rootroot00000000000000#!/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 # Defaults for testing against hg < 4.3 echo '[defaults]' >> $HGRCPATH echo 'backout = -d "0 0"' >> $HGRCPATH echo 'commit = -d "0 0"' >> $HGRCPATH echo 'shelve = --date "0 0"\n' >> $HGRCPATH echo 'tag = -d "0 0"\n' >> $HGRCPATH # Enable git subrepository for hg >= 4.4.1 echo '[subrepos]' >> $HGRCPATH echo 'git:allowed = yes' >> $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` }