##// END OF EJS Templates
convert: allow customizing git remote prefix...
Durham Goode -
r25787:d9133e89 default
parent child Browse files
Show More
@@ -1,437 +1,441
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.revs: revset specifying the source revisions to convert.
196 196
197 197 CVS Source
198 198 ##########
199 199
200 200 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
201 201 to indicate the starting point of what will be converted. Direct
202 202 access to the repository files is not needed, unless of course the
203 203 repository is ``:local:``. The conversion uses the top level
204 204 directory in the sandbox to find the CVS repository, and then uses
205 205 CVS rlog commands to find files to convert. This means that unless
206 206 a filemap is given, all files under the starting directory will be
207 207 converted, and that any directory reorganization in the CVS
208 208 sandbox is ignored.
209 209
210 210 The following options can be used with ``--config``:
211 211
212 212 :convert.cvsps.cache: Set to False to disable remote log caching,
213 213 for testing and debugging purposes. Default is True.
214 214
215 215 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
216 216 allowed between commits with identical user and log message in
217 217 a single changeset. When very large files were checked in as
218 218 part of a changeset then the default may not be long enough.
219 219 The default is 60.
220 220
221 221 :convert.cvsps.mergeto: Specify a regular expression to which
222 222 commit log messages are matched. If a match occurs, then the
223 223 conversion process will insert a dummy revision merging the
224 224 branch on which this log message occurs to the branch
225 225 indicated in the regex. Default is ``{{mergetobranch
226 226 ([-\\w]+)}}``
227 227
228 228 :convert.cvsps.mergefrom: Specify a regular expression to which
229 229 commit log messages are matched. If a match occurs, then the
230 230 conversion process will add the most recent revision on the
231 231 branch indicated in the regex as the second parent of the
232 232 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
233 233
234 234 :convert.localtimezone: use local time (as determined by the TZ
235 235 environment variable) for changeset date/times. The default
236 236 is False (use UTC).
237 237
238 238 :hooks.cvslog: Specify a Python function to be called at the end of
239 239 gathering the CVS log. The function is passed a list with the
240 240 log entries, and can modify the entries in-place, or add or
241 241 delete them.
242 242
243 243 :hooks.cvschangesets: Specify a Python function to be called after
244 244 the changesets are calculated from the CVS log. The
245 245 function is passed a list with the changeset entries, and can
246 246 modify the changesets in-place, or add or delete them.
247 247
248 248 An additional "debugcvsps" Mercurial command allows the builtin
249 249 changeset merging code to be run without doing a conversion. Its
250 250 parameters and output are similar to that of cvsps 2.1. Please see
251 251 the command help for more details.
252 252
253 253 Subversion Source
254 254 #################
255 255
256 256 Subversion source detects classical trunk/branches/tags layouts.
257 257 By default, the supplied ``svn://repo/path/`` source URL is
258 258 converted as a single branch. If ``svn://repo/path/trunk`` exists
259 259 it replaces the default branch. If ``svn://repo/path/branches``
260 260 exists, its subdirectories are listed as possible branches. If
261 261 ``svn://repo/path/tags`` exists, it is looked for tags referencing
262 262 converted branches. Default ``trunk``, ``branches`` and ``tags``
263 263 values can be overridden with following options. Set them to paths
264 264 relative to the source URL, or leave them blank to disable auto
265 265 detection.
266 266
267 267 The following options can be set with ``--config``:
268 268
269 269 :convert.svn.branches: specify the directory containing branches.
270 270 The default is ``branches``.
271 271
272 272 :convert.svn.tags: specify the directory containing tags. The
273 273 default is ``tags``.
274 274
275 275 :convert.svn.trunk: specify the name of the trunk branch. The
276 276 default is ``trunk``.
277 277
278 278 :convert.localtimezone: use local time (as determined by the TZ
279 279 environment variable) for changeset date/times. The default
280 280 is False (use UTC).
281 281
282 282 Source history can be retrieved starting at a specific revision,
283 283 instead of being integrally converted. Only single branch
284 284 conversions are supported.
285 285
286 286 :convert.svn.startrev: specify start Subversion revision number.
287 287 The default is 0.
288 288
289 289 Git Source
290 290 ##########
291 291
292 292 The Git importer converts commits from all reachable branches (refs
293 293 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
294 294 Branches are converted to bookmarks with the same name, with the
295 295 leading 'refs/heads' stripped. Git submodules are converted to Git
296 296 subrepos in Mercurial.
297 297
298 298 The following options can be set with ``--config``:
299 299
300 300 :convert.git.similarity: specify how similar files modified in a
301 301 commit must be to be imported as renames or copies, as a
302 302 percentage between ``0`` (disabled) and ``100`` (files must be
303 303 identical). For example, ``90`` means that a delete/add pair will
304 304 be imported as a rename if more than 90% of the file hasn't
305 305 changed. The default is ``50``.
306 306
307 307 :convert.git.findcopiesharder: while detecting copies, look at all
308 308 files in the working copy instead of just changed ones. This
309 309 is very expensive for large projects, and is only effective when
310 310 ``convert.git.similarity`` is greater than 0. The default is False.
311 311
312 :convert.git.remoteprefix: remote refs are converted as bookmarks with
313 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
314 is 'remote'.
315
312 316 Perforce Source
313 317 ###############
314 318
315 319 The Perforce (P4) importer can be given a p4 depot path or a
316 320 client specification as source. It will convert all files in the
317 321 source to a flat Mercurial repository, ignoring labels, branches
318 322 and integrations. Note that when a depot path is given you then
319 323 usually should specify a target directory, because otherwise the
320 324 target may be named ``...-hg``.
321 325
322 326 It is possible to limit the amount of source history to be
323 327 converted by specifying an initial Perforce revision:
324 328
325 329 :convert.p4.startrev: specify initial Perforce revision (a
326 330 Perforce changelist number).
327 331
328 332 Mercurial Destination
329 333 #####################
330 334
331 335 The Mercurial destination will recognize Mercurial subrepositories in the
332 336 destination directory, and update the .hgsubstate file automatically if the
333 337 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
334 338 Converting a repository with subrepositories requires converting a single
335 339 repository at a time, from the bottom up.
336 340
337 341 .. container:: verbose
338 342
339 343 An example showing how to convert a repository with subrepositories::
340 344
341 345 # so convert knows the type when it sees a non empty destination
342 346 $ hg init converted
343 347
344 348 $ hg convert orig/sub1 converted/sub1
345 349 $ hg convert orig/sub2 converted/sub2
346 350 $ hg convert orig converted
347 351
348 352 The following options are supported:
349 353
350 354 :convert.hg.clonebranches: dispatch source branches in separate
351 355 clones. The default is False.
352 356
353 357 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
354 358 ``default``.
355 359
356 360 :convert.hg.usebranchnames: preserve branch names. The default is
357 361 True.
358 362
359 363 :convert.hg.sourcename: records the given string as a 'convert_source' extra
360 364 value on each commit made in the target repository. The default is None.
361 365
362 366 All Destinations
363 367 ################
364 368
365 369 All destination types accept the following options:
366 370
367 371 :convert.skiptags: does not convert tags from the source repo to the target
368 372 repo. The default is False.
369 373 """
370 374 return convcmd.convert(ui, src, dest, revmapfile, **opts)
371 375
372 376 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
373 377 def debugsvnlog(ui, **opts):
374 378 return subversion.debugsvnlog(ui, **opts)
375 379
376 380 @command('debugcvsps',
377 381 [
378 382 # Main options shared with cvsps-2.1
379 383 ('b', 'branches', [], _('only return changes on specified branches')),
380 384 ('p', 'prefix', '', _('prefix to remove from file names')),
381 385 ('r', 'revisions', [],
382 386 _('only return changes after or between specified tags')),
383 387 ('u', 'update-cache', None, _("update cvs log cache")),
384 388 ('x', 'new-cache', None, _("create new cvs log cache")),
385 389 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
386 390 ('', 'root', '', _('specify cvsroot')),
387 391 # Options specific to builtin cvsps
388 392 ('', 'parents', '', _('show parent changesets')),
389 393 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
390 394 # Options that are ignored for compatibility with cvsps-2.1
391 395 ('A', 'cvs-direct', None, _('ignored for compatibility')),
392 396 ],
393 397 _('hg debugcvsps [OPTION]... [PATH]...'),
394 398 norepo=True)
395 399 def debugcvsps(ui, *args, **opts):
396 400 '''create changeset information from CVS
397 401
398 402 This command is intended as a debugging tool for the CVS to
399 403 Mercurial converter, and can be used as a direct replacement for
400 404 cvsps.
401 405
402 406 Hg debugcvsps reads the CVS rlog for current directory (or any
403 407 named directory) in the CVS repository, and converts the log to a
404 408 series of changesets based on matching commit log entries and
405 409 dates.'''
406 410 return cvsps.debugcvsps(ui, *args, **opts)
407 411
408 412 def kwconverted(ctx, name):
409 413 rev = ctx.extra().get('convert_revision', '')
410 414 if rev.startswith('svn:'):
411 415 if name == 'svnrev':
412 416 return str(subversion.revsplit(rev)[2])
413 417 elif name == 'svnpath':
414 418 return subversion.revsplit(rev)[1]
415 419 elif name == 'svnuuid':
416 420 return subversion.revsplit(rev)[0]
417 421 return rev
418 422
419 423 def kwsvnrev(repo, ctx, **args):
420 424 """:svnrev: String. Converted subversion revision number."""
421 425 return kwconverted(ctx, 'svnrev')
422 426
423 427 def kwsvnpath(repo, ctx, **args):
424 428 """:svnpath: String. Converted subversion revision project path."""
425 429 return kwconverted(ctx, 'svnpath')
426 430
427 431 def kwsvnuuid(repo, ctx, **args):
428 432 """:svnuuid: String. Converted subversion revision repository identifier."""
429 433 return kwconverted(ctx, 'svnuuid')
430 434
431 435 def extsetup(ui):
432 436 templatekw.keywords['svnrev'] = kwsvnrev
433 437 templatekw.keywords['svnpath'] = kwsvnpath
434 438 templatekw.keywords['svnuuid'] = kwsvnuuid
435 439
436 440 # tell hggettext to extract docstrings from these functions:
437 441 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,400 +1,401
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 227 def add(entry, f, isdest):
228 228 seen.add(f)
229 229 h = entry[3]
230 230 p = (entry[1] == "100755")
231 231 s = (entry[1] == "120000")
232 232 renamesource = (not isdest and entry[4][0] == 'R')
233 233
234 234 if f == '.gitmodules':
235 235 subexists[0] = True
236 236 if entry[4] == 'D' or renamesource:
237 237 subdeleted[0] = True
238 238 changes.append(('.hgsub', hex(nullid)))
239 239 else:
240 240 changes.append(('.hgsub', ''))
241 241 elif entry[1] == '160000' or entry[0] == ':160000':
242 242 subexists[0] = True
243 243 else:
244 244 if renamesource:
245 245 h = hex(nullid)
246 246 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
247 247 changes.append((f, h))
248 248
249 249 while i < lcount:
250 250 l = difftree[i]
251 251 i += 1
252 252 if not entry:
253 253 if not l.startswith(':'):
254 254 continue
255 255 entry = l.split()
256 256 continue
257 257 f = l
258 258 if f not in seen:
259 259 add(entry, f, False)
260 260 # A file can be copied multiple times, or modified and copied
261 261 # simultaneously. So f can be repeated even if fdest isn't.
262 262 if entry[4][0] in 'RC':
263 263 # rename or copy: next line is the destination
264 264 fdest = difftree[i]
265 265 i += 1
266 266 if fdest not in seen:
267 267 add(entry, fdest, True)
268 268 # .gitmodules isn't imported at all, so it being copied to
269 269 # and fro doesn't really make sense
270 270 if f != '.gitmodules' and fdest != '.gitmodules':
271 271 copies[fdest] = f
272 272 entry = None
273 273 if fh.close():
274 274 raise util.Abort(_('cannot read changes in %s') % version)
275 275
276 276 if subexists[0]:
277 277 if subdeleted[0]:
278 278 changes.append(('.hgsubstate', hex(nullid)))
279 279 else:
280 280 self.retrievegitmodules(version)
281 281 changes.append(('.hgsubstate', ''))
282 282 return (changes, copies, set())
283 283
284 284 def getcommit(self, version):
285 285 c = self.catfile(version, "commit") # read the commit hash
286 286 end = c.find("\n\n")
287 287 message = c[end + 2:]
288 288 message = self.recode(message)
289 289 l = c[:end].splitlines()
290 290 parents = []
291 291 author = committer = None
292 292 for e in l[1:]:
293 293 n, v = e.split(" ", 1)
294 294 if n == "author":
295 295 p = v.split()
296 296 tm, tz = p[-2:]
297 297 author = " ".join(p[:-2])
298 298 if author[0] == "<": author = author[1:-1]
299 299 author = self.recode(author)
300 300 if n == "committer":
301 301 p = v.split()
302 302 tm, tz = p[-2:]
303 303 committer = " ".join(p[:-2])
304 304 if committer[0] == "<": committer = committer[1:-1]
305 305 committer = self.recode(committer)
306 306 if n == "parent":
307 307 parents.append(v)
308 308
309 309 if committer and committer != author:
310 310 message += "\ncommitter: %s\n" % committer
311 311 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
312 312 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
313 313 date = tm + " " + str(tz)
314 314
315 315 c = commit(parents=parents, date=date, author=author, desc=message,
316 316 rev=version)
317 317 return c
318 318
319 319 def numcommits(self):
320 320 return len([None for _ in self.gitopen('git rev-list --all')])
321 321
322 322 def gettags(self):
323 323 tags = {}
324 324 alltags = {}
325 325 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
326 326 err=subprocess.STDOUT)
327 327 prefix = 'refs/tags/'
328 328
329 329 # Build complete list of tags, both annotated and bare ones
330 330 for line in fh:
331 331 line = line.strip()
332 332 if line.startswith("error:") or line.startswith("fatal:"):
333 333 raise util.Abort(_('cannot read tags from %s') % self.path)
334 334 node, tag = line.split(None, 1)
335 335 if not tag.startswith(prefix):
336 336 continue
337 337 alltags[tag[len(prefix):]] = node
338 338 if fh.close():
339 339 raise util.Abort(_('cannot read tags from %s') % self.path)
340 340
341 341 # Filter out tag objects for annotated tag refs
342 342 for tag in alltags:
343 343 if tag.endswith('^{}'):
344 344 tags[tag[:-3]] = alltags[tag]
345 345 else:
346 346 if tag + '^{}' in alltags:
347 347 continue
348 348 else:
349 349 tags[tag] = alltags[tag]
350 350
351 351 return tags
352 352
353 353 def getchangedfiles(self, version, i):
354 354 changes = []
355 355 if i is None:
356 356 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
357 357 for l in fh:
358 358 if "\t" not in l:
359 359 continue
360 360 m, f = l[:-1].split("\t")
361 361 changes.append(f)
362 362 else:
363 363 fh = self.gitopen('git diff-tree --name-only --root -r %s '
364 364 '"%s^%s" --' % (version, version, i + 1))
365 365 changes = [f.rstrip('\n') for f in fh]
366 366 if fh.close():
367 367 raise util.Abort(_('cannot read changes in %s') % version)
368 368
369 369 return changes
370 370
371 371 def getbookmarks(self):
372 372 bookmarks = {}
373 373
374 374 # Interesting references in git are prefixed
375 375 prefix = 'refs/heads/'
376 376 prefixlen = len(prefix)
377 377
378 378 # factor two commands
379 gitcmd = { 'remote/': 'git ls-remote --heads origin',
379 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
380 gitcmd = { remoteprefix + '/': 'git ls-remote --heads origin',
380 381 '': 'git show-ref'}
381 382
382 383 # Origin heads
383 384 for reftype in gitcmd:
384 385 try:
385 386 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
386 387 for line in fh:
387 388 line = line.strip()
388 389 rev, name = line.split(None, 1)
389 390 if not name.startswith(prefix):
390 391 continue
391 392 name = '%s%s' % (reftype, name[prefixlen:])
392 393 bookmarks[name] = rev
393 394 except Exception:
394 395 pass
395 396
396 397 return bookmarks
397 398
398 399 def checkrevformat(self, revstr, mapname='splicemap'):
399 400 """ git revision string is a 40 byte hex """
400 401 self.checkhexformat(revstr, mapname)
@@ -1,670 +1,694
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 $ echo baz2 >> baz
325 325 $ git add bar-copied baz-copied baz-copied2
326 326 $ commit -a -m 'rename and copy'
327 327 $ cd ..
328 328
329 329 input validation
330 330 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
331 331 abort: convert.git.similarity is not an integer ('foo')
332 332 [255]
333 333 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
334 334 abort: similarity must be between 0 and 100
335 335 [255]
336 336 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
337 337 abort: similarity must be between 0 and 100
338 338 [255]
339 339
340 340 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
341 341 $ hg -R fullrepo status -C --change master
342 342 M baz
343 343 A bar-copied
344 344 A baz-copied
345 345 baz
346 346 A baz-copied2
347 347 baz
348 348 A foo-renamed
349 349 foo
350 350 R foo
351 351
352 352 $ cd git-repo2
353 353 $ echo bar2 >> bar
354 354 $ commit -a -m 'change bar'
355 355 $ cp bar bar-copied2
356 356 $ git add bar-copied2
357 357 $ commit -a -m 'copy with no changes'
358 358 $ cd ..
359 359
360 360 $ hg -q convert --config convert.git.similarity=100 \
361 361 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
362 362 $ hg -R fullrepo status -C --change master
363 363 A bar-copied2
364 364 bar
365 365
366 366 test binary conversion (issue1359)
367 367
368 368 $ count=19
369 369 $ mkdir git-repo3
370 370 $ cd git-repo3
371 371 $ git init-db >/dev/null 2>/dev/null
372 372 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
373 373 $ git add b
374 374 $ commit -a -m addbinary
375 375 $ cd ..
376 376
377 377 convert binary file
378 378
379 379 $ hg convert git-repo3 git-repo3-hg
380 380 initializing destination git-repo3-hg repository
381 381 scanning source...
382 382 sorting...
383 383 converting...
384 384 0 addbinary
385 385 updating bookmarks
386 386 $ cd git-repo3-hg
387 387 $ hg up -C
388 388 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 389 $ $PYTHON -c 'print len(file("b", "rb").read())'
390 390 4096
391 391 $ cd ..
392 392
393 393 test author vs committer
394 394
395 395 $ mkdir git-repo4
396 396 $ cd git-repo4
397 397 $ git init-db >/dev/null 2>/dev/null
398 398 $ echo >> foo
399 399 $ git add foo
400 400 $ commit -a -m addfoo
401 401 $ echo >> foo
402 402 $ GIT_AUTHOR_NAME="nottest"
403 403 $ commit -a -m addfoo2
404 404 $ cd ..
405 405
406 406 convert author committer
407 407
408 408 $ hg convert git-repo4 git-repo4-hg
409 409 initializing destination git-repo4-hg repository
410 410 scanning source...
411 411 sorting...
412 412 converting...
413 413 1 addfoo
414 414 0 addfoo2
415 415 updating bookmarks
416 416 $ hg -R git-repo4-hg log -v
417 417 changeset: 1:d63e967f93da
418 418 bookmark: master
419 419 tag: tip
420 420 user: nottest <test@example.org>
421 421 date: Mon Jan 01 00:00:21 2007 +0000
422 422 files: foo
423 423 description:
424 424 addfoo2
425 425
426 426 committer: test <test@example.org>
427 427
428 428
429 429 changeset: 0:0735477b0224
430 430 user: test <test@example.org>
431 431 date: Mon Jan 01 00:00:20 2007 +0000
432 432 files: foo
433 433 description:
434 434 addfoo
435 435
436 436
437 437
438 438 --sourceorder should fail
439 439
440 440 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
441 441 initializing destination git-repo4-sourcesort-hg repository
442 442 abort: --sourcesort is not supported by this data source
443 443 [255]
444 444
445 445 test converting certain branches
446 446
447 447 $ mkdir git-testrevs
448 448 $ cd git-testrevs
449 449 $ git init
450 450 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
451 451 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
452 452 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
453 453 $ git checkout -b goodbranch 'HEAD^'
454 454 Switched to a new branch 'goodbranch'
455 455 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
456 456 $ git checkout -b badbranch 'HEAD^'
457 457 Switched to a new branch 'badbranch'
458 458 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
459 459 $ cd ..
460 460 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
461 461 initializing destination hg-testrevs repository
462 462 scanning source...
463 463 sorting...
464 464 converting...
465 465 2 first
466 466 1 good branch commit
467 467 0 master commit
468 468 updating bookmarks
469 469 $ cd hg-testrevs
470 470 $ hg log -G -T '{rev} {bookmarks}'
471 471 o 2 master
472 472 |
473 473 | o 1 goodbranch
474 474 |/
475 475 o 0
476 476
477 477 $ cd ..
478 478
479 479 test sub modules
480 480
481 481 $ mkdir git-repo5
482 482 $ cd git-repo5
483 483 $ git init-db >/dev/null 2>/dev/null
484 484 $ echo 'sub' >> foo
485 485 $ git add foo
486 486 $ commit -a -m 'addfoo'
487 487 $ BASE=`pwd`
488 488 $ cd ..
489 489 $ mkdir git-repo6
490 490 $ cd git-repo6
491 491 $ git init-db >/dev/null 2>/dev/null
492 492 $ git submodule add ${BASE} >/dev/null 2>/dev/null
493 493 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
494 494
495 495 test non-tab whitespace .gitmodules
496 496
497 497 $ cat >> .gitmodules <<EOF
498 498 > [submodule "git-repo5"]
499 499 > path = git-repo5
500 500 > url = git-repo5
501 501 > EOF
502 502 $ git commit -q -a -m "weird white space submodule"
503 503 $ cd ..
504 504 $ hg convert git-repo6 hg-repo6
505 505 initializing destination hg-repo6 repository
506 506 scanning source...
507 507 sorting...
508 508 converting...
509 509 1 addsubmodule
510 510 0 weird white space submodule
511 511 updating bookmarks
512 512
513 513 $ rm -rf hg-repo6
514 514 $ cd git-repo6
515 515 $ git reset --hard 'HEAD^' > /dev/null
516 516
517 517 test missing .gitmodules
518 518
519 519 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
520 520 $ git checkout HEAD .gitmodules
521 521 $ git rm .gitmodules
522 522 rm '.gitmodules'
523 523 $ git commit -q -m "remove .gitmodules" .gitmodules
524 524 $ git commit -q -m "missing .gitmodules"
525 525 $ cd ..
526 526 $ hg convert git-repo6 hg-repo6 --traceback
527 527 fatal: Path '.gitmodules' does not exist in '*' (glob)
528 528 initializing destination hg-repo6 repository
529 529 scanning source...
530 530 sorting...
531 531 converting...
532 532 2 addsubmodule
533 533 1 remove .gitmodules
534 534 0 missing .gitmodules
535 535 warning: cannot read submodules config file in * (glob)
536 536 updating bookmarks
537 537 $ rm -rf hg-repo6
538 538 $ cd git-repo6
539 539 $ rm -rf git-repo4
540 540 $ git reset --hard 'HEAD^^' > /dev/null
541 541 $ cd ..
542 542
543 543 test invalid splicemap1
544 544
545 545 $ cat > splicemap <<EOF
546 546 > $VALIDID1
547 547 > EOF
548 548 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
549 549 initializing destination git-repo2-splicemap1-hg repository
550 550 abort: syntax error in splicemap(1): child parent1[,parent2] expected
551 551 [255]
552 552
553 553 test invalid splicemap2
554 554
555 555 $ cat > splicemap <<EOF
556 556 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
557 557 > EOF
558 558 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
559 559 initializing destination git-repo2-splicemap2-hg repository
560 560 abort: syntax error in splicemap(1): child parent1[,parent2] expected
561 561 [255]
562 562
563 563 test invalid splicemap3
564 564
565 565 $ cat > splicemap <<EOF
566 566 > $INVALIDID1 $INVALIDID2
567 567 > EOF
568 568 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
569 569 initializing destination git-repo2-splicemap3-hg repository
570 570 abort: splicemap entry afd12345af is not a valid revision identifier
571 571 [255]
572 572
573 573 convert sub modules
574 574 $ hg convert git-repo6 git-repo6-hg
575 575 initializing destination git-repo6-hg repository
576 576 scanning source...
577 577 sorting...
578 578 converting...
579 579 0 addsubmodule
580 580 updating bookmarks
581 581 $ hg -R git-repo6-hg log -v
582 582 changeset: 0:* (glob)
583 583 bookmark: master
584 584 tag: tip
585 585 user: nottest <test@example.org>
586 586 date: Mon Jan 01 00:00:23 2007 +0000
587 587 files: .hgsub .hgsubstate
588 588 description:
589 589 addsubmodule
590 590
591 591 committer: test <test@example.org>
592 592
593 593
594 594
595 595 $ cd git-repo6-hg
596 596 $ hg up >/dev/null 2>/dev/null
597 597 $ cat .hgsubstate
598 598 * git-repo5 (glob)
599 599 $ cd git-repo5
600 600 $ cat foo
601 601 sub
602 602
603 603 $ cd ../..
604 604
605 605 make sure rename detection doesn't break removing and adding gitmodules
606 606
607 607 $ cd git-repo6
608 608 $ git mv .gitmodules .gitmodules-renamed
609 609 $ commit -a -m 'rename .gitmodules'
610 610 $ git mv .gitmodules-renamed .gitmodules
611 611 $ commit -a -m 'rename .gitmodules back'
612 612 $ cd ..
613 613
614 614 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
615 615 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
616 616 rename .gitmodules
617 617 $ hg -R git-repo6-hg status -C --change 'tip^'
618 618 A .gitmodules-renamed
619 619 R .hgsub
620 620 R .hgsubstate
621 621 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
622 622 rename .gitmodules back
623 623 $ hg -R git-repo6-hg status -C --change tip
624 624 A .hgsub
625 625 A .hgsubstate
626 626 R .gitmodules-renamed
627 627
628 628 convert the revision removing '.gitmodules' itself (and related
629 629 submodules)
630 630
631 631 $ cd git-repo6
632 632 $ git rm .gitmodules
633 633 rm '.gitmodules'
634 634 $ git rm --cached git-repo5
635 635 rm 'git-repo5'
636 636 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
637 637 $ cd ..
638 638
639 639 $ hg convert -q git-repo6 git-repo6-hg
640 640 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
641 641 remove .gitmodules and submodule git-repo5
642 642 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
643 643 .hgsub .hgsubstate
644 644
645 convert using a different remote prefix
646 $ git init git-repo7
647 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
648 $ cd git-repo7
649 $ touch a && git add a && git commit -am "commit a"
650 [master (root-commit) 8ae5f69] commit a
651 Author: nottest <test@example.org>
652 1 file changed, 0 insertions(+), 0 deletions(-)
653 create mode 100644 a
654 $ cd ..
655 $ git clone git-repo7 git-repo7-client
656 Cloning into 'git-repo7-client'...
657 done.
658 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
659 initializing destination hg-repo7 repository
660 scanning source...
661 sorting...
662 converting...
663 0 commit a
664 updating bookmarks
665 $ hg -R hg-repo7 bookmarks
666 master 0:03bf38caa4c6
667 origin/master 0:03bf38caa4c6
668
645 669 damaged git repository tests:
646 670 In case the hard-coded hashes change, the following commands can be used to
647 671 list the hashes and their corresponding types in the repository:
648 672 cd git-repo4/.git/objects
649 673 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
650 674 cd ../../..
651 675
652 676 damage git repository by renaming a commit object
653 677 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
654 678 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
655 679 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
656 680 abort: cannot read tags from git-repo4/.git
657 681 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
658 682 damage git repository by renaming a blob object
659 683
660 684 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
661 685 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
662 686 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
663 687 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
664 688 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
665 689 damage git repository by renaming a tree object
666 690
667 691 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
668 692 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
669 693 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
670 694 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
@@ -1,523 +1,527
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.revs
147 147 revset specifying the source revisions to convert.
148 148
149 149 CVS Source
150 150 ##########
151 151
152 152 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
153 153 indicate the starting point of what will be converted. Direct access to
154 154 the repository files is not needed, unless of course the repository is
155 155 ":local:". The conversion uses the top level directory in the sandbox to
156 156 find the CVS repository, and then uses CVS rlog commands to find files to
157 157 convert. This means that unless a filemap is given, all files under the
158 158 starting directory will be converted, and that any directory
159 159 reorganization in the CVS sandbox is ignored.
160 160
161 161 The following options can be used with "--config":
162 162
163 163 convert.cvsps.cache
164 164 Set to False to disable remote log caching, for testing and
165 165 debugging purposes. Default is True.
166 166 convert.cvsps.fuzz
167 167 Specify the maximum time (in seconds) that is allowed
168 168 between commits with identical user and log message in a
169 169 single changeset. When very large files were checked in as
170 170 part of a changeset then the default may not be long enough.
171 171 The default is 60.
172 172 convert.cvsps.mergeto
173 173 Specify a regular expression to which commit log messages
174 174 are matched. If a match occurs, then the conversion process
175 175 will insert a dummy revision merging the branch on which
176 176 this log message occurs to the branch indicated in the
177 177 regex. Default is "{{mergetobranch ([-\w]+)}}"
178 178 convert.cvsps.mergefrom
179 179 Specify a regular expression to which commit log messages
180 180 are matched. If a match occurs, then the conversion process
181 181 will add the most recent revision on the branch indicated in
182 182 the regex as the second parent of the changeset. Default is
183 183 "{{mergefrombranch ([-\w]+)}}"
184 184 convert.localtimezone
185 185 use local time (as determined by the TZ environment
186 186 variable) for changeset date/times. The default is False
187 187 (use UTC).
188 188 hooks.cvslog Specify a Python function to be called at the end of
189 189 gathering the CVS log. The function is passed a list with
190 190 the log entries, and can modify the entries in-place, or add
191 191 or delete them.
192 192 hooks.cvschangesets
193 193 Specify a Python function to be called after the changesets
194 194 are calculated from the CVS log. The function is passed a
195 195 list with the changeset entries, and can modify the
196 196 changesets in-place, or add or delete them.
197 197
198 198 An additional "debugcvsps" Mercurial command allows the builtin changeset
199 199 merging code to be run without doing a conversion. Its parameters and
200 200 output are similar to that of cvsps 2.1. Please see the command help for
201 201 more details.
202 202
203 203 Subversion Source
204 204 #################
205 205
206 206 Subversion source detects classical trunk/branches/tags layouts. By
207 207 default, the supplied "svn://repo/path/" source URL is converted as a
208 208 single branch. If "svn://repo/path/trunk" exists it replaces the default
209 209 branch. If "svn://repo/path/branches" exists, its subdirectories are
210 210 listed as possible branches. If "svn://repo/path/tags" exists, it is
211 211 looked for tags referencing converted branches. Default "trunk",
212 212 "branches" and "tags" values can be overridden with following options. Set
213 213 them to paths relative to the source URL, or leave them blank to disable
214 214 auto detection.
215 215
216 216 The following options can be set with "--config":
217 217
218 218 convert.svn.branches
219 219 specify the directory containing branches. The default is
220 220 "branches".
221 221 convert.svn.tags
222 222 specify the directory containing tags. The default is
223 223 "tags".
224 224 convert.svn.trunk
225 225 specify the name of the trunk branch. The default is
226 226 "trunk".
227 227 convert.localtimezone
228 228 use local time (as determined by the TZ environment
229 229 variable) for changeset date/times. The default is False
230 230 (use UTC).
231 231
232 232 Source history can be retrieved starting at a specific revision, instead
233 233 of being integrally converted. Only single branch conversions are
234 234 supported.
235 235
236 236 convert.svn.startrev
237 237 specify start Subversion revision number. The default is 0.
238 238
239 239 Git Source
240 240 ##########
241 241
242 242 The Git importer converts commits from all reachable branches (refs in
243 243 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
244 244 converted to bookmarks with the same name, with the leading 'refs/heads'
245 245 stripped. Git submodules are converted to Git subrepos in Mercurial.
246 246
247 247 The following options can be set with "--config":
248 248
249 249 convert.git.similarity
250 250 specify how similar files modified in a commit must be to be
251 251 imported as renames or copies, as a percentage between "0"
252 252 (disabled) and "100" (files must be identical). For example,
253 253 "90" means that a delete/add pair will be imported as a
254 254 rename if more than 90% of the file hasn't changed. The
255 255 default is "50".
256 256 convert.git.findcopiesharder
257 257 while detecting copies, look at all files in the working
258 258 copy instead of just changed ones. This is very expensive
259 259 for large projects, and is only effective when
260 260 "convert.git.similarity" is greater than 0. The default is
261 261 False.
262 convert.git.remoteprefix
263 remote refs are converted as bookmarks with
264 "convert.git.remoteprefix" as a prefix followed by a /. The
265 default is 'remote'.
262 266
263 267 Perforce Source
264 268 ###############
265 269
266 270 The Perforce (P4) importer can be given a p4 depot path or a client
267 271 specification as source. It will convert all files in the source to a flat
268 272 Mercurial repository, ignoring labels, branches and integrations. Note
269 273 that when a depot path is given you then usually should specify a target
270 274 directory, because otherwise the target may be named "...-hg".
271 275
272 276 It is possible to limit the amount of source history to be converted by
273 277 specifying an initial Perforce revision:
274 278
275 279 convert.p4.startrev
276 280 specify initial Perforce revision (a Perforce changelist
277 281 number).
278 282
279 283 Mercurial Destination
280 284 #####################
281 285
282 286 The Mercurial destination will recognize Mercurial subrepositories in the
283 287 destination directory, and update the .hgsubstate file automatically if
284 288 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
285 289 Converting a repository with subrepositories requires converting a single
286 290 repository at a time, from the bottom up.
287 291
288 292 The following options are supported:
289 293
290 294 convert.hg.clonebranches
291 295 dispatch source branches in separate clones. The default is
292 296 False.
293 297 convert.hg.tagsbranch
294 298 branch name for tag revisions, defaults to "default".
295 299 convert.hg.usebranchnames
296 300 preserve branch names. The default is True.
297 301 convert.hg.sourcename
298 302 records the given string as a 'convert_source' extra value
299 303 on each commit made in the target repository. The default is
300 304 None.
301 305
302 306 All Destinations
303 307 ################
304 308
305 309 All destination types accept the following options:
306 310
307 311 convert.skiptags
308 312 does not convert tags from the source repo to the target
309 313 repo. The default is False.
310 314
311 315 options ([+] can be repeated):
312 316
313 317 -s --source-type TYPE source repository type
314 318 -d --dest-type TYPE destination repository type
315 319 -r --rev REV [+] import up to source revision REV
316 320 -A --authormap FILE remap usernames using this file
317 321 --filemap FILE remap file names using contents of file
318 322 --full apply filemap changes by converting all files again
319 323 --splicemap FILE splice synthesized history into place
320 324 --branchmap FILE change branch names while converting
321 325 --branchsort try to sort changesets by branches
322 326 --datesort try to sort changesets by date
323 327 --sourcesort preserve source changesets order
324 328 --closesort try to reorder closed revisions
325 329
326 330 (some details hidden, use --verbose to show complete help)
327 331 $ hg init a
328 332 $ cd a
329 333 $ echo a > a
330 334 $ hg ci -d'0 0' -Ama
331 335 adding a
332 336 $ hg cp a b
333 337 $ hg ci -d'1 0' -mb
334 338 $ hg rm a
335 339 $ hg ci -d'2 0' -mc
336 340 $ hg mv b a
337 341 $ hg ci -d'3 0' -md
338 342 $ echo a >> a
339 343 $ hg ci -d'4 0' -me
340 344 $ cd ..
341 345 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
342 346 assuming destination a-hg
343 347 initializing destination a-hg repository
344 348 scanning source...
345 349 sorting...
346 350 converting...
347 351 4 a
348 352 3 b
349 353 2 c
350 354 1 d
351 355 0 e
352 356 $ hg --cwd a-hg pull ../a
353 357 pulling from ../a
354 358 searching for changes
355 359 no changes found
356 360
357 361 conversion to existing file should fail
358 362
359 363 $ touch bogusfile
360 364 $ hg convert a bogusfile
361 365 initializing destination bogusfile repository
362 366 abort: cannot create new bundle repository
363 367 [255]
364 368
365 369 #if unix-permissions no-root
366 370
367 371 conversion to dir without permissions should fail
368 372
369 373 $ mkdir bogusdir
370 374 $ chmod 000 bogusdir
371 375
372 376 $ hg convert a bogusdir
373 377 abort: Permission denied: 'bogusdir'
374 378 [255]
375 379
376 380 user permissions should succeed
377 381
378 382 $ chmod 700 bogusdir
379 383 $ hg convert a bogusdir
380 384 initializing destination bogusdir repository
381 385 scanning source...
382 386 sorting...
383 387 converting...
384 388 4 a
385 389 3 b
386 390 2 c
387 391 1 d
388 392 0 e
389 393
390 394 #endif
391 395
392 396 test pre and post conversion actions
393 397
394 398 $ echo 'include b' > filemap
395 399 $ hg convert --debug --filemap filemap a partialb | \
396 400 > grep 'run hg'
397 401 run hg source pre-conversion action
398 402 run hg sink pre-conversion action
399 403 run hg sink post-conversion action
400 404 run hg source post-conversion action
401 405
402 406 converting empty dir should fail "nicely
403 407
404 408 $ mkdir emptydir
405 409
406 410 override $PATH to ensure p4 not visible; use $PYTHON in case we're
407 411 running from a devel copy, not a temp installation
408 412
409 413 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
410 414 assuming destination emptydir-hg
411 415 initializing destination emptydir-hg repository
412 416 emptydir does not look like a CVS checkout
413 417 emptydir does not look like a Git repository
414 418 emptydir does not look like a Subversion repository
415 419 emptydir is not a local Mercurial repository
416 420 emptydir does not look like a darcs repository
417 421 emptydir does not look like a monotone repository
418 422 emptydir does not look like a GNU Arch repository
419 423 emptydir does not look like a Bazaar repository
420 424 cannot find required "p4" tool
421 425 abort: emptydir: missing or unsupported repository
422 426 [255]
423 427
424 428 convert with imaginary source type
425 429
426 430 $ hg convert --source-type foo a a-foo
427 431 initializing destination a-foo repository
428 432 abort: foo: invalid source repository type
429 433 [255]
430 434
431 435 convert with imaginary sink type
432 436
433 437 $ hg convert --dest-type foo a a-foo
434 438 abort: foo: invalid destination repository type
435 439 [255]
436 440
437 441 testing: convert must not produce duplicate entries in fncache
438 442
439 443 $ hg convert a b
440 444 initializing destination b repository
441 445 scanning source...
442 446 sorting...
443 447 converting...
444 448 4 a
445 449 3 b
446 450 2 c
447 451 1 d
448 452 0 e
449 453
450 454 contents of fncache file:
451 455
452 456 $ cat b/.hg/store/fncache | sort
453 457 data/a.i
454 458 data/b.i
455 459
456 460 test bogus URL
457 461
458 462 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
459 463 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
460 464 [255]
461 465
462 466 test revset converted() lookup
463 467
464 468 $ hg --config convert.hg.saverev=True convert a c
465 469 initializing destination c repository
466 470 scanning source...
467 471 sorting...
468 472 converting...
469 473 4 a
470 474 3 b
471 475 2 c
472 476 1 d
473 477 0 e
474 478 $ echo f > c/f
475 479 $ hg -R c ci -d'0 0' -Amf
476 480 adding f
477 481 created new head
478 482 $ hg -R c log -r "converted(09d945a62ce6)"
479 483 changeset: 1:98c3dd46a874
480 484 user: test
481 485 date: Thu Jan 01 00:00:01 1970 +0000
482 486 summary: b
483 487
484 488 $ hg -R c log -r "converted()"
485 489 changeset: 0:31ed57b2037c
486 490 user: test
487 491 date: Thu Jan 01 00:00:00 1970 +0000
488 492 summary: a
489 493
490 494 changeset: 1:98c3dd46a874
491 495 user: test
492 496 date: Thu Jan 01 00:00:01 1970 +0000
493 497 summary: b
494 498
495 499 changeset: 2:3b9ca06ef716
496 500 user: test
497 501 date: Thu Jan 01 00:00:02 1970 +0000
498 502 summary: c
499 503
500 504 changeset: 3:4e0debd37cf2
501 505 user: test
502 506 date: Thu Jan 01 00:00:03 1970 +0000
503 507 summary: d
504 508
505 509 changeset: 4:9de3bc9349c5
506 510 user: test
507 511 date: Thu Jan 01 00:00:04 1970 +0000
508 512 summary: e
509 513
510 514
511 515 test specifying a sourcename
512 516 $ echo g > a/g
513 517 $ hg -R a ci -d'0 0' -Amg
514 518 adding g
515 519 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
516 520 scanning source...
517 521 sorting...
518 522 converting...
519 523 0 g
520 524 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
521 525 branch=default
522 526 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
523 527 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now