##// END OF EJS Templates
convert: allow customizing git remote prefix...
Durham Goode -
r25787:d9133e89 default
parent child Browse files
Show More
@@ -1,437 +1,441 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 :convert.git.remoteprefix: remote refs are converted as bookmarks with
313 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
314 is 'remote'.
315
312 Perforce Source
316 Perforce Source
313 ###############
317 ###############
314
318
315 The Perforce (P4) importer can be given a p4 depot path or a
319 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
320 client specification as source. It will convert all files in the
317 source to a flat Mercurial repository, ignoring labels, branches
321 source to a flat Mercurial repository, ignoring labels, branches
318 and integrations. Note that when a depot path is given you then
322 and integrations. Note that when a depot path is given you then
319 usually should specify a target directory, because otherwise the
323 usually should specify a target directory, because otherwise the
320 target may be named ``...-hg``.
324 target may be named ``...-hg``.
321
325
322 It is possible to limit the amount of source history to be
326 It is possible to limit the amount of source history to be
323 converted by specifying an initial Perforce revision:
327 converted by specifying an initial Perforce revision:
324
328
325 :convert.p4.startrev: specify initial Perforce revision (a
329 :convert.p4.startrev: specify initial Perforce revision (a
326 Perforce changelist number).
330 Perforce changelist number).
327
331
328 Mercurial Destination
332 Mercurial Destination
329 #####################
333 #####################
330
334
331 The Mercurial destination will recognize Mercurial subrepositories in the
335 The Mercurial destination will recognize Mercurial subrepositories in the
332 destination directory, and update the .hgsubstate file automatically if the
336 destination directory, and update the .hgsubstate file automatically if the
333 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
337 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
334 Converting a repository with subrepositories requires converting a single
338 Converting a repository with subrepositories requires converting a single
335 repository at a time, from the bottom up.
339 repository at a time, from the bottom up.
336
340
337 .. container:: verbose
341 .. container:: verbose
338
342
339 An example showing how to convert a repository with subrepositories::
343 An example showing how to convert a repository with subrepositories::
340
344
341 # so convert knows the type when it sees a non empty destination
345 # so convert knows the type when it sees a non empty destination
342 $ hg init converted
346 $ hg init converted
343
347
344 $ hg convert orig/sub1 converted/sub1
348 $ hg convert orig/sub1 converted/sub1
345 $ hg convert orig/sub2 converted/sub2
349 $ hg convert orig/sub2 converted/sub2
346 $ hg convert orig converted
350 $ hg convert orig converted
347
351
348 The following options are supported:
352 The following options are supported:
349
353
350 :convert.hg.clonebranches: dispatch source branches in separate
354 :convert.hg.clonebranches: dispatch source branches in separate
351 clones. The default is False.
355 clones. The default is False.
352
356
353 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
357 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
354 ``default``.
358 ``default``.
355
359
356 :convert.hg.usebranchnames: preserve branch names. The default is
360 :convert.hg.usebranchnames: preserve branch names. The default is
357 True.
361 True.
358
362
359 :convert.hg.sourcename: records the given string as a 'convert_source' extra
363 :convert.hg.sourcename: records the given string as a 'convert_source' extra
360 value on each commit made in the target repository. The default is None.
364 value on each commit made in the target repository. The default is None.
361
365
362 All Destinations
366 All Destinations
363 ################
367 ################
364
368
365 All destination types accept the following options:
369 All destination types accept the following options:
366
370
367 :convert.skiptags: does not convert tags from the source repo to the target
371 :convert.skiptags: does not convert tags from the source repo to the target
368 repo. The default is False.
372 repo. The default is False.
369 """
373 """
370 return convcmd.convert(ui, src, dest, revmapfile, **opts)
374 return convcmd.convert(ui, src, dest, revmapfile, **opts)
371
375
372 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
376 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
373 def debugsvnlog(ui, **opts):
377 def debugsvnlog(ui, **opts):
374 return subversion.debugsvnlog(ui, **opts)
378 return subversion.debugsvnlog(ui, **opts)
375
379
376 @command('debugcvsps',
380 @command('debugcvsps',
377 [
381 [
378 # Main options shared with cvsps-2.1
382 # Main options shared with cvsps-2.1
379 ('b', 'branches', [], _('only return changes on specified branches')),
383 ('b', 'branches', [], _('only return changes on specified branches')),
380 ('p', 'prefix', '', _('prefix to remove from file names')),
384 ('p', 'prefix', '', _('prefix to remove from file names')),
381 ('r', 'revisions', [],
385 ('r', 'revisions', [],
382 _('only return changes after or between specified tags')),
386 _('only return changes after or between specified tags')),
383 ('u', 'update-cache', None, _("update cvs log cache")),
387 ('u', 'update-cache', None, _("update cvs log cache")),
384 ('x', 'new-cache', None, _("create new cvs log cache")),
388 ('x', 'new-cache', None, _("create new cvs log cache")),
385 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
389 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
386 ('', 'root', '', _('specify cvsroot')),
390 ('', 'root', '', _('specify cvsroot')),
387 # Options specific to builtin cvsps
391 # Options specific to builtin cvsps
388 ('', 'parents', '', _('show parent changesets')),
392 ('', 'parents', '', _('show parent changesets')),
389 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
393 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
390 # Options that are ignored for compatibility with cvsps-2.1
394 # Options that are ignored for compatibility with cvsps-2.1
391 ('A', 'cvs-direct', None, _('ignored for compatibility')),
395 ('A', 'cvs-direct', None, _('ignored for compatibility')),
392 ],
396 ],
393 _('hg debugcvsps [OPTION]... [PATH]...'),
397 _('hg debugcvsps [OPTION]... [PATH]...'),
394 norepo=True)
398 norepo=True)
395 def debugcvsps(ui, *args, **opts):
399 def debugcvsps(ui, *args, **opts):
396 '''create changeset information from CVS
400 '''create changeset information from CVS
397
401
398 This command is intended as a debugging tool for the CVS to
402 This command is intended as a debugging tool for the CVS to
399 Mercurial converter, and can be used as a direct replacement for
403 Mercurial converter, and can be used as a direct replacement for
400 cvsps.
404 cvsps.
401
405
402 Hg debugcvsps reads the CVS rlog for current directory (or any
406 Hg debugcvsps reads the CVS rlog for current directory (or any
403 named directory) in the CVS repository, and converts the log to a
407 named directory) in the CVS repository, and converts the log to a
404 series of changesets based on matching commit log entries and
408 series of changesets based on matching commit log entries and
405 dates.'''
409 dates.'''
406 return cvsps.debugcvsps(ui, *args, **opts)
410 return cvsps.debugcvsps(ui, *args, **opts)
407
411
408 def kwconverted(ctx, name):
412 def kwconverted(ctx, name):
409 rev = ctx.extra().get('convert_revision', '')
413 rev = ctx.extra().get('convert_revision', '')
410 if rev.startswith('svn:'):
414 if rev.startswith('svn:'):
411 if name == 'svnrev':
415 if name == 'svnrev':
412 return str(subversion.revsplit(rev)[2])
416 return str(subversion.revsplit(rev)[2])
413 elif name == 'svnpath':
417 elif name == 'svnpath':
414 return subversion.revsplit(rev)[1]
418 return subversion.revsplit(rev)[1]
415 elif name == 'svnuuid':
419 elif name == 'svnuuid':
416 return subversion.revsplit(rev)[0]
420 return subversion.revsplit(rev)[0]
417 return rev
421 return rev
418
422
419 def kwsvnrev(repo, ctx, **args):
423 def kwsvnrev(repo, ctx, **args):
420 """:svnrev: String. Converted subversion revision number."""
424 """:svnrev: String. Converted subversion revision number."""
421 return kwconverted(ctx, 'svnrev')
425 return kwconverted(ctx, 'svnrev')
422
426
423 def kwsvnpath(repo, ctx, **args):
427 def kwsvnpath(repo, ctx, **args):
424 """:svnpath: String. Converted subversion revision project path."""
428 """:svnpath: String. Converted subversion revision project path."""
425 return kwconverted(ctx, 'svnpath')
429 return kwconverted(ctx, 'svnpath')
426
430
427 def kwsvnuuid(repo, ctx, **args):
431 def kwsvnuuid(repo, ctx, **args):
428 """:svnuuid: String. Converted subversion revision repository identifier."""
432 """:svnuuid: String. Converted subversion revision repository identifier."""
429 return kwconverted(ctx, 'svnuuid')
433 return kwconverted(ctx, 'svnuuid')
430
434
431 def extsetup(ui):
435 def extsetup(ui):
432 templatekw.keywords['svnrev'] = kwsvnrev
436 templatekw.keywords['svnrev'] = kwsvnrev
433 templatekw.keywords['svnpath'] = kwsvnpath
437 templatekw.keywords['svnpath'] = kwsvnpath
434 templatekw.keywords['svnuuid'] = kwsvnuuid
438 templatekw.keywords['svnuuid'] = kwsvnuuid
435
439
436 # tell hggettext to extract docstrings from these functions:
440 # tell hggettext to extract docstrings from these functions:
437 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
441 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,400 +1,401 b''
1 # git.py - git support for the convert extension
1 # git.py - git support for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import os
8 import os
9 import subprocess
9 import subprocess
10 from mercurial import util, config, error
10 from mercurial import util, config, error
11 from mercurial.node import hex, nullid
11 from mercurial.node import hex, nullid
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 from common import NoRepo, commit, converter_source, checktool
14 from common import NoRepo, commit, converter_source, checktool
15
15
16 class submodule(object):
16 class submodule(object):
17 def __init__(self, path, node, url):
17 def __init__(self, path, node, url):
18 self.path = path
18 self.path = path
19 self.node = node
19 self.node = node
20 self.url = url
20 self.url = url
21
21
22 def hgsub(self):
22 def hgsub(self):
23 return "%s = [git]%s" % (self.path, self.url)
23 return "%s = [git]%s" % (self.path, self.url)
24
24
25 def hgsubstate(self):
25 def hgsubstate(self):
26 return "%s %s" % (self.node, self.path)
26 return "%s %s" % (self.node, self.path)
27
27
28 class convert_git(converter_source):
28 class convert_git(converter_source):
29 # Windows does not support GIT_DIR= construct while other systems
29 # Windows does not support GIT_DIR= construct while other systems
30 # cannot remove environment variable. Just assume none have
30 # cannot remove environment variable. Just assume none have
31 # both issues.
31 # both issues.
32 if util.safehasattr(os, 'unsetenv'):
32 if util.safehasattr(os, 'unsetenv'):
33 def gitopen(self, s, err=None):
33 def gitopen(self, s, err=None):
34 prevgitdir = os.environ.get('GIT_DIR')
34 prevgitdir = os.environ.get('GIT_DIR')
35 os.environ['GIT_DIR'] = self.path
35 os.environ['GIT_DIR'] = self.path
36 try:
36 try:
37 if err == subprocess.PIPE:
37 if err == subprocess.PIPE:
38 (stdin, stdout, stderr) = util.popen3(s)
38 (stdin, stdout, stderr) = util.popen3(s)
39 return stdout
39 return stdout
40 elif err == subprocess.STDOUT:
40 elif err == subprocess.STDOUT:
41 return self.popen_with_stderr(s)
41 return self.popen_with_stderr(s)
42 else:
42 else:
43 return util.popen(s, 'rb')
43 return util.popen(s, 'rb')
44 finally:
44 finally:
45 if prevgitdir is None:
45 if prevgitdir is None:
46 del os.environ['GIT_DIR']
46 del os.environ['GIT_DIR']
47 else:
47 else:
48 os.environ['GIT_DIR'] = prevgitdir
48 os.environ['GIT_DIR'] = prevgitdir
49
49
50 def gitpipe(self, s):
50 def gitpipe(self, s):
51 prevgitdir = os.environ.get('GIT_DIR')
51 prevgitdir = os.environ.get('GIT_DIR')
52 os.environ['GIT_DIR'] = self.path
52 os.environ['GIT_DIR'] = self.path
53 try:
53 try:
54 return util.popen3(s)
54 return util.popen3(s)
55 finally:
55 finally:
56 if prevgitdir is None:
56 if prevgitdir is None:
57 del os.environ['GIT_DIR']
57 del os.environ['GIT_DIR']
58 else:
58 else:
59 os.environ['GIT_DIR'] = prevgitdir
59 os.environ['GIT_DIR'] = prevgitdir
60
60
61 else:
61 else:
62 def gitopen(self, s, err=None):
62 def gitopen(self, s, err=None):
63 if err == subprocess.PIPE:
63 if err == subprocess.PIPE:
64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
64 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
65 return so
65 return so
66 elif err == subprocess.STDOUT:
66 elif err == subprocess.STDOUT:
67 return self.popen_with_stderr(s)
67 return self.popen_with_stderr(s)
68 else:
68 else:
69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
69 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
70
70
71 def gitpipe(self, s):
71 def gitpipe(self, s):
72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
72 return util.popen3('GIT_DIR=%s %s' % (self.path, s))
73
73
74 def popen_with_stderr(self, s):
74 def popen_with_stderr(self, s):
75 p = subprocess.Popen(s, shell=True, bufsize=-1,
75 p = subprocess.Popen(s, shell=True, bufsize=-1,
76 close_fds=util.closefds,
76 close_fds=util.closefds,
77 stdin=subprocess.PIPE,
77 stdin=subprocess.PIPE,
78 stdout=subprocess.PIPE,
78 stdout=subprocess.PIPE,
79 stderr=subprocess.STDOUT,
79 stderr=subprocess.STDOUT,
80 universal_newlines=False,
80 universal_newlines=False,
81 env=None)
81 env=None)
82 return p.stdout
82 return p.stdout
83
83
84 def gitread(self, s):
84 def gitread(self, s):
85 fh = self.gitopen(s)
85 fh = self.gitopen(s)
86 data = fh.read()
86 data = fh.read()
87 return data, fh.close()
87 return data, fh.close()
88
88
89 def __init__(self, ui, path, revs=None):
89 def __init__(self, ui, path, revs=None):
90 super(convert_git, self).__init__(ui, path, revs=revs)
90 super(convert_git, self).__init__(ui, path, revs=revs)
91
91
92 if os.path.isdir(path + "/.git"):
92 if os.path.isdir(path + "/.git"):
93 path += "/.git"
93 path += "/.git"
94 if not os.path.exists(path + "/objects"):
94 if not os.path.exists(path + "/objects"):
95 raise NoRepo(_("%s does not look like a Git repository") % path)
95 raise NoRepo(_("%s does not look like a Git repository") % path)
96
96
97 # The default value (50) is based on the default for 'git diff'.
97 # The default value (50) is based on the default for 'git diff'.
98 similarity = ui.configint('convert', 'git.similarity', default=50)
98 similarity = ui.configint('convert', 'git.similarity', default=50)
99 if similarity < 0 or similarity > 100:
99 if similarity < 0 or similarity > 100:
100 raise util.Abort(_('similarity must be between 0 and 100'))
100 raise util.Abort(_('similarity must be between 0 and 100'))
101 if similarity > 0:
101 if similarity > 0:
102 self.simopt = '-C%d%%' % similarity
102 self.simopt = '-C%d%%' % similarity
103 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
103 findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
104 False)
104 False)
105 if findcopiesharder:
105 if findcopiesharder:
106 self.simopt += ' --find-copies-harder'
106 self.simopt += ' --find-copies-harder'
107 else:
107 else:
108 self.simopt = ''
108 self.simopt = ''
109
109
110 checktool('git', 'git')
110 checktool('git', 'git')
111
111
112 self.path = path
112 self.path = path
113 self.submodules = []
113 self.submodules = []
114
114
115 self.catfilepipe = self.gitpipe('git cat-file --batch')
115 self.catfilepipe = self.gitpipe('git cat-file --batch')
116
116
117 def after(self):
117 def after(self):
118 for f in self.catfilepipe:
118 for f in self.catfilepipe:
119 f.close()
119 f.close()
120
120
121 def getheads(self):
121 def getheads(self):
122 if not self.revs:
122 if not self.revs:
123 heads, ret = self.gitread('git rev-parse --branches --remotes')
123 heads, ret = self.gitread('git rev-parse --branches --remotes')
124 heads = heads.splitlines()
124 heads = heads.splitlines()
125 if ret:
125 if ret:
126 raise util.Abort(_('cannot retrieve git heads'))
126 raise util.Abort(_('cannot retrieve git heads'))
127 else:
127 else:
128 heads = []
128 heads = []
129 for rev in self.revs:
129 for rev in self.revs:
130 rawhead, ret = self.gitread("git rev-parse --verify %s" % rev)
130 rawhead, ret = self.gitread("git rev-parse --verify %s" % rev)
131 heads.append(rawhead[:-1])
131 heads.append(rawhead[:-1])
132 if ret:
132 if ret:
133 raise util.Abort(_('cannot retrieve git head "%s"') % rev)
133 raise util.Abort(_('cannot retrieve git head "%s"') % rev)
134 return heads
134 return heads
135
135
136 def catfile(self, rev, type):
136 def catfile(self, rev, type):
137 if rev == hex(nullid):
137 if rev == hex(nullid):
138 raise IOError
138 raise IOError
139 self.catfilepipe[0].write(rev+'\n')
139 self.catfilepipe[0].write(rev+'\n')
140 self.catfilepipe[0].flush()
140 self.catfilepipe[0].flush()
141 info = self.catfilepipe[1].readline().split()
141 info = self.catfilepipe[1].readline().split()
142 if info[1] != type:
142 if info[1] != type:
143 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
143 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
144 size = int(info[2])
144 size = int(info[2])
145 data = self.catfilepipe[1].read(size)
145 data = self.catfilepipe[1].read(size)
146 if len(data) < size:
146 if len(data) < size:
147 raise util.Abort(_('cannot read %r object at %s: unexpected size')
147 raise util.Abort(_('cannot read %r object at %s: unexpected size')
148 % (type, rev))
148 % (type, rev))
149 # read the trailing newline
149 # read the trailing newline
150 self.catfilepipe[1].read(1)
150 self.catfilepipe[1].read(1)
151 return data
151 return data
152
152
153 def getfile(self, name, rev):
153 def getfile(self, name, rev):
154 if rev == hex(nullid):
154 if rev == hex(nullid):
155 return None, None
155 return None, None
156 if name == '.hgsub':
156 if name == '.hgsub':
157 data = '\n'.join([m.hgsub() for m in self.submoditer()])
157 data = '\n'.join([m.hgsub() for m in self.submoditer()])
158 mode = ''
158 mode = ''
159 elif name == '.hgsubstate':
159 elif name == '.hgsubstate':
160 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
160 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
161 mode = ''
161 mode = ''
162 else:
162 else:
163 data = self.catfile(rev, "blob")
163 data = self.catfile(rev, "blob")
164 mode = self.modecache[(name, rev)]
164 mode = self.modecache[(name, rev)]
165 return data, mode
165 return data, mode
166
166
167 def submoditer(self):
167 def submoditer(self):
168 null = hex(nullid)
168 null = hex(nullid)
169 for m in sorted(self.submodules, key=lambda p: p.path):
169 for m in sorted(self.submodules, key=lambda p: p.path):
170 if m.node != null:
170 if m.node != null:
171 yield m
171 yield m
172
172
173 def parsegitmodules(self, content):
173 def parsegitmodules(self, content):
174 """Parse the formatted .gitmodules file, example file format:
174 """Parse the formatted .gitmodules file, example file format:
175 [submodule "sub"]\n
175 [submodule "sub"]\n
176 \tpath = sub\n
176 \tpath = sub\n
177 \turl = git://giturl\n
177 \turl = git://giturl\n
178 """
178 """
179 self.submodules = []
179 self.submodules = []
180 c = config.config()
180 c = config.config()
181 # Each item in .gitmodules starts with whitespace that cant be parsed
181 # Each item in .gitmodules starts with whitespace that cant be parsed
182 c.parse('.gitmodules', '\n'.join(line.strip() for line in
182 c.parse('.gitmodules', '\n'.join(line.strip() for line in
183 content.split('\n')))
183 content.split('\n')))
184 for sec in c.sections():
184 for sec in c.sections():
185 s = c[sec]
185 s = c[sec]
186 if 'url' in s and 'path' in s:
186 if 'url' in s and 'path' in s:
187 self.submodules.append(submodule(s['path'], '', s['url']))
187 self.submodules.append(submodule(s['path'], '', s['url']))
188
188
189 def retrievegitmodules(self, version):
189 def retrievegitmodules(self, version):
190 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
190 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
191 if ret:
191 if ret:
192 # This can happen if a file is in the repo that has permissions
192 # This can happen if a file is in the repo that has permissions
193 # 160000, but there is no .gitmodules file.
193 # 160000, but there is no .gitmodules file.
194 self.ui.warn(_("warning: cannot read submodules config file in "
194 self.ui.warn(_("warning: cannot read submodules config file in "
195 "%s\n") % version)
195 "%s\n") % version)
196 return
196 return
197
197
198 try:
198 try:
199 self.parsegitmodules(modules)
199 self.parsegitmodules(modules)
200 except error.ParseError:
200 except error.ParseError:
201 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
201 self.ui.warn(_("warning: unable to parse .gitmodules in %s\n")
202 % version)
202 % version)
203 return
203 return
204
204
205 for m in self.submodules:
205 for m in self.submodules:
206 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
206 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
207 if ret:
207 if ret:
208 continue
208 continue
209 m.node = node.strip()
209 m.node = node.strip()
210
210
211 def getchanges(self, version, full):
211 def getchanges(self, version, full):
212 if full:
212 if full:
213 raise util.Abort(_("convert from git do not support --full"))
213 raise util.Abort(_("convert from git do not support --full"))
214 self.modecache = {}
214 self.modecache = {}
215 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
215 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
216 self.simopt, version))
216 self.simopt, version))
217 changes = []
217 changes = []
218 copies = {}
218 copies = {}
219 seen = set()
219 seen = set()
220 entry = None
220 entry = None
221 subexists = [False]
221 subexists = [False]
222 subdeleted = [False]
222 subdeleted = [False]
223 difftree = fh.read().split('\x00')
223 difftree = fh.read().split('\x00')
224 lcount = len(difftree)
224 lcount = len(difftree)
225 i = 0
225 i = 0
226
226
227 def add(entry, f, isdest):
227 def add(entry, f, isdest):
228 seen.add(f)
228 seen.add(f)
229 h = entry[3]
229 h = entry[3]
230 p = (entry[1] == "100755")
230 p = (entry[1] == "100755")
231 s = (entry[1] == "120000")
231 s = (entry[1] == "120000")
232 renamesource = (not isdest and entry[4][0] == 'R')
232 renamesource = (not isdest and entry[4][0] == 'R')
233
233
234 if f == '.gitmodules':
234 if f == '.gitmodules':
235 subexists[0] = True
235 subexists[0] = True
236 if entry[4] == 'D' or renamesource:
236 if entry[4] == 'D' or renamesource:
237 subdeleted[0] = True
237 subdeleted[0] = True
238 changes.append(('.hgsub', hex(nullid)))
238 changes.append(('.hgsub', hex(nullid)))
239 else:
239 else:
240 changes.append(('.hgsub', ''))
240 changes.append(('.hgsub', ''))
241 elif entry[1] == '160000' or entry[0] == ':160000':
241 elif entry[1] == '160000' or entry[0] == ':160000':
242 subexists[0] = True
242 subexists[0] = True
243 else:
243 else:
244 if renamesource:
244 if renamesource:
245 h = hex(nullid)
245 h = hex(nullid)
246 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
246 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
247 changes.append((f, h))
247 changes.append((f, h))
248
248
249 while i < lcount:
249 while i < lcount:
250 l = difftree[i]
250 l = difftree[i]
251 i += 1
251 i += 1
252 if not entry:
252 if not entry:
253 if not l.startswith(':'):
253 if not l.startswith(':'):
254 continue
254 continue
255 entry = l.split()
255 entry = l.split()
256 continue
256 continue
257 f = l
257 f = l
258 if f not in seen:
258 if f not in seen:
259 add(entry, f, False)
259 add(entry, f, False)
260 # A file can be copied multiple times, or modified and copied
260 # A file can be copied multiple times, or modified and copied
261 # simultaneously. So f can be repeated even if fdest isn't.
261 # simultaneously. So f can be repeated even if fdest isn't.
262 if entry[4][0] in 'RC':
262 if entry[4][0] in 'RC':
263 # rename or copy: next line is the destination
263 # rename or copy: next line is the destination
264 fdest = difftree[i]
264 fdest = difftree[i]
265 i += 1
265 i += 1
266 if fdest not in seen:
266 if fdest not in seen:
267 add(entry, fdest, True)
267 add(entry, fdest, True)
268 # .gitmodules isn't imported at all, so it being copied to
268 # .gitmodules isn't imported at all, so it being copied to
269 # and fro doesn't really make sense
269 # and fro doesn't really make sense
270 if f != '.gitmodules' and fdest != '.gitmodules':
270 if f != '.gitmodules' and fdest != '.gitmodules':
271 copies[fdest] = f
271 copies[fdest] = f
272 entry = None
272 entry = None
273 if fh.close():
273 if fh.close():
274 raise util.Abort(_('cannot read changes in %s') % version)
274 raise util.Abort(_('cannot read changes in %s') % version)
275
275
276 if subexists[0]:
276 if subexists[0]:
277 if subdeleted[0]:
277 if subdeleted[0]:
278 changes.append(('.hgsubstate', hex(nullid)))
278 changes.append(('.hgsubstate', hex(nullid)))
279 else:
279 else:
280 self.retrievegitmodules(version)
280 self.retrievegitmodules(version)
281 changes.append(('.hgsubstate', ''))
281 changes.append(('.hgsubstate', ''))
282 return (changes, copies, set())
282 return (changes, copies, set())
283
283
284 def getcommit(self, version):
284 def getcommit(self, version):
285 c = self.catfile(version, "commit") # read the commit hash
285 c = self.catfile(version, "commit") # read the commit hash
286 end = c.find("\n\n")
286 end = c.find("\n\n")
287 message = c[end + 2:]
287 message = c[end + 2:]
288 message = self.recode(message)
288 message = self.recode(message)
289 l = c[:end].splitlines()
289 l = c[:end].splitlines()
290 parents = []
290 parents = []
291 author = committer = None
291 author = committer = None
292 for e in l[1:]:
292 for e in l[1:]:
293 n, v = e.split(" ", 1)
293 n, v = e.split(" ", 1)
294 if n == "author":
294 if n == "author":
295 p = v.split()
295 p = v.split()
296 tm, tz = p[-2:]
296 tm, tz = p[-2:]
297 author = " ".join(p[:-2])
297 author = " ".join(p[:-2])
298 if author[0] == "<": author = author[1:-1]
298 if author[0] == "<": author = author[1:-1]
299 author = self.recode(author)
299 author = self.recode(author)
300 if n == "committer":
300 if n == "committer":
301 p = v.split()
301 p = v.split()
302 tm, tz = p[-2:]
302 tm, tz = p[-2:]
303 committer = " ".join(p[:-2])
303 committer = " ".join(p[:-2])
304 if committer[0] == "<": committer = committer[1:-1]
304 if committer[0] == "<": committer = committer[1:-1]
305 committer = self.recode(committer)
305 committer = self.recode(committer)
306 if n == "parent":
306 if n == "parent":
307 parents.append(v)
307 parents.append(v)
308
308
309 if committer and committer != author:
309 if committer and committer != author:
310 message += "\ncommitter: %s\n" % committer
310 message += "\ncommitter: %s\n" % committer
311 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
311 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
312 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
312 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
313 date = tm + " " + str(tz)
313 date = tm + " " + str(tz)
314
314
315 c = commit(parents=parents, date=date, author=author, desc=message,
315 c = commit(parents=parents, date=date, author=author, desc=message,
316 rev=version)
316 rev=version)
317 return c
317 return c
318
318
319 def numcommits(self):
319 def numcommits(self):
320 return len([None for _ in self.gitopen('git rev-list --all')])
320 return len([None for _ in self.gitopen('git rev-list --all')])
321
321
322 def gettags(self):
322 def gettags(self):
323 tags = {}
323 tags = {}
324 alltags = {}
324 alltags = {}
325 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
325 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
326 err=subprocess.STDOUT)
326 err=subprocess.STDOUT)
327 prefix = 'refs/tags/'
327 prefix = 'refs/tags/'
328
328
329 # Build complete list of tags, both annotated and bare ones
329 # Build complete list of tags, both annotated and bare ones
330 for line in fh:
330 for line in fh:
331 line = line.strip()
331 line = line.strip()
332 if line.startswith("error:") or line.startswith("fatal:"):
332 if line.startswith("error:") or line.startswith("fatal:"):
333 raise util.Abort(_('cannot read tags from %s') % self.path)
333 raise util.Abort(_('cannot read tags from %s') % self.path)
334 node, tag = line.split(None, 1)
334 node, tag = line.split(None, 1)
335 if not tag.startswith(prefix):
335 if not tag.startswith(prefix):
336 continue
336 continue
337 alltags[tag[len(prefix):]] = node
337 alltags[tag[len(prefix):]] = node
338 if fh.close():
338 if fh.close():
339 raise util.Abort(_('cannot read tags from %s') % self.path)
339 raise util.Abort(_('cannot read tags from %s') % self.path)
340
340
341 # Filter out tag objects for annotated tag refs
341 # Filter out tag objects for annotated tag refs
342 for tag in alltags:
342 for tag in alltags:
343 if tag.endswith('^{}'):
343 if tag.endswith('^{}'):
344 tags[tag[:-3]] = alltags[tag]
344 tags[tag[:-3]] = alltags[tag]
345 else:
345 else:
346 if tag + '^{}' in alltags:
346 if tag + '^{}' in alltags:
347 continue
347 continue
348 else:
348 else:
349 tags[tag] = alltags[tag]
349 tags[tag] = alltags[tag]
350
350
351 return tags
351 return tags
352
352
353 def getchangedfiles(self, version, i):
353 def getchangedfiles(self, version, i):
354 changes = []
354 changes = []
355 if i is None:
355 if i is None:
356 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
356 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
357 for l in fh:
357 for l in fh:
358 if "\t" not in l:
358 if "\t" not in l:
359 continue
359 continue
360 m, f = l[:-1].split("\t")
360 m, f = l[:-1].split("\t")
361 changes.append(f)
361 changes.append(f)
362 else:
362 else:
363 fh = self.gitopen('git diff-tree --name-only --root -r %s '
363 fh = self.gitopen('git diff-tree --name-only --root -r %s '
364 '"%s^%s" --' % (version, version, i + 1))
364 '"%s^%s" --' % (version, version, i + 1))
365 changes = [f.rstrip('\n') for f in fh]
365 changes = [f.rstrip('\n') for f in fh]
366 if fh.close():
366 if fh.close():
367 raise util.Abort(_('cannot read changes in %s') % version)
367 raise util.Abort(_('cannot read changes in %s') % version)
368
368
369 return changes
369 return changes
370
370
371 def getbookmarks(self):
371 def getbookmarks(self):
372 bookmarks = {}
372 bookmarks = {}
373
373
374 # Interesting references in git are prefixed
374 # Interesting references in git are prefixed
375 prefix = 'refs/heads/'
375 prefix = 'refs/heads/'
376 prefixlen = len(prefix)
376 prefixlen = len(prefix)
377
377
378 # factor two commands
378 # factor two commands
379 gitcmd = { 'remote/': 'git ls-remote --heads origin',
379 remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
380 '': 'git show-ref'}
380 gitcmd = { remoteprefix + '/': 'git ls-remote --heads origin',
381 '': 'git show-ref'}
381
382
382 # Origin heads
383 # Origin heads
383 for reftype in gitcmd:
384 for reftype in gitcmd:
384 try:
385 try:
385 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
386 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
386 for line in fh:
387 for line in fh:
387 line = line.strip()
388 line = line.strip()
388 rev, name = line.split(None, 1)
389 rev, name = line.split(None, 1)
389 if not name.startswith(prefix):
390 if not name.startswith(prefix):
390 continue
391 continue
391 name = '%s%s' % (reftype, name[prefixlen:])
392 name = '%s%s' % (reftype, name[prefixlen:])
392 bookmarks[name] = rev
393 bookmarks[name] = rev
393 except Exception:
394 except Exception:
394 pass
395 pass
395
396
396 return bookmarks
397 return bookmarks
397
398
398 def checkrevformat(self, revstr, mapname='splicemap'):
399 def checkrevformat(self, revstr, mapname='splicemap'):
399 """ git revision string is a 40 byte hex """
400 """ git revision string is a 40 byte hex """
400 self.checkhexformat(revstr, mapname)
401 self.checkhexformat(revstr, mapname)
@@ -1,670 +1,694 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 $ echo "[extensions]" >> $HGRCPATH
7 $ echo "[extensions]" >> $HGRCPATH
8 $ echo "convert=" >> $HGRCPATH
8 $ echo "convert=" >> $HGRCPATH
9 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
9 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
10 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
10 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
11 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
11 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
12 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
12 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
13 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
13 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
14 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
14 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
15 $ INVALIDID1=afd12345af
15 $ INVALIDID1=afd12345af
16 $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
16 $ INVALIDID2=28173x36ddd1e67bf7098d541130558ef5534a86
17 $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
17 $ VALIDID1=39b3d83f9a69a9ba4ebb111461071a0af0027357
18 $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
18 $ VALIDID2=8dd6476bd09d9c7776355dc454dafe38efaec5da
19 $ count=10
19 $ count=10
20 $ commit()
20 $ commit()
21 > {
21 > {
22 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
22 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
23 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
23 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
24 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
24 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
25 > count=`expr $count + 1`
25 > count=`expr $count + 1`
26 > }
26 > }
27 $ mkdir git-repo
27 $ mkdir git-repo
28 $ cd git-repo
28 $ cd git-repo
29 $ git init-db >/dev/null 2>/dev/null
29 $ git init-db >/dev/null 2>/dev/null
30 $ echo a > a
30 $ echo a > a
31 $ mkdir d
31 $ mkdir d
32 $ echo b > d/b
32 $ echo b > d/b
33 $ git add a d
33 $ git add a d
34 $ commit -a -m t1
34 $ commit -a -m t1
35
35
36 Remove the directory, then try to replace it with a file (issue754)
36 Remove the directory, then try to replace it with a file (issue754)
37
37
38 $ git rm -f d/b
38 $ git rm -f d/b
39 rm 'd/b'
39 rm 'd/b'
40 $ commit -m t2
40 $ commit -m t2
41 $ echo d > d
41 $ echo d > d
42 $ git add d
42 $ git add d
43 $ commit -m t3
43 $ commit -m t3
44 $ echo b >> a
44 $ echo b >> a
45 $ commit -a -m t4.1
45 $ commit -a -m t4.1
46 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
46 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
47 $ echo c > a
47 $ echo c > a
48 $ echo a >> a
48 $ echo a >> a
49 $ commit -a -m t4.2
49 $ commit -a -m t4.2
50 $ git checkout master >/dev/null 2>/dev/null
50 $ git checkout master >/dev/null 2>/dev/null
51 $ git pull --no-commit . other > /dev/null 2>/dev/null
51 $ git pull --no-commit . other > /dev/null 2>/dev/null
52 $ commit -m 'Merge branch other'
52 $ commit -m 'Merge branch other'
53 $ cd ..
53 $ cd ..
54 $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
54 $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
55 > --config progress.delay=0 --config progress.changedelay=0 \
55 > --config progress.delay=0 --config progress.changedelay=0 \
56 > --config progress.refresh=0 --config progress.width=60 \
56 > --config progress.refresh=0 --config progress.width=60 \
57 > --datesort git-repo
57 > --datesort git-repo
58 \r (no-eol) (esc)
58 \r (no-eol) (esc)
59 scanning [======> ] 1/6\r (no-eol) (esc)
59 scanning [======> ] 1/6\r (no-eol) (esc)
60 scanning [=============> ] 2/6\r (no-eol) (esc)
60 scanning [=============> ] 2/6\r (no-eol) (esc)
61 scanning [=====================> ] 3/6\r (no-eol) (esc)
61 scanning [=====================> ] 3/6\r (no-eol) (esc)
62 scanning [============================> ] 4/6\r (no-eol) (esc)
62 scanning [============================> ] 4/6\r (no-eol) (esc)
63 scanning [===================================> ] 5/6\r (no-eol) (esc)
63 scanning [===================================> ] 5/6\r (no-eol) (esc)
64 scanning [===========================================>] 6/6\r (no-eol) (esc)
64 scanning [===========================================>] 6/6\r (no-eol) (esc)
65 \r (no-eol) (esc)
65 \r (no-eol) (esc)
66 \r (no-eol) (esc)
66 \r (no-eol) (esc)
67 converting [ ] 0/6\r (no-eol) (esc)
67 converting [ ] 0/6\r (no-eol) (esc)
68 getting files [==================> ] 1/2\r (no-eol) (esc)
68 getting files [==================> ] 1/2\r (no-eol) (esc)
69 getting files [======================================>] 2/2\r (no-eol) (esc)
69 getting files [======================================>] 2/2\r (no-eol) (esc)
70 \r (no-eol) (esc)
70 \r (no-eol) (esc)
71 \r (no-eol) (esc)
71 \r (no-eol) (esc)
72 converting [======> ] 1/6\r (no-eol) (esc)
72 converting [======> ] 1/6\r (no-eol) (esc)
73 getting files [======================================>] 1/1\r (no-eol) (esc)
73 getting files [======================================>] 1/1\r (no-eol) (esc)
74 \r (no-eol) (esc)
74 \r (no-eol) (esc)
75 \r (no-eol) (esc)
75 \r (no-eol) (esc)
76 converting [=============> ] 2/6\r (no-eol) (esc)
76 converting [=============> ] 2/6\r (no-eol) (esc)
77 getting files [======================================>] 1/1\r (no-eol) (esc)
77 getting files [======================================>] 1/1\r (no-eol) (esc)
78 \r (no-eol) (esc)
78 \r (no-eol) (esc)
79 \r (no-eol) (esc)
79 \r (no-eol) (esc)
80 converting [====================> ] 3/6\r (no-eol) (esc)
80 converting [====================> ] 3/6\r (no-eol) (esc)
81 getting files [======================================>] 1/1\r (no-eol) (esc)
81 getting files [======================================>] 1/1\r (no-eol) (esc)
82 \r (no-eol) (esc)
82 \r (no-eol) (esc)
83 \r (no-eol) (esc)
83 \r (no-eol) (esc)
84 converting [===========================> ] 4/6\r (no-eol) (esc)
84 converting [===========================> ] 4/6\r (no-eol) (esc)
85 getting files [======================================>] 1/1\r (no-eol) (esc)
85 getting files [======================================>] 1/1\r (no-eol) (esc)
86 \r (no-eol) (esc)
86 \r (no-eol) (esc)
87 \r (no-eol) (esc)
87 \r (no-eol) (esc)
88 converting [==================================> ] 5/6\r (no-eol) (esc)
88 converting [==================================> ] 5/6\r (no-eol) (esc)
89 getting files [======================================>] 1/1\r (no-eol) (esc)
89 getting files [======================================>] 1/1\r (no-eol) (esc)
90 \r (no-eol) (esc)
90 \r (no-eol) (esc)
91 assuming destination git-repo-hg
91 assuming destination git-repo-hg
92 initializing destination git-repo-hg repository
92 initializing destination git-repo-hg repository
93 scanning source...
93 scanning source...
94 sorting...
94 sorting...
95 converting...
95 converting...
96 5 t1
96 5 t1
97 4 t2
97 4 t2
98 3 t3
98 3 t3
99 2 t4.1
99 2 t4.1
100 1 t4.2
100 1 t4.2
101 0 Merge branch other
101 0 Merge branch other
102 updating bookmarks
102 updating bookmarks
103 $ hg up -q -R git-repo-hg
103 $ hg up -q -R git-repo-hg
104 $ hg -R git-repo-hg tip -v
104 $ hg -R git-repo-hg tip -v
105 changeset: 5:c78094926be2
105 changeset: 5:c78094926be2
106 bookmark: master
106 bookmark: master
107 tag: tip
107 tag: tip
108 parent: 3:f5f5cb45432b
108 parent: 3:f5f5cb45432b
109 parent: 4:4e174f80c67c
109 parent: 4:4e174f80c67c
110 user: test <test@example.org>
110 user: test <test@example.org>
111 date: Mon Jan 01 00:00:15 2007 +0000
111 date: Mon Jan 01 00:00:15 2007 +0000
112 files: a
112 files: a
113 description:
113 description:
114 Merge branch other
114 Merge branch other
115
115
116
116
117 $ count=10
117 $ count=10
118 $ mkdir git-repo2
118 $ mkdir git-repo2
119 $ cd git-repo2
119 $ cd git-repo2
120 $ git init-db >/dev/null 2>/dev/null
120 $ git init-db >/dev/null 2>/dev/null
121 $ echo foo > foo
121 $ echo foo > foo
122 $ git add foo
122 $ git add foo
123 $ commit -a -m 'add foo'
123 $ commit -a -m 'add foo'
124 $ echo >> foo
124 $ echo >> foo
125 $ commit -a -m 'change foo'
125 $ commit -a -m 'change foo'
126 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
126 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
127 $ echo quux >> quux
127 $ echo quux >> quux
128 $ git add quux
128 $ git add quux
129 $ commit -a -m 'add quux'
129 $ commit -a -m 'add quux'
130 $ echo bar > bar
130 $ echo bar > bar
131 $ git add bar
131 $ git add bar
132 $ commit -a -m 'add bar'
132 $ commit -a -m 'add bar'
133 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
133 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
134 $ echo baz > baz
134 $ echo baz > baz
135 $ git add baz
135 $ git add baz
136 $ commit -a -m 'add baz'
136 $ commit -a -m 'add baz'
137 $ git checkout master >/dev/null 2>/dev/null
137 $ git checkout master >/dev/null 2>/dev/null
138 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
138 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
139 $ commit -m 'Octopus merge'
139 $ commit -m 'Octopus merge'
140 $ echo bar >> bar
140 $ echo bar >> bar
141 $ commit -a -m 'change bar'
141 $ commit -a -m 'change bar'
142 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
142 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
143 $ echo >> foo
143 $ echo >> foo
144 $ commit -a -m 'change foo'
144 $ commit -a -m 'change foo'
145 $ git checkout master >/dev/null 2>/dev/null
145 $ git checkout master >/dev/null 2>/dev/null
146 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
146 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
147 $ commit -m 'Discard change to foo'
147 $ commit -m 'Discard change to foo'
148 $ cd ..
148 $ cd ..
149 $ glog()
149 $ glog()
150 > {
150 > {
151 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
151 > hg log -G --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
152 > }
152 > }
153 $ splitrepo()
153 $ splitrepo()
154 > {
154 > {
155 > msg="$1"
155 > msg="$1"
156 > files="$2"
156 > files="$2"
157 > opts=$3
157 > opts=$3
158 > echo "% $files: $msg"
158 > echo "% $files: $msg"
159 > prefix=`echo "$files" | sed -e 's/ /-/g'`
159 > prefix=`echo "$files" | sed -e 's/ /-/g'`
160 > fmap="$prefix.fmap"
160 > fmap="$prefix.fmap"
161 > repo="$prefix.repo"
161 > repo="$prefix.repo"
162 > for i in $files; do
162 > for i in $files; do
163 > echo "include $i" >> "$fmap"
163 > echo "include $i" >> "$fmap"
164 > done
164 > done
165 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
165 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
166 > hg up -q -R "$repo"
166 > hg up -q -R "$repo"
167 > glog -R "$repo"
167 > glog -R "$repo"
168 > hg -R "$repo" manifest --debug
168 > hg -R "$repo" manifest --debug
169 > }
169 > }
170
170
171 full conversion
171 full conversion
172
172
173 $ hg convert --datesort git-repo2 fullrepo \
173 $ hg convert --datesort git-repo2 fullrepo \
174 > --config extensions.progress= --config progress.assume-tty=1 \
174 > --config extensions.progress= --config progress.assume-tty=1 \
175 > --config progress.delay=0 --config progress.changedelay=0 \
175 > --config progress.delay=0 --config progress.changedelay=0 \
176 > --config progress.refresh=0 --config progress.width=60
176 > --config progress.refresh=0 --config progress.width=60
177 \r (no-eol) (esc)
177 \r (no-eol) (esc)
178 scanning [===> ] 1/9\r (no-eol) (esc)
178 scanning [===> ] 1/9\r (no-eol) (esc)
179 scanning [========> ] 2/9\r (no-eol) (esc)
179 scanning [========> ] 2/9\r (no-eol) (esc)
180 scanning [=============> ] 3/9\r (no-eol) (esc)
180 scanning [=============> ] 3/9\r (no-eol) (esc)
181 scanning [==================> ] 4/9\r (no-eol) (esc)
181 scanning [==================> ] 4/9\r (no-eol) (esc)
182 scanning [=======================> ] 5/9\r (no-eol) (esc)
182 scanning [=======================> ] 5/9\r (no-eol) (esc)
183 scanning [============================> ] 6/9\r (no-eol) (esc)
183 scanning [============================> ] 6/9\r (no-eol) (esc)
184 scanning [=================================> ] 7/9\r (no-eol) (esc)
184 scanning [=================================> ] 7/9\r (no-eol) (esc)
185 scanning [======================================> ] 8/9\r (no-eol) (esc)
185 scanning [======================================> ] 8/9\r (no-eol) (esc)
186 scanning [===========================================>] 9/9\r (no-eol) (esc)
186 scanning [===========================================>] 9/9\r (no-eol) (esc)
187 \r (no-eol) (esc)
187 \r (no-eol) (esc)
188 \r (no-eol) (esc)
188 \r (no-eol) (esc)
189 converting [ ] 0/9\r (no-eol) (esc)
189 converting [ ] 0/9\r (no-eol) (esc)
190 getting files [======================================>] 1/1\r (no-eol) (esc)
190 getting files [======================================>] 1/1\r (no-eol) (esc)
191 \r (no-eol) (esc)
191 \r (no-eol) (esc)
192 \r (no-eol) (esc)
192 \r (no-eol) (esc)
193 converting [===> ] 1/9\r (no-eol) (esc)
193 converting [===> ] 1/9\r (no-eol) (esc)
194 getting files [======================================>] 1/1\r (no-eol) (esc)
194 getting files [======================================>] 1/1\r (no-eol) (esc)
195 \r (no-eol) (esc)
195 \r (no-eol) (esc)
196 \r (no-eol) (esc)
196 \r (no-eol) (esc)
197 converting [========> ] 2/9\r (no-eol) (esc)
197 converting [========> ] 2/9\r (no-eol) (esc)
198 getting files [======================================>] 1/1\r (no-eol) (esc)
198 getting files [======================================>] 1/1\r (no-eol) (esc)
199 \r (no-eol) (esc)
199 \r (no-eol) (esc)
200 \r (no-eol) (esc)
200 \r (no-eol) (esc)
201 converting [=============> ] 3/9\r (no-eol) (esc)
201 converting [=============> ] 3/9\r (no-eol) (esc)
202 getting files [======================================>] 1/1\r (no-eol) (esc)
202 getting files [======================================>] 1/1\r (no-eol) (esc)
203 \r (no-eol) (esc)
203 \r (no-eol) (esc)
204 \r (no-eol) (esc)
204 \r (no-eol) (esc)
205 converting [=================> ] 4/9\r (no-eol) (esc)
205 converting [=================> ] 4/9\r (no-eol) (esc)
206 getting files [======================================>] 1/1\r (no-eol) (esc)
206 getting files [======================================>] 1/1\r (no-eol) (esc)
207 \r (no-eol) (esc)
207 \r (no-eol) (esc)
208 \r (no-eol) (esc)
208 \r (no-eol) (esc)
209 converting [======================> ] 5/9\r (no-eol) (esc)
209 converting [======================> ] 5/9\r (no-eol) (esc)
210 getting files [===> ] 1/8\r (no-eol) (esc)
210 getting files [===> ] 1/8\r (no-eol) (esc)
211 getting files [========> ] 2/8\r (no-eol) (esc)
211 getting files [========> ] 2/8\r (no-eol) (esc)
212 getting files [=============> ] 3/8\r (no-eol) (esc)
212 getting files [=============> ] 3/8\r (no-eol) (esc)
213 getting files [==================> ] 4/8\r (no-eol) (esc)
213 getting files [==================> ] 4/8\r (no-eol) (esc)
214 getting files [=======================> ] 5/8\r (no-eol) (esc)
214 getting files [=======================> ] 5/8\r (no-eol) (esc)
215 getting files [============================> ] 6/8\r (no-eol) (esc)
215 getting files [============================> ] 6/8\r (no-eol) (esc)
216 getting files [=================================> ] 7/8\r (no-eol) (esc)
216 getting files [=================================> ] 7/8\r (no-eol) (esc)
217 getting files [======================================>] 8/8\r (no-eol) (esc)
217 getting files [======================================>] 8/8\r (no-eol) (esc)
218 \r (no-eol) (esc)
218 \r (no-eol) (esc)
219 \r (no-eol) (esc)
219 \r (no-eol) (esc)
220 converting [===========================> ] 6/9\r (no-eol) (esc)
220 converting [===========================> ] 6/9\r (no-eol) (esc)
221 getting files [======================================>] 1/1\r (no-eol) (esc)
221 getting files [======================================>] 1/1\r (no-eol) (esc)
222 \r (no-eol) (esc)
222 \r (no-eol) (esc)
223 \r (no-eol) (esc)
223 \r (no-eol) (esc)
224 converting [===============================> ] 7/9\r (no-eol) (esc)
224 converting [===============================> ] 7/9\r (no-eol) (esc)
225 getting files [======================================>] 1/1\r (no-eol) (esc)
225 getting files [======================================>] 1/1\r (no-eol) (esc)
226 \r (no-eol) (esc)
226 \r (no-eol) (esc)
227 \r (no-eol) (esc)
227 \r (no-eol) (esc)
228 converting [====================================> ] 8/9\r (no-eol) (esc)
228 converting [====================================> ] 8/9\r (no-eol) (esc)
229 getting files [==================> ] 1/2\r (no-eol) (esc)
229 getting files [==================> ] 1/2\r (no-eol) (esc)
230 getting files [======================================>] 2/2\r (no-eol) (esc)
230 getting files [======================================>] 2/2\r (no-eol) (esc)
231 \r (no-eol) (esc)
231 \r (no-eol) (esc)
232 initializing destination fullrepo repository
232 initializing destination fullrepo repository
233 scanning source...
233 scanning source...
234 sorting...
234 sorting...
235 converting...
235 converting...
236 8 add foo
236 8 add foo
237 7 change foo
237 7 change foo
238 6 add quux
238 6 add quux
239 5 add bar
239 5 add bar
240 4 add baz
240 4 add baz
241 3 Octopus merge
241 3 Octopus merge
242 2 change bar
242 2 change bar
243 1 change foo
243 1 change foo
244 0 Discard change to foo
244 0 Discard change to foo
245 updating bookmarks
245 updating bookmarks
246 $ hg up -q -R fullrepo
246 $ hg up -q -R fullrepo
247 $ glog -R fullrepo
247 $ glog -R fullrepo
248 @ 9 "Discard change to foo" files: foo
248 @ 9 "Discard change to foo" files: foo
249 |\
249 |\
250 | o 8 "change foo" files: foo
250 | o 8 "change foo" files: foo
251 | |
251 | |
252 o | 7 "change bar" files: bar
252 o | 7 "change bar" files: bar
253 |/
253 |/
254 o 6 "(octopus merge fixup)" files:
254 o 6 "(octopus merge fixup)" files:
255 |\
255 |\
256 | o 5 "Octopus merge" files: baz
256 | o 5 "Octopus merge" files: baz
257 | |\
257 | |\
258 o | | 4 "add baz" files: baz
258 o | | 4 "add baz" files: baz
259 | | |
259 | | |
260 +---o 3 "add bar" files: bar
260 +---o 3 "add bar" files: bar
261 | |
261 | |
262 o | 2 "add quux" files: quux
262 o | 2 "add quux" files: quux
263 | |
263 | |
264 | o 1 "change foo" files: foo
264 | o 1 "change foo" files: foo
265 |/
265 |/
266 o 0 "add foo" files: foo
266 o 0 "add foo" files: foo
267
267
268 $ hg -R fullrepo manifest --debug
268 $ hg -R fullrepo manifest --debug
269 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
269 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
270 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
270 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
271 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
271 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
272 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
272 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
273 $ splitrepo 'octopus merge' 'foo bar baz'
273 $ splitrepo 'octopus merge' 'foo bar baz'
274 % foo bar baz: octopus merge
274 % foo bar baz: octopus merge
275 @ 8 "Discard change to foo" files: foo
275 @ 8 "Discard change to foo" files: foo
276 |\
276 |\
277 | o 7 "change foo" files: foo
277 | o 7 "change foo" files: foo
278 | |
278 | |
279 o | 6 "change bar" files: bar
279 o | 6 "change bar" files: bar
280 |/
280 |/
281 o 5 "(octopus merge fixup)" files:
281 o 5 "(octopus merge fixup)" files:
282 |\
282 |\
283 | o 4 "Octopus merge" files: baz
283 | o 4 "Octopus merge" files: baz
284 | |\
284 | |\
285 o | | 3 "add baz" files: baz
285 o | | 3 "add baz" files: baz
286 | | |
286 | | |
287 +---o 2 "add bar" files: bar
287 +---o 2 "add bar" files: bar
288 | |
288 | |
289 | o 1 "change foo" files: foo
289 | o 1 "change foo" files: foo
290 |/
290 |/
291 o 0 "add foo" files: foo
291 o 0 "add foo" files: foo
292
292
293 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
293 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
294 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
294 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
295 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
295 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
296 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
296 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
297 % foo baz quux: only some parents of an octopus merge; "discard" a head
297 % foo baz quux: only some parents of an octopus merge; "discard" a head
298 @ 6 "Discard change to foo" files: foo
298 @ 6 "Discard change to foo" files: foo
299 |
299 |
300 o 5 "change foo" files: foo
300 o 5 "change foo" files: foo
301 |
301 |
302 o 4 "Octopus merge" files:
302 o 4 "Octopus merge" files:
303 |\
303 |\
304 | o 3 "add baz" files: baz
304 | o 3 "add baz" files: baz
305 | |
305 | |
306 | o 2 "add quux" files: quux
306 | o 2 "add quux" files: quux
307 | |
307 | |
308 o | 1 "change foo" files: foo
308 o | 1 "change foo" files: foo
309 |/
309 |/
310 o 0 "add foo" files: foo
310 o 0 "add foo" files: foo
311
311
312 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
312 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
313 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
313 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
314 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
314 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
315
315
316 test importing git renames and copies
316 test importing git renames and copies
317
317
318 $ cd git-repo2
318 $ cd git-repo2
319 $ git mv foo foo-renamed
319 $ git mv foo foo-renamed
320 since bar is not touched in this commit, this copy will not be detected
320 since bar is not touched in this commit, this copy will not be detected
321 $ cp bar bar-copied
321 $ cp bar bar-copied
322 $ cp baz baz-copied
322 $ cp baz baz-copied
323 $ cp baz baz-copied2
323 $ cp baz baz-copied2
324 $ echo baz2 >> baz
324 $ echo baz2 >> baz
325 $ git add bar-copied baz-copied baz-copied2
325 $ git add bar-copied baz-copied baz-copied2
326 $ commit -a -m 'rename and copy'
326 $ commit -a -m 'rename and copy'
327 $ cd ..
327 $ cd ..
328
328
329 input validation
329 input validation
330 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
330 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
331 abort: convert.git.similarity is not an integer ('foo')
331 abort: convert.git.similarity is not an integer ('foo')
332 [255]
332 [255]
333 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
333 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
334 abort: similarity must be between 0 and 100
334 abort: similarity must be between 0 and 100
335 [255]
335 [255]
336 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
336 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
337 abort: similarity must be between 0 and 100
337 abort: similarity must be between 0 and 100
338 [255]
338 [255]
339
339
340 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
340 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
341 $ hg -R fullrepo status -C --change master
341 $ hg -R fullrepo status -C --change master
342 M baz
342 M baz
343 A bar-copied
343 A bar-copied
344 A baz-copied
344 A baz-copied
345 baz
345 baz
346 A baz-copied2
346 A baz-copied2
347 baz
347 baz
348 A foo-renamed
348 A foo-renamed
349 foo
349 foo
350 R foo
350 R foo
351
351
352 $ cd git-repo2
352 $ cd git-repo2
353 $ echo bar2 >> bar
353 $ echo bar2 >> bar
354 $ commit -a -m 'change bar'
354 $ commit -a -m 'change bar'
355 $ cp bar bar-copied2
355 $ cp bar bar-copied2
356 $ git add bar-copied2
356 $ git add bar-copied2
357 $ commit -a -m 'copy with no changes'
357 $ commit -a -m 'copy with no changes'
358 $ cd ..
358 $ cd ..
359
359
360 $ hg -q convert --config convert.git.similarity=100 \
360 $ hg -q convert --config convert.git.similarity=100 \
361 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
361 > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
362 $ hg -R fullrepo status -C --change master
362 $ hg -R fullrepo status -C --change master
363 A bar-copied2
363 A bar-copied2
364 bar
364 bar
365
365
366 test binary conversion (issue1359)
366 test binary conversion (issue1359)
367
367
368 $ count=19
368 $ count=19
369 $ mkdir git-repo3
369 $ mkdir git-repo3
370 $ cd git-repo3
370 $ cd git-repo3
371 $ git init-db >/dev/null 2>/dev/null
371 $ git init-db >/dev/null 2>/dev/null
372 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
372 $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
373 $ git add b
373 $ git add b
374 $ commit -a -m addbinary
374 $ commit -a -m addbinary
375 $ cd ..
375 $ cd ..
376
376
377 convert binary file
377 convert binary file
378
378
379 $ hg convert git-repo3 git-repo3-hg
379 $ hg convert git-repo3 git-repo3-hg
380 initializing destination git-repo3-hg repository
380 initializing destination git-repo3-hg repository
381 scanning source...
381 scanning source...
382 sorting...
382 sorting...
383 converting...
383 converting...
384 0 addbinary
384 0 addbinary
385 updating bookmarks
385 updating bookmarks
386 $ cd git-repo3-hg
386 $ cd git-repo3-hg
387 $ hg up -C
387 $ hg up -C
388 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 $ $PYTHON -c 'print len(file("b", "rb").read())'
389 $ $PYTHON -c 'print len(file("b", "rb").read())'
390 4096
390 4096
391 $ cd ..
391 $ cd ..
392
392
393 test author vs committer
393 test author vs committer
394
394
395 $ mkdir git-repo4
395 $ mkdir git-repo4
396 $ cd git-repo4
396 $ cd git-repo4
397 $ git init-db >/dev/null 2>/dev/null
397 $ git init-db >/dev/null 2>/dev/null
398 $ echo >> foo
398 $ echo >> foo
399 $ git add foo
399 $ git add foo
400 $ commit -a -m addfoo
400 $ commit -a -m addfoo
401 $ echo >> foo
401 $ echo >> foo
402 $ GIT_AUTHOR_NAME="nottest"
402 $ GIT_AUTHOR_NAME="nottest"
403 $ commit -a -m addfoo2
403 $ commit -a -m addfoo2
404 $ cd ..
404 $ cd ..
405
405
406 convert author committer
406 convert author committer
407
407
408 $ hg convert git-repo4 git-repo4-hg
408 $ hg convert git-repo4 git-repo4-hg
409 initializing destination git-repo4-hg repository
409 initializing destination git-repo4-hg repository
410 scanning source...
410 scanning source...
411 sorting...
411 sorting...
412 converting...
412 converting...
413 1 addfoo
413 1 addfoo
414 0 addfoo2
414 0 addfoo2
415 updating bookmarks
415 updating bookmarks
416 $ hg -R git-repo4-hg log -v
416 $ hg -R git-repo4-hg log -v
417 changeset: 1:d63e967f93da
417 changeset: 1:d63e967f93da
418 bookmark: master
418 bookmark: master
419 tag: tip
419 tag: tip
420 user: nottest <test@example.org>
420 user: nottest <test@example.org>
421 date: Mon Jan 01 00:00:21 2007 +0000
421 date: Mon Jan 01 00:00:21 2007 +0000
422 files: foo
422 files: foo
423 description:
423 description:
424 addfoo2
424 addfoo2
425
425
426 committer: test <test@example.org>
426 committer: test <test@example.org>
427
427
428
428
429 changeset: 0:0735477b0224
429 changeset: 0:0735477b0224
430 user: test <test@example.org>
430 user: test <test@example.org>
431 date: Mon Jan 01 00:00:20 2007 +0000
431 date: Mon Jan 01 00:00:20 2007 +0000
432 files: foo
432 files: foo
433 description:
433 description:
434 addfoo
434 addfoo
435
435
436
436
437
437
438 --sourceorder should fail
438 --sourceorder should fail
439
439
440 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
440 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
441 initializing destination git-repo4-sourcesort-hg repository
441 initializing destination git-repo4-sourcesort-hg repository
442 abort: --sourcesort is not supported by this data source
442 abort: --sourcesort is not supported by this data source
443 [255]
443 [255]
444
444
445 test converting certain branches
445 test converting certain branches
446
446
447 $ mkdir git-testrevs
447 $ mkdir git-testrevs
448 $ cd git-testrevs
448 $ cd git-testrevs
449 $ git init
449 $ git init
450 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
450 Initialized empty Git repository in $TESTTMP/git-testrevs/.git/
451 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
451 $ echo a >> a ; git add a > /dev/null; git commit -m 'first' > /dev/null
452 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
452 $ echo a >> a ; git add a > /dev/null; git commit -m 'master commit' > /dev/null
453 $ git checkout -b goodbranch 'HEAD^'
453 $ git checkout -b goodbranch 'HEAD^'
454 Switched to a new branch 'goodbranch'
454 Switched to a new branch 'goodbranch'
455 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
455 $ echo a >> b ; git add b > /dev/null; git commit -m 'good branch commit' > /dev/null
456 $ git checkout -b badbranch 'HEAD^'
456 $ git checkout -b badbranch 'HEAD^'
457 Switched to a new branch 'badbranch'
457 Switched to a new branch 'badbranch'
458 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
458 $ echo a >> c ; git add c > /dev/null; git commit -m 'bad branch commit' > /dev/null
459 $ cd ..
459 $ cd ..
460 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
460 $ hg convert git-testrevs hg-testrevs --rev master --rev goodbranch
461 initializing destination hg-testrevs repository
461 initializing destination hg-testrevs repository
462 scanning source...
462 scanning source...
463 sorting...
463 sorting...
464 converting...
464 converting...
465 2 first
465 2 first
466 1 good branch commit
466 1 good branch commit
467 0 master commit
467 0 master commit
468 updating bookmarks
468 updating bookmarks
469 $ cd hg-testrevs
469 $ cd hg-testrevs
470 $ hg log -G -T '{rev} {bookmarks}'
470 $ hg log -G -T '{rev} {bookmarks}'
471 o 2 master
471 o 2 master
472 |
472 |
473 | o 1 goodbranch
473 | o 1 goodbranch
474 |/
474 |/
475 o 0
475 o 0
476
476
477 $ cd ..
477 $ cd ..
478
478
479 test sub modules
479 test sub modules
480
480
481 $ mkdir git-repo5
481 $ mkdir git-repo5
482 $ cd git-repo5
482 $ cd git-repo5
483 $ git init-db >/dev/null 2>/dev/null
483 $ git init-db >/dev/null 2>/dev/null
484 $ echo 'sub' >> foo
484 $ echo 'sub' >> foo
485 $ git add foo
485 $ git add foo
486 $ commit -a -m 'addfoo'
486 $ commit -a -m 'addfoo'
487 $ BASE=`pwd`
487 $ BASE=`pwd`
488 $ cd ..
488 $ cd ..
489 $ mkdir git-repo6
489 $ mkdir git-repo6
490 $ cd git-repo6
490 $ cd git-repo6
491 $ git init-db >/dev/null 2>/dev/null
491 $ git init-db >/dev/null 2>/dev/null
492 $ git submodule add ${BASE} >/dev/null 2>/dev/null
492 $ git submodule add ${BASE} >/dev/null 2>/dev/null
493 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
493 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
494
494
495 test non-tab whitespace .gitmodules
495 test non-tab whitespace .gitmodules
496
496
497 $ cat >> .gitmodules <<EOF
497 $ cat >> .gitmodules <<EOF
498 > [submodule "git-repo5"]
498 > [submodule "git-repo5"]
499 > path = git-repo5
499 > path = git-repo5
500 > url = git-repo5
500 > url = git-repo5
501 > EOF
501 > EOF
502 $ git commit -q -a -m "weird white space submodule"
502 $ git commit -q -a -m "weird white space submodule"
503 $ cd ..
503 $ cd ..
504 $ hg convert git-repo6 hg-repo6
504 $ hg convert git-repo6 hg-repo6
505 initializing destination hg-repo6 repository
505 initializing destination hg-repo6 repository
506 scanning source...
506 scanning source...
507 sorting...
507 sorting...
508 converting...
508 converting...
509 1 addsubmodule
509 1 addsubmodule
510 0 weird white space submodule
510 0 weird white space submodule
511 updating bookmarks
511 updating bookmarks
512
512
513 $ rm -rf hg-repo6
513 $ rm -rf hg-repo6
514 $ cd git-repo6
514 $ cd git-repo6
515 $ git reset --hard 'HEAD^' > /dev/null
515 $ git reset --hard 'HEAD^' > /dev/null
516
516
517 test missing .gitmodules
517 test missing .gitmodules
518
518
519 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
519 $ git submodule add ../git-repo4 >/dev/null 2>/dev/null
520 $ git checkout HEAD .gitmodules
520 $ git checkout HEAD .gitmodules
521 $ git rm .gitmodules
521 $ git rm .gitmodules
522 rm '.gitmodules'
522 rm '.gitmodules'
523 $ git commit -q -m "remove .gitmodules" .gitmodules
523 $ git commit -q -m "remove .gitmodules" .gitmodules
524 $ git commit -q -m "missing .gitmodules"
524 $ git commit -q -m "missing .gitmodules"
525 $ cd ..
525 $ cd ..
526 $ hg convert git-repo6 hg-repo6 --traceback
526 $ hg convert git-repo6 hg-repo6 --traceback
527 fatal: Path '.gitmodules' does not exist in '*' (glob)
527 fatal: Path '.gitmodules' does not exist in '*' (glob)
528 initializing destination hg-repo6 repository
528 initializing destination hg-repo6 repository
529 scanning source...
529 scanning source...
530 sorting...
530 sorting...
531 converting...
531 converting...
532 2 addsubmodule
532 2 addsubmodule
533 1 remove .gitmodules
533 1 remove .gitmodules
534 0 missing .gitmodules
534 0 missing .gitmodules
535 warning: cannot read submodules config file in * (glob)
535 warning: cannot read submodules config file in * (glob)
536 updating bookmarks
536 updating bookmarks
537 $ rm -rf hg-repo6
537 $ rm -rf hg-repo6
538 $ cd git-repo6
538 $ cd git-repo6
539 $ rm -rf git-repo4
539 $ rm -rf git-repo4
540 $ git reset --hard 'HEAD^^' > /dev/null
540 $ git reset --hard 'HEAD^^' > /dev/null
541 $ cd ..
541 $ cd ..
542
542
543 test invalid splicemap1
543 test invalid splicemap1
544
544
545 $ cat > splicemap <<EOF
545 $ cat > splicemap <<EOF
546 > $VALIDID1
546 > $VALIDID1
547 > EOF
547 > EOF
548 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
548 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
549 initializing destination git-repo2-splicemap1-hg repository
549 initializing destination git-repo2-splicemap1-hg repository
550 abort: syntax error in splicemap(1): child parent1[,parent2] expected
550 abort: syntax error in splicemap(1): child parent1[,parent2] expected
551 [255]
551 [255]
552
552
553 test invalid splicemap2
553 test invalid splicemap2
554
554
555 $ cat > splicemap <<EOF
555 $ cat > splicemap <<EOF
556 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
556 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
557 > EOF
557 > EOF
558 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
558 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
559 initializing destination git-repo2-splicemap2-hg repository
559 initializing destination git-repo2-splicemap2-hg repository
560 abort: syntax error in splicemap(1): child parent1[,parent2] expected
560 abort: syntax error in splicemap(1): child parent1[,parent2] expected
561 [255]
561 [255]
562
562
563 test invalid splicemap3
563 test invalid splicemap3
564
564
565 $ cat > splicemap <<EOF
565 $ cat > splicemap <<EOF
566 > $INVALIDID1 $INVALIDID2
566 > $INVALIDID1 $INVALIDID2
567 > EOF
567 > EOF
568 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
568 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
569 initializing destination git-repo2-splicemap3-hg repository
569 initializing destination git-repo2-splicemap3-hg repository
570 abort: splicemap entry afd12345af is not a valid revision identifier
570 abort: splicemap entry afd12345af is not a valid revision identifier
571 [255]
571 [255]
572
572
573 convert sub modules
573 convert sub modules
574 $ hg convert git-repo6 git-repo6-hg
574 $ hg convert git-repo6 git-repo6-hg
575 initializing destination git-repo6-hg repository
575 initializing destination git-repo6-hg repository
576 scanning source...
576 scanning source...
577 sorting...
577 sorting...
578 converting...
578 converting...
579 0 addsubmodule
579 0 addsubmodule
580 updating bookmarks
580 updating bookmarks
581 $ hg -R git-repo6-hg log -v
581 $ hg -R git-repo6-hg log -v
582 changeset: 0:* (glob)
582 changeset: 0:* (glob)
583 bookmark: master
583 bookmark: master
584 tag: tip
584 tag: tip
585 user: nottest <test@example.org>
585 user: nottest <test@example.org>
586 date: Mon Jan 01 00:00:23 2007 +0000
586 date: Mon Jan 01 00:00:23 2007 +0000
587 files: .hgsub .hgsubstate
587 files: .hgsub .hgsubstate
588 description:
588 description:
589 addsubmodule
589 addsubmodule
590
590
591 committer: test <test@example.org>
591 committer: test <test@example.org>
592
592
593
593
594
594
595 $ cd git-repo6-hg
595 $ cd git-repo6-hg
596 $ hg up >/dev/null 2>/dev/null
596 $ hg up >/dev/null 2>/dev/null
597 $ cat .hgsubstate
597 $ cat .hgsubstate
598 * git-repo5 (glob)
598 * git-repo5 (glob)
599 $ cd git-repo5
599 $ cd git-repo5
600 $ cat foo
600 $ cat foo
601 sub
601 sub
602
602
603 $ cd ../..
603 $ cd ../..
604
604
605 make sure rename detection doesn't break removing and adding gitmodules
605 make sure rename detection doesn't break removing and adding gitmodules
606
606
607 $ cd git-repo6
607 $ cd git-repo6
608 $ git mv .gitmodules .gitmodules-renamed
608 $ git mv .gitmodules .gitmodules-renamed
609 $ commit -a -m 'rename .gitmodules'
609 $ commit -a -m 'rename .gitmodules'
610 $ git mv .gitmodules-renamed .gitmodules
610 $ git mv .gitmodules-renamed .gitmodules
611 $ commit -a -m 'rename .gitmodules back'
611 $ commit -a -m 'rename .gitmodules back'
612 $ cd ..
612 $ cd ..
613
613
614 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
614 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
615 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
615 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
616 rename .gitmodules
616 rename .gitmodules
617 $ hg -R git-repo6-hg status -C --change 'tip^'
617 $ hg -R git-repo6-hg status -C --change 'tip^'
618 A .gitmodules-renamed
618 A .gitmodules-renamed
619 R .hgsub
619 R .hgsub
620 R .hgsubstate
620 R .hgsubstate
621 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
621 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
622 rename .gitmodules back
622 rename .gitmodules back
623 $ hg -R git-repo6-hg status -C --change tip
623 $ hg -R git-repo6-hg status -C --change tip
624 A .hgsub
624 A .hgsub
625 A .hgsubstate
625 A .hgsubstate
626 R .gitmodules-renamed
626 R .gitmodules-renamed
627
627
628 convert the revision removing '.gitmodules' itself (and related
628 convert the revision removing '.gitmodules' itself (and related
629 submodules)
629 submodules)
630
630
631 $ cd git-repo6
631 $ cd git-repo6
632 $ git rm .gitmodules
632 $ git rm .gitmodules
633 rm '.gitmodules'
633 rm '.gitmodules'
634 $ git rm --cached git-repo5
634 $ git rm --cached git-repo5
635 rm 'git-repo5'
635 rm 'git-repo5'
636 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
636 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
637 $ cd ..
637 $ cd ..
638
638
639 $ hg convert -q git-repo6 git-repo6-hg
639 $ hg convert -q git-repo6 git-repo6-hg
640 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
640 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
641 remove .gitmodules and submodule git-repo5
641 remove .gitmodules and submodule git-repo5
642 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
642 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
643 .hgsub .hgsubstate
643 .hgsub .hgsubstate
644
644
645 convert using a different remote prefix
646 $ git init git-repo7
647 Initialized empty Git repository in $TESTTMP/git-repo7/.git/
648 $ cd git-repo7
649 $ touch a && git add a && git commit -am "commit a"
650 [master (root-commit) 8ae5f69] commit a
651 Author: nottest <test@example.org>
652 1 file changed, 0 insertions(+), 0 deletions(-)
653 create mode 100644 a
654 $ cd ..
655 $ git clone git-repo7 git-repo7-client
656 Cloning into 'git-repo7-client'...
657 done.
658 $ hg convert --config convert.git.remoteprefix=origin git-repo7-client hg-repo7
659 initializing destination hg-repo7 repository
660 scanning source...
661 sorting...
662 converting...
663 0 commit a
664 updating bookmarks
665 $ hg -R hg-repo7 bookmarks
666 master 0:03bf38caa4c6
667 origin/master 0:03bf38caa4c6
668
645 damaged git repository tests:
669 damaged git repository tests:
646 In case the hard-coded hashes change, the following commands can be used to
670 In case the hard-coded hashes change, the following commands can be used to
647 list the hashes and their corresponding types in the repository:
671 list the hashes and their corresponding types in the repository:
648 cd git-repo4/.git/objects
672 cd git-repo4/.git/objects
649 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
673 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
650 cd ../../..
674 cd ../../..
651
675
652 damage git repository by renaming a commit object
676 damage git repository by renaming a commit object
653 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
677 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
654 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
678 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
655 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
679 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
656 abort: cannot read tags from git-repo4/.git
680 abort: cannot read tags from git-repo4/.git
657 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
681 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
658 damage git repository by renaming a blob object
682 damage git repository by renaming a blob object
659
683
660 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
684 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
661 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
685 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
662 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
686 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
663 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
687 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
664 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
688 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
665 damage git repository by renaming a tree object
689 damage git repository by renaming a tree object
666
690
667 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
691 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
668 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
692 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
669 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
693 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
670 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
694 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
@@ -1,523 +1,527 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 convert.git.remoteprefix
263 remote refs are converted as bookmarks with
264 "convert.git.remoteprefix" as a prefix followed by a /. The
265 default is 'remote'.
262
266
263 Perforce Source
267 Perforce Source
264 ###############
268 ###############
265
269
266 The Perforce (P4) importer can be given a p4 depot path or a client
270 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
271 specification as source. It will convert all files in the source to a flat
268 Mercurial repository, ignoring labels, branches and integrations. Note
272 Mercurial repository, ignoring labels, branches and integrations. Note
269 that when a depot path is given you then usually should specify a target
273 that when a depot path is given you then usually should specify a target
270 directory, because otherwise the target may be named "...-hg".
274 directory, because otherwise the target may be named "...-hg".
271
275
272 It is possible to limit the amount of source history to be converted by
276 It is possible to limit the amount of source history to be converted by
273 specifying an initial Perforce revision:
277 specifying an initial Perforce revision:
274
278
275 convert.p4.startrev
279 convert.p4.startrev
276 specify initial Perforce revision (a Perforce changelist
280 specify initial Perforce revision (a Perforce changelist
277 number).
281 number).
278
282
279 Mercurial Destination
283 Mercurial Destination
280 #####################
284 #####################
281
285
282 The Mercurial destination will recognize Mercurial subrepositories in the
286 The Mercurial destination will recognize Mercurial subrepositories in the
283 destination directory, and update the .hgsubstate file automatically if
287 destination directory, and update the .hgsubstate file automatically if
284 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
288 the destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
285 Converting a repository with subrepositories requires converting a single
289 Converting a repository with subrepositories requires converting a single
286 repository at a time, from the bottom up.
290 repository at a time, from the bottom up.
287
291
288 The following options are supported:
292 The following options are supported:
289
293
290 convert.hg.clonebranches
294 convert.hg.clonebranches
291 dispatch source branches in separate clones. The default is
295 dispatch source branches in separate clones. The default is
292 False.
296 False.
293 convert.hg.tagsbranch
297 convert.hg.tagsbranch
294 branch name for tag revisions, defaults to "default".
298 branch name for tag revisions, defaults to "default".
295 convert.hg.usebranchnames
299 convert.hg.usebranchnames
296 preserve branch names. The default is True.
300 preserve branch names. The default is True.
297 convert.hg.sourcename
301 convert.hg.sourcename
298 records the given string as a 'convert_source' extra value
302 records the given string as a 'convert_source' extra value
299 on each commit made in the target repository. The default is
303 on each commit made in the target repository. The default is
300 None.
304 None.
301
305
302 All Destinations
306 All Destinations
303 ################
307 ################
304
308
305 All destination types accept the following options:
309 All destination types accept the following options:
306
310
307 convert.skiptags
311 convert.skiptags
308 does not convert tags from the source repo to the target
312 does not convert tags from the source repo to the target
309 repo. The default is False.
313 repo. The default is False.
310
314
311 options ([+] can be repeated):
315 options ([+] can be repeated):
312
316
313 -s --source-type TYPE source repository type
317 -s --source-type TYPE source repository type
314 -d --dest-type TYPE destination repository type
318 -d --dest-type TYPE destination repository type
315 -r --rev REV [+] import up to source revision REV
319 -r --rev REV [+] import up to source revision REV
316 -A --authormap FILE remap usernames using this file
320 -A --authormap FILE remap usernames using this file
317 --filemap FILE remap file names using contents of file
321 --filemap FILE remap file names using contents of file
318 --full apply filemap changes by converting all files again
322 --full apply filemap changes by converting all files again
319 --splicemap FILE splice synthesized history into place
323 --splicemap FILE splice synthesized history into place
320 --branchmap FILE change branch names while converting
324 --branchmap FILE change branch names while converting
321 --branchsort try to sort changesets by branches
325 --branchsort try to sort changesets by branches
322 --datesort try to sort changesets by date
326 --datesort try to sort changesets by date
323 --sourcesort preserve source changesets order
327 --sourcesort preserve source changesets order
324 --closesort try to reorder closed revisions
328 --closesort try to reorder closed revisions
325
329
326 (some details hidden, use --verbose to show complete help)
330 (some details hidden, use --verbose to show complete help)
327 $ hg init a
331 $ hg init a
328 $ cd a
332 $ cd a
329 $ echo a > a
333 $ echo a > a
330 $ hg ci -d'0 0' -Ama
334 $ hg ci -d'0 0' -Ama
331 adding a
335 adding a
332 $ hg cp a b
336 $ hg cp a b
333 $ hg ci -d'1 0' -mb
337 $ hg ci -d'1 0' -mb
334 $ hg rm a
338 $ hg rm a
335 $ hg ci -d'2 0' -mc
339 $ hg ci -d'2 0' -mc
336 $ hg mv b a
340 $ hg mv b a
337 $ hg ci -d'3 0' -md
341 $ hg ci -d'3 0' -md
338 $ echo a >> a
342 $ echo a >> a
339 $ hg ci -d'4 0' -me
343 $ hg ci -d'4 0' -me
340 $ cd ..
344 $ cd ..
341 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
345 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
342 assuming destination a-hg
346 assuming destination a-hg
343 initializing destination a-hg repository
347 initializing destination a-hg repository
344 scanning source...
348 scanning source...
345 sorting...
349 sorting...
346 converting...
350 converting...
347 4 a
351 4 a
348 3 b
352 3 b
349 2 c
353 2 c
350 1 d
354 1 d
351 0 e
355 0 e
352 $ hg --cwd a-hg pull ../a
356 $ hg --cwd a-hg pull ../a
353 pulling from ../a
357 pulling from ../a
354 searching for changes
358 searching for changes
355 no changes found
359 no changes found
356
360
357 conversion to existing file should fail
361 conversion to existing file should fail
358
362
359 $ touch bogusfile
363 $ touch bogusfile
360 $ hg convert a bogusfile
364 $ hg convert a bogusfile
361 initializing destination bogusfile repository
365 initializing destination bogusfile repository
362 abort: cannot create new bundle repository
366 abort: cannot create new bundle repository
363 [255]
367 [255]
364
368
365 #if unix-permissions no-root
369 #if unix-permissions no-root
366
370
367 conversion to dir without permissions should fail
371 conversion to dir without permissions should fail
368
372
369 $ mkdir bogusdir
373 $ mkdir bogusdir
370 $ chmod 000 bogusdir
374 $ chmod 000 bogusdir
371
375
372 $ hg convert a bogusdir
376 $ hg convert a bogusdir
373 abort: Permission denied: 'bogusdir'
377 abort: Permission denied: 'bogusdir'
374 [255]
378 [255]
375
379
376 user permissions should succeed
380 user permissions should succeed
377
381
378 $ chmod 700 bogusdir
382 $ chmod 700 bogusdir
379 $ hg convert a bogusdir
383 $ hg convert a bogusdir
380 initializing destination bogusdir repository
384 initializing destination bogusdir repository
381 scanning source...
385 scanning source...
382 sorting...
386 sorting...
383 converting...
387 converting...
384 4 a
388 4 a
385 3 b
389 3 b
386 2 c
390 2 c
387 1 d
391 1 d
388 0 e
392 0 e
389
393
390 #endif
394 #endif
391
395
392 test pre and post conversion actions
396 test pre and post conversion actions
393
397
394 $ echo 'include b' > filemap
398 $ echo 'include b' > filemap
395 $ hg convert --debug --filemap filemap a partialb | \
399 $ hg convert --debug --filemap filemap a partialb | \
396 > grep 'run hg'
400 > grep 'run hg'
397 run hg source pre-conversion action
401 run hg source pre-conversion action
398 run hg sink pre-conversion action
402 run hg sink pre-conversion action
399 run hg sink post-conversion action
403 run hg sink post-conversion action
400 run hg source post-conversion action
404 run hg source post-conversion action
401
405
402 converting empty dir should fail "nicely
406 converting empty dir should fail "nicely
403
407
404 $ mkdir emptydir
408 $ mkdir emptydir
405
409
406 override $PATH to ensure p4 not visible; use $PYTHON in case we're
410 override $PATH to ensure p4 not visible; use $PYTHON in case we're
407 running from a devel copy, not a temp installation
411 running from a devel copy, not a temp installation
408
412
409 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
413 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
410 assuming destination emptydir-hg
414 assuming destination emptydir-hg
411 initializing destination emptydir-hg repository
415 initializing destination emptydir-hg repository
412 emptydir does not look like a CVS checkout
416 emptydir does not look like a CVS checkout
413 emptydir does not look like a Git repository
417 emptydir does not look like a Git repository
414 emptydir does not look like a Subversion repository
418 emptydir does not look like a Subversion repository
415 emptydir is not a local Mercurial repository
419 emptydir is not a local Mercurial repository
416 emptydir does not look like a darcs repository
420 emptydir does not look like a darcs repository
417 emptydir does not look like a monotone repository
421 emptydir does not look like a monotone repository
418 emptydir does not look like a GNU Arch repository
422 emptydir does not look like a GNU Arch repository
419 emptydir does not look like a Bazaar repository
423 emptydir does not look like a Bazaar repository
420 cannot find required "p4" tool
424 cannot find required "p4" tool
421 abort: emptydir: missing or unsupported repository
425 abort: emptydir: missing or unsupported repository
422 [255]
426 [255]
423
427
424 convert with imaginary source type
428 convert with imaginary source type
425
429
426 $ hg convert --source-type foo a a-foo
430 $ hg convert --source-type foo a a-foo
427 initializing destination a-foo repository
431 initializing destination a-foo repository
428 abort: foo: invalid source repository type
432 abort: foo: invalid source repository type
429 [255]
433 [255]
430
434
431 convert with imaginary sink type
435 convert with imaginary sink type
432
436
433 $ hg convert --dest-type foo a a-foo
437 $ hg convert --dest-type foo a a-foo
434 abort: foo: invalid destination repository type
438 abort: foo: invalid destination repository type
435 [255]
439 [255]
436
440
437 testing: convert must not produce duplicate entries in fncache
441 testing: convert must not produce duplicate entries in fncache
438
442
439 $ hg convert a b
443 $ hg convert a b
440 initializing destination b repository
444 initializing destination b repository
441 scanning source...
445 scanning source...
442 sorting...
446 sorting...
443 converting...
447 converting...
444 4 a
448 4 a
445 3 b
449 3 b
446 2 c
450 2 c
447 1 d
451 1 d
448 0 e
452 0 e
449
453
450 contents of fncache file:
454 contents of fncache file:
451
455
452 $ cat b/.hg/store/fncache | sort
456 $ cat b/.hg/store/fncache | sort
453 data/a.i
457 data/a.i
454 data/b.i
458 data/b.i
455
459
456 test bogus URL
460 test bogus URL
457
461
458 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
462 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
459 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
463 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
460 [255]
464 [255]
461
465
462 test revset converted() lookup
466 test revset converted() lookup
463
467
464 $ hg --config convert.hg.saverev=True convert a c
468 $ hg --config convert.hg.saverev=True convert a c
465 initializing destination c repository
469 initializing destination c repository
466 scanning source...
470 scanning source...
467 sorting...
471 sorting...
468 converting...
472 converting...
469 4 a
473 4 a
470 3 b
474 3 b
471 2 c
475 2 c
472 1 d
476 1 d
473 0 e
477 0 e
474 $ echo f > c/f
478 $ echo f > c/f
475 $ hg -R c ci -d'0 0' -Amf
479 $ hg -R c ci -d'0 0' -Amf
476 adding f
480 adding f
477 created new head
481 created new head
478 $ hg -R c log -r "converted(09d945a62ce6)"
482 $ hg -R c log -r "converted(09d945a62ce6)"
479 changeset: 1:98c3dd46a874
483 changeset: 1:98c3dd46a874
480 user: test
484 user: test
481 date: Thu Jan 01 00:00:01 1970 +0000
485 date: Thu Jan 01 00:00:01 1970 +0000
482 summary: b
486 summary: b
483
487
484 $ hg -R c log -r "converted()"
488 $ hg -R c log -r "converted()"
485 changeset: 0:31ed57b2037c
489 changeset: 0:31ed57b2037c
486 user: test
490 user: test
487 date: Thu Jan 01 00:00:00 1970 +0000
491 date: Thu Jan 01 00:00:00 1970 +0000
488 summary: a
492 summary: a
489
493
490 changeset: 1:98c3dd46a874
494 changeset: 1:98c3dd46a874
491 user: test
495 user: test
492 date: Thu Jan 01 00:00:01 1970 +0000
496 date: Thu Jan 01 00:00:01 1970 +0000
493 summary: b
497 summary: b
494
498
495 changeset: 2:3b9ca06ef716
499 changeset: 2:3b9ca06ef716
496 user: test
500 user: test
497 date: Thu Jan 01 00:00:02 1970 +0000
501 date: Thu Jan 01 00:00:02 1970 +0000
498 summary: c
502 summary: c
499
503
500 changeset: 3:4e0debd37cf2
504 changeset: 3:4e0debd37cf2
501 user: test
505 user: test
502 date: Thu Jan 01 00:00:03 1970 +0000
506 date: Thu Jan 01 00:00:03 1970 +0000
503 summary: d
507 summary: d
504
508
505 changeset: 4:9de3bc9349c5
509 changeset: 4:9de3bc9349c5
506 user: test
510 user: test
507 date: Thu Jan 01 00:00:04 1970 +0000
511 date: Thu Jan 01 00:00:04 1970 +0000
508 summary: e
512 summary: e
509
513
510
514
511 test specifying a sourcename
515 test specifying a sourcename
512 $ echo g > a/g
516 $ echo g > a/g
513 $ hg -R a ci -d'0 0' -Amg
517 $ hg -R a ci -d'0 0' -Amg
514 adding g
518 adding g
515 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
519 $ hg --config convert.hg.sourcename=mysource --config convert.hg.saverev=True convert a c
516 scanning source...
520 scanning source...
517 sorting...
521 sorting...
518 converting...
522 converting...
519 0 g
523 0 g
520 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
524 $ hg -R c log -r tip --template '{extras % "{extra}\n"}'
521 branch=default
525 branch=default
522 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
526 convert_revision=a3bc6100aa8ec03e00aaf271f1f50046fb432072
523 convert_source=mysource
527 convert_source=mysource
General Comments 0
You need to be logged in to leave comments. Login now