##// END OF EJS Templates
configitems: register the 'convert.hg.ignoreerrors' config
Boris Feld -
r34165:79c01348 default
parent child Browse files
Show More
@@ -1,552 +1,555
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''import revisions from foreign VCS repositories into Mercurial'''
9 9
10 10 from __future__ import absolute_import
11 11
12 12 from mercurial.i18n import _
13 13 from mercurial import (
14 14 registrar,
15 15 )
16 16
17 17 from . import (
18 18 convcmd,
19 19 cvsps,
20 20 subversion,
21 21 )
22 22
23 23 cmdtable = {}
24 24 command = registrar.command(cmdtable)
25 25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
26 26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 27 # be specifying the version(s) of Mercurial they are tested with, or
28 28 # leave the attribute unspecified.
29 29 testedwith = 'ships-with-hg-core'
30 30
31 31 configtable = {}
32 32 configitem = registrar.configitem(configtable)
33 33
34 34 configitem('convert', 'cvsps.cache',
35 35 default=True,
36 36 )
37 37 configitem('convert', 'cvsps.fuzz',
38 38 default=60,
39 39 )
40 40 configitem('convert', 'cvsps.mergefrom',
41 41 default=None,
42 42 )
43 43 configitem('convert', 'cvsps.mergeto',
44 44 default=None,
45 45 )
46 46 configitem('convert', 'git.committeractions',
47 47 default=lambda: ['messagedifferent'],
48 48 )
49 49 configitem('convert', 'git.extrakeys',
50 50 default=list,
51 51 )
52 52 configitem('convert', 'git.findcopiesharder',
53 53 default=False,
54 54 )
55 55 configitem('convert', 'git.remoteprefix',
56 56 default='remote',
57 57 )
58 58 configitem('convert', 'git.renamelimit',
59 59 default=400,
60 60 )
61 61 configitem('convert', 'git.saverev',
62 62 default=True,
63 63 )
64 64 configitem('convert', 'git.similarity',
65 65 default=50,
66 66 )
67 67 configitem('convert', 'git.skipsubmodules',
68 68 default=False,
69 69 )
70 70 configitem('convert', 'hg.clonebranches',
71 71 default=False,
72 72 )
73 configitem('convert', 'hg.ignoreerrors',
74 default=False,
75 )
73 76
74 77 # Commands definition was moved elsewhere to ease demandload job.
75 78
76 79 @command('convert',
77 80 [('', 'authors', '',
78 81 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
79 82 _('FILE')),
80 83 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
81 84 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
82 85 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
83 86 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
84 87 ('', 'filemap', '', _('remap file names using contents of file'),
85 88 _('FILE')),
86 89 ('', 'full', None,
87 90 _('apply filemap changes by converting all files again')),
88 91 ('', 'splicemap', '', _('splice synthesized history into place'),
89 92 _('FILE')),
90 93 ('', 'branchmap', '', _('change branch names while converting'),
91 94 _('FILE')),
92 95 ('', 'branchsort', None, _('try to sort changesets by branches')),
93 96 ('', 'datesort', None, _('try to sort changesets by date')),
94 97 ('', 'sourcesort', None, _('preserve source changesets order')),
95 98 ('', 'closesort', None, _('try to reorder closed revisions'))],
96 99 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
97 100 norepo=True)
98 101 def convert(ui, src, dest=None, revmapfile=None, **opts):
99 102 """convert a foreign SCM repository to a Mercurial one.
100 103
101 104 Accepted source formats [identifiers]:
102 105
103 106 - Mercurial [hg]
104 107 - CVS [cvs]
105 108 - Darcs [darcs]
106 109 - git [git]
107 110 - Subversion [svn]
108 111 - Monotone [mtn]
109 112 - GNU Arch [gnuarch]
110 113 - Bazaar [bzr]
111 114 - Perforce [p4]
112 115
113 116 Accepted destination formats [identifiers]:
114 117
115 118 - Mercurial [hg]
116 119 - Subversion [svn] (history on branches is not preserved)
117 120
118 121 If no revision is given, all revisions will be converted.
119 122 Otherwise, convert will only import up to the named revision
120 123 (given in a format understood by the source).
121 124
122 125 If no destination directory name is specified, it defaults to the
123 126 basename of the source with ``-hg`` appended. If the destination
124 127 repository doesn't exist, it will be created.
125 128
126 129 By default, all sources except Mercurial will use --branchsort.
127 130 Mercurial uses --sourcesort to preserve original revision numbers
128 131 order. Sort modes have the following effects:
129 132
130 133 --branchsort convert from parent to child revision when possible,
131 134 which means branches are usually converted one after
132 135 the other. It generates more compact repositories.
133 136
134 137 --datesort sort revisions by date. Converted repositories have
135 138 good-looking changelogs but are often an order of
136 139 magnitude larger than the same ones generated by
137 140 --branchsort.
138 141
139 142 --sourcesort try to preserve source revisions order, only
140 143 supported by Mercurial sources.
141 144
142 145 --closesort try to move closed revisions as close as possible
143 146 to parent branches, only supported by Mercurial
144 147 sources.
145 148
146 149 If ``REVMAP`` isn't given, it will be put in a default location
147 150 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
148 151 text file that maps each source commit ID to the destination ID
149 152 for that revision, like so::
150 153
151 154 <source ID> <destination ID>
152 155
153 156 If the file doesn't exist, it's automatically created. It's
154 157 updated on each commit copied, so :hg:`convert` can be interrupted
155 158 and can be run repeatedly to copy new commits.
156 159
157 160 The authormap is a simple text file that maps each source commit
158 161 author to a destination commit author. It is handy for source SCMs
159 162 that use unix logins to identify authors (e.g.: CVS). One line per
160 163 author mapping and the line format is::
161 164
162 165 source author = destination author
163 166
164 167 Empty lines and lines starting with a ``#`` are ignored.
165 168
166 169 The filemap is a file that allows filtering and remapping of files
167 170 and directories. Each line can contain one of the following
168 171 directives::
169 172
170 173 include path/to/file-or-dir
171 174
172 175 exclude path/to/file-or-dir
173 176
174 177 rename path/to/source path/to/destination
175 178
176 179 Comment lines start with ``#``. A specified path matches if it
177 180 equals the full relative name of a file or one of its parent
178 181 directories. The ``include`` or ``exclude`` directive with the
179 182 longest matching path applies, so line order does not matter.
180 183
181 184 The ``include`` directive causes a file, or all files under a
182 185 directory, to be included in the destination repository. The default
183 186 if there are no ``include`` statements is to include everything.
184 187 If there are any ``include`` statements, nothing else is included.
185 188 The ``exclude`` directive causes files or directories to
186 189 be omitted. The ``rename`` directive renames a file or directory if
187 190 it is converted. To rename from a subdirectory into the root of
188 191 the repository, use ``.`` as the path to rename to.
189 192
190 193 ``--full`` will make sure the converted changesets contain exactly
191 194 the right files with the right content. It will make a full
192 195 conversion of all files, not just the ones that have
193 196 changed. Files that already are correct will not be changed. This
194 197 can be used to apply filemap changes when converting
195 198 incrementally. This is currently only supported for Mercurial and
196 199 Subversion.
197 200
198 201 The splicemap is a file that allows insertion of synthetic
199 202 history, letting you specify the parents of a revision. This is
200 203 useful if you want to e.g. give a Subversion merge two parents, or
201 204 graft two disconnected series of history together. Each entry
202 205 contains a key, followed by a space, followed by one or two
203 206 comma-separated values::
204 207
205 208 key parent1, parent2
206 209
207 210 The key is the revision ID in the source
208 211 revision control system whose parents should be modified (same
209 212 format as a key in .hg/shamap). The values are the revision IDs
210 213 (in either the source or destination revision control system) that
211 214 should be used as the new parents for that node. For example, if
212 215 you have merged "release-1.0" into "trunk", then you should
213 216 specify the revision on "trunk" as the first parent and the one on
214 217 the "release-1.0" branch as the second.
215 218
216 219 The branchmap is a file that allows you to rename a branch when it is
217 220 being brought in from whatever external repository. When used in
218 221 conjunction with a splicemap, it allows for a powerful combination
219 222 to help fix even the most badly mismanaged repositories and turn them
220 223 into nicely structured Mercurial repositories. The branchmap contains
221 224 lines of the form::
222 225
223 226 original_branch_name new_branch_name
224 227
225 228 where "original_branch_name" is the name of the branch in the
226 229 source repository, and "new_branch_name" is the name of the branch
227 230 is the destination repository. No whitespace is allowed in the new
228 231 branch name. This can be used to (for instance) move code in one
229 232 repository from "default" to a named branch.
230 233
231 234 Mercurial Source
232 235 ################
233 236
234 237 The Mercurial source recognizes the following configuration
235 238 options, which you can set on the command line with ``--config``:
236 239
237 240 :convert.hg.ignoreerrors: ignore integrity errors when reading.
238 241 Use it to fix Mercurial repositories with missing revlogs, by
239 242 converting from and to Mercurial. Default is False.
240 243
241 244 :convert.hg.saverev: store original revision ID in changeset
242 245 (forces target IDs to change). It takes a boolean argument and
243 246 defaults to False.
244 247
245 248 :convert.hg.startrev: specify the initial Mercurial revision.
246 249 The default is 0.
247 250
248 251 :convert.hg.revs: revset specifying the source revisions to convert.
249 252
250 253 CVS Source
251 254 ##########
252 255
253 256 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
254 257 to indicate the starting point of what will be converted. Direct
255 258 access to the repository files is not needed, unless of course the
256 259 repository is ``:local:``. The conversion uses the top level
257 260 directory in the sandbox to find the CVS repository, and then uses
258 261 CVS rlog commands to find files to convert. This means that unless
259 262 a filemap is given, all files under the starting directory will be
260 263 converted, and that any directory reorganization in the CVS
261 264 sandbox is ignored.
262 265
263 266 The following options can be used with ``--config``:
264 267
265 268 :convert.cvsps.cache: Set to False to disable remote log caching,
266 269 for testing and debugging purposes. Default is True.
267 270
268 271 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
269 272 allowed between commits with identical user and log message in
270 273 a single changeset. When very large files were checked in as
271 274 part of a changeset then the default may not be long enough.
272 275 The default is 60.
273 276
274 277 :convert.cvsps.logencoding: Specify encoding name to be used for
275 278 transcoding CVS log messages. Multiple encoding names can be
276 279 specified as a list (see :hg:`help config.Syntax`), but only
277 280 the first acceptable encoding in the list is used per CVS log
278 281 entries. This transcoding is executed before cvslog hook below.
279 282
280 283 :convert.cvsps.mergeto: Specify a regular expression to which
281 284 commit log messages are matched. If a match occurs, then the
282 285 conversion process will insert a dummy revision merging the
283 286 branch on which this log message occurs to the branch
284 287 indicated in the regex. Default is ``{{mergetobranch
285 288 ([-\\w]+)}}``
286 289
287 290 :convert.cvsps.mergefrom: Specify a regular expression to which
288 291 commit log messages are matched. If a match occurs, then the
289 292 conversion process will add the most recent revision on the
290 293 branch indicated in the regex as the second parent of the
291 294 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
292 295
293 296 :convert.localtimezone: use local time (as determined by the TZ
294 297 environment variable) for changeset date/times. The default
295 298 is False (use UTC).
296 299
297 300 :hooks.cvslog: Specify a Python function to be called at the end of
298 301 gathering the CVS log. The function is passed a list with the
299 302 log entries, and can modify the entries in-place, or add or
300 303 delete them.
301 304
302 305 :hooks.cvschangesets: Specify a Python function to be called after
303 306 the changesets are calculated from the CVS log. The
304 307 function is passed a list with the changeset entries, and can
305 308 modify the changesets in-place, or add or delete them.
306 309
307 310 An additional "debugcvsps" Mercurial command allows the builtin
308 311 changeset merging code to be run without doing a conversion. Its
309 312 parameters and output are similar to that of cvsps 2.1. Please see
310 313 the command help for more details.
311 314
312 315 Subversion Source
313 316 #################
314 317
315 318 Subversion source detects classical trunk/branches/tags layouts.
316 319 By default, the supplied ``svn://repo/path/`` source URL is
317 320 converted as a single branch. If ``svn://repo/path/trunk`` exists
318 321 it replaces the default branch. If ``svn://repo/path/branches``
319 322 exists, its subdirectories are listed as possible branches. If
320 323 ``svn://repo/path/tags`` exists, it is looked for tags referencing
321 324 converted branches. Default ``trunk``, ``branches`` and ``tags``
322 325 values can be overridden with following options. Set them to paths
323 326 relative to the source URL, or leave them blank to disable auto
324 327 detection.
325 328
326 329 The following options can be set with ``--config``:
327 330
328 331 :convert.svn.branches: specify the directory containing branches.
329 332 The default is ``branches``.
330 333
331 334 :convert.svn.tags: specify the directory containing tags. The
332 335 default is ``tags``.
333 336
334 337 :convert.svn.trunk: specify the name of the trunk branch. The
335 338 default is ``trunk``.
336 339
337 340 :convert.localtimezone: use local time (as determined by the TZ
338 341 environment variable) for changeset date/times. The default
339 342 is False (use UTC).
340 343
341 344 Source history can be retrieved starting at a specific revision,
342 345 instead of being integrally converted. Only single branch
343 346 conversions are supported.
344 347
345 348 :convert.svn.startrev: specify start Subversion revision number.
346 349 The default is 0.
347 350
348 351 Git Source
349 352 ##########
350 353
351 354 The Git importer converts commits from all reachable branches (refs
352 355 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
353 356 Branches are converted to bookmarks with the same name, with the
354 357 leading 'refs/heads' stripped. Git submodules are converted to Git
355 358 subrepos in Mercurial.
356 359
357 360 The following options can be set with ``--config``:
358 361
359 362 :convert.git.similarity: specify how similar files modified in a
360 363 commit must be to be imported as renames or copies, as a
361 364 percentage between ``0`` (disabled) and ``100`` (files must be
362 365 identical). For example, ``90`` means that a delete/add pair will
363 366 be imported as a rename if more than 90% of the file hasn't
364 367 changed. The default is ``50``.
365 368
366 369 :convert.git.findcopiesharder: while detecting copies, look at all
367 370 files in the working copy instead of just changed ones. This
368 371 is very expensive for large projects, and is only effective when
369 372 ``convert.git.similarity`` is greater than 0. The default is False.
370 373
371 374 :convert.git.renamelimit: perform rename and copy detection up to this
372 375 many changed files in a commit. Increasing this will make rename
373 376 and copy detection more accurate but will significantly slow down
374 377 computation on large projects. The option is only relevant if
375 378 ``convert.git.similarity`` is greater than 0. The default is
376 379 ``400``.
377 380
378 381 :convert.git.committeractions: list of actions to take when processing
379 382 author and committer values.
380 383
381 384 Git commits have separate author (who wrote the commit) and committer
382 385 (who applied the commit) fields. Not all destinations support separate
383 386 author and committer fields (including Mercurial). This config option
384 387 controls what to do with these author and committer fields during
385 388 conversion.
386 389
387 390 A value of ``messagedifferent`` will append a ``committer: ...``
388 391 line to the commit message if the Git committer is different from the
389 392 author. The prefix of that line can be specified using the syntax
390 393 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
391 394 When a prefix is specified, a space will always be inserted between the
392 395 prefix and the value.
393 396
394 397 ``messagealways`` behaves like ``messagedifferent`` except it will
395 398 always result in a ``committer: ...`` line being appended to the commit
396 399 message. This value is mutually exclusive with ``messagedifferent``.
397 400
398 401 ``dropcommitter`` will remove references to the committer. Only
399 402 references to the author will remain. Actions that add references
400 403 to the committer will have no effect when this is set.
401 404
402 405 ``replaceauthor`` will replace the value of the author field with
403 406 the committer. Other actions that add references to the committer
404 407 will still take effect when this is set.
405 408
406 409 The default is ``messagedifferent``.
407 410
408 411 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
409 412 the destination. Some Git repositories store extra metadata in commits.
410 413 By default, this non-default metadata will be lost during conversion.
411 414 Setting this config option can retain that metadata. Some built-in
412 415 keys such as ``parent`` and ``branch`` are not allowed to be copied.
413 416
414 417 :convert.git.remoteprefix: remote refs are converted as bookmarks with
415 418 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
416 419 is 'remote'.
417 420
418 421 :convert.git.saverev: whether to store the original Git commit ID in the
419 422 metadata of the destination commit. The default is True.
420 423
421 424 :convert.git.skipsubmodules: does not convert root level .gitmodules files
422 425 or files with 160000 mode indicating a submodule. Default is False.
423 426
424 427 Perforce Source
425 428 ###############
426 429
427 430 The Perforce (P4) importer can be given a p4 depot path or a
428 431 client specification as source. It will convert all files in the
429 432 source to a flat Mercurial repository, ignoring labels, branches
430 433 and integrations. Note that when a depot path is given you then
431 434 usually should specify a target directory, because otherwise the
432 435 target may be named ``...-hg``.
433 436
434 437 The following options can be set with ``--config``:
435 438
436 439 :convert.p4.encoding: specify the encoding to use when decoding standard
437 440 output of the Perforce command line tool. The default is default system
438 441 encoding.
439 442
440 443 :convert.p4.startrev: specify initial Perforce revision (a
441 444 Perforce changelist number).
442 445
443 446 Mercurial Destination
444 447 #####################
445 448
446 449 The Mercurial destination will recognize Mercurial subrepositories in the
447 450 destination directory, and update the .hgsubstate file automatically if the
448 451 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
449 452 Converting a repository with subrepositories requires converting a single
450 453 repository at a time, from the bottom up.
451 454
452 455 .. container:: verbose
453 456
454 457 An example showing how to convert a repository with subrepositories::
455 458
456 459 # so convert knows the type when it sees a non empty destination
457 460 $ hg init converted
458 461
459 462 $ hg convert orig/sub1 converted/sub1
460 463 $ hg convert orig/sub2 converted/sub2
461 464 $ hg convert orig converted
462 465
463 466 The following options are supported:
464 467
465 468 :convert.hg.clonebranches: dispatch source branches in separate
466 469 clones. The default is False.
467 470
468 471 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
469 472 ``default``.
470 473
471 474 :convert.hg.usebranchnames: preserve branch names. The default is
472 475 True.
473 476
474 477 :convert.hg.sourcename: records the given string as a 'convert_source' extra
475 478 value on each commit made in the target repository. The default is None.
476 479
477 480 All Destinations
478 481 ################
479 482
480 483 All destination types accept the following options:
481 484
482 485 :convert.skiptags: does not convert tags from the source repo to the target
483 486 repo. The default is False.
484 487 """
485 488 return convcmd.convert(ui, src, dest, revmapfile, **opts)
486 489
487 490 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
488 491 def debugsvnlog(ui, **opts):
489 492 return subversion.debugsvnlog(ui, **opts)
490 493
491 494 @command('debugcvsps',
492 495 [
493 496 # Main options shared with cvsps-2.1
494 497 ('b', 'branches', [], _('only return changes on specified branches')),
495 498 ('p', 'prefix', '', _('prefix to remove from file names')),
496 499 ('r', 'revisions', [],
497 500 _('only return changes after or between specified tags')),
498 501 ('u', 'update-cache', None, _("update cvs log cache")),
499 502 ('x', 'new-cache', None, _("create new cvs log cache")),
500 503 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
501 504 ('', 'root', '', _('specify cvsroot')),
502 505 # Options specific to builtin cvsps
503 506 ('', 'parents', '', _('show parent changesets')),
504 507 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
505 508 # Options that are ignored for compatibility with cvsps-2.1
506 509 ('A', 'cvs-direct', None, _('ignored for compatibility')),
507 510 ],
508 511 _('hg debugcvsps [OPTION]... [PATH]...'),
509 512 norepo=True)
510 513 def debugcvsps(ui, *args, **opts):
511 514 '''create changeset information from CVS
512 515
513 516 This command is intended as a debugging tool for the CVS to
514 517 Mercurial converter, and can be used as a direct replacement for
515 518 cvsps.
516 519
517 520 Hg debugcvsps reads the CVS rlog for current directory (or any
518 521 named directory) in the CVS repository, and converts the log to a
519 522 series of changesets based on matching commit log entries and
520 523 dates.'''
521 524 return cvsps.debugcvsps(ui, *args, **opts)
522 525
523 526 def kwconverted(ctx, name):
524 527 rev = ctx.extra().get('convert_revision', '')
525 528 if rev.startswith('svn:'):
526 529 if name == 'svnrev':
527 530 return str(subversion.revsplit(rev)[2])
528 531 elif name == 'svnpath':
529 532 return subversion.revsplit(rev)[1]
530 533 elif name == 'svnuuid':
531 534 return subversion.revsplit(rev)[0]
532 535 return rev
533 536
534 537 templatekeyword = registrar.templatekeyword()
535 538
536 539 @templatekeyword('svnrev')
537 540 def kwsvnrev(repo, ctx, **args):
538 541 """String. Converted subversion revision number."""
539 542 return kwconverted(ctx, 'svnrev')
540 543
541 544 @templatekeyword('svnpath')
542 545 def kwsvnpath(repo, ctx, **args):
543 546 """String. Converted subversion revision project path."""
544 547 return kwconverted(ctx, 'svnpath')
545 548
546 549 @templatekeyword('svnuuid')
547 550 def kwsvnuuid(repo, ctx, **args):
548 551 """String. Converted subversion revision repository identifier."""
549 552 return kwconverted(ctx, 'svnuuid')
550 553
551 554 # tell hggettext to extract docstrings from these functions:
552 555 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,655 +1,655
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 from __future__ import absolute_import
20 20
21 21 import os
22 22 import re
23 23 import time
24 24
25 25 from mercurial.i18n import _
26 26 from mercurial import (
27 27 bookmarks,
28 28 context,
29 29 error,
30 30 exchange,
31 31 hg,
32 32 lock as lockmod,
33 33 merge as mergemod,
34 34 node as nodemod,
35 35 phases,
36 36 scmutil,
37 37 util,
38 38 )
39 39 stringio = util.stringio
40 40
41 41 from . import common
42 42 mapfile = common.mapfile
43 43 NoRepo = common.NoRepo
44 44
45 45 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
46 46
47 47 class mercurial_sink(common.converter_sink):
48 48 def __init__(self, ui, path):
49 49 common.converter_sink.__init__(self, ui, path)
50 50 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
51 51 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
52 52 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
53 53 self.lastbranch = None
54 54 if os.path.isdir(path) and len(os.listdir(path)) > 0:
55 55 try:
56 56 self.repo = hg.repository(self.ui, path)
57 57 if not self.repo.local():
58 58 raise NoRepo(_('%s is not a local Mercurial repository')
59 59 % path)
60 60 except error.RepoError as err:
61 61 ui.traceback()
62 62 raise NoRepo(err.args[0])
63 63 else:
64 64 try:
65 65 ui.status(_('initializing destination %s repository\n') % path)
66 66 self.repo = hg.repository(self.ui, path, create=True)
67 67 if not self.repo.local():
68 68 raise NoRepo(_('%s is not a local Mercurial repository')
69 69 % path)
70 70 self.created.append(path)
71 71 except error.RepoError:
72 72 ui.traceback()
73 73 raise NoRepo(_("could not create hg repository %s as sink")
74 74 % path)
75 75 self.lock = None
76 76 self.wlock = None
77 77 self.filemapmode = False
78 78 self.subrevmaps = {}
79 79
80 80 def before(self):
81 81 self.ui.debug('run hg sink pre-conversion action\n')
82 82 self.wlock = self.repo.wlock()
83 83 self.lock = self.repo.lock()
84 84
85 85 def after(self):
86 86 self.ui.debug('run hg sink post-conversion action\n')
87 87 if self.lock:
88 88 self.lock.release()
89 89 if self.wlock:
90 90 self.wlock.release()
91 91
92 92 def revmapfile(self):
93 93 return self.repo.vfs.join("shamap")
94 94
95 95 def authorfile(self):
96 96 return self.repo.vfs.join("authormap")
97 97
98 98 def setbranch(self, branch, pbranches):
99 99 if not self.clonebranches:
100 100 return
101 101
102 102 setbranch = (branch != self.lastbranch)
103 103 self.lastbranch = branch
104 104 if not branch:
105 105 branch = 'default'
106 106 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
107 107 if pbranches:
108 108 pbranch = pbranches[0][1]
109 109 else:
110 110 pbranch = 'default'
111 111
112 112 branchpath = os.path.join(self.path, branch)
113 113 if setbranch:
114 114 self.after()
115 115 try:
116 116 self.repo = hg.repository(self.ui, branchpath)
117 117 except Exception:
118 118 self.repo = hg.repository(self.ui, branchpath, create=True)
119 119 self.before()
120 120
121 121 # pbranches may bring revisions from other branches (merge parents)
122 122 # Make sure we have them, or pull them.
123 123 missings = {}
124 124 for b in pbranches:
125 125 try:
126 126 self.repo.lookup(b[0])
127 127 except Exception:
128 128 missings.setdefault(b[1], []).append(b[0])
129 129
130 130 if missings:
131 131 self.after()
132 132 for pbranch, heads in sorted(missings.iteritems()):
133 133 pbranchpath = os.path.join(self.path, pbranch)
134 134 prepo = hg.peer(self.ui, {}, pbranchpath)
135 135 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
136 136 exchange.pull(self.repo, prepo,
137 137 [prepo.lookup(h) for h in heads])
138 138 self.before()
139 139
140 140 def _rewritetags(self, source, revmap, data):
141 141 fp = 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 revid = revmap.get(source.lookuprev(s[0]))
147 147 if not revid:
148 148 if s[0] == nodemod.nullhex:
149 149 revid = s[0]
150 150 else:
151 151 continue
152 152 fp.write('%s %s\n' % (revid, s[1]))
153 153 return fp.getvalue()
154 154
155 155 def _rewritesubstate(self, source, data):
156 156 fp = stringio()
157 157 for line in data.splitlines():
158 158 s = line.split(' ', 1)
159 159 if len(s) != 2:
160 160 continue
161 161
162 162 revid = s[0]
163 163 subpath = s[1]
164 164 if revid != nodemod.nullhex:
165 165 revmap = self.subrevmaps.get(subpath)
166 166 if revmap is None:
167 167 revmap = mapfile(self.ui,
168 168 self.repo.wjoin(subpath, '.hg/shamap'))
169 169 self.subrevmaps[subpath] = revmap
170 170
171 171 # It is reasonable that one or more of the subrepos don't
172 172 # need to be converted, in which case they can be cloned
173 173 # into place instead of converted. Therefore, only warn
174 174 # once.
175 175 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
176 176 if len(revmap) == 0:
177 177 sub = self.repo.wvfs.reljoin(subpath, '.hg')
178 178
179 179 if self.repo.wvfs.exists(sub):
180 180 self.ui.warn(msg % subpath)
181 181
182 182 newid = revmap.get(revid)
183 183 if not newid:
184 184 if len(revmap) > 0:
185 185 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
186 186 (revid, subpath))
187 187 else:
188 188 revid = newid
189 189
190 190 fp.write('%s %s\n' % (revid, subpath))
191 191
192 192 return fp.getvalue()
193 193
194 194 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
195 195 """Calculates the files from p2 that we need to pull in when merging p1
196 196 and p2, given that the merge is coming from the given source.
197 197
198 198 This prevents us from losing files that only exist in the target p2 and
199 199 that don't come from the source repo (like if you're merging multiple
200 200 repositories together).
201 201 """
202 202 anc = [p1ctx.ancestor(p2ctx)]
203 203 # Calculate what files are coming from p2
204 204 actions, diverge, rename = mergemod.calculateupdates(
205 205 self.repo, p1ctx, p2ctx, anc,
206 206 True, # branchmerge
207 207 True, # force
208 208 False, # acceptremote
209 209 False, # followcopies
210 210 )
211 211
212 212 for file, (action, info, msg) in actions.iteritems():
213 213 if source.targetfilebelongstosource(file):
214 214 # If the file belongs to the source repo, ignore the p2
215 215 # since it will be covered by the existing fileset.
216 216 continue
217 217
218 218 # If the file requires actual merging, abort. We don't have enough
219 219 # context to resolve merges correctly.
220 220 if action in ['m', 'dm', 'cd', 'dc']:
221 221 raise error.Abort(_("unable to convert merge commit "
222 222 "since target parents do not merge cleanly (file "
223 223 "%s, parents %s and %s)") % (file, p1ctx,
224 224 p2ctx))
225 225 elif action == 'k':
226 226 # 'keep' means nothing changed from p1
227 227 continue
228 228 else:
229 229 # Any other change means we want to take the p2 version
230 230 yield file
231 231
232 232 def putcommit(self, files, copies, parents, commit, source, revmap, full,
233 233 cleanp2):
234 234 files = dict(files)
235 235
236 236 def getfilectx(repo, memctx, f):
237 237 if p2ctx and f in p2files and f not in copies:
238 238 self.ui.debug('reusing %s from p2\n' % f)
239 239 try:
240 240 return p2ctx[f]
241 241 except error.ManifestLookupError:
242 242 # If the file doesn't exist in p2, then we're syncing a
243 243 # delete, so just return None.
244 244 return None
245 245 try:
246 246 v = files[f]
247 247 except KeyError:
248 248 return None
249 249 data, mode = source.getfile(f, v)
250 250 if data is None:
251 251 return None
252 252 if f == '.hgtags':
253 253 data = self._rewritetags(source, revmap, data)
254 254 if f == '.hgsubstate':
255 255 data = self._rewritesubstate(source, data)
256 256 return context.memfilectx(self.repo, f, data, 'l' in mode,
257 257 'x' in mode, copies.get(f))
258 258
259 259 pl = []
260 260 for p in parents:
261 261 if p not in pl:
262 262 pl.append(p)
263 263 parents = pl
264 264 nparents = len(parents)
265 265 if self.filemapmode and nparents == 1:
266 266 m1node = self.repo.changelog.read(nodemod.bin(parents[0]))[0]
267 267 parent = parents[0]
268 268
269 269 if len(parents) < 2:
270 270 parents.append(nodemod.nullid)
271 271 if len(parents) < 2:
272 272 parents.append(nodemod.nullid)
273 273 p2 = parents.pop(0)
274 274
275 275 text = commit.desc
276 276
277 277 sha1s = re.findall(sha1re, text)
278 278 for sha1 in sha1s:
279 279 oldrev = source.lookuprev(sha1)
280 280 newrev = revmap.get(oldrev)
281 281 if newrev is not None:
282 282 text = text.replace(sha1, newrev[:len(sha1)])
283 283
284 284 extra = commit.extra.copy()
285 285
286 286 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
287 287 if sourcename:
288 288 extra['convert_source'] = sourcename
289 289
290 290 for label in ('source', 'transplant_source', 'rebase_source',
291 291 'intermediate-source'):
292 292 node = extra.get(label)
293 293
294 294 if node is None:
295 295 continue
296 296
297 297 # Only transplant stores its reference in binary
298 298 if label == 'transplant_source':
299 299 node = nodemod.hex(node)
300 300
301 301 newrev = revmap.get(node)
302 302 if newrev is not None:
303 303 if label == 'transplant_source':
304 304 newrev = nodemod.bin(newrev)
305 305
306 306 extra[label] = newrev
307 307
308 308 if self.branchnames and commit.branch:
309 309 extra['branch'] = commit.branch
310 310 if commit.rev and commit.saverev:
311 311 extra['convert_revision'] = commit.rev
312 312
313 313 while parents:
314 314 p1 = p2
315 315 p2 = parents.pop(0)
316 316 p1ctx = self.repo[p1]
317 317 p2ctx = None
318 318 if p2 != nodemod.nullid:
319 319 p2ctx = self.repo[p2]
320 320 fileset = set(files)
321 321 if full:
322 322 fileset.update(self.repo[p1])
323 323 fileset.update(self.repo[p2])
324 324
325 325 if p2ctx:
326 326 p2files = set(cleanp2)
327 327 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
328 328 p2files.add(file)
329 329 fileset.add(file)
330 330
331 331 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
332 332 getfilectx, commit.author, commit.date, extra)
333 333
334 334 # We won't know if the conversion changes the node until after the
335 335 # commit, so copy the source's phase for now.
336 336 self.repo.ui.setconfig('phases', 'new-commit',
337 337 phases.phasenames[commit.phase], 'convert')
338 338
339 339 with self.repo.transaction("convert") as tr:
340 340 node = nodemod.hex(self.repo.commitctx(ctx))
341 341
342 342 # If the node value has changed, but the phase is lower than
343 343 # draft, set it back to draft since it hasn't been exposed
344 344 # anywhere.
345 345 if commit.rev != node:
346 346 ctx = self.repo[node]
347 347 if ctx.phase() < phases.draft:
348 348 phases.registernew(self.repo, tr, phases.draft,
349 349 [ctx.node()])
350 350
351 351 text = "(octopus merge fixup)\n"
352 352 p2 = node
353 353
354 354 if self.filemapmode and nparents == 1:
355 355 man = self.repo.manifestlog._revlog
356 356 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
357 357 closed = 'close' in commit.extra
358 358 if not closed and not man.cmp(m1node, man.revision(mnode)):
359 359 self.ui.status(_("filtering out empty revision\n"))
360 360 self.repo.rollback(force=True)
361 361 return parent
362 362 return p2
363 363
364 364 def puttags(self, tags):
365 365 try:
366 366 parentctx = self.repo[self.tagsbranch]
367 367 tagparent = parentctx.node()
368 368 except error.RepoError:
369 369 parentctx = None
370 370 tagparent = nodemod.nullid
371 371
372 372 oldlines = set()
373 373 for branch, heads in self.repo.branchmap().iteritems():
374 374 for h in heads:
375 375 if '.hgtags' in self.repo[h]:
376 376 oldlines.update(
377 377 set(self.repo[h]['.hgtags'].data().splitlines(True)))
378 378 oldlines = sorted(list(oldlines))
379 379
380 380 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
381 381 if newlines == oldlines:
382 382 return None, None
383 383
384 384 # if the old and new tags match, then there is nothing to update
385 385 oldtags = set()
386 386 newtags = set()
387 387 for line in oldlines:
388 388 s = line.strip().split(' ', 1)
389 389 if len(s) != 2:
390 390 continue
391 391 oldtags.add(s[1])
392 392 for line in newlines:
393 393 s = line.strip().split(' ', 1)
394 394 if len(s) != 2:
395 395 continue
396 396 if s[1] not in oldtags:
397 397 newtags.add(s[1].strip())
398 398
399 399 if not newtags:
400 400 return None, None
401 401
402 402 data = "".join(newlines)
403 403 def getfilectx(repo, memctx, f):
404 404 return context.memfilectx(repo, f, data, False, False, None)
405 405
406 406 self.ui.status(_("updating tags\n"))
407 407 date = "%s 0" % int(time.mktime(time.gmtime()))
408 408 extra = {'branch': self.tagsbranch}
409 409 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
410 410 [".hgtags"], getfilectx, "convert-repo", date,
411 411 extra)
412 412 node = self.repo.commitctx(ctx)
413 413 return nodemod.hex(node), nodemod.hex(tagparent)
414 414
415 415 def setfilemapmode(self, active):
416 416 self.filemapmode = active
417 417
418 418 def putbookmarks(self, updatedbookmark):
419 419 if not len(updatedbookmark):
420 420 return
421 421 wlock = lock = tr = None
422 422 try:
423 423 wlock = self.repo.wlock()
424 424 lock = self.repo.lock()
425 425 tr = self.repo.transaction('bookmark')
426 426 self.ui.status(_("updating bookmarks\n"))
427 427 destmarks = self.repo._bookmarks
428 428 changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark]))
429 429 for bookmark in updatedbookmark]
430 430 destmarks.applychanges(self.repo, tr, changes)
431 431 tr.close()
432 432 finally:
433 433 lockmod.release(lock, wlock, tr)
434 434
435 435 def hascommitfrommap(self, rev):
436 436 # the exact semantics of clonebranches is unclear so we can't say no
437 437 return rev in self.repo or self.clonebranches
438 438
439 439 def hascommitforsplicemap(self, rev):
440 440 if rev not in self.repo and self.clonebranches:
441 441 raise error.Abort(_('revision %s not found in destination '
442 442 'repository (lookups with clonebranches=true '
443 443 'are not implemented)') % rev)
444 444 return rev in self.repo
445 445
446 446 class mercurial_source(common.converter_source):
447 447 def __init__(self, ui, path, revs=None):
448 448 common.converter_source.__init__(self, ui, path, revs)
449 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
449 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
450 450 self.ignored = set()
451 451 self.saverev = ui.configbool('convert', 'hg.saverev', False)
452 452 try:
453 453 self.repo = hg.repository(self.ui, path)
454 454 # try to provoke an exception if this isn't really a hg
455 455 # repo, but some other bogus compatible-looking url
456 456 if not self.repo.local():
457 457 raise error.RepoError
458 458 except error.RepoError:
459 459 ui.traceback()
460 460 raise NoRepo(_("%s is not a local Mercurial repository") % path)
461 461 self.lastrev = None
462 462 self.lastctx = None
463 463 self._changescache = None, None
464 464 self.convertfp = None
465 465 # Restrict converted revisions to startrev descendants
466 466 startnode = ui.config('convert', 'hg.startrev')
467 467 hgrevs = ui.config('convert', 'hg.revs')
468 468 if hgrevs is None:
469 469 if startnode is not None:
470 470 try:
471 471 startnode = self.repo.lookup(startnode)
472 472 except error.RepoError:
473 473 raise error.Abort(_('%s is not a valid start revision')
474 474 % startnode)
475 475 startrev = self.repo.changelog.rev(startnode)
476 476 children = {startnode: 1}
477 477 for r in self.repo.changelog.descendants([startrev]):
478 478 children[self.repo.changelog.node(r)] = 1
479 479 self.keep = children.__contains__
480 480 else:
481 481 self.keep = util.always
482 482 if revs:
483 483 self._heads = [self.repo[r].node() for r in revs]
484 484 else:
485 485 self._heads = self.repo.heads()
486 486 else:
487 487 if revs or startnode is not None:
488 488 raise error.Abort(_('hg.revs cannot be combined with '
489 489 'hg.startrev or --rev'))
490 490 nodes = set()
491 491 parents = set()
492 492 for r in scmutil.revrange(self.repo, [hgrevs]):
493 493 ctx = self.repo[r]
494 494 nodes.add(ctx.node())
495 495 parents.update(p.node() for p in ctx.parents())
496 496 self.keep = nodes.__contains__
497 497 self._heads = nodes - parents
498 498
499 499 def _changectx(self, rev):
500 500 if self.lastrev != rev:
501 501 self.lastctx = self.repo[rev]
502 502 self.lastrev = rev
503 503 return self.lastctx
504 504
505 505 def _parents(self, ctx):
506 506 return [p for p in ctx.parents() if p and self.keep(p.node())]
507 507
508 508 def getheads(self):
509 509 return [nodemod.hex(h) for h in self._heads if self.keep(h)]
510 510
511 511 def getfile(self, name, rev):
512 512 try:
513 513 fctx = self._changectx(rev)[name]
514 514 return fctx.data(), fctx.flags()
515 515 except error.LookupError:
516 516 return None, None
517 517
518 518 def _changedfiles(self, ctx1, ctx2):
519 519 ma, r = [], []
520 520 maappend = ma.append
521 521 rappend = r.append
522 522 d = ctx1.manifest().diff(ctx2.manifest())
523 523 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
524 524 if node2 is None:
525 525 rappend(f)
526 526 else:
527 527 maappend(f)
528 528 return ma, r
529 529
530 530 def getchanges(self, rev, full):
531 531 ctx = self._changectx(rev)
532 532 parents = self._parents(ctx)
533 533 if full or not parents:
534 534 files = copyfiles = ctx.manifest()
535 535 if parents:
536 536 if self._changescache[0] == rev:
537 537 ma, r = self._changescache[1]
538 538 else:
539 539 ma, r = self._changedfiles(parents[0], ctx)
540 540 if not full:
541 541 files = ma + r
542 542 copyfiles = ma
543 543 # _getcopies() is also run for roots and before filtering so missing
544 544 # revlogs are detected early
545 545 copies = self._getcopies(ctx, parents, copyfiles)
546 546 cleanp2 = set()
547 547 if len(parents) == 2:
548 548 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
549 549 for f, value in d.iteritems():
550 550 if value is None:
551 551 cleanp2.add(f)
552 552 changes = [(f, rev) for f in files if f not in self.ignored]
553 553 changes.sort()
554 554 return changes, copies, cleanp2
555 555
556 556 def _getcopies(self, ctx, parents, files):
557 557 copies = {}
558 558 for name in files:
559 559 if name in self.ignored:
560 560 continue
561 561 try:
562 562 copysource, _copynode = ctx.filectx(name).renamed()
563 563 if copysource in self.ignored:
564 564 continue
565 565 # Ignore copy sources not in parent revisions
566 566 found = False
567 567 for p in parents:
568 568 if copysource in p:
569 569 found = True
570 570 break
571 571 if not found:
572 572 continue
573 573 copies[name] = copysource
574 574 except TypeError:
575 575 pass
576 576 except error.LookupError as e:
577 577 if not self.ignoreerrors:
578 578 raise
579 579 self.ignored.add(name)
580 580 self.ui.warn(_('ignoring: %s\n') % e)
581 581 return copies
582 582
583 583 def getcommit(self, rev):
584 584 ctx = self._changectx(rev)
585 585 _parents = self._parents(ctx)
586 586 parents = [p.hex() for p in _parents]
587 587 optparents = [p.hex() for p in ctx.parents() if p and p not in _parents]
588 588 crev = rev
589 589
590 590 return common.commit(author=ctx.user(),
591 591 date=util.datestr(ctx.date(),
592 592 '%Y-%m-%d %H:%M:%S %1%2'),
593 593 desc=ctx.description(),
594 594 rev=crev,
595 595 parents=parents,
596 596 optparents=optparents,
597 597 branch=ctx.branch(),
598 598 extra=ctx.extra(),
599 599 sortkey=ctx.rev(),
600 600 saverev=self.saverev,
601 601 phase=ctx.phase())
602 602
603 603 def gettags(self):
604 604 # This will get written to .hgtags, filter non global tags out.
605 605 tags = [t for t in self.repo.tagslist()
606 606 if self.repo.tagtype(t[0]) == 'global']
607 607 return dict([(name, nodemod.hex(node)) for name, node in tags
608 608 if self.keep(node)])
609 609
610 610 def getchangedfiles(self, rev, i):
611 611 ctx = self._changectx(rev)
612 612 parents = self._parents(ctx)
613 613 if not parents and i is None:
614 614 i = 0
615 615 ma, r = ctx.manifest().keys(), []
616 616 else:
617 617 i = i or 0
618 618 ma, r = self._changedfiles(parents[i], ctx)
619 619 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
620 620
621 621 if i == 0:
622 622 self._changescache = (rev, (ma, r))
623 623
624 624 return ma + r
625 625
626 626 def converted(self, rev, destrev):
627 627 if self.convertfp is None:
628 628 self.convertfp = open(self.repo.vfs.join('shamap'), 'a')
629 629 self.convertfp.write('%s %s\n' % (destrev, rev))
630 630 self.convertfp.flush()
631 631
632 632 def before(self):
633 633 self.ui.debug('run hg source pre-conversion action\n')
634 634
635 635 def after(self):
636 636 self.ui.debug('run hg source post-conversion action\n')
637 637
638 638 def hasnativeorder(self):
639 639 return True
640 640
641 641 def hasnativeclose(self):
642 642 return True
643 643
644 644 def lookuprev(self, rev):
645 645 try:
646 646 return nodemod.hex(self.repo.lookup(rev))
647 647 except (error.RepoError, error.LookupError):
648 648 return None
649 649
650 650 def getbookmarks(self):
651 651 return bookmarks.listbookmarks(self.repo)
652 652
653 653 def checkrevformat(self, revstr, mapname='splicemap'):
654 654 """ Mercurial, revision string is a 40 byte hex """
655 655 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now