##// END OF EJS Templates
convert: add support to detect git renames and copies...
Siddharth Agarwal -
r22470:8e0c4df2 default
parent child Browse files
Show More
@@ -1,391 +1,400 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 testedwith = 'internal'
18 testedwith = 'internal'
19
19
20 # Commands definition was moved elsewhere to ease demandload job.
20 # Commands definition was moved elsewhere to ease demandload job.
21
21
22 @command('convert',
22 @command('convert',
23 [('', 'authors', '',
23 [('', 'authors', '',
24 _('username mapping filename (DEPRECATED, use --authormap instead)'),
24 _('username mapping filename (DEPRECATED, use --authormap instead)'),
25 _('FILE')),
25 _('FILE')),
26 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
26 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
27 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
27 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
28 ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
28 ('r', 'rev', '', _('import up to source revision REV'), _('REV')),
29 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
29 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
30 ('', 'filemap', '', _('remap file names using contents of file'),
30 ('', 'filemap', '', _('remap file names using contents of file'),
31 _('FILE')),
31 _('FILE')),
32 ('', 'full', None,
32 ('', 'full', None,
33 _('apply filemap changes by converting all files again')),
33 _('apply filemap changes by converting all files again')),
34 ('', 'splicemap', '', _('splice synthesized history into place'),
34 ('', 'splicemap', '', _('splice synthesized history into place'),
35 _('FILE')),
35 _('FILE')),
36 ('', 'branchmap', '', _('change branch names while converting'),
36 ('', 'branchmap', '', _('change branch names while converting'),
37 _('FILE')),
37 _('FILE')),
38 ('', 'branchsort', None, _('try to sort changesets by branches')),
38 ('', 'branchsort', None, _('try to sort changesets by branches')),
39 ('', 'datesort', None, _('try to sort changesets by date')),
39 ('', 'datesort', None, _('try to sort changesets by date')),
40 ('', 'sourcesort', None, _('preserve source changesets order')),
40 ('', 'sourcesort', None, _('preserve source changesets order')),
41 ('', 'closesort', None, _('try to reorder closed revisions'))],
41 ('', 'closesort', None, _('try to reorder closed revisions'))],
42 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
42 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
43 norepo=True)
43 norepo=True)
44 def convert(ui, src, dest=None, revmapfile=None, **opts):
44 def convert(ui, src, dest=None, revmapfile=None, **opts):
45 """convert a foreign SCM repository to a Mercurial one.
45 """convert a foreign SCM repository to a Mercurial one.
46
46
47 Accepted source formats [identifiers]:
47 Accepted source formats [identifiers]:
48
48
49 - Mercurial [hg]
49 - Mercurial [hg]
50 - CVS [cvs]
50 - CVS [cvs]
51 - Darcs [darcs]
51 - Darcs [darcs]
52 - git [git]
52 - git [git]
53 - Subversion [svn]
53 - Subversion [svn]
54 - Monotone [mtn]
54 - Monotone [mtn]
55 - GNU Arch [gnuarch]
55 - GNU Arch [gnuarch]
56 - Bazaar [bzr]
56 - Bazaar [bzr]
57 - Perforce [p4]
57 - Perforce [p4]
58
58
59 Accepted destination formats [identifiers]:
59 Accepted destination formats [identifiers]:
60
60
61 - Mercurial [hg]
61 - Mercurial [hg]
62 - Subversion [svn] (history on branches is not preserved)
62 - Subversion [svn] (history on branches is not preserved)
63
63
64 If no revision is given, all revisions will be converted.
64 If no revision is given, all revisions will be converted.
65 Otherwise, convert will only import up to the named revision
65 Otherwise, convert will only import up to the named revision
66 (given in a format understood by the source).
66 (given in a format understood by the source).
67
67
68 If no destination directory name is specified, it defaults to the
68 If no destination directory name is specified, it defaults to the
69 basename of the source with ``-hg`` appended. If the destination
69 basename of the source with ``-hg`` appended. If the destination
70 repository doesn't exist, it will be created.
70 repository doesn't exist, it will be created.
71
71
72 By default, all sources except Mercurial will use --branchsort.
72 By default, all sources except Mercurial will use --branchsort.
73 Mercurial uses --sourcesort to preserve original revision numbers
73 Mercurial uses --sourcesort to preserve original revision numbers
74 order. Sort modes have the following effects:
74 order. Sort modes have the following effects:
75
75
76 --branchsort convert from parent to child revision when possible,
76 --branchsort convert from parent to child revision when possible,
77 which means branches are usually converted one after
77 which means branches are usually converted one after
78 the other. It generates more compact repositories.
78 the other. It generates more compact repositories.
79
79
80 --datesort sort revisions by date. Converted repositories have
80 --datesort sort revisions by date. Converted repositories have
81 good-looking changelogs but are often an order of
81 good-looking changelogs but are often an order of
82 magnitude larger than the same ones generated by
82 magnitude larger than the same ones generated by
83 --branchsort.
83 --branchsort.
84
84
85 --sourcesort try to preserve source revisions order, only
85 --sourcesort try to preserve source revisions order, only
86 supported by Mercurial sources.
86 supported by Mercurial sources.
87
87
88 --closesort try to move closed revisions as close as possible
88 --closesort try to move closed revisions as close as possible
89 to parent branches, only supported by Mercurial
89 to parent branches, only supported by Mercurial
90 sources.
90 sources.
91
91
92 If ``REVMAP`` isn't given, it will be put in a default location
92 If ``REVMAP`` isn't given, it will be put in a default location
93 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
93 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
94 text file that maps each source commit ID to the destination ID
94 text file that maps each source commit ID to the destination ID
95 for that revision, like so::
95 for that revision, like so::
96
96
97 <source ID> <destination ID>
97 <source ID> <destination ID>
98
98
99 If the file doesn't exist, it's automatically created. It's
99 If the file doesn't exist, it's automatically created. It's
100 updated on each commit copied, so :hg:`convert` can be interrupted
100 updated on each commit copied, so :hg:`convert` can be interrupted
101 and can be run repeatedly to copy new commits.
101 and can be run repeatedly to copy new commits.
102
102
103 The authormap is a simple text file that maps each source commit
103 The authormap is a simple text file that maps each source commit
104 author to a destination commit author. It is handy for source SCMs
104 author to a destination commit author. It is handy for source SCMs
105 that use unix logins to identify authors (e.g.: CVS). One line per
105 that use unix logins to identify authors (e.g.: CVS). One line per
106 author mapping and the line format is::
106 author mapping and the line format is::
107
107
108 source author = destination author
108 source author = destination author
109
109
110 Empty lines and lines starting with a ``#`` are ignored.
110 Empty lines and lines starting with a ``#`` are ignored.
111
111
112 The filemap is a file that allows filtering and remapping of files
112 The filemap is a file that allows filtering and remapping of files
113 and directories. Each line can contain one of the following
113 and directories. Each line can contain one of the following
114 directives::
114 directives::
115
115
116 include path/to/file-or-dir
116 include path/to/file-or-dir
117
117
118 exclude path/to/file-or-dir
118 exclude path/to/file-or-dir
119
119
120 rename path/to/source path/to/destination
120 rename path/to/source path/to/destination
121
121
122 Comment lines start with ``#``. A specified path matches if it
122 Comment lines start with ``#``. A specified path matches if it
123 equals the full relative name of a file or one of its parent
123 equals the full relative name of a file or one of its parent
124 directories. The ``include`` or ``exclude`` directive with the
124 directories. The ``include`` or ``exclude`` directive with the
125 longest matching path applies, so line order does not matter.
125 longest matching path applies, so line order does not matter.
126
126
127 The ``include`` directive causes a file, or all files under a
127 The ``include`` directive causes a file, or all files under a
128 directory, to be included in the destination repository. The default
128 directory, to be included in the destination repository. The default
129 if there are no ``include`` statements is to include everything.
129 if there are no ``include`` statements is to include everything.
130 If there are any ``include`` statements, nothing else is included.
130 If there are any ``include`` statements, nothing else is included.
131 The ``exclude`` directive causes files or directories to
131 The ``exclude`` directive causes files or directories to
132 be omitted. The ``rename`` directive renames a file or directory if
132 be omitted. The ``rename`` directive renames a file or directory if
133 it is converted. To rename from a subdirectory into the root of
133 it is converted. To rename from a subdirectory into the root of
134 the repository, use ``.`` as the path to rename to.
134 the repository, use ``.`` as the path to rename to.
135
135
136 ``--full`` will make sure the converted changesets contain exactly
136 ``--full`` will make sure the converted changesets contain exactly
137 the right files with the right content. It will make a full
137 the right files with the right content. It will make a full
138 conversion of all files, not just the ones that have
138 conversion of all files, not just the ones that have
139 changed. Files that already are correct will not be changed. This
139 changed. Files that already are correct will not be changed. This
140 can be used to apply filemap changes when converting
140 can be used to apply filemap changes when converting
141 incrementally. This is currently only supported for Mercurial and
141 incrementally. This is currently only supported for Mercurial and
142 Subversion.
142 Subversion.
143
143
144 The splicemap is a file that allows insertion of synthetic
144 The splicemap is a file that allows insertion of synthetic
145 history, letting you specify the parents of a revision. This is
145 history, letting you specify the parents of a revision. This is
146 useful if you want to e.g. give a Subversion merge two parents, or
146 useful if you want to e.g. give a Subversion merge two parents, or
147 graft two disconnected series of history together. Each entry
147 graft two disconnected series of history together. Each entry
148 contains a key, followed by a space, followed by one or two
148 contains a key, followed by a space, followed by one or two
149 comma-separated values::
149 comma-separated values::
150
150
151 key parent1, parent2
151 key parent1, parent2
152
152
153 The key is the revision ID in the source
153 The key is the revision ID in the source
154 revision control system whose parents should be modified (same
154 revision control system whose parents should be modified (same
155 format as a key in .hg/shamap). The values are the revision IDs
155 format as a key in .hg/shamap). The values are the revision IDs
156 (in either the source or destination revision control system) that
156 (in either the source or destination revision control system) that
157 should be used as the new parents for that node. For example, if
157 should be used as the new parents for that node. For example, if
158 you have merged "release-1.0" into "trunk", then you should
158 you have merged "release-1.0" into "trunk", then you should
159 specify the revision on "trunk" as the first parent and the one on
159 specify the revision on "trunk" as the first parent and the one on
160 the "release-1.0" branch as the second.
160 the "release-1.0" branch as the second.
161
161
162 The branchmap is a file that allows you to rename a branch when it is
162 The branchmap is a file that allows you to rename a branch when it is
163 being brought in from whatever external repository. When used in
163 being brought in from whatever external repository. When used in
164 conjunction with a splicemap, it allows for a powerful combination
164 conjunction with a splicemap, it allows for a powerful combination
165 to help fix even the most badly mismanaged repositories and turn them
165 to help fix even the most badly mismanaged repositories and turn them
166 into nicely structured Mercurial repositories. The branchmap contains
166 into nicely structured Mercurial repositories. The branchmap contains
167 lines of the form::
167 lines of the form::
168
168
169 original_branch_name new_branch_name
169 original_branch_name new_branch_name
170
170
171 where "original_branch_name" is the name of the branch in the
171 where "original_branch_name" is the name of the branch in the
172 source repository, and "new_branch_name" is the name of the branch
172 source repository, and "new_branch_name" is the name of the branch
173 is the destination repository. No whitespace is allowed in the
173 is the destination repository. No whitespace is allowed in the
174 branch names. This can be used to (for instance) move code in one
174 branch names. This can be used to (for instance) move code in one
175 repository from "default" to a named branch.
175 repository from "default" to a named branch.
176
176
177 Mercurial Source
177 Mercurial Source
178 ################
178 ################
179
179
180 The Mercurial source recognizes the following configuration
180 The Mercurial source recognizes the following configuration
181 options, which you can set on the command line with ``--config``:
181 options, which you can set on the command line with ``--config``:
182
182
183 :convert.hg.ignoreerrors: ignore integrity errors when reading.
183 :convert.hg.ignoreerrors: ignore integrity errors when reading.
184 Use it to fix Mercurial repositories with missing revlogs, by
184 Use it to fix Mercurial repositories with missing revlogs, by
185 converting from and to Mercurial. Default is False.
185 converting from and to Mercurial. Default is False.
186
186
187 :convert.hg.saverev: store original revision ID in changeset
187 :convert.hg.saverev: store original revision ID in changeset
188 (forces target IDs to change). It takes a boolean argument and
188 (forces target IDs to change). It takes a boolean argument and
189 defaults to False.
189 defaults to False.
190
190
191 :convert.hg.revs: revset specifying the source revisions to convert.
191 :convert.hg.revs: revset specifying the source revisions to convert.
192
192
193 CVS Source
193 CVS Source
194 ##########
194 ##########
195
195
196 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
196 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
197 to indicate the starting point of what will be converted. Direct
197 to indicate the starting point of what will be converted. Direct
198 access to the repository files is not needed, unless of course the
198 access to the repository files is not needed, unless of course the
199 repository is ``:local:``. The conversion uses the top level
199 repository is ``:local:``. The conversion uses the top level
200 directory in the sandbox to find the CVS repository, and then uses
200 directory in the sandbox to find the CVS repository, and then uses
201 CVS rlog commands to find files to convert. This means that unless
201 CVS rlog commands to find files to convert. This means that unless
202 a filemap is given, all files under the starting directory will be
202 a filemap is given, all files under the starting directory will be
203 converted, and that any directory reorganization in the CVS
203 converted, and that any directory reorganization in the CVS
204 sandbox is ignored.
204 sandbox is ignored.
205
205
206 The following options can be used with ``--config``:
206 The following options can be used with ``--config``:
207
207
208 :convert.cvsps.cache: Set to False to disable remote log caching,
208 :convert.cvsps.cache: Set to False to disable remote log caching,
209 for testing and debugging purposes. Default is True.
209 for testing and debugging purposes. Default is True.
210
210
211 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
211 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
212 allowed between commits with identical user and log message in
212 allowed between commits with identical user and log message in
213 a single changeset. When very large files were checked in as
213 a single changeset. When very large files were checked in as
214 part of a changeset then the default may not be long enough.
214 part of a changeset then the default may not be long enough.
215 The default is 60.
215 The default is 60.
216
216
217 :convert.cvsps.mergeto: Specify a regular expression to which
217 :convert.cvsps.mergeto: Specify a regular expression to which
218 commit log messages are matched. If a match occurs, then the
218 commit log messages are matched. If a match occurs, then the
219 conversion process will insert a dummy revision merging the
219 conversion process will insert a dummy revision merging the
220 branch on which this log message occurs to the branch
220 branch on which this log message occurs to the branch
221 indicated in the regex. Default is ``{{mergetobranch
221 indicated in the regex. Default is ``{{mergetobranch
222 ([-\\w]+)}}``
222 ([-\\w]+)}}``
223
223
224 :convert.cvsps.mergefrom: Specify a regular expression to which
224 :convert.cvsps.mergefrom: Specify a regular expression to which
225 commit log messages are matched. If a match occurs, then the
225 commit log messages are matched. If a match occurs, then the
226 conversion process will add the most recent revision on the
226 conversion process will add the most recent revision on the
227 branch indicated in the regex as the second parent of the
227 branch indicated in the regex as the second parent of the
228 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
228 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
229
229
230 :convert.localtimezone: use local time (as determined by the TZ
230 :convert.localtimezone: use local time (as determined by the TZ
231 environment variable) for changeset date/times. The default
231 environment variable) for changeset date/times. The default
232 is False (use UTC).
232 is False (use UTC).
233
233
234 :hooks.cvslog: Specify a Python function to be called at the end of
234 :hooks.cvslog: Specify a Python function to be called at the end of
235 gathering the CVS log. The function is passed a list with the
235 gathering the CVS log. The function is passed a list with the
236 log entries, and can modify the entries in-place, or add or
236 log entries, and can modify the entries in-place, or add or
237 delete them.
237 delete them.
238
238
239 :hooks.cvschangesets: Specify a Python function to be called after
239 :hooks.cvschangesets: Specify a Python function to be called after
240 the changesets are calculated from the CVS log. The
240 the changesets are calculated from the CVS log. The
241 function is passed a list with the changeset entries, and can
241 function is passed a list with the changeset entries, and can
242 modify the changesets in-place, or add or delete them.
242 modify the changesets in-place, or add or delete them.
243
243
244 An additional "debugcvsps" Mercurial command allows the builtin
244 An additional "debugcvsps" Mercurial command allows the builtin
245 changeset merging code to be run without doing a conversion. Its
245 changeset merging code to be run without doing a conversion. Its
246 parameters and output are similar to that of cvsps 2.1. Please see
246 parameters and output are similar to that of cvsps 2.1. Please see
247 the command help for more details.
247 the command help for more details.
248
248
249 Subversion Source
249 Subversion Source
250 #################
250 #################
251
251
252 Subversion source detects classical trunk/branches/tags layouts.
252 Subversion source detects classical trunk/branches/tags layouts.
253 By default, the supplied ``svn://repo/path/`` source URL is
253 By default, the supplied ``svn://repo/path/`` source URL is
254 converted as a single branch. If ``svn://repo/path/trunk`` exists
254 converted as a single branch. If ``svn://repo/path/trunk`` exists
255 it replaces the default branch. If ``svn://repo/path/branches``
255 it replaces the default branch. If ``svn://repo/path/branches``
256 exists, its subdirectories are listed as possible branches. If
256 exists, its subdirectories are listed as possible branches. If
257 ``svn://repo/path/tags`` exists, it is looked for tags referencing
257 ``svn://repo/path/tags`` exists, it is looked for tags referencing
258 converted branches. Default ``trunk``, ``branches`` and ``tags``
258 converted branches. Default ``trunk``, ``branches`` and ``tags``
259 values can be overridden with following options. Set them to paths
259 values can be overridden with following options. Set them to paths
260 relative to the source URL, or leave them blank to disable auto
260 relative to the source URL, or leave them blank to disable auto
261 detection.
261 detection.
262
262
263 The following options can be set with ``--config``:
263 The following options can be set with ``--config``:
264
264
265 :convert.svn.branches: specify the directory containing branches.
265 :convert.svn.branches: specify the directory containing branches.
266 The default is ``branches``.
266 The default is ``branches``.
267
267
268 :convert.svn.tags: specify the directory containing tags. The
268 :convert.svn.tags: specify the directory containing tags. The
269 default is ``tags``.
269 default is ``tags``.
270
270
271 :convert.svn.trunk: specify the name of the trunk branch. The
271 :convert.svn.trunk: specify the name of the trunk branch. The
272 default is ``trunk``.
272 default is ``trunk``.
273
273
274 :convert.localtimezone: use local time (as determined by the TZ
274 :convert.localtimezone: use local time (as determined by the TZ
275 environment variable) for changeset date/times. The default
275 environment variable) for changeset date/times. The default
276 is False (use UTC).
276 is False (use UTC).
277
277
278 Source history can be retrieved starting at a specific revision,
278 Source history can be retrieved starting at a specific revision,
279 instead of being integrally converted. Only single branch
279 instead of being integrally converted. Only single branch
280 conversions are supported.
280 conversions are supported.
281
281
282 :convert.svn.startrev: specify start Subversion revision number.
282 :convert.svn.startrev: specify start Subversion revision number.
283 The default is 0.
283 The default is 0.
284
284
285 Git Source
285 Git Source
286 ##########
286 ##########
287
287
288 The Git importer converts commits from all reachable branches (refs
288 The Git importer converts commits from all reachable branches (refs
289 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
289 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
290 Branches are converted to bookmarks with the same name, with the
290 Branches are converted to bookmarks with the same name, with the
291 leading 'refs/heads' stripped. Git submodules are converted to Git
291 leading 'refs/heads' stripped. Git submodules are converted to Git
292 subrepos in Mercurial.
292 subrepos in Mercurial.
293
293
294 The following options can be set with ``--config``:
295
296 :convert.git.similarity: specify how similar files modified in a
297 commit must be to be imported as renames or copies, as a
298 percentage between ``0`` (disabled) and ``100`` (files must be
299 identical). For example, ``90`` means that a delete/add pair will
300 be imported as a rename if more than 90% of the file hasn't
301 changed. The default is ``0``.
302
294 Perforce Source
303 Perforce Source
295 ###############
304 ###############
296
305
297 The Perforce (P4) importer can be given a p4 depot path or a
306 The Perforce (P4) importer can be given a p4 depot path or a
298 client specification as source. It will convert all files in the
307 client specification as source. It will convert all files in the
299 source to a flat Mercurial repository, ignoring labels, branches
308 source to a flat Mercurial repository, ignoring labels, branches
300 and integrations. Note that when a depot path is given you then
309 and integrations. Note that when a depot path is given you then
301 usually should specify a target directory, because otherwise the
310 usually should specify a target directory, because otherwise the
302 target may be named ``...-hg``.
311 target may be named ``...-hg``.
303
312
304 It is possible to limit the amount of source history to be
313 It is possible to limit the amount of source history to be
305 converted by specifying an initial Perforce revision:
314 converted by specifying an initial Perforce revision:
306
315
307 :convert.p4.startrev: specify initial Perforce revision (a
316 :convert.p4.startrev: specify initial Perforce revision (a
308 Perforce changelist number).
317 Perforce changelist number).
309
318
310 Mercurial Destination
319 Mercurial Destination
311 #####################
320 #####################
312
321
313 The following options are supported:
322 The following options are supported:
314
323
315 :convert.hg.clonebranches: dispatch source branches in separate
324 :convert.hg.clonebranches: dispatch source branches in separate
316 clones. The default is False.
325 clones. The default is False.
317
326
318 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
327 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
319 ``default``.
328 ``default``.
320
329
321 :convert.hg.usebranchnames: preserve branch names. The default is
330 :convert.hg.usebranchnames: preserve branch names. The default is
322 True.
331 True.
323 """
332 """
324 return convcmd.convert(ui, src, dest, revmapfile, **opts)
333 return convcmd.convert(ui, src, dest, revmapfile, **opts)
325
334
326 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
335 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
327 def debugsvnlog(ui, **opts):
336 def debugsvnlog(ui, **opts):
328 return subversion.debugsvnlog(ui, **opts)
337 return subversion.debugsvnlog(ui, **opts)
329
338
330 @command('debugcvsps',
339 @command('debugcvsps',
331 [
340 [
332 # Main options shared with cvsps-2.1
341 # Main options shared with cvsps-2.1
333 ('b', 'branches', [], _('only return changes on specified branches')),
342 ('b', 'branches', [], _('only return changes on specified branches')),
334 ('p', 'prefix', '', _('prefix to remove from file names')),
343 ('p', 'prefix', '', _('prefix to remove from file names')),
335 ('r', 'revisions', [],
344 ('r', 'revisions', [],
336 _('only return changes after or between specified tags')),
345 _('only return changes after or between specified tags')),
337 ('u', 'update-cache', None, _("update cvs log cache")),
346 ('u', 'update-cache', None, _("update cvs log cache")),
338 ('x', 'new-cache', None, _("create new cvs log cache")),
347 ('x', 'new-cache', None, _("create new cvs log cache")),
339 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
348 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
340 ('', 'root', '', _('specify cvsroot')),
349 ('', 'root', '', _('specify cvsroot')),
341 # Options specific to builtin cvsps
350 # Options specific to builtin cvsps
342 ('', 'parents', '', _('show parent changesets')),
351 ('', 'parents', '', _('show parent changesets')),
343 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
352 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
344 # Options that are ignored for compatibility with cvsps-2.1
353 # Options that are ignored for compatibility with cvsps-2.1
345 ('A', 'cvs-direct', None, _('ignored for compatibility')),
354 ('A', 'cvs-direct', None, _('ignored for compatibility')),
346 ],
355 ],
347 _('hg debugcvsps [OPTION]... [PATH]...'),
356 _('hg debugcvsps [OPTION]... [PATH]...'),
348 norepo=True)
357 norepo=True)
349 def debugcvsps(ui, *args, **opts):
358 def debugcvsps(ui, *args, **opts):
350 '''create changeset information from CVS
359 '''create changeset information from CVS
351
360
352 This command is intended as a debugging tool for the CVS to
361 This command is intended as a debugging tool for the CVS to
353 Mercurial converter, and can be used as a direct replacement for
362 Mercurial converter, and can be used as a direct replacement for
354 cvsps.
363 cvsps.
355
364
356 Hg debugcvsps reads the CVS rlog for current directory (or any
365 Hg debugcvsps reads the CVS rlog for current directory (or any
357 named directory) in the CVS repository, and converts the log to a
366 named directory) in the CVS repository, and converts the log to a
358 series of changesets based on matching commit log entries and
367 series of changesets based on matching commit log entries and
359 dates.'''
368 dates.'''
360 return cvsps.debugcvsps(ui, *args, **opts)
369 return cvsps.debugcvsps(ui, *args, **opts)
361
370
362 def kwconverted(ctx, name):
371 def kwconverted(ctx, name):
363 rev = ctx.extra().get('convert_revision', '')
372 rev = ctx.extra().get('convert_revision', '')
364 if rev.startswith('svn:'):
373 if rev.startswith('svn:'):
365 if name == 'svnrev':
374 if name == 'svnrev':
366 return str(subversion.revsplit(rev)[2])
375 return str(subversion.revsplit(rev)[2])
367 elif name == 'svnpath':
376 elif name == 'svnpath':
368 return subversion.revsplit(rev)[1]
377 return subversion.revsplit(rev)[1]
369 elif name == 'svnuuid':
378 elif name == 'svnuuid':
370 return subversion.revsplit(rev)[0]
379 return subversion.revsplit(rev)[0]
371 return rev
380 return rev
372
381
373 def kwsvnrev(repo, ctx, **args):
382 def kwsvnrev(repo, ctx, **args):
374 """:svnrev: String. Converted subversion revision number."""
383 """:svnrev: String. Converted subversion revision number."""
375 return kwconverted(ctx, 'svnrev')
384 return kwconverted(ctx, 'svnrev')
376
385
377 def kwsvnpath(repo, ctx, **args):
386 def kwsvnpath(repo, ctx, **args):
378 """:svnpath: String. Converted subversion revision project path."""
387 """:svnpath: String. Converted subversion revision project path."""
379 return kwconverted(ctx, 'svnpath')
388 return kwconverted(ctx, 'svnpath')
380
389
381 def kwsvnuuid(repo, ctx, **args):
390 def kwsvnuuid(repo, ctx, **args):
382 """:svnuuid: String. Converted subversion revision repository identifier."""
391 """:svnuuid: String. Converted subversion revision repository identifier."""
383 return kwconverted(ctx, 'svnuuid')
392 return kwconverted(ctx, 'svnuuid')
384
393
385 def extsetup(ui):
394 def extsetup(ui):
386 templatekw.keywords['svnrev'] = kwsvnrev
395 templatekw.keywords['svnrev'] = kwsvnrev
387 templatekw.keywords['svnpath'] = kwsvnpath
396 templatekw.keywords['svnpath'] = kwsvnpath
388 templatekw.keywords['svnuuid'] = kwsvnuuid
397 templatekw.keywords['svnuuid'] = kwsvnuuid
389
398
390 # tell hggettext to extract docstrings from these functions:
399 # tell hggettext to extract docstrings from these functions:
391 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
400 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,355 +1,383 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
10 from mercurial import util, config
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, rev=None):
89 def __init__(self, ui, path, rev=None):
90 super(convert_git, self).__init__(ui, path, rev=rev)
90 super(convert_git, self).__init__(ui, path, rev=rev)
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 try:
98 similarity = int(ui.config('convert', 'git.similarity') or 0)
99 except ValueError:
100 raise util.Abort('convert.git.similarity must be a number')
101 if similarity < 0 or similarity > 100:
102 raise util.Abort(_('similarity must be between 0 and 100'))
103 if similarity > 0:
104 self.simopt = '--find-copies=%d%%' % similarity
105 else:
106 self.simopt = ''
107
97 checktool('git', 'git')
108 checktool('git', 'git')
98
109
99 self.path = path
110 self.path = path
100 self.submodules = []
111 self.submodules = []
101
112
102 self.catfilepipe = self.gitpipe('git cat-file --batch')
113 self.catfilepipe = self.gitpipe('git cat-file --batch')
103
114
104 def after(self):
115 def after(self):
105 for f in self.catfilepipe:
116 for f in self.catfilepipe:
106 f.close()
117 f.close()
107
118
108 def getheads(self):
119 def getheads(self):
109 if not self.rev:
120 if not self.rev:
110 heads, ret = self.gitread('git rev-parse --branches --remotes')
121 heads, ret = self.gitread('git rev-parse --branches --remotes')
111 heads = heads.splitlines()
122 heads = heads.splitlines()
112 else:
123 else:
113 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
124 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
114 heads = [heads[:-1]]
125 heads = [heads[:-1]]
115 if ret:
126 if ret:
116 raise util.Abort(_('cannot retrieve git heads'))
127 raise util.Abort(_('cannot retrieve git heads'))
117 return heads
128 return heads
118
129
119 def catfile(self, rev, type):
130 def catfile(self, rev, type):
120 if rev == hex(nullid):
131 if rev == hex(nullid):
121 raise IOError
132 raise IOError
122 self.catfilepipe[0].write(rev+'\n')
133 self.catfilepipe[0].write(rev+'\n')
123 self.catfilepipe[0].flush()
134 self.catfilepipe[0].flush()
124 info = self.catfilepipe[1].readline().split()
135 info = self.catfilepipe[1].readline().split()
125 if info[1] != type:
136 if info[1] != type:
126 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
137 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
127 size = int(info[2])
138 size = int(info[2])
128 data = self.catfilepipe[1].read(size)
139 data = self.catfilepipe[1].read(size)
129 if len(data) < size:
140 if len(data) < size:
130 raise util.Abort(_('cannot read %r object at %s: unexpected size')
141 raise util.Abort(_('cannot read %r object at %s: unexpected size')
131 % (type, rev))
142 % (type, rev))
132 # read the trailing newline
143 # read the trailing newline
133 self.catfilepipe[1].read(1)
144 self.catfilepipe[1].read(1)
134 return data
145 return data
135
146
136 def getfile(self, name, rev):
147 def getfile(self, name, rev):
137 if rev == hex(nullid):
148 if rev == hex(nullid):
138 return None, None
149 return None, None
139 if name == '.hgsub':
150 if name == '.hgsub':
140 data = '\n'.join([m.hgsub() for m in self.submoditer()])
151 data = '\n'.join([m.hgsub() for m in self.submoditer()])
141 mode = ''
152 mode = ''
142 elif name == '.hgsubstate':
153 elif name == '.hgsubstate':
143 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
154 data = '\n'.join([m.hgsubstate() for m in self.submoditer()])
144 mode = ''
155 mode = ''
145 else:
156 else:
146 data = self.catfile(rev, "blob")
157 data = self.catfile(rev, "blob")
147 mode = self.modecache[(name, rev)]
158 mode = self.modecache[(name, rev)]
148 return data, mode
159 return data, mode
149
160
150 def submoditer(self):
161 def submoditer(self):
151 null = hex(nullid)
162 null = hex(nullid)
152 for m in sorted(self.submodules, key=lambda p: p.path):
163 for m in sorted(self.submodules, key=lambda p: p.path):
153 if m.node != null:
164 if m.node != null:
154 yield m
165 yield m
155
166
156 def parsegitmodules(self, content):
167 def parsegitmodules(self, content):
157 """Parse the formatted .gitmodules file, example file format:
168 """Parse the formatted .gitmodules file, example file format:
158 [submodule "sub"]\n
169 [submodule "sub"]\n
159 \tpath = sub\n
170 \tpath = sub\n
160 \turl = git://giturl\n
171 \turl = git://giturl\n
161 """
172 """
162 self.submodules = []
173 self.submodules = []
163 c = config.config()
174 c = config.config()
164 # Each item in .gitmodules starts with \t that cant be parsed
175 # Each item in .gitmodules starts with \t that cant be parsed
165 c.parse('.gitmodules', content.replace('\t',''))
176 c.parse('.gitmodules', content.replace('\t',''))
166 for sec in c.sections():
177 for sec in c.sections():
167 s = c[sec]
178 s = c[sec]
168 if 'url' in s and 'path' in s:
179 if 'url' in s and 'path' in s:
169 self.submodules.append(submodule(s['path'], '', s['url']))
180 self.submodules.append(submodule(s['path'], '', s['url']))
170
181
171 def retrievegitmodules(self, version):
182 def retrievegitmodules(self, version):
172 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
183 modules, ret = self.gitread("git show %s:%s" % (version, '.gitmodules'))
173 if ret:
184 if ret:
174 raise util.Abort(_('cannot read submodules config file in %s') %
185 raise util.Abort(_('cannot read submodules config file in %s') %
175 version)
186 version)
176 self.parsegitmodules(modules)
187 self.parsegitmodules(modules)
177 for m in self.submodules:
188 for m in self.submodules:
178 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
189 node, ret = self.gitread("git rev-parse %s:%s" % (version, m.path))
179 if ret:
190 if ret:
180 continue
191 continue
181 m.node = node.strip()
192 m.node = node.strip()
182
193
183 def getchanges(self, version, full):
194 def getchanges(self, version, full):
184 if full:
195 if full:
185 raise util.Abort(_("convert from git do not support --full"))
196 raise util.Abort(_("convert from git do not support --full"))
186 self.modecache = {}
197 self.modecache = {}
187 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
198 fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
199 self.simopt, version))
188 changes = []
200 changes = []
201 copies = {}
189 seen = set()
202 seen = set()
190 entry = None
203 entry = None
191 subexists = [False]
204 subexists = [False]
192 subdeleted = [False]
205 subdeleted = [False]
193 difftree = fh.read().split('\x00')
206 difftree = fh.read().split('\x00')
194 lcount = len(difftree)
207 lcount = len(difftree)
195 i = 0
208 i = 0
196
209
197 def add(entry, f):
210 def add(entry, f, isdest):
198 seen.add(f)
211 seen.add(f)
199 h = entry[3]
212 h = entry[3]
200 p = (entry[1] == "100755")
213 p = (entry[1] == "100755")
201 s = (entry[1] == "120000")
214 s = (entry[1] == "120000")
215 renamesource = (not isdest and entry[4][0] == 'R')
202
216
203 if f == '.gitmodules':
217 if f == '.gitmodules':
204 subexists[0] = True
218 subexists[0] = True
205 if entry[4] == 'D':
219 if entry[4] == 'D' or renamesource:
206 subdeleted[0] = True
220 subdeleted[0] = True
207 changes.append(('.hgsub', hex(nullid)))
221 changes.append(('.hgsub', hex(nullid)))
208 else:
222 else:
209 changes.append(('.hgsub', ''))
223 changes.append(('.hgsub', ''))
210 elif entry[1] == '160000' or entry[0] == ':160000':
224 elif entry[1] == '160000' or entry[0] == ':160000':
211 subexists[0] = True
225 subexists[0] = True
212 else:
226 else:
227 if renamesource:
228 h = hex(nullid)
213 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
229 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
214 changes.append((f, h))
230 changes.append((f, h))
215
231
216 while i < lcount:
232 while i < lcount:
217 l = difftree[i]
233 l = difftree[i]
218 i += 1
234 i += 1
219 if not entry:
235 if not entry:
220 if not l.startswith(':'):
236 if not l.startswith(':'):
221 continue
237 continue
222 entry = l.split()
238 entry = l.split()
223 continue
239 continue
224 f = l
240 f = l
225 if f not in seen:
241 if f not in seen:
226 add(entry, f)
242 add(entry, f, False)
243 # A file can be copied multiple times, or modified and copied
244 # simultaneously. So f can be repeated even if fdest isn't.
245 if entry[4][0] in 'RC':
246 # rename or copy: next line is the destination
247 fdest = difftree[i]
248 i += 1
249 if fdest not in seen:
250 add(entry, fdest, True)
251 # .gitmodules isn't imported at all, so it being copied to
252 # and fro doesn't really make sense
253 if f != '.gitmodules' and fdest != '.gitmodules':
254 copies[fdest] = f
227 entry = None
255 entry = None
228 if fh.close():
256 if fh.close():
229 raise util.Abort(_('cannot read changes in %s') % version)
257 raise util.Abort(_('cannot read changes in %s') % version)
230
258
231 if subexists[0]:
259 if subexists[0]:
232 if subdeleted[0]:
260 if subdeleted[0]:
233 changes.append(('.hgsubstate', hex(nullid)))
261 changes.append(('.hgsubstate', hex(nullid)))
234 else:
262 else:
235 self.retrievegitmodules(version)
263 self.retrievegitmodules(version)
236 changes.append(('.hgsubstate', ''))
264 changes.append(('.hgsubstate', ''))
237 return (changes, {})
265 return (changes, copies)
238
266
239 def getcommit(self, version):
267 def getcommit(self, version):
240 c = self.catfile(version, "commit") # read the commit hash
268 c = self.catfile(version, "commit") # read the commit hash
241 end = c.find("\n\n")
269 end = c.find("\n\n")
242 message = c[end + 2:]
270 message = c[end + 2:]
243 message = self.recode(message)
271 message = self.recode(message)
244 l = c[:end].splitlines()
272 l = c[:end].splitlines()
245 parents = []
273 parents = []
246 author = committer = None
274 author = committer = None
247 for e in l[1:]:
275 for e in l[1:]:
248 n, v = e.split(" ", 1)
276 n, v = e.split(" ", 1)
249 if n == "author":
277 if n == "author":
250 p = v.split()
278 p = v.split()
251 tm, tz = p[-2:]
279 tm, tz = p[-2:]
252 author = " ".join(p[:-2])
280 author = " ".join(p[:-2])
253 if author[0] == "<": author = author[1:-1]
281 if author[0] == "<": author = author[1:-1]
254 author = self.recode(author)
282 author = self.recode(author)
255 if n == "committer":
283 if n == "committer":
256 p = v.split()
284 p = v.split()
257 tm, tz = p[-2:]
285 tm, tz = p[-2:]
258 committer = " ".join(p[:-2])
286 committer = " ".join(p[:-2])
259 if committer[0] == "<": committer = committer[1:-1]
287 if committer[0] == "<": committer = committer[1:-1]
260 committer = self.recode(committer)
288 committer = self.recode(committer)
261 if n == "parent":
289 if n == "parent":
262 parents.append(v)
290 parents.append(v)
263
291
264 if committer and committer != author:
292 if committer and committer != author:
265 message += "\ncommitter: %s\n" % committer
293 message += "\ncommitter: %s\n" % committer
266 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
294 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
267 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
295 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
268 date = tm + " " + str(tz)
296 date = tm + " " + str(tz)
269
297
270 c = commit(parents=parents, date=date, author=author, desc=message,
298 c = commit(parents=parents, date=date, author=author, desc=message,
271 rev=version)
299 rev=version)
272 return c
300 return c
273
301
274 def numcommits(self):
302 def numcommits(self):
275 return len([None for _ in self.gitopen('git rev-list --all')])
303 return len([None for _ in self.gitopen('git rev-list --all')])
276
304
277 def gettags(self):
305 def gettags(self):
278 tags = {}
306 tags = {}
279 alltags = {}
307 alltags = {}
280 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
308 fh = self.gitopen('git ls-remote --tags "%s"' % self.path,
281 err=subprocess.STDOUT)
309 err=subprocess.STDOUT)
282 prefix = 'refs/tags/'
310 prefix = 'refs/tags/'
283
311
284 # Build complete list of tags, both annotated and bare ones
312 # Build complete list of tags, both annotated and bare ones
285 for line in fh:
313 for line in fh:
286 line = line.strip()
314 line = line.strip()
287 if line.startswith("error:") or line.startswith("fatal:"):
315 if line.startswith("error:") or line.startswith("fatal:"):
288 raise util.Abort(_('cannot read tags from %s') % self.path)
316 raise util.Abort(_('cannot read tags from %s') % self.path)
289 node, tag = line.split(None, 1)
317 node, tag = line.split(None, 1)
290 if not tag.startswith(prefix):
318 if not tag.startswith(prefix):
291 continue
319 continue
292 alltags[tag[len(prefix):]] = node
320 alltags[tag[len(prefix):]] = node
293 if fh.close():
321 if fh.close():
294 raise util.Abort(_('cannot read tags from %s') % self.path)
322 raise util.Abort(_('cannot read tags from %s') % self.path)
295
323
296 # Filter out tag objects for annotated tag refs
324 # Filter out tag objects for annotated tag refs
297 for tag in alltags:
325 for tag in alltags:
298 if tag.endswith('^{}'):
326 if tag.endswith('^{}'):
299 tags[tag[:-3]] = alltags[tag]
327 tags[tag[:-3]] = alltags[tag]
300 else:
328 else:
301 if tag + '^{}' in alltags:
329 if tag + '^{}' in alltags:
302 continue
330 continue
303 else:
331 else:
304 tags[tag] = alltags[tag]
332 tags[tag] = alltags[tag]
305
333
306 return tags
334 return tags
307
335
308 def getchangedfiles(self, version, i):
336 def getchangedfiles(self, version, i):
309 changes = []
337 changes = []
310 if i is None:
338 if i is None:
311 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
339 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
312 for l in fh:
340 for l in fh:
313 if "\t" not in l:
341 if "\t" not in l:
314 continue
342 continue
315 m, f = l[:-1].split("\t")
343 m, f = l[:-1].split("\t")
316 changes.append(f)
344 changes.append(f)
317 else:
345 else:
318 fh = self.gitopen('git diff-tree --name-only --root -r %s '
346 fh = self.gitopen('git diff-tree --name-only --root -r %s '
319 '"%s^%s" --' % (version, version, i + 1))
347 '"%s^%s" --' % (version, version, i + 1))
320 changes = [f.rstrip('\n') for f in fh]
348 changes = [f.rstrip('\n') for f in fh]
321 if fh.close():
349 if fh.close():
322 raise util.Abort(_('cannot read changes in %s') % version)
350 raise util.Abort(_('cannot read changes in %s') % version)
323
351
324 return changes
352 return changes
325
353
326 def getbookmarks(self):
354 def getbookmarks(self):
327 bookmarks = {}
355 bookmarks = {}
328
356
329 # Interesting references in git are prefixed
357 # Interesting references in git are prefixed
330 prefix = 'refs/heads/'
358 prefix = 'refs/heads/'
331 prefixlen = len(prefix)
359 prefixlen = len(prefix)
332
360
333 # factor two commands
361 # factor two commands
334 gitcmd = { 'remote/': 'git ls-remote --heads origin',
362 gitcmd = { 'remote/': 'git ls-remote --heads origin',
335 '': 'git show-ref'}
363 '': 'git show-ref'}
336
364
337 # Origin heads
365 # Origin heads
338 for reftype in gitcmd:
366 for reftype in gitcmd:
339 try:
367 try:
340 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
368 fh = self.gitopen(gitcmd[reftype], err=subprocess.PIPE)
341 for line in fh:
369 for line in fh:
342 line = line.strip()
370 line = line.strip()
343 rev, name = line.split(None, 1)
371 rev, name = line.split(None, 1)
344 if not name.startswith(prefix):
372 if not name.startswith(prefix):
345 continue
373 continue
346 name = '%s%s' % (reftype, name[prefixlen:])
374 name = '%s%s' % (reftype, name[prefixlen:])
347 bookmarks[name] = rev
375 bookmarks[name] = rev
348 except Exception:
376 except Exception:
349 pass
377 pass
350
378
351 return bookmarks
379 return bookmarks
352
380
353 def checkrevformat(self, revstr, mapname='splicemap'):
381 def checkrevformat(self, revstr, mapname='splicemap'):
354 """ git revision string is a 40 byte hex """
382 """ git revision string is a 40 byte hex """
355 self.checkhexformat(revstr, mapname)
383 self.checkhexformat(revstr, mapname)
@@ -1,443 +1,503 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 -q convert --datesort git-repo2 fullrepo
173 $ hg -q convert --datesort git-repo2 fullrepo
174 $ hg up -q -R fullrepo
174 $ hg up -q -R fullrepo
175 $ glog -R fullrepo
175 $ glog -R fullrepo
176 @ 9 "Discard change to foo" files: foo
176 @ 9 "Discard change to foo" files: foo
177 |\
177 |\
178 | o 8 "change foo" files: foo
178 | o 8 "change foo" files: foo
179 | |
179 | |
180 o | 7 "change bar" files: bar
180 o | 7 "change bar" files: bar
181 |/
181 |/
182 o 6 "(octopus merge fixup)" files:
182 o 6 "(octopus merge fixup)" files:
183 |\
183 |\
184 | o 5 "Octopus merge" files: baz
184 | o 5 "Octopus merge" files: baz
185 | |\
185 | |\
186 o | | 4 "add baz" files: baz
186 o | | 4 "add baz" files: baz
187 | | |
187 | | |
188 +---o 3 "add bar" files: bar
188 +---o 3 "add bar" files: bar
189 | |
189 | |
190 o | 2 "add quux" files: quux
190 o | 2 "add quux" files: quux
191 | |
191 | |
192 | o 1 "change foo" files: foo
192 | o 1 "change foo" files: foo
193 |/
193 |/
194 o 0 "add foo" files: foo
194 o 0 "add foo" files: foo
195
195
196 $ hg -R fullrepo manifest --debug
196 $ hg -R fullrepo manifest --debug
197 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
197 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
198 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
198 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
199 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
199 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
200 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
200 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
201 $ splitrepo 'octopus merge' 'foo bar baz'
201 $ splitrepo 'octopus merge' 'foo bar baz'
202 % foo bar baz: octopus merge
202 % foo bar baz: octopus merge
203 @ 8 "Discard change to foo" files: foo
203 @ 8 "Discard change to foo" files: foo
204 |\
204 |\
205 | o 7 "change foo" files: foo
205 | o 7 "change foo" files: foo
206 | |
206 | |
207 o | 6 "change bar" files: bar
207 o | 6 "change bar" files: bar
208 |/
208 |/
209 o 5 "(octopus merge fixup)" files:
209 o 5 "(octopus merge fixup)" files:
210 |\
210 |\
211 | o 4 "Octopus merge" files: baz
211 | o 4 "Octopus merge" files: baz
212 | |\
212 | |\
213 o | | 3 "add baz" files: baz
213 o | | 3 "add baz" files: baz
214 | | |
214 | | |
215 +---o 2 "add bar" files: bar
215 +---o 2 "add bar" files: bar
216 | |
216 | |
217 | o 1 "change foo" files: foo
217 | o 1 "change foo" files: foo
218 |/
218 |/
219 o 0 "add foo" files: foo
219 o 0 "add foo" files: foo
220
220
221 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
221 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
222 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
222 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
223 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
223 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
224 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
224 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
225 % foo baz quux: only some parents of an octopus merge; "discard" a head
225 % foo baz quux: only some parents of an octopus merge; "discard" a head
226 @ 6 "Discard change to foo" files: foo
226 @ 6 "Discard change to foo" files: foo
227 |
227 |
228 o 5 "change foo" files: foo
228 o 5 "change foo" files: foo
229 |
229 |
230 o 4 "Octopus merge" files:
230 o 4 "Octopus merge" files:
231 |\
231 |\
232 | o 3 "add baz" files: baz
232 | o 3 "add baz" files: baz
233 | |
233 | |
234 | o 2 "add quux" files: quux
234 | o 2 "add quux" files: quux
235 | |
235 | |
236 o | 1 "change foo" files: foo
236 o | 1 "change foo" files: foo
237 |/
237 |/
238 o 0 "add foo" files: foo
238 o 0 "add foo" files: foo
239
239
240 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
240 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
241 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
241 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
242 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
242 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
243
243
244 test importing git renames and copies
245
246 $ cd git-repo2
247 $ git mv foo foo-renamed
248 since bar is not touched in this commit, this copy will not be detected
249 $ cp bar bar-copied
250 $ cp baz baz-copied
251 $ cp baz baz-copied2
252 $ echo baz2 >> baz
253 $ git add bar-copied baz-copied baz-copied2
254 $ commit -a -m 'rename and copy'
255 $ cd ..
256
257 input validation
258 $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
259 abort: convert.git.similarity must be a number
260 [255]
261 $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
262 abort: similarity must be between 0 and 100
263 [255]
264 $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
265 abort: similarity must be between 0 and 100
266 [255]
267
268 $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
269 $ hg -R fullrepo status -C --change master
270 M baz
271 A bar-copied
272 A baz-copied
273 baz
274 A baz-copied2
275 baz
276 A foo-renamed
277 foo
278 R foo
279
244 test binary conversion (issue1359)
280 test binary conversion (issue1359)
245
281
282 $ count=19
246 $ mkdir git-repo3
283 $ mkdir git-repo3
247 $ cd git-repo3
284 $ cd git-repo3
248 $ git init-db >/dev/null 2>/dev/null
285 $ git init-db >/dev/null 2>/dev/null
249 $ python -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
286 $ python -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
250 $ git add b
287 $ git add b
251 $ commit -a -m addbinary
288 $ commit -a -m addbinary
252 $ cd ..
289 $ cd ..
253
290
254 convert binary file
291 convert binary file
255
292
256 $ hg convert git-repo3 git-repo3-hg
293 $ hg convert git-repo3 git-repo3-hg
257 initializing destination git-repo3-hg repository
294 initializing destination git-repo3-hg repository
258 scanning source...
295 scanning source...
259 sorting...
296 sorting...
260 converting...
297 converting...
261 0 addbinary
298 0 addbinary
262 updating bookmarks
299 updating bookmarks
263 $ cd git-repo3-hg
300 $ cd git-repo3-hg
264 $ hg up -C
301 $ hg up -C
265 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 $ python -c 'print len(file("b", "rb").read())'
303 $ python -c 'print len(file("b", "rb").read())'
267 4096
304 4096
268 $ cd ..
305 $ cd ..
269
306
270 test author vs committer
307 test author vs committer
271
308
272 $ mkdir git-repo4
309 $ mkdir git-repo4
273 $ cd git-repo4
310 $ cd git-repo4
274 $ git init-db >/dev/null 2>/dev/null
311 $ git init-db >/dev/null 2>/dev/null
275 $ echo >> foo
312 $ echo >> foo
276 $ git add foo
313 $ git add foo
277 $ commit -a -m addfoo
314 $ commit -a -m addfoo
278 $ echo >> foo
315 $ echo >> foo
279 $ GIT_AUTHOR_NAME="nottest"
316 $ GIT_AUTHOR_NAME="nottest"
280 $ commit -a -m addfoo2
317 $ commit -a -m addfoo2
281 $ cd ..
318 $ cd ..
282
319
283 convert author committer
320 convert author committer
284
321
285 $ hg convert git-repo4 git-repo4-hg
322 $ hg convert git-repo4 git-repo4-hg
286 initializing destination git-repo4-hg repository
323 initializing destination git-repo4-hg repository
287 scanning source...
324 scanning source...
288 sorting...
325 sorting...
289 converting...
326 converting...
290 1 addfoo
327 1 addfoo
291 0 addfoo2
328 0 addfoo2
292 updating bookmarks
329 updating bookmarks
293 $ hg -R git-repo4-hg log -v
330 $ hg -R git-repo4-hg log -v
294 changeset: 1:d63e967f93da
331 changeset: 1:d63e967f93da
295 bookmark: master
332 bookmark: master
296 tag: tip
333 tag: tip
297 user: nottest <test@example.org>
334 user: nottest <test@example.org>
298 date: Mon Jan 01 00:00:21 2007 +0000
335 date: Mon Jan 01 00:00:21 2007 +0000
299 files: foo
336 files: foo
300 description:
337 description:
301 addfoo2
338 addfoo2
302
339
303 committer: test <test@example.org>
340 committer: test <test@example.org>
304
341
305
342
306 changeset: 0:0735477b0224
343 changeset: 0:0735477b0224
307 user: test <test@example.org>
344 user: test <test@example.org>
308 date: Mon Jan 01 00:00:20 2007 +0000
345 date: Mon Jan 01 00:00:20 2007 +0000
309 files: foo
346 files: foo
310 description:
347 description:
311 addfoo
348 addfoo
312
349
313
350
314
351
315 --sourceorder should fail
352 --sourceorder should fail
316
353
317 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
354 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
318 initializing destination git-repo4-sourcesort-hg repository
355 initializing destination git-repo4-sourcesort-hg repository
319 abort: --sourcesort is not supported by this data source
356 abort: --sourcesort is not supported by this data source
320 [255]
357 [255]
321
358
322 test sub modules
359 test sub modules
323
360
324 $ mkdir git-repo5
361 $ mkdir git-repo5
325 $ cd git-repo5
362 $ cd git-repo5
326 $ git init-db >/dev/null 2>/dev/null
363 $ git init-db >/dev/null 2>/dev/null
327 $ echo 'sub' >> foo
364 $ echo 'sub' >> foo
328 $ git add foo
365 $ git add foo
329 $ commit -a -m 'addfoo'
366 $ commit -a -m 'addfoo'
330 $ BASE=`pwd`
367 $ BASE=`pwd`
331 $ cd ..
368 $ cd ..
332 $ mkdir git-repo6
369 $ mkdir git-repo6
333 $ cd git-repo6
370 $ cd git-repo6
334 $ git init-db >/dev/null 2>/dev/null
371 $ git init-db >/dev/null 2>/dev/null
335 $ git submodule add ${BASE} >/dev/null 2>/dev/null
372 $ git submodule add ${BASE} >/dev/null 2>/dev/null
336 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
373 $ commit -a -m 'addsubmodule' >/dev/null 2>/dev/null
337 $ cd ..
374 $ cd ..
338
375
339 test invalid splicemap1
376 test invalid splicemap1
340
377
341 $ cat > splicemap <<EOF
378 $ cat > splicemap <<EOF
342 > $VALIDID1
379 > $VALIDID1
343 > EOF
380 > EOF
344 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
381 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap1-hg
345 initializing destination git-repo2-splicemap1-hg repository
382 initializing destination git-repo2-splicemap1-hg repository
346 abort: syntax error in splicemap(1): child parent1[,parent2] expected
383 abort: syntax error in splicemap(1): child parent1[,parent2] expected
347 [255]
384 [255]
348
385
349 test invalid splicemap2
386 test invalid splicemap2
350
387
351 $ cat > splicemap <<EOF
388 $ cat > splicemap <<EOF
352 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
389 > $VALIDID1 $VALIDID2, $VALIDID2, $VALIDID2
353 > EOF
390 > EOF
354 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
391 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap2-hg
355 initializing destination git-repo2-splicemap2-hg repository
392 initializing destination git-repo2-splicemap2-hg repository
356 abort: syntax error in splicemap(1): child parent1[,parent2] expected
393 abort: syntax error in splicemap(1): child parent1[,parent2] expected
357 [255]
394 [255]
358
395
359 test invalid splicemap3
396 test invalid splicemap3
360
397
361 $ cat > splicemap <<EOF
398 $ cat > splicemap <<EOF
362 > $INVALIDID1 $INVALIDID2
399 > $INVALIDID1 $INVALIDID2
363 > EOF
400 > EOF
364 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
401 $ hg convert --splicemap splicemap git-repo2 git-repo2-splicemap3-hg
365 initializing destination git-repo2-splicemap3-hg repository
402 initializing destination git-repo2-splicemap3-hg repository
366 abort: splicemap entry afd12345af is not a valid revision identifier
403 abort: splicemap entry afd12345af is not a valid revision identifier
367 [255]
404 [255]
368
405
369 convert sub modules
406 convert sub modules
370 $ hg convert git-repo6 git-repo6-hg
407 $ hg convert git-repo6 git-repo6-hg
371 initializing destination git-repo6-hg repository
408 initializing destination git-repo6-hg repository
372 scanning source...
409 scanning source...
373 sorting...
410 sorting...
374 converting...
411 converting...
375 0 addsubmodule
412 0 addsubmodule
376 updating bookmarks
413 updating bookmarks
377 $ hg -R git-repo6-hg log -v
414 $ hg -R git-repo6-hg log -v
378 changeset: 0:* (glob)
415 changeset: 0:* (glob)
379 bookmark: master
416 bookmark: master
380 tag: tip
417 tag: tip
381 user: nottest <test@example.org>
418 user: nottest <test@example.org>
382 date: Mon Jan 01 00:00:23 2007 +0000
419 date: Mon Jan 01 00:00:23 2007 +0000
383 files: .hgsub .hgsubstate
420 files: .hgsub .hgsubstate
384 description:
421 description:
385 addsubmodule
422 addsubmodule
386
423
387 committer: test <test@example.org>
424 committer: test <test@example.org>
388
425
389
426
390
427
391 $ cd git-repo6-hg
428 $ cd git-repo6-hg
392 $ hg up >/dev/null 2>/dev/null
429 $ hg up >/dev/null 2>/dev/null
393 $ cat .hgsubstate
430 $ cat .hgsubstate
394 * git-repo5 (glob)
431 * git-repo5 (glob)
395 $ cd git-repo5
432 $ cd git-repo5
396 $ cat foo
433 $ cat foo
397 sub
434 sub
398
435
399 $ cd ../..
436 $ cd ../..
400
437
438 make sure rename detection doesn't break removing and adding gitmodules
439
440 $ cd git-repo6
441 $ git mv .gitmodules .gitmodules-renamed
442 $ commit -a -m 'rename .gitmodules'
443 $ git mv .gitmodules-renamed .gitmodules
444 $ commit -a -m 'rename .gitmodules back'
445 $ cd ..
446
447 $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
448 $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
449 rename .gitmodules
450 $ hg -R git-repo6-hg status -C --change 'tip^'
451 A .gitmodules-renamed
452 R .hgsub
453 R .hgsubstate
454 $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
455 rename .gitmodules back
456 $ hg -R git-repo6-hg status -C --change tip
457 A .hgsub
458 A .hgsubstate
459 R .gitmodules-renamed
460
401 convert the revision removing '.gitmodules' itself (and related
461 convert the revision removing '.gitmodules' itself (and related
402 submodules)
462 submodules)
403
463
404 $ cd git-repo6
464 $ cd git-repo6
405 $ git rm .gitmodules
465 $ git rm .gitmodules
406 rm '.gitmodules'
466 rm '.gitmodules'
407 $ git rm --cached git-repo5
467 $ git rm --cached git-repo5
408 rm 'git-repo5'
468 rm 'git-repo5'
409 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
469 $ commit -a -m 'remove .gitmodules and submodule git-repo5'
410 $ cd ..
470 $ cd ..
411
471
412 $ hg convert -q git-repo6 git-repo6-hg
472 $ hg convert -q git-repo6 git-repo6-hg
413 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
473 $ hg -R git-repo6-hg tip -T "{desc|firstline}\n"
414 remove .gitmodules and submodule git-repo5
474 remove .gitmodules and submodule git-repo5
415 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
475 $ hg -R git-repo6-hg tip -T "{file_dels}\n"
416 .hgsub .hgsubstate
476 .hgsub .hgsubstate
417
477
418 damaged git repository tests:
478 damaged git repository tests:
419 In case the hard-coded hashes change, the following commands can be used to
479 In case the hard-coded hashes change, the following commands can be used to
420 list the hashes and their corresponding types in the repository:
480 list the hashes and their corresponding types in the repository:
421 cd git-repo4/.git/objects
481 cd git-repo4/.git/objects
422 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
482 find . -type f | cut -c 3- | sed 's_/__' | xargs -n 1 -t git cat-file -t
423 cd ../../..
483 cd ../../..
424
484
425 damage git repository by renaming a commit object
485 damage git repository by renaming a commit object
426 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
486 $ COMMIT_OBJ=1c/0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
427 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
487 $ mv git-repo4/.git/objects/$COMMIT_OBJ git-repo4/.git/objects/$COMMIT_OBJ.tmp
428 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
488 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
429 abort: cannot read tags from git-repo4/.git
489 abort: cannot read tags from git-repo4/.git
430 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
490 $ mv git-repo4/.git/objects/$COMMIT_OBJ.tmp git-repo4/.git/objects/$COMMIT_OBJ
431 damage git repository by renaming a blob object
491 damage git repository by renaming a blob object
432
492
433 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
493 $ BLOB_OBJ=8b/137891791fe96927ad78e64b0aad7bded08bdc
434 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
494 $ mv git-repo4/.git/objects/$BLOB_OBJ git-repo4/.git/objects/$BLOB_OBJ.tmp
435 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
495 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
436 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
496 abort: cannot read 'blob' object at 8b137891791fe96927ad78e64b0aad7bded08bdc
437 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
497 $ mv git-repo4/.git/objects/$BLOB_OBJ.tmp git-repo4/.git/objects/$BLOB_OBJ
438 damage git repository by renaming a tree object
498 damage git repository by renaming a tree object
439
499
440 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
500 $ TREE_OBJ=72/49f083d2a63a41cc737764a86981eb5f3e4635
441 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
501 $ mv git-repo4/.git/objects/$TREE_OBJ git-repo4/.git/objects/$TREE_OBJ.tmp
442 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
502 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | grep 'abort:'
443 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
503 abort: cannot read changes in 1c0ce3c5886f83a1d78a7b517cdff5cf9ca17bdd
@@ -1,474 +1,484 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":
248
249 convert.git.similarity
250 specify how similar files modified in a commit must be to be
251 imported as renames or copies, as a percentage between "0"
252 (disabled) and "100" (files must be identical). For example,
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
255 default is "0".
256
247 Perforce Source
257 Perforce Source
248 ###############
258 ###############
249
259
250 The Perforce (P4) importer can be given a p4 depot path or a client
260 The Perforce (P4) importer can be given a p4 depot path or a client
251 specification as source. It will convert all files in the source to a flat
261 specification as source. It will convert all files in the source to a flat
252 Mercurial repository, ignoring labels, branches and integrations. Note
262 Mercurial repository, ignoring labels, branches and integrations. Note
253 that when a depot path is given you then usually should specify a target
263 that when a depot path is given you then usually should specify a target
254 directory, because otherwise the target may be named "...-hg".
264 directory, because otherwise the target may be named "...-hg".
255
265
256 It is possible to limit the amount of source history to be converted by
266 It is possible to limit the amount of source history to be converted by
257 specifying an initial Perforce revision:
267 specifying an initial Perforce revision:
258
268
259 convert.p4.startrev
269 convert.p4.startrev
260 specify initial Perforce revision (a Perforce changelist
270 specify initial Perforce revision (a Perforce changelist
261 number).
271 number).
262
272
263 Mercurial Destination
273 Mercurial Destination
264 #####################
274 #####################
265
275
266 The following options are supported:
276 The following options are supported:
267
277
268 convert.hg.clonebranches
278 convert.hg.clonebranches
269 dispatch source branches in separate clones. The default is
279 dispatch source branches in separate clones. The default is
270 False.
280 False.
271 convert.hg.tagsbranch
281 convert.hg.tagsbranch
272 branch name for tag revisions, defaults to "default".
282 branch name for tag revisions, defaults to "default".
273 convert.hg.usebranchnames
283 convert.hg.usebranchnames
274 preserve branch names. The default is True.
284 preserve branch names. The default is True.
275
285
276 options:
286 options:
277
287
278 -s --source-type TYPE source repository type
288 -s --source-type TYPE source repository type
279 -d --dest-type TYPE destination repository type
289 -d --dest-type TYPE destination repository type
280 -r --rev REV import up to source revision REV
290 -r --rev REV import up to source revision REV
281 -A --authormap FILE remap usernames using this file
291 -A --authormap FILE remap usernames using this file
282 --filemap FILE remap file names using contents of file
292 --filemap FILE remap file names using contents of file
283 --full apply filemap changes by converting all files again
293 --full apply filemap changes by converting all files again
284 --splicemap FILE splice synthesized history into place
294 --splicemap FILE splice synthesized history into place
285 --branchmap FILE change branch names while converting
295 --branchmap FILE change branch names while converting
286 --branchsort try to sort changesets by branches
296 --branchsort try to sort changesets by branches
287 --datesort try to sort changesets by date
297 --datesort try to sort changesets by date
288 --sourcesort preserve source changesets order
298 --sourcesort preserve source changesets order
289 --closesort try to reorder closed revisions
299 --closesort try to reorder closed revisions
290
300
291 (some details hidden, use --verbose to show complete help)
301 (some details hidden, use --verbose to show complete help)
292 $ hg init a
302 $ hg init a
293 $ cd a
303 $ cd a
294 $ echo a > a
304 $ echo a > a
295 $ hg ci -d'0 0' -Ama
305 $ hg ci -d'0 0' -Ama
296 adding a
306 adding a
297 $ hg cp a b
307 $ hg cp a b
298 $ hg ci -d'1 0' -mb
308 $ hg ci -d'1 0' -mb
299 $ hg rm a
309 $ hg rm a
300 $ hg ci -d'2 0' -mc
310 $ hg ci -d'2 0' -mc
301 $ hg mv b a
311 $ hg mv b a
302 $ hg ci -d'3 0' -md
312 $ hg ci -d'3 0' -md
303 $ echo a >> a
313 $ echo a >> a
304 $ hg ci -d'4 0' -me
314 $ hg ci -d'4 0' -me
305 $ cd ..
315 $ cd ..
306 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
316 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
307 assuming destination a-hg
317 assuming destination a-hg
308 initializing destination a-hg repository
318 initializing destination a-hg repository
309 scanning source...
319 scanning source...
310 sorting...
320 sorting...
311 converting...
321 converting...
312 4 a
322 4 a
313 3 b
323 3 b
314 2 c
324 2 c
315 1 d
325 1 d
316 0 e
326 0 e
317 $ hg --cwd a-hg pull ../a
327 $ hg --cwd a-hg pull ../a
318 pulling from ../a
328 pulling from ../a
319 searching for changes
329 searching for changes
320 no changes found
330 no changes found
321
331
322 conversion to existing file should fail
332 conversion to existing file should fail
323
333
324 $ touch bogusfile
334 $ touch bogusfile
325 $ hg convert a bogusfile
335 $ hg convert a bogusfile
326 initializing destination bogusfile repository
336 initializing destination bogusfile repository
327 abort: cannot create new bundle repository
337 abort: cannot create new bundle repository
328 [255]
338 [255]
329
339
330 #if unix-permissions no-root
340 #if unix-permissions no-root
331
341
332 conversion to dir without permissions should fail
342 conversion to dir without permissions should fail
333
343
334 $ mkdir bogusdir
344 $ mkdir bogusdir
335 $ chmod 000 bogusdir
345 $ chmod 000 bogusdir
336
346
337 $ hg convert a bogusdir
347 $ hg convert a bogusdir
338 abort: Permission denied: 'bogusdir'
348 abort: Permission denied: 'bogusdir'
339 [255]
349 [255]
340
350
341 user permissions should succeed
351 user permissions should succeed
342
352
343 $ chmod 700 bogusdir
353 $ chmod 700 bogusdir
344 $ hg convert a bogusdir
354 $ hg convert a bogusdir
345 initializing destination bogusdir repository
355 initializing destination bogusdir repository
346 scanning source...
356 scanning source...
347 sorting...
357 sorting...
348 converting...
358 converting...
349 4 a
359 4 a
350 3 b
360 3 b
351 2 c
361 2 c
352 1 d
362 1 d
353 0 e
363 0 e
354
364
355 #endif
365 #endif
356
366
357 test pre and post conversion actions
367 test pre and post conversion actions
358
368
359 $ echo 'include b' > filemap
369 $ echo 'include b' > filemap
360 $ hg convert --debug --filemap filemap a partialb | \
370 $ hg convert --debug --filemap filemap a partialb | \
361 > grep 'run hg'
371 > grep 'run hg'
362 run hg source pre-conversion action
372 run hg source pre-conversion action
363 run hg sink pre-conversion action
373 run hg sink pre-conversion action
364 run hg sink post-conversion action
374 run hg sink post-conversion action
365 run hg source post-conversion action
375 run hg source post-conversion action
366
376
367 converting empty dir should fail "nicely
377 converting empty dir should fail "nicely
368
378
369 $ mkdir emptydir
379 $ mkdir emptydir
370
380
371 override $PATH to ensure p4 not visible; use $PYTHON in case we're
381 override $PATH to ensure p4 not visible; use $PYTHON in case we're
372 running from a devel copy, not a temp installation
382 running from a devel copy, not a temp installation
373
383
374 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
384 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
375 assuming destination emptydir-hg
385 assuming destination emptydir-hg
376 initializing destination emptydir-hg repository
386 initializing destination emptydir-hg repository
377 emptydir does not look like a CVS checkout
387 emptydir does not look like a CVS checkout
378 emptydir does not look like a Git repository
388 emptydir does not look like a Git repository
379 emptydir does not look like a Subversion repository
389 emptydir does not look like a Subversion repository
380 emptydir is not a local Mercurial repository
390 emptydir is not a local Mercurial repository
381 emptydir does not look like a darcs repository
391 emptydir does not look like a darcs repository
382 emptydir does not look like a monotone repository
392 emptydir does not look like a monotone repository
383 emptydir does not look like a GNU Arch repository
393 emptydir does not look like a GNU Arch repository
384 emptydir does not look like a Bazaar repository
394 emptydir does not look like a Bazaar repository
385 cannot find required "p4" tool
395 cannot find required "p4" tool
386 abort: emptydir: missing or unsupported repository
396 abort: emptydir: missing or unsupported repository
387 [255]
397 [255]
388
398
389 convert with imaginary source type
399 convert with imaginary source type
390
400
391 $ hg convert --source-type foo a a-foo
401 $ hg convert --source-type foo a a-foo
392 initializing destination a-foo repository
402 initializing destination a-foo repository
393 abort: foo: invalid source repository type
403 abort: foo: invalid source repository type
394 [255]
404 [255]
395
405
396 convert with imaginary sink type
406 convert with imaginary sink type
397
407
398 $ hg convert --dest-type foo a a-foo
408 $ hg convert --dest-type foo a a-foo
399 abort: foo: invalid destination repository type
409 abort: foo: invalid destination repository type
400 [255]
410 [255]
401
411
402 testing: convert must not produce duplicate entries in fncache
412 testing: convert must not produce duplicate entries in fncache
403
413
404 $ hg convert a b
414 $ hg convert a b
405 initializing destination b repository
415 initializing destination b repository
406 scanning source...
416 scanning source...
407 sorting...
417 sorting...
408 converting...
418 converting...
409 4 a
419 4 a
410 3 b
420 3 b
411 2 c
421 2 c
412 1 d
422 1 d
413 0 e
423 0 e
414
424
415 contents of fncache file:
425 contents of fncache file:
416
426
417 $ cat b/.hg/store/fncache | sort
427 $ cat b/.hg/store/fncache | sort
418 data/a.i
428 data/a.i
419 data/b.i
429 data/b.i
420
430
421 test bogus URL
431 test bogus URL
422
432
423 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
433 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
424 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
434 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
425 [255]
435 [255]
426
436
427 test revset converted() lookup
437 test revset converted() lookup
428
438
429 $ hg --config convert.hg.saverev=True convert a c
439 $ hg --config convert.hg.saverev=True convert a c
430 initializing destination c repository
440 initializing destination c repository
431 scanning source...
441 scanning source...
432 sorting...
442 sorting...
433 converting...
443 converting...
434 4 a
444 4 a
435 3 b
445 3 b
436 2 c
446 2 c
437 1 d
447 1 d
438 0 e
448 0 e
439 $ echo f > c/f
449 $ echo f > c/f
440 $ hg -R c ci -d'0 0' -Amf
450 $ hg -R c ci -d'0 0' -Amf
441 adding f
451 adding f
442 created new head
452 created new head
443 $ hg -R c log -r "converted(09d945a62ce6)"
453 $ hg -R c log -r "converted(09d945a62ce6)"
444 changeset: 1:98c3dd46a874
454 changeset: 1:98c3dd46a874
445 user: test
455 user: test
446 date: Thu Jan 01 00:00:01 1970 +0000
456 date: Thu Jan 01 00:00:01 1970 +0000
447 summary: b
457 summary: b
448
458
449 $ hg -R c log -r "converted()"
459 $ hg -R c log -r "converted()"
450 changeset: 0:31ed57b2037c
460 changeset: 0:31ed57b2037c
451 user: test
461 user: test
452 date: Thu Jan 01 00:00:00 1970 +0000
462 date: Thu Jan 01 00:00:00 1970 +0000
453 summary: a
463 summary: a
454
464
455 changeset: 1:98c3dd46a874
465 changeset: 1:98c3dd46a874
456 user: test
466 user: test
457 date: Thu Jan 01 00:00:01 1970 +0000
467 date: Thu Jan 01 00:00:01 1970 +0000
458 summary: b
468 summary: b
459
469
460 changeset: 2:3b9ca06ef716
470 changeset: 2:3b9ca06ef716
461 user: test
471 user: test
462 date: Thu Jan 01 00:00:02 1970 +0000
472 date: Thu Jan 01 00:00:02 1970 +0000
463 summary: c
473 summary: c
464
474
465 changeset: 3:4e0debd37cf2
475 changeset: 3:4e0debd37cf2
466 user: test
476 user: test
467 date: Thu Jan 01 00:00:03 1970 +0000
477 date: Thu Jan 01 00:00:03 1970 +0000
468 summary: d
478 summary: d
469
479
470 changeset: 4:9de3bc9349c5
480 changeset: 4:9de3bc9349c5
471 user: test
481 user: test
472 date: Thu Jan 01 00:00:04 1970 +0000
482 date: Thu Jan 01 00:00:04 1970 +0000
473 summary: e
483 summary: e
474
484
General Comments 0
You need to be logged in to leave comments. Login now