##// END OF EJS Templates
convert: config option for git rename limit...
Gregory Szorc -
r30646:ea3540e6 default
parent child Browse files
Show More
@@ -1,458 +1,465 b''
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 :convert.git.renamelimit: perform rename and copy detection up to this
324 many changed files in a commit. Increasing this will make rename
325 and copy detection more accurate but will significantly slow down
326 computation on large projects. The option is only relevant if
327 ``convert.git.similarity`` is greater than 0. The default is
328 ``400``.
329
323 330 :convert.git.remoteprefix: remote refs are converted as bookmarks with
324 331 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
325 332 is 'remote'.
326 333
327 334 :convert.git.skipsubmodules: does not convert root level .gitmodules files
328 335 or files with 160000 mode indicating a submodule. Default is False.
329 336
330 337 Perforce Source
331 338 ###############
332 339
333 340 The Perforce (P4) importer can be given a p4 depot path or a
334 341 client specification as source. It will convert all files in the
335 342 source to a flat Mercurial repository, ignoring labels, branches
336 343 and integrations. Note that when a depot path is given you then
337 344 usually should specify a target directory, because otherwise the
338 345 target may be named ``...-hg``.
339 346
340 347 The following options can be set with ``--config``:
341 348
342 349 :convert.p4.encoding: specify the encoding to use when decoding standard
343 350 output of the Perforce command line tool. The default is default system
344 351 encoding.
345 352
346 353 :convert.p4.startrev: specify initial Perforce revision (a
347 354 Perforce changelist number).
348 355
349 356 Mercurial Destination
350 357 #####################
351 358
352 359 The Mercurial destination will recognize Mercurial subrepositories in the
353 360 destination directory, and update the .hgsubstate file automatically if the
354 361 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
355 362 Converting a repository with subrepositories requires converting a single
356 363 repository at a time, from the bottom up.
357 364
358 365 .. container:: verbose
359 366
360 367 An example showing how to convert a repository with subrepositories::
361 368
362 369 # so convert knows the type when it sees a non empty destination
363 370 $ hg init converted
364 371
365 372 $ hg convert orig/sub1 converted/sub1
366 373 $ hg convert orig/sub2 converted/sub2
367 374 $ hg convert orig converted
368 375
369 376 The following options are supported:
370 377
371 378 :convert.hg.clonebranches: dispatch source branches in separate
372 379 clones. The default is False.
373 380
374 381 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
375 382 ``default``.
376 383
377 384 :convert.hg.usebranchnames: preserve branch names. The default is
378 385 True.
379 386
380 387 :convert.hg.sourcename: records the given string as a 'convert_source' extra
381 388 value on each commit made in the target repository. The default is None.
382 389
383 390 All Destinations
384 391 ################
385 392
386 393 All destination types accept the following options:
387 394
388 395 :convert.skiptags: does not convert tags from the source repo to the target
389 396 repo. The default is False.
390 397 """
391 398 return convcmd.convert(ui, src, dest, revmapfile, **opts)
392 399
393 400 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
394 401 def debugsvnlog(ui, **opts):
395 402 return subversion.debugsvnlog(ui, **opts)
396 403
397 404 @command('debugcvsps',
398 405 [
399 406 # Main options shared with cvsps-2.1
400 407 ('b', 'branches', [], _('only return changes on specified branches')),
401 408 ('p', 'prefix', '', _('prefix to remove from file names')),
402 409 ('r', 'revisions', [],
403 410 _('only return changes after or between specified tags')),
404 411 ('u', 'update-cache', None, _("update cvs log cache")),
405 412 ('x', 'new-cache', None, _("create new cvs log cache")),
406 413 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
407 414 ('', 'root', '', _('specify cvsroot')),
408 415 # Options specific to builtin cvsps
409 416 ('', 'parents', '', _('show parent changesets')),
410 417 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
411 418 # Options that are ignored for compatibility with cvsps-2.1
412 419 ('A', 'cvs-direct', None, _('ignored for compatibility')),
413 420 ],
414 421 _('hg debugcvsps [OPTION]... [PATH]...'),
415 422 norepo=True)
416 423 def debugcvsps(ui, *args, **opts):
417 424 '''create changeset information from CVS
418 425
419 426 This command is intended as a debugging tool for the CVS to
420 427 Mercurial converter, and can be used as a direct replacement for
421 428 cvsps.
422 429
423 430 Hg debugcvsps reads the CVS rlog for current directory (or any
424 431 named directory) in the CVS repository, and converts the log to a
425 432 series of changesets based on matching commit log entries and
426 433 dates.'''
427 434 return cvsps.debugcvsps(ui, *args, **opts)
428 435
429 436 def kwconverted(ctx, name):
430 437 rev = ctx.extra().get('convert_revision', '')
431 438 if rev.startswith('svn:'):
432 439 if name == 'svnrev':
433 440 return str(subversion.revsplit(rev)[2])
434 441 elif name == 'svnpath':
435 442 return subversion.revsplit(rev)[1]
436 443 elif name == 'svnuuid':
437 444 return subversion.revsplit(rev)[0]
438 445 return rev
439 446
440 447 templatekeyword = registrar.templatekeyword()
441 448
442 449 @templatekeyword('svnrev')
443 450 def kwsvnrev(repo, ctx, **args):
444 451 """String. Converted subversion revision number."""
445 452 return kwconverted(ctx, 'svnrev')
446 453
447 454 @templatekeyword('svnpath')
448 455 def kwsvnpath(repo, ctx, **args):
449 456 """String. Converted subversion revision project path."""
450 457 return kwconverted(ctx, 'svnpath')
451 458
452 459 @templatekeyword('svnuuid')
453 460 def kwsvnuuid(repo, ctx, **args):
454 461 """String. Converted subversion revision repository identifier."""
455 462 return kwconverted(ctx, 'svnuuid')
456 463
457 464 # tell hggettext to extract docstrings from these functions:
458 465 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,399 +1,403 b''
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 class convert_git(common.converter_source, common.commandline):
35 35 # Windows does not support GIT_DIR= construct while other systems
36 36 # cannot remove environment variable. Just assume none have
37 37 # both issues.
38 38
39 39 def _gitcmd(self, cmd, *args, **kwargs):
40 40 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
41 41
42 42 def gitrun0(self, *args, **kwargs):
43 43 return self._gitcmd(self.run0, *args, **kwargs)
44 44
45 45 def gitrun(self, *args, **kwargs):
46 46 return self._gitcmd(self.run, *args, **kwargs)
47 47
48 48 def gitrunlines0(self, *args, **kwargs):
49 49 return self._gitcmd(self.runlines0, *args, **kwargs)
50 50
51 51 def gitrunlines(self, *args, **kwargs):
52 52 return self._gitcmd(self.runlines, *args, **kwargs)
53 53
54 54 def gitpipe(self, *args, **kwargs):
55 55 return self._gitcmd(self._run3, *args, **kwargs)
56 56
57 57 def __init__(self, ui, path, revs=None):
58 58 super(convert_git, self).__init__(ui, path, revs=revs)
59 59 common.commandline.__init__(self, ui, 'git')
60 60
61 61 # Pass an absolute path to git to prevent from ever being interpreted
62 62 # as a URL
63 63 path = os.path.abspath(path)
64 64
65 65 if os.path.isdir(path + "/.git"):
66 66 path += "/.git"
67 67 if not os.path.exists(path + "/objects"):
68 68 raise common.NoRepo(_("%s does not look like a Git repository") %
69 69 path)
70 70
71 71 # The default value (50) is based on the default for 'git diff'.
72 72 similarity = ui.configint('convert', 'git.similarity', default=50)
73 73 if similarity < 0 or similarity > 100:
74 74 raise error.Abort(_('similarity must be between 0 and 100'))
75 75 if similarity > 0:
76 76 self.simopt = ['-C%d%%' % similarity]
77 77 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
78 78 False)
79 79 if findcopiesharder:
80 80 self.simopt.append('--find-copies-harder')
81
82 renamelimit = ui.configint('convert', 'git.renamelimit',
83 default=400)
84 self.simopt.append('-l%d' % renamelimit)
81 85 else:
82 86 self.simopt = []
83 87
84 88 common.checktool('git', 'git')
85 89
86 90 self.path = path
87 91 self.submodules = []
88 92
89 93 self.catfilepipe = self.gitpipe('cat-file', '--batch')
90 94
91 95 def after(self):
92 96 for f in self.catfilepipe:
93 97 f.close()
94 98
95 99 def getheads(self):
96 100 if not self.revs:
97 101 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
98 102 heads = output.splitlines()
99 103 if status:
100 104 raise error.Abort(_('cannot retrieve git heads'))
101 105 else:
102 106 heads = []
103 107 for rev in self.revs:
104 108 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
105 109 heads.append(rawhead[:-1])
106 110 if ret:
107 111 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
108 112 return heads
109 113
110 114 def catfile(self, rev, type):
111 115 if rev == nodemod.nullhex:
112 116 raise IOError
113 117 self.catfilepipe[0].write(rev+'\n')
114 118 self.catfilepipe[0].flush()
115 119 info = self.catfilepipe[1].readline().split()
116 120 if info[1] != type:
117 121 raise error.Abort(_('cannot read %r object at %s') % (type, rev))
118 122 size = int(info[2])
119 123 data = self.catfilepipe[1].read(size)
120 124 if len(data) < size:
121 125 raise error.Abort(_('cannot read %r object at %s: unexpected size')
122 126 % (type, rev))
123 127 # read the trailing newline
124 128 self.catfilepipe[1].read(1)
125 129 return data
126 130
127 131 def getfile(self, name, rev):
128 132 if rev == nodemod.nullhex:
129 133 return None, None
130 134 if name == '.hgsub':
131 135 data = '\n'.join([m.hgsub() for m in self.submoditer()])
132 136 mode = ''
133 137 elif name == '.hgsubstate':
134 138 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
135 139 mode = ''
136 140 else:
137 141 data = self.catfile(rev, "blob")
138 142 mode = self.modecache[(name, rev)]
139 143 return data, mode
140 144
141 145 def submoditer(self):
142 146 null = nodemod.nullhex
143 147 for m in sorted(self.submodules, key=lambda p: p.path):
144 148 if m.node != null:
145 149 yield m
146 150
147 151 def parsegitmodules(self, content):
148 152 """Parse the formatted .gitmodules file, example file format:
149 153 [submodule "sub"]\n
150 154 \tpath = sub\n
151 155 \turl = git://giturl\n
152 156 """
153 157 self.submodules = []
154 158 c = config.config()
155 159 # Each item in .gitmodules starts with whitespace that cant be parsed
156 160 c.parse('.gitmodules', '\n'.join(line.strip() for line in
157 161 content.split('\n')))
158 162 for sec in c.sections():
159 163 s = c[sec]
160 164 if 'url' in s and 'path' in s:
161 165 self.submodules.append(submodule(s['path'], '', s['url']))
162 166
163 167 def retrievegitmodules(self, version):
164 168 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
165 169 if ret:
166 170 # This can happen if a file is in the repo that has permissions
167 171 # 160000, but there is no .gitmodules file.
168 172 self.ui.warn(_("warning: cannot read submodules config file in "
169 173 "%s\n") % version)
170 174 return
171 175
172 176 try:
173 177 self.parsegitmodules(modules)
174 178 except error.ParseError:
175 179 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
176 180 % version)
177 181 return
178 182
179 183 for m in self.submodules:
180 184 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
181 185 if ret:
182 186 continue
183 187 m.node = node.strip()
184 188
185 189 def getchanges(self, version, full):
186 190 if full:
187 191 raise error.Abort(_("convert from git does not support --full"))
188 192 self.modecache = {}
189 193 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
190 194 output, status = self.gitrun(*cmd)
191 195 if status:
192 196 raise error.Abort(_('cannot read changes in %s') % version)
193 197 changes = []
194 198 copies = {}
195 199 seen = set()
196 200 entry = None
197 201 subexists = [False]
198 202 subdeleted = [False]
199 203 difftree = output.split('\x00')
200 204 lcount = len(difftree)
201 205 i = 0
202 206
203 207 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
204 208 False)
205 209 def add(entry, f, isdest):
206 210 seen.add(f)
207 211 h = entry[3]
208 212 p = (entry[1] == "100755")
209 213 s = (entry[1] == "120000")
210 214 renamesource = (not isdest and entry[4][0] == 'R')
211 215
212 216 if f == '.gitmodules':
213 217 if skipsubmodules:
214 218 return
215 219
216 220 subexists[0] = True
217 221 if entry[4] == 'D' or renamesource:
218 222 subdeleted[0] = True
219 223 changes.append(('.hgsub', nodemod.nullhex))
220 224 else:
221 225 changes.append(('.hgsub', ''))
222 226 elif entry[1] == '160000' or entry[0] == ':160000':
223 227 if not skipsubmodules:
224 228 subexists[0] = True
225 229 else:
226 230 if renamesource:
227 231 h = nodemod.nullhex
228 232 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
229 233 changes.append((f, h))
230 234
231 235 while i < lcount:
232 236 l = difftree[i]
233 237 i += 1
234 238 if not entry:
235 239 if not l.startswith(':'):
236 240 continue
237 241 entry = l.split()
238 242 continue
239 243 f = l
240 244 if entry[4][0] == 'C':
241 245 copysrc = f
242 246 copydest = difftree[i]
243 247 i += 1
244 248 f = copydest
245 249 copies[copydest] = copysrc
246 250 if f not in seen:
247 251 add(entry, f, False)
248 252 # A file can be copied multiple times, or modified and copied
249 253 # simultaneously. So f can be repeated even if fdest isn't.
250 254 if entry[4][0] == 'R':
251 255 # rename: next line is the destination
252 256 fdest = difftree[i]
253 257 i += 1
254 258 if fdest not in seen:
255 259 add(entry, fdest, True)
256 260 # .gitmodules isn't imported at all, so it being copied to
257 261 # and fro doesn't really make sense
258 262 if f != '.gitmodules' and fdest != '.gitmodules':
259 263 copies[fdest] = f
260 264 entry = None
261 265
262 266 if subexists[0]:
263 267 if subdeleted[0]:
264 268 changes.append(('.hgsubstate', nodemod.nullhex))
265 269 else:
266 270 self.retrievegitmodules(version)
267 271 changes.append(('.hgsubstate', ''))
268 272 return (changes, copies, set())
269 273
270 274 def getcommit(self, version):
271 275 c = self.catfile(version, "commit") # read the commit hash
272 276 end = c.find("\n\n")
273 277 message = c[end + 2:]
274 278 message = self.recode(message)
275 279 l = c[:end].splitlines()
276 280 parents = []
277 281 author = committer = None
278 282 for e in l[1:]:
279 283 n, v = e.split(" ", 1)
280 284 if n == "author":
281 285 p = v.split()
282 286 tm, tz = p[-2:]
283 287 author = " ".join(p[:-2])
284 288 if author[0] == "<": author = author[1:-1]
285 289 author = self.recode(author)
286 290 if n == "committer":
287 291 p = v.split()
288 292 tm, tz = p[-2:]
289 293 committer = " ".join(p[:-2])
290 294 if committer[0] == "<": committer = committer[1:-1]
291 295 committer = self.recode(committer)
292 296 if n == "parent":
293 297 parents.append(v)
294 298
295 299 if committer and committer != author:
296 300 message += "\ncommitter: %s\n" % committer
297 301 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
298 302 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
299 303 date = tm + " " + str(tz)
300 304
301 305 c = common.commit(parents=parents, date=date, author=author,
302 306 desc=message,
303 307 rev=version)
304 308 return c
305 309
306 310 def numcommits(self):
307 311 output, ret = self.gitrunlines('rev-list', '--all')
308 312 if ret:
309 313 raise error.Abort(_('cannot retrieve number of commits in %s') \
310 314 % self.path)
311 315 return len(output)
312 316
313 317 def gettags(self):
314 318 tags = {}
315 319 alltags = {}
316 320 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
317 321
318 322 if status:
319 323 raise error.Abort(_('cannot read tags from %s') % self.path)
320 324 prefix = 'refs/tags/'
321 325
322 326 # Build complete list of tags, both annotated and bare ones
323 327 for line in output:
324 328 line = line.strip()
325 329 if line.startswith("error:") or line.startswith("fatal:"):
326 330 raise error.Abort(_('cannot read tags from %s') % self.path)
327 331 node, tag = line.split(None, 1)
328 332 if not tag.startswith(prefix):
329 333 continue
330 334 alltags[tag[len(prefix):]] = node
331 335
332 336 # Filter out tag objects for annotated tag refs
333 337 for tag in alltags:
334 338 if tag.endswith('^{}'):
335 339 tags[tag[:-3]] = alltags[tag]
336 340 else:
337 341 if tag + '^{}' in alltags:
338 342 continue
339 343 else:
340 344 tags[tag] = alltags[tag]
341 345
342 346 return tags
343 347
344 348 def getchangedfiles(self, version, i):
345 349 changes = []
346 350 if i is None:
347 351 output, status = self.gitrunlines('diff-tree', '--root', '-m',
348 352 '-r', version)
349 353 if status:
350 354 raise error.Abort(_('cannot read changes in %s') % version)
351 355 for l in output:
352 356 if "\t" not in l:
353 357 continue
354 358 m, f = l[:-1].split("\t")
355 359 changes.append(f)
356 360 else:
357 361 output, status = self.gitrunlines('diff-tree', '--name-only',
358 362 '--root', '-r', version,
359 363 '%s^%s' % (version, i + 1), '--')
360 364 if status:
361 365 raise error.Abort(_('cannot read changes in %s') % version)
362 366 changes = [f.rstrip('\n') for f in output]
363 367
364 368 return changes
365 369
366 370 def getbookmarks(self):
367 371 bookmarks = {}
368 372
369 373 # Handle local and remote branches
370 374 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
371 375 reftypes = [
372 376 # (git prefix, hg prefix)
373 377 ('refs/remotes/origin/', remoteprefix + '/'),
374 378 ('refs/heads/', '')
375 379 ]
376 380
377 381 exclude = set([
378 382 'refs/remotes/origin/HEAD',
379 383 ])
380 384
381 385 try:
382 386 output, status = self.gitrunlines('show-ref')
383 387 for line in output:
384 388 line = line.strip()
385 389 rev, name = line.split(None, 1)
386 390 # Process each type of branch
387 391 for gitprefix, hgprefix in reftypes:
388 392 if not name.startswith(gitprefix) or name in exclude:
389 393 continue
390 394 name = '%s%s' % (hgprefix, name[len(gitprefix):])
391 395 bookmarks[name] = rev
392 396 except Exception:
393 397 pass
394 398
395 399 return bookmarks
396 400
397 401 def checkrevformat(self, revstr, mapname='splicemap'):
398 402 """ git revision string is a 40 byte hex """
399 403 self.checkhexformat(revstr, mapname)
@@ -1,770 +1,795 b''
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 renamelimit config option works
378
379 $ cd git-repo2
380 $ cp bar bar-copy0
381 $ echo 0 >> bar-copy0
382 $ cp bar bar-copy1
383 $ echo 1 >> bar-copy1
384 $ git add bar-copy0 bar-copy1
385 $ commit -a -m 'copy bar 2 times'
386 $ cd ..
387
388 $ hg -q convert --config convert.git.renamelimit=1 \
389 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo2
390 $ hg -R fullrepo2 status -C --change master
391 A bar-copy0
392 A bar-copy1
393
394 $ hg -q convert --config convert.git.renamelimit=100 \
395 > --config convert.git.findcopiesharder=true --datesort git-repo2 fullrepo3
396 $ hg -R fullrepo3 status -C --change master
397 A bar-copy0
398 bar
399 A bar-copy1
400 bar
401
377 402 test binary conversion (issue1359)
378 403
379 404 $ count=19
380 405 $ mkdir git-repo3
381 406 $ cd git-repo3
382 407 $ git init-db >/dev/null 2>/dev/null
383 408 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
384 409 $ git add b
385 410 $ commit -a -m addbinary
386 411 $ cd ..
387 412
388 413 convert binary file
389 414
390 415 $ hg convert git-repo3 git-repo3-hg
391 416 initializing destination git-repo3-hg repository
392 417 scanning source...
393 418 sorting...
394 419 converting...
395 420 0 addbinary
396 421 updating bookmarks
397 422 $ cd git-repo3-hg
398 423 $ hg up -C
399 424 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 425 $ $PYTHON -c 'print len(file("b", "rb").read())'
401 426 4096
402 427 $ cd ..
403 428
404 429 test author vs committer
405 430
406 431 $ mkdir git-repo4
407 432 $ cd git-repo4
408 433 $ git init-db >/dev/null 2>/dev/null
409 434 $ echo >> foo
410 435 $ git add foo
411 436 $ commit -a -m addfoo
412 437 $ echo >> foo
413 438 $ GIT_AUTHOR_NAME="nottest"
414 439 $ commit -a -m addfoo2
415 440 $ cd ..
416 441
417 442 convert author committer
418 443
419 444 $ hg convert git-repo4 git-repo4-hg
420 445 initializing destination git-repo4-hg repository
421 446 scanning source...
422 447 sorting...
423 448 converting...
424 449 1 addfoo
425 450 0 addfoo2
426 451 updating bookmarks
427 452 $ hg -R git-repo4-hg log -v
428 453 changeset: 1:d63e967f93da
429 454 bookmark: master
430 455 tag: tip
431 456 user: nottest <test@example.org>
432 457 date: Mon Jan 01 00:00:21 2007 +0000
433 458 files: foo
434 459 description:
435 460 addfoo2
436 461
437 462 committer: test <test@example.org>
438 463
439 464
440 465 changeset: 0:0735477b0224
441 466 user: test <test@example.org>
442 467 date: Mon Jan 01 00:00:20 2007 +0000
443 468 files: foo
444 469 description:
445 470 addfoo
446 471
447 472
448 473
449 474 --sourceorder should fail
450 475
451 476 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
452 477 initializing destination git-repo4-sourcesort-hg repository
453 478 abort: --sourcesort is not supported by this data source
454 479 [255]
455 480
456 481 test converting certain branches
457 482
458 483 $ mkdir git-testrevs
459 484 $ cd git-testrevs
460 485 $ git init
461 486 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
462 487 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
463 488 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
464 489 $ git checkout -b goodbranch 'HEAD^'
465 490 Switched to a new branch 'goodbranch'
466 491 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
467 492 $ git checkout -b badbranch 'HEAD^'
468 493 Switched to a new branch 'badbranch'
469 494 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
470 495 $ cd ..
471 496 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
472 497 initializing destination hg-testrevs repository
473 498 scanning source...
474 499 sorting...
475 500 converting...
476 501 2 first
477 502 1 good branch commit
478 503 0 master commit
479 504 updating bookmarks
480 505 $ cd hg-testrevs
481 506 $ hg log -G -T '{rev} {bookmarks}'
482 507 o 2 master
483 508 |
484 509 | o 1 goodbranch
485 510 |/
486 511 o 0
487 512
488 513 $ cd ..
489 514
490 515 test sub modules
491 516
492 517 $ mkdir git-repo5
493 518 $ cd git-repo5
494 519 $ git init-db >/dev/null 2>/dev/null
495 520 $ echo 'sub' >> foo
496 521 $ git add foo
497 522 $ commit -a -m 'addfoo'
498 523 $ BASE=`pwd`
499 524 $ cd ..
500 525 $ mkdir git-repo6
501 526 $ cd git-repo6
502 527 $ git init-db >/dev/null 2>/dev/null
503 528 $ git submodule add ${BASE} >/dev/null 2>/dev/null
504 529 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
505 530
506 531 test non-tab whitespace .gitmodules
507 532
508 533 $ cat >> .gitmodules <<EOF
509 534 > [submodule "git-repo5"]
510 535 > path = git-repo5
511 536 > url = git-repo5
512 537 > EOF
513 538 $ git commit -q -a -m "weird white space submodule"
514 539 $ cd ..
515 540 $ hg convert git-repo6 hg-repo6
516 541 initializing destination hg-repo6 repository
517 542 scanning source...
518 543 sorting...
519 544 converting...
520 545 1 addsubmodule
521 546 0 weird white space submodule
522 547 updating bookmarks
523 548
524 549 $ rm -rf hg-repo6
525 550 $ cd git-repo6
526 551 $ git reset --hard 'HEAD^' > /dev/null
527 552
528 553 test missing .gitmodules
529 554
530 555 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
531 556 $ git checkout HEAD .gitmodules
532 557 $ git rm .gitmodules
533 558 rm '.gitmodules'
534 559 $ git commit -q -m "remove .gitmodules" .gitmodules
535 560 $ git commit -q -m "missing .gitmodules"
536 561 $ cd ..
537 562 $ hg convert git-repo6 hg-repo6 --traceback 2>&1 | grep -v "fatal: Path '.gitmodules' does not exist"
538 563 initializing destination hg-repo6 repository
539 564 scanning source...
540 565 sorting...
541 566 converting...
542 567 2 addsubmodule
543 568 1 remove .gitmodules
544 569 0 missing .gitmodules
545 570 warning: cannot read submodules config file in * (glob)
546 571 updating bookmarks
547 572 $ rm -rf hg-repo6
548 573 $ cd git-repo6
549 574 $ rm -rf git-repo4
550 575 $ git reset --hard 'HEAD^^' > /dev/null
551 576 $ cd ..
552 577
553 578 test invalid splicemap1
554 579
555 580 $ cat > splicemap <<EOF
556 581 > $VALIDID1
557 582 > EOF
558 583 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
559 584 initializing destination git-repo2-splicemap1-hg repository
560 585 abort: syntax error in splicemap(1): child parent1[,parent2] expected
561 586 [255]
562 587
563 588 test invalid splicemap2
564 589
565 590 $ cat > splicemap <<EOF
566 591 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
567 592 > EOF
568 593 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
569 594 initializing destination git-repo2-splicemap2-hg repository
570 595 abort: syntax error in splicemap(1): child parent1[,parent2] expected
571 596 [255]
572 597
573 598 test invalid splicemap3
574 599
575 600 $ cat > splicemap <<EOF
576 601 > $INVALIDID1 $INVALIDID2
577 602 > EOF
578 603 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
579 604 initializing destination git-repo2-splicemap3-hg repository
580 605 abort: splicemap entry afd12345af is not a valid revision identifier
581 606 [255]
582 607
583 608 convert sub modules
584 609 $ hg convert git-repo6 git-repo6-hg
585 610 initializing destination git-repo6-hg repository
586 611 scanning source...
587 612 sorting...
588 613 converting...
589 614 0 addsubmodule
590 615 updating bookmarks
591 616 $ hg -R git-repo6-hg log -v
592 617 changeset: 0:* (glob)
593 618 bookmark: master
594 619 tag: tip
595 620 user: nottest <test@example.org>
596 621 date: Mon Jan 01 00:00:23 2007 +0000
597 622 files: .hgsub .hgsubstate
598 623 description:
599 624 addsubmodule
600 625
601 626 committer: test <test@example.org>
602 627
603 628
604 629
605 630 $ cd git-repo6-hg
606 631 $ hg up >/dev/null 2>/dev/null
607 632 $ cat .hgsubstate
608 633 * git-repo5 (glob)
609 634 $ cd git-repo5
610 635 $ cat foo
611 636 sub
612 637
613 638 $ cd ../..
614 639
615 640 make sure rename detection doesn't break removing and adding gitmodules
616 641
617 642 $ cd git-repo6
618 643 $ git mv .gitmodules .gitmodules-renamed
619 644 $ commit -a -m 'rename .gitmodules'
620 645 $ git mv .gitmodules-renamed .gitmodules
621 646 $ commit -a -m 'rename .gitmodules back'
622 647 $ cd ..
623 648
624 649 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
625 650 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
626 651 rename .gitmodules
627 652 $ hg -R git-repo6-hg status -C --change 'tip^'
628 653 A .gitmodules-renamed
629 654 R .hgsub
630 655 R .hgsubstate
631 656 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
632 657 rename .gitmodules back
633 658 $ hg -R git-repo6-hg status -C --change tip
634 659 A .hgsub
635 660 A .hgsubstate
636 661 R .gitmodules-renamed
637 662
638 663 convert the revision removing '.gitmodules' itself (and related
639 664 submodules)
640 665
641 666 $ cd git-repo6
642 667 $ git rm .gitmodules
643 668 rm '.gitmodules'
644 669 $ git rm --cached git-repo5
645 670 rm 'git-repo5'
646 671 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
647 672 $ cd ..
648 673
649 674 $ hg convert -q git-repo6 git-repo6-hg
650 675 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
651 676 remove .gitmodules and submodule git-repo5
652 677 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
653 678 .hgsub .hgsubstate
654 679
655 680 skip submodules in the conversion
656 681
657 682 $ hg convert -q git-repo6 no-submodules --config convert.git.skipsubmodules=True
658 683 $ hg -R no-submodules manifest --all
659 684 .gitmodules-renamed
660 685
661 686 convert using a different remote prefix
662 687 $ git init git-repo7
663 688 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
664 689 $ cd git-repo7
665 690 TODO: it'd be nice to use (?) lines instead of grep -v to handle the
666 691 git output variance, but that doesn't currently work in the middle of
667 692 a block, so do this for now.
668 693 $ touch a && git add a && git commit -am "commit a" | grep -v changed
669 694 [master (root-commit) 8ae5f69] commit a
670 695 Author: nottest <test@example.org>
671 696 create mode 100644 a
672 697 $ cd ..
673 698 $ git clone git-repo7 git-repo7-client
674 699 Cloning into 'git-repo7-client'...
675 700 done.
676 701 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
677 702 initializing destination hg-repo7 repository
678 703 scanning source...
679 704 sorting...
680 705 converting...
681 706 0 commit a
682 707 updating bookmarks
683 708 $ hg -R hg-repo7 bookmarks
684 709 master 0:03bf38caa4c6
685 710 origin/master 0:03bf38caa4c6
686 711
687 712 Run convert when the remote branches have changed
688 713 (there was an old bug where the local convert read branches from the server)
689 714
690 715 $ cd git-repo7
691 716 $ echo a >> a
692 717 $ git commit -q -am "move master forward"
693 718 $ cd ..
694 719 $ rm -rf hg-repo7
695 720 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
696 721 initializing destination hg-repo7 repository
697 722 scanning source...
698 723 sorting...
699 724 converting...
700 725 0 commit a
701 726 updating bookmarks
702 727 $ hg -R hg-repo7 bookmarks
703 728 master 0:03bf38caa4c6
704 729 origin/master 0:03bf38caa4c6
705 730
706 731 damaged git repository tests:
707 732 In case the hard-coded hashes change, the following commands can be used to
708 733 list the hashes and their corresponding types in the repository:
709 734 cd git-repo4/.git/objects
710 735 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
711 736 cd ../../..
712 737
713 738 damage git repository by renaming a commit object
714 739 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
715 740 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
716 741 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
717 742 abort: cannot retrieve number of commits in $TESTTMP/git-repo4/.git
718 743 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
719 744 damage git repository by renaming a blob object
720 745
721 746 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
722 747 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
723 748 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
724 749 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
725 750 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
726 751 damage git repository by renaming a tree object
727 752
728 753 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
729 754 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
730 755 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
731 756 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
732 757
733 758 #if no-windows
734 759
735 760 test for escaping the repo name (CVE-2016-3069)
736 761
737 762 $ git init '`echo pwned >COMMAND-INJECTION`'
738 763 Initialized empty Git repository in $TESTTMP/`echo pwned >COMMAND-INJECTION`/.git/
739 764 $ cd '`echo pwned >COMMAND-INJECTION`'
740 765 $ git commit -q --allow-empty -m 'empty'
741 766 $ cd ..
742 767 $ hg convert '`echo pwned >COMMAND-INJECTION`' 'converted'
743 768 initializing destination converted repository
744 769 scanning source...
745 770 sorting...
746 771 converting...
747 772 0 empty
748 773 updating bookmarks
749 774 $ test -f COMMAND-INJECTION
750 775 [1]
751 776
752 777 test for safely passing paths to git (CVE-2016-3105)
753 778
754 779 $ git init 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
755 780 Initialized empty Git repository in $TESTTMP/ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #/.git/
756 781 $ cd 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #'
757 782 $ git commit -q --allow-empty -m 'empty'
758 783 $ cd ..
759 784 $ hg convert 'ext::sh -c echo% pwned% >GIT-EXT-COMMAND-INJECTION% #' 'converted-git-ext'
760 785 initializing destination converted-git-ext repository
761 786 scanning source...
762 787 sorting...
763 788 converting...
764 789 0 empty
765 790 updating bookmarks
766 791 $ test -f GIT-EXT-COMMAND-INJECTION
767 792 [1]
768 793
769 794 #endif
770 795
@@ -1,535 +1,542 b''
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 convert.git.renamelimit
265 perform rename and copy detection up to this many changed
266 files in a commit. Increasing this will make rename and copy
267 detection more accurate but will significantly slow down
268 computation on large projects. The option is only relevant
269 if "convert.git.similarity" is greater than 0. The default
270 is "400".
264 271 convert.git.remoteprefix
265 272 remote refs are converted as bookmarks with
266 273 "convert.git.remoteprefix" as a prefix followed by a /. The
267 274 default is 'remote'.
268 275 convert.git.skipsubmodules
269 276 does not convert root level .gitmodules files or files with
270 277 160000 mode indicating a submodule. Default is False.
271 278
272 279 Perforce Source
273 280 ###############
274 281
275 282 The Perforce (P4) importer can be given a p4 depot path or a client
276 283 specification as source. It will convert all files in the source to a flat
277 284 Mercurial repository, ignoring labels, branches and integrations. Note
278 285 that when a depot path is given you then usually should specify a target
279 286 directory, because otherwise the target may be named "...-hg".
280 287
281 288 The following options can be set with "--config":
282 289
283 290 convert.p4.encoding
284 291 specify the encoding to use when decoding standard output of
285 292 the Perforce command line tool. The default is default
286 293 system encoding.
287 294 convert.p4.startrev
288 295 specify initial Perforce revision (a Perforce changelist
289 296 number).
290 297
291 298 Mercurial Destination
292 299 #####################
293 300
294 301 The Mercurial destination will recognize Mercurial subrepositories in the
295 302 destination directory, and update the .hgsubstate file automatically if
296 303 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
297 304 Converting a repository with subrepositories requires converting a single
298 305 repository at a time, from the bottom up.
299 306
300 307 The following options are supported:
301 308
302 309 convert.hg.clonebranches
303 310 dispatch source branches in separate clones. The default is
304 311 False.
305 312 convert.hg.tagsbranch
306 313 branch name for tag revisions, defaults to "default".
307 314 convert.hg.usebranchnames
308 315 preserve branch names. The default is True.
309 316 convert.hg.sourcename
310 317 records the given string as a 'convert_source' extra value
311 318 on each commit made in the target repository. The default is
312 319 None.
313 320
314 321 All Destinations
315 322 ################
316 323
317 324 All destination types accept the following options:
318 325
319 326 convert.skiptags
320 327 does not convert tags from the source repo to the target
321 328 repo. The default is False.
322 329
323 330 options ([+] can be repeated):
324 331
325 332 -s --source-type TYPE source repository type
326 333 -d --dest-type TYPE destination repository type
327 334 -r --rev REV [+] import up to source revision REV
328 335 -A --authormap FILE remap usernames using this file
329 336 --filemap FILE remap file names using contents of file
330 337 --full apply filemap changes by converting all files again
331 338 --splicemap FILE splice synthesized history into place
332 339 --branchmap FILE change branch names while converting
333 340 --branchsort try to sort changesets by branches
334 341 --datesort try to sort changesets by date
335 342 --sourcesort preserve source changesets order
336 343 --closesort try to reorder closed revisions
337 344
338 345 (some details hidden, use --verbose to show complete help)
339 346 $ hg init a
340 347 $ cd a
341 348 $ echo a > a
342 349 $ hg ci -d'0 0' -Ama
343 350 adding a
344 351 $ hg cp a b
345 352 $ hg ci -d'1 0' -mb
346 353 $ hg rm a
347 354 $ hg ci -d'2 0' -mc
348 355 $ hg mv b a
349 356 $ hg ci -d'3 0' -md
350 357 $ echo a >> a
351 358 $ hg ci -d'4 0' -me
352 359 $ cd ..
353 360 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
354 361 assuming destination a-hg
355 362 initializing destination a-hg repository
356 363 scanning source...
357 364 sorting...
358 365 converting...
359 366 4 a
360 367 3 b
361 368 2 c
362 369 1 d
363 370 0 e
364 371 $ hg --cwd a-hg pull ../a
365 372 pulling from ../a
366 373 searching for changes
367 374 no changes found
368 375
369 376 conversion to existing file should fail
370 377
371 378 $ touch bogusfile
372 379 $ hg convert a bogusfile
373 380 initializing destination bogusfile repository
374 381 abort: cannot create new bundle repository
375 382 [255]
376 383
377 384 #if unix-permissions no-root
378 385
379 386 conversion to dir without permissions should fail
380 387
381 388 $ mkdir bogusdir
382 389 $ chmod 000 bogusdir
383 390
384 391 $ hg convert a bogusdir
385 392 abort: Permission denied: 'bogusdir'
386 393 [255]
387 394
388 395 user permissions should succeed
389 396
390 397 $ chmod 700 bogusdir
391 398 $ hg convert a bogusdir
392 399 initializing destination bogusdir repository
393 400 scanning source...
394 401 sorting...
395 402 converting...
396 403 4 a
397 404 3 b
398 405 2 c
399 406 1 d
400 407 0 e
401 408
402 409 #endif
403 410
404 411 test pre and post conversion actions
405 412
406 413 $ echo 'include b' > filemap
407 414 $ hg convert --debug --filemap filemap a partialb | \
408 415 > grep 'run hg'
409 416 run hg source pre-conversion action
410 417 run hg sink pre-conversion action
411 418 run hg sink post-conversion action
412 419 run hg source post-conversion action
413 420
414 421 converting empty dir should fail "nicely
415 422
416 423 $ mkdir emptydir
417 424
418 425 override $PATH to ensure p4 not visible; use $PYTHON in case we're
419 426 running from a devel copy, not a temp installation
420 427
421 428 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
422 429 assuming destination emptydir-hg
423 430 initializing destination emptydir-hg repository
424 431 emptydir does not look like a CVS checkout
425 432 $TESTTMP/emptydir does not look like a Git repository (glob)
426 433 emptydir does not look like a Subversion repository
427 434 emptydir is not a local Mercurial repository
428 435 emptydir does not look like a darcs repository
429 436 emptydir does not look like a monotone repository
430 437 emptydir does not look like a GNU Arch repository
431 438 emptydir does not look like a Bazaar repository
432 439 cannot find required "p4" tool
433 440 abort: emptydir: missing or unsupported repository
434 441 [255]
435 442
436 443 convert with imaginary source type
437 444
438 445 $ hg convert --source-type foo a a-foo
439 446 initializing destination a-foo repository
440 447 abort: foo: invalid source repository type
441 448 [255]
442 449
443 450 convert with imaginary sink type
444 451
445 452 $ hg convert --dest-type foo a a-foo
446 453 abort: foo: invalid destination repository type
447 454 [255]
448 455
449 456 testing: convert must not produce duplicate entries in fncache
450 457
451 458 $ hg convert a b
452 459 initializing destination b repository
453 460 scanning source...
454 461 sorting...
455 462 converting...
456 463 4 a
457 464 3 b
458 465 2 c
459 466 1 d
460 467 0 e
461 468
462 469 contents of fncache file:
463 470
464 471 $ cat b/.hg/store/fncache | sort
465 472 data/a.i
466 473 data/b.i
467 474
468 475 test bogus URL
469 476
470 477 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
471 478 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
472 479 [255]
473 480
474 481 test revset converted() lookup
475 482
476 483 $ hg --config convert.hg.saverev=True convert a c
477 484 initializing destination c repository
478 485 scanning source...
479 486 sorting...
480 487 converting...
481 488 4 a
482 489 3 b
483 490 2 c
484 491 1 d
485 492 0 e
486 493 $ echo f > c/f
487 494 $ hg -R c ci -d'0 0' -Amf
488 495 adding f
489 496 created new head
490 497 $ hg -R c log -r "converted(09d945a62ce6)"
491 498 changeset: 1:98c3dd46a874
492 499 user: test
493 500 date: Thu Jan 01 00:00:01 1970 +0000
494 501 summary: b
495 502
496 503 $ hg -R c log -r "converted()"
497 504 changeset: 0:31ed57b2037c
498 505 user: test
499 506 date: Thu Jan 01 00:00:00 1970 +0000
500 507 summary: a
501 508
502 509 changeset: 1:98c3dd46a874
503 510 user: test
504 511 date: Thu Jan 01 00:00:01 1970 +0000
505 512 summary: b
506 513
507 514 changeset: 2:3b9ca06ef716
508 515 user: test
509 516 date: Thu Jan 01 00:00:02 1970 +0000
510 517 summary: c
511 518
512 519 changeset: 3:4e0debd37cf2
513 520 user: test
514 521 date: Thu Jan 01 00:00:03 1970 +0000
515 522 summary: d
516 523
517 524 changeset: 4:9de3bc9349c5
518 525 user: test
519 526 date: Thu Jan 01 00:00:04 1970 +0000
520 527 summary: e
521 528
522 529
523 530 test specifying a sourcename
524 531 $ echo g > a/g
525 532 $ hg -R a ci -d'0 0' -Amg
526 533 adding g
527 534 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
528 535 scanning source...
529 536 sorting...
530 537 converting...
531 538 0 g
532 539 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
533 540 branch=default
534 541 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
535 542 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now