##// END OF EJS Templates
configitems: register the 'convert.p4.encoding' config
Boris Feld -
r34504:0d5a1175 default
parent child Browse files
Show More
@@ -1,591 +1,594 b''
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',
52 configitem('convert', 'git.findcopiesharder',
53 default=False,
53 default=False,
54 )
54 )
55 configitem('convert', 'git.remoteprefix',
55 configitem('convert', 'git.remoteprefix',
56 default='remote',
56 default='remote',
57 )
57 )
58 configitem('convert', 'git.renamelimit',
58 configitem('convert', 'git.renamelimit',
59 default=400,
59 default=400,
60 )
60 )
61 configitem('convert', 'git.saverev',
61 configitem('convert', 'git.saverev',
62 default=True,
62 default=True,
63 )
63 )
64 configitem('convert', 'git.similarity',
64 configitem('convert', 'git.similarity',
65 default=50,
65 default=50,
66 )
66 )
67 configitem('convert', 'git.skipsubmodules',
67 configitem('convert', 'git.skipsubmodules',
68 default=False,
68 default=False,
69 )
69 )
70 configitem('convert', 'hg.clonebranches',
70 configitem('convert', 'hg.clonebranches',
71 default=False,
71 default=False,
72 )
72 )
73 configitem('convert', 'hg.ignoreerrors',
73 configitem('convert', 'hg.ignoreerrors',
74 default=False,
74 default=False,
75 )
75 )
76 configitem('convert', 'hg.revs',
76 configitem('convert', 'hg.revs',
77 default=None,
77 default=None,
78 )
78 )
79 configitem('convert', 'hg.saverev',
79 configitem('convert', 'hg.saverev',
80 default=False,
80 default=False,
81 )
81 )
82 configitem('convert', 'hg.sourcename',
82 configitem('convert', 'hg.sourcename',
83 default=None,
83 default=None,
84 )
84 )
85 configitem('convert', 'hg.startrev',
85 configitem('convert', 'hg.startrev',
86 default=None,
86 default=None,
87 )
87 )
88 configitem('convert', 'hg.tagsbranch',
88 configitem('convert', 'hg.tagsbranch',
89 default='default',
89 default='default',
90 )
90 )
91 configitem('convert', 'hg.usebranchnames',
91 configitem('convert', 'hg.usebranchnames',
92 default=True,
92 default=True,
93 )
93 )
94 configitem('convert', 'ignoreancestorcheck',
94 configitem('convert', 'ignoreancestorcheck',
95 default=False,
95 default=False,
96 )
96 )
97 configitem('convert', 'localtimezone',
97 configitem('convert', 'localtimezone',
98 default=False,
98 default=False,
99 )
99 )
100 configitem('convert', 'p4.encoding',
101 default=lambda: convcmd.orig_encoding,
102 )
100 configitem('convert', 'p4.startrev',
103 configitem('convert', 'p4.startrev',
101 default=0,
104 default=0,
102 )
105 )
103 configitem('convert', 'skiptags',
106 configitem('convert', 'skiptags',
104 default=False,
107 default=False,
105 )
108 )
106 configitem('convert', 'svn.debugsvnlog',
109 configitem('convert', 'svn.debugsvnlog',
107 default=True,
110 default=True,
108 )
111 )
109 configitem('convert', 'svn.startrev',
112 configitem('convert', 'svn.startrev',
110 default=0,
113 default=0,
111 )
114 )
112
115
113 # Commands definition was moved elsewhere to ease demandload job.
116 # Commands definition was moved elsewhere to ease demandload job.
114
117
115 @command('convert',
118 @command('convert',
116 [('', 'authors', '',
119 [('', 'authors', '',
117 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
120 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
118 _('FILE')),
121 _('FILE')),
119 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
122 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
120 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
123 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
121 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
124 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
122 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
125 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
123 ('', 'filemap', '', _('remap file names using contents of file'),
126 ('', 'filemap', '', _('remap file names using contents of file'),
124 _('FILE')),
127 _('FILE')),
125 ('', 'full', None,
128 ('', 'full', None,
126 _('apply filemap changes by converting all files again')),
129 _('apply filemap changes by converting all files again')),
127 ('', 'splicemap', '', _('splice synthesized history into place'),
130 ('', 'splicemap', '', _('splice synthesized history into place'),
128 _('FILE')),
131 _('FILE')),
129 ('', 'branchmap', '', _('change branch names while converting'),
132 ('', 'branchmap', '', _('change branch names while converting'),
130 _('FILE')),
133 _('FILE')),
131 ('', 'branchsort', None, _('try to sort changesets by branches')),
134 ('', 'branchsort', None, _('try to sort changesets by branches')),
132 ('', 'datesort', None, _('try to sort changesets by date')),
135 ('', 'datesort', None, _('try to sort changesets by date')),
133 ('', 'sourcesort', None, _('preserve source changesets order')),
136 ('', 'sourcesort', None, _('preserve source changesets order')),
134 ('', 'closesort', None, _('try to reorder closed revisions'))],
137 ('', 'closesort', None, _('try to reorder closed revisions'))],
135 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
138 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
136 norepo=True)
139 norepo=True)
137 def convert(ui, src, dest=None, revmapfile=None, **opts):
140 def convert(ui, src, dest=None, revmapfile=None, **opts):
138 """convert a foreign SCM repository to a Mercurial one.
141 """convert a foreign SCM repository to a Mercurial one.
139
142
140 Accepted source formats [identifiers]:
143 Accepted source formats [identifiers]:
141
144
142 - Mercurial [hg]
145 - Mercurial [hg]
143 - CVS [cvs]
146 - CVS [cvs]
144 - Darcs [darcs]
147 - Darcs [darcs]
145 - git [git]
148 - git [git]
146 - Subversion [svn]
149 - Subversion [svn]
147 - Monotone [mtn]
150 - Monotone [mtn]
148 - GNU Arch [gnuarch]
151 - GNU Arch [gnuarch]
149 - Bazaar [bzr]
152 - Bazaar [bzr]
150 - Perforce [p4]
153 - Perforce [p4]
151
154
152 Accepted destination formats [identifiers]:
155 Accepted destination formats [identifiers]:
153
156
154 - Mercurial [hg]
157 - Mercurial [hg]
155 - Subversion [svn] (history on branches is not preserved)
158 - Subversion [svn] (history on branches is not preserved)
156
159
157 If no revision is given, all revisions will be converted.
160 If no revision is given, all revisions will be converted.
158 Otherwise, convert will only import up to the named revision
161 Otherwise, convert will only import up to the named revision
159 (given in a format understood by the source).
162 (given in a format understood by the source).
160
163
161 If no destination directory name is specified, it defaults to the
164 If no destination directory name is specified, it defaults to the
162 basename of the source with ``-hg`` appended. If the destination
165 basename of the source with ``-hg`` appended. If the destination
163 repository doesn't exist, it will be created.
166 repository doesn't exist, it will be created.
164
167
165 By default, all sources except Mercurial will use --branchsort.
168 By default, all sources except Mercurial will use --branchsort.
166 Mercurial uses --sourcesort to preserve original revision numbers
169 Mercurial uses --sourcesort to preserve original revision numbers
167 order. Sort modes have the following effects:
170 order. Sort modes have the following effects:
168
171
169 --branchsort convert from parent to child revision when possible,
172 --branchsort convert from parent to child revision when possible,
170 which means branches are usually converted one after
173 which means branches are usually converted one after
171 the other. It generates more compact repositories.
174 the other. It generates more compact repositories.
172
175
173 --datesort sort revisions by date. Converted repositories have
176 --datesort sort revisions by date. Converted repositories have
174 good-looking changelogs but are often an order of
177 good-looking changelogs but are often an order of
175 magnitude larger than the same ones generated by
178 magnitude larger than the same ones generated by
176 --branchsort.
179 --branchsort.
177
180
178 --sourcesort try to preserve source revisions order, only
181 --sourcesort try to preserve source revisions order, only
179 supported by Mercurial sources.
182 supported by Mercurial sources.
180
183
181 --closesort try to move closed revisions as close as possible
184 --closesort try to move closed revisions as close as possible
182 to parent branches, only supported by Mercurial
185 to parent branches, only supported by Mercurial
183 sources.
186 sources.
184
187
185 If ``REVMAP`` isn't given, it will be put in a default location
188 If ``REVMAP`` isn't given, it will be put in a default location
186 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
189 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
187 text file that maps each source commit ID to the destination ID
190 text file that maps each source commit ID to the destination ID
188 for that revision, like so::
191 for that revision, like so::
189
192
190 <source ID> <destination ID>
193 <source ID> <destination ID>
191
194
192 If the file doesn't exist, it's automatically created. It's
195 If the file doesn't exist, it's automatically created. It's
193 updated on each commit copied, so :hg:`convert` can be interrupted
196 updated on each commit copied, so :hg:`convert` can be interrupted
194 and can be run repeatedly to copy new commits.
197 and can be run repeatedly to copy new commits.
195
198
196 The authormap is a simple text file that maps each source commit
199 The authormap is a simple text file that maps each source commit
197 author to a destination commit author. It is handy for source SCMs
200 author to a destination commit author. It is handy for source SCMs
198 that use unix logins to identify authors (e.g.: CVS). One line per
201 that use unix logins to identify authors (e.g.: CVS). One line per
199 author mapping and the line format is::
202 author mapping and the line format is::
200
203
201 source author = destination author
204 source author = destination author
202
205
203 Empty lines and lines starting with a ``#`` are ignored.
206 Empty lines and lines starting with a ``#`` are ignored.
204
207
205 The filemap is a file that allows filtering and remapping of files
208 The filemap is a file that allows filtering and remapping of files
206 and directories. Each line can contain one of the following
209 and directories. Each line can contain one of the following
207 directives::
210 directives::
208
211
209 include path/to/file-or-dir
212 include path/to/file-or-dir
210
213
211 exclude path/to/file-or-dir
214 exclude path/to/file-or-dir
212
215
213 rename path/to/source path/to/destination
216 rename path/to/source path/to/destination
214
217
215 Comment lines start with ``#``. A specified path matches if it
218 Comment lines start with ``#``. A specified path matches if it
216 equals the full relative name of a file or one of its parent
219 equals the full relative name of a file or one of its parent
217 directories. The ``include`` or ``exclude`` directive with the
220 directories. The ``include`` or ``exclude`` directive with the
218 longest matching path applies, so line order does not matter.
221 longest matching path applies, so line order does not matter.
219
222
220 The ``include`` directive causes a file, or all files under a
223 The ``include`` directive causes a file, or all files under a
221 directory, to be included in the destination repository. The default
224 directory, to be included in the destination repository. The default
222 if there are no ``include`` statements is to include everything.
225 if there are no ``include`` statements is to include everything.
223 If there are any ``include`` statements, nothing else is included.
226 If there are any ``include`` statements, nothing else is included.
224 The ``exclude`` directive causes files or directories to
227 The ``exclude`` directive causes files or directories to
225 be omitted. The ``rename`` directive renames a file or directory if
228 be omitted. The ``rename`` directive renames a file or directory if
226 it is converted. To rename from a subdirectory into the root of
229 it is converted. To rename from a subdirectory into the root of
227 the repository, use ``.`` as the path to rename to.
230 the repository, use ``.`` as the path to rename to.
228
231
229 ``--full`` will make sure the converted changesets contain exactly
232 ``--full`` will make sure the converted changesets contain exactly
230 the right files with the right content. It will make a full
233 the right files with the right content. It will make a full
231 conversion of all files, not just the ones that have
234 conversion of all files, not just the ones that have
232 changed. Files that already are correct will not be changed. This
235 changed. Files that already are correct will not be changed. This
233 can be used to apply filemap changes when converting
236 can be used to apply filemap changes when converting
234 incrementally. This is currently only supported for Mercurial and
237 incrementally. This is currently only supported for Mercurial and
235 Subversion.
238 Subversion.
236
239
237 The splicemap is a file that allows insertion of synthetic
240 The splicemap is a file that allows insertion of synthetic
238 history, letting you specify the parents of a revision. This is
241 history, letting you specify the parents of a revision. This is
239 useful if you want to e.g. give a Subversion merge two parents, or
242 useful if you want to e.g. give a Subversion merge two parents, or
240 graft two disconnected series of history together. Each entry
243 graft two disconnected series of history together. Each entry
241 contains a key, followed by a space, followed by one or two
244 contains a key, followed by a space, followed by one or two
242 comma-separated values::
245 comma-separated values::
243
246
244 key parent1, parent2
247 key parent1, parent2
245
248
246 The key is the revision ID in the source
249 The key is the revision ID in the source
247 revision control system whose parents should be modified (same
250 revision control system whose parents should be modified (same
248 format as a key in .hg/shamap). The values are the revision IDs
251 format as a key in .hg/shamap). The values are the revision IDs
249 (in either the source or destination revision control system) that
252 (in either the source or destination revision control system) that
250 should be used as the new parents for that node. For example, if
253 should be used as the new parents for that node. For example, if
251 you have merged "release-1.0" into "trunk", then you should
254 you have merged "release-1.0" into "trunk", then you should
252 specify the revision on "trunk" as the first parent and the one on
255 specify the revision on "trunk" as the first parent and the one on
253 the "release-1.0" branch as the second.
256 the "release-1.0" branch as the second.
254
257
255 The branchmap is a file that allows you to rename a branch when it is
258 The branchmap is a file that allows you to rename a branch when it is
256 being brought in from whatever external repository. When used in
259 being brought in from whatever external repository. When used in
257 conjunction with a splicemap, it allows for a powerful combination
260 conjunction with a splicemap, it allows for a powerful combination
258 to help fix even the most badly mismanaged repositories and turn them
261 to help fix even the most badly mismanaged repositories and turn them
259 into nicely structured Mercurial repositories. The branchmap contains
262 into nicely structured Mercurial repositories. The branchmap contains
260 lines of the form::
263 lines of the form::
261
264
262 original_branch_name new_branch_name
265 original_branch_name new_branch_name
263
266
264 where "original_branch_name" is the name of the branch in the
267 where "original_branch_name" is the name of the branch in the
265 source repository, and "new_branch_name" is the name of the branch
268 source repository, and "new_branch_name" is the name of the branch
266 is the destination repository. No whitespace is allowed in the new
269 is the destination repository. No whitespace is allowed in the new
267 branch name. This can be used to (for instance) move code in one
270 branch name. This can be used to (for instance) move code in one
268 repository from "default" to a named branch.
271 repository from "default" to a named branch.
269
272
270 Mercurial Source
273 Mercurial Source
271 ################
274 ################
272
275
273 The Mercurial source recognizes the following configuration
276 The Mercurial source recognizes the following configuration
274 options, which you can set on the command line with ``--config``:
277 options, which you can set on the command line with ``--config``:
275
278
276 :convert.hg.ignoreerrors: ignore integrity errors when reading.
279 :convert.hg.ignoreerrors: ignore integrity errors when reading.
277 Use it to fix Mercurial repositories with missing revlogs, by
280 Use it to fix Mercurial repositories with missing revlogs, by
278 converting from and to Mercurial. Default is False.
281 converting from and to Mercurial. Default is False.
279
282
280 :convert.hg.saverev: store original revision ID in changeset
283 :convert.hg.saverev: store original revision ID in changeset
281 (forces target IDs to change). It takes a boolean argument and
284 (forces target IDs to change). It takes a boolean argument and
282 defaults to False.
285 defaults to False.
283
286
284 :convert.hg.startrev: specify the initial Mercurial revision.
287 :convert.hg.startrev: specify the initial Mercurial revision.
285 The default is 0.
288 The default is 0.
286
289
287 :convert.hg.revs: revset specifying the source revisions to convert.
290 :convert.hg.revs: revset specifying the source revisions to convert.
288
291
289 CVS Source
292 CVS Source
290 ##########
293 ##########
291
294
292 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
295 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
293 to indicate the starting point of what will be converted. Direct
296 to indicate the starting point of what will be converted. Direct
294 access to the repository files is not needed, unless of course the
297 access to the repository files is not needed, unless of course the
295 repository is ``:local:``. The conversion uses the top level
298 repository is ``:local:``. The conversion uses the top level
296 directory in the sandbox to find the CVS repository, and then uses
299 directory in the sandbox to find the CVS repository, and then uses
297 CVS rlog commands to find files to convert. This means that unless
300 CVS rlog commands to find files to convert. This means that unless
298 a filemap is given, all files under the starting directory will be
301 a filemap is given, all files under the starting directory will be
299 converted, and that any directory reorganization in the CVS
302 converted, and that any directory reorganization in the CVS
300 sandbox is ignored.
303 sandbox is ignored.
301
304
302 The following options can be used with ``--config``:
305 The following options can be used with ``--config``:
303
306
304 :convert.cvsps.cache: Set to False to disable remote log caching,
307 :convert.cvsps.cache: Set to False to disable remote log caching,
305 for testing and debugging purposes. Default is True.
308 for testing and debugging purposes. Default is True.
306
309
307 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
310 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
308 allowed between commits with identical user and log message in
311 allowed between commits with identical user and log message in
309 a single changeset. When very large files were checked in as
312 a single changeset. When very large files were checked in as
310 part of a changeset then the default may not be long enough.
313 part of a changeset then the default may not be long enough.
311 The default is 60.
314 The default is 60.
312
315
313 :convert.cvsps.logencoding: Specify encoding name to be used for
316 :convert.cvsps.logencoding: Specify encoding name to be used for
314 transcoding CVS log messages. Multiple encoding names can be
317 transcoding CVS log messages. Multiple encoding names can be
315 specified as a list (see :hg:`help config.Syntax`), but only
318 specified as a list (see :hg:`help config.Syntax`), but only
316 the first acceptable encoding in the list is used per CVS log
319 the first acceptable encoding in the list is used per CVS log
317 entries. This transcoding is executed before cvslog hook below.
320 entries. This transcoding is executed before cvslog hook below.
318
321
319 :convert.cvsps.mergeto: Specify a regular expression to which
322 :convert.cvsps.mergeto: Specify a regular expression to which
320 commit log messages are matched. If a match occurs, then the
323 commit log messages are matched. If a match occurs, then the
321 conversion process will insert a dummy revision merging the
324 conversion process will insert a dummy revision merging the
322 branch on which this log message occurs to the branch
325 branch on which this log message occurs to the branch
323 indicated in the regex. Default is ``{{mergetobranch
326 indicated in the regex. Default is ``{{mergetobranch
324 ([-\\w]+)}}``
327 ([-\\w]+)}}``
325
328
326 :convert.cvsps.mergefrom: Specify a regular expression to which
329 :convert.cvsps.mergefrom: Specify a regular expression to which
327 commit log messages are matched. If a match occurs, then the
330 commit log messages are matched. If a match occurs, then the
328 conversion process will add the most recent revision on the
331 conversion process will add the most recent revision on the
329 branch indicated in the regex as the second parent of the
332 branch indicated in the regex as the second parent of the
330 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
333 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
331
334
332 :convert.localtimezone: use local time (as determined by the TZ
335 :convert.localtimezone: use local time (as determined by the TZ
333 environment variable) for changeset date/times. The default
336 environment variable) for changeset date/times. The default
334 is False (use UTC).
337 is False (use UTC).
335
338
336 :hooks.cvslog: Specify a Python function to be called at the end of
339 :hooks.cvslog: Specify a Python function to be called at the end of
337 gathering the CVS log. The function is passed a list with the
340 gathering the CVS log. The function is passed a list with the
338 log entries, and can modify the entries in-place, or add or
341 log entries, and can modify the entries in-place, or add or
339 delete them.
342 delete them.
340
343
341 :hooks.cvschangesets: Specify a Python function to be called after
344 :hooks.cvschangesets: Specify a Python function to be called after
342 the changesets are calculated from the CVS log. The
345 the changesets are calculated from the CVS log. The
343 function is passed a list with the changeset entries, and can
346 function is passed a list with the changeset entries, and can
344 modify the changesets in-place, or add or delete them.
347 modify the changesets in-place, or add or delete them.
345
348
346 An additional "debugcvsps" Mercurial command allows the builtin
349 An additional "debugcvsps" Mercurial command allows the builtin
347 changeset merging code to be run without doing a conversion. Its
350 changeset merging code to be run without doing a conversion. Its
348 parameters and output are similar to that of cvsps 2.1. Please see
351 parameters and output are similar to that of cvsps 2.1. Please see
349 the command help for more details.
352 the command help for more details.
350
353
351 Subversion Source
354 Subversion Source
352 #################
355 #################
353
356
354 Subversion source detects classical trunk/branches/tags layouts.
357 Subversion source detects classical trunk/branches/tags layouts.
355 By default, the supplied ``svn://repo/path/`` source URL is
358 By default, the supplied ``svn://repo/path/`` source URL is
356 converted as a single branch. If ``svn://repo/path/trunk`` exists
359 converted as a single branch. If ``svn://repo/path/trunk`` exists
357 it replaces the default branch. If ``svn://repo/path/branches``
360 it replaces the default branch. If ``svn://repo/path/branches``
358 exists, its subdirectories are listed as possible branches. If
361 exists, its subdirectories are listed as possible branches. If
359 ``svn://repo/path/tags`` exists, it is looked for tags referencing
362 ``svn://repo/path/tags`` exists, it is looked for tags referencing
360 converted branches. Default ``trunk``, ``branches`` and ``tags``
363 converted branches. Default ``trunk``, ``branches`` and ``tags``
361 values can be overridden with following options. Set them to paths
364 values can be overridden with following options. Set them to paths
362 relative to the source URL, or leave them blank to disable auto
365 relative to the source URL, or leave them blank to disable auto
363 detection.
366 detection.
364
367
365 The following options can be set with ``--config``:
368 The following options can be set with ``--config``:
366
369
367 :convert.svn.branches: specify the directory containing branches.
370 :convert.svn.branches: specify the directory containing branches.
368 The default is ``branches``.
371 The default is ``branches``.
369
372
370 :convert.svn.tags: specify the directory containing tags. The
373 :convert.svn.tags: specify the directory containing tags. The
371 default is ``tags``.
374 default is ``tags``.
372
375
373 :convert.svn.trunk: specify the name of the trunk branch. The
376 :convert.svn.trunk: specify the name of the trunk branch. The
374 default is ``trunk``.
377 default is ``trunk``.
375
378
376 :convert.localtimezone: use local time (as determined by the TZ
379 :convert.localtimezone: use local time (as determined by the TZ
377 environment variable) for changeset date/times. The default
380 environment variable) for changeset date/times. The default
378 is False (use UTC).
381 is False (use UTC).
379
382
380 Source history can be retrieved starting at a specific revision,
383 Source history can be retrieved starting at a specific revision,
381 instead of being integrally converted. Only single branch
384 instead of being integrally converted. Only single branch
382 conversions are supported.
385 conversions are supported.
383
386
384 :convert.svn.startrev: specify start Subversion revision number.
387 :convert.svn.startrev: specify start Subversion revision number.
385 The default is 0.
388 The default is 0.
386
389
387 Git Source
390 Git Source
388 ##########
391 ##########
389
392
390 The Git importer converts commits from all reachable branches (refs
393 The Git importer converts commits from all reachable branches (refs
391 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
394 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
392 Branches are converted to bookmarks with the same name, with the
395 Branches are converted to bookmarks with the same name, with the
393 leading 'refs/heads' stripped. Git submodules are converted to Git
396 leading 'refs/heads' stripped. Git submodules are converted to Git
394 subrepos in Mercurial.
397 subrepos in Mercurial.
395
398
396 The following options can be set with ``--config``:
399 The following options can be set with ``--config``:
397
400
398 :convert.git.similarity: specify how similar files modified in a
401 :convert.git.similarity: specify how similar files modified in a
399 commit must be to be imported as renames or copies, as a
402 commit must be to be imported as renames or copies, as a
400 percentage between ``0`` (disabled) and ``100`` (files must be
403 percentage between ``0`` (disabled) and ``100`` (files must be
401 identical). For example, ``90`` means that a delete/add pair will
404 identical). For example, ``90`` means that a delete/add pair will
402 be imported as a rename if more than 90% of the file hasn't
405 be imported as a rename if more than 90% of the file hasn't
403 changed. The default is ``50``.
406 changed. The default is ``50``.
404
407
405 :convert.git.findcopiesharder: while detecting copies, look at all
408 :convert.git.findcopiesharder: while detecting copies, look at all
406 files in the working copy instead of just changed ones. This
409 files in the working copy instead of just changed ones. This
407 is very expensive for large projects, and is only effective when
410 is very expensive for large projects, and is only effective when
408 ``convert.git.similarity`` is greater than 0. The default is False.
411 ``convert.git.similarity`` is greater than 0. The default is False.
409
412
410 :convert.git.renamelimit: perform rename and copy detection up to this
413 :convert.git.renamelimit: perform rename and copy detection up to this
411 many changed files in a commit. Increasing this will make rename
414 many changed files in a commit. Increasing this will make rename
412 and copy detection more accurate but will significantly slow down
415 and copy detection more accurate but will significantly slow down
413 computation on large projects. The option is only relevant if
416 computation on large projects. The option is only relevant if
414 ``convert.git.similarity`` is greater than 0. The default is
417 ``convert.git.similarity`` is greater than 0. The default is
415 ``400``.
418 ``400``.
416
419
417 :convert.git.committeractions: list of actions to take when processing
420 :convert.git.committeractions: list of actions to take when processing
418 author and committer values.
421 author and committer values.
419
422
420 Git commits have separate author (who wrote the commit) and committer
423 Git commits have separate author (who wrote the commit) and committer
421 (who applied the commit) fields. Not all destinations support separate
424 (who applied the commit) fields. Not all destinations support separate
422 author and committer fields (including Mercurial). This config option
425 author and committer fields (including Mercurial). This config option
423 controls what to do with these author and committer fields during
426 controls what to do with these author and committer fields during
424 conversion.
427 conversion.
425
428
426 A value of ``messagedifferent`` will append a ``committer: ...``
429 A value of ``messagedifferent`` will append a ``committer: ...``
427 line to the commit message if the Git committer is different from the
430 line to the commit message if the Git committer is different from the
428 author. The prefix of that line can be specified using the syntax
431 author. The prefix of that line can be specified using the syntax
429 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
432 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
430 When a prefix is specified, a space will always be inserted between the
433 When a prefix is specified, a space will always be inserted between the
431 prefix and the value.
434 prefix and the value.
432
435
433 ``messagealways`` behaves like ``messagedifferent`` except it will
436 ``messagealways`` behaves like ``messagedifferent`` except it will
434 always result in a ``committer: ...`` line being appended to the commit
437 always result in a ``committer: ...`` line being appended to the commit
435 message. This value is mutually exclusive with ``messagedifferent``.
438 message. This value is mutually exclusive with ``messagedifferent``.
436
439
437 ``dropcommitter`` will remove references to the committer. Only
440 ``dropcommitter`` will remove references to the committer. Only
438 references to the author will remain. Actions that add references
441 references to the author will remain. Actions that add references
439 to the committer will have no effect when this is set.
442 to the committer will have no effect when this is set.
440
443
441 ``replaceauthor`` will replace the value of the author field with
444 ``replaceauthor`` will replace the value of the author field with
442 the committer. Other actions that add references to the committer
445 the committer. Other actions that add references to the committer
443 will still take effect when this is set.
446 will still take effect when this is set.
444
447
445 The default is ``messagedifferent``.
448 The default is ``messagedifferent``.
446
449
447 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
450 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
448 the destination. Some Git repositories store extra metadata in commits.
451 the destination. Some Git repositories store extra metadata in commits.
449 By default, this non-default metadata will be lost during conversion.
452 By default, this non-default metadata will be lost during conversion.
450 Setting this config option can retain that metadata. Some built-in
453 Setting this config option can retain that metadata. Some built-in
451 keys such as ``parent`` and ``branch`` are not allowed to be copied.
454 keys such as ``parent`` and ``branch`` are not allowed to be copied.
452
455
453 :convert.git.remoteprefix: remote refs are converted as bookmarks with
456 :convert.git.remoteprefix: remote refs are converted as bookmarks with
454 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
457 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
455 is 'remote'.
458 is 'remote'.
456
459
457 :convert.git.saverev: whether to store the original Git commit ID in the
460 :convert.git.saverev: whether to store the original Git commit ID in the
458 metadata of the destination commit. The default is True.
461 metadata of the destination commit. The default is True.
459
462
460 :convert.git.skipsubmodules: does not convert root level .gitmodules files
463 :convert.git.skipsubmodules: does not convert root level .gitmodules files
461 or files with 160000 mode indicating a submodule. Default is False.
464 or files with 160000 mode indicating a submodule. Default is False.
462
465
463 Perforce Source
466 Perforce Source
464 ###############
467 ###############
465
468
466 The Perforce (P4) importer can be given a p4 depot path or a
469 The Perforce (P4) importer can be given a p4 depot path or a
467 client specification as source. It will convert all files in the
470 client specification as source. It will convert all files in the
468 source to a flat Mercurial repository, ignoring labels, branches
471 source to a flat Mercurial repository, ignoring labels, branches
469 and integrations. Note that when a depot path is given you then
472 and integrations. Note that when a depot path is given you then
470 usually should specify a target directory, because otherwise the
473 usually should specify a target directory, because otherwise the
471 target may be named ``...-hg``.
474 target may be named ``...-hg``.
472
475
473 The following options can be set with ``--config``:
476 The following options can be set with ``--config``:
474
477
475 :convert.p4.encoding: specify the encoding to use when decoding standard
478 :convert.p4.encoding: specify the encoding to use when decoding standard
476 output of the Perforce command line tool. The default is default system
479 output of the Perforce command line tool. The default is default system
477 encoding.
480 encoding.
478
481
479 :convert.p4.startrev: specify initial Perforce revision (a
482 :convert.p4.startrev: specify initial Perforce revision (a
480 Perforce changelist number).
483 Perforce changelist number).
481
484
482 Mercurial Destination
485 Mercurial Destination
483 #####################
486 #####################
484
487
485 The Mercurial destination will recognize Mercurial subrepositories in the
488 The Mercurial destination will recognize Mercurial subrepositories in the
486 destination directory, and update the .hgsubstate file automatically if the
489 destination directory, and update the .hgsubstate file automatically if the
487 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
490 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
488 Converting a repository with subrepositories requires converting a single
491 Converting a repository with subrepositories requires converting a single
489 repository at a time, from the bottom up.
492 repository at a time, from the bottom up.
490
493
491 .. container:: verbose
494 .. container:: verbose
492
495
493 An example showing how to convert a repository with subrepositories::
496 An example showing how to convert a repository with subrepositories::
494
497
495 # so convert knows the type when it sees a non empty destination
498 # so convert knows the type when it sees a non empty destination
496 $ hg init converted
499 $ hg init converted
497
500
498 $ hg convert orig/sub1 converted/sub1
501 $ hg convert orig/sub1 converted/sub1
499 $ hg convert orig/sub2 converted/sub2
502 $ hg convert orig/sub2 converted/sub2
500 $ hg convert orig converted
503 $ hg convert orig converted
501
504
502 The following options are supported:
505 The following options are supported:
503
506
504 :convert.hg.clonebranches: dispatch source branches in separate
507 :convert.hg.clonebranches: dispatch source branches in separate
505 clones. The default is False.
508 clones. The default is False.
506
509
507 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
510 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
508 ``default``.
511 ``default``.
509
512
510 :convert.hg.usebranchnames: preserve branch names. The default is
513 :convert.hg.usebranchnames: preserve branch names. The default is
511 True.
514 True.
512
515
513 :convert.hg.sourcename: records the given string as a 'convert_source' extra
516 :convert.hg.sourcename: records the given string as a 'convert_source' extra
514 value on each commit made in the target repository. The default is None.
517 value on each commit made in the target repository. The default is None.
515
518
516 All Destinations
519 All Destinations
517 ################
520 ################
518
521
519 All destination types accept the following options:
522 All destination types accept the following options:
520
523
521 :convert.skiptags: does not convert tags from the source repo to the target
524 :convert.skiptags: does not convert tags from the source repo to the target
522 repo. The default is False.
525 repo. The default is False.
523 """
526 """
524 return convcmd.convert(ui, src, dest, revmapfile, **opts)
527 return convcmd.convert(ui, src, dest, revmapfile, **opts)
525
528
526 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
529 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
527 def debugsvnlog(ui, **opts):
530 def debugsvnlog(ui, **opts):
528 return subversion.debugsvnlog(ui, **opts)
531 return subversion.debugsvnlog(ui, **opts)
529
532
530 @command('debugcvsps',
533 @command('debugcvsps',
531 [
534 [
532 # Main options shared with cvsps-2.1
535 # Main options shared with cvsps-2.1
533 ('b', 'branches', [], _('only return changes on specified branches')),
536 ('b', 'branches', [], _('only return changes on specified branches')),
534 ('p', 'prefix', '', _('prefix to remove from file names')),
537 ('p', 'prefix', '', _('prefix to remove from file names')),
535 ('r', 'revisions', [],
538 ('r', 'revisions', [],
536 _('only return changes after or between specified tags')),
539 _('only return changes after or between specified tags')),
537 ('u', 'update-cache', None, _("update cvs log cache")),
540 ('u', 'update-cache', None, _("update cvs log cache")),
538 ('x', 'new-cache', None, _("create new cvs log cache")),
541 ('x', 'new-cache', None, _("create new cvs log cache")),
539 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
542 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
540 ('', 'root', '', _('specify cvsroot')),
543 ('', 'root', '', _('specify cvsroot')),
541 # Options specific to builtin cvsps
544 # Options specific to builtin cvsps
542 ('', 'parents', '', _('show parent changesets')),
545 ('', 'parents', '', _('show parent changesets')),
543 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
546 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
544 # Options that are ignored for compatibility with cvsps-2.1
547 # Options that are ignored for compatibility with cvsps-2.1
545 ('A', 'cvs-direct', None, _('ignored for compatibility')),
548 ('A', 'cvs-direct', None, _('ignored for compatibility')),
546 ],
549 ],
547 _('hg debugcvsps [OPTION]... [PATH]...'),
550 _('hg debugcvsps [OPTION]... [PATH]...'),
548 norepo=True)
551 norepo=True)
549 def debugcvsps(ui, *args, **opts):
552 def debugcvsps(ui, *args, **opts):
550 '''create changeset information from CVS
553 '''create changeset information from CVS
551
554
552 This command is intended as a debugging tool for the CVS to
555 This command is intended as a debugging tool for the CVS to
553 Mercurial converter, and can be used as a direct replacement for
556 Mercurial converter, and can be used as a direct replacement for
554 cvsps.
557 cvsps.
555
558
556 Hg debugcvsps reads the CVS rlog for current directory (or any
559 Hg debugcvsps reads the CVS rlog for current directory (or any
557 named directory) in the CVS repository, and converts the log to a
560 named directory) in the CVS repository, and converts the log to a
558 series of changesets based on matching commit log entries and
561 series of changesets based on matching commit log entries and
559 dates.'''
562 dates.'''
560 return cvsps.debugcvsps(ui, *args, **opts)
563 return cvsps.debugcvsps(ui, *args, **opts)
561
564
562 def kwconverted(ctx, name):
565 def kwconverted(ctx, name):
563 rev = ctx.extra().get('convert_revision', '')
566 rev = ctx.extra().get('convert_revision', '')
564 if rev.startswith('svn:'):
567 if rev.startswith('svn:'):
565 if name == 'svnrev':
568 if name == 'svnrev':
566 return str(subversion.revsplit(rev)[2])
569 return str(subversion.revsplit(rev)[2])
567 elif name == 'svnpath':
570 elif name == 'svnpath':
568 return subversion.revsplit(rev)[1]
571 return subversion.revsplit(rev)[1]
569 elif name == 'svnuuid':
572 elif name == 'svnuuid':
570 return subversion.revsplit(rev)[0]
573 return subversion.revsplit(rev)[0]
571 return rev
574 return rev
572
575
573 templatekeyword = registrar.templatekeyword()
576 templatekeyword = registrar.templatekeyword()
574
577
575 @templatekeyword('svnrev')
578 @templatekeyword('svnrev')
576 def kwsvnrev(repo, ctx, **args):
579 def kwsvnrev(repo, ctx, **args):
577 """String. Converted subversion revision number."""
580 """String. Converted subversion revision number."""
578 return kwconverted(ctx, 'svnrev')
581 return kwconverted(ctx, 'svnrev')
579
582
580 @templatekeyword('svnpath')
583 @templatekeyword('svnpath')
581 def kwsvnpath(repo, ctx, **args):
584 def kwsvnpath(repo, ctx, **args):
582 """String. Converted subversion revision project path."""
585 """String. Converted subversion revision project path."""
583 return kwconverted(ctx, 'svnpath')
586 return kwconverted(ctx, 'svnpath')
584
587
585 @templatekeyword('svnuuid')
588 @templatekeyword('svnuuid')
586 def kwsvnuuid(repo, ctx, **args):
589 def kwsvnuuid(repo, ctx, **args):
587 """String. Converted subversion revision repository identifier."""
590 """String. Converted subversion revision repository identifier."""
588 return kwconverted(ctx, 'svnuuid')
591 return kwconverted(ctx, 'svnuuid')
589
592
590 # tell hggettext to extract docstrings from these functions:
593 # tell hggettext to extract docstrings from these functions:
591 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
594 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,373 +1,369 b''
1 # Perforce source for convert extension.
1 # Perforce source for convert extension.
2 #
2 #
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
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 marshal
9 import marshal
10 import re
10 import re
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 error,
14 error,
15 util,
15 util,
16 )
16 )
17
17
18 from . import common
18 from . import common
19
19
20 def loaditer(f):
20 def loaditer(f):
21 "Yield the dictionary objects generated by p4"
21 "Yield the dictionary objects generated by p4"
22 try:
22 try:
23 while True:
23 while True:
24 d = marshal.load(f)
24 d = marshal.load(f)
25 if not d:
25 if not d:
26 break
26 break
27 yield d
27 yield d
28 except EOFError:
28 except EOFError:
29 pass
29 pass
30
30
31 def decodefilename(filename):
31 def decodefilename(filename):
32 """Perforce escapes special characters @, #, *, or %
32 """Perforce escapes special characters @, #, *, or %
33 with %40, %23, %2A, or %25 respectively
33 with %40, %23, %2A, or %25 respectively
34
34
35 >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
35 >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
36 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
36 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
37 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
37 >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
38 '//Depot/Directory/%25/%23/#@.*'
38 '//Depot/Directory/%25/%23/#@.*'
39 """
39 """
40 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
40 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
41 for k, v in replacements:
41 for k, v in replacements:
42 filename = filename.replace(k, v)
42 filename = filename.replace(k, v)
43 return filename
43 return filename
44
44
45 class p4_source(common.converter_source):
45 class p4_source(common.converter_source):
46 def __init__(self, ui, path, revs=None):
46 def __init__(self, ui, path, revs=None):
47 # avoid import cycle
48 from . import convcmd
49
50 super(p4_source, self).__init__(ui, path, revs=revs)
47 super(p4_source, self).__init__(ui, path, revs=revs)
51
48
52 if "/" in path and not path.startswith('//'):
49 if "/" in path and not path.startswith('//'):
53 raise common.NoRepo(_('%s does not look like a P4 repository') %
50 raise common.NoRepo(_('%s does not look like a P4 repository') %
54 path)
51 path)
55
52
56 common.checktool('p4', abort=False)
53 common.checktool('p4', abort=False)
57
54
58 self.revmap = {}
55 self.revmap = {}
59 self.encoding = self.ui.config('convert', 'p4.encoding',
56 self.encoding = self.ui.config('convert', 'p4.encoding')
60 default=convcmd.orig_encoding)
61 self.re_type = re.compile(
57 self.re_type = re.compile(
62 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
58 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
63 "(\+\w+)?$")
59 "(\+\w+)?$")
64 self.re_keywords = re.compile(
60 self.re_keywords = re.compile(
65 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
61 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
66 r":[^$\n]*\$")
62 r":[^$\n]*\$")
67 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
63 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
68
64
69 if revs and len(revs) > 1:
65 if revs and len(revs) > 1:
70 raise error.Abort(_("p4 source does not support specifying "
66 raise error.Abort(_("p4 source does not support specifying "
71 "multiple revisions"))
67 "multiple revisions"))
72
68
73 def setrevmap(self, revmap):
69 def setrevmap(self, revmap):
74 """Sets the parsed revmap dictionary.
70 """Sets the parsed revmap dictionary.
75
71
76 Revmap stores mappings from a source revision to a target revision.
72 Revmap stores mappings from a source revision to a target revision.
77 It is set in convertcmd.convert and provided by the user as a file
73 It is set in convertcmd.convert and provided by the user as a file
78 on the commandline.
74 on the commandline.
79
75
80 Revisions in the map are considered beeing present in the
76 Revisions in the map are considered beeing present in the
81 repository and ignored during _parse(). This allows for incremental
77 repository and ignored during _parse(). This allows for incremental
82 imports if a revmap is provided.
78 imports if a revmap is provided.
83 """
79 """
84 self.revmap = revmap
80 self.revmap = revmap
85
81
86 def _parse_view(self, path):
82 def _parse_view(self, path):
87 "Read changes affecting the path"
83 "Read changes affecting the path"
88 cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
84 cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
89 stdout = util.popen(cmd, mode='rb')
85 stdout = util.popen(cmd, mode='rb')
90 p4changes = {}
86 p4changes = {}
91 for d in loaditer(stdout):
87 for d in loaditer(stdout):
92 c = d.get("change", None)
88 c = d.get("change", None)
93 if c:
89 if c:
94 p4changes[c] = True
90 p4changes[c] = True
95 return p4changes
91 return p4changes
96
92
97 def _parse(self, ui, path):
93 def _parse(self, ui, path):
98 "Prepare list of P4 filenames and revisions to import"
94 "Prepare list of P4 filenames and revisions to import"
99 p4changes = {}
95 p4changes = {}
100 changeset = {}
96 changeset = {}
101 files_map = {}
97 files_map = {}
102 copies_map = {}
98 copies_map = {}
103 localname = {}
99 localname = {}
104 depotname = {}
100 depotname = {}
105 heads = []
101 heads = []
106
102
107 ui.status(_('reading p4 views\n'))
103 ui.status(_('reading p4 views\n'))
108
104
109 # read client spec or view
105 # read client spec or view
110 if "/" in path:
106 if "/" in path:
111 p4changes.update(self._parse_view(path))
107 p4changes.update(self._parse_view(path))
112 if path.startswith("//") and path.endswith("/..."):
108 if path.startswith("//") and path.endswith("/..."):
113 views = {path[:-3]:""}
109 views = {path[:-3]:""}
114 else:
110 else:
115 views = {"//": ""}
111 views = {"//": ""}
116 else:
112 else:
117 cmd = 'p4 -G client -o %s' % util.shellquote(path)
113 cmd = 'p4 -G client -o %s' % util.shellquote(path)
118 clientspec = marshal.load(util.popen(cmd, mode='rb'))
114 clientspec = marshal.load(util.popen(cmd, mode='rb'))
119
115
120 views = {}
116 views = {}
121 for client in clientspec:
117 for client in clientspec:
122 if client.startswith("View"):
118 if client.startswith("View"):
123 sview, cview = clientspec[client].split()
119 sview, cview = clientspec[client].split()
124 p4changes.update(self._parse_view(sview))
120 p4changes.update(self._parse_view(sview))
125 if sview.endswith("...") and cview.endswith("..."):
121 if sview.endswith("...") and cview.endswith("..."):
126 sview = sview[:-3]
122 sview = sview[:-3]
127 cview = cview[:-3]
123 cview = cview[:-3]
128 cview = cview[2:]
124 cview = cview[2:]
129 cview = cview[cview.find("/") + 1:]
125 cview = cview[cview.find("/") + 1:]
130 views[sview] = cview
126 views[sview] = cview
131
127
132 # list of changes that affect our source files
128 # list of changes that affect our source files
133 p4changes = p4changes.keys()
129 p4changes = p4changes.keys()
134 p4changes.sort(key=int)
130 p4changes.sort(key=int)
135
131
136 # list with depot pathnames, longest first
132 # list with depot pathnames, longest first
137 vieworder = views.keys()
133 vieworder = views.keys()
138 vieworder.sort(key=len, reverse=True)
134 vieworder.sort(key=len, reverse=True)
139
135
140 # handle revision limiting
136 # handle revision limiting
141 startrev = self.ui.config('convert', 'p4.startrev')
137 startrev = self.ui.config('convert', 'p4.startrev')
142
138
143 # now read the full changelists to get the list of file revisions
139 # now read the full changelists to get the list of file revisions
144 ui.status(_('collecting p4 changelists\n'))
140 ui.status(_('collecting p4 changelists\n'))
145 lastid = None
141 lastid = None
146 for change in p4changes:
142 for change in p4changes:
147 if startrev and int(change) < int(startrev):
143 if startrev and int(change) < int(startrev):
148 continue
144 continue
149 if self.revs and int(change) > int(self.revs[0]):
145 if self.revs and int(change) > int(self.revs[0]):
150 continue
146 continue
151 if change in self.revmap:
147 if change in self.revmap:
152 # Ignore already present revisions, but set the parent pointer.
148 # Ignore already present revisions, but set the parent pointer.
153 lastid = change
149 lastid = change
154 continue
150 continue
155
151
156 if lastid:
152 if lastid:
157 parents = [lastid]
153 parents = [lastid]
158 else:
154 else:
159 parents = []
155 parents = []
160
156
161 d = self._fetch_revision(change)
157 d = self._fetch_revision(change)
162 c = self._construct_commit(d, parents)
158 c = self._construct_commit(d, parents)
163
159
164 descarr = c.desc.splitlines(True)
160 descarr = c.desc.splitlines(True)
165 if len(descarr) > 0:
161 if len(descarr) > 0:
166 shortdesc = descarr[0].rstrip('\r\n')
162 shortdesc = descarr[0].rstrip('\r\n')
167 else:
163 else:
168 shortdesc = '**empty changelist description**'
164 shortdesc = '**empty changelist description**'
169
165
170 t = '%s %s' % (c.rev, repr(shortdesc)[1:-1])
166 t = '%s %s' % (c.rev, repr(shortdesc)[1:-1])
171 ui.status(util.ellipsis(t, 80) + '\n')
167 ui.status(util.ellipsis(t, 80) + '\n')
172
168
173 files = []
169 files = []
174 copies = {}
170 copies = {}
175 copiedfiles = []
171 copiedfiles = []
176 i = 0
172 i = 0
177 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
173 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
178 oldname = d["depotFile%d" % i]
174 oldname = d["depotFile%d" % i]
179 filename = None
175 filename = None
180 for v in vieworder:
176 for v in vieworder:
181 if oldname.lower().startswith(v.lower()):
177 if oldname.lower().startswith(v.lower()):
182 filename = decodefilename(views[v] + oldname[len(v):])
178 filename = decodefilename(views[v] + oldname[len(v):])
183 break
179 break
184 if filename:
180 if filename:
185 files.append((filename, d["rev%d" % i]))
181 files.append((filename, d["rev%d" % i]))
186 depotname[filename] = oldname
182 depotname[filename] = oldname
187 if (d.get("action%d" % i) == "move/add"):
183 if (d.get("action%d" % i) == "move/add"):
188 copiedfiles.append(filename)
184 copiedfiles.append(filename)
189 localname[oldname] = filename
185 localname[oldname] = filename
190 i += 1
186 i += 1
191
187
192 # Collect information about copied files
188 # Collect information about copied files
193 for filename in copiedfiles:
189 for filename in copiedfiles:
194 oldname = depotname[filename]
190 oldname = depotname[filename]
195
191
196 flcmd = 'p4 -G filelog %s' \
192 flcmd = 'p4 -G filelog %s' \
197 % util.shellquote(oldname)
193 % util.shellquote(oldname)
198 flstdout = util.popen(flcmd, mode='rb')
194 flstdout = util.popen(flcmd, mode='rb')
199
195
200 copiedfilename = None
196 copiedfilename = None
201 for d in loaditer(flstdout):
197 for d in loaditer(flstdout):
202 copiedoldname = None
198 copiedoldname = None
203
199
204 i = 0
200 i = 0
205 while ("change%d" % i) in d:
201 while ("change%d" % i) in d:
206 if (d["change%d" % i] == change and
202 if (d["change%d" % i] == change and
207 d["action%d" % i] == "move/add"):
203 d["action%d" % i] == "move/add"):
208 j = 0
204 j = 0
209 while ("file%d,%d" % (i, j)) in d:
205 while ("file%d,%d" % (i, j)) in d:
210 if d["how%d,%d" % (i, j)] == "moved from":
206 if d["how%d,%d" % (i, j)] == "moved from":
211 copiedoldname = d["file%d,%d" % (i, j)]
207 copiedoldname = d["file%d,%d" % (i, j)]
212 break
208 break
213 j += 1
209 j += 1
214 i += 1
210 i += 1
215
211
216 if copiedoldname and copiedoldname in localname:
212 if copiedoldname and copiedoldname in localname:
217 copiedfilename = localname[copiedoldname]
213 copiedfilename = localname[copiedoldname]
218 break
214 break
219
215
220 if copiedfilename:
216 if copiedfilename:
221 copies[filename] = copiedfilename
217 copies[filename] = copiedfilename
222 else:
218 else:
223 ui.warn(_("cannot find source for copied file: %s@%s\n")
219 ui.warn(_("cannot find source for copied file: %s@%s\n")
224 % (filename, change))
220 % (filename, change))
225
221
226 changeset[change] = c
222 changeset[change] = c
227 files_map[change] = files
223 files_map[change] = files
228 copies_map[change] = copies
224 copies_map[change] = copies
229 lastid = change
225 lastid = change
230
226
231 if lastid and len(changeset) > 0:
227 if lastid and len(changeset) > 0:
232 heads = [lastid]
228 heads = [lastid]
233
229
234 return {
230 return {
235 'changeset': changeset,
231 'changeset': changeset,
236 'files': files_map,
232 'files': files_map,
237 'copies': copies_map,
233 'copies': copies_map,
238 'heads': heads,
234 'heads': heads,
239 'depotname': depotname,
235 'depotname': depotname,
240 }
236 }
241
237
242 @util.propertycache
238 @util.propertycache
243 def _parse_once(self):
239 def _parse_once(self):
244 return self._parse(self.ui, self.path)
240 return self._parse(self.ui, self.path)
245
241
246 @util.propertycache
242 @util.propertycache
247 def copies(self):
243 def copies(self):
248 return self._parse_once['copies']
244 return self._parse_once['copies']
249
245
250 @util.propertycache
246 @util.propertycache
251 def files(self):
247 def files(self):
252 return self._parse_once['files']
248 return self._parse_once['files']
253
249
254 @util.propertycache
250 @util.propertycache
255 def changeset(self):
251 def changeset(self):
256 return self._parse_once['changeset']
252 return self._parse_once['changeset']
257
253
258 @util.propertycache
254 @util.propertycache
259 def heads(self):
255 def heads(self):
260 return self._parse_once['heads']
256 return self._parse_once['heads']
261
257
262 @util.propertycache
258 @util.propertycache
263 def depotname(self):
259 def depotname(self):
264 return self._parse_once['depotname']
260 return self._parse_once['depotname']
265
261
266 def getheads(self):
262 def getheads(self):
267 return self.heads
263 return self.heads
268
264
269 def getfile(self, name, rev):
265 def getfile(self, name, rev):
270 cmd = 'p4 -G print %s' \
266 cmd = 'p4 -G print %s' \
271 % util.shellquote("%s#%s" % (self.depotname[name], rev))
267 % util.shellquote("%s#%s" % (self.depotname[name], rev))
272
268
273 lasterror = None
269 lasterror = None
274 while True:
270 while True:
275 stdout = util.popen(cmd, mode='rb')
271 stdout = util.popen(cmd, mode='rb')
276
272
277 mode = None
273 mode = None
278 contents = []
274 contents = []
279 keywords = None
275 keywords = None
280
276
281 for d in loaditer(stdout):
277 for d in loaditer(stdout):
282 code = d["code"]
278 code = d["code"]
283 data = d.get("data")
279 data = d.get("data")
284
280
285 if code == "error":
281 if code == "error":
286 # if this is the first time error happened
282 # if this is the first time error happened
287 # re-attempt getting the file
283 # re-attempt getting the file
288 if not lasterror:
284 if not lasterror:
289 lasterror = IOError(d["generic"], data)
285 lasterror = IOError(d["generic"], data)
290 # this will exit inner-most for-loop
286 # this will exit inner-most for-loop
291 break
287 break
292 else:
288 else:
293 raise lasterror
289 raise lasterror
294
290
295 elif code == "stat":
291 elif code == "stat":
296 action = d.get("action")
292 action = d.get("action")
297 if action in ["purge", "delete", "move/delete"]:
293 if action in ["purge", "delete", "move/delete"]:
298 return None, None
294 return None, None
299 p4type = self.re_type.match(d["type"])
295 p4type = self.re_type.match(d["type"])
300 if p4type:
296 if p4type:
301 mode = ""
297 mode = ""
302 flags = ((p4type.group(1) or "")
298 flags = ((p4type.group(1) or "")
303 + (p4type.group(3) or ""))
299 + (p4type.group(3) or ""))
304 if "x" in flags:
300 if "x" in flags:
305 mode = "x"
301 mode = "x"
306 if p4type.group(2) == "symlink":
302 if p4type.group(2) == "symlink":
307 mode = "l"
303 mode = "l"
308 if "ko" in flags:
304 if "ko" in flags:
309 keywords = self.re_keywords_old
305 keywords = self.re_keywords_old
310 elif "k" in flags:
306 elif "k" in flags:
311 keywords = self.re_keywords
307 keywords = self.re_keywords
312
308
313 elif code == "text" or code == "binary":
309 elif code == "text" or code == "binary":
314 contents.append(data)
310 contents.append(data)
315
311
316 lasterror = None
312 lasterror = None
317
313
318 if not lasterror:
314 if not lasterror:
319 break
315 break
320
316
321 if mode is None:
317 if mode is None:
322 return None, None
318 return None, None
323
319
324 contents = ''.join(contents)
320 contents = ''.join(contents)
325
321
326 if keywords:
322 if keywords:
327 contents = keywords.sub("$\\1$", contents)
323 contents = keywords.sub("$\\1$", contents)
328 if mode == "l" and contents.endswith("\n"):
324 if mode == "l" and contents.endswith("\n"):
329 contents = contents[:-1]
325 contents = contents[:-1]
330
326
331 return contents, mode
327 return contents, mode
332
328
333 def getchanges(self, rev, full):
329 def getchanges(self, rev, full):
334 if full:
330 if full:
335 raise error.Abort(_("convert from p4 does not support --full"))
331 raise error.Abort(_("convert from p4 does not support --full"))
336 return self.files[rev], self.copies[rev], set()
332 return self.files[rev], self.copies[rev], set()
337
333
338 def _construct_commit(self, obj, parents=None):
334 def _construct_commit(self, obj, parents=None):
339 """
335 """
340 Constructs a common.commit object from an unmarshalled
336 Constructs a common.commit object from an unmarshalled
341 `p4 describe` output
337 `p4 describe` output
342 """
338 """
343 desc = self.recode(obj.get("desc", ""))
339 desc = self.recode(obj.get("desc", ""))
344 date = (int(obj["time"]), 0) # timezone not set
340 date = (int(obj["time"]), 0) # timezone not set
345 if parents is None:
341 if parents is None:
346 parents = []
342 parents = []
347
343
348 return common.commit(author=self.recode(obj["user"]),
344 return common.commit(author=self.recode(obj["user"]),
349 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
345 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
350 parents=parents, desc=desc, branch=None, rev=obj['change'],
346 parents=parents, desc=desc, branch=None, rev=obj['change'],
351 extra={"p4": obj['change'], "convert_revision": obj['change']})
347 extra={"p4": obj['change'], "convert_revision": obj['change']})
352
348
353 def _fetch_revision(self, rev):
349 def _fetch_revision(self, rev):
354 """Return an output of `p4 describe` including author, commit date as
350 """Return an output of `p4 describe` including author, commit date as
355 a dictionary."""
351 a dictionary."""
356 cmd = "p4 -G describe -s %s" % rev
352 cmd = "p4 -G describe -s %s" % rev
357 stdout = util.popen(cmd, mode='rb')
353 stdout = util.popen(cmd, mode='rb')
358 return marshal.load(stdout)
354 return marshal.load(stdout)
359
355
360 def getcommit(self, rev):
356 def getcommit(self, rev):
361 if rev in self.changeset:
357 if rev in self.changeset:
362 return self.changeset[rev]
358 return self.changeset[rev]
363 elif rev in self.revmap:
359 elif rev in self.revmap:
364 d = self._fetch_revision(rev)
360 d = self._fetch_revision(rev)
365 return self._construct_commit(d, parents=None)
361 return self._construct_commit(d, parents=None)
366 raise error.Abort(
362 raise error.Abort(
367 _("cannot find %s in the revmap or parsed changesets") % rev)
363 _("cannot find %s in the revmap or parsed changesets") % rev)
368
364
369 def gettags(self):
365 def gettags(self):
370 return {}
366 return {}
371
367
372 def getchangedfiles(self, rev, i):
368 def getchangedfiles(self, rev, i):
373 return sorted([x[0] for x in self.files[rev]])
369 return sorted([x[0] for x in self.files[rev]])
General Comments 0
You need to be logged in to leave comments. Login now