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