├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── MANIFEST.in
├── README.rst
├── git-remote-testgitifyhg
├── gitifyhg
├── __init__.py
├── apiwrapper.py
├── gitexporter.py
├── gitifyhg.py
├── hgimporter.py
└── util.py
├── setup.py
└── test
├── Makefile
├── aggregate-results.sh
├── sharness.sh
├── test-lib.sh
├── test_anonymous_branches.t
├── test_author.t
├── test_bookmarks.t
├── test_clone.t
├── test_clone_file_operations.t
├── test_notes.t
├── test_pull.t
├── test_push.t
├── test_push_tags.t
├── test_spaces.t
└── test_special_cases.t
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | build
3 | dist
4 | *.pyc
5 | .tox
6 | gitifyhg.egg-info
7 | pytestdebug.log
8 | test/trash directory*
9 | test/test-results
10 | test/.prove
11 | venv/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 |
5 | # install recent git version, and make sure the git config
6 | # contains suitable default
7 | before_install:
8 | - sudo apt-get update -qq
9 | - sudo add-apt-repository -y ppa:pdoes/ppa
10 | - sudo apt-get install -qq git
11 | - git config --global user.email you@example.com
12 | - git config --global user.name Gitifyhg
13 | - git config --global push.default simple
14 |
15 | # install dependencies and gitifyhg itself;
16 | # py.test is already preinstalled
17 | install:
18 | - "pip install -q path.py>=2.5"
19 | - "pip install -q https://www.mercurial-scm.org/release/mercurial-$HG_VERSION.tar.gz"
20 | - "python setup.py -q install"
21 |
22 | # specify various mercurial versions to test against
23 | env:
24 | - HG_VERSION=2.5
25 | - HG_VERSION=2.6
26 | - HG_VERSION=2.8
27 | - HG_VERSION=2.9
28 | - HG_VERSION=3.0
29 | - HG_VERSION=3.1
30 | - HG_VERSION=3.2
31 | - HG_VERSION=3.3
32 | - HG_VERSION=3.4
33 | - HG_VERSION=3.5
34 | - HG_VERSION=3.6
35 | - HG_VERSION=3.7
36 | - HG_VERSION=3.8
37 | - HG_VERSION=3.9
38 | - HG_VERSION=4.0
39 | - HG_VERSION=4.0.1
40 |
41 |
42 |
43 | # command to run actual tests
44 | # We also output the git and hg versions to make sure we are using
45 | # the right ones.
46 | # Also, switch the traceback format to 'short' to work around
47 | # a bug in py.test <= 2.3.4.
48 | script:
49 | - git --version
50 | - hg --version
51 | - cd test
52 | - make
53 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.txt
2 | include git-remote-testgitifyhg
3 | recursive-include test Makefile *.sh *.t
4 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ..
2 | Copyright 2012-2013 Dusty Phillips
3 |
4 | This file is part of gitifyhg.
5 | gitifyhg is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | gitifyhg is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU General Public License for more details.
14 |
15 | You should have received a copy of the GNU General Public License
16 | along with gitifyhg. If not, see .
17 |
18 |
19 | gitifyhg
20 | ========
21 | This git remote allows you to do local development in a git repository and push
22 | changes to an upstream mercurial repository. It does this seamlessly and allows
23 | pushing and pulling to named branches in the upstream repository.
24 |
25 | ``gitifyhg`` does not rely on hg-git, and allows you to push and pull to and from
26 | a mercurial repository from right inside git. You do not need to adapt your
27 | git workflow in any way aside from cloning a gitifyhg url.
28 |
29 | This is a robust and usable git to hg bridge that has been tested in production settings. It has a large test suite and better documentation than
30 | the `alternatives we know about `_.
31 | It has been tested on several large mercurial repositories (including that
32 | of mercurial itself and the pypy repository) that break with various other
33 | git-to-hg bridge projects and is used daily in normal workflow scenarios.
34 |
35 | That said, gitifyhg is not yet complete. Some of the features that
36 | are not fully working include:
37 |
38 | * anonymous branches are dropped, only the tip of a named branch iqs kept
39 | * remote branch and bookmark tracking is not 100% stable
40 | * pushing octopus merges is not supported
41 | * cloning mercurial branches that are subdirectories of other branches fails
42 | * cloning duplicate case sensitive names on case insensitive filesystems (mac, windows) fails
43 |
44 | However, if you are looking for a git-svn type of workflow that allows you to
45 | clone mercurial repositories, work in local git branches, rebase those
46 | branches and push them back to mercurial, you have found it. It works. Try it.
47 |
48 | Unfortunately, though, maintenance of gitifyhg has fallen off. You can pick it up if you like, but you may also be interested in working on the `git-remote-hg` script that is shipping with the main git project. We're told it's more functional than it once was and that the improvements gitifyhg once had over it have been addressed. We have not confirmed this, but it's worth checking out. Because it has support from the git developers, it's probably the best project to get behind at this time.
49 |
50 | URLS
51 | ----
52 | * `source `_
53 | * `issues `_
54 | * `pypi package `_
55 | * `Dusty Phillips `_
56 | * `Inspired by Felipe Contreras
57 | `_
58 |
59 | Dependencies
60 | ------------
61 | ``gitifyhg`` has been tested to run on CPython 2.7. Any python that
62 | supports Mercurial should be supported. Sadly, this excludes both pypy and
63 | CPython 3.
64 |
65 | ``gitifyhg`` requires at least Mercurial 2.5, older versions are currently
66 | not supported. We perform continuous testing against various Mercurial
67 | versions ranging from 2.5 to 4.0.1. However, this does not completely rule
68 | out the possibility of compatibility issues, so we recommend using Mercurial
69 | 3.9.x or 4.0.x, as this is what ``gitifyhg`` is primarily developed for.
70 | Should you actually encounter any compatibility issues with any older or
71 | newer Mercurial versions, please submit an issue.
72 |
73 | It has been tested on Arch Linux and Mac OS X. In general it should
74 | work equally well on other Unix-like operating systems like *BSD or Solaris.
75 | All bets are off with Windows, but please let us know if it works or you fixed
76 | it.
77 |
78 | ``gitifyhg`` explicitly depends on:
79 |
80 | * `path.py `_
81 | * `Mercurial `_
82 |
83 | These packages will be installed automatically by ``easy_install``,
84 | ``pip``, ``setup.py install``, or ``setup.py develop``.
85 |
86 | ``gitifyhg`` also expects the following to be installed on your OS:
87 |
88 | * `python2 `_
89 | * `git `_
90 |
91 | Install
92 | -------
93 | ``gitifyhg`` is a properly designed Python package. You can get it from
94 | `pypi `_ using either ::
95 |
96 | pip install gitifyhg
97 |
98 | or ::
99 |
100 | easy_install gitifyhg
101 |
102 | ``gitifyhg`` works in a `virtualenv `_, but you're
103 | probably just as well off to install it at the system level.
104 |
105 | You can also install ``gitifyhg`` manually with ::
106 |
107 | git clone https://github.com/buchuki/gitifyhg.git
108 | python setup.py install
109 |
110 | If you want to hack on it, use ``setup.py develop``, instead. In this case, you
111 | probably **are** better off using a ``virtualenv``.
112 |
113 | Instructions
114 | ------------
115 | ``gitifyhg`` is a git remote. Once installed, you can clone any Mercurial repo
116 | using ::
117 |
118 | git clone gitifyhg::
119 |
120 | Now run ``git branch -r`` to see the list of Mercurial branches. If it was
121 | a named branch upstream, it will be named branches/ in git.
122 | Bookmarks are referred to directly by their name.
123 | For now, we recommend only interacting with named branches.
124 |
125 | ``master`` automatically tracks the default branch. You can check out any
126 | named mercurial branch using ::
127 |
128 | git checkout --track origin/branches/
129 |
130 | As a standard git practice, we recommend creating your own local branch
131 | to work on. Then change to the tracked branch and ``git pull`` to get
132 | upstream changes. Rebase your working branch onto that branch before pushing ::
133 |
134 | git checkout -b working_
135 | # hack add commit ad nauseam
136 | git checkout branches/
137 | git pull
138 | git checkout working_
139 | git rebase branches/
140 | git checkout branches/
141 | git merge working_
142 | git push
143 |
144 | You can create new named upstream branches by giving them the ``branches/``
145 | prefix ::
146 |
147 | git checkout -b "branches/my_new_branch"
148 | # hack add commit
149 | git push --set-upstream origin branches/my_new_branch
150 |
151 | And that's really it, you just use standard git commands and the remote
152 | takes care of the details. Just be cautious of incoming anonymous branches,
153 | don't do any octopus merges and you should be set.
154 |
155 | Caveats
156 | ~~~~~~~
157 | Mercurial allows spaces in branch, bookmark, and tag names, while
158 | git does not. To keep git from choking if upstream has spaces in names, gitifyhg
159 | will replace them with three underscores and has the sense to convert between
160 | the two formats when pushing and pulling.
161 |
162 | Mercurial does not support lightweight tags. Tags in mercurial that get pushed
163 | to the remote repo require an extra commit in he mercurial history. If you push
164 | a lightweight tag, then gitifyhg will set a default user, date, and commit
165 | message for you. However, if you create a heavyweight tag using
166 | ``git tag --message="commit message"``, gitifyhg will use the commit
167 | information associated with that tag when you run ``git push --tags``.
168 |
169 | By default, gitifyhg ignores branches that have been closed in Mercurial. This
170 | supplies a substantial cloning speedup on large repos, and alleviates a few
171 | issues we are still working out in conflicting branch names. If you would like
172 | to clone a repository including closed branches, first set the
173 | GITIFYHG_ALLOW_CLOSED_BRANCHES environment variable.
174 |
175 | If you have any trouble, please let us know via the issue tracker, preferably
176 | with pull requests containing test cases.
177 |
178 | Communicating with Mercurial Users
179 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
180 | One problem with using git to access Mercurial repos is that the sha identifiers
181 | in the two DVCSs are different. This makes it difficult to discuss or share
182 | patches on mailing lists or other mediums.
183 |
184 | Gitifyhg alleviates this by storing Mercurial's sha1 identifiers in a git-notes
185 | ref. If you need to discuss SHA1s with upstream Mercurial users, issue
186 | the following commands::
187 |
188 | $ ls .git/refs/notes/
189 | hg hg-ceda6818a39a022ef11ba5ee2d7964f57cb3accf
190 | # note the SHA1 above and adapt the following command
191 | git symbolic-ref refs/notes/hg refs/notes/hg-ceda6818a39a022ef11ba5ee2d7964f57cb3accf
192 | git config core.notesRef refs/notes/hg
193 |
194 | From now on, your git-log output will include lines that look like the
195 | following for each pulled ref::
196 |
197 | Notes (hg):
198 | e6eabc9d7e24f55e829d0848380f6645e57f4b6a
199 |
200 | That is the Mercurial SHA1 identifier of the commit in question; you can paste
201 | that into an e-mail or chat message to discuss a specific commit with other
202 | users.
203 |
204 | If somebody else mentions a commit by it's hg SHA1 identifier, you can search
205 | for that commit in git using::
206 |
207 | git log --grep=
208 |
209 | Development
210 | -----------
211 | You can hack on gitifyhg by forking the
212 | `github `_ repository. All the code is
213 | in the ``gitifyhg.py`` file, and tests are in the ``test`` directory.
214 |
215 | We recommend developing in a `virtualenv `_ ::
216 |
217 | cd gitifyhg
218 | virtualenv -p python2.7 venv
219 | . venv/bin/activate
220 | python setup.py develop
221 |
222 | There is currently a problem where if you have a development version of gitifyhg
223 | in an active virtualenv and a stable version installed at the system level, git
224 | will pick the system level gitifyhg regardless of the PATH setting in the
225 | virtualenv. The only workaround I have found is to temporarily uninstall the
226 | system version.
227 |
228 | If you want debugging information out of gitifyhg, set the DEBUG_GITIFYHG=on
229 | environment variable. This is done automatically if you are running the test
230 | suite.
231 |
232 | The gitifyhg remote is called by git and commands are passed on stdin.
233 | Output is sent to stdout. The protocol is described at
234 | https://www.kernel.org/pub/software/scm/git/docs/git-remote-helpers.html
235 | The git remote prints INPUT and OUTPUT lines for each of these to help
236 | introspect the protocol.
237 |
238 | We expect pep8 compliance on contributions. If possible, enable highlighting
239 | of pep8 violations in your editor before committing.
240 |
241 | The gitifyhg mailing list is hosted on
242 | `Google groups `_, but we
243 | prefer the `issue tracker `_
244 | for most development and decision-making related discussions.
245 |
246 | Testing
247 | =======
248 |
249 | Tests are continuously run by Travis-CI: |BuildStatus|_
250 |
251 | .. |BuildStatus| image:: https://secure.travis-ci.org/buchuki/gitifyhg.png
252 | .. _BuildStatus: http://travis-ci.org/buchuki/gitifyhg
253 |
254 | Note that testing has recently changed. We used to use `py.test `_
255 | and `tox `_ to run our tests. We've recently switched to
256 | `sharness `_ both because it's easier to
257 | test command-line tools with and because it is the same infrastructure used by
258 | git itself.
259 |
260 | To test with sharness, simply `cd test` and run `make`. You can run individual
261 | test files with `./test-name.t`.
262 |
263 | License
264 | -------
265 |
266 | gitifyhg is copyright 2012-2013 Dusty Phillips and is licensed under the
267 | `GNU General Public License `_
268 |
269 | Credits
270 | -------
271 | Dusty Phillips is the primary author of ``gitifyhg``.
272 |
273 | The current version was heavily inspired by and borrows code from Felipe Contreras's
274 | `git-remote-hg `_
275 | project.
276 |
277 | Other contributors include (alphabetical order):
278 |
279 | * Alex Sydell
280 | * Jason Chu
281 | * Jed Brown
282 | * Max Horn
283 | * Paul Price
284 |
--------------------------------------------------------------------------------
/git-remote-testgitifyhg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # This bin script is intended only for testing.
4 | # It will not be used by the installed egg.
5 | #
6 | # For testing, just put the path to this bin script ahead of all
7 | # others, e.g.:
8 | # export PATH=/path/to/gitifyhg/bin:$PATH
9 |
10 | import os
11 | import sys
12 |
13 | # Set up sys.path so our package is before all others
14 | # (including eggs of ourselves that have been installed).
15 | bindir = os.path.dirname(__file__)
16 | sys.path.insert(1, os.path.abspath(os.path.join(bindir, "..")))
17 |
18 | from gitifyhg import gitifyhg
19 | gitifyhg.main()
20 |
--------------------------------------------------------------------------------
/gitifyhg/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dusty-phillips/gitifyhg/58767ad31d03e3cbb26db25f268b05e95fa84e21/gitifyhg/__init__.py
--------------------------------------------------------------------------------
/gitifyhg/apiwrapper.py:
--------------------------------------------------------------------------------
1 | from mercurial.context import memctx, memfilectx
2 | from mercurial.util import version as hg_version
3 | from distutils.version import StrictVersion
4 | from mercurial import extensions
5 |
6 | # Conditional imports depending on Mercurial version
7 | if hg_version() >= '3.7':
8 | from mercurial.bookmarks import _readactive
9 | elif hg_version() >= '3.5' and hg_version() < '3.7':
10 | from mercurial.bookmarks import readactive
11 | else:
12 | from mercurial.bookmarks import readcurrent
13 |
14 | if hg_version() >= '3.2':
15 | from mercurial.util import digester
16 | else:
17 | from mercurial.util import sha1
18 |
19 | if hg_version() >= '3.0':
20 | from mercurial import exchange
21 | else:
22 | from mercurial.localrepo import localrepository as exchange
23 |
24 | # Functions wrapping the Mercurial API. They follow the naming convention of
25 | # hg_[function name]
26 |
27 | def hg_pull(repo, peer, heads=None, force=False):
28 | return exchange.pull(repo, peer, heads=heads, force=force)
29 |
30 | def hg_push(repo, peer, force=False, newbranch=None):
31 | return exchange.push(repo, peer, force=force, newbranch=newbranch)
32 |
33 | def hg_readactive(repo):
34 | if hg_version() >= '3.7':
35 | return _readactive(repo,repo._bookmarks)
36 | elif hg_version() >= '3.5' and hg_version() < '3.7':
37 | return readactive(repo)
38 | else:
39 | return readcurrent(repo)
40 |
41 | def hg_sha1(url):
42 | encoded = url.encode('utf-8')
43 |
44 | if hg_version() >= '3.2':
45 | d = digester(['md5', 'sha1'])
46 | d.update(encoded)
47 | return d['sha1']
48 | else:
49 | return sha1(encoded).hexdigest()
50 |
51 | def hg_memfilectx(repo, path, data, is_link=False, is_exec=False, copied=None):
52 | if hg_version() >= '3.1':
53 | return memfilectx(repo, path, data, is_link, is_exec, copied)
54 | else:
55 | return memfilectx(path, data, is_link, is_exec, copied)
56 |
57 | def hg_strip(repo, processed_nodes):
58 | class dummyui(object):
59 | def debug(self, msg):
60 | pass
61 |
62 | if StrictVersion(hg_version()) >= StrictVersion('2.8'):
63 | stripext = extensions.load(dummyui(), 'strip', '')
64 | return stripext.strip(dummyui(), repo, processed_nodes)
65 | else:
66 | return repo.mq.strip(repo, processed_nodes)
67 |
68 | # Helper Functions to help with changes to the mercurial API
69 |
70 | def handle_deleted_file():
71 | if hg_version() >= '3.2':
72 | return
73 | else:
74 | raise IOError
--------------------------------------------------------------------------------
/gitifyhg/gitexporter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2012-2013 Dusty Phillips
2 |
3 | # This file is part of gitifyhg.
4 |
5 | # gitifyhg is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # gitifyhg is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with gitifyhg. If not, see .
17 |
18 | # Some of this code comes from https://github.com/felipec/git/tree/fc/remote/hg
19 | # but much of it has been rewritten.
20 |
21 | from mercurial.context import memctx
22 | from mercurial.error import Abort
23 | from mercurial.node import hex as hghex # What idiot overrode a builtin?
24 | from mercurial.node import short as hgshort
25 | from mercurial.bookmarks import pushbookmark
26 | from mercurial.scmutil import revsingle
27 | from mercurial.util import version as hg_version
28 | from mercurial import encoding
29 |
30 | from .util import (die, output, git_to_hg_spaces, hgmode, branch_tip,
31 | ref_to_name_reftype, BRANCH, BOOKMARK, TAG, user_config)
32 |
33 | from apiwrapper import (hg_strip, hg_memfilectx, hg_push, handle_deleted_file)
34 |
35 | class GitExporter(object):
36 |
37 | '''A processor when the remote receives a git-remote `export` command.
38 | Provides export information to push commits from git to the mercurial
39 | repository.'''
40 |
41 | NULL_PARENT = '\0' * 20
42 |
43 | def __init__(self, hgremote, parser):
44 | self.hgremote = hgremote
45 | self.marks = self.hgremote.marks
46 | self.parsed_refs = self.hgremote.parsed_refs
47 | self.parsed_tags = {} # refs to tuple of (message, author)
48 | self.blob_marks = self.hgremote.blob_marks
49 | self.repo = self.hgremote.repo
50 | self.parser = parser
51 | self.processed_marks = set()
52 | self.processed_nodes = []
53 | self.hgrc = user_config()
54 |
55 | def process(self):
56 | self.marks.store() # checkpoint
57 | new_branch = False
58 | push_bookmarks = []
59 | self.parser.read_line()
60 | for line in self.parser.read_block('done'):
61 | command = line.split()[0]
62 | if command not in ('blob', 'commit', 'reset', 'tag', 'feature'):
63 | die('unhandled command: %s' % line)
64 | getattr(self, 'do_%s' % command)()
65 |
66 | updated_refs = {}
67 | for ref, node in self.parsed_refs.iteritems():
68 | if ref.startswith(self.hgremote.prefix):
69 | # This seems to be a git fast-export bug
70 | continue
71 | name, reftype = ref_to_name_reftype(ref)
72 | name = git_to_hg_spaces(name)
73 | if reftype == BRANCH:
74 | if name not in self.hgremote.branches:
75 | new_branch = True
76 | elif reftype == BOOKMARK:
77 | old = self.hgremote.bookmarks.get(name)
78 | old = old.hex() if old else ''
79 | if not pushbookmark(self.repo, name, old, node):
80 | continue
81 | push_bookmarks.append((name, old, hghex(node)))
82 | elif reftype == TAG:
83 | self.write_tag(name, node)
84 | else:
85 | assert False, "unexpected reftype: %s" % reftype
86 | updated_refs[ref] = node
87 |
88 | success = False
89 | try:
90 | hg_push(self.repo, self.hgremote.peer, False, new_branch)
91 |
92 | for bookmark, old, new in push_bookmarks:
93 | self.hgremote.peer.pushkey('bookmarks', bookmark, old, new)
94 | self.marks.store()
95 | success = True
96 | except Abort as e:
97 | # mercurial.error.Abort: push creates new remote head f14531ca4e2d!
98 | if e.message.startswith("push creates new remote head"):
99 | self.marks.load() # restore from checkpoint
100 | # strip revs, implementation finds min revision from list
101 | if self.processed_nodes:
102 | hg_strip(self.repo, self.processed_nodes)
103 | else:
104 | die("unknown hg exception: %s" % e)
105 | # TODO: handle network/other errors?
106 |
107 | for ref, node in updated_refs.items():
108 | if success:
109 | status = ""
110 | name, reftype = ref_to_name_reftype(ref)
111 | gitify_ref = self.hgremote.make_gitify_ref(name, reftype)
112 | last_known_rev = self.marks.tips.get(gitify_ref)
113 | new_rev = self.repo[node].rev()
114 | if last_known_rev is not None and last_known_rev == new_rev:
115 | # up to date status tells git that nothing has changed
116 | # during the push for this ref, which prevents it from
117 | # printing pointless status info to the user such as:
118 | # * [new branch] master -> master
119 | status = " up to date"
120 | output("ok %s%s" % (ref, status))
121 | else:
122 | output("error %s non-fast forward" % ref) # TODO: other errors as well
123 | output()
124 |
125 | if not success:
126 | # wait until fast-export finishes to muck with the marks file
127 | self.remove_processed_git_marks()
128 |
129 | def remove_processed_git_marks(self):
130 | with self.hgremote.marks_git_path.open() as fread:
131 | with self.hgremote.marks_git_path.open('r+') as fwrite:
132 | for line in fread:
133 | if not line.startswith(':'):
134 | die("invalid line in marks-git: " + line)
135 | mark = line[1:].split()[0]
136 | if mark not in self.processed_marks:
137 | fwrite.write(line)
138 | fwrite.truncate()
139 |
140 | def do_blob(self):
141 | mark = self.parser.read_mark()
142 | self.blob_marks[mark] = self.parser.read_data()
143 | self.parser.read_line()
144 |
145 | def do_reset(self):
146 | ref = self.parser.line.split()[1]
147 |
148 | # If the next line is a commit, allow it to process normally
149 | if not self.parser.peek().startswith('from'):
150 | return
151 |
152 | from_mark = self.parser.read_mark()
153 | from_revision = self.marks.mark_to_revision(from_mark)
154 | self.parsed_refs[ref] = from_revision
155 |
156 | # skip a line
157 | self.parser.read_line()
158 |
159 | def do_commit(self):
160 | files = {}
161 | extra = {}
162 | from_mark = merge_mark = None
163 |
164 | ref = self.parser.line.split()[1]
165 |
166 | commit_mark = self.parser.read_mark()
167 | author = self.parser.read_author()
168 | committer = self.parser.read_author()
169 | data = self.parser.read_data()
170 | if self.parser.peek().startswith('from'):
171 | from_mark = self.parser.read_mark()
172 | if self.parser.peek().startswith('merge'):
173 | merge_mark = self.parser.read_mark()
174 | if self.parser.peek().startswith('merge'):
175 | die('Octopus merges are not yet supported')
176 |
177 | self.parser.read_line()
178 |
179 | for line in self.parser.read_block(''):
180 | if line.startswith('M'):
181 | t, mode, mark_ref, path = line.split(' ', 3)
182 | mark = int(mark_ref[1:])
183 | filespec = {'mode': hgmode(mode), 'data': self.blob_marks[mark]}
184 | elif line.startswith('D'):
185 | t, path = line.split(' ', 1)
186 | filespec = {'deleted': True}
187 | if path[0] == '"' and path[-1] == '"':
188 | path = path.decode('string-escape')[1:-1]
189 | files[path] = filespec
190 |
191 | user, date, tz = author
192 |
193 | if committer != author:
194 | extra['committer'] = "%s %u %u" % committer
195 |
196 | if from_mark:
197 | parent_from = self.marks.mark_to_revision(from_mark)
198 | else:
199 | parent_from = self.NULL_PARENT
200 |
201 | if merge_mark:
202 | parent_merge = self.marks.mark_to_revision(merge_mark)
203 | else:
204 | parent_merge = self.NULL_PARENT
205 |
206 | # hg needs to know about files that changed from either parent
207 | # whereas git only cares if it changed from the first parent.
208 | if merge_mark:
209 | for file in self.repo[parent_from].files():
210 | if file not in files and file in\
211 | self.repo[parent_from].manifest():
212 | files[file] = {'ctx': self.repo[parent_from][file]}
213 |
214 | name, reftype = ref_to_name_reftype(ref)
215 | if reftype == BRANCH:
216 | extra['branch'] = git_to_hg_spaces(name)
217 |
218 | def get_filectx(repo, memctx, file):
219 | filespec = files[file]
220 |
221 | if 'deleted' in filespec:
222 | return handle_deleted_file()
223 | if 'ctx' in filespec:
224 | return filespec['ctx']
225 | is_exec = filespec['mode'] == 'x'
226 | is_link = filespec['mode'] == 'l'
227 | rename = filespec.get('rename', None)
228 |
229 | return hg_memfilectx(repo,file, filespec['data'],is_link, is_exec, rename)
230 |
231 | ctx = memctx(self.repo, (parent_from, parent_merge), data,
232 | files.keys(), get_filectx, user, (date, tz), extra)
233 |
234 | tmp = encoding.encoding
235 | encoding.encoding = 'utf-8'
236 | node = self.repo.commitctx(ctx)
237 | encoding.encoding = tmp
238 |
239 | self.parsed_refs[ref] = node
240 | self.marks.new_mark(node, commit_mark)
241 | self.processed_marks.add(str(commit_mark))
242 | self.processed_nodes.append(node)
243 |
244 | def do_tag(self):
245 | name = self.parser.line.split()[1]
246 | self.parser.read_mark()
247 | tagger = self.parser.read_author()
248 | message = self.parser.read_data()
249 | self.parser.read_line()
250 | self.parsed_tags[git_to_hg_spaces(name)] = tagger, message
251 |
252 | def do_feature(self):
253 | pass # Ignore
254 |
255 | def write_tag(self, name, node):
256 | branch = self.repo[node].branch()
257 | # Calling self.repo.tag() doesn't append the tag to the correct
258 | # commit. So I copied some of localrepo._tag into here.
259 | # But that method, like much of mercurial's code, is ugly.
260 | # So I then rewrote it.
261 |
262 | tags_revision = revsingle(self.repo, hghex(branch_tip(self.repo, branch)))
263 | if '.hgtags' in tags_revision:
264 | old_tags = tags_revision['.hgtags'].data()
265 | else:
266 | old_tags = ''
267 | newtags = [old_tags]
268 | if old_tags and old_tags[-1] != '\n':
269 | newtags.append('\n')
270 |
271 | encoded_tag = encoding.fromlocal(name)
272 | tag_line = '%s %s' % (hghex(node), encoded_tag)
273 | if tag_line in old_tags:
274 | return # Don't commit a tag that was previously committed
275 | newtags.append(tag_line)
276 |
277 | def get_filectx(repo, memctx, file):
278 | return hg_memfilectx(repo, file, ''.join(newtags))
279 |
280 | if name in self.parsed_tags:
281 | author, message = self.parsed_tags[name]
282 | user, date, tz = author
283 | date_tz = (date, tz)
284 | else:
285 | message = "Added tag %s for changeset %s" % (name, hgshort(node))
286 | user = self.hgrc.get("ui", "username", None)
287 | date_tz = None # XXX insert current date here
288 | ctx = memctx(self.repo,
289 | (branch_tip(self.repo, branch), self.NULL_PARENT), message,
290 | ['.hgtags'], get_filectx, user, date_tz, {'branch': branch})
291 |
292 | tmp = encoding.encoding
293 | encoding.encoding = 'utf-8'
294 | node = self.repo.commitctx(ctx)
295 | encoding.encoding = tmp
296 |
--------------------------------------------------------------------------------
/gitifyhg/gitifyhg.py:
--------------------------------------------------------------------------------
1 | # Copyright 2012-2013 Dusty Phillips
2 |
3 | # This file is part of gitifyhg.
4 |
5 | # gitifyhg is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # gitifyhg is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with gitifyhg. If not, see .
17 |
18 | # Some of this code comes from https://github.com/felipec/git/tree/fc/remote/hg
19 | # but much of it has been rewritten.
20 |
21 | import sys
22 | import os
23 | import re
24 | import optparse
25 | import subprocess
26 | from path import Path as p
27 |
28 | # Enable "plain" mode to make us resilient against changes to the locale, as we
29 | # rely on parsing certain messages produced by Mercurial. See issue #26.
30 | os.environ['HGPLAIN'] = '1'
31 | # Disable loading of the user's $HOME/.hgrc as extensions can cause weird
32 | # interactions and it's better to run in a known state.
33 | os.environ['HGRCPATH'] = ''
34 |
35 | from .util import (log, die, output, branch_head, GitMarks,
36 | HGMarks, hg_to_git_spaces, name_reftype_to_ref, BRANCH, BOOKMARK, TAG,
37 | version, deactivate_stdout)
38 |
39 | # Version specific libraries from the Mercurial API
40 | from mercurial import hg
41 | from mercurial import encoding
42 | from mercurial.bookmarks import listbookmarks
43 | from mercurial.ui import ui
44 | from mercurial.error import Abort, RepoError
45 | from mercurial.util import version as hg_version
46 |
47 | from .util import (log, die, output, branch_head, GitMarks,
48 | HGMarks, hg_to_git_spaces, name_reftype_to_ref, BRANCH, BOOKMARK, TAG,
49 | version, deactivate_stdout)
50 |
51 | from apiwrapper import (hg_sha1, hg_readactive, hg_pull)
52 | from .hgimporter import HGImporter
53 | from .gitexporter import GitExporter
54 |
55 | class GitRemoteParser(object):
56 | '''Parser for stdin that processes the git-remote protocol.'''
57 |
58 | def __init__(self):
59 | self.peek_stack = []
60 | self.read_line()
61 |
62 | def read_line(self):
63 | '''Read a line from the standard input.'''
64 | if self.peek_stack:
65 | self.line = self.peek_stack.pop(0)
66 | else:
67 | self.line = sys.stdin.readline().strip()
68 | log("INPUT: %s" % self.line)
69 | return self.line
70 |
71 | def peek(self):
72 | '''Look at the next line and store it so that it can still be returned
73 | by read_line.'''
74 | line = sys.stdin.readline().strip()
75 | self.peek_stack.append(line)
76 | return line
77 |
78 | def read_mark(self):
79 | '''The remote protocol contains lines of the format mark: number.
80 | Return the mark.'''
81 | return int(self.read_line().partition(':')[-1])
82 |
83 | def read_data(self):
84 | '''Read all data following a data line for the given number of bytes'''
85 | self.read_line()
86 | if not self.line.startswith('data'):
87 | return None
88 | size = int(self.line.partition(' ')[-1])
89 | return sys.stdin.read(size)
90 |
91 | def read_author(self):
92 | '''Read and parse an author string. Return a tuple of
93 | (user string, date, git_tz).'''
94 | self.read_line()
95 | AUTHOR_RE = re.compile(r'^(?:author|committer|tagger)(?: ([^<>]+)?)? <([^<>]*)> (\d+) ([+-]\d+)')
96 | match = AUTHOR_RE.match(self.line)
97 | if not match:
98 | return None
99 |
100 | user, email, date, tz = match.groups()
101 | if user is None:
102 | user = ''
103 | user += ' <' + email + '>'
104 |
105 | date = int(date)
106 | tz = -(((int(tz) / 100) * 3600) + ((int(tz) % 100) * 60))
107 | return (user, date, tz)
108 |
109 | def read_block(self, sentinel):
110 | '''Yield a block of lines one by one until the sentinel value
111 | is returned. Sentinel may be an empty string, 'done', or other values
112 | depending on what block is being read.'''
113 | while self.line != sentinel:
114 | yield self.line
115 | self.line = self.read_line()
116 |
117 | class HGRemote(object):
118 | def __init__(self, alias, url):
119 | if hg.islocal(url.encode('utf-8')):
120 | url = p(url).abspath()
121 | # Force git to use an absolute path in the future
122 | remote_name = os.path.basename(sys.argv[0]).replace("git-remote-", "")
123 | cmd = ['git', 'config', 'remote.%s.url' % alias, "%s::%s" % (
124 | remote_name, url)]
125 | subprocess.call(cmd)
126 |
127 | # use hash of URL as unique identifier in various places.
128 | # this has the advantage over 'alias' that it stays constant
129 | # when the user does a "git remote rename old new".
130 |
131 | self.uuid = hg_sha1(url)
132 |
133 | gitdir = p(os.environ['GIT_DIR'].decode('utf-8'))
134 | self.remotedir = gitdir.joinpath('hg', self.uuid)
135 | self.marks_git_path = self.remotedir.joinpath('marks-git')
136 | self.marks_hg_path = self.remotedir.joinpath('marks-hg')
137 | self.marks = HGMarks(self.marks_hg_path)
138 | self.git_marks = GitMarks(self.marks_git_path)
139 | self.parsed_refs = {}
140 | self.blob_marks = {}
141 | self.branches = {}
142 | self.bookmarks = {}
143 |
144 | self.prefix = 'refs/hg/%s' % alias
145 | self.alias = alias
146 | self.url = url
147 | self.build_repo(url)
148 |
149 | def build_repo(self, url):
150 | '''Make the Mercurial repo object self.repo available. If the local
151 | clone does not exist, clone it, otherwise, ensure it is fetched.'''
152 | myui = ui()
153 | myui.setconfig('ui', 'interactive', 'off')
154 | myui.setconfig('extensions', 'mq', '')
155 | # FIXME: the following is a hack to achieve hg-git / remote-git compatibility
156 | # at least for *local* operations. still need to figure out what the right
157 | # thing to do is.
158 | myui.setconfig('phases', 'publish', False)
159 |
160 | local_path = self.remotedir.joinpath('clone')
161 | if not local_path.exists():
162 | try:
163 | self.peer, dstpeer = hg.clone(myui, {}, url.encode('utf-8'),
164 | local_path.encode('utf-8'), update=False, pull=True)
165 | except (RepoError, Abort) as e:
166 | sys.stderr.write("abort: %s\n" % e)
167 | if e.hint:
168 | sys.stderr.write("(%s)\n" % e.hint)
169 | sys.exit(-1)
170 |
171 | self.repo = dstpeer.local()
172 | else:
173 | self.repo = hg.repository(myui, local_path.encode('utf-8'))
174 | self.peer = hg.peer(myui, {}, url.encode('utf-8'))
175 | hg_pull(self.repo, self.peer, None, True)
176 |
177 | self.marks.upgrade_marks(self)
178 |
179 | def make_gitify_ref(self, name, reftype):
180 | if not isinstance(name, unicode):
181 | name = name.decode('utf-8')
182 | if reftype == BRANCH:
183 | if name == 'default':
184 | # I have no idea where 'bookmarks' comes from in this case.
185 | # I don't think there is meant to be many bookmarks/master ref,
186 | # but this is what I had to do to make tests pass when special
187 | # casing the master/default dichotomy. Something is still fishy
188 | # here, but it's less fishy than it was. See issue #34.
189 | return "%s/bookmarks/master" % self.prefix
190 | else:
191 | return '%s/branches/%s' % (self.prefix, name)
192 | elif reftype == BOOKMARK:
193 | return '%s/bookmarks/%s' % (self.prefix, name)
194 | elif reftype == TAG:
195 | return '%s/tags/%s' % (self.prefix, name)
196 | else:
197 | assert False, "unknown reftype: %s" % reftype
198 |
199 | def process(self):
200 | '''Process the messages coming in on stdin using the git-remote
201 | protocol and respond appropriately'''
202 | parser = GitRemoteParser()
203 |
204 | for line in parser.read_block(''):
205 | command = line.split()[0]
206 | if command not in ('capabilities', 'list', 'import', 'export'):
207 | die('unhandled command: %s' % line)
208 | getattr(self, 'do_%s' % command)(parser)
209 |
210 | try:
211 | self.marks.store()
212 | except IOError as e:
213 | if e.errno == 2 and e.filename == self.marks_hg_path:
214 | log("The marks file has been removed. This usually suggests "
215 | "that a git clone operation failed. "
216 | "To debug, set environment variable DEBUG_GITIFYHG "
217 | "and rerun. ", "ERROR")
218 | die("Error updating marks.")
219 | raise
220 |
221 | def do_capabilities(self, parser):
222 | '''Process the capabilities request when incoming from git-remote.
223 | '''
224 | output(u"import")
225 | output(u"export")
226 | for reftype in (BRANCH, BOOKMARK, TAG):
227 | output(u"refspec %s:%s" %
228 | (name_reftype_to_ref('*', reftype),
229 | self.make_gitify_ref('*', reftype)))
230 |
231 | if self.marks_git_path.exists():
232 | output(u"*import-marks %s" % self.marks_git_path)
233 | output(u"*export-marks %s" % self.marks_git_path)
234 |
235 | output()
236 |
237 | def _change_hash(self, changectx):
238 | node = changectx.node()
239 | if node and self.marks.is_marked(node):
240 | mark = self.marks.revision_to_mark(node)
241 | if self.git_marks.has_mark(mark):
242 | return self.git_marks.mark_to_hash(mark)
243 | return '?'
244 |
245 | def do_list(self, parser):
246 | '''List all references in the mercurial repository. This includes
247 | the current head, all branches, tags, and bookmarks.'''
248 |
249 | current_branch = self.repo.dirstate.branch()
250 |
251 | # Update the head reference
252 | head = hg_readactive(self.repo)
253 |
254 | if head:
255 | node = self.repo[head]
256 | else:
257 | # If there is no bookmark for head, mock one
258 | head = current_branch
259 | node = self.repo['.']
260 | # I think this means an initial clone occured and we haven't
261 | # hg updated yet in the local clone
262 | if not node:
263 | if 'default' in self.repo:
264 | node = self.repo['default']
265 | else: # empty repository or head is at 0 commit
266 | output()
267 | return
268 | head = head if head != 'default' else 'master'
269 | #self.bookmarks[head] = node
270 |
271 | self.headnode = (head, node)
272 |
273 | # Update the bookmark references
274 | for bookmark, node in listbookmarks(self.repo).iteritems():
275 | self.bookmarks[bookmark] = self.repo[node]
276 |
277 | # update the named branch references
278 | for branch in self.repo.branchmap():
279 | # FIXME: Probably a git config instead of an env var would make
280 | # people happier here.
281 | clone_closed = os.environ.get("GITIFYHG_ALLOW_CLOSED_BRANCHES") != None
282 | heads = self.repo.branchheads(branch, closed=clone_closed)
283 | if heads:
284 | self.branches[branch] = heads
285 |
286 | # list the head reference
287 | output("@refs/heads/%s HEAD" % self.headnode[0])
288 |
289 | # list the named branch references
290 | for branch in self.branches:
291 | output("%s %s" %
292 | (self._change_hash(branch_head(self, branch)),
293 | name_reftype_to_ref(hg_to_git_spaces(branch), BRANCH)))
294 |
295 | # list the bookmark references
296 | for bookmark, changectx in self.bookmarks.items():
297 | if bookmark != "master":
298 | output("%s %s" %
299 | (self._change_hash(changectx),
300 | name_reftype_to_ref(hg_to_git_spaces(bookmark), BOOKMARK)))
301 |
302 | # list the tags
303 | for tag, node in self.repo.tagslist():
304 | if tag != "tip":
305 | output("%s %s" %
306 | (self._change_hash(self.repo[node]),
307 | name_reftype_to_ref(hg_to_git_spaces(tag), TAG)))
308 |
309 | output()
310 |
311 | def do_import(self, parser):
312 | HGImporter(self, parser).process()
313 |
314 | def do_export(self, parser):
315 | GitExporter(self, parser).process()
316 |
317 | def log_versions(level="DEBUG"):
318 | log("gitifyhg version %s" % version(), level=level)
319 | log("Mercurial version %s" % hg_version(), level=level)
320 | log("Python version %s" % (sys.version.replace("\n", "")), level=level)
321 |
322 | def main():
323 | '''Main entry point for the git-remote-gitifyhg command. Parses sys.argv
324 | and constructs a parser from the result.
325 | '''
326 | log_versions()
327 |
328 | name = os.path.basename(sys.argv[0]).replace("git-remote-", "")
329 | description = """This is a remote helper for git to interact with hg.
330 | You should generally not call this executable directly; it will be called
331 | by git if you put this executable on your PATH and set your git remote to:
332 | %s::
333 | """ % name
334 |
335 | parser = optparse.OptionParser(usage="usage: %prog [options] ", description=description)
336 | parser.add_option("-v", "--version", default=False, action="store_true",
337 | help="Print version number only")
338 | opts, args = parser.parse_args()
339 | if opts.version:
340 | log_versions("VERSION")
341 | sys.exit(0)
342 | if not args:
343 | parser.print_help()
344 | sys.exit(0)
345 |
346 | deactivate_stdout()
347 | HGRemote(*[x.decode('utf-8') for x in args]).process()
348 | try:
349 | sys.stderr.close()
350 | except:
351 | pass
352 |
353 | if __name__ == '__main__':
354 | sys.exit(main())
355 |
--------------------------------------------------------------------------------
/gitifyhg/hgimporter.py:
--------------------------------------------------------------------------------
1 | # Copyright 2012-2013 Dusty Phillips
2 |
3 | # This file is part of gitifyhg.
4 |
5 | # gitifyhg is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # gitifyhg is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with gitifyhg. If not, see .
17 |
18 | # Some of this code comes from https://github.com/felipec/git/tree/fc/remote/hg
19 | # but much of it has been rewritten.
20 |
21 | import time
22 | import re
23 |
24 | from mercurial import encoding
25 |
26 | from .util import (log, output, gittz, gitmode,
27 | git_to_hg_spaces, hg_to_git_spaces, branch_head, ref_to_name_reftype,
28 | BRANCH, BOOKMARK, TAG, relative_path)
29 |
30 | AUTHOR = re.compile(r'^([^<>]+)?(<(?:[^<>]*)>| [^ ]*@.*|[<>].*)$')
31 |
32 |
33 | def sanitize_author(author):
34 | '''Mercurial allows a more freeform user string than git, so we have to
35 | massage it to be compatible. Git expects "name ", where email can be
36 | empty (as long as it's surrounded by <>).'''
37 | name = ''
38 | email = ''
39 | author = author.replace('"', '')
40 | match = AUTHOR.match(author)
41 | if match:
42 | if match.group(1): # handle 'None', e.g for input ""
43 | name = match.group(1).strip()
44 | email = match.group(2).translate(None, "<>").strip()
45 | else:
46 | author = author.translate(None, "<>").strip()
47 | if "@" in author:
48 | email = author
49 | else:
50 | name = author
51 |
52 | if not name:
53 | name = 'Unknown'
54 |
55 | return "%s <%s>" % (name, email)
56 |
57 |
58 | class HGImporter(object):
59 |
60 | '''A processor when the remote receives a git-remote import command.
61 | Provides import information from the mercurial repository to git.'''
62 | def __init__(self, hgremote, parser):
63 | self.hgremote = hgremote
64 | self.marks = self.hgremote.marks
65 | self.prefix = self.hgremote.prefix
66 | self.repo = self.hgremote.repo
67 | self.parser = parser
68 | self.notes_committed = 0
69 |
70 | def process(self):
71 | output("feature done")
72 | if self.hgremote.marks_git_path.exists():
73 | output("feature import-marks=%s" % self.hgremote.marks_git_path)
74 | output("feature export-marks=%s" % self.hgremote.marks_git_path)
75 | output("feature notes")
76 |
77 | tmp = encoding.encoding
78 | encoding.encoding = 'utf-8'
79 |
80 | self.commit_count = 0
81 | while self.parser.line.startswith('import'):
82 | ref = self.parser.line.split()[1]
83 |
84 | if ref == 'HEAD':
85 | self.process_ref(
86 | self.hgremote.headnode[0],
87 | BOOKMARK,
88 | self.hgremote.headnode[1])
89 | else:
90 | name, reftype = ref_to_name_reftype(ref)
91 | if reftype == BRANCH:
92 | head = branch_head(self.hgremote, git_to_hg_spaces(name))
93 | elif reftype == BOOKMARK:
94 | head = self.hgremote.bookmarks[git_to_hg_spaces(name)]
95 | elif reftype == TAG:
96 | head = self.repo[git_to_hg_spaces(name)]
97 | else:
98 | assert False, "unexpected reftype: %s" % reftype
99 | self.process_ref(name, reftype, head)
100 |
101 | self.process_notes()
102 |
103 | self.parser.read_line()
104 |
105 | encoding.encoding = tmp
106 | output('done')
107 |
108 | def process_notes(self):
109 | last_notes_mark = self.marks.notes_mark if self.marks.notes_mark is not None else 0
110 | mark_to_hgsha1 = [(mark, self.repo[rev].hex()) for rev, mark in
111 | self.marks.revisions_to_marks.iteritems() if mark > last_notes_mark]
112 | if not mark_to_hgsha1 or self.commit_count < 1:
113 | return
114 | output("commit refs/notes/hg-%s" % (self.hgremote.uuid))
115 | output("mark :%d" % (self.marks.new_notes_mark()))
116 | output("committer %s %s" % (int(time.time()), time.strftime('%z')))
117 | message = u"hg from %s (%s)\n" % (self.prefix, self.hgremote.url)
118 | message = message.encode("utf-8")
119 | output("data %d" % (len(message)))
120 | output(message)
121 | if last_notes_mark > 0:
122 | output("from :%d" % (last_notes_mark))
123 | for mark, hgsha1 in mark_to_hgsha1:
124 | output("N inline :%d" % (mark))
125 | output("data 40")
126 | output(hgsha1)
127 | output()
128 |
129 | def process_ref(self, name, reftype, head):
130 | gitify_ref = self.hgremote.make_gitify_ref(name, reftype)
131 | tip = self.marks.tips.get(gitify_ref, 0)
132 |
133 | revs = xrange(tip, head.rev() + 1)
134 | count = 0
135 |
136 | for rev in revs:
137 | node = self.repo[rev].node()
138 | if self.marks.is_marked(node):
139 | continue
140 |
141 | (manifest, user, (time, tz), files, description, extra
142 | ) = self.repo.changelog.read(self.repo[rev].node())
143 |
144 | user = sanitize_author(user)
145 | author = "%s %d %s" % (user, time, gittz(tz))
146 |
147 | if 'committer' in extra:
148 | user, time, tz = extra['committer'].rsplit(' ', 2)
149 | user = sanitize_author(user)
150 | committer = "%s %s %s" % (user, time, gittz(int(tz)))
151 | else:
152 | committer = author
153 |
154 | parents = [p for p in self.repo.changelog.parentrevs(rev) if p >= 0]
155 |
156 | if parents:
157 | modified, removed = self.get_filechanges(self.repo[rev],
158 | parents[0])
159 | else:
160 | modified, removed = self.repo[rev].manifest().keys(), []
161 |
162 | if not parents and rev:
163 | output('reset %s' % gitify_ref)
164 |
165 | output("commit %s" % gitify_ref)
166 | output("mark :%d" % (self.marks.get_mark(node)))
167 | output("author %s" % (author))
168 | output("committer %s" % (committer))
169 | output("data %d" % (len(description)))
170 | output(description)
171 |
172 | if parents:
173 | output("from :%s" % (self.marks.revision_to_mark(self.repo[parents[0]].node())))
174 | if len(parents) > 1:
175 | output("merge :%s" % (self.marks.revision_to_mark(self.repo[parents[1]].node())))
176 |
177 | for file in modified:
178 | filecontext = self.repo[rev].filectx(file)
179 | data = filecontext.data()
180 | output("M %s inline %s" % (
181 | gitmode(filecontext.flags()), relative_path(filecontext.path())))
182 | output("data %d" % len(data))
183 | output(data)
184 | for file in removed:
185 | output("D %s" % (relative_path(file)))
186 | output()
187 |
188 | count += 1
189 | if (count % 100 == 0):
190 | output("progress revision %d '%s' (%d/%d)" % (
191 | rev, name, count, len(revs)))
192 | output("#############################################################")
193 |
194 | # make sure the ref is updated
195 | output("reset %s" % gitify_ref)
196 | output("from :%u" % self.marks.revision_to_mark(head.node()))
197 | output()
198 |
199 | self.marks.tips[gitify_ref] = head.rev()
200 | self.commit_count += count
201 |
202 | def get_filechanges(self, context, parent):
203 | modified = set()
204 | added = set()
205 | removed = set()
206 |
207 | current = context.manifest()
208 | previous = self.repo[parent].manifest().copy()
209 |
210 | for fn in current:
211 | if fn in previous:
212 | if (current.flags(fn) != previous.flags(fn) or current[fn] != previous[fn]):
213 | modified.add(fn)
214 | del previous[fn]
215 | else:
216 | added.add(fn)
217 | removed |= set(previous.keys())
218 |
219 | return added | modified, removed
220 |
--------------------------------------------------------------------------------
/gitifyhg/util.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import json
4 |
5 | from mercurial.node import hex as hghex # What idiot overrode a builtin?
6 | from mercurial.node import bin as hgbin
7 | from mercurial.config import config
8 | from mercurial.scmutil import userrcpath
9 |
10 |
11 | DEBUG_GITIFYHG = os.environ.get("DEBUG_GITIFYHG") != None
12 |
13 |
14 | BRANCH = 'branch'
15 | BOOKMARK = 'bookmark'
16 | TAG = 'tag'
17 |
18 | actual_stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) # Ensure stdout is unbuffered
19 |
20 |
21 | def deactivate_stdout():
22 | """Hijack stdout to prevent mercurial from inadvertently talking to git.
23 |
24 | Mere interactive=off and ui.pushbuffer() don't seem to work.
25 | """
26 | class DummyOut(object):
27 | def write(self, x):
28 | pass
29 |
30 | def flush(self):
31 | pass
32 | sys.stdout = DummyOut()
33 |
34 |
35 | def log(msg, level="DEBUG"):
36 | '''The git remote operates on stdin and stdout, so all debugging information
37 | has to go to stderr.'''
38 | if DEBUG_GITIFYHG or level != "DEBUG":
39 | sys.stderr.write(u'%s: %r\n' % (level, msg))
40 |
41 |
42 | def die(msg, *args):
43 | log(msg, 'ERROR', *args)
44 | sys.exit(1)
45 |
46 |
47 | def output(msg=''):
48 | if isinstance(msg, unicode):
49 | msg = msg.encode('utf-8')
50 | log("OUT: %s" % msg)
51 | print >> actual_stdout, msg
52 |
53 |
54 | def version():
55 | """Return version of gitifyhg"""
56 | try:
57 | import pkg_resources
58 | return pkg_resources.get_distribution("gitifyhg").version
59 | except Exception:
60 | return "UNKNOWN"
61 |
62 |
63 | def gittz(tz):
64 | return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
65 |
66 |
67 | def gitmode(flags):
68 | if 'l' in flags:
69 | return '120000'
70 | elif 'x' in flags:
71 | return '100755'
72 | else:
73 | return '100644'
74 |
75 |
76 | def hgmode(mode):
77 | modes = {'100755': 'x', '120000': 'l'}
78 | return modes.get(mode, '')
79 |
80 |
81 | def hg_to_git_spaces(name):
82 | '''Spaces are allowed in mercurial, but not in git. We convert them to
83 | the unlikely string ___'''
84 | return name.replace(' ', '___')
85 |
86 |
87 | def git_to_hg_spaces(name):
88 | '''But when we push back to mercurial, we need to convert it the other way.'''
89 | return name.replace('___', ' ')
90 |
91 |
92 | def branch_tip(repo, branch):
93 | '''HG has a lovely branch_tip method, but it requires mercurial 2.4
94 | This function provides backwards compatibility. If we ever get to
95 | drop older versions, we can drop this function.'''
96 | if hasattr(repo, 'branchtip'):
97 | return repo.branchtip(branch)
98 | else:
99 | return repo.branchtags()[branch]
100 |
101 |
102 | def branch_head(hgremote, branch):
103 | try:
104 | heads = hgremote.branches[branch]
105 | except KeyError:
106 | return
107 |
108 | if len(heads) > 1:
109 | log("Branch '%s' has more than one head, consider merging" % (
110 | branch), "WARNING")
111 | tip = branch_tip(hgremote.repo, branch)
112 | else:
113 | tip = heads[0]
114 |
115 | return hgremote.repo[tip]
116 |
117 |
118 | def ref_to_name_reftype(ref):
119 | '''Converts a git ref into a name (e.g., the name of that branch, tag, etc.)
120 | and its hg type (one of BRANCH, BOOKMARK, or TAG).'''
121 | if ref == 'refs/heads/master':
122 | return ('default', BRANCH)
123 | elif ref.startswith('refs/heads/branches/'):
124 | return (ref[len('refs/heads/branches/'):], BRANCH)
125 | elif ref.startswith('refs/heads/'):
126 | return (ref[len('refs/heads/'):], BOOKMARK)
127 | elif ref.startswith('refs/tags/'):
128 | return (ref[len('refs/tags/'):], TAG)
129 | else:
130 | assert False, "unexpected ref: %s" % ref
131 |
132 |
133 | def name_reftype_to_ref(name, reftype):
134 | '''Converts a name and type (e.g., '1.0' and 'tags') into a git ref.'''
135 | if reftype == BRANCH:
136 | if name == 'default':
137 | return 'refs/heads/master'
138 | else:
139 | return 'refs/heads/branches/%s' % name
140 | elif reftype == BOOKMARK:
141 | return 'refs/heads/%s' % name
142 | elif reftype == TAG:
143 | return 'refs/tags/%s' % name
144 | assert False, "unknown reftype: %s" % reftype
145 |
146 |
147 | def user_config():
148 | """Read the Mercurial user configuration
149 |
150 | This is typically ~/.hgrc on POSIX. This is returned
151 | as a Mercurial.config.config object.
152 | """
153 | hgrc = config()
154 | for cfg in userrcpath():
155 | if not os.path.exists(cfg):
156 | log("NOT reading missing cfg: " + cfg)
157 | continue
158 | log("Reading config: " + cfg)
159 | hgrc.read(cfg)
160 | return hgrc
161 |
162 |
163 | def relative_path(path):
164 | """Ensure path is relative"""
165 | return os.path.relpath(path, '/') if os.path.isabs(path) else path
166 |
167 |
168 | class HGMarks(object):
169 | '''Maps integer marks to specific string mercurial revision identifiers.
170 | Identifiers are passed as binary nodes and converted to/from hex strings
171 | before and after storage.'''
172 |
173 | def __init__(self, storage_path):
174 | ''':param storage_path: The file that marks are stored in between calls.
175 | Marks are stored in json format.'''
176 | self.storage_path = storage_path
177 | self.load()
178 |
179 | def load(self):
180 | '''Load the marks from the storage file'''
181 | if self.storage_path.exists():
182 | with self.storage_path.open() as file:
183 | loaded = json.load(file)
184 |
185 | self.tips = loaded['tips']
186 | self.revisions_to_marks = loaded['revisions_to_marks']
187 | self.last_mark = loaded['last-mark']
188 | self.marks_to_revisions = dict([(int(v), k) for k, v in
189 | self.revisions_to_marks.iteritems()])
190 | self.notes_mark = loaded.get('notes-mark', None)
191 | self.marks_version = loaded.get('marks-version', 1)
192 | else:
193 | self.tips = {}
194 | self.revisions_to_marks = {}
195 | self.marks_to_revisions = {}
196 | self.last_mark = 0
197 | self.notes_mark = None
198 | self.marks_version = 3
199 |
200 | def store(self):
201 | '''Save marks to the storage file.'''
202 | with self.storage_path.open('w') as file:
203 | file.write(
204 | json.dumps({
205 | 'tips': self.tips,
206 | 'revisions_to_marks': self.revisions_to_marks,
207 | 'last-mark': self.last_mark,
208 | 'notes-mark': self.notes_mark,
209 | 'marks-version': self.marks_version,
210 | }).decode('UTF-8')
211 | )
212 |
213 | def upgrade_marks(self, hgremote):
214 | if self.marks_version == 1: # Convert from integer reversions to hgsha1
215 | log("Upgrading marks-hg from hg sequence number to SHA1", "WARNING")
216 | self.marks_to_revisions = dict(
217 | (mark, hghex(hgremote.repo.changelog.node(int(rev)))) for mark, rev in self.marks_to_revisions.iteritems())
218 | self.revisions_to_marks = dict(
219 | (hghex(hgremote.repo.changelog.node(int(rev))), mark) for rev, mark in self.revisions_to_marks.iteritems())
220 | self.marks_version = 2
221 | log("Upgrade complete", "WARNING")
222 | if self.marks_version == 2: # Convert tips to use gitify refs as keys
223 | log("Upgrading marks-hg tips", "WARNING")
224 | self.tips = dict(
225 | ("%s/%s" % (hgremote.prefix, reftype_and_name), tip) for reftype_and_name, tip in self.tips.iteritems())
226 | self.marks_version = 3
227 | log("Upgrade complete", "WARNING")
228 |
229 | def mark_to_revision(self, mark):
230 | return hgbin(self.marks_to_revisions[mark])
231 |
232 | def revision_to_mark(self, revision):
233 | return self.revisions_to_marks[hghex(revision)]
234 |
235 | def get_mark(self, revision):
236 | self.last_mark += 1
237 | self.revisions_to_marks[hghex(revision)] = self.last_mark
238 | return self.last_mark
239 |
240 | def new_mark(self, revision, mark):
241 | self.revisions_to_marks[hghex(revision)] = mark
242 | self.marks_to_revisions[mark] = hghex(revision)
243 | self.last_mark = mark
244 |
245 | def is_marked(self, revision):
246 | return hghex(revision) in self.revisions_to_marks
247 |
248 | def new_notes_mark(self):
249 | self.last_mark += 1
250 | self.notes_mark = self.last_mark
251 | return self.notes_mark
252 |
253 |
254 | class GitMarks(object):
255 | '''Maps integer marks to git commit hashes.'''
256 |
257 | def __init__(self, storage_path):
258 | ''':param storage_path: The file that marks are stored in between calls.'''
259 | self.storage_path = storage_path
260 | self.load()
261 |
262 | def load(self):
263 | '''Load the marks from the storage file.'''
264 | # TODO: Combine remove_processed_git_marks with this, perhaps by using
265 | # an OrderedDict to write entires back out in the order they came in.
266 | self.marks_to_hashes = {}
267 | if self.storage_path.exists():
268 | with self.storage_path.open() as file:
269 | for line in file:
270 | if not line.startswith(':'):
271 | die("invalid line in marks-git: " + line)
272 | mark, sha1 = line[1:].split()
273 | self.marks_to_hashes[mark] = sha1
274 |
275 | def has_mark(self, mark):
276 | return str(mark) in self.marks_to_hashes
277 |
278 | def mark_to_hash(self, mark):
279 | return self.marks_to_hashes[str(mark)]
280 |
281 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright 2012-2013 Dusty Phillips
2 |
3 | # This file is part of gitifyhg.
4 |
5 | # gitifyhg is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # gitifyhg is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with gitifyhg. If not, see .
17 |
18 |
19 | from setuptools import setup
20 | setup(
21 | name="gitifyhg",
22 | author="Dusty Phillips",
23 | author_email="dusty@buchuki.com",
24 | url="https://github.com/buchuki/gitifyhg",
25 | description="Use git as client for hg repos",
26 | version="0.8.6",
27 | packages=['gitifyhg'],
28 | install_requires=[
29 | 'path.py>=2.5',
30 | 'Mercurial>=2.5, <4.0.2',
31 | ],
32 | entry_points={
33 | 'console_scripts': [
34 | 'git-remote-gitifyhg = gitifyhg.gitifyhg:main',
35 | ],
36 | },
37 | classifiers=[
38 | 'Development Status :: 4 - Beta',
39 | 'Intended Audience :: Developers',
40 | 'Programming Language :: Python :: 2.7',
41 | 'Topic :: Software Development :: Version Control',
42 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)'
43 | ]
44 | )
45 |
--------------------------------------------------------------------------------
/test/Makefile:
--------------------------------------------------------------------------------
1 | # Run tests
2 | #
3 | # Copyright (c) 2011-2012 Mathias Lafeldt
4 | # Copyright (c) 2005-2012 Git project
5 | # Copyright (c) 2005-2012 Junio C Hamano
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 2 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see http://www.gnu.org/licenses/ .
19 |
20 | SHELL_PATH ?= $(SHELL)
21 | SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
22 | RM ?= rm -f
23 | PROVE ?= prove
24 | AGGREGATE_SCRIPT ?= aggregate-results.sh
25 | DEFAULT_TEST_TARGET ?= test
26 |
27 | T = $(wildcard *.t)
28 |
29 | all: $(DEFAULT_TEST_TARGET)
30 |
31 | test: pre-clean
32 | $(MAKE) aggregate-results-and-cleanup
33 |
34 | prove: pre-clean
35 | @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(PROVE_OPTS) $(T) :: $(TEST_OPTS)
36 | $(MAKE) clean-except-prove-cache
37 |
38 | $(T):
39 | @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(TEST_OPTS)
40 |
41 | pre-clean:
42 | $(RM) -r test-results
43 |
44 | clean-except-prove-cache:
45 | $(RM) -r 'trash directory'.* test-results
46 |
47 | clean: clean-except-prove-cache
48 | $(RM) .prove
49 |
50 | aggregate-results-and-cleanup: $(T)
51 | $(MAKE) aggregate-results
52 | $(MAKE) clean
53 |
54 | aggregate-results:
55 | for f in test-results/*.counts; do \
56 | echo "$$f"; \
57 | done | '$(SHELL_PATH_SQ)' '$(AGGREGATE_SCRIPT)'
58 |
59 | .PHONY: all test prove $(T) pre-clean clean
60 | .PHONY: aggregate-results-and-cleanup aggregate-results
61 |
--------------------------------------------------------------------------------
/test/aggregate-results.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2008-2012 Git project
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 2 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see http://www.gnu.org/licenses/ .
17 |
18 | failed_tests=
19 | fixed=0
20 | success=0
21 | failed=0
22 | broken=0
23 | total=0
24 |
25 | while read file; do
26 | while read type value; do
27 | case $type in
28 | '')
29 | continue ;;
30 | fixed)
31 | fixed=$(($fixed + $value)) ;;
32 | success)
33 | success=$(($success + $value)) ;;
34 | failed)
35 | failed=$(($failed + $value))
36 | if test $value != 0; then
37 | test_name=$(expr "$file" : 'test-results/\(.*\)\.[0-9]*\.counts')
38 | failed_tests="$failed_tests $test_name"
39 | fi
40 | ;;
41 | broken)
42 | broken=$(($broken + $value)) ;;
43 | total)
44 | total=$(($total + $value)) ;;
45 | esac
46 | done <"$file"
47 | done
48 |
49 | if test -n "$failed_tests"; then
50 | printf "\nfailed test(s):$failed_tests\n\n"
51 | fi
52 |
53 | printf "%-8s%d\n" fixed $fixed
54 | printf "%-8s%d\n" success $success
55 | printf "%-8s%d\n" failed $failed
56 | printf "%-8s%d\n" broken $broken
57 | printf "%-8s%d\n" total $total
58 |
--------------------------------------------------------------------------------
/test/sharness.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Copyright (c) 2011-2012 Mathias Lafeldt
4 | # Copyright (c) 2005-2012 Git project
5 | # Copyright (c) 2005-2012 Junio C Hamano
6 | #
7 | # This program is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 2 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # This program is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with this program. If not, see http://www.gnu.org/licenses/ .
19 |
20 | # Public: Current version of Sharness.
21 | SHARNESS_VERSION="0.3.0"
22 | export SHARNESS_VERSION
23 |
24 | # Public: The file extension for tests. By default, it is set to "t".
25 | : ${SHARNESS_TEST_EXTENSION:=t}
26 | export SHARNESS_TEST_EXTENSION
27 |
28 | # Keep the original TERM for say_color
29 | ORIGINAL_TERM=$TERM
30 |
31 | # For repeatability, reset the environment to a known state.
32 | LANG=C
33 | LC_ALL=C
34 | PAGER=cat
35 | TZ=UTC
36 | TERM=dumb
37 | EDITOR=:
38 | export LANG LC_ALL PAGER TZ TERM EDITOR
39 | unset VISUAL CDPATH GREP_OPTIONS
40 |
41 | # Line feed
42 | LF='
43 | '
44 |
45 | [ "x$ORIGINAL_TERM" != "xdumb" ] && (
46 | TERM=$ORIGINAL_TERM &&
47 | export TERM &&
48 | [ -t 1 ] &&
49 | tput bold >/dev/null 2>&1 &&
50 | tput setaf 1 >/dev/null 2>&1 &&
51 | tput sgr0 >/dev/null 2>&1
52 | ) &&
53 | color=t
54 |
55 | while test "$#" -ne 0; do
56 | case "$1" in
57 | -d|--d|--de|--deb|--debu|--debug)
58 | debug=t; shift ;;
59 | -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
60 | immediate=t; shift ;;
61 | -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests)
62 | TEST_LONG=t; export TEST_LONG; shift ;;
63 | -h|--h|--he|--hel|--help)
64 | help=t; shift ;;
65 | -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
66 | verbose=t; shift ;;
67 | -q|--q|--qu|--qui|--quie|--quiet)
68 | # Ignore --quiet under a TAP::Harness. Saying how many tests
69 | # passed without the ok/not ok details is always an error.
70 | test -z "$HARNESS_ACTIVE" && quiet=t; shift ;;
71 | --no-color)
72 | color=; shift ;;
73 | --root=*)
74 | root=$(expr "z$1" : 'z[^=]*=\(.*\)')
75 | shift ;;
76 | *)
77 | echo "error: unknown test option '$1'" >&2; exit 1 ;;
78 | esac
79 | done
80 |
81 | if test -n "$color"; then
82 | say_color() {
83 | (
84 | TERM=$ORIGINAL_TERM
85 | export TERM
86 | case "$1" in
87 | error)
88 | tput bold; tput setaf 1;; # bold red
89 | skip)
90 | tput setaf 4;; # blue
91 | warn)
92 | tput setaf 3;; # brown/yellow
93 | pass)
94 | tput setaf 2;; # green
95 | info)
96 | tput setaf 6;; # cyan
97 | *)
98 | test -n "$quiet" && return;;
99 | esac
100 | shift
101 | printf "%s" "$*"
102 | tput sgr0
103 | echo
104 | )
105 | }
106 | else
107 | say_color() {
108 | test -z "$1" && test -n "$quiet" && return
109 | shift
110 | printf "%s\n" "$*"
111 | }
112 | fi
113 |
114 | error() {
115 | say_color error "error: $*"
116 | EXIT_OK=t
117 | exit 1
118 | }
119 |
120 | say() {
121 | say_color info "$*"
122 | }
123 |
124 | test -n "$test_description" || error "Test script did not set test_description."
125 |
126 | if test "$help" = "t"; then
127 | echo "$test_description"
128 | exit 0
129 | fi
130 |
131 | exec 5>&1
132 | exec 6<&0
133 | if test "$verbose" = "t"; then
134 | exec 4>&2 3>&1
135 | else
136 | exec 4>/dev/null 3>/dev/null
137 | fi
138 |
139 | test_failure=0
140 | test_count=0
141 | test_fixed=0
142 | test_broken=0
143 | test_success=0
144 |
145 | die() {
146 | code=$?
147 | if test -n "$EXIT_OK"; then
148 | exit $code
149 | else
150 | echo >&5 "FATAL: Unexpected exit with code $code"
151 | exit 1
152 | fi
153 | }
154 |
155 | EXIT_OK=
156 | trap 'die' EXIT
157 |
158 | # Public: Define that a test prerequisite is available.
159 | #
160 | # The prerequisite can later be checked explicitly using test_have_prereq or
161 | # implicitly by specifying the prerequisite name in calls to test_expect_success
162 | # or test_expect_failure.
163 | #
164 | # $1 - Name of prerequiste (a simple word, in all capital letters by convention)
165 | #
166 | # Examples
167 | #
168 | # # Set PYTHON prerequisite if interpreter is available.
169 | # command -v python >/dev/null && test_set_prereq PYTHON
170 | #
171 | # # Set prerequisite depending on some variable.
172 | # test -z "$NO_GETTEXT" && test_set_prereq GETTEXT
173 | #
174 | # Returns nothing.
175 | test_set_prereq() {
176 | satisfied_prereq="$satisfied_prereq$1 "
177 | }
178 | satisfied_prereq=" "
179 |
180 | # Public: Check if one or more test prerequisites are defined.
181 | #
182 | # The prerequisites must have previously been set with test_set_prereq.
183 | # The most common use of this is to skip all the tests if some essential
184 | # prerequisite is missing.
185 | #
186 | # $1 - Comma-separated list of test prerequisites.
187 | #
188 | # Examples
189 | #
190 | # # Skip all remaining tests if prerequisite is not set.
191 | # if ! test_have_prereq PERL; then
192 | # skip_all='skipping perl interface tests, perl not available'
193 | # test_done
194 | # fi
195 | #
196 | # Returns 0 if all prerequisites are defined or 1 otherwise.
197 | test_have_prereq() {
198 | # prerequisites can be concatenated with ','
199 | save_IFS=$IFS
200 | IFS=,
201 | set -- $*
202 | IFS=$save_IFS
203 |
204 | total_prereq=0
205 | ok_prereq=0
206 | missing_prereq=
207 |
208 | for prerequisite; do
209 | case "$prerequisite" in
210 | !*)
211 | negative_prereq=t
212 | prerequisite=${prerequisite#!}
213 | ;;
214 | *)
215 | negative_prereq=
216 | esac
217 |
218 | total_prereq=$(($total_prereq + 1))
219 | case "$satisfied_prereq" in
220 | *" $prerequisite "*)
221 | satisfied_this_prereq=t
222 | ;;
223 | *)
224 | satisfied_this_prereq=
225 | esac
226 |
227 | case "$satisfied_this_prereq,$negative_prereq" in
228 | t,|,t)
229 | ok_prereq=$(($ok_prereq + 1))
230 | ;;
231 | *)
232 | # Keep a list of missing prerequisites; restore
233 | # the negative marker if necessary.
234 | prerequisite=${negative_prereq:+!}$prerequisite
235 | if test -z "$missing_prereq"; then
236 | missing_prereq=$prerequisite
237 | else
238 | missing_prereq="$prerequisite,$missing_prereq"
239 | fi
240 | esac
241 | done
242 |
243 | test $total_prereq = $ok_prereq
244 | }
245 |
246 | # You are not expected to call test_ok_ and test_failure_ directly, use
247 | # the text_expect_* functions instead.
248 |
249 | test_ok_() {
250 | test_success=$(($test_success + 1))
251 | say_color "" "ok $test_count - $@"
252 | }
253 |
254 | test_failure_() {
255 | test_failure=$(($test_failure + 1))
256 | say_color error "not ok $test_count - $1"
257 | shift
258 | echo "$@" | sed -e 's/^/# /'
259 | test "$immediate" = "" || { EXIT_OK=t; exit 1; }
260 | }
261 |
262 | test_known_broken_ok_() {
263 | test_fixed=$(($test_fixed + 1))
264 | say_color error "ok $test_count - $@ # TODO known breakage vanished"
265 | }
266 |
267 | test_known_broken_failure_() {
268 | test_broken=$(($test_broken + 1))
269 | say_color warn "not ok $test_count - $@ # TODO known breakage"
270 | }
271 |
272 | # Public: Execute commands in debug mode.
273 | #
274 | # Takes a single argument and evaluates it only when the test script is started
275 | # with --debug. This is primarily meant for use during the development of test
276 | # scripts.
277 | #
278 | # $1 - Commands to be executed.
279 | #
280 | # Examples
281 | #
282 | # test_debug "cat some_log_file"
283 | #
284 | # Returns the exit code of the last command executed in debug mode or 0
285 | # otherwise.
286 | test_debug() {
287 | test "$debug" = "" || eval "$1"
288 | }
289 |
290 | test_eval_() {
291 | # This is a separate function because some tests use
292 | # "return" to end a test_expect_success block early.
293 | eval &3 2>&4 "$*"
294 | }
295 |
296 | test_run_() {
297 | test_cleanup=:
298 | expecting_failure=$2
299 | test_eval_ "$1"
300 | eval_ret=$?
301 |
302 | if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure"; then
303 | test_eval_ "$test_cleanup"
304 | fi
305 | if test "$verbose" = "t" && test -n "$HARNESS_ACTIVE"; then
306 | echo ""
307 | fi
308 | return "$eval_ret"
309 | }
310 |
311 | test_skip_() {
312 | test_count=$(($test_count + 1))
313 | to_skip=
314 | for skp in $SKIP_TESTS; do
315 | case $this_test.$test_count in
316 | $skp)
317 | to_skip=t
318 | break
319 | esac
320 | done
321 | if test -z "$to_skip" && test -n "$test_prereq" && ! test_have_prereq "$test_prereq"; then
322 | to_skip=t
323 | fi
324 | case "$to_skip" in
325 | t)
326 | of_prereq=
327 | if test "$missing_prereq" != "$test_prereq"; then
328 | of_prereq=" of $test_prereq"
329 | fi
330 |
331 | say_color skip >&3 "skipping test: $@"
332 | say_color skip "ok $test_count # skip $1 (missing $missing_prereq${of_prereq})"
333 | : true
334 | ;;
335 | *)
336 | false
337 | ;;
338 | esac
339 | }
340 |
341 | # Public: Run test commands and expect them to succeed.
342 | #
343 | # When the test passed, an "ok" message is printed and the number of successful
344 | # tests is incremented. When it failed, a "not ok" message is printed and the
345 | # number of failed tests is incremented.
346 | #
347 | # With --immediate, exit test immediately upon the first failed test.
348 | #
349 | # Usually takes two arguments:
350 | # $1 - Test description
351 | # $2 - Commands to be executed.
352 | #
353 | # With three arguments, the first will be taken to be a prerequisite:
354 | # $1 - Comma-separated list of test prerequisites. The test will be skipped if
355 | # not all of the given prerequisites are set. To negate a prerequisite,
356 | # put a "!" in front of it.
357 | # $2 - Test description
358 | # $3 - Commands to be executed.
359 | #
360 | # Examples
361 | #
362 | # test_expect_success \
363 | # 'git-write-tree should be able to write an empty tree.' \
364 | # 'tree=$(git-write-tree)'
365 | #
366 | # # Test depending on one prerequisite.
367 | # test_expect_success TTY 'git --paginate rev-list uses a pager' \
368 | # ' ... '
369 | #
370 | # # Multiple prerequisites are separated by a comma.
371 | # test_expect_success PERL,PYTHON 'yo dawg' \
372 | # ' test $(perl -E 'print eval "1 +" . qx[python -c "print 2"]') == "4" '
373 | #
374 | # Returns nothing.
375 | test_expect_success() {
376 | test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
377 | test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_success"
378 | export test_prereq
379 | if ! test_skip_ "$@"; then
380 | say >&3 "expecting success: $2"
381 | if test_run_ "$2"; then
382 | test_ok_ "$1"
383 | else
384 | test_failure_ "$@"
385 | fi
386 | fi
387 | echo >&3 ""
388 | }
389 |
390 | # Public: Run test commands and expect them to fail. Used to demonstrate a known
391 | # breakage.
392 | #
393 | # This is NOT the opposite of test_expect_success, but rather used to mark a
394 | # test that demonstrates a known breakage.
395 | #
396 | # When the test passed, an "ok" message is printed and the number of fixed tests
397 | # is incremented. When it failed, a "not ok" message is printed and the number
398 | # of tests still broken is incremented.
399 | #
400 | # Failures from these tests won't cause --immediate to stop.
401 | #
402 | # Usually takes two arguments:
403 | # $1 - Test description
404 | # $2 - Commands to be executed.
405 | #
406 | # With three arguments, the first will be taken to be a prerequisite:
407 | # $1 - Comma-separated list of test prerequisites. The test will be skipped if
408 | # not all of the given prerequisites are set. To negate a prerequisite,
409 | # put a "!" in front of it.
410 | # $2 - Test description
411 | # $3 - Commands to be executed.
412 | #
413 | # Returns nothing.
414 | test_expect_failure() {
415 | test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq=
416 | test "$#" = 2 || error "bug in the test script: not 2 or 3 parameters to test_expect_failure"
417 | export test_prereq
418 | if ! test_skip_ "$@"; then
419 | say >&3 "checking known breakage: $2"
420 | if test_run_ "$2" expecting_failure; then
421 | test_known_broken_ok_ "$1"
422 | else
423 | test_known_broken_failure_ "$1"
424 | fi
425 | fi
426 | echo >&3 ""
427 | }
428 |
429 | # Public: Run command and ensure that it fails in a controlled way.
430 | #
431 | # Use it instead of "! ". For example, when dies due to a
432 | # segfault, test_must_fail diagnoses it as an error, while "! " would
433 | # mistakenly be treated as just another expected failure.
434 | #
435 | # This is one of the prefix functions to be used inside test_expect_success or
436 | # test_expect_failure.
437 | #
438 | # $1.. - Command to be executed.
439 | #
440 | # Examples
441 | #
442 | # test_expect_success 'complain and die' '
443 | # do something &&
444 | # do something else &&
445 | # test_must_fail git checkout ../outerspace
446 | # '
447 | #
448 | # Returns 1 if the command succeeded (exit code 0).
449 | # Returns 1 if the command died by signal (exit codes 130-192)
450 | # Returns 1 if the command could not be found (exit code 127).
451 | # Returns 0 otherwise.
452 | test_must_fail() {
453 | "$@"
454 | exit_code=$?
455 | if test $exit_code = 0; then
456 | echo >&2 "test_must_fail: command succeeded: $*"
457 | return 1
458 | elif test $exit_code -gt 129 -a $exit_code -le 192; then
459 | echo >&2 "test_must_fail: died by signal: $*"
460 | return 1
461 | elif test $exit_code = 127; then
462 | echo >&2 "test_must_fail: command not found: $*"
463 | return 1
464 | fi
465 | return 0
466 | }
467 |
468 | # Public: Run command and ensure that it succeeds or fails in a controlled way.
469 | #
470 | # Similar to test_must_fail, but tolerates success too. Use it instead of
471 | # " || :" to catch failures caused by a segfault, for instance.
472 | #
473 | # This is one of the prefix functions to be used inside test_expect_success or
474 | # test_expect_failure.
475 | #
476 | # $1.. - Command to be executed.
477 | #
478 | # Examples
479 | #
480 | # test_expect_success 'some command works without configuration' '
481 | # test_might_fail git config --unset all.configuration &&
482 | # do something
483 | # '
484 | #
485 | # Returns 1 if the command died by signal (exit codes 130-192)
486 | # Returns 1 if the command could not be found (exit code 127).
487 | # Returns 0 otherwise.
488 | test_might_fail() {
489 | "$@"
490 | exit_code=$?
491 | if test $exit_code -gt 129 -a $exit_code -le 192; then
492 | echo >&2 "test_might_fail: died by signal: $*"
493 | return 1
494 | elif test $exit_code = 127; then
495 | echo >&2 "test_might_fail: command not found: $*"
496 | return 1
497 | fi
498 | return 0
499 | }
500 |
501 | # Public: Run command and ensure it exits with a given exit code.
502 | #
503 | # This is one of the prefix functions to be used inside test_expect_success or
504 | # test_expect_failure.
505 | #
506 | # $1 - Expected exit code.
507 | # $2.. - Command to be executed.
508 | #
509 | # Examples
510 | #
511 | # test_expect_success 'Merge with d/f conflicts' '
512 | # test_expect_code 1 git merge "merge msg" B master
513 | # '
514 | #
515 | # Returns 0 if the expected exit code is returned or 1 otherwise.
516 | test_expect_code() {
517 | want_code=$1
518 | shift
519 | "$@"
520 | exit_code=$?
521 | if test $exit_code = $want_code; then
522 | return 0
523 | fi
524 |
525 | echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
526 | return 1
527 | }
528 |
529 | # Public: Compare two files to see if expected output matches actual output.
530 | #
531 | # The TEST_CMP variable defines the command used for the comparision; it
532 | # defaults to "diff -u". Only when the test script was started with --verbose,
533 | # will the command's output, the diff, be printed to the standard output.
534 | #
535 | # This is one of the prefix functions to be used inside test_expect_success or
536 | # test_expect_failure.
537 | #
538 | # $1 - Path to file with expected output.
539 | # $2 - Path to file with actual output.
540 | #
541 | # Examples
542 | #
543 | # test_expect_success 'foo works' '
544 | # echo expected >expected &&
545 | # foo >actual &&
546 | # test_cmp expected actual
547 | # '
548 | #
549 | # Returns the exit code of the command set by TEST_CMP.
550 | test_cmp() {
551 | ${TEST_CMP:-diff -u} "$@"
552 | }
553 |
554 | # Public: Schedule cleanup commands to be run unconditionally at the end of a
555 | # test.
556 | #
557 | # If some cleanup command fails, the test will not pass. With --immediate, no
558 | # cleanup is done to help diagnose what went wrong.
559 | #
560 | # This is one of the prefix functions to be used inside test_expect_success or
561 | # test_expect_failure.
562 | #
563 | # $1.. - Commands to prepend to the list of cleanup commands.
564 | #
565 | # Examples
566 | #
567 | # test_expect_success 'test core.capslock' '
568 | # git config core.capslock true &&
569 | # test_when_finished "git config --unset core.capslock" &&
570 | # do_something
571 | # '
572 | #
573 | # Returns the exit code of the last cleanup command executed.
574 | test_when_finished() {
575 | test_cleanup="{ $*
576 | } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup"
577 | }
578 |
579 | # Public: Summarize test results and exit with an appropriate error code.
580 | #
581 | # Must be called at the end of each test script.
582 | #
583 | # Can also be used to stop tests early and skip all remaining tests. For this,
584 | # set skip_all to a string explaining why the tests were skipped before calling
585 | # test_done.
586 | #
587 | # Examples
588 | #
589 | # # Each test script must call test_done at the end.
590 | # test_done
591 | #
592 | # # Skip all remaining tests if prerequisite is not set.
593 | # if ! test_have_prereq PERL; then
594 | # skip_all='skipping perl interface tests, perl not available'
595 | # test_done
596 | # fi
597 | #
598 | # Returns 0 if all tests passed or 1 if there was a failure.
599 | test_done() {
600 | EXIT_OK=t
601 |
602 | if test -z "$HARNESS_ACTIVE"; then
603 | test_results_dir="$SHARNESS_TEST_DIRECTORY/test-results"
604 | mkdir -p "$test_results_dir"
605 | test_results_path="$test_results_dir/${SHARNESS_TEST_FILE%.$SHARNESS_TEST_EXTENSION}.$$.counts"
606 |
607 | cat >>"$test_results_path" <<-EOF
608 | total $test_count
609 | success $test_success
610 | fixed $test_fixed
611 | broken $test_broken
612 | failed $test_failure
613 |
614 | EOF
615 | fi
616 |
617 | if test "$test_fixed" != 0; then
618 | say_color error "# $test_fixed known breakage(s) vanished; please update test(s)"
619 | fi
620 | if test "$test_broken" != 0; then
621 | say_color warn "# still have $test_broken known breakage(s)"
622 | fi
623 | if test "$test_broken" != 0 || test "$test_fixed" != 0; then
624 | test_remaining=$(( $test_count - $test_broken - $test_fixed ))
625 | msg="remaining $test_remaining test(s)"
626 | else
627 | test_remaining=$test_count
628 | msg="$test_count test(s)"
629 | fi
630 |
631 | case "$test_failure" in
632 | 0)
633 | # Maybe print SKIP message
634 | if test -n "$skip_all" && test $test_count -gt 0; then
635 | error "Can't use skip_all after running some tests"
636 | fi
637 | [ -z "$skip_all" ] || skip_all=" # SKIP $skip_all"
638 |
639 | if test $test_remaining -gt 0; then
640 | say_color pass "# passed all $msg"
641 | fi
642 | say "1..$test_count$skip_all"
643 |
644 | test -d "$remove_trash" &&
645 | cd "$(dirname "$remove_trash")" &&
646 | rm -rf "$(basename "$remove_trash")"
647 |
648 | exit 0 ;;
649 |
650 | *)
651 | say_color error "# failed $test_failure among $msg"
652 | say "1..$test_count"
653 |
654 | exit 1 ;;
655 |
656 | esac
657 | }
658 |
659 | # Public: Root directory containing tests. Tests can override this variable,
660 | # e.g. for testing Sharness itself.
661 | : ${SHARNESS_TEST_DIRECTORY:=$(pwd)}
662 | export SHARNESS_TEST_DIRECTORY
663 |
664 | # Public: Build directory that will be added to PATH. By default, it is set to
665 | # the parent directory of SHARNESS_TEST_DIRECTORY.
666 | : ${SHARNESS_BUILD_DIRECTORY:="$SHARNESS_TEST_DIRECTORY/.."}
667 | PATH="$SHARNESS_BUILD_DIRECTORY:$PATH"
668 | export PATH SHARNESS_BUILD_DIRECTORY
669 |
670 | # Public: Path to test script currently executed.
671 | SHARNESS_TEST_FILE="$0"
672 | export SHARNESS_TEST_FILE
673 |
674 | # Prepare test area.
675 | test_dir="trash directory.$(basename "$SHARNESS_TEST_FILE" ".$SHARNESS_TEST_EXTENSION")"
676 | test -n "$root" && test_dir="$root/$test_dir"
677 | case "$test_dir" in
678 | /*) SHARNESS_TRASH_DIRECTORY="$test_dir" ;;
679 | *) SHARNESS_TRASH_DIRECTORY="$SHARNESS_TEST_DIRECTORY/$test_dir" ;;
680 | esac
681 | test "$debug" = "t" || remove_trash="$SHARNESS_TRASH_DIRECTORY"
682 | rm -rf "$test_dir" || {
683 | EXIT_OK=t
684 | echo >&5 "FATAL: Cannot prepare test area"
685 | exit 1
686 | }
687 |
688 | # Public: Empty trash directory, the test area, provided for each test. The HOME
689 | # variable is set to that directory too.
690 | export SHARNESS_TRASH_DIRECTORY
691 |
692 | HOME="$SHARNESS_TRASH_DIRECTORY"
693 | export HOME
694 |
695 | mkdir -p "$test_dir" || exit 1
696 | # Use -P to resolve symlinks in our working directory so that the cwd
697 | # in subprocesses like git equals our $PWD (for pathname comparisons).
698 | cd -P "$test_dir" || exit 1
699 |
700 | this_test=${SHARNESS_TEST_FILE##*/}
701 | this_test=${this_test%.$SHARNESS_TEST_EXTENSION}
702 | for skp in $SKIP_TESTS; do
703 | case "$this_test" in
704 | $skp)
705 | say_color info >&3 "skipping test $this_test altogether"
706 | skip_all="skip all tests in $this_test"
707 | test_done
708 | esac
709 | done
710 |
711 | # vi: set ts=4 sw=4 noet :
712 |
--------------------------------------------------------------------------------
/test/test-lib.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | . ./sharness.sh
4 |
5 | export GIT_AUTHOR_EMAIL=git.user@example.com
6 | export GIT_AUTHOR_NAME='Git User'
7 | export GIT_USER="$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>"
8 | export HG_USER="Hg User "
9 | export DEBUG_GITIFYHG=$debug
10 | export GIT_PAGER=cat
11 | export HGRCPATH="$HOME/.hgrc"
12 | export NL='
13 | '
14 |
15 | make_hg_repo() {
16 | hg init hg_repo &&
17 | cd hg_repo &&
18 | echo 'a\n' >> test_file &&
19 | hg add test_file &&
20 | hg commit --message="a" --user="$HG_USER"
21 | }
22 |
23 | clone_repo() {
24 | cd .. &&
25 | test_expect_code 0 git clone "testgitifyhg::hg_repo" git_clone &&
26 | cd git_clone &&
27 | git config user.email $GIT_AUTHOR_EMAIL &&
28 | git config user.name "$GIT_USER"
29 | }
30 |
31 | make_cloned_repo() {
32 | make_hg_repo &&
33 | clone_repo &&
34 | cd ../hg_repo
35 | }
36 |
37 | make_hg_commit() {
38 | if test $# -eq 3 ; then
39 | user=$3
40 | else
41 | user=$HG_USER
42 | fi
43 | echo "$1" >> $2 &&
44 | hg add $2 &&
45 | hg commit -m "$1" --user="$user"
46 | }
47 |
48 | make_git_commit() {
49 | echo "$1" >> "$2" &&
50 | git add "$2" &&
51 | git commit -m "$1"
52 | }
53 |
54 | assert_git_messages() {
55 | if test $# -eq 2 ; then
56 | test "`git log --pretty=format:%B $2`" = "$1"
57 | else
58 | test "`git log --pretty=format:%B`" = "$1"
59 | fi
60 | }
61 |
62 | assert_hg_messages() {
63 | if test $# -eq 2 ; then
64 | test "`hg log --template=\"{desc}\n\" -r $2`" = "$1"
65 | else
66 | test "`hg log --template=\"{desc}\n\"`" = "$1"
67 | fi
68 | }
69 |
70 | assert_hg_author() {
71 | if test $# -eq 2 ; then
72 | rev=$2
73 | else
74 | rev=tip
75 | fi
76 | test "`hg log --template='{author}' --rev=$rev`" = "$1"
77 | }
78 |
79 | assert_git_author() {
80 | if test $# -eq 2 ; then
81 | ref=$2
82 | else
83 | ref=HEAD
84 | fi
85 | test "`git show -s --format='%an <%ae>' $ref`" = "$1"
86 | }
87 |
88 | assert_git_count() {
89 | if test $# -eq 2 ; then
90 | ref=$2
91 | else
92 | ref=HEAD
93 | fi
94 | test `git rev-list $ref --count` -eq $1
95 | }
96 |
97 | assert_hg_count() {
98 | if test $# -eq 2 ; then
99 | rev=$2
100 | else
101 | rev=tip
102 | fi
103 |
104 | test "`hg log -q -r 0:$rev | wc -l`" -eq "$1"
105 | }
106 |
107 | assert_git_notes() {
108 | git notes --ref=hg merge $(basename $(ls .git/refs/notes/hg-*)) &&
109 | git log --pretty="format:%N" --notes='hg' | grep -v '^$'
110 | echo $1
111 | test "`git log --pretty="format:%N" --notes='hg' | grep -v '^$'`" = "$1"
112 | }
--------------------------------------------------------------------------------
/test/test_anonymous_branches.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg clone, pull, and push with spaces'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_failure 'anonymous branches dont work' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_hg_repo &&
21 | make_hg_commit b test_file &&
22 | hg update -r 0 &&
23 | make_hg_commit c test_file &&
24 | cd .. &&
25 |
26 | git clone testgitifyhg::hg_repo git_clone 2>&1 | grep "more than one head" &&
27 |
28 | # TODO: "more than one head" is the correct response for now, but a more
29 | # appropriate result would be to clone the extra commits, perhaps naming
30 | # the branch anonymous/ or something. assert False to mark an expected
31 | # failure.
32 |
33 | false &&
34 |
35 | cd ..
36 | '
37 |
38 | test_expect_failure 'anonymous branch from named branch' '
39 | test_when_finished "rm -rf hg_repo git_clone" &&
40 | make_hg_repo &&
41 | hg branch featurebranch &&
42 | make_hg_commit b test_file &&
43 | make_hg_commit c test_file &&
44 | hg update -r 1 &&
45 | make_hg_commit d test_file &&
46 | hg update default &&
47 | make_hg_commit e test_file &&
48 |
49 | cd .. &&
50 | git clone testgitifyhg::hg_repo git_clone 2>&1 | grep "more than one head" &&
51 | cd git_clone &&
52 | test "`git branch -r`" = " origin/HEAD -> origin/master
53 | origin/branches/featurebranch
54 | origin/master" &&
55 |
56 | # TODO: Same issue as above test.
57 |
58 | false &&
59 |
60 | cd ..
61 | '
62 |
63 | test_expect_failure 'pull from anonymous branch' '
64 | test_when_finished "rm -rf hg_repo git_clone" &&
65 |
66 | make_hg_repo &&
67 | make_hg_commit b test_file &&
68 |
69 | clone_repo &&
70 | cd ../hg_repo &&
71 | make_hg_commit c test_file &&
72 | hg update --rev=-2 &&
73 | make_hg_commit c2 test_file &&
74 |
75 | cd ../git_clone &&
76 | git pull &&
77 |
78 | # TODO: pulling anonymous branches are currently pruned, need to test
79 | # and assert that they are actually dealt with properly.
80 | false &&
81 |
82 | cd ..
83 | '
84 |
85 | test_done
86 |
--------------------------------------------------------------------------------
/test/test_author.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg authors'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'push email is correct' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_hg_repo &&
21 | clone_repo &&
22 | make_git_commit b test_file &&
23 | git push &&
24 | cd ../hg_repo &&
25 | hg update &&
26 | assert_hg_author "$GIT_USER" &&
27 | make_hg_commit "c" test_file &&
28 | assert_hg_author "$HG_USER" &&
29 | cd ../git_clone &&
30 | git pull &&
31 | assert_git_author "$GIT_USER" "HEAD^" &&
32 | assert_git_author "$HG_USER" &&
33 |
34 |
35 | cd ..
36 | '
37 |
38 | test_expect_success 'author all good' '
39 | test_when_finished "rm -rf hg_repo git_clone" &&
40 |
41 | make_hg_repo &&
42 | make_hg_commit b test_file "all is good " &&
43 |
44 | clone_repo &&
45 | git show -s --format="%an <%ae>"
46 | assert_git_author "all is good " &&
47 |
48 | cd ..
49 | '
50 |
51 | test_expect_success 'author no email' '
52 | test_when_finished "rm -rf hg_repo git_clone" &&
53 |
54 | make_hg_repo &&
55 | make_hg_commit b test_file "no email supplied" &&
56 |
57 | clone_repo &&
58 | assert_git_author "no email supplied <>" &&
59 |
60 | cd ..
61 | '
62 |
63 | test_expect_success 'author only email' '
64 | test_when_finished "rm -rf hg_repo git_clone" &&
65 |
66 | make_hg_repo &&
67 | make_hg_commit b test_file "" &&
68 |
69 | clone_repo &&
70 | assert_git_author "Unknown " &&
71 |
72 | cd ..
73 | '
74 |
75 | test_expect_success 'author not quoted only email' '
76 | test_when_finished "rm -rf hg_repo git_clone" &&
77 |
78 | make_hg_repo &&
79 | make_hg_commit b test_file "email@example.com" &&
80 |
81 | clone_repo &&
82 | assert_git_author "Unknown " &&
83 |
84 | cd ..
85 | '
86 |
87 | test_expect_success 'author no spaces before email' '
88 | test_when_finished "rm -rf hg_repo git_clone" &&
89 |
90 | make_hg_repo &&
91 | make_hg_commit b test_file "no space before email" &&
92 |
93 | clone_repo &&
94 | assert_git_author "no space before email " &&
95 |
96 | cd ..
97 | '
98 |
99 | # See #22
100 | test_expect_success 'author no email quoting' '
101 | test_when_finished "rm -rf hg_repo git_clone" &&
102 |
103 | make_hg_repo &&
104 | make_hg_commit b test_file "no email quoting email@example.com" &&
105 |
106 | clone_repo &&
107 | assert_git_author "no email quoting " &&
108 |
109 | cd ..
110 | '
111 |
112 | # See #22
113 | test_expect_success 'author missing end quote' '
114 | test_when_finished "rm -rf hg_repo git_clone" &&
115 |
116 | make_hg_repo &&
117 | make_hg_commit b test_file "missing end quote " &&
121 |
122 | cd ..
123 | '
124 |
125 | test_expect_success 'author obfuscated email' '
126 | test_when_finished "rm -rf hg_repo git_clone" &&
127 |
128 | make_hg_repo &&
129 | make_hg_commit b test_file "Author " &&
130 |
131 | clone_repo &&
132 | assert_git_author "Author " &&
133 |
134 | cd ..
135 | '
136 |
137 | test_expect_success 'author abuse quotes' '
138 | test_when_finished "rm -rf hg_repo git_clone" &&
139 |
140 | make_hg_repo &&
141 | make_hg_commit b test_file "totally >>> bad <<< quote can be used in hg <><><" &&
142 |
143 | clone_repo &&
144 | assert_git_author "totally " &&
145 |
146 | cd ..
147 | '
148 |
149 |
150 | test_done
--------------------------------------------------------------------------------
/test/test_bookmarks.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg bookmark management'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'clone bookmark' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 | make_hg_repo &&
20 | hg bookmark featurebookmark &&
21 | make_hg_commit b test_file &&
22 |
23 | clone_repo &&
24 |
25 | test "`git branch -r`" = " origin/HEAD -> origin/master
26 | origin/featurebookmark
27 | origin/master" &&
28 |
29 | git checkout origin/featurebookmark &&
30 | assert_git_messages "b${NL}a" &&
31 | git checkout master &&
32 | assert_git_messages "b${NL}a" &&
33 |
34 | cd ..
35 | '
36 |
37 | test_expect_success 'clone divergent bookmarks' '
38 | test_when_finished "rm -rf hg_repo git_clone" &&
39 | make_hg_repo &&
40 | hg bookmark bookmark_one &&
41 | make_hg_commit b test_file &&
42 | hg update -r 0 &&
43 | make_hg_commit c test_file &&
44 | hg bookmark bookmark_two &&
45 | make_hg_commit d test_file &&
46 |
47 | clone_repo &&
48 |
49 | test "`git branch -r`" = " origin/HEAD -> origin/master
50 | origin/bookmark_one
51 | origin/bookmark_two
52 | origin/master" &&
53 |
54 | git checkout origin/bookmark_one &&
55 | assert_git_messages "b${NL}a" &&
56 |
57 | git checkout origin/bookmark_two &&
58 | assert_git_messages "d${NL}c${NL}a" &&
59 |
60 | cd ..
61 | '
62 |
63 | test_expect_success 'clone bookmark not at tip' '
64 | test_when_finished "rm -rf hg_repo git_clone" &&
65 | make_hg_repo &&
66 | make_hg_commit b test_file &&
67 | hg update -r 0 &&
68 | hg bookmark bookmark_one &&
69 | hg update tip &&
70 |
71 | clone_repo &&
72 |
73 | test "`git branch -r`" = " origin/HEAD -> origin/master
74 | origin/bookmark_one
75 | origin/master" &&
76 |
77 | git checkout bookmark_one &&
78 | assert_git_messages "a" &&
79 | git checkout master &&
80 | assert_git_messages "b${NL}a" &&
81 |
82 | cd ..
83 | '
84 |
85 | # See issue #13
86 | test_expect_success 'clone bookmark named master not at tip' '
87 | test_when_finished "rm -rf hg_repo git_clone" &&
88 | make_hg_repo &&
89 | make_hg_commit b test_file &&
90 | hg update -r 0 &&
91 | hg bookmark master &&
92 | hg update tip &&
93 |
94 | clone_repo &&
95 |
96 | cd ..
97 | '
98 |
99 | test_expect_success 'push to bookmark' '
100 | test_when_finished "rm -rf hg_repo git_clone" &&
101 |
102 | make_hg_repo &&
103 | hg bookmark feature &&
104 | make_hg_commit b test_file &&
105 | clone_repo &&
106 | git checkout --track origin/feature &&
107 | make_git_commit c test_file &&
108 | git push &&
109 |
110 | cd ../hg_repo &&
111 | hg update &&
112 |
113 | assert_hg_messages "c${NL}b${NL}a" &&
114 | hg bookmark | grep feature &&
115 | hg update feature &&
116 | test_cmp test_file ../git_clone/test_file &&
117 |
118 | cd ..
119 | '
120 |
121 | test_expect_success 'push multiple bookmarks' '
122 | test_when_finished "rm -rf hg_repo git_clone" &&
123 |
124 | make_hg_repo &&
125 | hg bookmark feature &&
126 | make_hg_commit b test_file &&
127 | hg update --rev 0 &&
128 | hg bookmark feature2 &&
129 | make_hg_commit c test_file &&
130 |
131 | clone_repo &&
132 | git checkout --track origin/feature &&
133 | make_git_commit d test_file &&
134 | git push &&
135 |
136 | cd ../hg_repo &&
137 | assert_hg_messages "d${NL}c${NL}b${NL}a" &&
138 | assert_hg_messages "a${NL}b${NL}d" "0..feature" &&
139 | assert_hg_messages "a${NL}c" "0..feature2" &&
140 |
141 | hg update feature &&
142 | hg bookmark | grep feature &&
143 | test_cmp test_file ../git_clone/test_file &&
144 |
145 | cd ..
146 | '
147 |
148 | test_expect_success 'push new bookmark' '
149 | test_when_finished "rm -rf hg_repo git_clone" &&
150 |
151 | make_hg_repo &&
152 | clone_repo &&
153 | git checkout -b anewbranch &&
154 | make_git_commit b test_file &&
155 | git push --set-upstream origin anewbranch &&
156 |
157 | cd ../hg_repo &&
158 | assert_hg_messages "b${NL}a" &&
159 | hg bookmark | grep anewbranch &&
160 | hg tip | grep anewbranch &&
161 |
162 | cd ..
163 | '
164 |
165 | test_expect_failure 'pull_from_bookmark' '
166 | test_when_finished "rm -rf hg_repo git_clone" &&
167 |
168 | make_hg_repo &&
169 | hg bookmark feature &&
170 | make_hg_commit b test_file &&
171 | hg update -r 0 &&
172 | hg bookmark feature2 &&
173 | make_hg_commit c test_file &&
174 |
175 | clone_repo &&
176 | git checkout origin/feature --track &&
177 | assert_git_messages "b${NL}a" &&
178 |
179 | cd ../hg_repo &&
180 | hg update feature &&
181 | make_hg_commit d test_file &&
182 | hg update feature2 &&
183 | make_hg_commit e test_file &&
184 |
185 | cd ../git_clone &&
186 | git pull origin feature &&
187 | assert_git_messages "d${NL}b${NL}a" &&
188 | git checkout origin/feature2 --track &&
189 | assert_git_messages "c${NL}a" &&
190 |
191 | git pull origin feature2 &&
192 | assert_git_messages "e${NL}c${NL}a" &&
193 |
194 | # TODO: Pulling into a bookmark does not seem to be working. Find the
195 | # problem and fix.
196 |
197 | cd ..
198 | '
199 |
200 | test_done
201 |
--------------------------------------------------------------------------------
/test/test_clone.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg clones'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'basic clone with default branch and two commits' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 | make_hg_repo &&
20 | make_hg_commit b test_file &&
21 | clone_repo &&
22 | test_cmp ../hg_repo/test_file test_file &&
23 | test -d .git &&
24 | assert_git_messages "b${NL}a" &&
25 |
26 | cd ..
27 | '
28 | test_expect_success 'clone linear branch, no multiple parents' '
29 | test_when_finished "rm -rf hg_repo git_clone" &&
30 | make_hg_repo &&
31 | hg branch featurebranch &&
32 | make_hg_commit b test_file &&
33 | clone_repo &&
34 | assert_git_messages "a" &&
35 | test "`git branch -r`" = " origin/HEAD -> origin/master
36 | origin/branches/featurebranch
37 | origin/master" &&
38 |
39 | git checkout branches/featurebranch &&
40 | test_cmp ../hg_repo/test_file test_file &&
41 | assert_git_messages "b${NL}a" &&
42 |
43 | cd ..
44 | '
45 |
46 | test_expect_success 'clone simple divergent branch' '
47 | test_when_finished "rm -rf hg_repo git_clone" &&
48 | make_hg_repo &&
49 | hg branch featurebranch &&
50 | make_hg_commit b test_file &&
51 | hg update default &&
52 | make_hg_commit c c &&
53 | clone_repo &&
54 | assert_git_messages "c${NL}a" &&
55 | git checkout "origin/branches/featurebranch" &&
56 | assert_git_messages "b${NL}a" &&
57 |
58 | cd ..
59 | '
60 |
61 | test_expect_success 'clone merged branch' '
62 | test_when_finished "rm -rf hg_repo git_clone" &&
63 |
64 | make_hg_repo &&
65 | hg branch featurebranch &&
66 | make_hg_commit b test_file &&
67 | hg update default &&
68 | make_hg_commit c c &&
69 | hg merge featurebranch &&
70 | hg commit -m "merge" &&
71 | make_hg_commit d test_file &&
72 |
73 | clone_repo &&
74 |
75 | assert_git_messages "d${NL}merge${NL}c${NL}b${NL}a" &&
76 | git checkout origin/branches/featurebranch &&
77 | assert_git_messages "b${NL}a"
78 |
79 | cd ..
80 | '
81 |
82 | test_expect_success 'clone basic tag' '
83 | test_when_finished "rm -rf hg_repo git_clone" &&
84 |
85 | make_hg_repo &&
86 | make_hg_commit b test_file &&
87 | hg tag "this_is_tagged" &&
88 | make_hg_commit c test_file &&
89 |
90 | clone_repo &&
91 |
92 | test $(git tag) = "this_is_tagged" &&
93 | git checkout this_is_tagged &&
94 | assert_git_messages "b${NL}a" &&
95 |
96 | cd ..
97 | '
98 |
99 | test_expect_success 'clone close branch' '
100 | test_when_finished "rm -rf hg_repo git_clone" &&
101 | test_when_finished "unset GITIFYHG_ALLOW_CLOSED_BRANCHES" &&
102 |
103 | export GITIFYHG_ALLOW_CLOSED_BRANCHES=on &&
104 | make_hg_repo &&
105 | hg branch feature &&
106 | make_hg_commit b b &&
107 | hg update default &&
108 | make_hg_commit c c &&
109 | hg update feature &&
110 | echo d >> b &&
111 | hg commit --close-branch -m "d" &&
112 |
113 | clone_repo &&
114 | test "`git branch -r`" = " origin/HEAD -> origin/master
115 | origin/branches/feature
116 | origin/master" &&
117 | assert_git_messages "c${NL}a" &&
118 | git checkout origin/branches/feature &&
119 | assert_git_messages "d${NL}b${NL}a" &&
120 |
121 | cd ..
122 | '
123 |
124 | test_expect_success 'no implicit clone close branch' '
125 | test_when_finished "rm -rf hg_repo git_clone" &&
126 | echo $GITIFYHG_ALLOW_CLOSED_BRANCHES &&
127 |
128 | make_hg_repo &&
129 | hg branch feature &&
130 | make_hg_commit b b &&
131 | hg update default &&
132 | make_hg_commit c c &&
133 | hg update feature &&
134 | echo d >> b &&
135 | hg commit --close-branch -m "d" &&
136 |
137 | clone_repo &&
138 | git branch -r &&
139 | test "`git branch -r`" = " origin/HEAD -> origin/master
140 | origin/master" &&
141 | assert_git_messages "c${NL}a" &&
142 |
143 | cd ..
144 | '
145 |
146 | test_done
--------------------------------------------------------------------------------
/test/test_clone_file_operations.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test various file operations in gitifyhg clones'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'cloning a removed file works' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_hg_repo &&
21 | make_hg_commit b test_file &&
22 | echo "b"
23 | hg rm test_file &&
24 | hg commit -m "c" &&
25 |
26 | clone_repo &&
27 |
28 | test_expect_code 2 ls test_file &&
29 |
30 | cd ..
31 | '
32 |
33 | # See issue #36
34 | test_expect_failure 'cloning a file replaced with a directory' '
35 | test_when_finished "rm -rf hg_repo git_clone" &&
36 |
37 | make_hg_repo &&
38 | make_hg_commit "b" dir_or_file &&
39 |
40 | hg rm dir_or_file &&
41 | mkdir dir_or_file &&
42 | make_hg_commit c dir_or_file/test_file &&
43 |
44 | clone_repo &&
45 | test -d dir_or_file &&
46 | test -f dir_or_file/test_file &&
47 |
48 | cd ..
49 | '
50 |
51 | # also issue #36
52 | test_expect_failure 'clone replacing a symlink with a directory' '
53 | test_when_finished "rm -rf hg_repo git_clone" &&
54 |
55 | make_hg_repo &&
56 | ln -s test_file dir_or_link &&
57 | hg add dir_or_link &&
58 | hg commit -m "b" &&
59 | hg rm dir_or_link &&
60 | mkdir dir_or_link &&
61 | make_hg_commit c dir_or_link/test_file &&
62 |
63 | clone_repo &&
64 |
65 | test -d dir_or_link &&
66 | test -f dir_or_link/test_file &&
67 |
68 | cd ..
69 | '
70 |
71 | test_expect_success 'clone replace directory with a file' '
72 | test_when_finished "rm -rf hg_repo git_clone" &&
73 |
74 | make_hg_repo &&
75 | mkdir dir_or_file &&
76 | make_hg_commit "b" dir_or_file/test_file &&
77 | hg rm dir_or_file/test_file &&
78 | make_hg_commit "c" dir_or_file &&
79 |
80 | clone_repo &&
81 |
82 | test -f dir_or_file &&
83 |
84 | cd ..
85 | '
86 |
87 | test_expect_success 'clone replace file with a symlink' '
88 | test_when_finished "rm -rf hg_repo git_clone" &&
89 |
90 | make_hg_repo &&
91 | make_hg_commit b link_or_file &&
92 | hg rm link_or_file &&
93 | ln -s test_file link_or_file &&
94 | hg add link_or_file &&
95 | hg commit -m "c" &&
96 |
97 | clone_repo &&
98 |
99 | test -f link_or_file &&
100 | test -L link_or_file &&
101 |
102 | cd ..
103 | '
104 |
105 | test_expect_success 'clone replace directory with symlink' '
106 | test_when_finished "rm -rf hg_repo git_clone" &&
107 |
108 | make_hg_repo &&
109 | mkdir dir_or_link &&
110 | make_hg_commit b dir_or_link/test_file &&
111 | hg rm dir_or_link/test_file &&
112 | ln -s test_file dir_or_link &&
113 | hg add dir_or_link &&
114 | hg commit -m c
115 |
116 | clone_repo &&
117 |
118 | test -f dir_or_link &&
119 | test -L dir_or_link &&
120 |
121 | cd ..
122 | '
123 |
124 | test_done
125 |
--------------------------------------------------------------------------------
/test/test_notes.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg notes'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'basic clone with notes' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_hg_repo &&
21 | make_hg_commit b test_file &&
22 | hgsha1s=`hg log --template "{node}\n"` &&
23 |
24 | clone_repo &&
25 |
26 | assert_git_messages "b${NL}a" &&
27 | assert_git_notes "$hgsha1s" &&
28 |
29 |
30 | cd ..
31 | '
32 |
33 | test_expect_success 'basic pull with notes' '
34 | test_when_finished "rm -rf hg_repo git_clone" &&
35 |
36 | make_cloned_repo &&
37 | make_hg_commit b test_file &&
38 | hgsha1s=`hg log --template "{node}\n"` &&
39 |
40 | cd ../git_clone &&
41 | git pull &&
42 |
43 | assert_git_messages "b${NL}a" &&
44 | assert_git_notes "$hgsha1s" &&
45 |
46 | cd ..
47 | '
48 |
49 | test_expect_success 'pull notes rename remote' '
50 | test_when_finished "rm -rf hg_repo git_clone" &&
51 |
52 | make_hg_repo &&
53 | cd .. &&
54 | mkdir git_clone &&
55 | cd git_clone &&
56 | git init &&
57 | git remote add --fetch the_remote testgitifyhg::../hg_repo &&
58 | git pull the_remote master &&
59 | assert_git_messages "a" &&
60 | cd ../hg_repo &&
61 | make_hg_commit b test_file &&
62 | make_hg_commit c test_file &&
63 | cd ../git_clone &&
64 | git fetch the_remote &&
65 | assert_git_count 3 the_remote/master &&
66 | cd ../hg_repo &&
67 | make_hg_commit d test_file &&
68 | hgsha1s=`hg log --template "{node}\n"` &&
69 |
70 | cd ../git_clone &&
71 | git remote rename the_remote new_remote_name &&
72 | git pull new_remote_name master &&
73 | assert_git_messages "d${NL}c${NL}b${NL}a" &&
74 | assert_git_notes "$hgsha1s" &&
75 |
76 | cd ..
77 | '
78 |
79 | # see 30
80 | test_expect_failure 'simple push updates notes' '
81 | test_when_finished "rm -rf hg_repo git_clone" &&
82 |
83 | make_hg_repo &&
84 | clone_repo &&
85 | make_git_commit b test_file &&
86 | git push &&
87 | cd ../hg_repo &&
88 | hgsha1s=`hg log --template "{node}\n"` &&
89 | cd ../git_clone &&
90 | test_expect_code 0 git fetch &&
91 | assert_git_count 2 'origin' &&
92 | assert_git_notes $hgsha1s &&
93 |
94 | cd ..
95 | '
96 |
97 | test_expect_success 'simple push updates after pull' '
98 | test_when_finished "rm -rf hg_repo git_clone" &&
99 |
100 | make_hg_repo &&
101 | clone_repo &&
102 | make_git_commit b test_file &&
103 | git push &&
104 | test_expect_code 0 git fetch &&
105 | cd ../hg_repo &&
106 | hg update &&
107 | make_hg_commit "c" test_file &&
108 | hgsha1s=`hg log --template "{node}\n"` &&
109 | cd ../git_clone &&
110 | git pull &&
111 | assert_git_count 3 &&
112 | assert_git_notes "$hgsha1s" &&
113 |
114 | cd ..
115 | '
116 |
117 | test_done
118 |
--------------------------------------------------------------------------------
/test/test_pull.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg pull from hg'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'basic pull' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_cloned_repo &&
21 | make_hg_commit b test_file &&
22 | cd ../git_clone &&
23 | git pull &&
24 |
25 | assert_git_messages "b${NL}a" &&
26 |
27 | cd ..
28 | '
29 |
30 | test_expect_success 'pull named remote' '
31 | test_when_finished "rm -rf hg_repo git_clone" &&
32 |
33 | make_hg_repo &&
34 | cd .. &&
35 | mkdir git_repo &&
36 | cd git_repo &&
37 | git init &&
38 | git remote add --fetch the_remote testgitifyhg::../hg_repo
39 | git pull the_remote master &&
40 | assert_git_messages a &&
41 | cd ../hg_repo &&
42 | make_hg_commit b test_file &&
43 | make_hg_commit c test_file &&
44 | cd ../git_repo &&
45 | git fetch the_remote &&
46 | assert_git_messages "c${NL}b${NL}a" the_remote/master &&
47 |
48 | cd ../hg_repo &&
49 | make_hg_commit d test_file &&
50 |
51 | cd ../git_repo &&
52 | git remote rename the_remote new_remote_name &&
53 | git pull new_remote_name master &&
54 | assert_git_messages "d${NL}c${NL}b${NL}a" &&
55 |
56 |
57 | cd ..
58 | '
59 |
60 | test_expect_success 'pull from named branch' '
61 | test_when_finished "rm -rf hg_repo git_clone" &&
62 |
63 | make_hg_repo &&
64 | hg branch feature &&
65 | make_hg_commit b test_file &&
66 |
67 | clone_repo &&
68 | cd ../hg_repo &&
69 | make_hg_commit c test_file &&
70 | cd ../git_clone &&
71 | git checkout origin/branches/feature --track &&
72 | assert_git_messages "b${NL}a" &&
73 | git pull &&
74 | assert_git_messages "c${NL}b${NL}a" &&
75 |
76 | cd ..
77 | '
78 |
79 | test_expect_success 'pull conflict' '
80 | test_when_finished "rm -rf hg_repo git_clone" &&
81 |
82 | make_cloned_repo &&
83 | make_hg_commit b test_file &&
84 | cd ../git_clone &&
85 | make_git_commit c test_file &&
86 |
87 | git pull 2>&1 | grep "Automatic merge failed" &&
88 |
89 | cd ..
90 | '
91 |
92 | test_expect_success 'pull auto merge' '
93 | test_when_finished "rm -rf hg_repo git_clone" &&
94 |
95 | make_cloned_repo &&
96 | make_hg_commit b test_file &&
97 | cd ../git_clone &&
98 | make_git_commit c c &&
99 | git pull &&
100 | assert_git_count 4 &&
101 | # Merge order appears to be non-deterministic, but I would like to see
102 | # this better tested.
103 |
104 | cd ..
105 | '
106 |
107 | test_expect_success 'pull tags' '
108 | test_when_finished "rm -rf hg_repo git_clone" &&
109 |
110 | make_cloned_repo &&
111 | hg tag tag1 &&
112 | cd ../git_clone &&
113 | git pull &&
114 | git tag | grep tag1 &&
115 |
116 | cd ..
117 | '
118 |
119 | test_done
120 |
--------------------------------------------------------------------------------
/test/test_push.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg push'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 | test_expect_success 'simple push from master' '
17 | test_when_finished "rm -rf hg_repo git_clone" &&
18 | make_hg_repo &&
19 | clone_repo &&
20 | make_git_commit b test_file &&
21 | git push &&
22 | # Make sure that the remote ref has updated
23 | test "`git log --pretty=format:%B origin`" = "b${NL}${NL}a" &&
24 |
25 | cd ../hg_repo &&
26 | assert_hg_messages "b${NL}a" &&
27 | hg update &&
28 | test_cmp ../git_clone/test_file test_file &&
29 |
30 | cd ..
31 | '
32 |
33 | test_expect_success 'push not create bookmark' '
34 | test_when_finished "rm -rf hg_repo git_clone" &&
35 | make_hg_repo &&
36 | clone_repo &&
37 | make_git_commit b test_file &&
38 | git push &&
39 | cd ../hg_repo &&
40 |
41 | test "`hg bookmarks`" = "no bookmarks set" &&
42 |
43 | cd ..
44 | '
45 |
46 | test_expect_success 'test push empty repo' '
47 | test_when_finished "rm -rf hg_repo git_clone" &&
48 |
49 | mkdir hg_repo &&
50 | cd hg_repo &&
51 | hg init &&
52 |
53 | clone_repo &&
54 | git status | grep "Initial commit" &&
55 | make_git_commit a test_file &&
56 | git push origin master &&
57 | cd ../hg_repo &&
58 | assert_hg_messages "a" &&
59 | hg update &&
60 | test_cmp test_file ../git_clone/test_file &&
61 |
62 | cd ..
63 | '
64 |
65 | test_expect_success 'push conflict default' '
66 | test_when_finished "rm -rf hg_repo git_clone" &&
67 |
68 | make_hg_repo &&
69 |
70 | clone_repo &&
71 | cd ../hg_repo &&
72 | make_hg_commit b test_file &&
73 | cd ../git_clone &&
74 | make_git_commit c test_file &&
75 | test_expect_code 1 git push &&
76 | # test it again because we were having issues with it succeeding the second time
77 | test_expect_code 1 git push &&
78 |
79 | cd ..
80 | '
81 |
82 | test_expect_success 'push to named branch' '
83 | test_when_finished "rm -rf hg_repo git_clone" &&
84 |
85 | make_hg_repo &&
86 | hg branch branch_one &&
87 | make_hg_commit b test_file &&
88 |
89 | clone_repo &&
90 | git checkout -t "origin/branches/branch_one" &&
91 | make_git_commit c test_file &&
92 | git push &&
93 |
94 | cd ../hg_repo &&
95 | assert_hg_messages "c${NL}b${NL}a" &&
96 | hg update tip &&
97 | test `hg branch` = "branch_one" &&
98 |
99 | cd ..
100 | '
101 |
102 | test_expect_success 'push merged named branch' '
103 | test_when_finished "rm -rf hg_repo git_clone" &&
104 |
105 | make_hg_repo &&
106 | hg branch branch-one &&
107 | make_hg_commit b1 b &&
108 | hg update default &&
109 | make_hg_commit c1 c &&
110 |
111 | clone_repo &&
112 | git merge origin/branches/branch_one &&
113 | git push &&
114 |
115 | cd ../hg_repo &&
116 | hg update &&
117 | hg log --template="{desc}" &&
118 | assert_hg_messages "Merge${NL}c1${NL}b1${NL}a"
119 |
120 | cd ..
121 | '
122 |
123 | test_expect_success 'push new named branch' '
124 | test_when_finished "rm -rf hg_repo git_clone" &&
125 |
126 | make_hg_repo &&
127 | clone_repo &&
128 | git checkout -b branches/branch_one &&
129 | make_git_commit b test_file &&
130 | git push --set-upstream origin branches/branch_one &&
131 |
132 | cd ../hg_repo &&
133 | assert_hg_messages "b${NL}a"
134 | hg update tip &&
135 | test `hg branch` = "branch_one" &&
136 |
137 | cd ..
138 | '
139 |
140 | test_expect_success 'push conflict named branch' '
141 | test_when_finished "rm -rf hg_repo git_clone" &&
142 |
143 | make_hg_repo &&
144 | hg branch feature &&
145 | make_hg_commit b test_file &&
146 | clone_repo &&
147 | cd ../hg_repo &&
148 | make_hg_commit c test_file &&
149 | cd ../git_clone &&
150 | git checkout --track origin/branches/feature &&
151 | make_git_commit d test_file &&
152 | test_expect_code 1 git push &&
153 |
154 | cd ..
155 | '
156 |
157 | test_expect_success 'fetch after bad push updates master' '
158 | test_when_finished "rm -rf hg_repo git_clone" &&
159 |
160 | make_hg_repo &&
161 | clone_repo &&
162 | cd ../hg_repo &&
163 | make_hg_commit b test_file &&
164 | cd ../git_clone &&
165 | make_git_commit c c &&
166 | test_expect_code 1 git push &&
167 | git fetch &&
168 | assert_git_messages "b${NL}a" origin/master &&
169 | git pull --rebase &&
170 | assert_git_messages "c${NL}${NL}b${NL}a" &&
171 | git push &&
172 | cd ../hg_repo &&
173 | hg log --template="{desc}\n"
174 | assert_hg_messages "c${NL}b${NL}a" &&
175 |
176 | cd ..
177 | '
178 |
179 | test_expect_success 'test push after merge' '
180 | test_when_finished "rm -rf hg_repo git_clone" &&
181 |
182 | make_hg_repo &&
183 | clone_repo &&
184 | cd ../hg_repo &&
185 | make_hg_commit b "test_file" &&
186 | cd ../git_clone &&
187 | make_git_commit c c &&
188 | git pull && # automatically merges
189 | assert_git_count 4 &&
190 | git push &&
191 | cd ../hg_repo &&
192 | assert_hg_count 4 &&
193 |
194 | cd ..
195 | '
196 |
197 | test_expect_success 'push two commits' '
198 | test_when_finished "rm -rf hg_repo git_clone" &&
199 |
200 | make_hg_repo &&
201 | clone_repo &&
202 | make_git_commit b test_file &&
203 | make_git_commit c test_file &&
204 | git push &&
205 | cd ../hg_repo &&
206 | assert_hg_messages "c${NL}b${NL}a"
207 | cd ..
208 | '
209 |
210 | test_expect_success 'push up to date' '
211 | test_when_finished "rm -rf hg_repo git_clone" &&
212 |
213 | make_hg_repo &&
214 | clone_repo &&
215 | git push 2>&1 | grep "Everything up-to-date" &&
216 |
217 | # push with a commit on hg default/git master
218 | cd ../hg_repo &&
219 | make_hg_commit b test_file &&
220 | cd ../git_clone &&
221 | git pull &&
222 | git push 2>&1 | grep "Everything up-to-date" &&
223 |
224 | # push with a commit on non-default branch
225 | cd ../hg_repo &&
226 | hg branch new_branch &&
227 | make_hg_commit c test_file &&
228 | cd ../git_clone &&
229 | git pull &&
230 | git checkout origin/branches/new_branch --track &&
231 | git push 2>&1 | grep "Everything up-to-date" &&
232 |
233 | make_git_commit d test_file &&
234 | out=`git push origin branches/new_branch 2>&1` &&
235 | echo -e $out &&
236 | echo $out | grep "branches/new_branch -> branches/new_branch" &&
237 |
238 | cd ..
239 | '
240 |
241 | test_expect_success 'test git push messages' '
242 | test_when_finished "rm -rf hg_repo git_clone" &&
243 |
244 | make_hg_repo &&
245 | clone_repo &&
246 | make_git_commit b test_file &&
247 | out=`git push 2>&1` &&
248 | ! echo $out | grep "new branch" &&
249 | echo $out | grep "master -> master" &&
250 |
251 | git checkout -b branches/test_branch &&
252 | make_git_commit c test_file &&
253 | out=`git push --set-upstream origin branches/test_branch 2>&1` &&
254 | echo $out | grep "new branch" &&
255 | echo $out | grep "branches/test_branch -> branches/test_branch" &&
256 |
257 | make_git_commit c test_file &&
258 | out=`git push 2>&1` &&
259 | ! echo $out | grep "new branch" &&
260 | echo $out | grep "branches/test_branch -> branches/test_branch" &&
261 |
262 | cd ..
263 | '
264 |
265 | test_expect_success 'handle paths with whitespace' '
266 | test_when_finished "rm -rf hg_repo git_clone" &&
267 | make_hg_repo &&
268 | clone_repo &&
269 | make_git_commit b "test file" &&
270 | git push &&
271 | # Make sure that the remote ref has updated
272 | test "`git log --pretty=format:%B origin`" = "b${NL}${NL}a" &&
273 |
274 | cd ../hg_repo &&
275 | assert_hg_messages "b${NL}a" &&
276 | hg update &&
277 | test_cmp "../git_clone/test file" "test file" &&
278 |
279 | cd ..
280 | '
281 |
282 | test_expect_success 'handle paths with quotes' '
283 | test_when_finished "rm -rf hg_repo git_clone" &&
284 | make_hg_repo &&
285 | clone_repo &&
286 | make_git_commit b "\"test file\"" &&
287 | git push &&
288 | # Make sure that the remote ref has updated
289 | test "`git log --pretty=format:%B origin`" = "b${NL}${NL}a" &&
290 |
291 | cd ../hg_repo &&
292 | assert_hg_messages "b${NL}a" &&
293 | hg update &&
294 | test_cmp "../git_clone/\"test file\"" "\"test file\"" &&
295 |
296 | cd ..
297 | '
298 |
299 | test_done
300 |
--------------------------------------------------------------------------------
/test/test_push_tags.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg push tags'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'push lightweight tag' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 |
20 | make_hg_repo &&
21 | clone_repo &&
22 | git tag "this_is_a_tag" &&
23 | git push --tags &&
24 |
25 | cd ../hg_repo &&
26 | hg tags | grep "this_is_a_tag" &&
27 | assert_hg_count 2 &&
28 |
29 | cd ..
30 | '
31 |
32 | # FIXME: See #77
33 | test_expect_success 'lightweight tag sets hg username' '
34 | test_when_finished "rm -rf hg_repo git_clone .hgrc" &&
35 | user="Lite Wait " &&
36 |
37 | # NOTE: sharness set #HOME to the working directory for us, so this is
38 | # the default hgrc.
39 | echo "[ui]${NL}username=$user" > .hgrc
40 | make_hg_repo &&
41 | clone_repo &&
42 | git tag "lightweight" &&
43 | git push --tags &&
44 |
45 | cd ../hg_repo &&
46 | assert_hg_count 2 &&
47 | assert_hg_author "$user" &&
48 |
49 | cd ..
50 | '
51 |
52 | test_expect_success 'push tag with subsequent commits' '
53 | test_when_finished "rm -rf hg_repo git_clone" &&
54 |
55 | make_hg_repo &&
56 | clone_repo &&
57 | git tag this_is_a_tag &&
58 | make_git_commit b test_file &&
59 | git push origin HEAD --tags &&
60 |
61 | cd ../hg_repo &&
62 | hg tags | grep this_is_a_tag &&
63 | assert_hg_messages "Added tag this_is_a_tag for changeset $(hg id --id)${NL}b${NL}a"
64 | cd ..
65 | '
66 |
67 | test_expect_success 'push tag with previous commits' '
68 | test_when_finished "rm -rf hg_repo git_clone" &&
69 |
70 | make_hg_repo &&
71 | hgsha1=`hg id --id` &&
72 | hg tag an_old_tag &&
73 | clone_repo &&
74 | make_git_commit b test_file &&
75 | git tag this_is_a_tag &&
76 | git push --tags &&
77 |
78 | cd ../hg_repo &&
79 | hg tags | grep this_is_a_tag &&
80 | assert_hg_messages "Added tag this_is_a_tag for changeset $(hg id --id -r 2)${NL}b${NL}Added tag an_old_tag for changeset $hgsha1${NL}a" &&
81 |
82 | cd ..
83 | '
84 |
85 | test_expect_success 'push messaged tag' '
86 | test_when_finished "rm -rf hg_repo git_clone" &&
87 |
88 | make_hg_repo &&
89 | clone_repo &&
90 | git tag this_is_a_tag --message="I tagged a message and a user" &&
91 | git push --tags &&
92 |
93 | cd ../hg_repo &&
94 | hg tags | grep this_is_a_tag &&
95 | assert_hg_messages "I tagged a message and a user${NL}a" &&
96 | hg log &&
97 | # FIXME: I feel like this should be $GIT_USER
98 | # but git seems to be passing me the e-mail twice. Is this a bug in
99 | # git or something gitifyhg needs to parse?
100 | assert_hg_author "$GIT_AUTHOR_NAME $GIT_AUTHOR_EMAIL <$GIT_AUTHOR_EMAIL>" &&
101 | cd ..
102 | '
103 |
104 | test_expect_success 'push tag different branch' '
105 | test_when_finished "rm -rf hg_repo git_clone" &&
106 |
107 | make_hg_repo &&
108 | hg branch branch_one &&
109 | make_hg_commit b test_file &&
110 | clone_repo &&
111 | git checkout origin/branches/branch_one --track &&
112 | git tag this_is_a_tag &&
113 | git push --tags &&
114 |
115 | cd ../hg_repo &&
116 | hg tags | grep this_is_a_tag &&
117 | assert_hg_count 3 &&
118 | hg update tip &&
119 | hg branch | grep branch_one &&
120 |
121 | cd ..
122 | '
123 |
124 | test_expect_success 'push only new tag' '
125 | test_when_finished "rm -rf hg_repo git_clone" &&
126 |
127 | make_hg_repo &&
128 | hg tag an_old_tag &&
129 | clone_repo &&
130 | git tag this_is_a_tag &&
131 | git push --tags &&
132 |
133 | cd ../hg_repo &&
134 | test `hg tags | wc -l` -eq 3 && # 3 is tip
135 | hg tags | grep this_is_a_tag &&
136 | hg tags | grep an_old_tag &&
137 | assert_hg_count 3 &&
138 |
139 | cd ..
140 | '
141 |
142 | test_done
143 |
--------------------------------------------------------------------------------
/test/test_spaces.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg clone, pull, and push with spaces'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | test_expect_success 'clone branch with spaces' '
18 | test_when_finished "rm -rf hg_repo git_clone" &&
19 | make_hg_repo &&
20 | hg branch "feature branch" &&
21 | make_hg_commit b test_file &&
22 | clone_repo &&
23 | assert_git_messages "a" &&
24 | test "`git branch -r`" = " origin/HEAD -> origin/master
25 | origin/branches/feature___branch
26 | origin/master" &&
27 |
28 | git checkout branches/feature___branch &&
29 | test_cmp ../hg_repo/test_file test_file &&
30 | assert_git_messages "b${NL}a" &&
31 |
32 | cd ..
33 |
34 | '
35 |
36 | test_expect_success 'clone bookmark with spaces' '
37 | test_when_finished "rm -rf hg_repo git_clone" &&
38 | make_hg_repo &&
39 | hg bookmark "feature bookmark"
40 | make_hg_commit b test_file
41 |
42 | clone_repo
43 |
44 | test "`git branch -r`" = " origin/HEAD -> origin/master
45 | origin/feature___bookmark
46 | origin/master" &&
47 |
48 | git checkout origin/feature___bookmark &&
49 | assert_git_messages "b${NL}a" &&
50 | git checkout master &&
51 | assert_git_messages "b${NL}a" &&
52 |
53 | cd ..
54 | '
55 |
56 | test_expect_success 'clone tag with spaces' '
57 | test_when_finished "rm -rf hg_repo git_clone" &&
58 |
59 | make_hg_repo &&
60 | make_hg_commit b test_file &&
61 | hg tag "this is tagged" &&
62 | make_hg_commit c test_file &&
63 |
64 | clone_repo &&
65 |
66 | test $(git tag) = "this___is___tagged" &&
67 | git checkout this___is___tagged &&
68 | assert_git_messages "b${NL}a" &&
69 |
70 | cd ..
71 | '
72 |
73 | test_expect_success 'push to named branch with spaces' '
74 | test_when_finished "rm -rf hg_repo git_clone" &&
75 |
76 | make_hg_repo &&
77 | hg branch "branch one" &&
78 | make_hg_commit b test_file &&
79 |
80 | clone_repo &&
81 | git checkout -t "origin/branches/branch___one" &&
82 | make_git_commit c test_file &&
83 | git push &&
84 |
85 | cd ../hg_repo &&
86 | hg log --template="{desc}\n" &&
87 | assert_hg_messages "c${NL}b${NL}a" &&
88 | hg update tip &&
89 | test "`hg branch`" = "branch one" &&
90 |
91 | cd ..
92 | '
93 |
94 | test_expect_success 'push to bookmark with spaces' '
95 | test_when_finished "rm -rf hg_repo git_clone" &&
96 |
97 | make_hg_repo &&
98 | hg bookmark "feature bookmark" &&
99 | make_hg_commit b test_file &&
100 | clone_repo &&
101 | git checkout --track origin/feature___bookmark &&
102 | make_git_commit c test_file &&
103 | git push &&
104 |
105 | cd ../hg_repo &&
106 | hg update &&
107 |
108 | assert_hg_messages "c${NL}b${NL}a" &&
109 | hg bookmark | grep "feature bookmark" &&
110 | hg update "feature bookmark" &&
111 | test_cmp test_file ../git_clone/test_file &&
112 |
113 | cd ..
114 | '
115 |
116 | test_expect_success 'push tag with spaces' '
117 | test_when_finished "rm -rf hg_repo git_clone" &&
118 |
119 | make_hg_repo &&
120 | clone_repo &&
121 | git tag "this___is___a___tag" &&
122 | git push --tags &&
123 |
124 | cd ../hg_repo &&
125 | hg tags | grep "this is a tag" &&
126 |
127 | cd ..
128 | '
129 |
130 | test_expect_success 'push after rm file with spaces' '
131 | test_when_finished "rm -rf hg_repo git_clone" &&
132 |
133 | make_hg_repo &&
134 | clone_repo &&
135 | make_git_commit b "name with spaces" &&
136 |
137 | git rm "name with spaces" &&
138 | git commit -m "c" &&
139 |
140 | git push &&
141 |
142 | cd ../hg_repo &&
143 |
144 | hg update &&
145 | ls &&
146 |
147 | cd ..
148 | '
149 |
150 | test_expect_success 'push named branch with spaces' '
151 | test_when_finished "rm -rf hg_repo git_clone" &&
152 |
153 | make_hg_repo &&
154 | hg branch "feature one" &&
155 | make_hg_commit b test_file &&
156 | clone_repo &&
157 | cd ../hg_repo &&
158 | make_hg_commit c test_file &&
159 | cd ../git_clone &&
160 | git checkout origin/branches/feature___one --track &&
161 | assert_git_messages "b${NL}a" &&
162 | git pull &&
163 | assert_git_messages "c${NL}b${NL}a" &&
164 |
165 | cd ..
166 | '
167 |
168 | test_expect_success 'pull tags with spaces' '
169 | test_when_finished "rm -rf hg_repo git_clone" &&
170 |
171 | make_cloned_repo &&
172 | hg tag "tag one" &&
173 | cd ../git_clone &&
174 | git pull &&
175 | git tag | grep tag___one &&
176 |
177 | cd ..
178 | '
179 |
180 |
181 | test_done
182 |
--------------------------------------------------------------------------------
/test/test_special_cases.t:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | test_description='Test gitifyhg notes'
4 |
5 | . ./test-lib.sh
6 |
7 | # if ! test_have_prereq PYTHON; then
8 | # skip_all='skipping gitifyhg tests; python not available'
9 | # test_done
10 | # fi
11 |
12 | # if ! "$PYTHON_PATH" -c 'import mercurial'; then
13 | # skip_all='skipping gitifyhg tests; mercurial not available'
14 | # test_done
15 | # fi
16 |
17 | SB=$'\xE2\x98\xA0'
18 |
19 | test_expect_failure 'unicode paths' '
20 | # NOTE: This is failing, but I do not know why. It works in py.test.
21 | # The error is in os.getcwdu. I am not sure if it is a test problem or
22 | # a bug in gitifyhg.
23 |
24 | # This test has been ported from py.test but not fully tested due to the
25 | # early error.
26 | oldlang=$LANG &&
27 | oldlc=$LC_ALL &&
28 | export LANG="en_US.utf8" &&
29 | export LC_ALL="en_US.utf8" &&
30 | test_when_finished "rm -rf hg${SB}repo git${SB}clone" &&
31 | test_when_finished "export LANG=$oldlang" &&
32 | test_when_finished "export LC_ALL=$oldlc" &&
33 |
34 | mkdir hg${SB}repo &&
35 | cd hg${SB}repo &&
36 | echo $SB > file${SB} &&
37 | hg init &&
38 | hg add file${SB} &&
39 | hg commit -m ${SB} --user="$HG_USER" &&
40 | cd .. &&
41 | git clone testgitifyhg::hg${SB}repo git${SB}clone &&
42 | cd git${SB}clone &&
43 | git config user.email $GIT_AUTHOR_EMAIL &&
44 | git config user.name "$GIT_USER"
45 | assert_git_messages "${SB}" &&
46 |
47 | echo ${SB} >> file${SB} &&
48 | git add file${SB} &&
49 | git commit -m ${SB}2 &&
50 | git push &&
51 | cd ../hg${SB}repo &&
52 | hg update &&
53 | assert_hg_messages "${SB}2${NL}${SB}" &&
54 |
55 | echo ${SB} >> file${SB} &&
56 | hg commit -m "${SB}3" --user="$HG_USER" &&
57 |
58 | cd ../git${SB}clone &&
59 | git pull &&
60 | assert_git_messages "${SB}3${NL}${SB}2${NL}${NL}${SB}" &&
61 |
62 | cd ..
63 | '
64 |
65 | test_expect_failure 'executable bit' '
66 | test_when_finished "rm -rf hg_repo git_clone" &&
67 |
68 | make_hg_repo &&
69 | echo b >> test_file &&
70 | chmod 644 test_file &&
71 | hg commit -m "make file" &&
72 | chmod 755 test_file &&
73 | hg commit -m "make executable" &&
74 | chmod 644 test_file &&
75 | hg commit -m "make unexecutable" &&
76 |
77 | clone_repo &&
78 | test ! -x test_file &&
79 | git checkout HEAD^ &&
80 | test -x test_file &&
81 | git checkout HEAD^ &&
82 | test ! -x test_file &&
83 |
84 | git checkout master &&
85 | chmod 755 test_file &&
86 | git add test_file &&
87 | git commit -m "make executable again" &&
88 | git push &&
89 |
90 | cd ../hg_repo &&
91 | hg update &&
92 | test -x test_file &&
93 |
94 | cd ..
95 | '
96 | test_expect_failure 'symlinks' '
97 | test_when_finished "rm -rf hg_repo git_clone" &&
98 |
99 | make_hg_repo &&
100 | ln -s test_file linked &&
101 | hg add linked &&
102 | hg commit -m c &&
103 |
104 | clone_repo &&
105 | test -L linked &&
106 | ln -s test_file second_link &&
107 | git add second_link &&
108 | git commit -m d &&
109 | git push &&
110 |
111 | cd ../hg_repo &&
112 | hg update &&
113 | test -L second_link &&
114 |
115 | cd ..
116 | '
117 |
118 | test_done
119 |
120 |
121 | here
122 |
--------------------------------------------------------------------------------