##// END OF EJS Templates
convert: add config for recording the source name...
Durham Goode -
r25750:c9093d4d default
parent child Browse files
Show More
@@ -1,434 +1,437 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.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 312 Perforce Source
313 313 ###############
314 314
315 315 The Perforce (P4) importer can be given a p4 depot path or a
316 316 client specification as source. It will convert all files in the
317 317 source to a flat Mercurial repository, ignoring labels, branches
318 318 and integrations. Note that when a depot path is given you then
319 319 usually should specify a target directory, because otherwise the
320 320 target may be named ``...-hg``.
321 321
322 322 It is possible to limit the amount of source history to be
323 323 converted by specifying an initial Perforce revision:
324 324
325 325 :convert.p4.startrev: specify initial Perforce revision (a
326 326 Perforce changelist number).
327 327
328 328 Mercurial Destination
329 329 #####################
330 330
331 331 The Mercurial destination will recognize Mercurial subrepositories in the
332 332 destination directory, and update the .hgsubstate file automatically if the
333 333 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
334 334 Converting a repository with subrepositories requires converting a single
335 335 repository at a time, from the bottom up.
336 336
337 337 .. container:: verbose
338 338
339 339 An example showing how to convert a repository with subrepositories::
340 340
341 341 # so convert knows the type when it sees a non empty destination
342 342 $ hg init converted
343 343
344 344 $ hg convert orig/sub1 converted/sub1
345 345 $ hg convert orig/sub2 converted/sub2
346 346 $ hg convert orig converted
347 347
348 348 The following options are supported:
349 349
350 350 :convert.hg.clonebranches: dispatch source branches in separate
351 351 clones. The default is False.
352 352
353 353 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
354 354 ``default``.
355 355
356 356 :convert.hg.usebranchnames: preserve branch names. The default is
357 357 True.
358 358
359 :convert.hg.sourcename: records the given string as a 'convert_source' extra
360 value on each commit made in the target repository. The default is None.
361
359 362 All Destinations
360 363 ################
361 364
362 365 All destination types accept the following options:
363 366
364 367 :convert.skiptags: does not convert tags from the source repo to the target
365 368 repo. The default is False.
366 369 """
367 370 return convcmd.convert(ui, src, dest, revmapfile, **opts)
368 371
369 372 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
370 373 def debugsvnlog(ui, **opts):
371 374 return subversion.debugsvnlog(ui, **opts)
372 375
373 376 @command('debugcvsps',
374 377 [
375 378 # Main options shared with cvsps-2.1
376 379 ('b', 'branches', [], _('only return changes on specified branches')),
377 380 ('p', 'prefix', '', _('prefix to remove from file names')),
378 381 ('r', 'revisions', [],
379 382 _('only return changes after or between specified tags')),
380 383 ('u', 'update-cache', None, _("update cvs log cache")),
381 384 ('x', 'new-cache', None, _("create new cvs log cache")),
382 385 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
383 386 ('', 'root', '', _('specify cvsroot')),
384 387 # Options specific to builtin cvsps
385 388 ('', 'parents', '', _('show parent changesets')),
386 389 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
387 390 # Options that are ignored for compatibility with cvsps-2.1
388 391 ('A', 'cvs-direct', None, _('ignored for compatibility')),
389 392 ],
390 393 _('hg debugcvsps [OPTION]... [PATH]...'),
391 394 norepo=True)
392 395 def debugcvsps(ui, *args, **opts):
393 396 '''create changeset information from CVS
394 397
395 398 This command is intended as a debugging tool for the CVS to
396 399 Mercurial converter, and can be used as a direct replacement for
397 400 cvsps.
398 401
399 402 Hg debugcvsps reads the CVS rlog for current directory (or any
400 403 named directory) in the CVS repository, and converts the log to a
401 404 series of changesets based on matching commit log entries and
402 405 dates.'''
403 406 return cvsps.debugcvsps(ui, *args, **opts)
404 407
405 408 def kwconverted(ctx, name):
406 409 rev = ctx.extra().get('convert_revision', '')
407 410 if rev.startswith('svn:'):
408 411 if name == 'svnrev':
409 412 return str(subversion.revsplit(rev)[2])
410 413 elif name == 'svnpath':
411 414 return subversion.revsplit(rev)[1]
412 415 elif name == 'svnuuid':
413 416 return subversion.revsplit(rev)[0]
414 417 return rev
415 418
416 419 def kwsvnrev(repo, ctx, **args):
417 420 """:svnrev: String. Converted subversion revision number."""
418 421 return kwconverted(ctx, 'svnrev')
419 422
420 423 def kwsvnpath(repo, ctx, **args):
421 424 """:svnpath: String. Converted subversion revision project path."""
422 425 return kwconverted(ctx, 'svnpath')
423 426
424 427 def kwsvnuuid(repo, ctx, **args):
425 428 """:svnuuid: String. Converted subversion revision repository identifier."""
426 429 return kwconverted(ctx, 'svnuuid')
427 430
428 431 def extsetup(ui):
429 432 templatekw.keywords['svnrev'] = kwsvnrev
430 433 templatekw.keywords['svnpath'] = kwsvnpath
431 434 templatekw.keywords['svnuuid'] = kwsvnuuid
432 435
433 436 # tell hggettext to extract docstrings from these functions:
434 437 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,564 +1,568 b''
1 1 # hg.py - hg backend for 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 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19
20 20
21 21 import os, time, cStringIO
22 22 from mercurial.i18n import _
23 23 from mercurial.node import bin, hex, nullid
24 24 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
25 25 from mercurial import phases
26 26
27 27 from common import NoRepo, commit, converter_source, converter_sink, mapfile
28 28
29 29 import re
30 30 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
31 31
32 32 class mercurial_sink(converter_sink):
33 33 def __init__(self, ui, path):
34 34 converter_sink.__init__(self, ui, path)
35 35 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
36 36 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
37 37 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
38 38 self.lastbranch = None
39 39 if os.path.isdir(path) and len(os.listdir(path)) > 0:
40 40 try:
41 41 self.repo = hg.repository(self.ui, path)
42 42 if not self.repo.local():
43 43 raise NoRepo(_('%s is not a local Mercurial repository')
44 44 % path)
45 45 except error.RepoError as err:
46 46 ui.traceback()
47 47 raise NoRepo(err.args[0])
48 48 else:
49 49 try:
50 50 ui.status(_('initializing destination %s repository\n') % path)
51 51 self.repo = hg.repository(self.ui, path, create=True)
52 52 if not self.repo.local():
53 53 raise NoRepo(_('%s is not a local Mercurial repository')
54 54 % path)
55 55 self.created.append(path)
56 56 except error.RepoError:
57 57 ui.traceback()
58 58 raise NoRepo(_("could not create hg repository %s as sink")
59 59 % path)
60 60 self.lock = None
61 61 self.wlock = None
62 62 self.filemapmode = False
63 63 self.subrevmaps = {}
64 64
65 65 def before(self):
66 66 self.ui.debug('run hg sink pre-conversion action\n')
67 67 self.wlock = self.repo.wlock()
68 68 self.lock = self.repo.lock()
69 69
70 70 def after(self):
71 71 self.ui.debug('run hg sink post-conversion action\n')
72 72 if self.lock:
73 73 self.lock.release()
74 74 if self.wlock:
75 75 self.wlock.release()
76 76
77 77 def revmapfile(self):
78 78 return self.repo.join("shamap")
79 79
80 80 def authorfile(self):
81 81 return self.repo.join("authormap")
82 82
83 83 def setbranch(self, branch, pbranches):
84 84 if not self.clonebranches:
85 85 return
86 86
87 87 setbranch = (branch != self.lastbranch)
88 88 self.lastbranch = branch
89 89 if not branch:
90 90 branch = 'default'
91 91 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
92 92 if pbranches:
93 93 pbranch = pbranches[0][1]
94 94 else:
95 95 pbranch = 'default'
96 96
97 97 branchpath = os.path.join(self.path, branch)
98 98 if setbranch:
99 99 self.after()
100 100 try:
101 101 self.repo = hg.repository(self.ui, branchpath)
102 102 except Exception:
103 103 self.repo = hg.repository(self.ui, branchpath, create=True)
104 104 self.before()
105 105
106 106 # pbranches may bring revisions from other branches (merge parents)
107 107 # Make sure we have them, or pull them.
108 108 missings = {}
109 109 for b in pbranches:
110 110 try:
111 111 self.repo.lookup(b[0])
112 112 except Exception:
113 113 missings.setdefault(b[1], []).append(b[0])
114 114
115 115 if missings:
116 116 self.after()
117 117 for pbranch, heads in sorted(missings.iteritems()):
118 118 pbranchpath = os.path.join(self.path, pbranch)
119 119 prepo = hg.peer(self.ui, {}, pbranchpath)
120 120 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
121 121 exchange.pull(self.repo, prepo,
122 122 [prepo.lookup(h) for h in heads])
123 123 self.before()
124 124
125 125 def _rewritetags(self, source, revmap, data):
126 126 fp = cStringIO.StringIO()
127 127 for line in data.splitlines():
128 128 s = line.split(' ', 1)
129 129 if len(s) != 2:
130 130 continue
131 131 revid = revmap.get(source.lookuprev(s[0]))
132 132 if not revid:
133 133 if s[0] == hex(nullid):
134 134 revid = s[0]
135 135 else:
136 136 continue
137 137 fp.write('%s %s\n' % (revid, s[1]))
138 138 return fp.getvalue()
139 139
140 140 def _rewritesubstate(self, source, data):
141 141 fp = cStringIO.StringIO()
142 142 for line in data.splitlines():
143 143 s = line.split(' ', 1)
144 144 if len(s) != 2:
145 145 continue
146 146
147 147 revid = s[0]
148 148 subpath = s[1]
149 149 if revid != hex(nullid):
150 150 revmap = self.subrevmaps.get(subpath)
151 151 if revmap is None:
152 152 revmap = mapfile(self.ui,
153 153 self.repo.wjoin(subpath, '.hg/shamap'))
154 154 self.subrevmaps[subpath] = revmap
155 155
156 156 # It is reasonable that one or more of the subrepos don't
157 157 # need to be converted, in which case they can be cloned
158 158 # into place instead of converted. Therefore, only warn
159 159 # once.
160 160 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
161 161 if len(revmap) == 0:
162 162 sub = self.repo.wvfs.reljoin(subpath, '.hg')
163 163
164 164 if self.repo.wvfs.exists(sub):
165 165 self.ui.warn(msg % subpath)
166 166
167 167 newid = revmap.get(revid)
168 168 if not newid:
169 169 if len(revmap) > 0:
170 170 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
171 171 (revid, subpath))
172 172 else:
173 173 revid = newid
174 174
175 175 fp.write('%s %s\n' % (revid, subpath))
176 176
177 177 return fp.getvalue()
178 178
179 179 def putcommit(self, files, copies, parents, commit, source, revmap, full,
180 180 cleanp2):
181 181 files = dict(files)
182 182
183 183 def getfilectx(repo, memctx, f):
184 184 if p2ctx and f in cleanp2 and f not in copies:
185 185 self.ui.debug('reusing %s from p2\n' % f)
186 186 return p2ctx[f]
187 187 try:
188 188 v = files[f]
189 189 except KeyError:
190 190 return None
191 191 data, mode = source.getfile(f, v)
192 192 if data is None:
193 193 return None
194 194 if f == '.hgtags':
195 195 data = self._rewritetags(source, revmap, data)
196 196 if f == '.hgsubstate':
197 197 data = self._rewritesubstate(source, data)
198 198 return context.memfilectx(self.repo, f, data, 'l' in mode,
199 199 'x' in mode, copies.get(f))
200 200
201 201 pl = []
202 202 for p in parents:
203 203 if p not in pl:
204 204 pl.append(p)
205 205 parents = pl
206 206 nparents = len(parents)
207 207 if self.filemapmode and nparents == 1:
208 208 m1node = self.repo.changelog.read(bin(parents[0]))[0]
209 209 parent = parents[0]
210 210
211 211 if len(parents) < 2:
212 212 parents.append(nullid)
213 213 if len(parents) < 2:
214 214 parents.append(nullid)
215 215 p2 = parents.pop(0)
216 216
217 217 text = commit.desc
218 218
219 219 sha1s = re.findall(sha1re, text)
220 220 for sha1 in sha1s:
221 221 oldrev = source.lookuprev(sha1)
222 222 newrev = revmap.get(oldrev)
223 223 if newrev is not None:
224 224 text = text.replace(sha1, newrev[:len(sha1)])
225 225
226 226 extra = commit.extra.copy()
227 227
228 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
229 if sourcename:
230 extra['convert_source'] = sourcename
231
228 232 for label in ('source', 'transplant_source', 'rebase_source',
229 233 'intermediate-source'):
230 234 node = extra.get(label)
231 235
232 236 if node is None:
233 237 continue
234 238
235 239 # Only transplant stores its reference in binary
236 240 if label == 'transplant_source':
237 241 node = hex(node)
238 242
239 243 newrev = revmap.get(node)
240 244 if newrev is not None:
241 245 if label == 'transplant_source':
242 246 newrev = bin(newrev)
243 247
244 248 extra[label] = newrev
245 249
246 250 if self.branchnames and commit.branch:
247 251 extra['branch'] = commit.branch
248 252 if commit.rev and commit.saverev:
249 253 extra['convert_revision'] = commit.rev
250 254
251 255 while parents:
252 256 p1 = p2
253 257 p2 = parents.pop(0)
254 258 p2ctx = None
255 259 if p2 != nullid:
256 260 p2ctx = self.repo[p2]
257 261 fileset = set(files)
258 262 if full:
259 263 fileset.update(self.repo[p1])
260 264 fileset.update(self.repo[p2])
261 265 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
262 266 getfilectx, commit.author, commit.date, extra)
263 267
264 268 # We won't know if the conversion changes the node until after the
265 269 # commit, so copy the source's phase for now.
266 270 self.repo.ui.setconfig('phases', 'new-commit',
267 271 phases.phasenames[commit.phase], 'convert')
268 272
269 273 tr = self.repo.transaction("convert")
270 274
271 275 try:
272 276 node = hex(self.repo.commitctx(ctx))
273 277
274 278 # If the node value has changed, but the phase is lower than
275 279 # draft, set it back to draft since it hasn't been exposed
276 280 # anywhere.
277 281 if commit.rev != node:
278 282 ctx = self.repo[node]
279 283 if ctx.phase() < phases.draft:
280 284 phases.retractboundary(self.repo, tr, phases.draft,
281 285 [ctx.node()])
282 286 tr.close()
283 287 finally:
284 288 tr.release()
285 289
286 290 text = "(octopus merge fixup)\n"
287 291 p2 = node
288 292
289 293 if self.filemapmode and nparents == 1:
290 294 man = self.repo.manifest
291 295 mnode = self.repo.changelog.read(bin(p2))[0]
292 296 closed = 'close' in commit.extra
293 297 if not closed and not man.cmp(m1node, man.revision(mnode)):
294 298 self.ui.status(_("filtering out empty revision\n"))
295 299 self.repo.rollback(force=True)
296 300 return parent
297 301 return p2
298 302
299 303 def puttags(self, tags):
300 304 try:
301 305 parentctx = self.repo[self.tagsbranch]
302 306 tagparent = parentctx.node()
303 307 except error.RepoError:
304 308 parentctx = None
305 309 tagparent = nullid
306 310
307 311 oldlines = set()
308 312 for branch, heads in self.repo.branchmap().iteritems():
309 313 for h in heads:
310 314 if '.hgtags' in self.repo[h]:
311 315 oldlines.update(
312 316 set(self.repo[h]['.hgtags'].data().splitlines(True)))
313 317 oldlines = sorted(list(oldlines))
314 318
315 319 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
316 320 if newlines == oldlines:
317 321 return None, None
318 322
319 323 # if the old and new tags match, then there is nothing to update
320 324 oldtags = set()
321 325 newtags = set()
322 326 for line in oldlines:
323 327 s = line.strip().split(' ', 1)
324 328 if len(s) != 2:
325 329 continue
326 330 oldtags.add(s[1])
327 331 for line in newlines:
328 332 s = line.strip().split(' ', 1)
329 333 if len(s) != 2:
330 334 continue
331 335 if s[1] not in oldtags:
332 336 newtags.add(s[1].strip())
333 337
334 338 if not newtags:
335 339 return None, None
336 340
337 341 data = "".join(newlines)
338 342 def getfilectx(repo, memctx, f):
339 343 return context.memfilectx(repo, f, data, False, False, None)
340 344
341 345 self.ui.status(_("updating tags\n"))
342 346 date = "%s 0" % int(time.mktime(time.gmtime()))
343 347 extra = {'branch': self.tagsbranch}
344 348 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
345 349 [".hgtags"], getfilectx, "convert-repo", date,
346 350 extra)
347 351 node = self.repo.commitctx(ctx)
348 352 return hex(node), hex(tagparent)
349 353
350 354 def setfilemapmode(self, active):
351 355 self.filemapmode = active
352 356
353 357 def putbookmarks(self, updatedbookmark):
354 358 if not len(updatedbookmark):
355 359 return
356 360
357 361 self.ui.status(_("updating bookmarks\n"))
358 362 destmarks = self.repo._bookmarks
359 363 for bookmark in updatedbookmark:
360 364 destmarks[bookmark] = bin(updatedbookmark[bookmark])
361 365 destmarks.write()
362 366
363 367 def hascommitfrommap(self, rev):
364 368 # the exact semantics of clonebranches is unclear so we can't say no
365 369 return rev in self.repo or self.clonebranches
366 370
367 371 def hascommitforsplicemap(self, rev):
368 372 if rev not in self.repo and self.clonebranches:
369 373 raise util.Abort(_('revision %s not found in destination '
370 374 'repository (lookups with clonebranches=true '
371 375 'are not implemented)') % rev)
372 376 return rev in self.repo
373 377
374 378 class mercurial_source(converter_source):
375 379 def __init__(self, ui, path, revs=None):
376 380 converter_source.__init__(self, ui, path, revs)
377 381 if revs and len(revs) > 1:
378 382 raise util.Abort(_("mercurial source does not support specifying "
379 383 "multiple revisions"))
380 384 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
381 385 self.ignored = set()
382 386 self.saverev = ui.configbool('convert', 'hg.saverev', False)
383 387 try:
384 388 self.repo = hg.repository(self.ui, path)
385 389 # try to provoke an exception if this isn't really a hg
386 390 # repo, but some other bogus compatible-looking url
387 391 if not self.repo.local():
388 392 raise error.RepoError
389 393 except error.RepoError:
390 394 ui.traceback()
391 395 raise NoRepo(_("%s is not a local Mercurial repository") % path)
392 396 self.lastrev = None
393 397 self.lastctx = None
394 398 self._changescache = None, None
395 399 self.convertfp = None
396 400 # Restrict converted revisions to startrev descendants
397 401 startnode = ui.config('convert', 'hg.startrev')
398 402 hgrevs = ui.config('convert', 'hg.revs')
399 403 if hgrevs is None:
400 404 if startnode is not None:
401 405 try:
402 406 startnode = self.repo.lookup(startnode)
403 407 except error.RepoError:
404 408 raise util.Abort(_('%s is not a valid start revision')
405 409 % startnode)
406 410 startrev = self.repo.changelog.rev(startnode)
407 411 children = {startnode: 1}
408 412 for r in self.repo.changelog.descendants([startrev]):
409 413 children[self.repo.changelog.node(r)] = 1
410 414 self.keep = children.__contains__
411 415 else:
412 416 self.keep = util.always
413 417 if revs:
414 418 self._heads = [self.repo[revs[0]].node()]
415 419 else:
416 420 self._heads = self.repo.heads()
417 421 else:
418 422 if revs or startnode is not None:
419 423 raise util.Abort(_('hg.revs cannot be combined with '
420 424 'hg.startrev or --rev'))
421 425 nodes = set()
422 426 parents = set()
423 427 for r in scmutil.revrange(self.repo, [hgrevs]):
424 428 ctx = self.repo[r]
425 429 nodes.add(ctx.node())
426 430 parents.update(p.node() for p in ctx.parents())
427 431 self.keep = nodes.__contains__
428 432 self._heads = nodes - parents
429 433
430 434 def changectx(self, rev):
431 435 if self.lastrev != rev:
432 436 self.lastctx = self.repo[rev]
433 437 self.lastrev = rev
434 438 return self.lastctx
435 439
436 440 def parents(self, ctx):
437 441 return [p for p in ctx.parents() if p and self.keep(p.node())]
438 442
439 443 def getheads(self):
440 444 return [hex(h) for h in self._heads if self.keep(h)]
441 445
442 446 def getfile(self, name, rev):
443 447 try:
444 448 fctx = self.changectx(rev)[name]
445 449 return fctx.data(), fctx.flags()
446 450 except error.LookupError:
447 451 return None, None
448 452
449 453 def getchanges(self, rev, full):
450 454 ctx = self.changectx(rev)
451 455 parents = self.parents(ctx)
452 456 if full or not parents:
453 457 files = copyfiles = ctx.manifest()
454 458 if parents:
455 459 if self._changescache[0] == rev:
456 460 m, a, r = self._changescache[1]
457 461 else:
458 462 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
459 463 if not full:
460 464 files = m + a + r
461 465 copyfiles = m + a
462 466 # getcopies() is also run for roots and before filtering so missing
463 467 # revlogs are detected early
464 468 copies = self.getcopies(ctx, parents, copyfiles)
465 469 cleanp2 = set()
466 470 if len(parents) == 2:
467 471 cleanp2.update(self.repo.status(parents[1].node(), ctx.node(),
468 472 clean=True).clean)
469 473 changes = [(f, rev) for f in files if f not in self.ignored]
470 474 changes.sort()
471 475 return changes, copies, cleanp2
472 476
473 477 def getcopies(self, ctx, parents, files):
474 478 copies = {}
475 479 for name in files:
476 480 if name in self.ignored:
477 481 continue
478 482 try:
479 483 copysource, _copynode = ctx.filectx(name).renamed()
480 484 if copysource in self.ignored:
481 485 continue
482 486 # Ignore copy sources not in parent revisions
483 487 found = False
484 488 for p in parents:
485 489 if copysource in p:
486 490 found = True
487 491 break
488 492 if not found:
489 493 continue
490 494 copies[name] = copysource
491 495 except TypeError:
492 496 pass
493 497 except error.LookupError as e:
494 498 if not self.ignoreerrors:
495 499 raise
496 500 self.ignored.add(name)
497 501 self.ui.warn(_('ignoring: %s\n') % e)
498 502 return copies
499 503
500 504 def getcommit(self, rev):
501 505 ctx = self.changectx(rev)
502 506 parents = [p.hex() for p in self.parents(ctx)]
503 507 crev = rev
504 508
505 509 return commit(author=ctx.user(),
506 510 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
507 511 desc=ctx.description(), rev=crev, parents=parents,
508 512 branch=ctx.branch(), extra=ctx.extra(),
509 513 sortkey=ctx.rev(), saverev=self.saverev,
510 514 phase=ctx.phase())
511 515
512 516 def gettags(self):
513 517 # This will get written to .hgtags, filter non global tags out.
514 518 tags = [t for t in self.repo.tagslist()
515 519 if self.repo.tagtype(t[0]) == 'global']
516 520 return dict([(name, hex(node)) for name, node in tags
517 521 if self.keep(node)])
518 522
519 523 def getchangedfiles(self, rev, i):
520 524 ctx = self.changectx(rev)
521 525 parents = self.parents(ctx)
522 526 if not parents and i is None:
523 527 i = 0
524 528 changes = [], ctx.manifest().keys(), []
525 529 else:
526 530 i = i or 0
527 531 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
528 532 changes = [[f for f in l if f not in self.ignored] for l in changes]
529 533
530 534 if i == 0:
531 535 self._changescache = (rev, changes)
532 536
533 537 return changes[0] + changes[1] + changes[2]
534 538
535 539 def converted(self, rev, destrev):
536 540 if self.convertfp is None:
537 541 self.convertfp = open(self.repo.join('shamap'), 'a')
538 542 self.convertfp.write('%s %s\n' % (destrev, rev))
539 543 self.convertfp.flush()
540 544
541 545 def before(self):
542 546 self.ui.debug('run hg source pre-conversion action\n')
543 547
544 548 def after(self):
545 549 self.ui.debug('run hg source post-conversion action\n')
546 550
547 551 def hasnativeorder(self):
548 552 return True
549 553
550 554 def hasnativeclose(self):
551 555 return True
552 556
553 557 def lookuprev(self, rev):
554 558 try:
555 559 return hex(self.repo.lookup(rev))
556 560 except (error.RepoError, error.LookupError):
557 561 return None
558 562
559 563 def getbookmarks(self):
560 564 return bookmarks.listbookmarks(self.repo)
561 565
562 566 def checkrevformat(self, revstr, mapname='splicemap'):
563 567 """ Mercurial, revision string is a 40 byte hex """
564 568 self.checkhexformat(revstr, mapname)
@@ -1,505 +1,523 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.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 262
263 263 Perforce Source
264 264 ###############
265 265
266 266 The Perforce (P4) importer can be given a p4 depot path or a client
267 267 specification as source. It will convert all files in the source to a flat
268 268 Mercurial repository, ignoring labels, branches and integrations. Note
269 269 that when a depot path is given you then usually should specify a target
270 270 directory, because otherwise the target may be named "...-hg".
271 271
272 272 It is possible to limit the amount of source history to be converted by
273 273 specifying an initial Perforce revision:
274 274
275 275 convert.p4.startrev
276 276 specify initial Perforce revision (a Perforce changelist
277 277 number).
278 278
279 279 Mercurial Destination
280 280 #####################
281 281
282 282 The Mercurial destination will recognize Mercurial subrepositories in the
283 283 destination directory, and update the .hgsubstate file automatically if
284 284 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
285 285 Converting a repository with subrepositories requires converting a single
286 286 repository at a time, from the bottom up.
287 287
288 288 The following options are supported:
289 289
290 290 convert.hg.clonebranches
291 291 dispatch source branches in separate clones. The default is
292 292 False.
293 293 convert.hg.tagsbranch
294 294 branch name for tag revisions, defaults to "default".
295 295 convert.hg.usebranchnames
296 296 preserve branch names. The default is True.
297 convert.hg.sourcename
298 records the given string as a 'convert_source' extra value
299 on each commit made in the target repository. The default is
300 None.
297 301
298 302 All Destinations
299 303 ################
300 304
301 305 All destination types accept the following options:
302 306
303 307 convert.skiptags
304 308 does not convert tags from the source repo to the target
305 309 repo. The default is False.
306 310
307 311 options ([+] can be repeated):
308 312
309 313 -s --source-type TYPE source repository type
310 314 -d --dest-type TYPE destination repository type
311 315 -r --rev REV [+] import up to source revision REV
312 316 -A --authormap FILE remap usernames using this file
313 317 --filemap FILE remap file names using contents of file
314 318 --full apply filemap changes by converting all files again
315 319 --splicemap FILE splice synthesized history into place
316 320 --branchmap FILE change branch names while converting
317 321 --branchsort try to sort changesets by branches
318 322 --datesort try to sort changesets by date
319 323 --sourcesort preserve source changesets order
320 324 --closesort try to reorder closed revisions
321 325
322 326 (some details hidden, use --verbose to show complete help)
323 327 $ hg init a
324 328 $ cd a
325 329 $ echo a > a
326 330 $ hg ci -d'0 0' -Ama
327 331 adding a
328 332 $ hg cp a b
329 333 $ hg ci -d'1 0' -mb
330 334 $ hg rm a
331 335 $ hg ci -d'2 0' -mc
332 336 $ hg mv b a
333 337 $ hg ci -d'3 0' -md
334 338 $ echo a >> a
335 339 $ hg ci -d'4 0' -me
336 340 $ cd ..
337 341 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
338 342 assuming destination a-hg
339 343 initializing destination a-hg repository
340 344 scanning source...
341 345 sorting...
342 346 converting...
343 347 4 a
344 348 3 b
345 349 2 c
346 350 1 d
347 351 0 e
348 352 $ hg --cwd a-hg pull ../a
349 353 pulling from ../a
350 354 searching for changes
351 355 no changes found
352 356
353 357 conversion to existing file should fail
354 358
355 359 $ touch bogusfile
356 360 $ hg convert a bogusfile
357 361 initializing destination bogusfile repository
358 362 abort: cannot create new bundle repository
359 363 [255]
360 364
361 365 #if unix-permissions no-root
362 366
363 367 conversion to dir without permissions should fail
364 368
365 369 $ mkdir bogusdir
366 370 $ chmod 000 bogusdir
367 371
368 372 $ hg convert a bogusdir
369 373 abort: Permission denied: 'bogusdir'
370 374 [255]
371 375
372 376 user permissions should succeed
373 377
374 378 $ chmod 700 bogusdir
375 379 $ hg convert a bogusdir
376 380 initializing destination bogusdir repository
377 381 scanning source...
378 382 sorting...
379 383 converting...
380 384 4 a
381 385 3 b
382 386 2 c
383 387 1 d
384 388 0 e
385 389
386 390 #endif
387 391
388 392 test pre and post conversion actions
389 393
390 394 $ echo 'include b' > filemap
391 395 $ hg convert --debug --filemap filemap a partialb | \
392 396 > grep 'run hg'
393 397 run hg source pre-conversion action
394 398 run hg sink pre-conversion action
395 399 run hg sink post-conversion action
396 400 run hg source post-conversion action
397 401
398 402 converting empty dir should fail "nicely
399 403
400 404 $ mkdir emptydir
401 405
402 406 override $PATH to ensure p4 not visible; use $PYTHON in case we're
403 407 running from a devel copy, not a temp installation
404 408
405 409 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
406 410 assuming destination emptydir-hg
407 411 initializing destination emptydir-hg repository
408 412 emptydir does not look like a CVS checkout
409 413 emptydir does not look like a Git repository
410 414 emptydir does not look like a Subversion repository
411 415 emptydir is not a local Mercurial repository
412 416 emptydir does not look like a darcs repository
413 417 emptydir does not look like a monotone repository
414 418 emptydir does not look like a GNU Arch repository
415 419 emptydir does not look like a Bazaar repository
416 420 cannot find required "p4" tool
417 421 abort: emptydir: missing or unsupported repository
418 422 [255]
419 423
420 424 convert with imaginary source type
421 425
422 426 $ hg convert --source-type foo a a-foo
423 427 initializing destination a-foo repository
424 428 abort: foo: invalid source repository type
425 429 [255]
426 430
427 431 convert with imaginary sink type
428 432
429 433 $ hg convert --dest-type foo a a-foo
430 434 abort: foo: invalid destination repository type
431 435 [255]
432 436
433 437 testing: convert must not produce duplicate entries in fncache
434 438
435 439 $ hg convert a b
436 440 initializing destination b repository
437 441 scanning source...
438 442 sorting...
439 443 converting...
440 444 4 a
441 445 3 b
442 446 2 c
443 447 1 d
444 448 0 e
445 449
446 450 contents of fncache file:
447 451
448 452 $ cat b/.hg/store/fncache | sort
449 453 data/a.i
450 454 data/b.i
451 455
452 456 test bogus URL
453 457
454 458 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
455 459 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
456 460 [255]
457 461
458 462 test revset converted() lookup
459 463
460 464 $ hg --config convert.hg.saverev=True convert a c
461 465 initializing destination c repository
462 466 scanning source...
463 467 sorting...
464 468 converting...
465 469 4 a
466 470 3 b
467 471 2 c
468 472 1 d
469 473 0 e
470 474 $ echo f > c/f
471 475 $ hg -R c ci -d'0 0' -Amf
472 476 adding f
473 477 created new head
474 478 $ hg -R c log -r "converted(09d945a62ce6)"
475 479 changeset: 1:98c3dd46a874
476 480 user: test
477 481 date: Thu Jan 01 00:00:01 1970 +0000
478 482 summary: b
479 483
480 484 $ hg -R c log -r "converted()"
481 485 changeset: 0:31ed57b2037c
482 486 user: test
483 487 date: Thu Jan 01 00:00:00 1970 +0000
484 488 summary: a
485 489
486 490 changeset: 1:98c3dd46a874
487 491 user: test
488 492 date: Thu Jan 01 00:00:01 1970 +0000
489 493 summary: b
490 494
491 495 changeset: 2:3b9ca06ef716
492 496 user: test
493 497 date: Thu Jan 01 00:00:02 1970 +0000
494 498 summary: c
495 499
496 500 changeset: 3:4e0debd37cf2
497 501 user: test
498 502 date: Thu Jan 01 00:00:03 1970 +0000
499 503 summary: d
500 504
501 505 changeset: 4:9de3bc9349c5
502 506 user: test
503 507 date: Thu Jan 01 00:00:04 1970 +0000
504 508 summary: e
505 509
510
511 test specifying a sourcename
512 $ echo g > a/g
513 $ hg -R a ci -d'0 0' -Amg
514 adding g
515 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
516 scanning source...
517 sorting...
518 converting...
519 0 g
520 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
521 branch=default
522 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
523 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now