##// END OF EJS Templates
configitems: register the 'convert.git.committeractions' config
Boris Feld -
r34156:a608b46a default
parent child Browse files
Show More
@@ -1,525 +1,528
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 configitem('convert', 'git.committeractions',
47 default=lambda: ['messagedifferent'],
48 )
46 49
47 50 # Commands definition was moved elsewhere to ease demandload job.
48 51
49 52 @command('convert',
50 53 [('', 'authors', '',
51 54 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
52 55 _('FILE')),
53 56 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
54 57 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
55 58 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
56 59 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
57 60 ('', 'filemap', '', _('remap file names using contents of file'),
58 61 _('FILE')),
59 62 ('', 'full', None,
60 63 _('apply filemap changes by converting all files again')),
61 64 ('', 'splicemap', '', _('splice synthesized history into place'),
62 65 _('FILE')),
63 66 ('', 'branchmap', '', _('change branch names while converting'),
64 67 _('FILE')),
65 68 ('', 'branchsort', None, _('try to sort changesets by branches')),
66 69 ('', 'datesort', None, _('try to sort changesets by date')),
67 70 ('', 'sourcesort', None, _('preserve source changesets order')),
68 71 ('', 'closesort', None, _('try to reorder closed revisions'))],
69 72 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
70 73 norepo=True)
71 74 def convert(ui, src, dest=None, revmapfile=None, **opts):
72 75 """convert a foreign SCM repository to a Mercurial one.
73 76
74 77 Accepted source formats [identifiers]:
75 78
76 79 - Mercurial [hg]
77 80 - CVS [cvs]
78 81 - Darcs [darcs]
79 82 - git [git]
80 83 - Subversion [svn]
81 84 - Monotone [mtn]
82 85 - GNU Arch [gnuarch]
83 86 - Bazaar [bzr]
84 87 - Perforce [p4]
85 88
86 89 Accepted destination formats [identifiers]:
87 90
88 91 - Mercurial [hg]
89 92 - Subversion [svn] (history on branches is not preserved)
90 93
91 94 If no revision is given, all revisions will be converted.
92 95 Otherwise, convert will only import up to the named revision
93 96 (given in a format understood by the source).
94 97
95 98 If no destination directory name is specified, it defaults to the
96 99 basename of the source with ``-hg`` appended. If the destination
97 100 repository doesn't exist, it will be created.
98 101
99 102 By default, all sources except Mercurial will use --branchsort.
100 103 Mercurial uses --sourcesort to preserve original revision numbers
101 104 order. Sort modes have the following effects:
102 105
103 106 --branchsort convert from parent to child revision when possible,
104 107 which means branches are usually converted one after
105 108 the other. It generates more compact repositories.
106 109
107 110 --datesort sort revisions by date. Converted repositories have
108 111 good-looking changelogs but are often an order of
109 112 magnitude larger than the same ones generated by
110 113 --branchsort.
111 114
112 115 --sourcesort try to preserve source revisions order, only
113 116 supported by Mercurial sources.
114 117
115 118 --closesort try to move closed revisions as close as possible
116 119 to parent branches, only supported by Mercurial
117 120 sources.
118 121
119 122 If ``REVMAP`` isn't given, it will be put in a default location
120 123 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
121 124 text file that maps each source commit ID to the destination ID
122 125 for that revision, like so::
123 126
124 127 <source ID> <destination ID>
125 128
126 129 If the file doesn't exist, it's automatically created. It's
127 130 updated on each commit copied, so :hg:`convert` can be interrupted
128 131 and can be run repeatedly to copy new commits.
129 132
130 133 The authormap is a simple text file that maps each source commit
131 134 author to a destination commit author. It is handy for source SCMs
132 135 that use unix logins to identify authors (e.g.: CVS). One line per
133 136 author mapping and the line format is::
134 137
135 138 source author = destination author
136 139
137 140 Empty lines and lines starting with a ``#`` are ignored.
138 141
139 142 The filemap is a file that allows filtering and remapping of files
140 143 and directories. Each line can contain one of the following
141 144 directives::
142 145
143 146 include path/to/file-or-dir
144 147
145 148 exclude path/to/file-or-dir
146 149
147 150 rename path/to/source path/to/destination
148 151
149 152 Comment lines start with ``#``. A specified path matches if it
150 153 equals the full relative name of a file or one of its parent
151 154 directories. The ``include`` or ``exclude`` directive with the
152 155 longest matching path applies, so line order does not matter.
153 156
154 157 The ``include`` directive causes a file, or all files under a
155 158 directory, to be included in the destination repository. The default
156 159 if there are no ``include`` statements is to include everything.
157 160 If there are any ``include`` statements, nothing else is included.
158 161 The ``exclude`` directive causes files or directories to
159 162 be omitted. The ``rename`` directive renames a file or directory if
160 163 it is converted. To rename from a subdirectory into the root of
161 164 the repository, use ``.`` as the path to rename to.
162 165
163 166 ``--full`` will make sure the converted changesets contain exactly
164 167 the right files with the right content. It will make a full
165 168 conversion of all files, not just the ones that have
166 169 changed. Files that already are correct will not be changed. This
167 170 can be used to apply filemap changes when converting
168 171 incrementally. This is currently only supported for Mercurial and
169 172 Subversion.
170 173
171 174 The splicemap is a file that allows insertion of synthetic
172 175 history, letting you specify the parents of a revision. This is
173 176 useful if you want to e.g. give a Subversion merge two parents, or
174 177 graft two disconnected series of history together. Each entry
175 178 contains a key, followed by a space, followed by one or two
176 179 comma-separated values::
177 180
178 181 key parent1, parent2
179 182
180 183 The key is the revision ID in the source
181 184 revision control system whose parents should be modified (same
182 185 format as a key in .hg/shamap). The values are the revision IDs
183 186 (in either the source or destination revision control system) that
184 187 should be used as the new parents for that node. For example, if
185 188 you have merged "release-1.0" into "trunk", then you should
186 189 specify the revision on "trunk" as the first parent and the one on
187 190 the "release-1.0" branch as the second.
188 191
189 192 The branchmap is a file that allows you to rename a branch when it is
190 193 being brought in from whatever external repository. When used in
191 194 conjunction with a splicemap, it allows for a powerful combination
192 195 to help fix even the most badly mismanaged repositories and turn them
193 196 into nicely structured Mercurial repositories. The branchmap contains
194 197 lines of the form::
195 198
196 199 original_branch_name new_branch_name
197 200
198 201 where "original_branch_name" is the name of the branch in the
199 202 source repository, and "new_branch_name" is the name of the branch
200 203 is the destination repository. No whitespace is allowed in the new
201 204 branch name. This can be used to (for instance) move code in one
202 205 repository from "default" to a named branch.
203 206
204 207 Mercurial Source
205 208 ################
206 209
207 210 The Mercurial source recognizes the following configuration
208 211 options, which you can set on the command line with ``--config``:
209 212
210 213 :convert.hg.ignoreerrors: ignore integrity errors when reading.
211 214 Use it to fix Mercurial repositories with missing revlogs, by
212 215 converting from and to Mercurial. Default is False.
213 216
214 217 :convert.hg.saverev: store original revision ID in changeset
215 218 (forces target IDs to change). It takes a boolean argument and
216 219 defaults to False.
217 220
218 221 :convert.hg.startrev: specify the initial Mercurial revision.
219 222 The default is 0.
220 223
221 224 :convert.hg.revs: revset specifying the source revisions to convert.
222 225
223 226 CVS Source
224 227 ##########
225 228
226 229 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
227 230 to indicate the starting point of what will be converted. Direct
228 231 access to the repository files is not needed, unless of course the
229 232 repository is ``:local:``. The conversion uses the top level
230 233 directory in the sandbox to find the CVS repository, and then uses
231 234 CVS rlog commands to find files to convert. This means that unless
232 235 a filemap is given, all files under the starting directory will be
233 236 converted, and that any directory reorganization in the CVS
234 237 sandbox is ignored.
235 238
236 239 The following options can be used with ``--config``:
237 240
238 241 :convert.cvsps.cache: Set to False to disable remote log caching,
239 242 for testing and debugging purposes. Default is True.
240 243
241 244 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
242 245 allowed between commits with identical user and log message in
243 246 a single changeset. When very large files were checked in as
244 247 part of a changeset then the default may not be long enough.
245 248 The default is 60.
246 249
247 250 :convert.cvsps.logencoding: Specify encoding name to be used for
248 251 transcoding CVS log messages. Multiple encoding names can be
249 252 specified as a list (see :hg:`help config.Syntax`), but only
250 253 the first acceptable encoding in the list is used per CVS log
251 254 entries. This transcoding is executed before cvslog hook below.
252 255
253 256 :convert.cvsps.mergeto: Specify a regular expression to which
254 257 commit log messages are matched. If a match occurs, then the
255 258 conversion process will insert a dummy revision merging the
256 259 branch on which this log message occurs to the branch
257 260 indicated in the regex. Default is ``{{mergetobranch
258 261 ([-\\w]+)}}``
259 262
260 263 :convert.cvsps.mergefrom: Specify a regular expression to which
261 264 commit log messages are matched. If a match occurs, then the
262 265 conversion process will add the most recent revision on the
263 266 branch indicated in the regex as the second parent of the
264 267 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
265 268
266 269 :convert.localtimezone: use local time (as determined by the TZ
267 270 environment variable) for changeset date/times. The default
268 271 is False (use UTC).
269 272
270 273 :hooks.cvslog: Specify a Python function to be called at the end of
271 274 gathering the CVS log. The function is passed a list with the
272 275 log entries, and can modify the entries in-place, or add or
273 276 delete them.
274 277
275 278 :hooks.cvschangesets: Specify a Python function to be called after
276 279 the changesets are calculated from the CVS log. The
277 280 function is passed a list with the changeset entries, and can
278 281 modify the changesets in-place, or add or delete them.
279 282
280 283 An additional "debugcvsps" Mercurial command allows the builtin
281 284 changeset merging code to be run without doing a conversion. Its
282 285 parameters and output are similar to that of cvsps 2.1. Please see
283 286 the command help for more details.
284 287
285 288 Subversion Source
286 289 #################
287 290
288 291 Subversion source detects classical trunk/branches/tags layouts.
289 292 By default, the supplied ``svn://repo/path/`` source URL is
290 293 converted as a single branch. If ``svn://repo/path/trunk`` exists
291 294 it replaces the default branch. If ``svn://repo/path/branches``
292 295 exists, its subdirectories are listed as possible branches. If
293 296 ``svn://repo/path/tags`` exists, it is looked for tags referencing
294 297 converted branches. Default ``trunk``, ``branches`` and ``tags``
295 298 values can be overridden with following options. Set them to paths
296 299 relative to the source URL, or leave them blank to disable auto
297 300 detection.
298 301
299 302 The following options can be set with ``--config``:
300 303
301 304 :convert.svn.branches: specify the directory containing branches.
302 305 The default is ``branches``.
303 306
304 307 :convert.svn.tags: specify the directory containing tags. The
305 308 default is ``tags``.
306 309
307 310 :convert.svn.trunk: specify the name of the trunk branch. The
308 311 default is ``trunk``.
309 312
310 313 :convert.localtimezone: use local time (as determined by the TZ
311 314 environment variable) for changeset date/times. The default
312 315 is False (use UTC).
313 316
314 317 Source history can be retrieved starting at a specific revision,
315 318 instead of being integrally converted. Only single branch
316 319 conversions are supported.
317 320
318 321 :convert.svn.startrev: specify start Subversion revision number.
319 322 The default is 0.
320 323
321 324 Git Source
322 325 ##########
323 326
324 327 The Git importer converts commits from all reachable branches (refs
325 328 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
326 329 Branches are converted to bookmarks with the same name, with the
327 330 leading 'refs/heads' stripped. Git submodules are converted to Git
328 331 subrepos in Mercurial.
329 332
330 333 The following options can be set with ``--config``:
331 334
332 335 :convert.git.similarity: specify how similar files modified in a
333 336 commit must be to be imported as renames or copies, as a
334 337 percentage between ``0`` (disabled) and ``100`` (files must be
335 338 identical). For example, ``90`` means that a delete/add pair will
336 339 be imported as a rename if more than 90% of the file hasn't
337 340 changed. The default is ``50``.
338 341
339 342 :convert.git.findcopiesharder: while detecting copies, look at all
340 343 files in the working copy instead of just changed ones. This
341 344 is very expensive for large projects, and is only effective when
342 345 ``convert.git.similarity`` is greater than 0. The default is False.
343 346
344 347 :convert.git.renamelimit: perform rename and copy detection up to this
345 348 many changed files in a commit. Increasing this will make rename
346 349 and copy detection more accurate but will significantly slow down
347 350 computation on large projects. The option is only relevant if
348 351 ``convert.git.similarity`` is greater than 0. The default is
349 352 ``400``.
350 353
351 354 :convert.git.committeractions: list of actions to take when processing
352 355 author and committer values.
353 356
354 357 Git commits have separate author (who wrote the commit) and committer
355 358 (who applied the commit) fields. Not all destinations support separate
356 359 author and committer fields (including Mercurial). This config option
357 360 controls what to do with these author and committer fields during
358 361 conversion.
359 362
360 363 A value of ``messagedifferent`` will append a ``committer: ...``
361 364 line to the commit message if the Git committer is different from the
362 365 author. The prefix of that line can be specified using the syntax
363 366 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
364 367 When a prefix is specified, a space will always be inserted between the
365 368 prefix and the value.
366 369
367 370 ``messagealways`` behaves like ``messagedifferent`` except it will
368 371 always result in a ``committer: ...`` line being appended to the commit
369 372 message. This value is mutually exclusive with ``messagedifferent``.
370 373
371 374 ``dropcommitter`` will remove references to the committer. Only
372 375 references to the author will remain. Actions that add references
373 376 to the committer will have no effect when this is set.
374 377
375 378 ``replaceauthor`` will replace the value of the author field with
376 379 the committer. Other actions that add references to the committer
377 380 will still take effect when this is set.
378 381
379 382 The default is ``messagedifferent``.
380 383
381 384 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
382 385 the destination. Some Git repositories store extra metadata in commits.
383 386 By default, this non-default metadata will be lost during conversion.
384 387 Setting this config option can retain that metadata. Some built-in
385 388 keys such as ``parent`` and ``branch`` are not allowed to be copied.
386 389
387 390 :convert.git.remoteprefix: remote refs are converted as bookmarks with
388 391 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
389 392 is 'remote'.
390 393
391 394 :convert.git.saverev: whether to store the original Git commit ID in the
392 395 metadata of the destination commit. The default is True.
393 396
394 397 :convert.git.skipsubmodules: does not convert root level .gitmodules files
395 398 or files with 160000 mode indicating a submodule. Default is False.
396 399
397 400 Perforce Source
398 401 ###############
399 402
400 403 The Perforce (P4) importer can be given a p4 depot path or a
401 404 client specification as source. It will convert all files in the
402 405 source to a flat Mercurial repository, ignoring labels, branches
403 406 and integrations. Note that when a depot path is given you then
404 407 usually should specify a target directory, because otherwise the
405 408 target may be named ``...-hg``.
406 409
407 410 The following options can be set with ``--config``:
408 411
409 412 :convert.p4.encoding: specify the encoding to use when decoding standard
410 413 output of the Perforce command line tool. The default is default system
411 414 encoding.
412 415
413 416 :convert.p4.startrev: specify initial Perforce revision (a
414 417 Perforce changelist number).
415 418
416 419 Mercurial Destination
417 420 #####################
418 421
419 422 The Mercurial destination will recognize Mercurial subrepositories in the
420 423 destination directory, and update the .hgsubstate file automatically if the
421 424 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
422 425 Converting a repository with subrepositories requires converting a single
423 426 repository at a time, from the bottom up.
424 427
425 428 .. container:: verbose
426 429
427 430 An example showing how to convert a repository with subrepositories::
428 431
429 432 # so convert knows the type when it sees a non empty destination
430 433 $ hg init converted
431 434
432 435 $ hg convert orig/sub1 converted/sub1
433 436 $ hg convert orig/sub2 converted/sub2
434 437 $ hg convert orig converted
435 438
436 439 The following options are supported:
437 440
438 441 :convert.hg.clonebranches: dispatch source branches in separate
439 442 clones. The default is False.
440 443
441 444 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
442 445 ``default``.
443 446
444 447 :convert.hg.usebranchnames: preserve branch names. The default is
445 448 True.
446 449
447 450 :convert.hg.sourcename: records the given string as a 'convert_source' extra
448 451 value on each commit made in the target repository. The default is None.
449 452
450 453 All Destinations
451 454 ################
452 455
453 456 All destination types accept the following options:
454 457
455 458 :convert.skiptags: does not convert tags from the source repo to the target
456 459 repo. The default is False.
457 460 """
458 461 return convcmd.convert(ui, src, dest, revmapfile, **opts)
459 462
460 463 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
461 464 def debugsvnlog(ui, **opts):
462 465 return subversion.debugsvnlog(ui, **opts)
463 466
464 467 @command('debugcvsps',
465 468 [
466 469 # Main options shared with cvsps-2.1
467 470 ('b', 'branches', [], _('only return changes on specified branches')),
468 471 ('p', 'prefix', '', _('prefix to remove from file names')),
469 472 ('r', 'revisions', [],
470 473 _('only return changes after or between specified tags')),
471 474 ('u', 'update-cache', None, _("update cvs log cache")),
472 475 ('x', 'new-cache', None, _("create new cvs log cache")),
473 476 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
474 477 ('', 'root', '', _('specify cvsroot')),
475 478 # Options specific to builtin cvsps
476 479 ('', 'parents', '', _('show parent changesets')),
477 480 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
478 481 # Options that are ignored for compatibility with cvsps-2.1
479 482 ('A', 'cvs-direct', None, _('ignored for compatibility')),
480 483 ],
481 484 _('hg debugcvsps [OPTION]... [PATH]...'),
482 485 norepo=True)
483 486 def debugcvsps(ui, *args, **opts):
484 487 '''create changeset information from CVS
485 488
486 489 This command is intended as a debugging tool for the CVS to
487 490 Mercurial converter, and can be used as a direct replacement for
488 491 cvsps.
489 492
490 493 Hg debugcvsps reads the CVS rlog for current directory (or any
491 494 named directory) in the CVS repository, and converts the log to a
492 495 series of changesets based on matching commit log entries and
493 496 dates.'''
494 497 return cvsps.debugcvsps(ui, *args, **opts)
495 498
496 499 def kwconverted(ctx, name):
497 500 rev = ctx.extra().get('convert_revision', '')
498 501 if rev.startswith('svn:'):
499 502 if name == 'svnrev':
500 503 return str(subversion.revsplit(rev)[2])
501 504 elif name == 'svnpath':
502 505 return subversion.revsplit(rev)[1]
503 506 elif name == 'svnuuid':
504 507 return subversion.revsplit(rev)[0]
505 508 return rev
506 509
507 510 templatekeyword = registrar.templatekeyword()
508 511
509 512 @templatekeyword('svnrev')
510 513 def kwsvnrev(repo, ctx, **args):
511 514 """String. Converted subversion revision number."""
512 515 return kwconverted(ctx, 'svnrev')
513 516
514 517 @templatekeyword('svnpath')
515 518 def kwsvnpath(repo, ctx, **args):
516 519 """String. Converted subversion revision project path."""
517 520 return kwconverted(ctx, 'svnpath')
518 521
519 522 @templatekeyword('svnuuid')
520 523 def kwsvnuuid(repo, ctx, **args):
521 524 """String. Converted subversion revision repository identifier."""
522 525 return kwconverted(ctx, 'svnuuid')
523 526
524 527 # tell hggettext to extract docstrings from these functions:
525 528 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,480 +1,479
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7 from __future__ import absolute_import
8 8
9 9 import os
10 10
11 11 from mercurial.i18n import _
12 12 from mercurial import (
13 13 config,
14 14 error,
15 15 node as nodemod,
16 16 )
17 17
18 18 from . import (
19 19 common,
20 20 )
21 21
22 22 class submodule(object):
23 23 def __init__(self, path, node, url):
24 24 self.path = path
25 25 self.node = node
26 26 self.url = url
27 27
28 28 def hgsub(self):
29 29 return "%s = [git]%s" % (self.path, self.url)
30 30
31 31 def hgsubstate(self):
32 32 return "%s %s" % (self.node, self.path)
33 33
34 34 # Keys in extra fields that should not be copied if the user requests.
35 35 bannedextrakeys = {
36 36 # Git commit object built-ins.
37 37 'tree',
38 38 'parent',
39 39 'author',
40 40 'committer',
41 41 # Mercurial built-ins.
42 42 'branch',
43 43 'close',
44 44 }
45 45
46 46 class convert_git(common.converter_source, common.commandline):
47 47 # Windows does not support GIT_DIR= construct while other systems
48 48 # cannot remove environment variable. Just assume none have
49 49 # both issues.
50 50
51 51 def _gitcmd(self, cmd, *args, **kwargs):
52 52 return cmd('--git-dir=%s' % self.path, *args, **kwargs)
53 53
54 54 def gitrun0(self, *args, **kwargs):
55 55 return self._gitcmd(self.run0, *args, **kwargs)
56 56
57 57 def gitrun(self, *args, **kwargs):
58 58 return self._gitcmd(self.run, *args, **kwargs)
59 59
60 60 def gitrunlines0(self, *args, **kwargs):
61 61 return self._gitcmd(self.runlines0, *args, **kwargs)
62 62
63 63 def gitrunlines(self, *args, **kwargs):
64 64 return self._gitcmd(self.runlines, *args, **kwargs)
65 65
66 66 def gitpipe(self, *args, **kwargs):
67 67 return self._gitcmd(self._run3, *args, **kwargs)
68 68
69 69 def __init__(self, ui, path, revs=None):
70 70 super(convert_git, self).__init__(ui, path, revs=revs)
71 71 common.commandline.__init__(self, ui, 'git')
72 72
73 73 # Pass an absolute path to git to prevent from ever being interpreted
74 74 # as a URL
75 75 path = os.path.abspath(path)
76 76
77 77 if os.path.isdir(path + "/.git"):
78 78 path += "/.git"
79 79 if not os.path.exists(path + "/objects"):
80 80 raise common.NoRepo(_("%s does not look like a Git repository") %
81 81 path)
82 82
83 83 # The default value (50) is based on the default for 'git diff'.
84 84 similarity = ui.configint('convert', 'git.similarity', default=50)
85 85 if similarity < 0 or similarity > 100:
86 86 raise error.Abort(_('similarity must be between 0 and 100'))
87 87 if similarity > 0:
88 88 self.simopt = ['-C%d%%' % similarity]
89 89 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
90 90 False)
91 91 if findcopiesharder:
92 92 self.simopt.append('--find-copies-harder')
93 93
94 94 renamelimit = ui.configint('convert', 'git.renamelimit',
95 95 default=400)
96 96 self.simopt.append('-l%d' % renamelimit)
97 97 else:
98 98 self.simopt = []
99 99
100 100 common.checktool('git', 'git')
101 101
102 102 self.path = path
103 103 self.submodules = []
104 104
105 105 self.catfilepipe = self.gitpipe('cat-file', '--batch')
106 106
107 107 self.copyextrakeys = self.ui.configlist('convert', 'git.extrakeys')
108 108 banned = set(self.copyextrakeys) & bannedextrakeys
109 109 if banned:
110 110 raise error.Abort(_('copying of extra key is forbidden: %s') %
111 111 _(', ').join(sorted(banned)))
112 112
113 committeractions = self.ui.configlist('convert', 'git.committeractions',
114 'messagedifferent')
113 committeractions = self.ui.configlist('convert', 'git.committeractions')
115 114
116 115 messagedifferent = None
117 116 messagealways = None
118 117 for a in committeractions:
119 118 if a.startswith(('messagedifferent', 'messagealways')):
120 119 k = a
121 120 v = None
122 121 if '=' in a:
123 122 k, v = a.split('=', 1)
124 123
125 124 if k == 'messagedifferent':
126 125 messagedifferent = v or 'committer:'
127 126 elif k == 'messagealways':
128 127 messagealways = v or 'committer:'
129 128
130 129 if messagedifferent and messagealways:
131 130 raise error.Abort(_('committeractions cannot define both '
132 131 'messagedifferent and messagealways'))
133 132
134 133 dropcommitter = 'dropcommitter' in committeractions
135 134 replaceauthor = 'replaceauthor' in committeractions
136 135
137 136 if dropcommitter and replaceauthor:
138 137 raise error.Abort(_('committeractions cannot define both '
139 138 'dropcommitter and replaceauthor'))
140 139
141 140 if dropcommitter and messagealways:
142 141 raise error.Abort(_('committeractions cannot define both '
143 142 'dropcommitter and messagealways'))
144 143
145 144 if not messagedifferent and not messagealways:
146 145 messagedifferent = 'committer:'
147 146
148 147 self.committeractions = {
149 148 'dropcommitter': dropcommitter,
150 149 'replaceauthor': replaceauthor,
151 150 'messagedifferent': messagedifferent,
152 151 'messagealways': messagealways,
153 152 }
154 153
155 154 def after(self):
156 155 for f in self.catfilepipe:
157 156 f.close()
158 157
159 158 def getheads(self):
160 159 if not self.revs:
161 160 output, status = self.gitrun('rev-parse', '--branches', '--remotes')
162 161 heads = output.splitlines()
163 162 if status:
164 163 raise error.Abort(_('cannot retrieve git heads'))
165 164 else:
166 165 heads = []
167 166 for rev in self.revs:
168 167 rawhead, ret = self.gitrun('rev-parse', '--verify', rev)
169 168 heads.append(rawhead[:-1])
170 169 if ret:
171 170 raise error.Abort(_('cannot retrieve git head "%s"') % rev)
172 171 return heads
173 172
174 173 def catfile(self, rev, type):
175 174 if rev == nodemod.nullhex:
176 175 raise IOError
177 176 self.catfilepipe[0].write(rev+'\n')
178 177 self.catfilepipe[0].flush()
179 178 info = self.catfilepipe[1].readline().split()
180 179 if info[1] != type:
181 180 raise error.Abort(_('cannot read %r object at %s') % (type, rev))
182 181 size = int(info[2])
183 182 data = self.catfilepipe[1].read(size)
184 183 if len(data) < size:
185 184 raise error.Abort(_('cannot read %r object at %s: unexpected size')
186 185 % (type, rev))
187 186 # read the trailing newline
188 187 self.catfilepipe[1].read(1)
189 188 return data
190 189
191 190 def getfile(self, name, rev):
192 191 if rev == nodemod.nullhex:
193 192 return None, None
194 193 if name == '.hgsub':
195 194 data = '\n'.join([m.hgsub() for m in self.submoditer()])
196 195 mode = ''
197 196 elif name == '.hgsubstate':
198 197 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
199 198 mode = ''
200 199 else:
201 200 data = self.catfile(rev, "blob")
202 201 mode = self.modecache[(name, rev)]
203 202 return data, mode
204 203
205 204 def submoditer(self):
206 205 null = nodemod.nullhex
207 206 for m in sorted(self.submodules, key=lambda p: p.path):
208 207 if m.node != null:
209 208 yield m
210 209
211 210 def parsegitmodules(self, content):
212 211 """Parse the formatted .gitmodules file, example file format:
213 212 [submodule "sub"]\n
214 213 \tpath = sub\n
215 214 \turl = git://giturl\n
216 215 """
217 216 self.submodules = []
218 217 c = config.config()
219 218 # Each item in .gitmodules starts with whitespace that cant be parsed
220 219 c.parse('.gitmodules', '\n'.join(line.strip() for line in
221 220 content.split('\n')))
222 221 for sec in c.sections():
223 222 s = c[sec]
224 223 if 'url' in s and 'path' in s:
225 224 self.submodules.append(submodule(s['path'], '', s['url']))
226 225
227 226 def retrievegitmodules(self, version):
228 227 modules, ret = self.gitrun('show', '%s:%s' % (version, '.gitmodules'))
229 228 if ret:
230 229 # This can happen if a file is in the repo that has permissions
231 230 # 160000, but there is no .gitmodules file.
232 231 self.ui.warn(_("warning: cannot read submodules config file in "
233 232 "%s\n") % version)
234 233 return
235 234
236 235 try:
237 236 self.parsegitmodules(modules)
238 237 except error.ParseError:
239 238 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
240 239 % version)
241 240 return
242 241
243 242 for m in self.submodules:
244 243 node, ret = self.gitrun('rev-parse', '%s:%s' % (version, m.path))
245 244 if ret:
246 245 continue
247 246 m.node = node.strip()
248 247
249 248 def getchanges(self, version, full):
250 249 if full:
251 250 raise error.Abort(_("convert from git does not support --full"))
252 251 self.modecache = {}
253 252 cmd = ['diff-tree','-z', '--root', '-m', '-r'] + self.simopt + [version]
254 253 output, status = self.gitrun(*cmd)
255 254 if status:
256 255 raise error.Abort(_('cannot read changes in %s') % version)
257 256 changes = []
258 257 copies = {}
259 258 seen = set()
260 259 entry = None
261 260 subexists = [False]
262 261 subdeleted = [False]
263 262 difftree = output.split('\x00')
264 263 lcount = len(difftree)
265 264 i = 0
266 265
267 266 skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
268 267 False)
269 268 def add(entry, f, isdest):
270 269 seen.add(f)
271 270 h = entry[3]
272 271 p = (entry[1] == "100755")
273 272 s = (entry[1] == "120000")
274 273 renamesource = (not isdest and entry[4][0] == 'R')
275 274
276 275 if f == '.gitmodules':
277 276 if skipsubmodules:
278 277 return
279 278
280 279 subexists[0] = True
281 280 if entry[4] == 'D' or renamesource:
282 281 subdeleted[0] = True
283 282 changes.append(('.hgsub', nodemod.nullhex))
284 283 else:
285 284 changes.append(('.hgsub', ''))
286 285 elif entry[1] == '160000' or entry[0] == ':160000':
287 286 if not skipsubmodules:
288 287 subexists[0] = True
289 288 else:
290 289 if renamesource:
291 290 h = nodemod.nullhex
292 291 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
293 292 changes.append((f, h))
294 293
295 294 while i < lcount:
296 295 l = difftree[i]
297 296 i += 1
298 297 if not entry:
299 298 if not l.startswith(':'):
300 299 continue
301 300 entry = l.split()
302 301 continue
303 302 f = l
304 303 if entry[4][0] == 'C':
305 304 copysrc = f
306 305 copydest = difftree[i]
307 306 i += 1
308 307 f = copydest
309 308 copies[copydest] = copysrc
310 309 if f not in seen:
311 310 add(entry, f, False)
312 311 # A file can be copied multiple times, or modified and copied
313 312 # simultaneously. So f can be repeated even if fdest isn't.
314 313 if entry[4][0] == 'R':
315 314 # rename: next line is the destination
316 315 fdest = difftree[i]
317 316 i += 1
318 317 if fdest not in seen:
319 318 add(entry, fdest, True)
320 319 # .gitmodules isn't imported at all, so it being copied to
321 320 # and fro doesn't really make sense
322 321 if f != '.gitmodules' and fdest != '.gitmodules':
323 322 copies[fdest] = f
324 323 entry = None
325 324
326 325 if subexists[0]:
327 326 if subdeleted[0]:
328 327 changes.append(('.hgsubstate', nodemod.nullhex))
329 328 else:
330 329 self.retrievegitmodules(version)
331 330 changes.append(('.hgsubstate', ''))
332 331 return (changes, copies, set())
333 332
334 333 def getcommit(self, version):
335 334 c = self.catfile(version, "commit") # read the commit hash
336 335 end = c.find("\n\n")
337 336 message = c[end + 2:]
338 337 message = self.recode(message)
339 338 l = c[:end].splitlines()
340 339 parents = []
341 340 author = committer = None
342 341 extra = {}
343 342 for e in l[1:]:
344 343 n, v = e.split(" ", 1)
345 344 if n == "author":
346 345 p = v.split()
347 346 tm, tz = p[-2:]
348 347 author = " ".join(p[:-2])
349 348 if author[0] == "<": author = author[1:-1]
350 349 author = self.recode(author)
351 350 if n == "committer":
352 351 p = v.split()
353 352 tm, tz = p[-2:]
354 353 committer = " ".join(p[:-2])
355 354 if committer[0] == "<": committer = committer[1:-1]
356 355 committer = self.recode(committer)
357 356 if n == "parent":
358 357 parents.append(v)
359 358 if n in self.copyextrakeys:
360 359 extra[n] = v
361 360
362 361 if self.committeractions['dropcommitter']:
363 362 committer = None
364 363 elif self.committeractions['replaceauthor']:
365 364 author = committer
366 365
367 366 if committer:
368 367 messagealways = self.committeractions['messagealways']
369 368 messagedifferent = self.committeractions['messagedifferent']
370 369 if messagealways:
371 370 message += '\n%s %s\n' % (messagealways, committer)
372 371 elif messagedifferent and author != committer:
373 372 message += '\n%s %s\n' % (messagedifferent, committer)
374 373
375 374 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
376 375 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
377 376 date = tm + " " + str(tz)
378 377 saverev = self.ui.configbool('convert', 'git.saverev', True)
379 378
380 379 c = common.commit(parents=parents, date=date, author=author,
381 380 desc=message,
382 381 rev=version,
383 382 extra=extra,
384 383 saverev=saverev)
385 384 return c
386 385
387 386 def numcommits(self):
388 387 output, ret = self.gitrunlines('rev-list', '--all')
389 388 if ret:
390 389 raise error.Abort(_('cannot retrieve number of commits in %s') \
391 390 % self.path)
392 391 return len(output)
393 392
394 393 def gettags(self):
395 394 tags = {}
396 395 alltags = {}
397 396 output, status = self.gitrunlines('ls-remote', '--tags', self.path)
398 397
399 398 if status:
400 399 raise error.Abort(_('cannot read tags from %s') % self.path)
401 400 prefix = 'refs/tags/'
402 401
403 402 # Build complete list of tags, both annotated and bare ones
404 403 for line in output:
405 404 line = line.strip()
406 405 if line.startswith("error:") or line.startswith("fatal:"):
407 406 raise error.Abort(_('cannot read tags from %s') % self.path)
408 407 node, tag = line.split(None, 1)
409 408 if not tag.startswith(prefix):
410 409 continue
411 410 alltags[tag[len(prefix):]] = node
412 411
413 412 # Filter out tag objects for annotated tag refs
414 413 for tag in alltags:
415 414 if tag.endswith('^{}'):
416 415 tags[tag[:-3]] = alltags[tag]
417 416 else:
418 417 if tag + '^{}' in alltags:
419 418 continue
420 419 else:
421 420 tags[tag] = alltags[tag]
422 421
423 422 return tags
424 423
425 424 def getchangedfiles(self, version, i):
426 425 changes = []
427 426 if i is None:
428 427 output, status = self.gitrunlines('diff-tree', '--root', '-m',
429 428 '-r', version)
430 429 if status:
431 430 raise error.Abort(_('cannot read changes in %s') % version)
432 431 for l in output:
433 432 if "\t" not in l:
434 433 continue
435 434 m, f = l[:-1].split("\t")
436 435 changes.append(f)
437 436 else:
438 437 output, status = self.gitrunlines('diff-tree', '--name-only',
439 438 '--root', '-r', version,
440 439 '%s^%s' % (version, i + 1), '--')
441 440 if status:
442 441 raise error.Abort(_('cannot read changes in %s') % version)
443 442 changes = [f.rstrip('\n') for f in output]
444 443
445 444 return changes
446 445
447 446 def getbookmarks(self):
448 447 bookmarks = {}
449 448
450 449 # Handle local and remote branches
451 450 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
452 451 reftypes = [
453 452 # (git prefix, hg prefix)
454 453 ('refs/remotes/origin/', remoteprefix + '/'),
455 454 ('refs/heads/', '')
456 455 ]
457 456
458 457 exclude = {
459 458 'refs/remotes/origin/HEAD',
460 459 }
461 460
462 461 try:
463 462 output, status = self.gitrunlines('show-ref')
464 463 for line in output:
465 464 line = line.strip()
466 465 rev, name = line.split(None, 1)
467 466 # Process each type of branch
468 467 for gitprefix, hgprefix in reftypes:
469 468 if not name.startswith(gitprefix) or name in exclude:
470 469 continue
471 470 name = '%s%s' % (hgprefix, name[len(gitprefix):])
472 471 bookmarks[name] = rev
473 472 except Exception:
474 473 pass
475 474
476 475 return bookmarks
477 476
478 477 def checkrevformat(self, revstr, mapname='splicemap'):
479 478 """ git revision string is a 40 byte hex """
480 479 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now