##// END OF EJS Templates
convert: remove "replacecommitter" action...
Gregory Szorc -
r30815:c5bf2e8e default
parent child Browse files
Show More
@@ -1,507 +1,504
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''import revisions from foreign VCS repositories into Mercurial'''
9 9
10 10 from __future__ import absolute_import
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 cmdutil,
15 15 registrar,
16 16 )
17 17
18 18 from . import (
19 19 convcmd,
20 20 cvsps,
21 21 subversion,
22 22 )
23 23
24 24 cmdtable = {}
25 25 command = cmdutil.command(cmdtable)
26 26 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
27 27 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
28 28 # be specifying the version(s) of Mercurial they are tested with, or
29 29 # leave the attribute unspecified.
30 30 testedwith = 'ships-with-hg-core'
31 31
32 32 # Commands definition was moved elsewhere to ease demandload job.
33 33
34 34 @command('convert',
35 35 [('', 'authors', '',
36 36 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
37 37 _('FILE')),
38 38 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
39 39 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
40 40 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
41 41 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
42 42 ('', 'filemap', '', _('remap file names using contents of file'),
43 43 _('FILE')),
44 44 ('', 'full', None,
45 45 _('apply filemap changes by converting all files again')),
46 46 ('', 'splicemap', '', _('splice synthesized history into place'),
47 47 _('FILE')),
48 48 ('', 'branchmap', '', _('change branch names while converting'),
49 49 _('FILE')),
50 50 ('', 'branchsort', None, _('try to sort changesets by branches')),
51 51 ('', 'datesort', None, _('try to sort changesets by date')),
52 52 ('', 'sourcesort', None, _('preserve source changesets order')),
53 53 ('', 'closesort', None, _('try to reorder closed revisions'))],
54 54 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
55 55 norepo=True)
56 56 def convert(ui, src, dest=None, revmapfile=None, **opts):
57 57 """convert a foreign SCM repository to a Mercurial one.
58 58
59 59 Accepted source formats [identifiers]:
60 60
61 61 - Mercurial [hg]
62 62 - CVS [cvs]
63 63 - Darcs [darcs]
64 64 - git [git]
65 65 - Subversion [svn]
66 66 - Monotone [mtn]
67 67 - GNU Arch [gnuarch]
68 68 - Bazaar [bzr]
69 69 - Perforce [p4]
70 70
71 71 Accepted destination formats [identifiers]:
72 72
73 73 - Mercurial [hg]
74 74 - Subversion [svn] (history on branches is not preserved)
75 75
76 76 If no revision is given, all revisions will be converted.
77 77 Otherwise, convert will only import up to the named revision
78 78 (given in a format understood by the source).
79 79
80 80 If no destination directory name is specified, it defaults to the
81 81 basename of the source with ``-hg`` appended. If the destination
82 82 repository doesn't exist, it will be created.
83 83
84 84 By default, all sources except Mercurial will use --branchsort.
85 85 Mercurial uses --sourcesort to preserve original revision numbers
86 86 order. Sort modes have the following effects:
87 87
88 88 --branchsort convert from parent to child revision when possible,
89 89 which means branches are usually converted one after
90 90 the other. It generates more compact repositories.
91 91
92 92 --datesort sort revisions by date. Converted repositories have
93 93 good-looking changelogs but are often an order of
94 94 magnitude larger than the same ones generated by
95 95 --branchsort.
96 96
97 97 --sourcesort try to preserve source revisions order, only
98 98 supported by Mercurial sources.
99 99
100 100 --closesort try to move closed revisions as close as possible
101 101 to parent branches, only supported by Mercurial
102 102 sources.
103 103
104 104 If ``REVMAP`` isn't given, it will be put in a default location
105 105 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
106 106 text file that maps each source commit ID to the destination ID
107 107 for that revision, like so::
108 108
109 109 <source ID> <destination ID>
110 110
111 111 If the file doesn't exist, it's automatically created. It's
112 112 updated on each commit copied, so :hg:`convert` can be interrupted
113 113 and can be run repeatedly to copy new commits.
114 114
115 115 The authormap is a simple text file that maps each source commit
116 116 author to a destination commit author. It is handy for source SCMs
117 117 that use unix logins to identify authors (e.g.: CVS). One line per
118 118 author mapping and the line format is::
119 119
120 120 source author = destination author
121 121
122 122 Empty lines and lines starting with a ``#`` are ignored.
123 123
124 124 The filemap is a file that allows filtering and remapping of files
125 125 and directories. Each line can contain one of the following
126 126 directives::
127 127
128 128 include path/to/file-or-dir
129 129
130 130 exclude path/to/file-or-dir
131 131
132 132 rename path/to/source path/to/destination
133 133
134 134 Comment lines start with ``#``. A specified path matches if it
135 135 equals the full relative name of a file or one of its parent
136 136 directories. The ``include`` or ``exclude`` directive with the
137 137 longest matching path applies, so line order does not matter.
138 138
139 139 The ``include`` directive causes a file, or all files under a
140 140 directory, to be included in the destination repository. The default
141 141 if there are no ``include`` statements is to include everything.
142 142 If there are any ``include`` statements, nothing else is included.
143 143 The ``exclude`` directive causes files or directories to
144 144 be omitted. The ``rename`` directive renames a file or directory if
145 145 it is converted. To rename from a subdirectory into the root of
146 146 the repository, use ``.`` as the path to rename to.
147 147
148 148 ``--full`` will make sure the converted changesets contain exactly
149 149 the right files with the right content. It will make a full
150 150 conversion of all files, not just the ones that have
151 151 changed. Files that already are correct will not be changed. This
152 152 can be used to apply filemap changes when converting
153 153 incrementally. This is currently only supported for Mercurial and
154 154 Subversion.
155 155
156 156 The splicemap is a file that allows insertion of synthetic
157 157 history, letting you specify the parents of a revision. This is
158 158 useful if you want to e.g. give a Subversion merge two parents, or
159 159 graft two disconnected series of history together. Each entry
160 160 contains a key, followed by a space, followed by one or two
161 161 comma-separated values::
162 162
163 163 key parent1, parent2
164 164
165 165 The key is the revision ID in the source
166 166 revision control system whose parents should be modified (same
167 167 format as a key in .hg/shamap). The values are the revision IDs
168 168 (in either the source or destination revision control system) that
169 169 should be used as the new parents for that node. For example, if
170 170 you have merged "release-1.0" into "trunk", then you should
171 171 specify the revision on "trunk" as the first parent and the one on
172 172 the "release-1.0" branch as the second.
173 173
174 174 The branchmap is a file that allows you to rename a branch when it is
175 175 being brought in from whatever external repository. When used in
176 176 conjunction with a splicemap, it allows for a powerful combination
177 177 to help fix even the most badly mismanaged repositories and turn them
178 178 into nicely structured Mercurial repositories. The branchmap contains
179 179 lines of the form::
180 180
181 181 original_branch_name new_branch_name
182 182
183 183 where "original_branch_name" is the name of the branch in the
184 184 source repository, and "new_branch_name" is the name of the branch
185 185 is the destination repository. No whitespace is allowed in the
186 186 branch names. This can be used to (for instance) move code in one
187 187 repository from "default" to a named branch.
188 188
189 189 Mercurial Source
190 190 ################
191 191
192 192 The Mercurial source recognizes the following configuration
193 193 options, which you can set on the command line with ``--config``:
194 194
195 195 :convert.hg.ignoreerrors: ignore integrity errors when reading.
196 196 Use it to fix Mercurial repositories with missing revlogs, by
197 197 converting from and to Mercurial. Default is False.
198 198
199 199 :convert.hg.saverev: store original revision ID in changeset
200 200 (forces target IDs to change). It takes a boolean argument and
201 201 defaults to False.
202 202
203 203 :convert.hg.startrev: specify the initial Mercurial revision.
204 204 The default is 0.
205 205
206 206 :convert.hg.revs: revset specifying the source revisions to convert.
207 207
208 208 CVS Source
209 209 ##########
210 210
211 211 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
212 212 to indicate the starting point of what will be converted. Direct
213 213 access to the repository files is not needed, unless of course the
214 214 repository is ``:local:``. The conversion uses the top level
215 215 directory in the sandbox to find the CVS repository, and then uses
216 216 CVS rlog commands to find files to convert. This means that unless
217 217 a filemap is given, all files under the starting directory will be
218 218 converted, and that any directory reorganization in the CVS
219 219 sandbox is ignored.
220 220
221 221 The following options can be used with ``--config``:
222 222
223 223 :convert.cvsps.cache: Set to False to disable remote log caching,
224 224 for testing and debugging purposes. Default is True.
225 225
226 226 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
227 227 allowed between commits with identical user and log message in
228 228 a single changeset. When very large files were checked in as
229 229 part of a changeset then the default may not be long enough.
230 230 The default is 60.
231 231
232 232 :convert.cvsps.mergeto: Specify a regular expression to which
233 233 commit log messages are matched. If a match occurs, then the
234 234 conversion process will insert a dummy revision merging the
235 235 branch on which this log message occurs to the branch
236 236 indicated in the regex. Default is ``{{mergetobranch
237 237 ([-\\w]+)}}``
238 238
239 239 :convert.cvsps.mergefrom: Specify a regular expression to which
240 240 commit log messages are matched. If a match occurs, then the
241 241 conversion process will add the most recent revision on the
242 242 branch indicated in the regex as the second parent of the
243 243 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
244 244
245 245 :convert.localtimezone: use local time (as determined by the TZ
246 246 environment variable) for changeset date/times. The default
247 247 is False (use UTC).
248 248
249 249 :hooks.cvslog: Specify a Python function to be called at the end of
250 250 gathering the CVS log. The function is passed a list with the
251 251 log entries, and can modify the entries in-place, or add or
252 252 delete them.
253 253
254 254 :hooks.cvschangesets: Specify a Python function to be called after
255 255 the changesets are calculated from the CVS log. The
256 256 function is passed a list with the changeset entries, and can
257 257 modify the changesets in-place, or add or delete them.
258 258
259 259 An additional "debugcvsps" Mercurial command allows the builtin
260 260 changeset merging code to be run without doing a conversion. Its
261 261 parameters and output are similar to that of cvsps 2.1. Please see
262 262 the command help for more details.
263 263
264 264 Subversion Source
265 265 #################
266 266
267 267 Subversion source detects classical trunk/branches/tags layouts.
268 268 By default, the supplied ``svn://repo/path/`` source URL is
269 269 converted as a single branch. If ``svn://repo/path/trunk`` exists
270 270 it replaces the default branch. If ``svn://repo/path/branches``
271 271 exists, its subdirectories are listed as possible branches. If
272 272 ``svn://repo/path/tags`` exists, it is looked for tags referencing
273 273 converted branches. Default ``trunk``, ``branches`` and ``tags``
274 274 values can be overridden with following options. Set them to paths
275 275 relative to the source URL, or leave them blank to disable auto
276 276 detection.
277 277
278 278 The following options can be set with ``--config``:
279 279
280 280 :convert.svn.branches: specify the directory containing branches.
281 281 The default is ``branches``.
282 282
283 283 :convert.svn.tags: specify the directory containing tags. The
284 284 default is ``tags``.
285 285
286 286 :convert.svn.trunk: specify the name of the trunk branch. The
287 287 default is ``trunk``.
288 288
289 289 :convert.localtimezone: use local time (as determined by the TZ
290 290 environment variable) for changeset date/times. The default
291 291 is False (use UTC).
292 292
293 293 Source history can be retrieved starting at a specific revision,
294 294 instead of being integrally converted. Only single branch
295 295 conversions are supported.
296 296
297 297 :convert.svn.startrev: specify start Subversion revision number.
298 298 The default is 0.
299 299
300 300 Git Source
301 301 ##########
302 302
303 303 The Git importer converts commits from all reachable branches (refs
304 304 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
305 305 Branches are converted to bookmarks with the same name, with the
306 306 leading 'refs/heads' stripped. Git submodules are converted to Git
307 307 subrepos in Mercurial.
308 308
309 309 The following options can be set with ``--config``:
310 310
311 311 :convert.git.similarity: specify how similar files modified in a
312 312 commit must be to be imported as renames or copies, as a
313 313 percentage between ``0`` (disabled) and ``100`` (files must be
314 314 identical). For example, ``90`` means that a delete/add pair will
315 315 be imported as a rename if more than 90% of the file hasn't
316 316 changed. The default is ``50``.
317 317
318 318 :convert.git.findcopiesharder: while detecting copies, look at all
319 319 files in the working copy instead of just changed ones. This
320 320 is very expensive for large projects, and is only effective when
321 321 ``convert.git.similarity`` is greater than 0. The default is False.
322 322
323 323 :convert.git.renamelimit: perform rename and copy detection up to this
324 324 many changed files in a commit. Increasing this will make rename
325 325 and copy detection more accurate but will significantly slow down
326 326 computation on large projects. The option is only relevant if
327 327 ``convert.git.similarity`` is greater than 0. The default is
328 328 ``400``.
329 329
330 330 :convert.git.committeractions: list of actions to take when processing
331 331 author and committer values.
332 332
333 333 Git commits have separate author (who wrote the commit) and committer
334 334 (who applied the commit) fields. Not all destinations support separate
335 335 author and committer fields (including Mercurial). This config option
336 336 controls what to do with these author and committer fields during
337 337 conversion.
338 338
339 339 A value of ``messagedifferent`` will append a ``committer: ...``
340 340 line to the commit message if the Git committer is different from the
341 341 author. The prefix of that line can be specified using the syntax
342 342 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
343 343 When a prefix is specified, a space will always be inserted between the
344 344 prefix and the value.
345 345
346 346 ``messagealways`` behaves like ``messagedifferent`` except it will
347 347 always result in a ``committer: ...`` line being appended to the commit
348 348 message. This value is mutually exclusive with ``messagedifferent``.
349 349
350 350 ``dropcommitter`` will remove references to the committer. Only
351 351 references to the author will remain. Actions that add references
352 352 to the committer will have no effect when this is set.
353 353
354 354 ``replaceauthor`` will replace the value of the author field with
355 355 the committer. Other actions that add references to the committer
356 356 will still take effect when this is set.
357 357
358 ``replacecommitter`` will replace the value of the committer field
359 with the author.
360
361 358 The default is ``messagedifferent``.
362 359
363 360 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
364 361 the destination. Some Git repositories store extra metadata in commits.
365 362 By default, this non-default metadata will be lost during conversion.
366 363 Setting this config option can retain that metadata. Some built-in
367 364 keys such as ``parent`` and ``branch`` are not allowed to be copied.
368 365
369 366 :convert.git.remoteprefix: remote refs are converted as bookmarks with
370 367 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
371 368 is 'remote'.
372 369
373 370 :convert.git.saverev: whether to store the original Git commit ID in the
374 371 metadata of the destination commit. The default is True.
375 372
376 373 :convert.git.skipsubmodules: does not convert root level .gitmodules files
377 374 or files with 160000 mode indicating a submodule. Default is False.
378 375
379 376 Perforce Source
380 377 ###############
381 378
382 379 The Perforce (P4) importer can be given a p4 depot path or a
383 380 client specification as source. It will convert all files in the
384 381 source to a flat Mercurial repository, ignoring labels, branches
385 382 and integrations. Note that when a depot path is given you then
386 383 usually should specify a target directory, because otherwise the
387 384 target may be named ``...-hg``.
388 385
389 386 The following options can be set with ``--config``:
390 387
391 388 :convert.p4.encoding: specify the encoding to use when decoding standard
392 389 output of the Perforce command line tool. The default is default system
393 390 encoding.
394 391
395 392 :convert.p4.startrev: specify initial Perforce revision (a
396 393 Perforce changelist number).
397 394
398 395 Mercurial Destination
399 396 #####################
400 397
401 398 The Mercurial destination will recognize Mercurial subrepositories in the
402 399 destination directory, and update the .hgsubstate file automatically if the
403 400 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
404 401 Converting a repository with subrepositories requires converting a single
405 402 repository at a time, from the bottom up.
406 403
407 404 .. container:: verbose
408 405
409 406 An example showing how to convert a repository with subrepositories::
410 407
411 408 # so convert knows the type when it sees a non empty destination
412 409 $ hg init converted
413 410
414 411 $ hg convert orig/sub1 converted/sub1
415 412 $ hg convert orig/sub2 converted/sub2
416 413 $ hg convert orig converted
417 414
418 415 The following options are supported:
419 416
420 417 :convert.hg.clonebranches: dispatch source branches in separate
421 418 clones. The default is False.
422 419
423 420 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
424 421 ``default``.
425 422
426 423 :convert.hg.usebranchnames: preserve branch names. The default is
427 424 True.
428 425
429 426 :convert.hg.sourcename: records the given string as a 'convert_source' extra
430 427 value on each commit made in the target repository. The default is None.
431 428
432 429 All Destinations
433 430 ################
434 431
435 432 All destination types accept the following options:
436 433
437 434 :convert.skiptags: does not convert tags from the source repo to the target
438 435 repo. The default is False.
439 436 """
440 437 return convcmd.convert(ui, src, dest, revmapfile, **opts)
441 438
442 439 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
443 440 def debugsvnlog(ui, **opts):
444 441 return subversion.debugsvnlog(ui, **opts)
445 442
446 443 @command('debugcvsps',
447 444 [
448 445 # Main options shared with cvsps-2.1
449 446 ('b', 'branches', [], _('only return changes on specified branches')),
450 447 ('p', 'prefix', '', _('prefix to remove from file names')),
451 448 ('r', 'revisions', [],
452 449 _('only return changes after or between specified tags')),
453 450 ('u', 'update-cache', None, _("update cvs log cache")),
454 451 ('x', 'new-cache', None, _("create new cvs log cache")),
455 452 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
456 453 ('', 'root', '', _('specify cvsroot')),
457 454 # Options specific to builtin cvsps
458 455 ('', 'parents', '', _('show parent changesets')),
459 456 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
460 457 # Options that are ignored for compatibility with cvsps-2.1
461 458 ('A', 'cvs-direct', None, _('ignored for compatibility')),
462 459 ],
463 460 _('hg debugcvsps [OPTION]... [PATH]...'),
464 461 norepo=True)
465 462 def debugcvsps(ui, *args, **opts):
466 463 '''create changeset information from CVS
467 464
468 465 This command is intended as a debugging tool for the CVS to
469 466 Mercurial converter, and can be used as a direct replacement for
470 467 cvsps.
471 468
472 469 Hg debugcvsps reads the CVS rlog for current directory (or any
473 470 named directory) in the CVS repository, and converts the log to a
474 471 series of changesets based on matching commit log entries and
475 472 dates.'''
476 473 return cvsps.debugcvsps(ui, *args, **opts)
477 474
478 475 def kwconverted(ctx, name):
479 476 rev = ctx.extra().get('convert_revision', '')
480 477 if rev.startswith('svn:'):
481 478 if name == 'svnrev':
482 479 return str(subversion.revsplit(rev)[2])
483 480 elif name == 'svnpath':
484 481 return subversion.revsplit(rev)[1]
485 482 elif name == 'svnuuid':
486 483 return subversion.revsplit(rev)[0]
487 484 return rev
488 485
489 486 templatekeyword = registrar.templatekeyword()
490 487
491 488 @templatekeyword('svnrev')
492 489 def kwsvnrev(repo, ctx, **args):
493 490 """String. Converted subversion revision number."""
494 491 return kwconverted(ctx, 'svnrev')
495 492
496 493 @templatekeyword('svnpath')
497 494 def kwsvnpath(repo, ctx, **args):
498 495 """String. Converted subversion revision project path."""
499 496 return kwconverted(ctx, 'svnpath')
500 497
501 498 @templatekeyword('svnuuid')
502 499 def kwsvnuuid(repo, ctx, **args):
503 500 """String. Converted subversion revision repository identifier."""
504 501 return kwconverted(ctx, 'svnuuid')
505 502
506 503 # tell hggettext to extract docstrings from these functions:
507 504 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,490 +1,480
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import os
10 10
11 11 from mercurial.i18n import _
12 12 from mercurial import (
13 13 config,
14 14 error,
15 15 node as nodemod,
16 16 )
17 17
18 18 from . import (
19 19 common,
20 20 )
21 21
22 22 class submodule(object):
23 23 def __init__(self, path, node, url):
24 24 self.path = path
25 25 self.node = node
26 26 self.url = url
27 27
28 28 def hgsub(self):
29 29 return "%s = [git]%s" % (self.path, self.url)
30 30
31 31 def hgsubstate(self):
32 32 return "%s %s" % (self.node, self.path)
33 33
34 34 # Keys in extra fields that should not be copied if the user requests.
35 35 bannedextrakeys = set([
36 36 # Git commit object built-ins.
37 37 'tree',
38 38 'parent',
39 39 'author',
40 40 'committer',
41 41 # Mercurial built-ins.
42 42 'branch',
43 43 'close',
44 44 ])
45 45
46 46 class convert_git(common.converter_source, common.commandline):
47 47 # Windows does not support GIT_DIR= construct while other systems
48 48 # cannot remove environment variable. Just assume none have
49 49 # both issues.
50 50
51 51 def _gitcmd(self, cmd, *args, **kwargs):
52 52 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
53 53
54 54 def gitrun0(self, *args, **kwargs):
55 55 return self._gitcmd(self.run0, *args, **kwargs)
56 56
57 57 def gitrun(self, *args, **kwargs):
58 58 return self._gitcmd(self.run, *args, **kwargs)
59 59
60 60 def gitrunlines0(self, *args, **kwargs):
61 61 return self._gitcmd(self.runlines0, *args, **kwargs)
62 62
63 63 def gitrunlines(self, *args, **kwargs):
64 64 return self._gitcmd(self.runlines, *args, **kwargs)
65 65
66 66 def gitpipe(self, *args, **kwargs):
67 67 return self._gitcmd(self._run3, *args, **kwargs)
68 68
69 69 def __init__(self, ui, path, revs=None):
70 70 super(convert_git, self).__init__(ui, path, revs=revs)
71 71 common.commandline.__init__(self, ui, 'git')
72 72
73 73 # Pass an absolute path to git to prevent from ever being interpreted
74 74 # as a URL
75 75 path = os.path.abspath(path)
76 76
77 77 if os.path.isdir(path + "/.git"):
78 78 path += "/.git"
79 79 if not os.path.exists(path + "/objects"):
80 80 raise common.NoRepo(_("%s does not look like a Git repository") %
81 81 path)
82 82
83 83 # The default value (50) is based on the default for 'git diff'.
84 84 similarity = ui.configint('convert', 'git.similarity', default=50)
85 85 if similarity < 0 or similarity > 100:
86 86 raise error.Abort(_('similarity must be between 0 and 100'))
87 87 if similarity > 0:
88 88 self.simopt = ['-C%d%%' % similarity]
89 89 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
90 90 False)
91 91 if findcopiesharder:
92 92 self.simopt.append('--find-copies-harder')
93 93
94 94 renamelimit = ui.configint('convert', 'git.renamelimit',
95 95 default=400)
96 96 self.simopt.append('-l%d' % renamelimit)
97 97 else:
98 98 self.simopt = []
99 99
100 100 common.checktool('git', 'git')
101 101
102 102 self.path = path
103 103 self.submodules = []
104 104
105 105 self.catfilepipe = self.gitpipe('cat-file', '--batch')
106 106
107 107 self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys')
108 108 banned = set(self.copyextrakeys) & bannedextrakeys
109 109 if banned:
110 110 raise error.Abort(_('copying of extra key is forbidden: %s') %
111 111 _(', ').join(sorted(banned)))
112 112
113 113 committeractions = self.ui.configlist('convert', 'git.committeractions',
114 114 'messagedifferent')
115 115
116 116 messagedifferent = None
117 117 messagealways = None
118 118 for a in committeractions:
119 119 if a.startswith(('messagedifferent', 'messagealways')):
120 120 k = a
121 121 v = None
122 122 if '=' in a:
123 123 k, v = a.split('=', 1)
124 124
125 125 if k == 'messagedifferent':
126 126 messagedifferent = v or 'committer:'
127 127 elif k == 'messagealways':
128 128 messagealways = v or 'committer:'
129 129
130 130 if messagedifferent and messagealways:
131 131 raise error.Abort(_('committeractions cannot define both '
132 132 'messagedifferent and messagealways'))
133 133
134 134 dropcommitter = 'dropcommitter' in committeractions
135 135 replaceauthor = 'replaceauthor' in committeractions
136 replacecommitter = 'replacecommitter' in committeractions
137 136
138 if dropcommitter and (replaceauthor or replacecommitter):
137 if dropcommitter and replaceauthor:
139 138 raise error.Abort(_('committeractions cannot define both '
140 'dropcommitter and '
141 'replaceauthor/replacecommitter'))
139 'dropcommitter and replaceauthor'))
142 140
143 141 if dropcommitter and messagealways:
144 142 raise error.Abort(_('committeractions cannot define both '
145 143 'dropcommitter and messagealways'))
146 144
147 if replaceauthor and replacecommitter:
148 raise error.Abort(_('committeractions cannot define both '
149 'replaceauthor and replacecommitter'))
150
151 145 if not messagedifferent and not messagealways:
152 146 messagedifferent = 'committer:'
153 147
154 148 self.committeractions = {
155 149 'dropcommitter': dropcommitter,
156 150 'replaceauthor': replaceauthor,
157 'replacecommitter': replacecommitter,
158 151 'messagedifferent': messagedifferent,
159 152 'messagealways': messagealways,
160 153 }
161 154
162 155 def after(self):
163 156 for f in self.catfilepipe:
164 157 f.close()
165 158
166 159 def getheads(self):
167 160 if not self.revs:
168 161 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
169 162 heads = output.splitlines()
170 163 if status:
171 164 raise error.Abort(_('cannot retrieve git heads'))
172 165 else:
173 166 heads = []
174 167 for rev in self.revs:
175 168 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
176 169 heads.append(rawhead[:-1])
177 170 if ret:
178 171 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
179 172 return heads
180 173
181 174 def catfile(self, rev, type):
182 175 if rev == nodemod.nullhex:
183 176 raise IOError
184 177 self.catfilepipe[0].write(rev+'\n')
185 178 self.catfilepipe[0].flush()
186 179 info = self.catfilepipe[1].readline().split()
187 180 if info[1] != type:
188 181 raise error.Abort(_('cannot read %r object at %s') % (type, rev))
189 182 size = int(info[2])
190 183 data = self.catfilepipe[1].read(size)
191 184 if len(data) < size:
192 185 raise error.Abort(_('cannot read %r object at %s: unexpected size')
193 186 % (type, rev))
194 187 # read the trailing newline
195 188 self.catfilepipe[1].read(1)
196 189 return data
197 190
198 191 def getfile(self, name, rev):
199 192 if rev == nodemod.nullhex:
200 193 return None, None
201 194 if name == '.hgsub':
202 195 data = '\n'.join([m.hgsub() for m in self.submoditer()])
203 196 mode = ''
204 197 elif name == '.hgsubstate':
205 198 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
206 199 mode = ''
207 200 else:
208 201 data = self.catfile(rev, "blob")
209 202 mode = self.modecache[(name, rev)]
210 203 return data, mode
211 204
212 205 def submoditer(self):
213 206 null = nodemod.nullhex
214 207 for m in sorted(self.submodules, key=lambda p: p.path):
215 208 if m.node != null:
216 209 yield m
217 210
218 211 def parsegitmodules(self, content):
219 212 """Parse the formatted .gitmodules file, example file format:
220 213 [submodule "sub"]\n
221 214 \tpath = sub\n
222 215 \turl = git://giturl\n
223 216 """
224 217 self.submodules = []
225 218 c = config.config()
226 219 # Each item in .gitmodules starts with whitespace that cant be parsed
227 220 c.parse('.gitmodules', '\n'.join(line.strip() for line in
228 221 content.split('\n')))
229 222 for sec in c.sections():
230 223 s = c[sec]
231 224 if 'url' in s and 'path' in s:
232 225 self.submodules.append(submodule(s['path'], '', s['url']))
233 226
234 227 def retrievegitmodules(self, version):
235 228 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
236 229 if ret:
237 230 # This can happen if a file is in the repo that has permissions
238 231 # 160000, but there is no .gitmodules file.
239 232 self.ui.warn(_("warning: cannot read submodules config file in "
240 233 "%s\n") % version)
241 234 return
242 235
243 236 try:
244 237 self.parsegitmodules(modules)
245 238 except error.ParseError:
246 239 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
247 240 % version)
248 241 return
249 242
250 243 for m in self.submodules:
251 244 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
252 245 if ret:
253 246 continue
254 247 m.node = node.strip()
255 248
256 249 def getchanges(self, version, full):
257 250 if full:
258 251 raise error.Abort(_("convert from git does not support --full"))
259 252 self.modecache = {}
260 253 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
261 254 output, status = self.gitrun(*cmd)
262 255 if status:
263 256 raise error.Abort(_('cannot read changes in %s') % version)
264 257 changes = []
265 258 copies = {}
266 259 seen = set()
267 260 entry = None
268 261 subexists = [False]
269 262 subdeleted = [False]
270 263 difftree = output.split('\x00')
271 264 lcount = len(difftree)
272 265 i = 0
273 266
274 267 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
275 268 False)
276 269 def add(entry, f, isdest):
277 270 seen.add(f)
278 271 h = entry[3]
279 272 p = (entry[1] == "100755")
280 273 s = (entry[1] == "120000")
281 274 renamesource = (not isdest and entry[4][0] == 'R')
282 275
283 276 if f == '.gitmodules':
284 277 if skipsubmodules:
285 278 return
286 279
287 280 subexists[0] = True
288 281 if entry[4] == 'D' or renamesource:
289 282 subdeleted[0] = True
290 283 changes.append(('.hgsub', nodemod.nullhex))
291 284 else:
292 285 changes.append(('.hgsub', ''))
293 286 elif entry[1] == '160000' or entry[0] == ':160000':
294 287 if not skipsubmodules:
295 288 subexists[0] = True
296 289 else:
297 290 if renamesource:
298 291 h = nodemod.nullhex
299 292 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
300 293 changes.append((f, h))
301 294
302 295 while i < lcount:
303 296 l = difftree[i]
304 297 i += 1
305 298 if not entry:
306 299 if not l.startswith(':'):
307 300 continue
308 301 entry = l.split()
309 302 continue
310 303 f = l
311 304 if entry[4][0] == 'C':
312 305 copysrc = f
313 306 copydest = difftree[i]
314 307 i += 1
315 308 f = copydest
316 309 copies[copydest] = copysrc
317 310 if f not in seen:
318 311 add(entry, f, False)
319 312 # A file can be copied multiple times, or modified and copied
320 313 # simultaneously. So f can be repeated even if fdest isn't.
321 314 if entry[4][0] == 'R':
322 315 # rename: next line is the destination
323 316 fdest = difftree[i]
324 317 i += 1
325 318 if fdest not in seen:
326 319 add(entry, fdest, True)
327 320 # .gitmodules isn't imported at all, so it being copied to
328 321 # and fro doesn't really make sense
329 322 if f != '.gitmodules' and fdest != '.gitmodules':
330 323 copies[fdest] = f
331 324 entry = None
332 325
333 326 if subexists[0]:
334 327 if subdeleted[0]:
335 328 changes.append(('.hgsubstate', nodemod.nullhex))
336 329 else:
337 330 self.retrievegitmodules(version)
338 331 changes.append(('.hgsubstate', ''))
339 332 return (changes, copies, set())
340 333
341 334 def getcommit(self, version):
342 335 c = self.catfile(version, "commit") # read the commit hash
343 336 end = c.find("\n\n")
344 337 message = c[end + 2:]
345 338 message = self.recode(message)
346 339 l = c[:end].splitlines()
347 340 parents = []
348 341 author = committer = None
349 342 extra = {}
350 343 for e in l[1:]:
351 344 n, v = e.split(" ", 1)
352 345 if n == "author":
353 346 p = v.split()
354 347 tm, tz = p[-2:]
355 348 author = " ".join(p[:-2])
356 349 if author[0] == "<": author = author[1:-1]
357 350 author = self.recode(author)
358 351 if n == "committer":
359 352 p = v.split()
360 353 tm, tz = p[-2:]
361 354 committer = " ".join(p[:-2])
362 355 if committer[0] == "<": committer = committer[1:-1]
363 356 committer = self.recode(committer)
364 357 if n == "parent":
365 358 parents.append(v)
366 359 if n in self.copyextrakeys:
367 360 extra[n] = v
368 361
369 362 if self.committeractions['dropcommitter']:
370 363 committer = None
371
372 if self.committeractions['replacecommitter']:
373 committer = author
374 364 elif self.committeractions['replaceauthor']:
375 365 author = committer
376 366
377 367 if committer:
378 368 messagealways = self.committeractions['messagealways']
379 369 messagedifferent = self.committeractions['messagedifferent']
380 370 if messagealways:
381 371 message += '\n%s %s\n' % (messagealways, committer)
382 372 elif messagedifferent and author != committer:
383 373 message += '\n%s %s\n' % (messagedifferent, committer)
384 374
385 375 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
386 376 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
387 377 date = tm + " " + str(tz)
388 378 saverev = self.ui.configbool('convert', 'git.saverev', True)
389 379
390 380 c = common.commit(parents=parents, date=date, author=author,
391 381 desc=message,
392 382 rev=version,
393 383 extra=extra,
394 384 saverev=saverev)
395 385 return c
396 386
397 387 def numcommits(self):
398 388 output, ret = self.gitrunlines('rev-list', '--all')
399 389 if ret:
400 390 raise error.Abort(_('cannot retrieve number of commits in %s') \
401 391 % self.path)
402 392 return len(output)
403 393
404 394 def gettags(self):
405 395 tags = {}
406 396 alltags = {}
407 397 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
408 398
409 399 if status:
410 400 raise error.Abort(_('cannot read tags from %s') % self.path)
411 401 prefix = 'refs/tags/'
412 402
413 403 # Build complete list of tags, both annotated and bare ones
414 404 for line in output:
415 405 line = line.strip()
416 406 if line.startswith("error:") or line.startswith("fatal:"):
417 407 raise error.Abort(_('cannot read tags from %s') % self.path)
418 408 node, tag = line.split(None, 1)
419 409 if not tag.startswith(prefix):
420 410 continue
421 411 alltags[tag[len(prefix):]] = node
422 412
423 413 # Filter out tag objects for annotated tag refs
424 414 for tag in alltags:
425 415 if tag.endswith('^{}'):
426 416 tags[tag[:-3]] = alltags[tag]
427 417 else:
428 418 if tag + '^{}' in alltags:
429 419 continue
430 420 else:
431 421 tags[tag] = alltags[tag]
432 422
433 423 return tags
434 424
435 425 def getchangedfiles(self, version, i):
436 426 changes = []
437 427 if i is None:
438 428 output, status = self.gitrunlines('diff-tree', '--root', '-m',
439 429 '-r', version)
440 430 if status:
441 431 raise error.Abort(_('cannot read changes in %s') % version)
442 432 for l in output:
443 433 if "\t" not in l:
444 434 continue
445 435 m, f = l[:-1].split("\t")
446 436 changes.append(f)
447 437 else:
448 438 output, status = self.gitrunlines('diff-tree', '--name-only',
449 439 '--root', '-r', version,
450 440 '%s^%s' % (version, i + 1), '--')
451 441 if status:
452 442 raise error.Abort(_('cannot read changes in %s') % version)
453 443 changes = [f.rstrip('\n') for f in output]
454 444
455 445 return changes
456 446
457 447 def getbookmarks(self):
458 448 bookmarks = {}
459 449
460 450 # Handle local and remote branches
461 451 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
462 452 reftypes = [
463 453 # (git prefix, hg prefix)
464 454 ('refs/remotes/origin/', remoteprefix + '/'),
465 455 ('refs/heads/', '')
466 456 ]
467 457
468 458 exclude = set([
469 459 'refs/remotes/origin/HEAD',
470 460 ])
471 461
472 462 try:
473 463 output, status = self.gitrunlines('show-ref')
474 464 for line in output:
475 465 line = line.strip()
476 466 rev, name = line.split(None, 1)
477 467 # Process each type of branch
478 468 for gitprefix, hgprefix in reftypes:
479 469 if not name.startswith(gitprefix) or name in exclude:
480 470 continue
481 471 name = '%s%s' % (hgprefix, name[len(gitprefix):])
482 472 bookmarks[name] = rev
483 473 except Exception:
484 474 pass
485 475
486 476 return bookmarks
487 477
488 478 def checkrevformat(self, revstr, mapname='splicemap'):
489 479 """ git revision string is a 40 byte hex """
490 480 self.checkhexformat(revstr, mapname)
@@ -1,1208 +1,1167
1 1 #require git
2 2
3 3 $ echo "[core]" >> $HOME/.gitconfig
4 4 $ echo "autocrlf = false" >> $HOME/.gitconfig
5 5 $ echo "[core]" >> $HOME/.gitconfig
6 6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 7 $ echo "[extensions]" >> $HGRCPATH
8 8 $ echo "convert=" >> $HGRCPATH
9 9 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
10 10 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
11 11 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
12 12 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
13 13 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
14 14 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
15 15 $ INVALIDID1=afd12345af
16 16 $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
17 17 $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
18 18 $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
19 19 $ count=10
20 20 $ commit()
21 21 > {
22 22 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
23 23 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
24 24 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
25 25 > count=`expr $count + 1`
26 26 > }
27 27 $ mkdir git-repo
28 28 $ cd git-repo
29 29 $ git init-db >/dev/null 2>/dev/null
30 30 $ echo a > a
31 31 $ mkdir d
32 32 $ echo b > d/b
33 33 $ git add a d
34 34 $ commit -a -m t1
35 35
36 36 Remove the directory, then try to replace it with a file (issue754)
37 37
38 38 $ git rm -f d/b
39 39 rm 'd/b'
40 40 $ commit -m t2
41 41 $ echo d > d
42 42 $ git add d
43 43 $ commit -m t3
44 44 $ echo b >> a
45 45 $ commit -a -m t4.1
46 46 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
47 47 $ echo c > a
48 48 $ echo a >> a
49 49 $ commit -a -m t4.2
50 50 $ git checkout master >/dev/null 2>/dev/null
51 51 $ git pull --no-commit . other > /dev/null 2>/dev/null
52 52 $ commit -m 'Merge branch other'
53 53 $ cd ..
54 54 $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
55 55 > --config progress.delay=0 --config progress.changedelay=0 \
56 56 > --config progress.refresh=0 --config progress.width=60 \
57 57 > --config progress.format='topic, bar, number' --datesort git-repo
58 58 \r (no-eol) (esc)
59 59 scanning [======> ] 1/6\r (no-eol) (esc)
60 60 scanning [=============> ] 2/6\r (no-eol) (esc)
61 61 scanning [=====================> ] 3/6\r (no-eol) (esc)
62 62 scanning [============================> ] 4/6\r (no-eol) (esc)
63 63 scanning [===================================> ] 5/6\r (no-eol) (esc)
64 64 scanning [===========================================>] 6/6\r (no-eol) (esc)
65 65 \r (no-eol) (esc)
66 66 \r (no-eol) (esc)
67 67 converting [ ] 0/6\r (no-eol) (esc)
68 68 getting files [==================> ] 1/2\r (no-eol) (esc)
69 69 getting files [======================================>] 2/2\r (no-eol) (esc)
70 70 \r (no-eol) (esc)
71 71 \r (no-eol) (esc)
72 72 converting [======> ] 1/6\r (no-eol) (esc)
73 73 getting files [======================================>] 1/1\r (no-eol) (esc)
74 74 \r (no-eol) (esc)
75 75 \r (no-eol) (esc)
76 76 converting [=============> ] 2/6\r (no-eol) (esc)
77 77 getting files [======================================>] 1/1\r (no-eol) (esc)
78 78 \r (no-eol) (esc)
79 79 \r (no-eol) (esc)
80 80 converting [====================> ] 3/6\r (no-eol) (esc)
81 81 getting files [======================================>] 1/1\r (no-eol) (esc)
82 82 \r (no-eol) (esc)
83 83 \r (no-eol) (esc)
84 84 converting [===========================> ] 4/6\r (no-eol) (esc)
85 85 getting files [======================================>] 1/1\r (no-eol) (esc)
86 86 \r (no-eol) (esc)
87 87 \r (no-eol) (esc)
88 88 converting [==================================> ] 5/6\r (no-eol) (esc)
89 89 getting files [======================================>] 1/1\r (no-eol) (esc)
90 90 \r (no-eol) (esc)
91 91 assuming destination git-repo-hg
92 92 initializing destination git-repo-hg repository
93 93 scanning source...
94 94 sorting...
95 95 converting...
96 96 5 t1
97 97 4 t2
98 98 3 t3
99 99 2 t4.1
100 100 1 t4.2
101 101 0 Merge branch other
102 102 updating bookmarks
103 103 $ hg up -q -R git-repo-hg
104 104 $ hg -R git-repo-hg tip -v
105 105 changeset: 5:c78094926be2
106 106 bookmark: master
107 107 tag: tip
108 108 parent: 3:f5f5cb45432b
109 109 parent: 4:4e174f80c67c
110 110 user: test <test@example.org>
111 111 date: Mon Jan 01 00:00:15 2007 +0000
112 112 files: a
113 113 description:
114 114 Merge branch other
115 115
116 116
117 117 $ count=10
118 118 $ mkdir git-repo2
119 119 $ cd git-repo2
120 120 $ git init-db >/dev/null 2>/dev/null
121 121 $ echo foo > foo
122 122 $ git add foo
123 123 $ commit -a -m 'add foo'
124 124 $ echo >> foo
125 125 $ commit -a -m 'change foo'
126 126 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
127 127 $ echo quux >> quux
128 128 $ git add quux
129 129 $ commit -a -m 'add quux'
130 130 $ echo bar > bar
131 131 $ git add bar
132 132 $ commit -a -m 'add bar'
133 133 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
134 134 $ echo baz > baz
135 135 $ git add baz
136 136 $ commit -a -m 'add baz'
137 137 $ git checkout master >/dev/null 2>/dev/null
138 138 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
139 139 $ commit -m 'Octopus merge'
140 140 $ echo bar >> bar
141 141 $ commit -a -m 'change bar'
142 142 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
143 143 $ echo >> foo
144 144 $ commit -a -m 'change foo'
145 145 $ git checkout master >/dev/null 2>/dev/null
146 146 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
147 147 $ commit -m 'Discard change to foo'
148 148 $ cd ..
149 149 $ glog()
150 150 > {
151 151 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
152 152 > }
153 153 $ splitrepo()
154 154 > {
155 155 > msg="$1"
156 156 > files="$2"
157 157 > opts=$3
158 158 > echo "% $files: $msg"
159 159 > prefix=`echo "$files" | sed -e 's/ /-/g'`
160 160 > fmap="$prefix.fmap"
161 161 > repo="$prefix.repo"
162 162 > for i in $files; do
163 163 > echo "include $i" >> "$fmap"
164 164 > done
165 165 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
166 166 > hg up -q -R "$repo"
167 167 > glog -R "$repo"
168 168 > hg -R "$repo" manifest --debug
169 169 > }
170 170
171 171 full conversion
172 172
173 173 $ hg convert --datesort git-repo2 fullrepo \
174 174 > --config extensions.progress= --config progress.assume-tty=1 \
175 175 > --config progress.delay=0 --config progress.changedelay=0 \
176 176 > --config progress.refresh=0 --config progress.width=60 \
177 177 > --config progress.format='topic, bar, number'
178 178 \r (no-eol) (esc)
179 179 scanning [===> ] 1/9\r (no-eol) (esc)
180 180 scanning [========> ] 2/9\r (no-eol) (esc)
181 181 scanning [=============> ] 3/9\r (no-eol) (esc)
182 182 scanning [==================> ] 4/9\r (no-eol) (esc)
183 183 scanning [=======================> ] 5/9\r (no-eol) (esc)
184 184 scanning [============================> ] 6/9\r (no-eol) (esc)
185 185 scanning [=================================> ] 7/9\r (no-eol) (esc)
186 186 scanning [======================================> ] 8/9\r (no-eol) (esc)
187 187 scanning [===========================================>] 9/9\r (no-eol) (esc)
188 188 \r (no-eol) (esc)
189 189 \r (no-eol) (esc)
190 190 converting [ ] 0/9\r (no-eol) (esc)
191 191 getting files [======================================>] 1/1\r (no-eol) (esc)
192 192 \r (no-eol) (esc)
193 193 \r (no-eol) (esc)
194 194 converting [===> ] 1/9\r (no-eol) (esc)
195 195 getting files [======================================>] 1/1\r (no-eol) (esc)
196 196 \r (no-eol) (esc)
197 197 \r (no-eol) (esc)
198 198 converting [========> ] 2/9\r (no-eol) (esc)
199 199 getting files [======================================>] 1/1\r (no-eol) (esc)
200 200 \r (no-eol) (esc)
201 201 \r (no-eol) (esc)
202 202 converting [=============> ] 3/9\r (no-eol) (esc)
203 203 getting files [======================================>] 1/1\r (no-eol) (esc)
204 204 \r (no-eol) (esc)
205 205 \r (no-eol) (esc)
206 206 converting [=================> ] 4/9\r (no-eol) (esc)
207 207 getting files [======================================>] 1/1\r (no-eol) (esc)
208 208 \r (no-eol) (esc)
209 209 \r (no-eol) (esc)
210 210 converting [======================> ] 5/9\r (no-eol) (esc)
211 211 getting files [===> ] 1/8\r (no-eol) (esc)
212 212 getting files [========> ] 2/8\r (no-eol) (esc)
213 213 getting files [=============> ] 3/8\r (no-eol) (esc)
214 214 getting files [==================> ] 4/8\r (no-eol) (esc)
215 215 getting files [=======================> ] 5/8\r (no-eol) (esc)
216 216 getting files [============================> ] 6/8\r (no-eol) (esc)
217 217 getting files [=================================> ] 7/8\r (no-eol) (esc)
218 218 getting files [======================================>] 8/8\r (no-eol) (esc)
219 219 \r (no-eol) (esc)
220 220 \r (no-eol) (esc)
221 221 converting [===========================> ] 6/9\r (no-eol) (esc)
222 222 getting files [======================================>] 1/1\r (no-eol) (esc)
223 223 \r (no-eol) (esc)
224 224 \r (no-eol) (esc)
225 225 converting [===============================> ] 7/9\r (no-eol) (esc)
226 226 getting files [======================================>] 1/1\r (no-eol) (esc)
227 227 \r (no-eol) (esc)
228 228 \r (no-eol) (esc)
229 229 converting [====================================> ] 8/9\r (no-eol) (esc)
230 230 getting files [==================> ] 1/2\r (no-eol) (esc)
231 231 getting files [======================================>] 2/2\r (no-eol) (esc)
232 232 \r (no-eol) (esc)
233 233 initializing destination fullrepo repository
234 234 scanning source...
235 235 sorting...
236 236 converting...
237 237 8 add foo
238 238 7 change foo
239 239 6 add quux
240 240 5 add bar
241 241 4 add baz
242 242 3 Octopus merge
243 243 2 change bar
244 244 1 change foo
245 245 0 Discard change to foo
246 246 updating bookmarks
247 247 $ hg up -q -R fullrepo
248 248 $ glog -R fullrepo
249 249 @ 9 "Discard change to foo" files: foo
250 250 |\
251 251 | o 8 "change foo" files: foo
252 252 | |
253 253 o | 7 "change bar" files: bar
254 254 |/
255 255 o 6 "(octopus merge fixup)" files:
256 256 |\
257 257 | o 5 "Octopus merge" files: baz
258 258 | |\
259 259 o | | 4 "add baz" files: baz
260 260 | | |
261 261 +---o 3 "add bar" files: bar
262 262 | |
263 263 o | 2 "add quux" files: quux
264 264 | |
265 265 | o 1 "change foo" files: foo
266 266 |/
267 267 o 0 "add foo" files: foo
268 268
269 269 $ hg -R fullrepo manifest --debug
270 270 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
271 271 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
272 272 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
273 273 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
274 274 $ splitrepo 'octopus merge' 'foo bar baz'
275 275 % foo bar baz: octopus merge
276 276 @ 8 "Discard change to foo" files: foo
277 277 |\
278 278 | o 7 "change foo" files: foo
279 279 | |
280 280 o | 6 "change bar" files: bar
281 281 |/
282 282 o 5 "(octopus merge fixup)" files:
283 283 |\
284 284 | o 4 "Octopus merge" files: baz
285 285 | |\
286 286 o | | 3 "add baz" files: baz
287 287 | | |
288 288 +---o 2 "add bar" files: bar
289 289 | |
290 290 | o 1 "change foo" files: foo
291 291 |/
292 292 o 0 "add foo" files: foo
293 293
294 294 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
295 295 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
296 296 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
297 297 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
298 298 % foo baz quux: only some parents of an octopus merge; "discard" a head
299 299 @ 6 "Discard change to foo" files: foo
300 300 |
301 301 o 5 "change foo" files: foo
302 302 |
303 303 o 4 "Octopus merge" files:
304 304 |\
305 305 | o 3 "add baz" files: baz
306 306 | |
307 307 | o 2 "add quux" files: quux
308 308 | |
309 309 o | 1 "change foo" files: foo
310 310 |/
311 311 o 0 "add foo" files: foo
312 312
313 313 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
314 314 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
315 315 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
316 316
317 317 test importing git renames and copies
318 318
319 319 $ cd git-repo2
320 320 $ git mv foo foo-renamed
321 321 since bar is not touched in this commit, this copy will not be detected
322 322 $ cp bar bar-copied
323 323 $ cp baz baz-copied
324 324 $ cp baz baz-copied2
325 325 $ cp baz ba-copy
326 326 $ echo baz2 >> baz
327 327 $ git add bar-copied baz-copied baz-copied2 ba-copy
328 328 $ commit -a -m 'rename and copy'
329 329 $ cd ..
330 330
331 331 input validation
332 332 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
333 333 abort: convert.git.similarity is not an integer ('foo')
334 334 [255]
335 335 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
336 336 abort: similarity must be between 0 and 100
337 337 [255]
338 338 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
339 339 abort: similarity must be between 0 and 100
340 340 [255]
341 341
342 342 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
343 343 $ hg -R fullrepo status -C --change master
344 344 M baz
345 345 A ba-copy
346 346 baz
347 347 A bar-copied
348 348 A baz-copied
349 349 baz
350 350 A baz-copied2
351 351 baz
352 352 A foo-renamed
353 353 foo
354 354 R foo
355 355
356 356 Ensure that the modification to the copy source was preserved
357 357 (there was a bug where if the copy dest was alphabetically prior to the copy
358 358 source, the copy source took the contents of the copy dest)
359 359 $ hg cat -r tip fullrepo/baz
360 360 baz
361 361 baz2
362 362
363 363 $ cd git-repo2
364 364 $ echo bar2 >> bar
365 365 $ commit -a -m 'change bar'
366 366 $ cp bar bar-copied2
367 367 $ git add bar-copied2
368 368 $ commit -a -m 'copy with no changes'
369 369 $ cd ..
370 370
371 371 $ hg -q convert --config convert.git.similarity=100 \
372 372 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
373 373 $ hg -R fullrepo status -C --change master
374 374 A bar-copied2
375 375 bar
376 376
377 377 renamelimit config option works
378 378
379 379 $ cd git-repo2
380 380 $ cat >> copy-source << EOF
381 381 > sc0
382 382 > sc1
383 383 > sc2
384 384 > sc3
385 385 > sc4
386 386 > sc5
387 387 > sc6
388 388 > EOF
389 389 $ git add copy-source
390 390 $ commit -m 'add copy-source'
391 391 $ cp copy-source source-copy0
392 392 $ echo 0 >> source-copy0
393 393 $ cp copy-source source-copy1
394 394 $ echo 1 >> source-copy1
395 395 $ git add source-copy0 source-copy1
396 396 $ commit -a -m 'copy copy-source 2 times'
397 397 $ cd ..
398 398
399 399 $ hg -q convert --config convert.git.renamelimit=1 \
400 400 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo2
401 401 $ hg -R fullrepo2 status -C --change master
402 402 A source-copy0
403 403 A source-copy1
404 404
405 405 $ hg -q convert --config convert.git.renamelimit=100 \
406 406 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo3
407 407 $ hg -R fullrepo3 status -C --change master
408 408 A source-copy0
409 409 copy-source
410 410 A source-copy1
411 411 copy-source
412 412
413 413 test binary conversion (issue1359)
414 414
415 415 $ count=19
416 416 $ mkdir git-repo3
417 417 $ cd git-repo3
418 418 $ git init-db >/dev/null 2>/dev/null
419 419 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
420 420 $ git add b
421 421 $ commit -a -m addbinary
422 422 $ cd ..
423 423
424 424 convert binary file
425 425
426 426 $ hg convert git-repo3 git-repo3-hg
427 427 initializing destination git-repo3-hg repository
428 428 scanning source...
429 429 sorting...
430 430 converting...
431 431 0 addbinary
432 432 updating bookmarks
433 433 $ cd git-repo3-hg
434 434 $ hg up -C
435 435 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 436 $ $PYTHON -c 'print len(file("b", "rb").read())'
437 437 4096
438 438 $ cd ..
439 439
440 440 test author vs committer
441 441
442 442 $ mkdir git-repo4
443 443 $ cd git-repo4
444 444 $ git init-db >/dev/null 2>/dev/null
445 445 $ echo >> foo
446 446 $ git add foo
447 447 $ commit -a -m addfoo
448 448 $ echo >> foo
449 449 $ GIT_AUTHOR_NAME="nottest"
450 450 $ commit -a -m addfoo2
451 451 $ cd ..
452 452
453 453 convert author committer
454 454
455 455 $ hg convert git-repo4 git-repo4-hg
456 456 initializing destination git-repo4-hg repository
457 457 scanning source...
458 458 sorting...
459 459 converting...
460 460 1 addfoo
461 461 0 addfoo2
462 462 updating bookmarks
463 463 $ hg -R git-repo4-hg log -v
464 464 changeset: 1:d63e967f93da
465 465 bookmark: master
466 466 tag: tip
467 467 user: nottest <test@example.org>
468 468 date: Mon Jan 01 00:00:21 2007 +0000
469 469 files: foo
470 470 description:
471 471 addfoo2
472 472
473 473 committer: test <test@example.org>
474 474
475 475
476 476 changeset: 0:0735477b0224
477 477 user: test <test@example.org>
478 478 date: Mon Jan 01 00:00:20 2007 +0000
479 479 files: foo
480 480 description:
481 481 addfoo
482 482
483 483
484 484
485 485 Various combinations of committeractions fail
486 486
487 487 $ hg --config convert.git.committeractions=messagedifferent,messagealways convert git-repo4 bad-committer
488 488 initializing destination bad-committer repository
489 489 abort: committeractions cannot define both messagedifferent and messagealways
490 490 [255]
491 491
492 492 $ hg --config convert.git.committeractions=dropcommitter,replaceauthor convert git-repo4 bad-committer
493 493 initializing destination bad-committer repository
494 abort: committeractions cannot define both dropcommitter and replaceauthor/replacecommitter
495 [255]
496
497 $ hg --config convert.git.committeractions=dropcommitter,replacecommitter convert git-repo4 bad-committer
498 initializing destination bad-committer repository
499 abort: committeractions cannot define both dropcommitter and replaceauthor/replacecommitter
494 abort: committeractions cannot define both dropcommitter and replaceauthor
500 495 [255]
501 496
502 497 $ hg --config convert.git.committeractions=dropcommitter,messagealways convert git-repo4 bad-committer
503 498 initializing destination bad-committer repository
504 499 abort: committeractions cannot define both dropcommitter and messagealways
505 500 [255]
506 501
507 $ hg --config convert.git.committeractions=replaceauthor,replacecommitter convert git-repo4 bad-committer
508 initializing destination bad-committer repository
509 abort: committeractions cannot define both replaceauthor and replacecommitter
510 [255]
511
512 502 custom prefix on messagedifferent works
513 503
514 504 $ hg --config convert.git.committeractions=messagedifferent=different: convert git-repo4 git-repo4-hg-messagedifferentprefix
515 505 initializing destination git-repo4-hg-messagedifferentprefix repository
516 506 scanning source...
517 507 sorting...
518 508 converting...
519 509 1 addfoo
520 510 0 addfoo2
521 511 updating bookmarks
522 512
523 513 $ hg -R git-repo4-hg-messagedifferentprefix log -v
524 514 changeset: 1:2fe0c98a109d
525 515 bookmark: master
526 516 tag: tip
527 517 user: nottest <test@example.org>
528 518 date: Mon Jan 01 00:00:21 2007 +0000
529 519 files: foo
530 520 description:
531 521 addfoo2
532 522
533 523 different: test <test@example.org>
534 524
535 525
536 526 changeset: 0:0735477b0224
537 527 user: test <test@example.org>
538 528 date: Mon Jan 01 00:00:20 2007 +0000
539 529 files: foo
540 530 description:
541 531 addfoo
542 532
543 533
544 534
545 535 messagealways will always add the "committer: " line even if committer identical
546 536
547 537 $ hg --config convert.git.committeractions=messagealways convert git-repo4 git-repo4-hg-messagealways
548 538 initializing destination git-repo4-hg-messagealways repository
549 539 scanning source...
550 540 sorting...
551 541 converting...
552 542 1 addfoo
553 543 0 addfoo2
554 544 updating bookmarks
555 545
556 546 $ hg -R git-repo4-hg-messagealways log -v
557 547 changeset: 1:8db057d8cd37
558 548 bookmark: master
559 549 tag: tip
560 550 user: nottest <test@example.org>
561 551 date: Mon Jan 01 00:00:21 2007 +0000
562 552 files: foo
563 553 description:
564 554 addfoo2
565 555
566 556 committer: test <test@example.org>
567 557
568 558
569 559 changeset: 0:8f71fe9c98be
570 560 user: test <test@example.org>
571 561 date: Mon Jan 01 00:00:20 2007 +0000
572 562 files: foo
573 563 description:
574 564 addfoo
575 565
576 566 committer: test <test@example.org>
577 567
578 568
579 569
580 570 custom prefix on messagealways works
581 571
582 572 $ hg --config convert.git.committeractions=messagealways=always: convert git-repo4 git-repo4-hg-messagealwaysprefix
583 573 initializing destination git-repo4-hg-messagealwaysprefix repository
584 574 scanning source...
585 575 sorting...
586 576 converting...
587 577 1 addfoo
588 578 0 addfoo2
589 579 updating bookmarks
590 580
591 581 $ hg -R git-repo4-hg-messagealwaysprefix log -v
592 582 changeset: 1:83c17174de79
593 583 bookmark: master
594 584 tag: tip
595 585 user: nottest <test@example.org>
596 586 date: Mon Jan 01 00:00:21 2007 +0000
597 587 files: foo
598 588 description:
599 589 addfoo2
600 590
601 591 always: test <test@example.org>
602 592
603 593
604 594 changeset: 0:2ac9bcb3534a
605 595 user: test <test@example.org>
606 596 date: Mon Jan 01 00:00:20 2007 +0000
607 597 files: foo
608 598 description:
609 599 addfoo
610 600
611 601 always: test <test@example.org>
612 602
613 603
614 604
615 605 replaceauthor replaces author with committer
616 606
617 607 $ hg --config convert.git.committeractions=replaceauthor convert git-repo4 git-repo4-hg-replaceauthor
618 608 initializing destination git-repo4-hg-replaceauthor repository
619 609 scanning source...
620 610 sorting...
621 611 converting...
622 612 1 addfoo
623 613 0 addfoo2
624 614 updating bookmarks
625 615
626 616 $ hg -R git-repo4-hg-replaceauthor log -v
627 617 changeset: 1:122c1d8999ea
628 618 bookmark: master
629 619 tag: tip
630 620 user: test <test@example.org>
631 621 date: Mon Jan 01 00:00:21 2007 +0000
632 622 files: foo
633 623 description:
634 624 addfoo2
635 625
636 626
637 627 changeset: 0:0735477b0224
638 628 user: test <test@example.org>
639 629 date: Mon Jan 01 00:00:20 2007 +0000
640 630 files: foo
641 631 description:
642 632 addfoo
643 633
644 634
645 635
646 replacecommitter replaces committer with author
647
648 $ hg --config convert.git.committeractions=replacecommitter convert git-repo4 git-repo4-hg-replacecommitter
649 initializing destination git-repo4-hg-replacecommitter repository
650 scanning source...
651 sorting...
652 converting...
653 1 addfoo
654 0 addfoo2
655 updating bookmarks
656
657 $ hg -R git-repo4-hg-replacecommitter log -v
658 changeset: 1:190b2da396cc
659 bookmark: master
660 tag: tip
661 user: nottest <test@example.org>
662 date: Mon Jan 01 00:00:21 2007 +0000
663 files: foo
664 description:
665 addfoo2
666
667
668 changeset: 0:0735477b0224
669 user: test <test@example.org>
670 date: Mon Jan 01 00:00:20 2007 +0000
671 files: foo
672 description:
673 addfoo
674
675
676
677 636 dropcommitter removes the committer
678 637
679 638 $ hg --config convert.git.committeractions=dropcommitter convert git-repo4 git-repo4-hg-dropcommitter
680 639 initializing destination git-repo4-hg-dropcommitter repository
681 640 scanning source...
682 641 sorting...
683 642 converting...
684 643 1 addfoo
685 644 0 addfoo2
686 645 updating bookmarks
687 646
688 647 $ hg -R git-repo4-hg-dropcommitter log -v
689 648 changeset: 1:190b2da396cc
690 649 bookmark: master
691 650 tag: tip
692 651 user: nottest <test@example.org>
693 652 date: Mon Jan 01 00:00:21 2007 +0000
694 653 files: foo
695 654 description:
696 655 addfoo2
697 656
698 657
699 658 changeset: 0:0735477b0224
700 659 user: test <test@example.org>
701 660 date: Mon Jan 01 00:00:20 2007 +0000
702 661 files: foo
703 662 description:
704 663 addfoo
705 664
706 665
707 666
708 667 --sourceorder should fail
709 668
710 669 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
711 670 initializing destination git-repo4-sourcesort-hg repository
712 671 abort: --sourcesort is not supported by this data source
713 672 [255]
714 673
715 674 test converting certain branches
716 675
717 676 $ mkdir git-testrevs
718 677 $ cd git-testrevs
719 678 $ git init
720 679 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
721 680 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
722 681 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
723 682 $ git checkout -b goodbranch 'HEAD^'
724 683 Switched to a new branch 'goodbranch'
725 684 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
726 685 $ git checkout -b badbranch 'HEAD^'
727 686 Switched to a new branch 'badbranch'
728 687 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
729 688 $ cd ..
730 689 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
731 690 initializing destination hg-testrevs repository
732 691 scanning source...
733 692 sorting...
734 693 converting...
735 694 2 first
736 695 1 good branch commit
737 696 0 master commit
738 697 updating bookmarks
739 698 $ cd hg-testrevs
740 699 $ hg log -G -T '{rev} {bookmarks}'
741 700 o 2 master
742 701 |
743 702 | o 1 goodbranch
744 703 |/
745 704 o 0
746 705
747 706 $ cd ..
748 707
749 708 test sub modules
750 709
751 710 $ mkdir git-repo5
752 711 $ cd git-repo5
753 712 $ git init-db >/dev/null 2>/dev/null
754 713 $ echo 'sub' >> foo
755 714 $ git add foo
756 715 $ commit -a -m 'addfoo'
757 716 $ BASE=`pwd`
758 717 $ cd ..
759 718 $ mkdir git-repo6
760 719 $ cd git-repo6
761 720 $ git init-db >/dev/null 2>/dev/null
762 721 $ git submodule add ${BASE} >/dev/null 2>/dev/null
763 722 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
764 723
765 724 test non-tab whitespace .gitmodules
766 725
767 726 $ cat >> .gitmodules <<EOF
768 727 > [submodule "git-repo5"]
769 728 > path = git-repo5
770 729 > url = git-repo5
771 730 > EOF
772 731 $ git commit -q -a -m "weird white space submodule"
773 732 $ cd ..
774 733 $ hg convert git-repo6 hg-repo6
775 734 initializing destination hg-repo6 repository
776 735 scanning source...
777 736 sorting...
778 737 converting...
779 738 1 addsubmodule
780 739 0 weird white space submodule
781 740 updating bookmarks
782 741
783 742 $ rm -rf hg-repo6
784 743 $ cd git-repo6
785 744 $ git reset --hard 'HEAD^' > /dev/null
786 745
787 746 test missing .gitmodules
788 747
789 748 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
790 749 $ git checkout HEAD .gitmodules
791 750 $ git rm .gitmodules
792 751 rm '.gitmodules'
793 752 $ git commit -q -m "remove .gitmodules" .gitmodules
794 753 $ git commit -q -m "missing .gitmodules"
795 754 $ cd ..
796 755 $ hg convert git-repo6 hg-repo6 --traceback 2>&1 | grep -v "fatal: Path '.gitmodules' does not exist"
797 756 initializing destination hg-repo6 repository
798 757 scanning source...
799 758 sorting...
800 759 converting...
801 760 2 addsubmodule
802 761 1 remove .gitmodules
803 762 0 missing .gitmodules
804 763 warning: cannot read submodules config file in * (glob)
805 764 updating bookmarks
806 765 $ rm -rf hg-repo6
807 766 $ cd git-repo6
808 767 $ rm -rf git-repo4
809 768 $ git reset --hard 'HEAD^^' > /dev/null
810 769 $ cd ..
811 770
812 771 test invalid splicemap1
813 772
814 773 $ cat > splicemap <<EOF
815 774 > $VALIDID1
816 775 > EOF
817 776 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
818 777 initializing destination git-repo2-splicemap1-hg repository
819 778 abort: syntax error in splicemap(1): child parent1[,parent2] expected
820 779 [255]
821 780
822 781 test invalid splicemap2
823 782
824 783 $ cat > splicemap <<EOF
825 784 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
826 785 > EOF
827 786 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
828 787 initializing destination git-repo2-splicemap2-hg repository
829 788 abort: syntax error in splicemap(1): child parent1[,parent2] expected
830 789 [255]
831 790
832 791 test invalid splicemap3
833 792
834 793 $ cat > splicemap <<EOF
835 794 > $INVALIDID1 $INVALIDID2
836 795 > EOF
837 796 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
838 797 initializing destination git-repo2-splicemap3-hg repository
839 798 abort: splicemap entry afd12345af is not a valid revision identifier
840 799 [255]
841 800
842 801 convert sub modules
843 802 $ hg convert git-repo6 git-repo6-hg
844 803 initializing destination git-repo6-hg repository
845 804 scanning source...
846 805 sorting...
847 806 converting...
848 807 0 addsubmodule
849 808 updating bookmarks
850 809 $ hg -R git-repo6-hg log -v
851 810 changeset: 0:* (glob)
852 811 bookmark: master
853 812 tag: tip
854 813 user: nottest <test@example.org>
855 814 date: Mon Jan 01 00:00:23 2007 +0000
856 815 files: .hgsub .hgsubstate
857 816 description:
858 817 addsubmodule
859 818
860 819 committer: test <test@example.org>
861 820
862 821
863 822
864 823 $ cd git-repo6-hg
865 824 $ hg up >/dev/null 2>/dev/null
866 825 $ cat .hgsubstate
867 826 * git-repo5 (glob)
868 827 $ cd git-repo5
869 828 $ cat foo
870 829 sub
871 830
872 831 $ cd ../..
873 832
874 833 make sure rename detection doesn't break removing and adding gitmodules
875 834
876 835 $ cd git-repo6
877 836 $ git mv .gitmodules .gitmodules-renamed
878 837 $ commit -a -m 'rename .gitmodules'
879 838 $ git mv .gitmodules-renamed .gitmodules
880 839 $ commit -a -m 'rename .gitmodules back'
881 840 $ cd ..
882 841
883 842 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
884 843 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
885 844 rename .gitmodules
886 845 $ hg -R git-repo6-hg status -C --change 'tip^'
887 846 A .gitmodules-renamed
888 847 R .hgsub
889 848 R .hgsubstate
890 849 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
891 850 rename .gitmodules back
892 851 $ hg -R git-repo6-hg status -C --change tip
893 852 A .hgsub
894 853 A .hgsubstate
895 854 R .gitmodules-renamed
896 855
897 856 convert the revision removing '.gitmodules' itself (and related
898 857 submodules)
899 858
900 859 $ cd git-repo6
901 860 $ git rm .gitmodules
902 861 rm '.gitmodules'
903 862 $ git rm --cached git-repo5
904 863 rm 'git-repo5'
905 864 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
906 865 $ cd ..
907 866
908 867 $ hg convert -q git-repo6 git-repo6-hg
909 868 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
910 869 remove .gitmodules and submodule git-repo5
911 870 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
912 871 .hgsub .hgsubstate
913 872
914 873 skip submodules in the conversion
915 874
916 875 $ hg convert -q git-repo6 no-submodules --config convert.git.skipsubmodules=True
917 876 $ hg -R no-submodules manifest --all
918 877 .gitmodules-renamed
919 878
920 879 convert using a different remote prefix
921 880 $ git init git-repo7
922 881 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
923 882 $ cd git-repo7
924 883 TODO: it'd be nice to use (?) lines instead of grep -v to handle the
925 884 git output variance, but that doesn't currently work in the middle of
926 885 a block, so do this for now.
927 886 $ touch a && git add a && git commit -am "commit a" | grep -v changed
928 887 [master (root-commit) 8ae5f69] commit a
929 888 Author: nottest <test@example.org>
930 889 create mode 100644 a
931 890 $ cd ..
932 891 $ git clone git-repo7 git-repo7-client
933 892 Cloning into 'git-repo7-client'...
934 893 done.
935 894 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
936 895 initializing destination hg-repo7 repository
937 896 scanning source...
938 897 sorting...
939 898 converting...
940 899 0 commit a
941 900 updating bookmarks
942 901 $ hg -R hg-repo7 bookmarks
943 902 master 0:03bf38caa4c6
944 903 origin/master 0:03bf38caa4c6
945 904
946 905 Run convert when the remote branches have changed
947 906 (there was an old bug where the local convert read branches from the server)
948 907
949 908 $ cd git-repo7
950 909 $ echo a >> a
951 910 $ git commit -q -am "move master forward"
952 911 $ cd ..
953 912 $ rm -rf hg-repo7
954 913 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
955 914 initializing destination hg-repo7 repository
956 915 scanning source...
957 916 sorting...
958 917 converting...
959 918 0 commit a
960 919 updating bookmarks
961 920 $ hg -R hg-repo7 bookmarks
962 921 master 0:03bf38caa4c6
963 922 origin/master 0:03bf38caa4c6
964 923
965 924 damaged git repository tests:
966 925 In case the hard-coded hashes change, the following commands can be used to
967 926 list the hashes and their corresponding types in the repository:
968 927 cd git-repo4/.git/objects
969 928 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
970 929 cd ../../..
971 930
972 931 damage git repository by renaming a commit object
973 932 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
974 933 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
975 934 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
976 935 abort: cannot retrieve number of commits in $TESTTMP/git-repo4/.git (glob)
977 936 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
978 937 damage git repository by renaming a blob object
979 938
980 939 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
981 940 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
982 941 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
983 942 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
984 943 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
985 944 damage git repository by renaming a tree object
986 945
987 946 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
988 947 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
989 948 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
990 949 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
991 950
992 951 #if no-windows
993 952
994 953 test for escaping the repo name (CVE-2016-3069)
995 954
996 955 $ git init '`echo pwned >COMMAND-INJECTION`'
997 956 Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/
998 957 $ cd '`echo pwned >COMMAND-INJECTION`'
999 958 $ git commit -q --allow-empty -m 'empty'
1000 959 $ cd ..
1001 960 $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted'
1002 961 initializing destination converted repository
1003 962 scanning source...
1004 963 sorting...
1005 964 converting...
1006 965 0 empty
1007 966 updating bookmarks
1008 967 $ test -f COMMAND-INJECTION
1009 968 [1]
1010 969
1011 970 test for safely passing paths to git (CVE-2016-3105)
1012 971
1013 972 $ git init 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
1014 973 Initialized empty Git repository in $TESTTMP/ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #/.git/
1015 974 $ cd 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
1016 975 $ git commit -q --allow-empty -m 'empty'
1017 976 $ cd ..
1018 977 $ hg convert 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #' 'converted-git-ext'
1019 978 initializing destination converted-git-ext repository
1020 979 scanning source...
1021 980 sorting...
1022 981 converting...
1023 982 0 empty
1024 983 updating bookmarks
1025 984 $ test -f GIT-EXT-COMMAND-INJECTION
1026 985 [1]
1027 986
1028 987 #endif
1029 988
1030 989 Conversion of extra commit metadata to extras works
1031 990
1032 991 $ git init gitextras >/dev/null 2>/dev/null
1033 992 $ cd gitextras
1034 993 $ touch foo
1035 994 $ git add foo
1036 995 $ commit -m initial
1037 996 $ echo 1 > foo
1038 997 $ tree=`git write-tree`
1039 998
1040 999 Git doesn't provider a user-facing API to write extra metadata into the
1041 1000 commit, so create the commit object by hand
1042 1001
1043 1002 $ git hash-object -t commit -w --stdin << EOF
1044 1003 > tree ${tree}
1045 1004 > parent ba6b1344e977ece9e00958dbbf17f1f09384b2c1
1046 1005 > author test <test@example.com> 1000000000 +0000
1047 1006 > committer test <test@example.com> 1000000000 +0000
1048 1007 > extra-1 extra-1
1049 1008 > extra-2 extra-2 with space
1050 1009 > convert_revision 0000aaaabbbbccccddddeeee
1051 1010 >
1052 1011 > message with extras
1053 1012 > EOF
1054 1013 8123727c8361a4117d1a2d80e0c4e7d70c757f18
1055 1014
1056 1015 $ git reset --hard 8123727c8361a4117d1a2d80e0c4e7d70c757f18 > /dev/null
1057 1016
1058 1017 $ cd ..
1059 1018
1060 1019 convert will not retain custom metadata keys by default
1061 1020
1062 1021 $ hg convert gitextras hgextras1
1063 1022 initializing destination hgextras1 repository
1064 1023 scanning source...
1065 1024 sorting...
1066 1025 converting...
1067 1026 1 initial
1068 1027 0 message with extras
1069 1028 updating bookmarks
1070 1029
1071 1030 $ hg -R hgextras1 log --debug -r 1
1072 1031 changeset: 1:e13a39880f68479127b2a80fa0b448cc8524aa09
1073 1032 bookmark: master
1074 1033 tag: tip
1075 1034 phase: draft
1076 1035 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1077 1036 parent: -1:0000000000000000000000000000000000000000
1078 1037 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1079 1038 user: test <test@example.com>
1080 1039 date: Sun Sep 09 01:46:40 2001 +0000
1081 1040 extra: branch=default
1082 1041 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1083 1042 description:
1084 1043 message with extras
1085 1044
1086 1045
1087 1046
1088 1047 Attempting to convert a banned extra is disallowed
1089 1048
1090 1049 $ hg convert --config convert.git.extrakeys=tree,parent gitextras hgextras-banned
1091 1050 initializing destination hgextras-banned repository
1092 1051 abort: copying of extra key is forbidden: parent, tree
1093 1052 [255]
1094 1053
1095 1054 Converting a specific extra works
1096 1055
1097 1056 $ hg convert --config convert.git.extrakeys=extra-1 gitextras hgextras2
1098 1057 initializing destination hgextras2 repository
1099 1058 scanning source...
1100 1059 sorting...
1101 1060 converting...
1102 1061 1 initial
1103 1062 0 message with extras
1104 1063 updating bookmarks
1105 1064
1106 1065 $ hg -R hgextras2 log --debug -r 1
1107 1066 changeset: 1:d40fb205d58597e6ecfd55b16f198be5bf436391
1108 1067 bookmark: master
1109 1068 tag: tip
1110 1069 phase: draft
1111 1070 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1112 1071 parent: -1:0000000000000000000000000000000000000000
1113 1072 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1114 1073 user: test <test@example.com>
1115 1074 date: Sun Sep 09 01:46:40 2001 +0000
1116 1075 extra: branch=default
1117 1076 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1118 1077 extra: extra-1=extra-1
1119 1078 description:
1120 1079 message with extras
1121 1080
1122 1081
1123 1082
1124 1083 Converting multiple extras works
1125 1084
1126 1085 $ hg convert --config convert.git.extrakeys=extra-1,extra-2 gitextras hgextras3
1127 1086 initializing destination hgextras3 repository
1128 1087 scanning source...
1129 1088 sorting...
1130 1089 converting...
1131 1090 1 initial
1132 1091 0 message with extras
1133 1092 updating bookmarks
1134 1093
1135 1094 $ hg -R hgextras3 log --debug -r 1
1136 1095 changeset: 1:0105af33379e7b6491501fd34141b7af700fe125
1137 1096 bookmark: master
1138 1097 tag: tip
1139 1098 phase: draft
1140 1099 parent: 0:dcb68977c55cd02cbd13b901df65c4b6e7b9c4b9
1141 1100 parent: -1:0000000000000000000000000000000000000000
1142 1101 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1143 1102 user: test <test@example.com>
1144 1103 date: Sun Sep 09 01:46:40 2001 +0000
1145 1104 extra: branch=default
1146 1105 extra: convert_revision=8123727c8361a4117d1a2d80e0c4e7d70c757f18
1147 1106 extra: extra-1=extra-1
1148 1107 extra: extra-2=extra-2 with space
1149 1108 description:
1150 1109 message with extras
1151 1110
1152 1111
1153 1112
1154 1113 convert.git.saverev can be disabled to prevent convert_revision from being written
1155 1114
1156 1115 $ hg convert --config convert.git.saverev=false gitextras hgextras4
1157 1116 initializing destination hgextras4 repository
1158 1117 scanning source...
1159 1118 sorting...
1160 1119 converting...
1161 1120 1 initial
1162 1121 0 message with extras
1163 1122 updating bookmarks
1164 1123
1165 1124 $ hg -R hgextras4 log --debug -r 1
1166 1125 changeset: 1:1dcaf4ffe5bee43fa86db2800821f6f0af212c5c
1167 1126 bookmark: master
1168 1127 tag: tip
1169 1128 phase: draft
1170 1129 parent: 0:a13935fec4daf06a5a87a7307ccb0fc94f98d06d
1171 1130 parent: -1:0000000000000000000000000000000000000000
1172 1131 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1173 1132 user: test <test@example.com>
1174 1133 date: Sun Sep 09 01:46:40 2001 +0000
1175 1134 extra: branch=default
1176 1135 description:
1177 1136 message with extras
1178 1137
1179 1138
1180 1139
1181 1140 convert.git.saverev and convert.git.extrakeys can be combined to preserve
1182 1141 convert_revision from source
1183 1142
1184 1143 $ hg convert --config convert.git.saverev=false --config convert.git.extrakeys=convert_revision gitextras hgextras5
1185 1144 initializing destination hgextras5 repository
1186 1145 scanning source...
1187 1146 sorting...
1188 1147 converting...
1189 1148 1 initial
1190 1149 0 message with extras
1191 1150 updating bookmarks
1192 1151
1193 1152 $ hg -R hgextras5 log --debug -r 1
1194 1153 changeset: 1:574d85931544d4542007664fee3747360e85ee28
1195 1154 bookmark: master
1196 1155 tag: tip
1197 1156 phase: draft
1198 1157 parent: 0:a13935fec4daf06a5a87a7307ccb0fc94f98d06d
1199 1158 parent: -1:0000000000000000000000000000000000000000
1200 1159 manifest: 0:6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50
1201 1160 user: test <test@example.com>
1202 1161 date: Sun Sep 09 01:46:40 2001 +0000
1203 1162 extra: branch=default
1204 1163 extra: convert_revision=0000aaaabbbbccccddddeeee
1205 1164 description:
1206 1165 message with extras
1207 1166
1208 1167
@@ -1,586 +1,583
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > convert=
4 4 > [convert]
5 5 > hg.saverev=False
6 6 > EOF
7 7 $ hg help convert
8 8 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
9 9
10 10 convert a foreign SCM repository to a Mercurial one.
11 11
12 12 Accepted source formats [identifiers]:
13 13
14 14 - Mercurial [hg]
15 15 - CVS [cvs]
16 16 - Darcs [darcs]
17 17 - git [git]
18 18 - Subversion [svn]
19 19 - Monotone [mtn]
20 20 - GNU Arch [gnuarch]
21 21 - Bazaar [bzr]
22 22 - Perforce [p4]
23 23
24 24 Accepted destination formats [identifiers]:
25 25
26 26 - Mercurial [hg]
27 27 - Subversion [svn] (history on branches is not preserved)
28 28
29 29 If no revision is given, all revisions will be converted. Otherwise,
30 30 convert will only import up to the named revision (given in a format
31 31 understood by the source).
32 32
33 33 If no destination directory name is specified, it defaults to the basename
34 34 of the source with "-hg" appended. If the destination repository doesn't
35 35 exist, it will be created.
36 36
37 37 By default, all sources except Mercurial will use --branchsort. Mercurial
38 38 uses --sourcesort to preserve original revision numbers order. Sort modes
39 39 have the following effects:
40 40
41 41 --branchsort convert from parent to child revision when possible, which
42 42 means branches are usually converted one after the other.
43 43 It generates more compact repositories.
44 44 --datesort sort revisions by date. Converted repositories have good-
45 45 looking changelogs but are often an order of magnitude
46 46 larger than the same ones generated by --branchsort.
47 47 --sourcesort try to preserve source revisions order, only supported by
48 48 Mercurial sources.
49 49 --closesort try to move closed revisions as close as possible to parent
50 50 branches, only supported by Mercurial sources.
51 51
52 52 If "REVMAP" isn't given, it will be put in a default location
53 53 ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
54 54 maps each source commit ID to the destination ID for that revision, like
55 55 so:
56 56
57 57 <source ID> <destination ID>
58 58
59 59 If the file doesn't exist, it's automatically created. It's updated on
60 60 each commit copied, so 'hg convert' can be interrupted and can be run
61 61 repeatedly to copy new commits.
62 62
63 63 The authormap is a simple text file that maps each source commit author to
64 64 a destination commit author. It is handy for source SCMs that use unix
65 65 logins to identify authors (e.g.: CVS). One line per author mapping and
66 66 the line format is:
67 67
68 68 source author = destination author
69 69
70 70 Empty lines and lines starting with a "#" are ignored.
71 71
72 72 The filemap is a file that allows filtering and remapping of files and
73 73 directories. Each line can contain one of the following directives:
74 74
75 75 include path/to/file-or-dir
76 76
77 77 exclude path/to/file-or-dir
78 78
79 79 rename path/to/source path/to/destination
80 80
81 81 Comment lines start with "#". A specified path matches if it equals the
82 82 full relative name of a file or one of its parent directories. The
83 83 "include" or "exclude" directive with the longest matching path applies,
84 84 so line order does not matter.
85 85
86 86 The "include" directive causes a file, or all files under a directory, to
87 87 be included in the destination repository. The default if there are no
88 88 "include" statements is to include everything. If there are any "include"
89 89 statements, nothing else is included. The "exclude" directive causes files
90 90 or directories to be omitted. The "rename" directive renames a file or
91 91 directory if it is converted. To rename from a subdirectory into the root
92 92 of the repository, use "." as the path to rename to.
93 93
94 94 "--full" will make sure the converted changesets contain exactly the right
95 95 files with the right content. It will make a full conversion of all files,
96 96 not just the ones that have changed. Files that already are correct will
97 97 not be changed. This can be used to apply filemap changes when converting
98 98 incrementally. This is currently only supported for Mercurial and
99 99 Subversion.
100 100
101 101 The splicemap is a file that allows insertion of synthetic history,
102 102 letting you specify the parents of a revision. This is useful if you want
103 103 to e.g. give a Subversion merge two parents, or graft two disconnected
104 104 series of history together. Each entry contains a key, followed by a
105 105 space, followed by one or two comma-separated values:
106 106
107 107 key parent1, parent2
108 108
109 109 The key is the revision ID in the source revision control system whose
110 110 parents should be modified (same format as a key in .hg/shamap). The
111 111 values are the revision IDs (in either the source or destination revision
112 112 control system) that should be used as the new parents for that node. For
113 113 example, if you have merged "release-1.0" into "trunk", then you should
114 114 specify the revision on "trunk" as the first parent and the one on the
115 115 "release-1.0" branch as the second.
116 116
117 117 The branchmap is a file that allows you to rename a branch when it is
118 118 being brought in from whatever external repository. When used in
119 119 conjunction with a splicemap, it allows for a powerful combination to help
120 120 fix even the most badly mismanaged repositories and turn them into nicely
121 121 structured Mercurial repositories. The branchmap contains lines of the
122 122 form:
123 123
124 124 original_branch_name new_branch_name
125 125
126 126 where "original_branch_name" is the name of the branch in the source
127 127 repository, and "new_branch_name" is the name of the branch is the
128 128 destination repository. No whitespace is allowed in the branch names. This
129 129 can be used to (for instance) move code in one repository from "default"
130 130 to a named branch.
131 131
132 132 Mercurial Source
133 133 ################
134 134
135 135 The Mercurial source recognizes the following configuration options, which
136 136 you can set on the command line with "--config":
137 137
138 138 convert.hg.ignoreerrors
139 139 ignore integrity errors when reading. Use it to fix
140 140 Mercurial repositories with missing revlogs, by converting
141 141 from and to Mercurial. Default is False.
142 142 convert.hg.saverev
143 143 store original revision ID in changeset (forces target IDs
144 144 to change). It takes a boolean argument and defaults to
145 145 False.
146 146 convert.hg.startrev
147 147 specify the initial Mercurial revision. The default is 0.
148 148 convert.hg.revs
149 149 revset specifying the source revisions to convert.
150 150
151 151 CVS Source
152 152 ##########
153 153
154 154 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
155 155 indicate the starting point of what will be converted. Direct access to
156 156 the repository files is not needed, unless of course the repository is
157 157 ":local:". The conversion uses the top level directory in the sandbox to
158 158 find the CVS repository, and then uses CVS rlog commands to find files to
159 159 convert. This means that unless a filemap is given, all files under the
160 160 starting directory will be converted, and that any directory
161 161 reorganization in the CVS sandbox is ignored.
162 162
163 163 The following options can be used with "--config":
164 164
165 165 convert.cvsps.cache
166 166 Set to False to disable remote log caching, for testing and
167 167 debugging purposes. Default is True.
168 168 convert.cvsps.fuzz
169 169 Specify the maximum time (in seconds) that is allowed
170 170 between commits with identical user and log message in a
171 171 single changeset. When very large files were checked in as
172 172 part of a changeset then the default may not be long enough.
173 173 The default is 60.
174 174 convert.cvsps.mergeto
175 175 Specify a regular expression to which commit log messages
176 176 are matched. If a match occurs, then the conversion process
177 177 will insert a dummy revision merging the branch on which
178 178 this log message occurs to the branch indicated in the
179 179 regex. Default is "{{mergetobranch ([-\w]+)}}"
180 180 convert.cvsps.mergefrom
181 181 Specify a regular expression to which commit log messages
182 182 are matched. If a match occurs, then the conversion process
183 183 will add the most recent revision on the branch indicated in
184 184 the regex as the second parent of the changeset. Default is
185 185 "{{mergefrombranch ([-\w]+)}}"
186 186 convert.localtimezone
187 187 use local time (as determined by the TZ environment
188 188 variable) for changeset date/times. The default is False
189 189 (use UTC).
190 190 hooks.cvslog Specify a Python function to be called at the end of
191 191 gathering the CVS log. The function is passed a list with
192 192 the log entries, and can modify the entries in-place, or add
193 193 or delete them.
194 194 hooks.cvschangesets
195 195 Specify a Python function to be called after the changesets
196 196 are calculated from the CVS log. The function is passed a
197 197 list with the changeset entries, and can modify the
198 198 changesets in-place, or add or delete them.
199 199
200 200 An additional "debugcvsps" Mercurial command allows the builtin changeset
201 201 merging code to be run without doing a conversion. Its parameters and
202 202 output are similar to that of cvsps 2.1. Please see the command help for
203 203 more details.
204 204
205 205 Subversion Source
206 206 #################
207 207
208 208 Subversion source detects classical trunk/branches/tags layouts. By
209 209 default, the supplied "svn://repo/path/" source URL is converted as a
210 210 single branch. If "svn://repo/path/trunk" exists it replaces the default
211 211 branch. If "svn://repo/path/branches" exists, its subdirectories are
212 212 listed as possible branches. If "svn://repo/path/tags" exists, it is
213 213 looked for tags referencing converted branches. Default "trunk",
214 214 "branches" and "tags" values can be overridden with following options. Set
215 215 them to paths relative to the source URL, or leave them blank to disable
216 216 auto detection.
217 217
218 218 The following options can be set with "--config":
219 219
220 220 convert.svn.branches
221 221 specify the directory containing branches. The default is
222 222 "branches".
223 223 convert.svn.tags
224 224 specify the directory containing tags. The default is
225 225 "tags".
226 226 convert.svn.trunk
227 227 specify the name of the trunk branch. The default is
228 228 "trunk".
229 229 convert.localtimezone
230 230 use local time (as determined by the TZ environment
231 231 variable) for changeset date/times. The default is False
232 232 (use UTC).
233 233
234 234 Source history can be retrieved starting at a specific revision, instead
235 235 of being integrally converted. Only single branch conversions are
236 236 supported.
237 237
238 238 convert.svn.startrev
239 239 specify start Subversion revision number. The default is 0.
240 240
241 241 Git Source
242 242 ##########
243 243
244 244 The Git importer converts commits from all reachable branches (refs in
245 245 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
246 246 converted to bookmarks with the same name, with the leading 'refs/heads'
247 247 stripped. Git submodules are converted to Git subrepos in Mercurial.
248 248
249 249 The following options can be set with "--config":
250 250
251 251 convert.git.similarity
252 252 specify how similar files modified in a commit must be to be
253 253 imported as renames or copies, as a percentage between "0"
254 254 (disabled) and "100" (files must be identical). For example,
255 255 "90" means that a delete/add pair will be imported as a
256 256 rename if more than 90% of the file hasn't changed. The
257 257 default is "50".
258 258 convert.git.findcopiesharder
259 259 while detecting copies, look at all files in the working
260 260 copy instead of just changed ones. This is very expensive
261 261 for large projects, and is only effective when
262 262 "convert.git.similarity" is greater than 0. The default is
263 263 False.
264 264 convert.git.renamelimit
265 265 perform rename and copy detection up to this many changed
266 266 files in a commit. Increasing this will make rename and copy
267 267 detection more accurate but will significantly slow down
268 268 computation on large projects. The option is only relevant
269 269 if "convert.git.similarity" is greater than 0. The default
270 270 is "400".
271 271 convert.git.committeractions
272 272 list of actions to take when processing author and committer
273 273 values.
274 274
275 275 Git commits have separate author (who wrote the commit) and committer
276 276 (who applied the commit) fields. Not all destinations support separate
277 277 author and committer fields (including Mercurial). This config option
278 278 controls what to do with these author and committer fields during
279 279 conversion.
280 280
281 281 A value of "messagedifferent" will append a "committer: ..." line to
282 282 the commit message if the Git committer is different from the author.
283 283 The prefix of that line can be specified using the syntax
284 284 "messagedifferent=<prefix>". e.g. "messagedifferent=git-committer:".
285 285 When a prefix is specified, a space will always be inserted between
286 286 the prefix and the value.
287 287
288 288 "messagealways" behaves like "messagedifferent" except it will always
289 289 result in a "committer: ..." line being appended to the commit
290 290 message. This value is mutually exclusive with "messagedifferent".
291 291
292 292 "dropcommitter" will remove references to the committer. Only
293 293 references to the author will remain. Actions that add references to
294 294 the committer will have no effect when this is set.
295 295
296 296 "replaceauthor" will replace the value of the author field with the
297 297 committer. Other actions that add references to the committer will
298 298 still take effect when this is set.
299 299
300 "replacecommitter" will replace the value of the committer field with
301 the author.
302
303 300 The default is "messagedifferent".
304 301
305 302 convert.git.extrakeys
306 303 list of extra keys from commit metadata to copy to the
307 304 destination. Some Git repositories store extra metadata in
308 305 commits. By default, this non-default metadata will be lost
309 306 during conversion. Setting this config option can retain
310 307 that metadata. Some built-in keys such as "parent" and
311 308 "branch" are not allowed to be copied.
312 309 convert.git.remoteprefix
313 310 remote refs are converted as bookmarks with
314 311 "convert.git.remoteprefix" as a prefix followed by a /. The
315 312 default is 'remote'.
316 313 convert.git.saverev
317 314 whether to store the original Git commit ID in the metadata
318 315 of the destination commit. The default is True.
319 316 convert.git.skipsubmodules
320 317 does not convert root level .gitmodules files or files with
321 318 160000 mode indicating a submodule. Default is False.
322 319
323 320 Perforce Source
324 321 ###############
325 322
326 323 The Perforce (P4) importer can be given a p4 depot path or a client
327 324 specification as source. It will convert all files in the source to a flat
328 325 Mercurial repository, ignoring labels, branches and integrations. Note
329 326 that when a depot path is given you then usually should specify a target
330 327 directory, because otherwise the target may be named "...-hg".
331 328
332 329 The following options can be set with "--config":
333 330
334 331 convert.p4.encoding
335 332 specify the encoding to use when decoding standard output of
336 333 the Perforce command line tool. The default is default
337 334 system encoding.
338 335 convert.p4.startrev
339 336 specify initial Perforce revision (a Perforce changelist
340 337 number).
341 338
342 339 Mercurial Destination
343 340 #####################
344 341
345 342 The Mercurial destination will recognize Mercurial subrepositories in the
346 343 destination directory, and update the .hgsubstate file automatically if
347 344 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
348 345 Converting a repository with subrepositories requires converting a single
349 346 repository at a time, from the bottom up.
350 347
351 348 The following options are supported:
352 349
353 350 convert.hg.clonebranches
354 351 dispatch source branches in separate clones. The default is
355 352 False.
356 353 convert.hg.tagsbranch
357 354 branch name for tag revisions, defaults to "default".
358 355 convert.hg.usebranchnames
359 356 preserve branch names. The default is True.
360 357 convert.hg.sourcename
361 358 records the given string as a 'convert_source' extra value
362 359 on each commit made in the target repository. The default is
363 360 None.
364 361
365 362 All Destinations
366 363 ################
367 364
368 365 All destination types accept the following options:
369 366
370 367 convert.skiptags
371 368 does not convert tags from the source repo to the target
372 369 repo. The default is False.
373 370
374 371 options ([+] can be repeated):
375 372
376 373 -s --source-type TYPE source repository type
377 374 -d --dest-type TYPE destination repository type
378 375 -r --rev REV [+] import up to source revision REV
379 376 -A --authormap FILE remap usernames using this file
380 377 --filemap FILE remap file names using contents of file
381 378 --full apply filemap changes by converting all files again
382 379 --splicemap FILE splice synthesized history into place
383 380 --branchmap FILE change branch names while converting
384 381 --branchsort try to sort changesets by branches
385 382 --datesort try to sort changesets by date
386 383 --sourcesort preserve source changesets order
387 384 --closesort try to reorder closed revisions
388 385
389 386 (some details hidden, use --verbose to show complete help)
390 387 $ hg init a
391 388 $ cd a
392 389 $ echo a > a
393 390 $ hg ci -d'0 0' -Ama
394 391 adding a
395 392 $ hg cp a b
396 393 $ hg ci -d'1 0' -mb
397 394 $ hg rm a
398 395 $ hg ci -d'2 0' -mc
399 396 $ hg mv b a
400 397 $ hg ci -d'3 0' -md
401 398 $ echo a >> a
402 399 $ hg ci -d'4 0' -me
403 400 $ cd ..
404 401 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
405 402 assuming destination a-hg
406 403 initializing destination a-hg repository
407 404 scanning source...
408 405 sorting...
409 406 converting...
410 407 4 a
411 408 3 b
412 409 2 c
413 410 1 d
414 411 0 e
415 412 $ hg --cwd a-hg pull ../a
416 413 pulling from ../a
417 414 searching for changes
418 415 no changes found
419 416
420 417 conversion to existing file should fail
421 418
422 419 $ touch bogusfile
423 420 $ hg convert a bogusfile
424 421 initializing destination bogusfile repository
425 422 abort: cannot create new bundle repository
426 423 [255]
427 424
428 425 #if unix-permissions no-root
429 426
430 427 conversion to dir without permissions should fail
431 428
432 429 $ mkdir bogusdir
433 430 $ chmod 000 bogusdir
434 431
435 432 $ hg convert a bogusdir
436 433 abort: Permission denied: 'bogusdir'
437 434 [255]
438 435
439 436 user permissions should succeed
440 437
441 438 $ chmod 700 bogusdir
442 439 $ hg convert a bogusdir
443 440 initializing destination bogusdir repository
444 441 scanning source...
445 442 sorting...
446 443 converting...
447 444 4 a
448 445 3 b
449 446 2 c
450 447 1 d
451 448 0 e
452 449
453 450 #endif
454 451
455 452 test pre and post conversion actions
456 453
457 454 $ echo 'include b' > filemap
458 455 $ hg convert --debug --filemap filemap a partialb | \
459 456 > grep 'run hg'
460 457 run hg source pre-conversion action
461 458 run hg sink pre-conversion action
462 459 run hg sink post-conversion action
463 460 run hg source post-conversion action
464 461
465 462 converting empty dir should fail "nicely
466 463
467 464 $ mkdir emptydir
468 465
469 466 override $PATH to ensure p4 not visible; use $PYTHON in case we're
470 467 running from a devel copy, not a temp installation
471 468
472 469 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
473 470 assuming destination emptydir-hg
474 471 initializing destination emptydir-hg repository
475 472 emptydir does not look like a CVS checkout
476 473 $TESTTMP/emptydir does not look like a Git repository (glob)
477 474 emptydir does not look like a Subversion repository
478 475 emptydir is not a local Mercurial repository
479 476 emptydir does not look like a darcs repository
480 477 emptydir does not look like a monotone repository
481 478 emptydir does not look like a GNU Arch repository
482 479 emptydir does not look like a Bazaar repository
483 480 cannot find required "p4" tool
484 481 abort: emptydir: missing or unsupported repository
485 482 [255]
486 483
487 484 convert with imaginary source type
488 485
489 486 $ hg convert --source-type foo a a-foo
490 487 initializing destination a-foo repository
491 488 abort: foo: invalid source repository type
492 489 [255]
493 490
494 491 convert with imaginary sink type
495 492
496 493 $ hg convert --dest-type foo a a-foo
497 494 abort: foo: invalid destination repository type
498 495 [255]
499 496
500 497 testing: convert must not produce duplicate entries in fncache
501 498
502 499 $ hg convert a b
503 500 initializing destination b repository
504 501 scanning source...
505 502 sorting...
506 503 converting...
507 504 4 a
508 505 3 b
509 506 2 c
510 507 1 d
511 508 0 e
512 509
513 510 contents of fncache file:
514 511
515 512 $ cat b/.hg/store/fncache | sort
516 513 data/a.i
517 514 data/b.i
518 515
519 516 test bogus URL
520 517
521 518 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
522 519 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
523 520 [255]
524 521
525 522 test revset converted() lookup
526 523
527 524 $ hg --config convert.hg.saverev=True convert a c
528 525 initializing destination c repository
529 526 scanning source...
530 527 sorting...
531 528 converting...
532 529 4 a
533 530 3 b
534 531 2 c
535 532 1 d
536 533 0 e
537 534 $ echo f > c/f
538 535 $ hg -R c ci -d'0 0' -Amf
539 536 adding f
540 537 created new head
541 538 $ hg -R c log -r "converted(09d945a62ce6)"
542 539 changeset: 1:98c3dd46a874
543 540 user: test
544 541 date: Thu Jan 01 00:00:01 1970 +0000
545 542 summary: b
546 543
547 544 $ hg -R c log -r "converted()"
548 545 changeset: 0:31ed57b2037c
549 546 user: test
550 547 date: Thu Jan 01 00:00:00 1970 +0000
551 548 summary: a
552 549
553 550 changeset: 1:98c3dd46a874
554 551 user: test
555 552 date: Thu Jan 01 00:00:01 1970 +0000
556 553 summary: b
557 554
558 555 changeset: 2:3b9ca06ef716
559 556 user: test
560 557 date: Thu Jan 01 00:00:02 1970 +0000
561 558 summary: c
562 559
563 560 changeset: 3:4e0debd37cf2
564 561 user: test
565 562 date: Thu Jan 01 00:00:03 1970 +0000
566 563 summary: d
567 564
568 565 changeset: 4:9de3bc9349c5
569 566 user: test
570 567 date: Thu Jan 01 00:00:04 1970 +0000
571 568 summary: e
572 569
573 570
574 571 test specifying a sourcename
575 572 $ echo g > a/g
576 573 $ hg -R a ci -d'0 0' -Amg
577 574 adding g
578 575 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
579 576 scanning source...
580 577 sorting...
581 578 converting...
582 579 0 g
583 580 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
584 581 branch=default
585 582 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
586 583 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now