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