##// END OF EJS Templates
convert: add convert.git.skipsubmodules option...
Durham Goode -
r26077:e63d05fb default
parent child Browse files
Show More
@@ -1,447 +1,450 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 import convcmd
11 11 import cvsps
12 12 import subversion
13 13 from mercurial import cmdutil, templatekw
14 14 from mercurial.i18n import _
15 15
16 16 cmdtable = {}
17 17 command = cmdutil.command(cmdtable)
18 18 # Note for extension authors: ONLY specify testedwith = 'internal' for
19 19 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
20 20 # be specifying the version(s) of Mercurial they are tested with, or
21 21 # leave the attribute unspecified.
22 22 testedwith = 'internal'
23 23
24 24 # Commands definition was moved elsewhere to ease demandload job.
25 25
26 26 @command('convert',
27 27 [('', 'authors', '',
28 28 _('username mapping filename (DEPRECATED, use --authormap instead)'),
29 29 _('FILE')),
30 30 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
31 31 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
32 32 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
33 33 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
34 34 ('', 'filemap', '', _('remap file names using contents of file'),
35 35 _('FILE')),
36 36 ('', 'full', None,
37 37 _('apply filemap changes by converting all files again')),
38 38 ('', 'splicemap', '', _('splice synthesized history into place'),
39 39 _('FILE')),
40 40 ('', 'branchmap', '', _('change branch names while converting'),
41 41 _('FILE')),
42 42 ('', 'branchsort', None, _('try to sort changesets by branches')),
43 43 ('', 'datesort', None, _('try to sort changesets by date')),
44 44 ('', 'sourcesort', None, _('preserve source changesets order')),
45 45 ('', 'closesort', None, _('try to reorder closed revisions'))],
46 46 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
47 47 norepo=True)
48 48 def convert(ui, src, dest=None, revmapfile=None, **opts):
49 49 """convert a foreign SCM repository to a Mercurial one.
50 50
51 51 Accepted source formats [identifiers]:
52 52
53 53 - Mercurial [hg]
54 54 - CVS [cvs]
55 55 - Darcs [darcs]
56 56 - git [git]
57 57 - Subversion [svn]
58 58 - Monotone [mtn]
59 59 - GNU Arch [gnuarch]
60 60 - Bazaar [bzr]
61 61 - Perforce [p4]
62 62
63 63 Accepted destination formats [identifiers]:
64 64
65 65 - Mercurial [hg]
66 66 - Subversion [svn] (history on branches is not preserved)
67 67
68 68 If no revision is given, all revisions will be converted.
69 69 Otherwise, convert will only import up to the named revision
70 70 (given in a format understood by the source).
71 71
72 72 If no destination directory name is specified, it defaults to the
73 73 basename of the source with ``-hg`` appended. If the destination
74 74 repository doesn't exist, it will be created.
75 75
76 76 By default, all sources except Mercurial will use --branchsort.
77 77 Mercurial uses --sourcesort to preserve original revision numbers
78 78 order. Sort modes have the following effects:
79 79
80 80 --branchsort convert from parent to child revision when possible,
81 81 which means branches are usually converted one after
82 82 the other. It generates more compact repositories.
83 83
84 84 --datesort sort revisions by date. Converted repositories have
85 85 good-looking changelogs but are often an order of
86 86 magnitude larger than the same ones generated by
87 87 --branchsort.
88 88
89 89 --sourcesort try to preserve source revisions order, only
90 90 supported by Mercurial sources.
91 91
92 92 --closesort try to move closed revisions as close as possible
93 93 to parent branches, only supported by Mercurial
94 94 sources.
95 95
96 96 If ``REVMAP`` isn't given, it will be put in a default location
97 97 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
98 98 text file that maps each source commit ID to the destination ID
99 99 for that revision, like so::
100 100
101 101 <source ID> <destination ID>
102 102
103 103 If the file doesn't exist, it's automatically created. It's
104 104 updated on each commit copied, so :hg:`convert` can be interrupted
105 105 and can be run repeatedly to copy new commits.
106 106
107 107 The authormap is a simple text file that maps each source commit
108 108 author to a destination commit author. It is handy for source SCMs
109 109 that use unix logins to identify authors (e.g.: CVS). One line per
110 110 author mapping and the line format is::
111 111
112 112 source author = destination author
113 113
114 114 Empty lines and lines starting with a ``#`` are ignored.
115 115
116 116 The filemap is a file that allows filtering and remapping of files
117 117 and directories. Each line can contain one of the following
118 118 directives::
119 119
120 120 include path/to/file-or-dir
121 121
122 122 exclude path/to/file-or-dir
123 123
124 124 rename path/to/source path/to/destination
125 125
126 126 Comment lines start with ``#``. A specified path matches if it
127 127 equals the full relative name of a file or one of its parent
128 128 directories. The ``include`` or ``exclude`` directive with the
129 129 longest matching path applies, so line order does not matter.
130 130
131 131 The ``include`` directive causes a file, or all files under a
132 132 directory, to be included in the destination repository. The default
133 133 if there are no ``include`` statements is to include everything.
134 134 If there are any ``include`` statements, nothing else is included.
135 135 The ``exclude`` directive causes files or directories to
136 136 be omitted. The ``rename`` directive renames a file or directory if
137 137 it is converted. To rename from a subdirectory into the root of
138 138 the repository, use ``.`` as the path to rename to.
139 139
140 140 ``--full`` will make sure the converted changesets contain exactly
141 141 the right files with the right content. It will make a full
142 142 conversion of all files, not just the ones that have
143 143 changed. Files that already are correct will not be changed. This
144 144 can be used to apply filemap changes when converting
145 145 incrementally. This is currently only supported for Mercurial and
146 146 Subversion.
147 147
148 148 The splicemap is a file that allows insertion of synthetic
149 149 history, letting you specify the parents of a revision. This is
150 150 useful if you want to e.g. give a Subversion merge two parents, or
151 151 graft two disconnected series of history together. Each entry
152 152 contains a key, followed by a space, followed by one or two
153 153 comma-separated values::
154 154
155 155 key parent1, parent2
156 156
157 157 The key is the revision ID in the source
158 158 revision control system whose parents should be modified (same
159 159 format as a key in .hg/shamap). The values are the revision IDs
160 160 (in either the source or destination revision control system) that
161 161 should be used as the new parents for that node. For example, if
162 162 you have merged "release-1.0" into "trunk", then you should
163 163 specify the revision on "trunk" as the first parent and the one on
164 164 the "release-1.0" branch as the second.
165 165
166 166 The branchmap is a file that allows you to rename a branch when it is
167 167 being brought in from whatever external repository. When used in
168 168 conjunction with a splicemap, it allows for a powerful combination
169 169 to help fix even the most badly mismanaged repositories and turn them
170 170 into nicely structured Mercurial repositories. The branchmap contains
171 171 lines of the form::
172 172
173 173 original_branch_name new_branch_name
174 174
175 175 where "original_branch_name" is the name of the branch in the
176 176 source repository, and "new_branch_name" is the name of the branch
177 177 is the destination repository. No whitespace is allowed in the
178 178 branch names. This can be used to (for instance) move code in one
179 179 repository from "default" to a named branch.
180 180
181 181 Mercurial Source
182 182 ################
183 183
184 184 The Mercurial source recognizes the following configuration
185 185 options, which you can set on the command line with ``--config``:
186 186
187 187 :convert.hg.ignoreerrors: ignore integrity errors when reading.
188 188 Use it to fix Mercurial repositories with missing revlogs, by
189 189 converting from and to Mercurial. Default is False.
190 190
191 191 :convert.hg.saverev: store original revision ID in changeset
192 192 (forces target IDs to change). It takes a boolean argument and
193 193 defaults to False.
194 194
195 195 :convert.hg.startrev: specify the initial Mercurial revision.
196 196 The default is 0.
197 197
198 198 :convert.hg.revs: revset specifying the source revisions to convert.
199 199
200 200 CVS Source
201 201 ##########
202 202
203 203 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
204 204 to indicate the starting point of what will be converted. Direct
205 205 access to the repository files is not needed, unless of course the
206 206 repository is ``:local:``. The conversion uses the top level
207 207 directory in the sandbox to find the CVS repository, and then uses
208 208 CVS rlog commands to find files to convert. This means that unless
209 209 a filemap is given, all files under the starting directory will be
210 210 converted, and that any directory reorganization in the CVS
211 211 sandbox is ignored.
212 212
213 213 The following options can be used with ``--config``:
214 214
215 215 :convert.cvsps.cache: Set to False to disable remote log caching,
216 216 for testing and debugging purposes. Default is True.
217 217
218 218 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
219 219 allowed between commits with identical user and log message in
220 220 a single changeset. When very large files were checked in as
221 221 part of a changeset then the default may not be long enough.
222 222 The default is 60.
223 223
224 224 :convert.cvsps.mergeto: Specify a regular expression to which
225 225 commit log messages are matched. If a match occurs, then the
226 226 conversion process will insert a dummy revision merging the
227 227 branch on which this log message occurs to the branch
228 228 indicated in the regex. Default is ``{{mergetobranch
229 229 ([-\\w]+)}}``
230 230
231 231 :convert.cvsps.mergefrom: Specify a regular expression to which
232 232 commit log messages are matched. If a match occurs, then the
233 233 conversion process will add the most recent revision on the
234 234 branch indicated in the regex as the second parent of the
235 235 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
236 236
237 237 :convert.localtimezone: use local time (as determined by the TZ
238 238 environment variable) for changeset date/times. The default
239 239 is False (use UTC).
240 240
241 241 :hooks.cvslog: Specify a Python function to be called at the end of
242 242 gathering the CVS log. The function is passed a list with the
243 243 log entries, and can modify the entries in-place, or add or
244 244 delete them.
245 245
246 246 :hooks.cvschangesets: Specify a Python function to be called after
247 247 the changesets are calculated from the CVS log. The
248 248 function is passed a list with the changeset entries, and can
249 249 modify the changesets in-place, or add or delete them.
250 250
251 251 An additional "debugcvsps" Mercurial command allows the builtin
252 252 changeset merging code to be run without doing a conversion. Its
253 253 parameters and output are similar to that of cvsps 2.1. Please see
254 254 the command help for more details.
255 255
256 256 Subversion Source
257 257 #################
258 258
259 259 Subversion source detects classical trunk/branches/tags layouts.
260 260 By default, the supplied ``svn://repo/path/`` source URL is
261 261 converted as a single branch. If ``svn://repo/path/trunk`` exists
262 262 it replaces the default branch. If ``svn://repo/path/branches``
263 263 exists, its subdirectories are listed as possible branches. If
264 264 ``svn://repo/path/tags`` exists, it is looked for tags referencing
265 265 converted branches. Default ``trunk``, ``branches`` and ``tags``
266 266 values can be overridden with following options. Set them to paths
267 267 relative to the source URL, or leave them blank to disable auto
268 268 detection.
269 269
270 270 The following options can be set with ``--config``:
271 271
272 272 :convert.svn.branches: specify the directory containing branches.
273 273 The default is ``branches``.
274 274
275 275 :convert.svn.tags: specify the directory containing tags. The
276 276 default is ``tags``.
277 277
278 278 :convert.svn.trunk: specify the name of the trunk branch. The
279 279 default is ``trunk``.
280 280
281 281 :convert.localtimezone: use local time (as determined by the TZ
282 282 environment variable) for changeset date/times. The default
283 283 is False (use UTC).
284 284
285 285 Source history can be retrieved starting at a specific revision,
286 286 instead of being integrally converted. Only single branch
287 287 conversions are supported.
288 288
289 289 :convert.svn.startrev: specify start Subversion revision number.
290 290 The default is 0.
291 291
292 292 Git Source
293 293 ##########
294 294
295 295 The Git importer converts commits from all reachable branches (refs
296 296 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
297 297 Branches are converted to bookmarks with the same name, with the
298 298 leading 'refs/heads' stripped. Git submodules are converted to Git
299 299 subrepos in Mercurial.
300 300
301 301 The following options can be set with ``--config``:
302 302
303 303 :convert.git.similarity: specify how similar files modified in a
304 304 commit must be to be imported as renames or copies, as a
305 305 percentage between ``0`` (disabled) and ``100`` (files must be
306 306 identical). For example, ``90`` means that a delete/add pair will
307 307 be imported as a rename if more than 90% of the file hasn't
308 308 changed. The default is ``50``.
309 309
310 310 :convert.git.findcopiesharder: while detecting copies, look at all
311 311 files in the working copy instead of just changed ones. This
312 312 is very expensive for large projects, and is only effective when
313 313 ``convert.git.similarity`` is greater than 0. The default is False.
314 314
315 315 :convert.git.remoteprefix: remote refs are converted as bookmarks with
316 316 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
317 317 is 'remote'.
318 318
319 :convert.git.skipsubmodules: does not convert root level .gitmodules files
320 or files with 160000 mode indicating a submodule. Default is False.
321
319 322 Perforce Source
320 323 ###############
321 324
322 325 The Perforce (P4) importer can be given a p4 depot path or a
323 326 client specification as source. It will convert all files in the
324 327 source to a flat Mercurial repository, ignoring labels, branches
325 328 and integrations. Note that when a depot path is given you then
326 329 usually should specify a target directory, because otherwise the
327 330 target may be named ``...-hg``.
328 331
329 332 The following options can be set with ``--config``:
330 333
331 334 :convert.p4.encoding: specify the encoding to use when decoding standard
332 335 output of the Perforce command line tool. The default is default system
333 336 encoding.
334 337
335 338 :convert.p4.startrev: specify initial Perforce revision (a
336 339 Perforce changelist number).
337 340
338 341 Mercurial Destination
339 342 #####################
340 343
341 344 The Mercurial destination will recognize Mercurial subrepositories in the
342 345 destination directory, and update the .hgsubstate file automatically if the
343 346 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
344 347 Converting a repository with subrepositories requires converting a single
345 348 repository at a time, from the bottom up.
346 349
347 350 .. container:: verbose
348 351
349 352 An example showing how to convert a repository with subrepositories::
350 353
351 354 # so convert knows the type when it sees a non empty destination
352 355 $ hg init converted
353 356
354 357 $ hg convert orig/sub1 converted/sub1
355 358 $ hg convert orig/sub2 converted/sub2
356 359 $ hg convert orig converted
357 360
358 361 The following options are supported:
359 362
360 363 :convert.hg.clonebranches: dispatch source branches in separate
361 364 clones. The default is False.
362 365
363 366 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
364 367 ``default``.
365 368
366 369 :convert.hg.usebranchnames: preserve branch names. The default is
367 370 True.
368 371
369 372 :convert.hg.sourcename: records the given string as a 'convert_source' extra
370 373 value on each commit made in the target repository. The default is None.
371 374
372 375 All Destinations
373 376 ################
374 377
375 378 All destination types accept the following options:
376 379
377 380 :convert.skiptags: does not convert tags from the source repo to the target
378 381 repo. The default is False.
379 382 """
380 383 return convcmd.convert(ui, src, dest, revmapfile, **opts)
381 384
382 385 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
383 386 def debugsvnlog(ui, **opts):
384 387 return subversion.debugsvnlog(ui, **opts)
385 388
386 389 @command('debugcvsps',
387 390 [
388 391 # Main options shared with cvsps-2.1
389 392 ('b', 'branches', [], _('only return changes on specified branches')),
390 393 ('p', 'prefix', '', _('prefix to remove from file names')),
391 394 ('r', 'revisions', [],
392 395 _('only return changes after or between specified tags')),
393 396 ('u', 'update-cache', None, _("update cvs log cache")),
394 397 ('x', 'new-cache', None, _("create new cvs log cache")),
395 398 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
396 399 ('', 'root', '', _('specify cvsroot')),
397 400 # Options specific to builtin cvsps
398 401 ('', 'parents', '', _('show parent changesets')),
399 402 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
400 403 # Options that are ignored for compatibility with cvsps-2.1
401 404 ('A', 'cvs-direct', None, _('ignored for compatibility')),
402 405 ],
403 406 _('hg debugcvsps [OPTION]... [PATH]...'),
404 407 norepo=True)
405 408 def debugcvsps(ui, *args, **opts):
406 409 '''create changeset information from CVS
407 410
408 411 This command is intended as a debugging tool for the CVS to
409 412 Mercurial converter, and can be used as a direct replacement for
410 413 cvsps.
411 414
412 415 Hg debugcvsps reads the CVS rlog for current directory (or any
413 416 named directory) in the CVS repository, and converts the log to a
414 417 series of changesets based on matching commit log entries and
415 418 dates.'''
416 419 return cvsps.debugcvsps(ui, *args, **opts)
417 420
418 421 def kwconverted(ctx, name):
419 422 rev = ctx.extra().get('convert_revision', '')
420 423 if rev.startswith('svn:'):
421 424 if name == 'svnrev':
422 425 return str(subversion.revsplit(rev)[2])
423 426 elif name == 'svnpath':
424 427 return subversion.revsplit(rev)[1]
425 428 elif name == 'svnuuid':
426 429 return subversion.revsplit(rev)[0]
427 430 return rev
428 431
429 432 def kwsvnrev(repo, ctx, **args):
430 433 """:svnrev: String. Converted subversion revision number."""
431 434 return kwconverted(ctx, 'svnrev')
432 435
433 436 def kwsvnpath(repo, ctx, **args):
434 437 """:svnpath: String. Converted subversion revision project path."""
435 438 return kwconverted(ctx, 'svnpath')
436 439
437 440 def kwsvnuuid(repo, ctx, **args):
438 441 """:svnuuid: String. Converted subversion revision repository identifier."""
439 442 return kwconverted(ctx, 'svnuuid')
440 443
441 444 def extsetup(ui):
442 445 templatekw.keywords['svnrev'] = kwsvnrev
443 446 templatekw.keywords['svnpath'] = kwsvnpath
444 447 templatekw.keywords['svnuuid'] = kwsvnuuid
445 448
446 449 # tell hggettext to extract docstrings from these functions:
447 450 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,410 +1,416 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
8 8 import os
9 9 import subprocess
10 10 from mercurial import util, config, error
11 11 from mercurial.node import hex, nullid
12 12 from mercurial.i18n import _
13 13
14 14 from common import NoRepo, commit, converter_source, checktool
15 15
16 16 class submodule(object):
17 17 def __init__(self, path, node, url):
18 18 self.path = path
19 19 self.node = node
20 20 self.url = url
21 21
22 22 def hgsub(self):
23 23 return "%s = [git]%s" % (self.path, self.url)
24 24
25 25 def hgsubstate(self):
26 26 return "%s %s" % (self.node, self.path)
27 27
28 28 class convert_git(converter_source):
29 29 # Windows does not support GIT_DIR= construct while other systems
30 30 # cannot remove environment variable. Just assume none have
31 31 # both issues.
32 32 if util.safehasattr(os, 'unsetenv'):
33 33 def gitopen(self, s, err=None):
34 34 prevgitdir = os.environ.get('GIT_DIR')
35 35 os.environ['GIT_DIR'] = self.path
36 36 try:
37 37 if err == subprocess.PIPE:
38 38 (stdin, stdout, stderr) = util.popen3(s)
39 39 return stdout
40 40 elif err == subprocess.STDOUT:
41 41 return self.popen_with_stderr(s)
42 42 else:
43 43 return util.popen(s, 'rb')
44 44 finally:
45 45 if prevgitdir is None:
46 46 del os.environ['GIT_DIR']
47 47 else:
48 48 os.environ['GIT_DIR'] = prevgitdir
49 49
50 50 def gitpipe(self, s):
51 51 prevgitdir = os.environ.get('GIT_DIR')
52 52 os.environ['GIT_DIR'] = self.path
53 53 try:
54 54 return util.popen3(s)
55 55 finally:
56 56 if prevgitdir is None:
57 57 del os.environ['GIT_DIR']
58 58 else:
59 59 os.environ['GIT_DIR'] = prevgitdir
60 60
61 61 else:
62 62 def gitopen(self, s, err=None):
63 63 if err == subprocess.PIPE:
64 64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
65 65 return so
66 66 elif err == subprocess.STDOUT:
67 67 return self.popen_with_stderr(s)
68 68 else:
69 69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
70 70
71 71 def gitpipe(self, s):
72 72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
73 73
74 74 def popen_with_stderr(self, s):
75 75 p = subprocess.Popen(s, shell=True, bufsize=-1,
76 76 close_fds=util.closefds,
77 77 stdin=subprocess.PIPE,
78 78 stdout=subprocess.PIPE,
79 79 stderr=subprocess.STDOUT,
80 80 universal_newlines=False,
81 81 env=None)
82 82 return p.stdout
83 83
84 84 def gitread(self, s):
85 85 fh = self.gitopen(s)
86 86 data = fh.read()
87 87 return data, fh.close()
88 88
89 89 def __init__(self, ui, path, revs=None):
90 90 super(convert_git, self).__init__(ui, path, revs=revs)
91 91
92 92 if os.path.isdir(path + "/.git"):
93 93 path += "/.git"
94 94 if not os.path.exists(path + "/objects"):
95 95 raise NoRepo(_("%s does not look like a Git repository") % path)
96 96
97 97 # The default value (50) is based on the default for 'git diff'.
98 98 similarity = ui.configint('convert', 'git.similarity', default=50)
99 99 if similarity < 0 or similarity > 100:
100 100 raise util.Abort(_('similarity must be between 0 and 100'))
101 101 if similarity > 0:
102 102 self.simopt = '-C%d%%' % similarity
103 103 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
104 104 False)
105 105 if findcopiesharder:
106 106 self.simopt += ' --find-copies-harder'
107 107 else:
108 108 self.simopt = ''
109 109
110 110 checktool('git', 'git')
111 111
112 112 self.path = path
113 113 self.submodules = []
114 114
115 115 self.catfilepipe = self.gitpipe('git cat-file --batch')
116 116
117 117 def after(self):
118 118 for f in self.catfilepipe:
119 119 f.close()
120 120
121 121 def getheads(self):
122 122 if not self.revs:
123 123 heads, ret = self.gitread('git rev-parse --branches --remotes')
124 124 heads = heads.splitlines()
125 125 if ret:
126 126 raise util.Abort(_('cannot retrieve git heads'))
127 127 else:
128 128 heads = []
129 129 for rev in self.revs:
130 130 rawhead, ret = self.gitread("git rev-parse --verify %s" % rev)
131 131 heads.append(rawhead[:-1])
132 132 if ret:
133 133 raise util.Abort(_('cannot retrieve git head "%s"') % rev)
134 134 return heads
135 135
136 136 def catfile(self, rev, type):
137 137 if rev == hex(nullid):
138 138 raise IOError
139 139 self.catfilepipe[0].write(rev+'\n')
140 140 self.catfilepipe[0].flush()
141 141 info = self.catfilepipe[1].readline().split()
142 142 if info[1] != type:
143 143 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
144 144 size = int(info[2])
145 145 data = self.catfilepipe[1].read(size)
146 146 if len(data) < size:
147 147 raise util.Abort(_('cannot read %r object at %s: unexpected size')
148 148 % (type, rev))
149 149 # read the trailing newline
150 150 self.catfilepipe[1].read(1)
151 151 return data
152 152
153 153 def getfile(self, name, rev):
154 154 if rev == hex(nullid):
155 155 return None, None
156 156 if name == '.hgsub':
157 157 data = '\n'.join([m.hgsub() for m in self.submoditer()])
158 158 mode = ''
159 159 elif name == '.hgsubstate':
160 160 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
161 161 mode = ''
162 162 else:
163 163 data = self.catfile(rev, "blob")
164 164 mode = self.modecache[(name, rev)]
165 165 return data, mode
166 166
167 167 def submoditer(self):
168 168 null = hex(nullid)
169 169 for m in sorted(self.submodules, key=lambda p: p.path):
170 170 if m.node != null:
171 171 yield m
172 172
173 173 def parsegitmodules(self, content):
174 174 """Parse the formatted .gitmodules file, example file format:
175 175 [submodule "sub"]\n
176 176 \tpath = sub\n
177 177 \turl = git://giturl\n
178 178 """
179 179 self.submodules = []
180 180 c = config.config()
181 181 # Each item in .gitmodules starts with whitespace that cant be parsed
182 182 c.parse('.gitmodules', '\n'.join(line.strip() for line in
183 183 content.split('\n')))
184 184 for sec in c.sections():
185 185 s = c[sec]
186 186 if 'url' in s and 'path' in s:
187 187 self.submodules.append(submodule(s['path'], '', s['url']))
188 188
189 189 def retrievegitmodules(self, version):
190 190 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
191 191 if ret:
192 192 # This can happen if a file is in the repo that has permissions
193 193 # 160000, but there is no .gitmodules file.
194 194 self.ui.warn(_("warning: cannot read submodules config file in "
195 195 "%s\n") % version)
196 196 return
197 197
198 198 try:
199 199 self.parsegitmodules(modules)
200 200 except error.ParseError:
201 201 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
202 202 % version)
203 203 return
204 204
205 205 for m in self.submodules:
206 206 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
207 207 if ret:
208 208 continue
209 209 m.node = node.strip()
210 210
211 211 def getchanges(self, version, full):
212 212 if full:
213 213 raise util.Abort(_("convert from git do not support --full"))
214 214 self.modecache = {}
215 215 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
216 216 self.simopt, version))
217 217 changes = []
218 218 copies = {}
219 219 seen = set()
220 220 entry = None
221 221 subexists = [False]
222 222 subdeleted = [False]
223 223 difftree = fh.read().split('\x00')
224 224 lcount = len(difftree)
225 225 i = 0
226 226
227 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
228 False)
227 229 def add(entry, f, isdest):
228 230 seen.add(f)
229 231 h = entry[3]
230 232 p = (entry[1] == "100755")
231 233 s = (entry[1] == "120000")
232 234 renamesource = (not isdest and entry[4][0] == 'R')
233 235
234 236 if f == '.gitmodules':
237 if skipsubmodules:
238 return
239
235 240 subexists[0] = True
236 241 if entry[4] == 'D' or renamesource:
237 242 subdeleted[0] = True
238 243 changes.append(('.hgsub', hex(nullid)))
239 244 else:
240 245 changes.append(('.hgsub', ''))
241 246 elif entry[1] == '160000' or entry[0] == ':160000':
242 subexists[0] = True
247 if not skipsubmodules:
248 subexists[0] = True
243 249 else:
244 250 if renamesource:
245 251 h = hex(nullid)
246 252 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
247 253 changes.append((f, h))
248 254
249 255 while i < lcount:
250 256 l = difftree[i]
251 257 i += 1
252 258 if not entry:
253 259 if not l.startswith(':'):
254 260 continue
255 261 entry = l.split()
256 262 continue
257 263 f = l
258 264 if entry[4][0] == 'C':
259 265 copysrc = f
260 266 copydest = difftree[i]
261 267 i += 1
262 268 f = copydest
263 269 copies[copydest] = copysrc
264 270 if f not in seen:
265 271 add(entry, f, False)
266 272 # A file can be copied multiple times, or modified and copied
267 273 # simultaneously. So f can be repeated even if fdest isn't.
268 274 if entry[4][0] == 'R':
269 275 # rename: next line is the destination
270 276 fdest = difftree[i]
271 277 i += 1
272 278 if fdest not in seen:
273 279 add(entry, fdest, True)
274 280 # .gitmodules isn't imported at all, so it being copied to
275 281 # and fro doesn't really make sense
276 282 if f != '.gitmodules' and fdest != '.gitmodules':
277 283 copies[fdest] = f
278 284 entry = None
279 285 if fh.close():
280 286 raise util.Abort(_('cannot read changes in %s') % version)
281 287
282 288 if subexists[0]:
283 289 if subdeleted[0]:
284 290 changes.append(('.hgsubstate', hex(nullid)))
285 291 else:
286 292 self.retrievegitmodules(version)
287 293 changes.append(('.hgsubstate', ''))
288 294 return (changes, copies, set())
289 295
290 296 def getcommit(self, version):
291 297 c = self.catfile(version, "commit") # read the commit hash
292 298 end = c.find("\n\n")
293 299 message = c[end + 2:]
294 300 message = self.recode(message)
295 301 l = c[:end].splitlines()
296 302 parents = []
297 303 author = committer = None
298 304 for e in l[1:]:
299 305 n, v = e.split(" ", 1)
300 306 if n == "author":
301 307 p = v.split()
302 308 tm, tz = p[-2:]
303 309 author = " ".join(p[:-2])
304 310 if author[0] == "<": author = author[1:-1]
305 311 author = self.recode(author)
306 312 if n == "committer":
307 313 p = v.split()
308 314 tm, tz = p[-2:]
309 315 committer = " ".join(p[:-2])
310 316 if committer[0] == "<": committer = committer[1:-1]
311 317 committer = self.recode(committer)
312 318 if n == "parent":
313 319 parents.append(v)
314 320
315 321 if committer and committer != author:
316 322 message += "\ncommitter: %s\n" % committer
317 323 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
318 324 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
319 325 date = tm + " " + str(tz)
320 326
321 327 c = commit(parents=parents, date=date, author=author, desc=message,
322 328 rev=version)
323 329 return c
324 330
325 331 def numcommits(self):
326 332 return len([None for _ in self.gitopen('git rev-list --all')])
327 333
328 334 def gettags(self):
329 335 tags = {}
330 336 alltags = {}
331 337 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
332 338 err=subprocess.STDOUT)
333 339 prefix = 'refs/tags/'
334 340
335 341 # Build complete list of tags, both annotated and bare ones
336 342 for line in fh:
337 343 line = line.strip()
338 344 if line.startswith("error:") or line.startswith("fatal:"):
339 345 raise util.Abort(_('cannot read tags from %s') % self.path)
340 346 node, tag = line.split(None, 1)
341 347 if not tag.startswith(prefix):
342 348 continue
343 349 alltags[tag[len(prefix):]] = node
344 350 if fh.close():
345 351 raise util.Abort(_('cannot read tags from %s') % self.path)
346 352
347 353 # Filter out tag objects for annotated tag refs
348 354 for tag in alltags:
349 355 if tag.endswith('^{}'):
350 356 tags[tag[:-3]] = alltags[tag]
351 357 else:
352 358 if tag + '^{}' in alltags:
353 359 continue
354 360 else:
355 361 tags[tag] = alltags[tag]
356 362
357 363 return tags
358 364
359 365 def getchangedfiles(self, version, i):
360 366 changes = []
361 367 if i is None:
362 368 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
363 369 for l in fh:
364 370 if "\t" not in l:
365 371 continue
366 372 m, f = l[:-1].split("\t")
367 373 changes.append(f)
368 374 else:
369 375 fh = self.gitopen('git diff-tree --name-only --root -r %s '
370 376 '"%s^%s" --' % (version, version, i + 1))
371 377 changes = [f.rstrip('\n') for f in fh]
372 378 if fh.close():
373 379 raise util.Abort(_('cannot read changes in %s') % version)
374 380
375 381 return changes
376 382
377 383 def getbookmarks(self):
378 384 bookmarks = {}
379 385
380 386 # Handle local and remote branches
381 387 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
382 388 reftypes = [
383 389 # (git prefix, hg prefix)
384 390 ('refs/remotes/origin/', remoteprefix + '/'),
385 391 ('refs/heads/', '')
386 392 ]
387 393
388 394 exclude = set([
389 395 'refs/remotes/origin/HEAD',
390 396 ])
391 397
392 398 try:
393 399 fh = self.gitopen('git show-ref', err=subprocess.PIPE)
394 400 for line in fh:
395 401 line = line.strip()
396 402 rev, name = line.split(None, 1)
397 403 # Process each type of branch
398 404 for gitprefix, hgprefix in reftypes:
399 405 if not name.startswith(gitprefix) or name in exclude:
400 406 continue
401 407 name = '%s%s' % (hgprefix, name[len(gitprefix):])
402 408 bookmarks[name] = rev
403 409 except Exception:
404 410 pass
405 411
406 412 return bookmarks
407 413
408 414 def checkrevformat(self, revstr, mapname='splicemap'):
409 415 """ git revision string is a 40 byte hex """
410 416 self.checkhexformat(revstr, mapname)
@@ -1,728 +1,734 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 > --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 \r (no-eol) (esc)
178 178 scanning [===> ] 1/9\r (no-eol) (esc)
179 179 scanning [========> ] 2/9\r (no-eol) (esc)
180 180 scanning [=============> ] 3/9\r (no-eol) (esc)
181 181 scanning [==================> ] 4/9\r (no-eol) (esc)
182 182 scanning [=======================> ] 5/9\r (no-eol) (esc)
183 183 scanning [============================> ] 6/9\r (no-eol) (esc)
184 184 scanning [=================================> ] 7/9\r (no-eol) (esc)
185 185 scanning [======================================> ] 8/9\r (no-eol) (esc)
186 186 scanning [===========================================>] 9/9\r (no-eol) (esc)
187 187 \r (no-eol) (esc)
188 188 \r (no-eol) (esc)
189 189 converting [ ] 0/9\r (no-eol) (esc)
190 190 getting files [======================================>] 1/1\r (no-eol) (esc)
191 191 \r (no-eol) (esc)
192 192 \r (no-eol) (esc)
193 193 converting [===> ] 1/9\r (no-eol) (esc)
194 194 getting files [======================================>] 1/1\r (no-eol) (esc)
195 195 \r (no-eol) (esc)
196 196 \r (no-eol) (esc)
197 197 converting [========> ] 2/9\r (no-eol) (esc)
198 198 getting files [======================================>] 1/1\r (no-eol) (esc)
199 199 \r (no-eol) (esc)
200 200 \r (no-eol) (esc)
201 201 converting [=============> ] 3/9\r (no-eol) (esc)
202 202 getting files [======================================>] 1/1\r (no-eol) (esc)
203 203 \r (no-eol) (esc)
204 204 \r (no-eol) (esc)
205 205 converting [=================> ] 4/9\r (no-eol) (esc)
206 206 getting files [======================================>] 1/1\r (no-eol) (esc)
207 207 \r (no-eol) (esc)
208 208 \r (no-eol) (esc)
209 209 converting [======================> ] 5/9\r (no-eol) (esc)
210 210 getting files [===> ] 1/8\r (no-eol) (esc)
211 211 getting files [========> ] 2/8\r (no-eol) (esc)
212 212 getting files [=============> ] 3/8\r (no-eol) (esc)
213 213 getting files [==================> ] 4/8\r (no-eol) (esc)
214 214 getting files [=======================> ] 5/8\r (no-eol) (esc)
215 215 getting files [============================> ] 6/8\r (no-eol) (esc)
216 216 getting files [=================================> ] 7/8\r (no-eol) (esc)
217 217 getting files [======================================>] 8/8\r (no-eol) (esc)
218 218 \r (no-eol) (esc)
219 219 \r (no-eol) (esc)
220 220 converting [===========================> ] 6/9\r (no-eol) (esc)
221 221 getting files [======================================>] 1/1\r (no-eol) (esc)
222 222 \r (no-eol) (esc)
223 223 \r (no-eol) (esc)
224 224 converting [===============================> ] 7/9\r (no-eol) (esc)
225 225 getting files [======================================>] 1/1\r (no-eol) (esc)
226 226 \r (no-eol) (esc)
227 227 \r (no-eol) (esc)
228 228 converting [====================================> ] 8/9\r (no-eol) (esc)
229 229 getting files [==================> ] 1/2\r (no-eol) (esc)
230 230 getting files [======================================>] 2/2\r (no-eol) (esc)
231 231 \r (no-eol) (esc)
232 232 initializing destination fullrepo repository
233 233 scanning source...
234 234 sorting...
235 235 converting...
236 236 8 add foo
237 237 7 change foo
238 238 6 add quux
239 239 5 add bar
240 240 4 add baz
241 241 3 Octopus merge
242 242 2 change bar
243 243 1 change foo
244 244 0 Discard change to foo
245 245 updating bookmarks
246 246 $ hg up -q -R fullrepo
247 247 $ glog -R fullrepo
248 248 @ 9 "Discard change to foo" files: foo
249 249 |\
250 250 | o 8 "change foo" files: foo
251 251 | |
252 252 o | 7 "change bar" files: bar
253 253 |/
254 254 o 6 "(octopus merge fixup)" files:
255 255 |\
256 256 | o 5 "Octopus merge" files: baz
257 257 | |\
258 258 o | | 4 "add baz" files: baz
259 259 | | |
260 260 +---o 3 "add bar" files: bar
261 261 | |
262 262 o | 2 "add quux" files: quux
263 263 | |
264 264 | o 1 "change foo" files: foo
265 265 |/
266 266 o 0 "add foo" files: foo
267 267
268 268 $ hg -R fullrepo manifest --debug
269 269 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
270 270 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
271 271 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
272 272 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
273 273 $ splitrepo 'octopus merge' 'foo bar baz'
274 274 % foo bar baz: octopus merge
275 275 @ 8 "Discard change to foo" files: foo
276 276 |\
277 277 | o 7 "change foo" files: foo
278 278 | |
279 279 o | 6 "change bar" files: bar
280 280 |/
281 281 o 5 "(octopus merge fixup)" files:
282 282 |\
283 283 | o 4 "Octopus merge" files: baz
284 284 | |\
285 285 o | | 3 "add baz" files: baz
286 286 | | |
287 287 +---o 2 "add bar" files: bar
288 288 | |
289 289 | o 1 "change foo" files: foo
290 290 |/
291 291 o 0 "add foo" files: foo
292 292
293 293 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
294 294 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
295 295 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
296 296 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
297 297 % foo baz quux: only some parents of an octopus merge; "discard" a head
298 298 @ 6 "Discard change to foo" files: foo
299 299 |
300 300 o 5 "change foo" files: foo
301 301 |
302 302 o 4 "Octopus merge" files:
303 303 |\
304 304 | o 3 "add baz" files: baz
305 305 | |
306 306 | o 2 "add quux" files: quux
307 307 | |
308 308 o | 1 "change foo" files: foo
309 309 |/
310 310 o 0 "add foo" files: foo
311 311
312 312 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
313 313 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
314 314 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
315 315
316 316 test importing git renames and copies
317 317
318 318 $ cd git-repo2
319 319 $ git mv foo foo-renamed
320 320 since bar is not touched in this commit, this copy will not be detected
321 321 $ cp bar bar-copied
322 322 $ cp baz baz-copied
323 323 $ cp baz baz-copied2
324 324 $ cp baz ba-copy
325 325 $ echo baz2 >> baz
326 326 $ git add bar-copied baz-copied baz-copied2 ba-copy
327 327 $ commit -a -m 'rename and copy'
328 328 $ cd ..
329 329
330 330 input validation
331 331 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
332 332 abort: convert.git.similarity is not an integer ('foo')
333 333 [255]
334 334 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
335 335 abort: similarity must be between 0 and 100
336 336 [255]
337 337 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
338 338 abort: similarity must be between 0 and 100
339 339 [255]
340 340
341 341 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
342 342 $ hg -R fullrepo status -C --change master
343 343 M baz
344 344 A ba-copy
345 345 baz
346 346 A bar-copied
347 347 A baz-copied
348 348 baz
349 349 A baz-copied2
350 350 baz
351 351 A foo-renamed
352 352 foo
353 353 R foo
354 354
355 355 Ensure that the modification to the copy source was preserved
356 356 (there was a bug where if the copy dest was alphabetically prior to the copy
357 357 source, the copy source took the contents of the copy dest)
358 358 $ hg cat -r tip fullrepo/baz
359 359 baz
360 360 baz2
361 361
362 362 $ cd git-repo2
363 363 $ echo bar2 >> bar
364 364 $ commit -a -m 'change bar'
365 365 $ cp bar bar-copied2
366 366 $ git add bar-copied2
367 367 $ commit -a -m 'copy with no changes'
368 368 $ cd ..
369 369
370 370 $ hg -q convert --config convert.git.similarity=100 \
371 371 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
372 372 $ hg -R fullrepo status -C --change master
373 373 A bar-copied2
374 374 bar
375 375
376 376 test binary conversion (issue1359)
377 377
378 378 $ count=19
379 379 $ mkdir git-repo3
380 380 $ cd git-repo3
381 381 $ git init-db >/dev/null 2>/dev/null
382 382 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
383 383 $ git add b
384 384 $ commit -a -m addbinary
385 385 $ cd ..
386 386
387 387 convert binary file
388 388
389 389 $ hg convert git-repo3 git-repo3-hg
390 390 initializing destination git-repo3-hg repository
391 391 scanning source...
392 392 sorting...
393 393 converting...
394 394 0 addbinary
395 395 updating bookmarks
396 396 $ cd git-repo3-hg
397 397 $ hg up -C
398 398 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
399 399 $ $PYTHON -c 'print len(file("b", "rb").read())'
400 400 4096
401 401 $ cd ..
402 402
403 403 test author vs committer
404 404
405 405 $ mkdir git-repo4
406 406 $ cd git-repo4
407 407 $ git init-db >/dev/null 2>/dev/null
408 408 $ echo >> foo
409 409 $ git add foo
410 410 $ commit -a -m addfoo
411 411 $ echo >> foo
412 412 $ GIT_AUTHOR_NAME="nottest"
413 413 $ commit -a -m addfoo2
414 414 $ cd ..
415 415
416 416 convert author committer
417 417
418 418 $ hg convert git-repo4 git-repo4-hg
419 419 initializing destination git-repo4-hg repository
420 420 scanning source...
421 421 sorting...
422 422 converting...
423 423 1 addfoo
424 424 0 addfoo2
425 425 updating bookmarks
426 426 $ hg -R git-repo4-hg log -v
427 427 changeset: 1:d63e967f93da
428 428 bookmark: master
429 429 tag: tip
430 430 user: nottest <test@example.org>
431 431 date: Mon Jan 01 00:00:21 2007 +0000
432 432 files: foo
433 433 description:
434 434 addfoo2
435 435
436 436 committer: test <test@example.org>
437 437
438 438
439 439 changeset: 0:0735477b0224
440 440 user: test <test@example.org>
441 441 date: Mon Jan 01 00:00:20 2007 +0000
442 442 files: foo
443 443 description:
444 444 addfoo
445 445
446 446
447 447
448 448 --sourceorder should fail
449 449
450 450 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
451 451 initializing destination git-repo4-sourcesort-hg repository
452 452 abort: --sourcesort is not supported by this data source
453 453 [255]
454 454
455 455 test converting certain branches
456 456
457 457 $ mkdir git-testrevs
458 458 $ cd git-testrevs
459 459 $ git init
460 460 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
461 461 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
462 462 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
463 463 $ git checkout -b goodbranch 'HEAD^'
464 464 Switched to a new branch 'goodbranch'
465 465 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
466 466 $ git checkout -b badbranch 'HEAD^'
467 467 Switched to a new branch 'badbranch'
468 468 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
469 469 $ cd ..
470 470 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
471 471 initializing destination hg-testrevs repository
472 472 scanning source...
473 473 sorting...
474 474 converting...
475 475 2 first
476 476 1 good branch commit
477 477 0 master commit
478 478 updating bookmarks
479 479 $ cd hg-testrevs
480 480 $ hg log -G -T '{rev} {bookmarks}'
481 481 o 2 master
482 482 |
483 483 | o 1 goodbranch
484 484 |/
485 485 o 0
486 486
487 487 $ cd ..
488 488
489 489 test sub modules
490 490
491 491 $ mkdir git-repo5
492 492 $ cd git-repo5
493 493 $ git init-db >/dev/null 2>/dev/null
494 494 $ echo 'sub' >> foo
495 495 $ git add foo
496 496 $ commit -a -m 'addfoo'
497 497 $ BASE=`pwd`
498 498 $ cd ..
499 499 $ mkdir git-repo6
500 500 $ cd git-repo6
501 501 $ git init-db >/dev/null 2>/dev/null
502 502 $ git submodule add ${BASE} >/dev/null 2>/dev/null
503 503 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
504 504
505 505 test non-tab whitespace .gitmodules
506 506
507 507 $ cat >> .gitmodules <<EOF
508 508 > [submodule "git-repo5"]
509 509 > path = git-repo5
510 510 > url = git-repo5
511 511 > EOF
512 512 $ git commit -q -a -m "weird white space submodule"
513 513 $ cd ..
514 514 $ hg convert git-repo6 hg-repo6
515 515 initializing destination hg-repo6 repository
516 516 scanning source...
517 517 sorting...
518 518 converting...
519 519 1 addsubmodule
520 520 0 weird white space submodule
521 521 updating bookmarks
522 522
523 523 $ rm -rf hg-repo6
524 524 $ cd git-repo6
525 525 $ git reset --hard 'HEAD^' > /dev/null
526 526
527 527 test missing .gitmodules
528 528
529 529 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
530 530 $ git checkout HEAD .gitmodules
531 531 $ git rm .gitmodules
532 532 rm '.gitmodules'
533 533 $ git commit -q -m "remove .gitmodules" .gitmodules
534 534 $ git commit -q -m "missing .gitmodules"
535 535 $ cd ..
536 536 $ hg convert git-repo6 hg-repo6 --traceback
537 537 fatal: Path '.gitmodules' does not exist in '*' (glob)
538 538 initializing destination hg-repo6 repository
539 539 scanning source...
540 540 sorting...
541 541 converting...
542 542 2 addsubmodule
543 543 1 remove .gitmodules
544 544 0 missing .gitmodules
545 545 warning: cannot read submodules config file in * (glob)
546 546 updating bookmarks
547 547 $ rm -rf hg-repo6
548 548 $ cd git-repo6
549 549 $ rm -rf git-repo4
550 550 $ git reset --hard 'HEAD^^' > /dev/null
551 551 $ cd ..
552 552
553 553 test invalid splicemap1
554 554
555 555 $ cat > splicemap <<EOF
556 556 > $VALIDID1
557 557 > EOF
558 558 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
559 559 initializing destination git-repo2-splicemap1-hg repository
560 560 abort: syntax error in splicemap(1): child parent1[,parent2] expected
561 561 [255]
562 562
563 563 test invalid splicemap2
564 564
565 565 $ cat > splicemap <<EOF
566 566 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
567 567 > EOF
568 568 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
569 569 initializing destination git-repo2-splicemap2-hg repository
570 570 abort: syntax error in splicemap(1): child parent1[,parent2] expected
571 571 [255]
572 572
573 573 test invalid splicemap3
574 574
575 575 $ cat > splicemap <<EOF
576 576 > $INVALIDID1 $INVALIDID2
577 577 > EOF
578 578 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
579 579 initializing destination git-repo2-splicemap3-hg repository
580 580 abort: splicemap entry afd12345af is not a valid revision identifier
581 581 [255]
582 582
583 583 convert sub modules
584 584 $ hg convert git-repo6 git-repo6-hg
585 585 initializing destination git-repo6-hg repository
586 586 scanning source...
587 587 sorting...
588 588 converting...
589 589 0 addsubmodule
590 590 updating bookmarks
591 591 $ hg -R git-repo6-hg log -v
592 592 changeset: 0:* (glob)
593 593 bookmark: master
594 594 tag: tip
595 595 user: nottest <test@example.org>
596 596 date: Mon Jan 01 00:00:23 2007 +0000
597 597 files: .hgsub .hgsubstate
598 598 description:
599 599 addsubmodule
600 600
601 601 committer: test <test@example.org>
602 602
603 603
604 604
605 605 $ cd git-repo6-hg
606 606 $ hg up >/dev/null 2>/dev/null
607 607 $ cat .hgsubstate
608 608 * git-repo5 (glob)
609 609 $ cd git-repo5
610 610 $ cat foo
611 611 sub
612 612
613 613 $ cd ../..
614 614
615 615 make sure rename detection doesn't break removing and adding gitmodules
616 616
617 617 $ cd git-repo6
618 618 $ git mv .gitmodules .gitmodules-renamed
619 619 $ commit -a -m 'rename .gitmodules'
620 620 $ git mv .gitmodules-renamed .gitmodules
621 621 $ commit -a -m 'rename .gitmodules back'
622 622 $ cd ..
623 623
624 624 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
625 625 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
626 626 rename .gitmodules
627 627 $ hg -R git-repo6-hg status -C --change 'tip^'
628 628 A .gitmodules-renamed
629 629 R .hgsub
630 630 R .hgsubstate
631 631 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
632 632 rename .gitmodules back
633 633 $ hg -R git-repo6-hg status -C --change tip
634 634 A .hgsub
635 635 A .hgsubstate
636 636 R .gitmodules-renamed
637 637
638 638 convert the revision removing '.gitmodules' itself (and related
639 639 submodules)
640 640
641 641 $ cd git-repo6
642 642 $ git rm .gitmodules
643 643 rm '.gitmodules'
644 644 $ git rm --cached git-repo5
645 645 rm 'git-repo5'
646 646 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
647 647 $ cd ..
648 648
649 649 $ hg convert -q git-repo6 git-repo6-hg
650 650 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
651 651 remove .gitmodules and submodule git-repo5
652 652 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
653 653 .hgsub .hgsubstate
654 654
655 skip submodules in the conversion
656
657 $ hg convert -q git-repo6 no-submodules --config convert.git.skipsubmodules=True
658 $ hg -R no-submodules manifest --all
659 .gitmodules-renamed
660
655 661 convert using a different remote prefix
656 662 $ git init git-repo7
657 663 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
658 664 $ cd git-repo7
659 665 TODO: it'd be nice to use (?) lines instead of grep -v to handle the
660 666 git output variance, but that doesn't currently work in the middle of
661 667 a block, so do this for now.
662 668 $ touch a && git add a && git commit -am "commit a" | grep -v changed
663 669 [master (root-commit) 8ae5f69] commit a
664 670 Author: nottest <test@example.org>
665 671 create mode 100644 a
666 672 $ cd ..
667 673 $ git clone git-repo7 git-repo7-client
668 674 Cloning into 'git-repo7-client'...
669 675 done.
670 676 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
671 677 initializing destination hg-repo7 repository
672 678 scanning source...
673 679 sorting...
674 680 converting...
675 681 0 commit a
676 682 updating bookmarks
677 683 $ hg -R hg-repo7 bookmarks
678 684 master 0:03bf38caa4c6
679 685 origin/master 0:03bf38caa4c6
680 686
681 687 Run convert when the remote branches have changed
682 688 (there was an old bug where the local convert read branches from the server)
683 689
684 690 $ cd git-repo7
685 691 $ echo a >> a
686 692 $ git commit -am "move master forward"
687 693 [master 0c81947] move master forward
688 694 Author: nottest <test@example.org>
689 695 1 file changed, 1 insertion(+)
690 696 $ cd ..
691 697 $ rm -rf hg-repo7
692 698 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
693 699 initializing destination hg-repo7 repository
694 700 scanning source...
695 701 sorting...
696 702 converting...
697 703 0 commit a
698 704 updating bookmarks
699 705 $ hg -R hg-repo7 bookmarks
700 706 master 0:03bf38caa4c6
701 707 origin/master 0:03bf38caa4c6
702 708
703 709 damaged git repository tests:
704 710 In case the hard-coded hashes change, the following commands can be used to
705 711 list the hashes and their corresponding types in the repository:
706 712 cd git-repo4/.git/objects
707 713 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
708 714 cd ../../..
709 715
710 716 damage git repository by renaming a commit object
711 717 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
712 718 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
713 719 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
714 720 abort: cannot read tags from git-repo4/.git
715 721 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
716 722 damage git repository by renaming a blob object
717 723
718 724 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
719 725 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
720 726 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
721 727 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
722 728 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
723 729 damage git repository by renaming a tree object
724 730
725 731 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
726 732 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
727 733 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
728 734 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
@@ -1,532 +1,535 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 264 convert.git.remoteprefix
265 265 remote refs are converted as bookmarks with
266 266 "convert.git.remoteprefix" as a prefix followed by a /. The
267 267 default is 'remote'.
268 convert.git.skipsubmodules
269 does not convert root level .gitmodules files or files with
270 160000 mode indicating a submodule. Default is False.
268 271
269 272 Perforce Source
270 273 ###############
271 274
272 275 The Perforce (P4) importer can be given a p4 depot path or a client
273 276 specification as source. It will convert all files in the source to a flat
274 277 Mercurial repository, ignoring labels, branches and integrations. Note
275 278 that when a depot path is given you then usually should specify a target
276 279 directory, because otherwise the target may be named "...-hg".
277 280
278 281 The following options can be set with "--config":
279 282
280 283 convert.p4.encoding
281 284 specify the encoding to use when decoding standard output of
282 285 the Perforce command line tool. The default is default
283 286 system encoding.
284 287 convert.p4.startrev
285 288 specify initial Perforce revision (a Perforce changelist
286 289 number).
287 290
288 291 Mercurial Destination
289 292 #####################
290 293
291 294 The Mercurial destination will recognize Mercurial subrepositories in the
292 295 destination directory, and update the .hgsubstate file automatically if
293 296 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
294 297 Converting a repository with subrepositories requires converting a single
295 298 repository at a time, from the bottom up.
296 299
297 300 The following options are supported:
298 301
299 302 convert.hg.clonebranches
300 303 dispatch source branches in separate clones. The default is
301 304 False.
302 305 convert.hg.tagsbranch
303 306 branch name for tag revisions, defaults to "default".
304 307 convert.hg.usebranchnames
305 308 preserve branch names. The default is True.
306 309 convert.hg.sourcename
307 310 records the given string as a 'convert_source' extra value
308 311 on each commit made in the target repository. The default is
309 312 None.
310 313
311 314 All Destinations
312 315 ################
313 316
314 317 All destination types accept the following options:
315 318
316 319 convert.skiptags
317 320 does not convert tags from the source repo to the target
318 321 repo. The default is False.
319 322
320 323 options ([+] can be repeated):
321 324
322 325 -s --source-type TYPE source repository type
323 326 -d --dest-type TYPE destination repository type
324 327 -r --rev REV [+] import up to source revision REV
325 328 -A --authormap FILE remap usernames using this file
326 329 --filemap FILE remap file names using contents of file
327 330 --full apply filemap changes by converting all files again
328 331 --splicemap FILE splice synthesized history into place
329 332 --branchmap FILE change branch names while converting
330 333 --branchsort try to sort changesets by branches
331 334 --datesort try to sort changesets by date
332 335 --sourcesort preserve source changesets order
333 336 --closesort try to reorder closed revisions
334 337
335 338 (some details hidden, use --verbose to show complete help)
336 339 $ hg init a
337 340 $ cd a
338 341 $ echo a > a
339 342 $ hg ci -d'0 0' -Ama
340 343 adding a
341 344 $ hg cp a b
342 345 $ hg ci -d'1 0' -mb
343 346 $ hg rm a
344 347 $ hg ci -d'2 0' -mc
345 348 $ hg mv b a
346 349 $ hg ci -d'3 0' -md
347 350 $ echo a >> a
348 351 $ hg ci -d'4 0' -me
349 352 $ cd ..
350 353 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
351 354 assuming destination a-hg
352 355 initializing destination a-hg repository
353 356 scanning source...
354 357 sorting...
355 358 converting...
356 359 4 a
357 360 3 b
358 361 2 c
359 362 1 d
360 363 0 e
361 364 $ hg --cwd a-hg pull ../a
362 365 pulling from ../a
363 366 searching for changes
364 367 no changes found
365 368
366 369 conversion to existing file should fail
367 370
368 371 $ touch bogusfile
369 372 $ hg convert a bogusfile
370 373 initializing destination bogusfile repository
371 374 abort: cannot create new bundle repository
372 375 [255]
373 376
374 377 #if unix-permissions no-root
375 378
376 379 conversion to dir without permissions should fail
377 380
378 381 $ mkdir bogusdir
379 382 $ chmod 000 bogusdir
380 383
381 384 $ hg convert a bogusdir
382 385 abort: Permission denied: 'bogusdir'
383 386 [255]
384 387
385 388 user permissions should succeed
386 389
387 390 $ chmod 700 bogusdir
388 391 $ hg convert a bogusdir
389 392 initializing destination bogusdir repository
390 393 scanning source...
391 394 sorting...
392 395 converting...
393 396 4 a
394 397 3 b
395 398 2 c
396 399 1 d
397 400 0 e
398 401
399 402 #endif
400 403
401 404 test pre and post conversion actions
402 405
403 406 $ echo 'include b' > filemap
404 407 $ hg convert --debug --filemap filemap a partialb | \
405 408 > grep 'run hg'
406 409 run hg source pre-conversion action
407 410 run hg sink pre-conversion action
408 411 run hg sink post-conversion action
409 412 run hg source post-conversion action
410 413
411 414 converting empty dir should fail "nicely
412 415
413 416 $ mkdir emptydir
414 417
415 418 override $PATH to ensure p4 not visible; use $PYTHON in case we're
416 419 running from a devel copy, not a temp installation
417 420
418 421 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
419 422 assuming destination emptydir-hg
420 423 initializing destination emptydir-hg repository
421 424 emptydir does not look like a CVS checkout
422 425 emptydir does not look like a Git repository
423 426 emptydir does not look like a Subversion repository
424 427 emptydir is not a local Mercurial repository
425 428 emptydir does not look like a darcs repository
426 429 emptydir does not look like a monotone repository
427 430 emptydir does not look like a GNU Arch repository
428 431 emptydir does not look like a Bazaar repository
429 432 cannot find required "p4" tool
430 433 abort: emptydir: missing or unsupported repository
431 434 [255]
432 435
433 436 convert with imaginary source type
434 437
435 438 $ hg convert --source-type foo a a-foo
436 439 initializing destination a-foo repository
437 440 abort: foo: invalid source repository type
438 441 [255]
439 442
440 443 convert with imaginary sink type
441 444
442 445 $ hg convert --dest-type foo a a-foo
443 446 abort: foo: invalid destination repository type
444 447 [255]
445 448
446 449 testing: convert must not produce duplicate entries in fncache
447 450
448 451 $ hg convert a b
449 452 initializing destination b repository
450 453 scanning source...
451 454 sorting...
452 455 converting...
453 456 4 a
454 457 3 b
455 458 2 c
456 459 1 d
457 460 0 e
458 461
459 462 contents of fncache file:
460 463
461 464 $ cat b/.hg/store/fncache | sort
462 465 data/a.i
463 466 data/b.i
464 467
465 468 test bogus URL
466 469
467 470 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
468 471 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
469 472 [255]
470 473
471 474 test revset converted() lookup
472 475
473 476 $ hg --config convert.hg.saverev=True convert a c
474 477 initializing destination c repository
475 478 scanning source...
476 479 sorting...
477 480 converting...
478 481 4 a
479 482 3 b
480 483 2 c
481 484 1 d
482 485 0 e
483 486 $ echo f > c/f
484 487 $ hg -R c ci -d'0 0' -Amf
485 488 adding f
486 489 created new head
487 490 $ hg -R c log -r "converted(09d945a62ce6)"
488 491 changeset: 1:98c3dd46a874
489 492 user: test
490 493 date: Thu Jan 01 00:00:01 1970 +0000
491 494 summary: b
492 495
493 496 $ hg -R c log -r "converted()"
494 497 changeset: 0:31ed57b2037c
495 498 user: test
496 499 date: Thu Jan 01 00:00:00 1970 +0000
497 500 summary: a
498 501
499 502 changeset: 1:98c3dd46a874
500 503 user: test
501 504 date: Thu Jan 01 00:00:01 1970 +0000
502 505 summary: b
503 506
504 507 changeset: 2:3b9ca06ef716
505 508 user: test
506 509 date: Thu Jan 01 00:00:02 1970 +0000
507 510 summary: c
508 511
509 512 changeset: 3:4e0debd37cf2
510 513 user: test
511 514 date: Thu Jan 01 00:00:03 1970 +0000
512 515 summary: d
513 516
514 517 changeset: 4:9de3bc9349c5
515 518 user: test
516 519 date: Thu Jan 01 00:00:04 1970 +0000
517 520 summary: e
518 521
519 522
520 523 test specifying a sourcename
521 524 $ echo g > a/g
522 525 $ hg -R a ci -d'0 0' -Amg
523 526 adding g
524 527 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
525 528 scanning source...
526 529 sorting...
527 530 converting...
528 531 0 g
529 532 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
530 533 branch=default
531 534 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
532 535 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now