##// END OF EJS Templates
convert: add config to not convert tags...
Durham Goode -
r25741:86fe3c40 default
parent child Browse files
Show More
@@ -1,426 +1,434 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 import convcmd
10 import convcmd
11 import cvsps
11 import cvsps
12 import subversion
12 import subversion
13 from mercurial import cmdutil, templatekw
13 from mercurial import cmdutil, templatekw
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 cmdtable = {}
16 cmdtable = {}
17 command = cmdutil.command(cmdtable)
17 command = cmdutil.command(cmdtable)
18 # Note for extension authors: ONLY specify testedwith = 'internal' for
18 # Note for extension authors: ONLY specify testedwith = 'internal' for
19 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
19 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
20 # be specifying the version(s) of Mercurial they are tested with, or
20 # be specifying the version(s) of Mercurial they are tested with, or
21 # leave the attribute unspecified.
21 # leave the attribute unspecified.
22 testedwith = 'internal'
22 testedwith = 'internal'
23
23
24 # Commands definition was moved elsewhere to ease demandload job.
24 # Commands definition was moved elsewhere to ease demandload job.
25
25
26 @command('convert',
26 @command('convert',
27 [('', 'authors', '',
27 [('', 'authors', '',
28 _('username mapping filename (DEPRECATED, use --authormap instead)'),
28 _('username mapping filename (DEPRECATED, use --authormap instead)'),
29 _('FILE')),
29 _('FILE')),
30 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
30 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
31 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
31 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
32 ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
32 ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
33 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
33 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
34 ('', 'filemap', '', _('remap file names using contents of file'),
34 ('', 'filemap', '', _('remap file names using contents of file'),
35 _('FILE')),
35 _('FILE')),
36 ('', 'full', None,
36 ('', 'full', None,
37 _('apply filemap changes by converting all files again')),
37 _('apply filemap changes by converting all files again')),
38 ('', 'splicemap', '', _('splice synthesized history into place'),
38 ('', 'splicemap', '', _('splice synthesized history into place'),
39 _('FILE')),
39 _('FILE')),
40 ('', 'branchmap', '', _('change branch names while converting'),
40 ('', 'branchmap', '', _('change branch names while converting'),
41 _('FILE')),
41 _('FILE')),
42 ('', 'branchsort', None, _('try to sort changesets by branches')),
42 ('', 'branchsort', None, _('try to sort changesets by branches')),
43 ('', 'datesort', None, _('try to sort changesets by date')),
43 ('', 'datesort', None, _('try to sort changesets by date')),
44 ('', 'sourcesort', None, _('preserve source changesets order')),
44 ('', 'sourcesort', None, _('preserve source changesets order')),
45 ('', 'closesort', None, _('try to reorder closed revisions'))],
45 ('', 'closesort', None, _('try to reorder closed revisions'))],
46 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
46 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
47 norepo=True)
47 norepo=True)
48 def convert(ui, src, dest=None, revmapfile=None, **opts):
48 def convert(ui, src, dest=None, revmapfile=None, **opts):
49 """convert a foreign SCM repository to a Mercurial one.
49 """convert a foreign SCM repository to a Mercurial one.
50
50
51 Accepted source formats [identifiers]:
51 Accepted source formats [identifiers]:
52
52
53 - Mercurial [hg]
53 - Mercurial [hg]
54 - CVS [cvs]
54 - CVS [cvs]
55 - Darcs [darcs]
55 - Darcs [darcs]
56 - git [git]
56 - git [git]
57 - Subversion [svn]
57 - Subversion [svn]
58 - Monotone [mtn]
58 - Monotone [mtn]
59 - GNU Arch [gnuarch]
59 - GNU Arch [gnuarch]
60 - Bazaar [bzr]
60 - Bazaar [bzr]
61 - Perforce [p4]
61 - Perforce [p4]
62
62
63 Accepted destination formats [identifiers]:
63 Accepted destination formats [identifiers]:
64
64
65 - Mercurial [hg]
65 - Mercurial [hg]
66 - Subversion [svn] (history on branches is not preserved)
66 - Subversion [svn] (history on branches is not preserved)
67
67
68 If no revision is given, all revisions will be converted.
68 If no revision is given, all revisions will be converted.
69 Otherwise, convert will only import up to the named revision
69 Otherwise, convert will only import up to the named revision
70 (given in a format understood by the source).
70 (given in a format understood by the source).
71
71
72 If no destination directory name is specified, it defaults to the
72 If no destination directory name is specified, it defaults to the
73 basename of the source with ``-hg`` appended. If the destination
73 basename of the source with ``-hg`` appended. If the destination
74 repository doesn't exist, it will be created.
74 repository doesn't exist, it will be created.
75
75
76 By default, all sources except Mercurial will use --branchsort.
76 By default, all sources except Mercurial will use --branchsort.
77 Mercurial uses --sourcesort to preserve original revision numbers
77 Mercurial uses --sourcesort to preserve original revision numbers
78 order. Sort modes have the following effects:
78 order. Sort modes have the following effects:
79
79
80 --branchsort convert from parent to child revision when possible,
80 --branchsort convert from parent to child revision when possible,
81 which means branches are usually converted one after
81 which means branches are usually converted one after
82 the other. It generates more compact repositories.
82 the other. It generates more compact repositories.
83
83
84 --datesort sort revisions by date. Converted repositories have
84 --datesort sort revisions by date. Converted repositories have
85 good-looking changelogs but are often an order of
85 good-looking changelogs but are often an order of
86 magnitude larger than the same ones generated by
86 magnitude larger than the same ones generated by
87 --branchsort.
87 --branchsort.
88
88
89 --sourcesort try to preserve source revisions order, only
89 --sourcesort try to preserve source revisions order, only
90 supported by Mercurial sources.
90 supported by Mercurial sources.
91
91
92 --closesort try to move closed revisions as close as possible
92 --closesort try to move closed revisions as close as possible
93 to parent branches, only supported by Mercurial
93 to parent branches, only supported by Mercurial
94 sources.
94 sources.
95
95
96 If ``REVMAP`` isn't given, it will be put in a default location
96 If ``REVMAP`` isn't given, it will be put in a default location
97 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
97 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
98 text file that maps each source commit ID to the destination ID
98 text file that maps each source commit ID to the destination ID
99 for that revision, like so::
99 for that revision, like so::
100
100
101 <source ID> <destination ID>
101 <source ID> <destination ID>
102
102
103 If the file doesn't exist, it's automatically created. It's
103 If the file doesn't exist, it's automatically created. It's
104 updated on each commit copied, so :hg:`convert` can be interrupted
104 updated on each commit copied, so :hg:`convert` can be interrupted
105 and can be run repeatedly to copy new commits.
105 and can be run repeatedly to copy new commits.
106
106
107 The authormap is a simple text file that maps each source commit
107 The authormap is a simple text file that maps each source commit
108 author to a destination commit author. It is handy for source SCMs
108 author to a destination commit author. It is handy for source SCMs
109 that use unix logins to identify authors (e.g.: CVS). One line per
109 that use unix logins to identify authors (e.g.: CVS). One line per
110 author mapping and the line format is::
110 author mapping and the line format is::
111
111
112 source author = destination author
112 source author = destination author
113
113
114 Empty lines and lines starting with a ``#`` are ignored.
114 Empty lines and lines starting with a ``#`` are ignored.
115
115
116 The filemap is a file that allows filtering and remapping of files
116 The filemap is a file that allows filtering and remapping of files
117 and directories. Each line can contain one of the following
117 and directories. Each line can contain one of the following
118 directives::
118 directives::
119
119
120 include path/to/file-or-dir
120 include path/to/file-or-dir
121
121
122 exclude path/to/file-or-dir
122 exclude path/to/file-or-dir
123
123
124 rename path/to/source path/to/destination
124 rename path/to/source path/to/destination
125
125
126 Comment lines start with ``#``. A specified path matches if it
126 Comment lines start with ``#``. A specified path matches if it
127 equals the full relative name of a file or one of its parent
127 equals the full relative name of a file or one of its parent
128 directories. The ``include`` or ``exclude`` directive with the
128 directories. The ``include`` or ``exclude`` directive with the
129 longest matching path applies, so line order does not matter.
129 longest matching path applies, so line order does not matter.
130
130
131 The ``include`` directive causes a file, or all files under a
131 The ``include`` directive causes a file, or all files under a
132 directory, to be included in the destination repository. The default
132 directory, to be included in the destination repository. The default
133 if there are no ``include`` statements is to include everything.
133 if there are no ``include`` statements is to include everything.
134 If there are any ``include`` statements, nothing else is included.
134 If there are any ``include`` statements, nothing else is included.
135 The ``exclude`` directive causes files or directories to
135 The ``exclude`` directive causes files or directories to
136 be omitted. The ``rename`` directive renames a file or directory if
136 be omitted. The ``rename`` directive renames a file or directory if
137 it is converted. To rename from a subdirectory into the root of
137 it is converted. To rename from a subdirectory into the root of
138 the repository, use ``.`` as the path to rename to.
138 the repository, use ``.`` as the path to rename to.
139
139
140 ``--full`` will make sure the converted changesets contain exactly
140 ``--full`` will make sure the converted changesets contain exactly
141 the right files with the right content. It will make a full
141 the right files with the right content. It will make a full
142 conversion of all files, not just the ones that have
142 conversion of all files, not just the ones that have
143 changed. Files that already are correct will not be changed. This
143 changed. Files that already are correct will not be changed. This
144 can be used to apply filemap changes when converting
144 can be used to apply filemap changes when converting
145 incrementally. This is currently only supported for Mercurial and
145 incrementally. This is currently only supported for Mercurial and
146 Subversion.
146 Subversion.
147
147
148 The splicemap is a file that allows insertion of synthetic
148 The splicemap is a file that allows insertion of synthetic
149 history, letting you specify the parents of a revision. This is
149 history, letting you specify the parents of a revision. This is
150 useful if you want to e.g. give a Subversion merge two parents, or
150 useful if you want to e.g. give a Subversion merge two parents, or
151 graft two disconnected series of history together. Each entry
151 graft two disconnected series of history together. Each entry
152 contains a key, followed by a space, followed by one or two
152 contains a key, followed by a space, followed by one or two
153 comma-separated values::
153 comma-separated values::
154
154
155 key parent1, parent2
155 key parent1, parent2
156
156
157 The key is the revision ID in the source
157 The key is the revision ID in the source
158 revision control system whose parents should be modified (same
158 revision control system whose parents should be modified (same
159 format as a key in .hg/shamap). The values are the revision IDs
159 format as a key in .hg/shamap). The values are the revision IDs
160 (in either the source or destination revision control system) that
160 (in either the source or destination revision control system) that
161 should be used as the new parents for that node. For example, if
161 should be used as the new parents for that node. For example, if
162 you have merged "release-1.0" into "trunk", then you should
162 you have merged "release-1.0" into "trunk", then you should
163 specify the revision on "trunk" as the first parent and the one on
163 specify the revision on "trunk" as the first parent and the one on
164 the "release-1.0" branch as the second.
164 the "release-1.0" branch as the second.
165
165
166 The branchmap is a file that allows you to rename a branch when it is
166 The branchmap is a file that allows you to rename a branch when it is
167 being brought in from whatever external repository. When used in
167 being brought in from whatever external repository. When used in
168 conjunction with a splicemap, it allows for a powerful combination
168 conjunction with a splicemap, it allows for a powerful combination
169 to help fix even the most badly mismanaged repositories and turn them
169 to help fix even the most badly mismanaged repositories and turn them
170 into nicely structured Mercurial repositories. The branchmap contains
170 into nicely structured Mercurial repositories. The branchmap contains
171 lines of the form::
171 lines of the form::
172
172
173 original_branch_name new_branch_name
173 original_branch_name new_branch_name
174
174
175 where "original_branch_name" is the name of the branch in the
175 where "original_branch_name" is the name of the branch in the
176 source repository, and "new_branch_name" is the name of the branch
176 source repository, and "new_branch_name" is the name of the branch
177 is the destination repository. No whitespace is allowed in the
177 is the destination repository. No whitespace is allowed in the
178 branch names. This can be used to (for instance) move code in one
178 branch names. This can be used to (for instance) move code in one
179 repository from "default" to a named branch.
179 repository from "default" to a named branch.
180
180
181 Mercurial Source
181 Mercurial Source
182 ################
182 ################
183
183
184 The Mercurial source recognizes the following configuration
184 The Mercurial source recognizes the following configuration
185 options, which you can set on the command line with ``--config``:
185 options, which you can set on the command line with ``--config``:
186
186
187 :convert.hg.ignoreerrors: ignore integrity errors when reading.
187 :convert.hg.ignoreerrors: ignore integrity errors when reading.
188 Use it to fix Mercurial repositories with missing revlogs, by
188 Use it to fix Mercurial repositories with missing revlogs, by
189 converting from and to Mercurial. Default is False.
189 converting from and to Mercurial. Default is False.
190
190
191 :convert.hg.saverev: store original revision ID in changeset
191 :convert.hg.saverev: store original revision ID in changeset
192 (forces target IDs to change). It takes a boolean argument and
192 (forces target IDs to change). It takes a boolean argument and
193 defaults to False.
193 defaults to False.
194
194
195 :convert.hg.revs: revset specifying the source revisions to convert.
195 :convert.hg.revs: revset specifying the source revisions to convert.
196
196
197 CVS Source
197 CVS Source
198 ##########
198 ##########
199
199
200 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
200 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
201 to indicate the starting point of what will be converted. Direct
201 to indicate the starting point of what will be converted. Direct
202 access to the repository files is not needed, unless of course the
202 access to the repository files is not needed, unless of course the
203 repository is ``:local:``. The conversion uses the top level
203 repository is ``:local:``. The conversion uses the top level
204 directory in the sandbox to find the CVS repository, and then uses
204 directory in the sandbox to find the CVS repository, and then uses
205 CVS rlog commands to find files to convert. This means that unless
205 CVS rlog commands to find files to convert. This means that unless
206 a filemap is given, all files under the starting directory will be
206 a filemap is given, all files under the starting directory will be
207 converted, and that any directory reorganization in the CVS
207 converted, and that any directory reorganization in the CVS
208 sandbox is ignored.
208 sandbox is ignored.
209
209
210 The following options can be used with ``--config``:
210 The following options can be used with ``--config``:
211
211
212 :convert.cvsps.cache: Set to False to disable remote log caching,
212 :convert.cvsps.cache: Set to False to disable remote log caching,
213 for testing and debugging purposes. Default is True.
213 for testing and debugging purposes. Default is True.
214
214
215 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
215 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
216 allowed between commits with identical user and log message in
216 allowed between commits with identical user and log message in
217 a single changeset. When very large files were checked in as
217 a single changeset. When very large files were checked in as
218 part of a changeset then the default may not be long enough.
218 part of a changeset then the default may not be long enough.
219 The default is 60.
219 The default is 60.
220
220
221 :convert.cvsps.mergeto: Specify a regular expression to which
221 :convert.cvsps.mergeto: Specify a regular expression to which
222 commit log messages are matched. If a match occurs, then the
222 commit log messages are matched. If a match occurs, then the
223 conversion process will insert a dummy revision merging the
223 conversion process will insert a dummy revision merging the
224 branch on which this log message occurs to the branch
224 branch on which this log message occurs to the branch
225 indicated in the regex. Default is ``{{mergetobranch
225 indicated in the regex. Default is ``{{mergetobranch
226 ([-\\w]+)}}``
226 ([-\\w]+)}}``
227
227
228 :convert.cvsps.mergefrom: Specify a regular expression to which
228 :convert.cvsps.mergefrom: Specify a regular expression to which
229 commit log messages are matched. If a match occurs, then the
229 commit log messages are matched. If a match occurs, then the
230 conversion process will add the most recent revision on the
230 conversion process will add the most recent revision on the
231 branch indicated in the regex as the second parent of the
231 branch indicated in the regex as the second parent of the
232 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
232 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
233
233
234 :convert.localtimezone: use local time (as determined by the TZ
234 :convert.localtimezone: use local time (as determined by the TZ
235 environment variable) for changeset date/times. The default
235 environment variable) for changeset date/times. The default
236 is False (use UTC).
236 is False (use UTC).
237
237
238 :hooks.cvslog: Specify a Python function to be called at the end of
238 :hooks.cvslog: Specify a Python function to be called at the end of
239 gathering the CVS log. The function is passed a list with the
239 gathering the CVS log. The function is passed a list with the
240 log entries, and can modify the entries in-place, or add or
240 log entries, and can modify the entries in-place, or add or
241 delete them.
241 delete them.
242
242
243 :hooks.cvschangesets: Specify a Python function to be called after
243 :hooks.cvschangesets: Specify a Python function to be called after
244 the changesets are calculated from the CVS log. The
244 the changesets are calculated from the CVS log. The
245 function is passed a list with the changeset entries, and can
245 function is passed a list with the changeset entries, and can
246 modify the changesets in-place, or add or delete them.
246 modify the changesets in-place, or add or delete them.
247
247
248 An additional "debugcvsps" Mercurial command allows the builtin
248 An additional "debugcvsps" Mercurial command allows the builtin
249 changeset merging code to be run without doing a conversion. Its
249 changeset merging code to be run without doing a conversion. Its
250 parameters and output are similar to that of cvsps 2.1. Please see
250 parameters and output are similar to that of cvsps 2.1. Please see
251 the command help for more details.
251 the command help for more details.
252
252
253 Subversion Source
253 Subversion Source
254 #################
254 #################
255
255
256 Subversion source detects classical trunk/branches/tags layouts.
256 Subversion source detects classical trunk/branches/tags layouts.
257 By default, the supplied ``svn://repo/path/`` source URL is
257 By default, the supplied ``svn://repo/path/`` source URL is
258 converted as a single branch. If ``svn://repo/path/trunk`` exists
258 converted as a single branch. If ``svn://repo/path/trunk`` exists
259 it replaces the default branch. If ``svn://repo/path/branches``
259 it replaces the default branch. If ``svn://repo/path/branches``
260 exists, its subdirectories are listed as possible branches. If
260 exists, its subdirectories are listed as possible branches. If
261 ``svn://repo/path/tags`` exists, it is looked for tags referencing
261 ``svn://repo/path/tags`` exists, it is looked for tags referencing
262 converted branches. Default ``trunk``, ``branches`` and ``tags``
262 converted branches. Default ``trunk``, ``branches`` and ``tags``
263 values can be overridden with following options. Set them to paths
263 values can be overridden with following options. Set them to paths
264 relative to the source URL, or leave them blank to disable auto
264 relative to the source URL, or leave them blank to disable auto
265 detection.
265 detection.
266
266
267 The following options can be set with ``--config``:
267 The following options can be set with ``--config``:
268
268
269 :convert.svn.branches: specify the directory containing branches.
269 :convert.svn.branches: specify the directory containing branches.
270 The default is ``branches``.
270 The default is ``branches``.
271
271
272 :convert.svn.tags: specify the directory containing tags. The
272 :convert.svn.tags: specify the directory containing tags. The
273 default is ``tags``.
273 default is ``tags``.
274
274
275 :convert.svn.trunk: specify the name of the trunk branch. The
275 :convert.svn.trunk: specify the name of the trunk branch. The
276 default is ``trunk``.
276 default is ``trunk``.
277
277
278 :convert.localtimezone: use local time (as determined by the TZ
278 :convert.localtimezone: use local time (as determined by the TZ
279 environment variable) for changeset date/times. The default
279 environment variable) for changeset date/times. The default
280 is False (use UTC).
280 is False (use UTC).
281
281
282 Source history can be retrieved starting at a specific revision,
282 Source history can be retrieved starting at a specific revision,
283 instead of being integrally converted. Only single branch
283 instead of being integrally converted. Only single branch
284 conversions are supported.
284 conversions are supported.
285
285
286 :convert.svn.startrev: specify start Subversion revision number.
286 :convert.svn.startrev: specify start Subversion revision number.
287 The default is 0.
287 The default is 0.
288
288
289 Git Source
289 Git Source
290 ##########
290 ##########
291
291
292 The Git importer converts commits from all reachable branches (refs
292 The Git importer converts commits from all reachable branches (refs
293 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
293 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
294 Branches are converted to bookmarks with the same name, with the
294 Branches are converted to bookmarks with the same name, with the
295 leading 'refs/heads' stripped. Git submodules are converted to Git
295 leading 'refs/heads' stripped. Git submodules are converted to Git
296 subrepos in Mercurial.
296 subrepos in Mercurial.
297
297
298 The following options can be set with ``--config``:
298 The following options can be set with ``--config``:
299
299
300 :convert.git.similarity: specify how similar files modified in a
300 :convert.git.similarity: specify how similar files modified in a
301 commit must be to be imported as renames or copies, as a
301 commit must be to be imported as renames or copies, as a
302 percentage between ``0`` (disabled) and ``100`` (files must be
302 percentage between ``0`` (disabled) and ``100`` (files must be
303 identical). For example, ``90`` means that a delete/add pair will
303 identical). For example, ``90`` means that a delete/add pair will
304 be imported as a rename if more than 90% of the file hasn't
304 be imported as a rename if more than 90% of the file hasn't
305 changed. The default is ``50``.
305 changed. The default is ``50``.
306
306
307 :convert.git.findcopiesharder: while detecting copies, look at all
307 :convert.git.findcopiesharder: while detecting copies, look at all
308 files in the working copy instead of just changed ones. This
308 files in the working copy instead of just changed ones. This
309 is very expensive for large projects, and is only effective when
309 is very expensive for large projects, and is only effective when
310 ``convert.git.similarity`` is greater than 0. The default is False.
310 ``convert.git.similarity`` is greater than 0. The default is False.
311
311
312 Perforce Source
312 Perforce Source
313 ###############
313 ###############
314
314
315 The Perforce (P4) importer can be given a p4 depot path or a
315 The Perforce (P4) importer can be given a p4 depot path or a
316 client specification as source. It will convert all files in the
316 client specification as source. It will convert all files in the
317 source to a flat Mercurial repository, ignoring labels, branches
317 source to a flat Mercurial repository, ignoring labels, branches
318 and integrations. Note that when a depot path is given you then
318 and integrations. Note that when a depot path is given you then
319 usually should specify a target directory, because otherwise the
319 usually should specify a target directory, because otherwise the
320 target may be named ``...-hg``.
320 target may be named ``...-hg``.
321
321
322 It is possible to limit the amount of source history to be
322 It is possible to limit the amount of source history to be
323 converted by specifying an initial Perforce revision:
323 converted by specifying an initial Perforce revision:
324
324
325 :convert.p4.startrev: specify initial Perforce revision (a
325 :convert.p4.startrev: specify initial Perforce revision (a
326 Perforce changelist number).
326 Perforce changelist number).
327
327
328 Mercurial Destination
328 Mercurial Destination
329 #####################
329 #####################
330
330
331 The Mercurial destination will recognize Mercurial subrepositories in the
331 The Mercurial destination will recognize Mercurial subrepositories in the
332 destination directory, and update the .hgsubstate file automatically if the
332 destination directory, and update the .hgsubstate file automatically if the
333 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
333 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
334 Converting a repository with subrepositories requires converting a single
334 Converting a repository with subrepositories requires converting a single
335 repository at a time, from the bottom up.
335 repository at a time, from the bottom up.
336
336
337 .. container:: verbose
337 .. container:: verbose
338
338
339 An example showing how to convert a repository with subrepositories::
339 An example showing how to convert a repository with subrepositories::
340
340
341 # so convert knows the type when it sees a non empty destination
341 # so convert knows the type when it sees a non empty destination
342 $ hg init converted
342 $ hg init converted
343
343
344 $ hg convert orig/sub1 converted/sub1
344 $ hg convert orig/sub1 converted/sub1
345 $ hg convert orig/sub2 converted/sub2
345 $ hg convert orig/sub2 converted/sub2
346 $ hg convert orig converted
346 $ hg convert orig converted
347
347
348 The following options are supported:
348 The following options are supported:
349
349
350 :convert.hg.clonebranches: dispatch source branches in separate
350 :convert.hg.clonebranches: dispatch source branches in separate
351 clones. The default is False.
351 clones. The default is False.
352
352
353 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
353 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
354 ``default``.
354 ``default``.
355
355
356 :convert.hg.usebranchnames: preserve branch names. The default is
356 :convert.hg.usebranchnames: preserve branch names. The default is
357 True.
357 True.
358
359 All Destinations
360 ################
361
362 All destination types accept the following options:
363
364 :convert.skiptags: does not convert tags from the source repo to the target
365 repo. The default is False.
358 """
366 """
359 return convcmd.convert(ui, src, dest, revmapfile, **opts)
367 return convcmd.convert(ui, src, dest, revmapfile, **opts)
360
368
361 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
369 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
362 def debugsvnlog(ui, **opts):
370 def debugsvnlog(ui, **opts):
363 return subversion.debugsvnlog(ui, **opts)
371 return subversion.debugsvnlog(ui, **opts)
364
372
365 @command('debugcvsps',
373 @command('debugcvsps',
366 [
374 [
367 # Main options shared with cvsps-2.1
375 # Main options shared with cvsps-2.1
368 ('b', 'branches', [], _('only return changes on specified branches')),
376 ('b', 'branches', [], _('only return changes on specified branches')),
369 ('p', 'prefix', '', _('prefix to remove from file names')),
377 ('p', 'prefix', '', _('prefix to remove from file names')),
370 ('r', 'revisions', [],
378 ('r', 'revisions', [],
371 _('only return changes after or between specified tags')),
379 _('only return changes after or between specified tags')),
372 ('u', 'update-cache', None, _("update cvs log cache")),
380 ('u', 'update-cache', None, _("update cvs log cache")),
373 ('x', 'new-cache', None, _("create new cvs log cache")),
381 ('x', 'new-cache', None, _("create new cvs log cache")),
374 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
382 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
375 ('', 'root', '', _('specify cvsroot')),
383 ('', 'root', '', _('specify cvsroot')),
376 # Options specific to builtin cvsps
384 # Options specific to builtin cvsps
377 ('', 'parents', '', _('show parent changesets')),
385 ('', 'parents', '', _('show parent changesets')),
378 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
386 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
379 # Options that are ignored for compatibility with cvsps-2.1
387 # Options that are ignored for compatibility with cvsps-2.1
380 ('A', 'cvs-direct', None, _('ignored for compatibility')),
388 ('A', 'cvs-direct', None, _('ignored for compatibility')),
381 ],
389 ],
382 _('hg debugcvsps [OPTION]... [PATH]...'),
390 _('hg debugcvsps [OPTION]... [PATH]...'),
383 norepo=True)
391 norepo=True)
384 def debugcvsps(ui, *args, **opts):
392 def debugcvsps(ui, *args, **opts):
385 '''create changeset information from CVS
393 '''create changeset information from CVS
386
394
387 This command is intended as a debugging tool for the CVS to
395 This command is intended as a debugging tool for the CVS to
388 Mercurial converter, and can be used as a direct replacement for
396 Mercurial converter, and can be used as a direct replacement for
389 cvsps.
397 cvsps.
390
398
391 Hg debugcvsps reads the CVS rlog for current directory (or any
399 Hg debugcvsps reads the CVS rlog for current directory (or any
392 named directory) in the CVS repository, and converts the log to a
400 named directory) in the CVS repository, and converts the log to a
393 series of changesets based on matching commit log entries and
401 series of changesets based on matching commit log entries and
394 dates.'''
402 dates.'''
395 return cvsps.debugcvsps(ui, *args, **opts)
403 return cvsps.debugcvsps(ui, *args, **opts)
396
404
397 def kwconverted(ctx, name):
405 def kwconverted(ctx, name):
398 rev = ctx.extra().get('convert_revision', '')
406 rev = ctx.extra().get('convert_revision', '')
399 if rev.startswith('svn:'):
407 if rev.startswith('svn:'):
400 if name == 'svnrev':
408 if name == 'svnrev':
401 return str(subversion.revsplit(rev)[2])
409 return str(subversion.revsplit(rev)[2])
402 elif name == 'svnpath':
410 elif name == 'svnpath':
403 return subversion.revsplit(rev)[1]
411 return subversion.revsplit(rev)[1]
404 elif name == 'svnuuid':
412 elif name == 'svnuuid':
405 return subversion.revsplit(rev)[0]
413 return subversion.revsplit(rev)[0]
406 return rev
414 return rev
407
415
408 def kwsvnrev(repo, ctx, **args):
416 def kwsvnrev(repo, ctx, **args):
409 """:svnrev: String. Converted subversion revision number."""
417 """:svnrev: String. Converted subversion revision number."""
410 return kwconverted(ctx, 'svnrev')
418 return kwconverted(ctx, 'svnrev')
411
419
412 def kwsvnpath(repo, ctx, **args):
420 def kwsvnpath(repo, ctx, **args):
413 """:svnpath: String. Converted subversion revision project path."""
421 """:svnpath: String. Converted subversion revision project path."""
414 return kwconverted(ctx, 'svnpath')
422 return kwconverted(ctx, 'svnpath')
415
423
416 def kwsvnuuid(repo, ctx, **args):
424 def kwsvnuuid(repo, ctx, **args):
417 """:svnuuid: String. Converted subversion revision repository identifier."""
425 """:svnuuid: String. Converted subversion revision repository identifier."""
418 return kwconverted(ctx, 'svnuuid')
426 return kwconverted(ctx, 'svnuuid')
419
427
420 def extsetup(ui):
428 def extsetup(ui):
421 templatekw.keywords['svnrev'] = kwsvnrev
429 templatekw.keywords['svnrev'] = kwsvnrev
422 templatekw.keywords['svnpath'] = kwsvnpath
430 templatekw.keywords['svnpath'] = kwsvnpath
423 templatekw.keywords['svnuuid'] = kwsvnuuid
431 templatekw.keywords['svnuuid'] = kwsvnuuid
424
432
425 # tell hggettext to extract docstrings from these functions:
433 # tell hggettext to extract docstrings from these functions:
426 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
434 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,547 +1,548 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
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 from common import NoRepo, MissingTool, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import svn_source, svn_sink
13 from subversion import svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 from bzr import bzr_source
16 from bzr import bzr_source
17 from p4 import p4_source
17 from p4 import p4_source
18 import filemap
18 import filemap
19
19
20 import os, shutil, shlex
20 import os, shutil, shlex
21 from mercurial import hg, util, encoding
21 from mercurial import hg, util, encoding
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24 orig_encoding = 'ascii'
24 orig_encoding = 'ascii'
25
25
26 def recode(s):
26 def recode(s):
27 if isinstance(s, unicode):
27 if isinstance(s, unicode):
28 return s.encode(orig_encoding, 'replace')
28 return s.encode(orig_encoding, 'replace')
29 else:
29 else:
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
31
31
32 source_converters = [
32 source_converters = [
33 ('cvs', convert_cvs, 'branchsort'),
33 ('cvs', convert_cvs, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
36 ('hg', mercurial_source, 'sourcesort'),
36 ('hg', mercurial_source, 'sourcesort'),
37 ('darcs', darcs_source, 'branchsort'),
37 ('darcs', darcs_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
42 ]
42 ]
43
43
44 sink_converters = [
44 sink_converters = [
45 ('hg', mercurial_sink),
45 ('hg', mercurial_sink),
46 ('svn', svn_sink),
46 ('svn', svn_sink),
47 ]
47 ]
48
48
49 def convertsource(ui, path, type, rev):
49 def convertsource(ui, path, type, rev):
50 exceptions = []
50 exceptions = []
51 if type and type not in [s[0] for s in source_converters]:
51 if type and type not in [s[0] for s in source_converters]:
52 raise util.Abort(_('%s: invalid source repository type') % type)
52 raise util.Abort(_('%s: invalid source repository type') % type)
53 for name, source, sortmode in source_converters:
53 for name, source, sortmode in source_converters:
54 try:
54 try:
55 if not type or name == type:
55 if not type or name == type:
56 return source(ui, path, rev), sortmode
56 return source(ui, path, rev), sortmode
57 except (NoRepo, MissingTool) as inst:
57 except (NoRepo, MissingTool) as inst:
58 exceptions.append(inst)
58 exceptions.append(inst)
59 if not ui.quiet:
59 if not ui.quiet:
60 for inst in exceptions:
60 for inst in exceptions:
61 ui.write("%s\n" % inst)
61 ui.write("%s\n" % inst)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
63
63
64 def convertsink(ui, path, type):
64 def convertsink(ui, path, type):
65 if type and type not in [s[0] for s in sink_converters]:
65 if type and type not in [s[0] for s in sink_converters]:
66 raise util.Abort(_('%s: invalid destination repository type') % type)
66 raise util.Abort(_('%s: invalid destination repository type') % type)
67 for name, sink in sink_converters:
67 for name, sink in sink_converters:
68 try:
68 try:
69 if not type or name == type:
69 if not type or name == type:
70 return sink(ui, path)
70 return sink(ui, path)
71 except NoRepo as inst:
71 except NoRepo as inst:
72 ui.note(_("convert: %s\n") % inst)
72 ui.note(_("convert: %s\n") % inst)
73 except MissingTool as inst:
73 except MissingTool as inst:
74 raise util.Abort('%s\n' % inst)
74 raise util.Abort('%s\n' % inst)
75 raise util.Abort(_('%s: unknown repository type') % path)
75 raise util.Abort(_('%s: unknown repository type') % path)
76
76
77 class progresssource(object):
77 class progresssource(object):
78 def __init__(self, ui, source, filecount):
78 def __init__(self, ui, source, filecount):
79 self.ui = ui
79 self.ui = ui
80 self.source = source
80 self.source = source
81 self.filecount = filecount
81 self.filecount = filecount
82 self.retrieved = 0
82 self.retrieved = 0
83
83
84 def getfile(self, file, rev):
84 def getfile(self, file, rev):
85 self.retrieved += 1
85 self.retrieved += 1
86 self.ui.progress(_('getting files'), self.retrieved,
86 self.ui.progress(_('getting files'), self.retrieved,
87 item=file, total=self.filecount)
87 item=file, total=self.filecount)
88 return self.source.getfile(file, rev)
88 return self.source.getfile(file, rev)
89
89
90 def lookuprev(self, rev):
90 def lookuprev(self, rev):
91 return self.source.lookuprev(rev)
91 return self.source.lookuprev(rev)
92
92
93 def close(self):
93 def close(self):
94 self.ui.progress(_('getting files'), None)
94 self.ui.progress(_('getting files'), None)
95
95
96 class converter(object):
96 class converter(object):
97 def __init__(self, ui, source, dest, revmapfile, opts):
97 def __init__(self, ui, source, dest, revmapfile, opts):
98
98
99 self.source = source
99 self.source = source
100 self.dest = dest
100 self.dest = dest
101 self.ui = ui
101 self.ui = ui
102 self.opts = opts
102 self.opts = opts
103 self.commitcache = {}
103 self.commitcache = {}
104 self.authors = {}
104 self.authors = {}
105 self.authorfile = None
105 self.authorfile = None
106
106
107 # Record converted revisions persistently: maps source revision
107 # Record converted revisions persistently: maps source revision
108 # ID to target revision ID (both strings). (This is how
108 # ID to target revision ID (both strings). (This is how
109 # incremental conversions work.)
109 # incremental conversions work.)
110 self.map = mapfile(ui, revmapfile)
110 self.map = mapfile(ui, revmapfile)
111
111
112 # Read first the dst author map if any
112 # Read first the dst author map if any
113 authorfile = self.dest.authorfile()
113 authorfile = self.dest.authorfile()
114 if authorfile and os.path.exists(authorfile):
114 if authorfile and os.path.exists(authorfile):
115 self.readauthormap(authorfile)
115 self.readauthormap(authorfile)
116 # Extend/Override with new author map if necessary
116 # Extend/Override with new author map if necessary
117 if opts.get('authormap'):
117 if opts.get('authormap'):
118 self.readauthormap(opts.get('authormap'))
118 self.readauthormap(opts.get('authormap'))
119 self.authorfile = self.dest.authorfile()
119 self.authorfile = self.dest.authorfile()
120
120
121 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
121 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
123
123
124 def parsesplicemap(self, path):
124 def parsesplicemap(self, path):
125 """ check and validate the splicemap format and
125 """ check and validate the splicemap format and
126 return a child/parents dictionary.
126 return a child/parents dictionary.
127 Format checking has two parts.
127 Format checking has two parts.
128 1. generic format which is same across all source types
128 1. generic format which is same across all source types
129 2. specific format checking which may be different for
129 2. specific format checking which may be different for
130 different source type. This logic is implemented in
130 different source type. This logic is implemented in
131 checkrevformat function in source files like
131 checkrevformat function in source files like
132 hg.py, subversion.py etc.
132 hg.py, subversion.py etc.
133 """
133 """
134
134
135 if not path:
135 if not path:
136 return {}
136 return {}
137 m = {}
137 m = {}
138 try:
138 try:
139 fp = open(path, 'r')
139 fp = open(path, 'r')
140 for i, line in enumerate(fp):
140 for i, line in enumerate(fp):
141 line = line.splitlines()[0].rstrip()
141 line = line.splitlines()[0].rstrip()
142 if not line:
142 if not line:
143 # Ignore blank lines
143 # Ignore blank lines
144 continue
144 continue
145 # split line
145 # split line
146 lex = shlex.shlex(line, posix=True)
146 lex = shlex.shlex(line, posix=True)
147 lex.whitespace_split = True
147 lex.whitespace_split = True
148 lex.whitespace += ','
148 lex.whitespace += ','
149 line = list(lex)
149 line = list(lex)
150 # check number of parents
150 # check number of parents
151 if not (2 <= len(line) <= 3):
151 if not (2 <= len(line) <= 3):
152 raise util.Abort(_('syntax error in %s(%d): child parent1'
152 raise util.Abort(_('syntax error in %s(%d): child parent1'
153 '[,parent2] expected') % (path, i + 1))
153 '[,parent2] expected') % (path, i + 1))
154 for part in line:
154 for part in line:
155 self.source.checkrevformat(part)
155 self.source.checkrevformat(part)
156 child, p1, p2 = line[0], line[1:2], line[2:]
156 child, p1, p2 = line[0], line[1:2], line[2:]
157 if p1 == p2:
157 if p1 == p2:
158 m[child] = p1
158 m[child] = p1
159 else:
159 else:
160 m[child] = p1 + p2
160 m[child] = p1 + p2
161 # if file does not exist or error reading, exit
161 # if file does not exist or error reading, exit
162 except IOError:
162 except IOError:
163 raise util.Abort(_('splicemap file not found or error reading %s:')
163 raise util.Abort(_('splicemap file not found or error reading %s:')
164 % path)
164 % path)
165 return m
165 return m
166
166
167
167
168 def walktree(self, heads):
168 def walktree(self, heads):
169 '''Return a mapping that identifies the uncommitted parents of every
169 '''Return a mapping that identifies the uncommitted parents of every
170 uncommitted changeset.'''
170 uncommitted changeset.'''
171 visit = heads
171 visit = heads
172 known = set()
172 known = set()
173 parents = {}
173 parents = {}
174 numcommits = self.source.numcommits()
174 numcommits = self.source.numcommits()
175 while visit:
175 while visit:
176 n = visit.pop(0)
176 n = visit.pop(0)
177 if n in known:
177 if n in known:
178 continue
178 continue
179 if n in self.map:
179 if n in self.map:
180 m = self.map[n]
180 m = self.map[n]
181 if m == SKIPREV or self.dest.hascommitfrommap(m):
181 if m == SKIPREV or self.dest.hascommitfrommap(m):
182 continue
182 continue
183 known.add(n)
183 known.add(n)
184 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
184 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
185 total=numcommits)
185 total=numcommits)
186 commit = self.cachecommit(n)
186 commit = self.cachecommit(n)
187 parents[n] = []
187 parents[n] = []
188 for p in commit.parents:
188 for p in commit.parents:
189 parents[n].append(p)
189 parents[n].append(p)
190 visit.append(p)
190 visit.append(p)
191 self.ui.progress(_('scanning'), None)
191 self.ui.progress(_('scanning'), None)
192
192
193 return parents
193 return parents
194
194
195 def mergesplicemap(self, parents, splicemap):
195 def mergesplicemap(self, parents, splicemap):
196 """A splicemap redefines child/parent relationships. Check the
196 """A splicemap redefines child/parent relationships. Check the
197 map contains valid revision identifiers and merge the new
197 map contains valid revision identifiers and merge the new
198 links in the source graph.
198 links in the source graph.
199 """
199 """
200 for c in sorted(splicemap):
200 for c in sorted(splicemap):
201 if c not in parents:
201 if c not in parents:
202 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
202 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
203 # Could be in source but not converted during this run
203 # Could be in source but not converted during this run
204 self.ui.warn(_('splice map revision %s is not being '
204 self.ui.warn(_('splice map revision %s is not being '
205 'converted, ignoring\n') % c)
205 'converted, ignoring\n') % c)
206 continue
206 continue
207 pc = []
207 pc = []
208 for p in splicemap[c]:
208 for p in splicemap[c]:
209 # We do not have to wait for nodes already in dest.
209 # We do not have to wait for nodes already in dest.
210 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
210 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
211 continue
211 continue
212 # Parent is not in dest and not being converted, not good
212 # Parent is not in dest and not being converted, not good
213 if p not in parents:
213 if p not in parents:
214 raise util.Abort(_('unknown splice map parent: %s') % p)
214 raise util.Abort(_('unknown splice map parent: %s') % p)
215 pc.append(p)
215 pc.append(p)
216 parents[c] = pc
216 parents[c] = pc
217
217
218 def toposort(self, parents, sortmode):
218 def toposort(self, parents, sortmode):
219 '''Return an ordering such that every uncommitted changeset is
219 '''Return an ordering such that every uncommitted changeset is
220 preceded by all its uncommitted ancestors.'''
220 preceded by all its uncommitted ancestors.'''
221
221
222 def mapchildren(parents):
222 def mapchildren(parents):
223 """Return a (children, roots) tuple where 'children' maps parent
223 """Return a (children, roots) tuple where 'children' maps parent
224 revision identifiers to children ones, and 'roots' is the list of
224 revision identifiers to children ones, and 'roots' is the list of
225 revisions without parents. 'parents' must be a mapping of revision
225 revisions without parents. 'parents' must be a mapping of revision
226 identifier to its parents ones.
226 identifier to its parents ones.
227 """
227 """
228 visit = sorted(parents)
228 visit = sorted(parents)
229 seen = set()
229 seen = set()
230 children = {}
230 children = {}
231 roots = []
231 roots = []
232
232
233 while visit:
233 while visit:
234 n = visit.pop(0)
234 n = visit.pop(0)
235 if n in seen:
235 if n in seen:
236 continue
236 continue
237 seen.add(n)
237 seen.add(n)
238 # Ensure that nodes without parents are present in the
238 # Ensure that nodes without parents are present in the
239 # 'children' mapping.
239 # 'children' mapping.
240 children.setdefault(n, [])
240 children.setdefault(n, [])
241 hasparent = False
241 hasparent = False
242 for p in parents[n]:
242 for p in parents[n]:
243 if p not in self.map:
243 if p not in self.map:
244 visit.append(p)
244 visit.append(p)
245 hasparent = True
245 hasparent = True
246 children.setdefault(p, []).append(n)
246 children.setdefault(p, []).append(n)
247 if not hasparent:
247 if not hasparent:
248 roots.append(n)
248 roots.append(n)
249
249
250 return children, roots
250 return children, roots
251
251
252 # Sort functions are supposed to take a list of revisions which
252 # Sort functions are supposed to take a list of revisions which
253 # can be converted immediately and pick one
253 # can be converted immediately and pick one
254
254
255 def makebranchsorter():
255 def makebranchsorter():
256 """If the previously converted revision has a child in the
256 """If the previously converted revision has a child in the
257 eligible revisions list, pick it. Return the list head
257 eligible revisions list, pick it. Return the list head
258 otherwise. Branch sort attempts to minimize branch
258 otherwise. Branch sort attempts to minimize branch
259 switching, which is harmful for Mercurial backend
259 switching, which is harmful for Mercurial backend
260 compression.
260 compression.
261 """
261 """
262 prev = [None]
262 prev = [None]
263 def picknext(nodes):
263 def picknext(nodes):
264 next = nodes[0]
264 next = nodes[0]
265 for n in nodes:
265 for n in nodes:
266 if prev[0] in parents[n]:
266 if prev[0] in parents[n]:
267 next = n
267 next = n
268 break
268 break
269 prev[0] = next
269 prev[0] = next
270 return next
270 return next
271 return picknext
271 return picknext
272
272
273 def makesourcesorter():
273 def makesourcesorter():
274 """Source specific sort."""
274 """Source specific sort."""
275 keyfn = lambda n: self.commitcache[n].sortkey
275 keyfn = lambda n: self.commitcache[n].sortkey
276 def picknext(nodes):
276 def picknext(nodes):
277 return sorted(nodes, key=keyfn)[0]
277 return sorted(nodes, key=keyfn)[0]
278 return picknext
278 return picknext
279
279
280 def makeclosesorter():
280 def makeclosesorter():
281 """Close order sort."""
281 """Close order sort."""
282 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
282 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
283 self.commitcache[n].sortkey)
283 self.commitcache[n].sortkey)
284 def picknext(nodes):
284 def picknext(nodes):
285 return sorted(nodes, key=keyfn)[0]
285 return sorted(nodes, key=keyfn)[0]
286 return picknext
286 return picknext
287
287
288 def makedatesorter():
288 def makedatesorter():
289 """Sort revisions by date."""
289 """Sort revisions by date."""
290 dates = {}
290 dates = {}
291 def getdate(n):
291 def getdate(n):
292 if n not in dates:
292 if n not in dates:
293 dates[n] = util.parsedate(self.commitcache[n].date)
293 dates[n] = util.parsedate(self.commitcache[n].date)
294 return dates[n]
294 return dates[n]
295
295
296 def picknext(nodes):
296 def picknext(nodes):
297 return min([(getdate(n), n) for n in nodes])[1]
297 return min([(getdate(n), n) for n in nodes])[1]
298
298
299 return picknext
299 return picknext
300
300
301 if sortmode == 'branchsort':
301 if sortmode == 'branchsort':
302 picknext = makebranchsorter()
302 picknext = makebranchsorter()
303 elif sortmode == 'datesort':
303 elif sortmode == 'datesort':
304 picknext = makedatesorter()
304 picknext = makedatesorter()
305 elif sortmode == 'sourcesort':
305 elif sortmode == 'sourcesort':
306 picknext = makesourcesorter()
306 picknext = makesourcesorter()
307 elif sortmode == 'closesort':
307 elif sortmode == 'closesort':
308 picknext = makeclosesorter()
308 picknext = makeclosesorter()
309 else:
309 else:
310 raise util.Abort(_('unknown sort mode: %s') % sortmode)
310 raise util.Abort(_('unknown sort mode: %s') % sortmode)
311
311
312 children, actives = mapchildren(parents)
312 children, actives = mapchildren(parents)
313
313
314 s = []
314 s = []
315 pendings = {}
315 pendings = {}
316 while actives:
316 while actives:
317 n = picknext(actives)
317 n = picknext(actives)
318 actives.remove(n)
318 actives.remove(n)
319 s.append(n)
319 s.append(n)
320
320
321 # Update dependents list
321 # Update dependents list
322 for c in children.get(n, []):
322 for c in children.get(n, []):
323 if c not in pendings:
323 if c not in pendings:
324 pendings[c] = [p for p in parents[c] if p not in self.map]
324 pendings[c] = [p for p in parents[c] if p not in self.map]
325 try:
325 try:
326 pendings[c].remove(n)
326 pendings[c].remove(n)
327 except ValueError:
327 except ValueError:
328 raise util.Abort(_('cycle detected between %s and %s')
328 raise util.Abort(_('cycle detected between %s and %s')
329 % (recode(c), recode(n)))
329 % (recode(c), recode(n)))
330 if not pendings[c]:
330 if not pendings[c]:
331 # Parents are converted, node is eligible
331 # Parents are converted, node is eligible
332 actives.insert(0, c)
332 actives.insert(0, c)
333 pendings[c] = None
333 pendings[c] = None
334
334
335 if len(s) != len(parents):
335 if len(s) != len(parents):
336 raise util.Abort(_("not all revisions were sorted"))
336 raise util.Abort(_("not all revisions were sorted"))
337
337
338 return s
338 return s
339
339
340 def writeauthormap(self):
340 def writeauthormap(self):
341 authorfile = self.authorfile
341 authorfile = self.authorfile
342 if authorfile:
342 if authorfile:
343 self.ui.status(_('writing author map file %s\n') % authorfile)
343 self.ui.status(_('writing author map file %s\n') % authorfile)
344 ofile = open(authorfile, 'w+')
344 ofile = open(authorfile, 'w+')
345 for author in self.authors:
345 for author in self.authors:
346 ofile.write("%s=%s\n" % (author, self.authors[author]))
346 ofile.write("%s=%s\n" % (author, self.authors[author]))
347 ofile.close()
347 ofile.close()
348
348
349 def readauthormap(self, authorfile):
349 def readauthormap(self, authorfile):
350 afile = open(authorfile, 'r')
350 afile = open(authorfile, 'r')
351 for line in afile:
351 for line in afile:
352
352
353 line = line.strip()
353 line = line.strip()
354 if not line or line.startswith('#'):
354 if not line or line.startswith('#'):
355 continue
355 continue
356
356
357 try:
357 try:
358 srcauthor, dstauthor = line.split('=', 1)
358 srcauthor, dstauthor = line.split('=', 1)
359 except ValueError:
359 except ValueError:
360 msg = _('ignoring bad line in author map file %s: %s\n')
360 msg = _('ignoring bad line in author map file %s: %s\n')
361 self.ui.warn(msg % (authorfile, line.rstrip()))
361 self.ui.warn(msg % (authorfile, line.rstrip()))
362 continue
362 continue
363
363
364 srcauthor = srcauthor.strip()
364 srcauthor = srcauthor.strip()
365 dstauthor = dstauthor.strip()
365 dstauthor = dstauthor.strip()
366 if self.authors.get(srcauthor) in (None, dstauthor):
366 if self.authors.get(srcauthor) in (None, dstauthor):
367 msg = _('mapping author %s to %s\n')
367 msg = _('mapping author %s to %s\n')
368 self.ui.debug(msg % (srcauthor, dstauthor))
368 self.ui.debug(msg % (srcauthor, dstauthor))
369 self.authors[srcauthor] = dstauthor
369 self.authors[srcauthor] = dstauthor
370 continue
370 continue
371
371
372 m = _('overriding mapping for author %s, was %s, will be %s\n')
372 m = _('overriding mapping for author %s, was %s, will be %s\n')
373 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
373 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
374
374
375 afile.close()
375 afile.close()
376
376
377 def cachecommit(self, rev):
377 def cachecommit(self, rev):
378 commit = self.source.getcommit(rev)
378 commit = self.source.getcommit(rev)
379 commit.author = self.authors.get(commit.author, commit.author)
379 commit.author = self.authors.get(commit.author, commit.author)
380 # If commit.branch is None, this commit is coming from the source
380 # If commit.branch is None, this commit is coming from the source
381 # repository's default branch and destined for the default branch in the
381 # repository's default branch and destined for the default branch in the
382 # destination repository. For such commits, passing a literal "None"
382 # destination repository. For such commits, passing a literal "None"
383 # string to branchmap.get() below allows the user to map "None" to an
383 # string to branchmap.get() below allows the user to map "None" to an
384 # alternate default branch in the destination repository.
384 # alternate default branch in the destination repository.
385 commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
385 commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
386 self.commitcache[rev] = commit
386 self.commitcache[rev] = commit
387 return commit
387 return commit
388
388
389 def copy(self, rev):
389 def copy(self, rev):
390 commit = self.commitcache[rev]
390 commit = self.commitcache[rev]
391 full = self.opts.get('full')
391 full = self.opts.get('full')
392 changes = self.source.getchanges(rev, full)
392 changes = self.source.getchanges(rev, full)
393 if isinstance(changes, basestring):
393 if isinstance(changes, basestring):
394 if changes == SKIPREV:
394 if changes == SKIPREV:
395 dest = SKIPREV
395 dest = SKIPREV
396 else:
396 else:
397 dest = self.map[changes]
397 dest = self.map[changes]
398 self.map[rev] = dest
398 self.map[rev] = dest
399 return
399 return
400 files, copies, cleanp2 = changes
400 files, copies, cleanp2 = changes
401 pbranches = []
401 pbranches = []
402 if commit.parents:
402 if commit.parents:
403 for prev in commit.parents:
403 for prev in commit.parents:
404 if prev not in self.commitcache:
404 if prev not in self.commitcache:
405 self.cachecommit(prev)
405 self.cachecommit(prev)
406 pbranches.append((self.map[prev],
406 pbranches.append((self.map[prev],
407 self.commitcache[prev].branch))
407 self.commitcache[prev].branch))
408 self.dest.setbranch(commit.branch, pbranches)
408 self.dest.setbranch(commit.branch, pbranches)
409 try:
409 try:
410 parents = self.splicemap[rev]
410 parents = self.splicemap[rev]
411 self.ui.status(_('spliced in %s as parents of %s\n') %
411 self.ui.status(_('spliced in %s as parents of %s\n') %
412 (parents, rev))
412 (parents, rev))
413 parents = [self.map.get(p, p) for p in parents]
413 parents = [self.map.get(p, p) for p in parents]
414 except KeyError:
414 except KeyError:
415 parents = [b[0] for b in pbranches]
415 parents = [b[0] for b in pbranches]
416 if len(pbranches) != 2:
416 if len(pbranches) != 2:
417 cleanp2 = set()
417 cleanp2 = set()
418 if len(parents) < 3:
418 if len(parents) < 3:
419 source = progresssource(self.ui, self.source, len(files))
419 source = progresssource(self.ui, self.source, len(files))
420 else:
420 else:
421 # For an octopus merge, we end up traversing the list of
421 # For an octopus merge, we end up traversing the list of
422 # changed files N-1 times. This tweak to the number of
422 # changed files N-1 times. This tweak to the number of
423 # files makes it so the progress bar doesn't overflow
423 # files makes it so the progress bar doesn't overflow
424 # itself.
424 # itself.
425 source = progresssource(self.ui, self.source,
425 source = progresssource(self.ui, self.source,
426 len(files) * (len(parents) - 1))
426 len(files) * (len(parents) - 1))
427 newnode = self.dest.putcommit(files, copies, parents, commit,
427 newnode = self.dest.putcommit(files, copies, parents, commit,
428 source, self.map, full, cleanp2)
428 source, self.map, full, cleanp2)
429 source.close()
429 source.close()
430 self.source.converted(rev, newnode)
430 self.source.converted(rev, newnode)
431 self.map[rev] = newnode
431 self.map[rev] = newnode
432
432
433 def convert(self, sortmode):
433 def convert(self, sortmode):
434 try:
434 try:
435 self.source.before()
435 self.source.before()
436 self.dest.before()
436 self.dest.before()
437 self.source.setrevmap(self.map)
437 self.source.setrevmap(self.map)
438 self.ui.status(_("scanning source...\n"))
438 self.ui.status(_("scanning source...\n"))
439 heads = self.source.getheads()
439 heads = self.source.getheads()
440 parents = self.walktree(heads)
440 parents = self.walktree(heads)
441 self.mergesplicemap(parents, self.splicemap)
441 self.mergesplicemap(parents, self.splicemap)
442 self.ui.status(_("sorting...\n"))
442 self.ui.status(_("sorting...\n"))
443 t = self.toposort(parents, sortmode)
443 t = self.toposort(parents, sortmode)
444 num = len(t)
444 num = len(t)
445 c = None
445 c = None
446
446
447 self.ui.status(_("converting...\n"))
447 self.ui.status(_("converting...\n"))
448 for i, c in enumerate(t):
448 for i, c in enumerate(t):
449 num -= 1
449 num -= 1
450 desc = self.commitcache[c].desc
450 desc = self.commitcache[c].desc
451 if "\n" in desc:
451 if "\n" in desc:
452 desc = desc.splitlines()[0]
452 desc = desc.splitlines()[0]
453 # convert log message to local encoding without using
453 # convert log message to local encoding without using
454 # tolocal() because the encoding.encoding convert()
454 # tolocal() because the encoding.encoding convert()
455 # uses is 'utf-8'
455 # uses is 'utf-8'
456 self.ui.status("%d %s\n" % (num, recode(desc)))
456 self.ui.status("%d %s\n" % (num, recode(desc)))
457 self.ui.note(_("source: %s\n") % recode(c))
457 self.ui.note(_("source: %s\n") % recode(c))
458 self.ui.progress(_('converting'), i, unit=_('revisions'),
458 self.ui.progress(_('converting'), i, unit=_('revisions'),
459 total=len(t))
459 total=len(t))
460 self.copy(c)
460 self.copy(c)
461 self.ui.progress(_('converting'), None)
461 self.ui.progress(_('converting'), None)
462
462
463 tags = self.source.gettags()
463 if not self.ui.configbool('convert', 'skiptags'):
464 ctags = {}
464 tags = self.source.gettags()
465 for k in tags:
465 ctags = {}
466 v = tags[k]
466 for k in tags:
467 if self.map.get(v, SKIPREV) != SKIPREV:
467 v = tags[k]
468 ctags[k] = self.map[v]
468 if self.map.get(v, SKIPREV) != SKIPREV:
469 ctags[k] = self.map[v]
469
470
470 if c and ctags:
471 if c and ctags:
471 nrev, tagsparent = self.dest.puttags(ctags)
472 nrev, tagsparent = self.dest.puttags(ctags)
472 if nrev and tagsparent:
473 if nrev and tagsparent:
473 # write another hash correspondence to override the previous
474 # write another hash correspondence to override the
474 # one so we don't end up with extra tag heads
475 # previous one so we don't end up with extra tag heads
475 tagsparents = [e for e in self.map.iteritems()
476 tagsparents = [e for e in self.map.iteritems()
476 if e[1] == tagsparent]
477 if e[1] == tagsparent]
477 if tagsparents:
478 if tagsparents:
478 self.map[tagsparents[0][0]] = nrev
479 self.map[tagsparents[0][0]] = nrev
479
480
480 bookmarks = self.source.getbookmarks()
481 bookmarks = self.source.getbookmarks()
481 cbookmarks = {}
482 cbookmarks = {}
482 for k in bookmarks:
483 for k in bookmarks:
483 v = bookmarks[k]
484 v = bookmarks[k]
484 if self.map.get(v, SKIPREV) != SKIPREV:
485 if self.map.get(v, SKIPREV) != SKIPREV:
485 cbookmarks[k] = self.map[v]
486 cbookmarks[k] = self.map[v]
486
487
487 if c and cbookmarks:
488 if c and cbookmarks:
488 self.dest.putbookmarks(cbookmarks)
489 self.dest.putbookmarks(cbookmarks)
489
490
490 self.writeauthormap()
491 self.writeauthormap()
491 finally:
492 finally:
492 self.cleanup()
493 self.cleanup()
493
494
494 def cleanup(self):
495 def cleanup(self):
495 try:
496 try:
496 self.dest.after()
497 self.dest.after()
497 finally:
498 finally:
498 self.source.after()
499 self.source.after()
499 self.map.close()
500 self.map.close()
500
501
501 def convert(ui, src, dest=None, revmapfile=None, **opts):
502 def convert(ui, src, dest=None, revmapfile=None, **opts):
502 global orig_encoding
503 global orig_encoding
503 orig_encoding = encoding.encoding
504 orig_encoding = encoding.encoding
504 encoding.encoding = 'UTF-8'
505 encoding.encoding = 'UTF-8'
505
506
506 # support --authors as an alias for --authormap
507 # support --authors as an alias for --authormap
507 if not opts.get('authormap'):
508 if not opts.get('authormap'):
508 opts['authormap'] = opts.get('authors')
509 opts['authormap'] = opts.get('authors')
509
510
510 if not dest:
511 if not dest:
511 dest = hg.defaultdest(src) + "-hg"
512 dest = hg.defaultdest(src) + "-hg"
512 ui.status(_("assuming destination %s\n") % dest)
513 ui.status(_("assuming destination %s\n") % dest)
513
514
514 destc = convertsink(ui, dest, opts.get('dest_type'))
515 destc = convertsink(ui, dest, opts.get('dest_type'))
515
516
516 try:
517 try:
517 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
518 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
518 opts.get('rev'))
519 opts.get('rev'))
519 except Exception:
520 except Exception:
520 for path in destc.created:
521 for path in destc.created:
521 shutil.rmtree(path, True)
522 shutil.rmtree(path, True)
522 raise
523 raise
523
524
524 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
525 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
525 sortmode = [m for m in sortmodes if opts.get(m)]
526 sortmode = [m for m in sortmodes if opts.get(m)]
526 if len(sortmode) > 1:
527 if len(sortmode) > 1:
527 raise util.Abort(_('more than one sort mode specified'))
528 raise util.Abort(_('more than one sort mode specified'))
528 if sortmode:
529 if sortmode:
529 sortmode = sortmode[0]
530 sortmode = sortmode[0]
530 else:
531 else:
531 sortmode = defaultsort
532 sortmode = defaultsort
532
533
533 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
534 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
534 raise util.Abort(_('--sourcesort is not supported by this data source'))
535 raise util.Abort(_('--sourcesort is not supported by this data source'))
535 if sortmode == 'closesort' and not srcc.hasnativeclose():
536 if sortmode == 'closesort' and not srcc.hasnativeclose():
536 raise util.Abort(_('--closesort is not supported by this data source'))
537 raise util.Abort(_('--closesort is not supported by this data source'))
537
538
538 fmap = opts.get('filemap')
539 fmap = opts.get('filemap')
539 if fmap:
540 if fmap:
540 srcc = filemap.filemap_source(ui, srcc, fmap)
541 srcc = filemap.filemap_source(ui, srcc, fmap)
541 destc.setfilemapmode(True)
542 destc.setfilemapmode(True)
542
543
543 if not revmapfile:
544 if not revmapfile:
544 revmapfile = destc.revmapfile()
545 revmapfile = destc.revmapfile()
545
546
546 c = converter(ui, srcc, destc, revmapfile, opts)
547 c = converter(ui, srcc, destc, revmapfile, opts)
547 c.convert(sortmode)
548 c.convert(sortmode)
@@ -1,91 +1,104 b''
1 #require git
1 #require git
2
2
3 $ echo "[core]" >> $HOME/.gitconfig
3 $ echo "[core]" >> $HOME/.gitconfig
4 $ echo "autocrlf = false" >> $HOME/.gitconfig
4 $ echo "autocrlf = false" >> $HOME/.gitconfig
5 $ echo "[core]" >> $HOME/.gitconfig
5 $ echo "[core]" >> $HOME/.gitconfig
6 $ echo "autocrlf = false" >> $HOME/.gitconfig
6 $ echo "autocrlf = false" >> $HOME/.gitconfig
7 $ cat <<EOF >> $HGRCPATH
7 $ cat <<EOF >> $HGRCPATH
8 > [extensions]
8 > [extensions]
9 > convert =
9 > convert =
10 > [convert]
10 > [convert]
11 > hg.usebranchnames = True
11 > hg.usebranchnames = True
12 > hg.tagsbranch = tags-update
12 > hg.tagsbranch = tags-update
13 > EOF
13 > EOF
14 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
14 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
15 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
15 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
16 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
16 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
17 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
17 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
18 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
18 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
19 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
19 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
20 $ count=10
20 $ count=10
21 $ action()
21 $ action()
22 > {
22 > {
23 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
23 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
24 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
24 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
25 > git "$@" >/dev/null 2>/dev/null || echo "git command error"
25 > git "$@" >/dev/null 2>/dev/null || echo "git command error"
26 > count=`expr $count + 1`
26 > count=`expr $count + 1`
27 > }
27 > }
28 $ glog()
28 $ glog()
29 > {
29 > {
30 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
30 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
31 > }
31 > }
32 $ convertrepo()
32 $ convertrepo()
33 > {
33 > {
34 > hg convert --datesort git-repo hg-repo
34 > hg convert --datesort git-repo hg-repo
35 > }
35 > }
36
36
37 Build a GIT repo with at least 1 tag
37 Build a GIT repo with at least 1 tag
38
38
39 $ mkdir git-repo
39 $ mkdir git-repo
40 $ cd git-repo
40 $ cd git-repo
41 $ git init >/dev/null 2>&1
41 $ git init >/dev/null 2>&1
42 $ echo a > a
42 $ echo a > a
43 $ git add a
43 $ git add a
44 $ action commit -m "rev1"
44 $ action commit -m "rev1"
45 $ action tag -m "tag1" tag1
45 $ action tag -m "tag1" tag1
46 $ cd ..
46 $ cd ..
47
47
48 Convert without tags
49
50 $ hg convert git-repo hg-repo --config convert.skiptags=True
51 initializing destination hg-repo repository
52 scanning source...
53 sorting...
54 converting...
55 0 rev1
56 updating bookmarks
57 $ hg -R hg-repo tags
58 tip 0:d98c8ad3a4cf
59 $ rm -rf hg-repo
60
48 Do a first conversion
61 Do a first conversion
49
62
50 $ convertrepo
63 $ convertrepo
51 initializing destination hg-repo repository
64 initializing destination hg-repo repository
52 scanning source...
65 scanning source...
53 sorting...
66 sorting...
54 converting...
67 converting...
55 0 rev1
68 0 rev1
56 updating tags
69 updating tags
57 updating bookmarks
70 updating bookmarks
58
71
59 Simulate upstream updates after first conversion
72 Simulate upstream updates after first conversion
60
73
61 $ cd git-repo
74 $ cd git-repo
62 $ echo b > a
75 $ echo b > a
63 $ git add a
76 $ git add a
64 $ action commit -m "rev2"
77 $ action commit -m "rev2"
65 $ action tag -m "tag2" tag2
78 $ action tag -m "tag2" tag2
66 $ cd ..
79 $ cd ..
67
80
68 Perform an incremental conversion
81 Perform an incremental conversion
69
82
70 $ convertrepo
83 $ convertrepo
71 scanning source...
84 scanning source...
72 sorting...
85 sorting...
73 converting...
86 converting...
74 0 rev2
87 0 rev2
75 updating tags
88 updating tags
76 updating bookmarks
89 updating bookmarks
77
90
78 Print the log
91 Print the log
79
92
80 $ cd hg-repo
93 $ cd hg-repo
81 $ glog
94 $ glog
82 o 3 "update tags" files: .hgtags
95 o 3 "update tags" files: .hgtags
83 |
96 |
84 | o 2 "rev2" files: a
97 | o 2 "rev2" files: a
85 | |
98 | |
86 o | 1 "update tags" files: .hgtags
99 o | 1 "update tags" files: .hgtags
87 /
100 /
88 o 0 "rev1" files: a
101 o 0 "rev1" files: a
89
102
90
103
91 $ cd ..
104 $ cd ..
@@ -1,496 +1,505 b''
1 $ cat >> $HGRCPATH <<EOF
1 $ cat >> $HGRCPATH <<EOF
2 > [extensions]
2 > [extensions]
3 > convert=
3 > convert=
4 > [convert]
4 > [convert]
5 > hg.saverev=False
5 > hg.saverev=False
6 > EOF
6 > EOF
7 $ hg help convert
7 $ hg help convert
8 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
8 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
9
9
10 convert a foreign SCM repository to a Mercurial one.
10 convert a foreign SCM repository to a Mercurial one.
11
11
12 Accepted source formats [identifiers]:
12 Accepted source formats [identifiers]:
13
13
14 - Mercurial [hg]
14 - Mercurial [hg]
15 - CVS [cvs]
15 - CVS [cvs]
16 - Darcs [darcs]
16 - Darcs [darcs]
17 - git [git]
17 - git [git]
18 - Subversion [svn]
18 - Subversion [svn]
19 - Monotone [mtn]
19 - Monotone [mtn]
20 - GNU Arch [gnuarch]
20 - GNU Arch [gnuarch]
21 - Bazaar [bzr]
21 - Bazaar [bzr]
22 - Perforce [p4]
22 - Perforce [p4]
23
23
24 Accepted destination formats [identifiers]:
24 Accepted destination formats [identifiers]:
25
25
26 - Mercurial [hg]
26 - Mercurial [hg]
27 - Subversion [svn] (history on branches is not preserved)
27 - Subversion [svn] (history on branches is not preserved)
28
28
29 If no revision is given, all revisions will be converted. Otherwise,
29 If no revision is given, all revisions will be converted. Otherwise,
30 convert will only import up to the named revision (given in a format
30 convert will only import up to the named revision (given in a format
31 understood by the source).
31 understood by the source).
32
32
33 If no destination directory name is specified, it defaults to the basename
33 If no destination directory name is specified, it defaults to the basename
34 of the source with "-hg" appended. If the destination repository doesn't
34 of the source with "-hg" appended. If the destination repository doesn't
35 exist, it will be created.
35 exist, it will be created.
36
36
37 By default, all sources except Mercurial will use --branchsort. Mercurial
37 By default, all sources except Mercurial will use --branchsort. Mercurial
38 uses --sourcesort to preserve original revision numbers order. Sort modes
38 uses --sourcesort to preserve original revision numbers order. Sort modes
39 have the following effects:
39 have the following effects:
40
40
41 --branchsort convert from parent to child revision when possible, which
41 --branchsort convert from parent to child revision when possible, which
42 means branches are usually converted one after the other.
42 means branches are usually converted one after the other.
43 It generates more compact repositories.
43 It generates more compact repositories.
44 --datesort sort revisions by date. Converted repositories have good-
44 --datesort sort revisions by date. Converted repositories have good-
45 looking changelogs but are often an order of magnitude
45 looking changelogs but are often an order of magnitude
46 larger than the same ones generated by --branchsort.
46 larger than the same ones generated by --branchsort.
47 --sourcesort try to preserve source revisions order, only supported by
47 --sourcesort try to preserve source revisions order, only supported by
48 Mercurial sources.
48 Mercurial sources.
49 --closesort try to move closed revisions as close as possible to parent
49 --closesort try to move closed revisions as close as possible to parent
50 branches, only supported by Mercurial sources.
50 branches, only supported by Mercurial sources.
51
51
52 If "REVMAP" isn't given, it will be put in a default location
52 If "REVMAP" isn't given, it will be put in a default location
53 ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
53 ("<dest>/.hg/shamap" by default). The "REVMAP" is a simple text file that
54 maps each source commit ID to the destination ID for that revision, like
54 maps each source commit ID to the destination ID for that revision, like
55 so:
55 so:
56
56
57 <source ID> <destination ID>
57 <source ID> <destination ID>
58
58
59 If the file doesn't exist, it's automatically created. It's updated on
59 If the file doesn't exist, it's automatically created. It's updated on
60 each commit copied, so "hg convert" can be interrupted and can be run
60 each commit copied, so "hg convert" can be interrupted and can be run
61 repeatedly to copy new commits.
61 repeatedly to copy new commits.
62
62
63 The authormap is a simple text file that maps each source commit author to
63 The authormap is a simple text file that maps each source commit author to
64 a destination commit author. It is handy for source SCMs that use unix
64 a destination commit author. It is handy for source SCMs that use unix
65 logins to identify authors (e.g.: CVS). One line per author mapping and
65 logins to identify authors (e.g.: CVS). One line per author mapping and
66 the line format is:
66 the line format is:
67
67
68 source author = destination author
68 source author = destination author
69
69
70 Empty lines and lines starting with a "#" are ignored.
70 Empty lines and lines starting with a "#" are ignored.
71
71
72 The filemap is a file that allows filtering and remapping of files and
72 The filemap is a file that allows filtering and remapping of files and
73 directories. Each line can contain one of the following directives:
73 directories. Each line can contain one of the following directives:
74
74
75 include path/to/file-or-dir
75 include path/to/file-or-dir
76
76
77 exclude path/to/file-or-dir
77 exclude path/to/file-or-dir
78
78
79 rename path/to/source path/to/destination
79 rename path/to/source path/to/destination
80
80
81 Comment lines start with "#". A specified path matches if it equals the
81 Comment lines start with "#". A specified path matches if it equals the
82 full relative name of a file or one of its parent directories. The
82 full relative name of a file or one of its parent directories. The
83 "include" or "exclude" directive with the longest matching path applies,
83 "include" or "exclude" directive with the longest matching path applies,
84 so line order does not matter.
84 so line order does not matter.
85
85
86 The "include" directive causes a file, or all files under a directory, to
86 The "include" directive causes a file, or all files under a directory, to
87 be included in the destination repository. The default if there are no
87 be included in the destination repository. The default if there are no
88 "include" statements is to include everything. If there are any "include"
88 "include" statements is to include everything. If there are any "include"
89 statements, nothing else is included. The "exclude" directive causes files
89 statements, nothing else is included. The "exclude" directive causes files
90 or directories to be omitted. The "rename" directive renames a file or
90 or directories to be omitted. The "rename" directive renames a file or
91 directory if it is converted. To rename from a subdirectory into the root
91 directory if it is converted. To rename from a subdirectory into the root
92 of the repository, use "." as the path to rename to.
92 of the repository, use "." as the path to rename to.
93
93
94 "--full" will make sure the converted changesets contain exactly the right
94 "--full" will make sure the converted changesets contain exactly the right
95 files with the right content. It will make a full conversion of all files,
95 files with the right content. It will make a full conversion of all files,
96 not just the ones that have changed. Files that already are correct will
96 not just the ones that have changed. Files that already are correct will
97 not be changed. This can be used to apply filemap changes when converting
97 not be changed. This can be used to apply filemap changes when converting
98 incrementally. This is currently only supported for Mercurial and
98 incrementally. This is currently only supported for Mercurial and
99 Subversion.
99 Subversion.
100
100
101 The splicemap is a file that allows insertion of synthetic history,
101 The splicemap is a file that allows insertion of synthetic history,
102 letting you specify the parents of a revision. This is useful if you want
102 letting you specify the parents of a revision. This is useful if you want
103 to e.g. give a Subversion merge two parents, or graft two disconnected
103 to e.g. give a Subversion merge two parents, or graft two disconnected
104 series of history together. Each entry contains a key, followed by a
104 series of history together. Each entry contains a key, followed by a
105 space, followed by one or two comma-separated values:
105 space, followed by one or two comma-separated values:
106
106
107 key parent1, parent2
107 key parent1, parent2
108
108
109 The key is the revision ID in the source revision control system whose
109 The key is the revision ID in the source revision control system whose
110 parents should be modified (same format as a key in .hg/shamap). The
110 parents should be modified (same format as a key in .hg/shamap). The
111 values are the revision IDs (in either the source or destination revision
111 values are the revision IDs (in either the source or destination revision
112 control system) that should be used as the new parents for that node. For
112 control system) that should be used as the new parents for that node. For
113 example, if you have merged "release-1.0" into "trunk", then you should
113 example, if you have merged "release-1.0" into "trunk", then you should
114 specify the revision on "trunk" as the first parent and the one on the
114 specify the revision on "trunk" as the first parent and the one on the
115 "release-1.0" branch as the second.
115 "release-1.0" branch as the second.
116
116
117 The branchmap is a file that allows you to rename a branch when it is
117 The branchmap is a file that allows you to rename a branch when it is
118 being brought in from whatever external repository. When used in
118 being brought in from whatever external repository. When used in
119 conjunction with a splicemap, it allows for a powerful combination to help
119 conjunction with a splicemap, it allows for a powerful combination to help
120 fix even the most badly mismanaged repositories and turn them into nicely
120 fix even the most badly mismanaged repositories and turn them into nicely
121 structured Mercurial repositories. The branchmap contains lines of the
121 structured Mercurial repositories. The branchmap contains lines of the
122 form:
122 form:
123
123
124 original_branch_name new_branch_name
124 original_branch_name new_branch_name
125
125
126 where "original_branch_name" is the name of the branch in the source
126 where "original_branch_name" is the name of the branch in the source
127 repository, and "new_branch_name" is the name of the branch is the
127 repository, and "new_branch_name" is the name of the branch is the
128 destination repository. No whitespace is allowed in the branch names. This
128 destination repository. No whitespace is allowed in the branch names. This
129 can be used to (for instance) move code in one repository from "default"
129 can be used to (for instance) move code in one repository from "default"
130 to a named branch.
130 to a named branch.
131
131
132 Mercurial Source
132 Mercurial Source
133 ################
133 ################
134
134
135 The Mercurial source recognizes the following configuration options, which
135 The Mercurial source recognizes the following configuration options, which
136 you can set on the command line with "--config":
136 you can set on the command line with "--config":
137
137
138 convert.hg.ignoreerrors
138 convert.hg.ignoreerrors
139 ignore integrity errors when reading. Use it to fix
139 ignore integrity errors when reading. Use it to fix
140 Mercurial repositories with missing revlogs, by converting
140 Mercurial repositories with missing revlogs, by converting
141 from and to Mercurial. Default is False.
141 from and to Mercurial. Default is False.
142 convert.hg.saverev
142 convert.hg.saverev
143 store original revision ID in changeset (forces target IDs
143 store original revision ID in changeset (forces target IDs
144 to change). It takes a boolean argument and defaults to
144 to change). It takes a boolean argument and defaults to
145 False.
145 False.
146 convert.hg.revs
146 convert.hg.revs
147 revset specifying the source revisions to convert.
147 revset specifying the source revisions to convert.
148
148
149 CVS Source
149 CVS Source
150 ##########
150 ##########
151
151
152 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
152 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
153 indicate the starting point of what will be converted. Direct access to
153 indicate the starting point of what will be converted. Direct access to
154 the repository files is not needed, unless of course the repository is
154 the repository files is not needed, unless of course the repository is
155 ":local:". The conversion uses the top level directory in the sandbox to
155 ":local:". The conversion uses the top level directory in the sandbox to
156 find the CVS repository, and then uses CVS rlog commands to find files to
156 find the CVS repository, and then uses CVS rlog commands to find files to
157 convert. This means that unless a filemap is given, all files under the
157 convert. This means that unless a filemap is given, all files under the
158 starting directory will be converted, and that any directory
158 starting directory will be converted, and that any directory
159 reorganization in the CVS sandbox is ignored.
159 reorganization in the CVS sandbox is ignored.
160
160
161 The following options can be used with "--config":
161 The following options can be used with "--config":
162
162
163 convert.cvsps.cache
163 convert.cvsps.cache
164 Set to False to disable remote log caching, for testing and
164 Set to False to disable remote log caching, for testing and
165 debugging purposes. Default is True.
165 debugging purposes. Default is True.
166 convert.cvsps.fuzz
166 convert.cvsps.fuzz
167 Specify the maximum time (in seconds) that is allowed
167 Specify the maximum time (in seconds) that is allowed
168 between commits with identical user and log message in a
168 between commits with identical user and log message in a
169 single changeset. When very large files were checked in as
169 single changeset. When very large files were checked in as
170 part of a changeset then the default may not be long enough.
170 part of a changeset then the default may not be long enough.
171 The default is 60.
171 The default is 60.
172 convert.cvsps.mergeto
172 convert.cvsps.mergeto
173 Specify a regular expression to which commit log messages
173 Specify a regular expression to which commit log messages
174 are matched. If a match occurs, then the conversion process
174 are matched. If a match occurs, then the conversion process
175 will insert a dummy revision merging the branch on which
175 will insert a dummy revision merging the branch on which
176 this log message occurs to the branch indicated in the
176 this log message occurs to the branch indicated in the
177 regex. Default is "{{mergetobranch ([-\w]+)}}"
177 regex. Default is "{{mergetobranch ([-\w]+)}}"
178 convert.cvsps.mergefrom
178 convert.cvsps.mergefrom
179 Specify a regular expression to which commit log messages
179 Specify a regular expression to which commit log messages
180 are matched. If a match occurs, then the conversion process
180 are matched. If a match occurs, then the conversion process
181 will add the most recent revision on the branch indicated in
181 will add the most recent revision on the branch indicated in
182 the regex as the second parent of the changeset. Default is
182 the regex as the second parent of the changeset. Default is
183 "{{mergefrombranch ([-\w]+)}}"
183 "{{mergefrombranch ([-\w]+)}}"
184 convert.localtimezone
184 convert.localtimezone
185 use local time (as determined by the TZ environment
185 use local time (as determined by the TZ environment
186 variable) for changeset date/times. The default is False
186 variable) for changeset date/times. The default is False
187 (use UTC).
187 (use UTC).
188 hooks.cvslog Specify a Python function to be called at the end of
188 hooks.cvslog Specify a Python function to be called at the end of
189 gathering the CVS log. The function is passed a list with
189 gathering the CVS log. The function is passed a list with
190 the log entries, and can modify the entries in-place, or add
190 the log entries, and can modify the entries in-place, or add
191 or delete them.
191 or delete them.
192 hooks.cvschangesets
192 hooks.cvschangesets
193 Specify a Python function to be called after the changesets
193 Specify a Python function to be called after the changesets
194 are calculated from the CVS log. The function is passed a
194 are calculated from the CVS log. The function is passed a
195 list with the changeset entries, and can modify the
195 list with the changeset entries, and can modify the
196 changesets in-place, or add or delete them.
196 changesets in-place, or add or delete them.
197
197
198 An additional "debugcvsps" Mercurial command allows the builtin changeset
198 An additional "debugcvsps" Mercurial command allows the builtin changeset
199 merging code to be run without doing a conversion. Its parameters and
199 merging code to be run without doing a conversion. Its parameters and
200 output are similar to that of cvsps 2.1. Please see the command help for
200 output are similar to that of cvsps 2.1. Please see the command help for
201 more details.
201 more details.
202
202
203 Subversion Source
203 Subversion Source
204 #################
204 #################
205
205
206 Subversion source detects classical trunk/branches/tags layouts. By
206 Subversion source detects classical trunk/branches/tags layouts. By
207 default, the supplied "svn://repo/path/" source URL is converted as a
207 default, the supplied "svn://repo/path/" source URL is converted as a
208 single branch. If "svn://repo/path/trunk" exists it replaces the default
208 single branch. If "svn://repo/path/trunk" exists it replaces the default
209 branch. If "svn://repo/path/branches" exists, its subdirectories are
209 branch. If "svn://repo/path/branches" exists, its subdirectories are
210 listed as possible branches. If "svn://repo/path/tags" exists, it is
210 listed as possible branches. If "svn://repo/path/tags" exists, it is
211 looked for tags referencing converted branches. Default "trunk",
211 looked for tags referencing converted branches. Default "trunk",
212 "branches" and "tags" values can be overridden with following options. Set
212 "branches" and "tags" values can be overridden with following options. Set
213 them to paths relative to the source URL, or leave them blank to disable
213 them to paths relative to the source URL, or leave them blank to disable
214 auto detection.
214 auto detection.
215
215
216 The following options can be set with "--config":
216 The following options can be set with "--config":
217
217
218 convert.svn.branches
218 convert.svn.branches
219 specify the directory containing branches. The default is
219 specify the directory containing branches. The default is
220 "branches".
220 "branches".
221 convert.svn.tags
221 convert.svn.tags
222 specify the directory containing tags. The default is
222 specify the directory containing tags. The default is
223 "tags".
223 "tags".
224 convert.svn.trunk
224 convert.svn.trunk
225 specify the name of the trunk branch. The default is
225 specify the name of the trunk branch. The default is
226 "trunk".
226 "trunk".
227 convert.localtimezone
227 convert.localtimezone
228 use local time (as determined by the TZ environment
228 use local time (as determined by the TZ environment
229 variable) for changeset date/times. The default is False
229 variable) for changeset date/times. The default is False
230 (use UTC).
230 (use UTC).
231
231
232 Source history can be retrieved starting at a specific revision, instead
232 Source history can be retrieved starting at a specific revision, instead
233 of being integrally converted. Only single branch conversions are
233 of being integrally converted. Only single branch conversions are
234 supported.
234 supported.
235
235
236 convert.svn.startrev
236 convert.svn.startrev
237 specify start Subversion revision number. The default is 0.
237 specify start Subversion revision number. The default is 0.
238
238
239 Git Source
239 Git Source
240 ##########
240 ##########
241
241
242 The Git importer converts commits from all reachable branches (refs in
242 The Git importer converts commits from all reachable branches (refs in
243 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
243 refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
244 converted to bookmarks with the same name, with the leading 'refs/heads'
244 converted to bookmarks with the same name, with the leading 'refs/heads'
245 stripped. Git submodules are converted to Git subrepos in Mercurial.
245 stripped. Git submodules are converted to Git subrepos in Mercurial.
246
246
247 The following options can be set with "--config":
247 The following options can be set with "--config":
248
248
249 convert.git.similarity
249 convert.git.similarity
250 specify how similar files modified in a commit must be to be
250 specify how similar files modified in a commit must be to be
251 imported as renames or copies, as a percentage between "0"
251 imported as renames or copies, as a percentage between "0"
252 (disabled) and "100" (files must be identical). For example,
252 (disabled) and "100" (files must be identical). For example,
253 "90" means that a delete/add pair will be imported as a
253 "90" means that a delete/add pair will be imported as a
254 rename if more than 90% of the file hasn't changed. The
254 rename if more than 90% of the file hasn't changed. The
255 default is "50".
255 default is "50".
256 convert.git.findcopiesharder
256 convert.git.findcopiesharder
257 while detecting copies, look at all files in the working
257 while detecting copies, look at all files in the working
258 copy instead of just changed ones. This is very expensive
258 copy instead of just changed ones. This is very expensive
259 for large projects, and is only effective when
259 for large projects, and is only effective when
260 "convert.git.similarity" is greater than 0. The default is
260 "convert.git.similarity" is greater than 0. The default is
261 False.
261 False.
262
262
263 Perforce Source
263 Perforce Source
264 ###############
264 ###############
265
265
266 The Perforce (P4) importer can be given a p4 depot path or a client
266 The Perforce (P4) importer can be given a p4 depot path or a client
267 specification as source. It will convert all files in the source to a flat
267 specification as source. It will convert all files in the source to a flat
268 Mercurial repository, ignoring labels, branches and integrations. Note
268 Mercurial repository, ignoring labels, branches and integrations. Note
269 that when a depot path is given you then usually should specify a target
269 that when a depot path is given you then usually should specify a target
270 directory, because otherwise the target may be named "...-hg".
270 directory, because otherwise the target may be named "...-hg".
271
271
272 It is possible to limit the amount of source history to be converted by
272 It is possible to limit the amount of source history to be converted by
273 specifying an initial Perforce revision:
273 specifying an initial Perforce revision:
274
274
275 convert.p4.startrev
275 convert.p4.startrev
276 specify initial Perforce revision (a Perforce changelist
276 specify initial Perforce revision (a Perforce changelist
277 number).
277 number).
278
278
279 Mercurial Destination
279 Mercurial Destination
280 #####################
280 #####################
281
281
282 The Mercurial destination will recognize Mercurial subrepositories in the
282 The Mercurial destination will recognize Mercurial subrepositories in the
283 destination directory, and update the .hgsubstate file automatically if
283 destination directory, and update the .hgsubstate file automatically if
284 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
284 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
285 Converting a repository with subrepositories requires converting a single
285 Converting a repository with subrepositories requires converting a single
286 repository at a time, from the bottom up.
286 repository at a time, from the bottom up.
287
287
288 The following options are supported:
288 The following options are supported:
289
289
290 convert.hg.clonebranches
290 convert.hg.clonebranches
291 dispatch source branches in separate clones. The default is
291 dispatch source branches in separate clones. The default is
292 False.
292 False.
293 convert.hg.tagsbranch
293 convert.hg.tagsbranch
294 branch name for tag revisions, defaults to "default".
294 branch name for tag revisions, defaults to "default".
295 convert.hg.usebranchnames
295 convert.hg.usebranchnames
296 preserve branch names. The default is True.
296 preserve branch names. The default is True.
297
297
298 All Destinations
299 ################
300
301 All destination types accept the following options:
302
303 convert.skiptags
304 does not convert tags from the source repo to the target
305 repo. The default is False.
306
298 options:
307 options:
299
308
300 -s --source-type TYPE source repository type
309 -s --source-type TYPE source repository type
301 -d --dest-type TYPE destination repository type
310 -d --dest-type TYPE destination repository type
302 -r --rev REV import up to source revision REV
311 -r --rev REV import up to source revision REV
303 -A --authormap FILE remap usernames using this file
312 -A --authormap FILE remap usernames using this file
304 --filemap FILE remap file names using contents of file
313 --filemap FILE remap file names using contents of file
305 --full apply filemap changes by converting all files again
314 --full apply filemap changes by converting all files again
306 --splicemap FILE splice synthesized history into place
315 --splicemap FILE splice synthesized history into place
307 --branchmap FILE change branch names while converting
316 --branchmap FILE change branch names while converting
308 --branchsort try to sort changesets by branches
317 --branchsort try to sort changesets by branches
309 --datesort try to sort changesets by date
318 --datesort try to sort changesets by date
310 --sourcesort preserve source changesets order
319 --sourcesort preserve source changesets order
311 --closesort try to reorder closed revisions
320 --closesort try to reorder closed revisions
312
321
313 (some details hidden, use --verbose to show complete help)
322 (some details hidden, use --verbose to show complete help)
314 $ hg init a
323 $ hg init a
315 $ cd a
324 $ cd a
316 $ echo a > a
325 $ echo a > a
317 $ hg ci -d'0 0' -Ama
326 $ hg ci -d'0 0' -Ama
318 adding a
327 adding a
319 $ hg cp a b
328 $ hg cp a b
320 $ hg ci -d'1 0' -mb
329 $ hg ci -d'1 0' -mb
321 $ hg rm a
330 $ hg rm a
322 $ hg ci -d'2 0' -mc
331 $ hg ci -d'2 0' -mc
323 $ hg mv b a
332 $ hg mv b a
324 $ hg ci -d'3 0' -md
333 $ hg ci -d'3 0' -md
325 $ echo a >> a
334 $ echo a >> a
326 $ hg ci -d'4 0' -me
335 $ hg ci -d'4 0' -me
327 $ cd ..
336 $ cd ..
328 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
337 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
329 assuming destination a-hg
338 assuming destination a-hg
330 initializing destination a-hg repository
339 initializing destination a-hg repository
331 scanning source...
340 scanning source...
332 sorting...
341 sorting...
333 converting...
342 converting...
334 4 a
343 4 a
335 3 b
344 3 b
336 2 c
345 2 c
337 1 d
346 1 d
338 0 e
347 0 e
339 $ hg --cwd a-hg pull ../a
348 $ hg --cwd a-hg pull ../a
340 pulling from ../a
349 pulling from ../a
341 searching for changes
350 searching for changes
342 no changes found
351 no changes found
343
352
344 conversion to existing file should fail
353 conversion to existing file should fail
345
354
346 $ touch bogusfile
355 $ touch bogusfile
347 $ hg convert a bogusfile
356 $ hg convert a bogusfile
348 initializing destination bogusfile repository
357 initializing destination bogusfile repository
349 abort: cannot create new bundle repository
358 abort: cannot create new bundle repository
350 [255]
359 [255]
351
360
352 #if unix-permissions no-root
361 #if unix-permissions no-root
353
362
354 conversion to dir without permissions should fail
363 conversion to dir without permissions should fail
355
364
356 $ mkdir bogusdir
365 $ mkdir bogusdir
357 $ chmod 000 bogusdir
366 $ chmod 000 bogusdir
358
367
359 $ hg convert a bogusdir
368 $ hg convert a bogusdir
360 abort: Permission denied: 'bogusdir'
369 abort: Permission denied: 'bogusdir'
361 [255]
370 [255]
362
371
363 user permissions should succeed
372 user permissions should succeed
364
373
365 $ chmod 700 bogusdir
374 $ chmod 700 bogusdir
366 $ hg convert a bogusdir
375 $ hg convert a bogusdir
367 initializing destination bogusdir repository
376 initializing destination bogusdir repository
368 scanning source...
377 scanning source...
369 sorting...
378 sorting...
370 converting...
379 converting...
371 4 a
380 4 a
372 3 b
381 3 b
373 2 c
382 2 c
374 1 d
383 1 d
375 0 e
384 0 e
376
385
377 #endif
386 #endif
378
387
379 test pre and post conversion actions
388 test pre and post conversion actions
380
389
381 $ echo 'include b' > filemap
390 $ echo 'include b' > filemap
382 $ hg convert --debug --filemap filemap a partialb | \
391 $ hg convert --debug --filemap filemap a partialb | \
383 > grep 'run hg'
392 > grep 'run hg'
384 run hg source pre-conversion action
393 run hg source pre-conversion action
385 run hg sink pre-conversion action
394 run hg sink pre-conversion action
386 run hg sink post-conversion action
395 run hg sink post-conversion action
387 run hg source post-conversion action
396 run hg source post-conversion action
388
397
389 converting empty dir should fail "nicely
398 converting empty dir should fail "nicely
390
399
391 $ mkdir emptydir
400 $ mkdir emptydir
392
401
393 override $PATH to ensure p4 not visible; use $PYTHON in case we're
402 override $PATH to ensure p4 not visible; use $PYTHON in case we're
394 running from a devel copy, not a temp installation
403 running from a devel copy, not a temp installation
395
404
396 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
405 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
397 assuming destination emptydir-hg
406 assuming destination emptydir-hg
398 initializing destination emptydir-hg repository
407 initializing destination emptydir-hg repository
399 emptydir does not look like a CVS checkout
408 emptydir does not look like a CVS checkout
400 emptydir does not look like a Git repository
409 emptydir does not look like a Git repository
401 emptydir does not look like a Subversion repository
410 emptydir does not look like a Subversion repository
402 emptydir is not a local Mercurial repository
411 emptydir is not a local Mercurial repository
403 emptydir does not look like a darcs repository
412 emptydir does not look like a darcs repository
404 emptydir does not look like a monotone repository
413 emptydir does not look like a monotone repository
405 emptydir does not look like a GNU Arch repository
414 emptydir does not look like a GNU Arch repository
406 emptydir does not look like a Bazaar repository
415 emptydir does not look like a Bazaar repository
407 cannot find required "p4" tool
416 cannot find required "p4" tool
408 abort: emptydir: missing or unsupported repository
417 abort: emptydir: missing or unsupported repository
409 [255]
418 [255]
410
419
411 convert with imaginary source type
420 convert with imaginary source type
412
421
413 $ hg convert --source-type foo a a-foo
422 $ hg convert --source-type foo a a-foo
414 initializing destination a-foo repository
423 initializing destination a-foo repository
415 abort: foo: invalid source repository type
424 abort: foo: invalid source repository type
416 [255]
425 [255]
417
426
418 convert with imaginary sink type
427 convert with imaginary sink type
419
428
420 $ hg convert --dest-type foo a a-foo
429 $ hg convert --dest-type foo a a-foo
421 abort: foo: invalid destination repository type
430 abort: foo: invalid destination repository type
422 [255]
431 [255]
423
432
424 testing: convert must not produce duplicate entries in fncache
433 testing: convert must not produce duplicate entries in fncache
425
434
426 $ hg convert a b
435 $ hg convert a b
427 initializing destination b repository
436 initializing destination b repository
428 scanning source...
437 scanning source...
429 sorting...
438 sorting...
430 converting...
439 converting...
431 4 a
440 4 a
432 3 b
441 3 b
433 2 c
442 2 c
434 1 d
443 1 d
435 0 e
444 0 e
436
445
437 contents of fncache file:
446 contents of fncache file:
438
447
439 $ cat b/.hg/store/fncache | sort
448 $ cat b/.hg/store/fncache | sort
440 data/a.i
449 data/a.i
441 data/b.i
450 data/b.i
442
451
443 test bogus URL
452 test bogus URL
444
453
445 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
454 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
446 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
455 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
447 [255]
456 [255]
448
457
449 test revset converted() lookup
458 test revset converted() lookup
450
459
451 $ hg --config convert.hg.saverev=True convert a c
460 $ hg --config convert.hg.saverev=True convert a c
452 initializing destination c repository
461 initializing destination c repository
453 scanning source...
462 scanning source...
454 sorting...
463 sorting...
455 converting...
464 converting...
456 4 a
465 4 a
457 3 b
466 3 b
458 2 c
467 2 c
459 1 d
468 1 d
460 0 e
469 0 e
461 $ echo f > c/f
470 $ echo f > c/f
462 $ hg -R c ci -d'0 0' -Amf
471 $ hg -R c ci -d'0 0' -Amf
463 adding f
472 adding f
464 created new head
473 created new head
465 $ hg -R c log -r "converted(09d945a62ce6)"
474 $ hg -R c log -r "converted(09d945a62ce6)"
466 changeset: 1:98c3dd46a874
475 changeset: 1:98c3dd46a874
467 user: test
476 user: test
468 date: Thu Jan 01 00:00:01 1970 +0000
477 date: Thu Jan 01 00:00:01 1970 +0000
469 summary: b
478 summary: b
470
479
471 $ hg -R c log -r "converted()"
480 $ hg -R c log -r "converted()"
472 changeset: 0:31ed57b2037c
481 changeset: 0:31ed57b2037c
473 user: test
482 user: test
474 date: Thu Jan 01 00:00:00 1970 +0000
483 date: Thu Jan 01 00:00:00 1970 +0000
475 summary: a
484 summary: a
476
485
477 changeset: 1:98c3dd46a874
486 changeset: 1:98c3dd46a874
478 user: test
487 user: test
479 date: Thu Jan 01 00:00:01 1970 +0000
488 date: Thu Jan 01 00:00:01 1970 +0000
480 summary: b
489 summary: b
481
490
482 changeset: 2:3b9ca06ef716
491 changeset: 2:3b9ca06ef716
483 user: test
492 user: test
484 date: Thu Jan 01 00:00:02 1970 +0000
493 date: Thu Jan 01 00:00:02 1970 +0000
485 summary: c
494 summary: c
486
495
487 changeset: 3:4e0debd37cf2
496 changeset: 3:4e0debd37cf2
488 user: test
497 user: test
489 date: Thu Jan 01 00:00:03 1970 +0000
498 date: Thu Jan 01 00:00:03 1970 +0000
490 summary: d
499 summary: d
491
500
492 changeset: 4:9de3bc9349c5
501 changeset: 4:9de3bc9349c5
493 user: test
502 user: test
494 date: Thu Jan 01 00:00:04 1970 +0000
503 date: Thu Jan 01 00:00:04 1970 +0000
495 summary: e
504 summary: e
496
505
General Comments 0
You need to be logged in to leave comments. Login now