##// END OF EJS Templates
convert: add closesort algorithm to mercurial sources...
Constantine Linnick -
r18819:05acdf8e default
parent child Browse files
Show More
@@ -1,378 +1,383 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 commands, templatekw
13 from mercurial import commands, templatekw
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 testedwith = 'internal'
16 testedwith = 'internal'
17
17
18 # Commands definition was moved elsewhere to ease demandload job.
18 # Commands definition was moved elsewhere to ease demandload job.
19
19
20 def convert(ui, src, dest=None, revmapfile=None, **opts):
20 def convert(ui, src, dest=None, revmapfile=None, **opts):
21 """convert a foreign SCM repository to a Mercurial one.
21 """convert a foreign SCM repository to a Mercurial one.
22
22
23 Accepted source formats [identifiers]:
23 Accepted source formats [identifiers]:
24
24
25 - Mercurial [hg]
25 - Mercurial [hg]
26 - CVS [cvs]
26 - CVS [cvs]
27 - Darcs [darcs]
27 - Darcs [darcs]
28 - git [git]
28 - git [git]
29 - Subversion [svn]
29 - Subversion [svn]
30 - Monotone [mtn]
30 - Monotone [mtn]
31 - GNU Arch [gnuarch]
31 - GNU Arch [gnuarch]
32 - Bazaar [bzr]
32 - Bazaar [bzr]
33 - Perforce [p4]
33 - Perforce [p4]
34
34
35 Accepted destination formats [identifiers]:
35 Accepted destination formats [identifiers]:
36
36
37 - Mercurial [hg]
37 - Mercurial [hg]
38 - Subversion [svn] (history on branches is not preserved)
38 - Subversion [svn] (history on branches is not preserved)
39
39
40 If no revision is given, all revisions will be converted.
40 If no revision is given, all revisions will be converted.
41 Otherwise, convert will only import up to the named revision
41 Otherwise, convert will only import up to the named revision
42 (given in a format understood by the source).
42 (given in a format understood by the source).
43
43
44 If no destination directory name is specified, it defaults to the
44 If no destination directory name is specified, it defaults to the
45 basename of the source with ``-hg`` appended. If the destination
45 basename of the source with ``-hg`` appended. If the destination
46 repository doesn't exist, it will be created.
46 repository doesn't exist, it will be created.
47
47
48 By default, all sources except Mercurial will use --branchsort.
48 By default, all sources except Mercurial will use --branchsort.
49 Mercurial uses --sourcesort to preserve original revision numbers
49 Mercurial uses --sourcesort to preserve original revision numbers
50 order. Sort modes have the following effects:
50 order. Sort modes have the following effects:
51
51
52 --branchsort convert from parent to child revision when possible,
52 --branchsort convert from parent to child revision when possible,
53 which means branches are usually converted one after
53 which means branches are usually converted one after
54 the other. It generates more compact repositories.
54 the other. It generates more compact repositories.
55
55
56 --datesort sort revisions by date. Converted repositories have
56 --datesort sort revisions by date. Converted repositories have
57 good-looking changelogs but are often an order of
57 good-looking changelogs but are often an order of
58 magnitude larger than the same ones generated by
58 magnitude larger than the same ones generated by
59 --branchsort.
59 --branchsort.
60
60
61 --sourcesort try to preserve source revisions order, only
61 --sourcesort try to preserve source revisions order, only
62 supported by Mercurial sources.
62 supported by Mercurial sources.
63
63
64 --closesort try to move closed revisions as close as possible
65 to parent branches, only supported by Mercurial
66 sources.
67
64 If ``REVMAP`` isn't given, it will be put in a default location
68 If ``REVMAP`` isn't given, it will be put in a default location
65 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
69 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
66 text file that maps each source commit ID to the destination ID
70 text file that maps each source commit ID to the destination ID
67 for that revision, like so::
71 for that revision, like so::
68
72
69 <source ID> <destination ID>
73 <source ID> <destination ID>
70
74
71 If the file doesn't exist, it's automatically created. It's
75 If the file doesn't exist, it's automatically created. It's
72 updated on each commit copied, so :hg:`convert` can be interrupted
76 updated on each commit copied, so :hg:`convert` can be interrupted
73 and can be run repeatedly to copy new commits.
77 and can be run repeatedly to copy new commits.
74
78
75 The authormap is a simple text file that maps each source commit
79 The authormap is a simple text file that maps each source commit
76 author to a destination commit author. It is handy for source SCMs
80 author to a destination commit author. It is handy for source SCMs
77 that use unix logins to identify authors (e.g.: CVS). One line per
81 that use unix logins to identify authors (e.g.: CVS). One line per
78 author mapping and the line format is::
82 author mapping and the line format is::
79
83
80 source author = destination author
84 source author = destination author
81
85
82 Empty lines and lines starting with a ``#`` are ignored.
86 Empty lines and lines starting with a ``#`` are ignored.
83
87
84 The filemap is a file that allows filtering and remapping of files
88 The filemap is a file that allows filtering and remapping of files
85 and directories. Each line can contain one of the following
89 and directories. Each line can contain one of the following
86 directives::
90 directives::
87
91
88 include path/to/file-or-dir
92 include path/to/file-or-dir
89
93
90 exclude path/to/file-or-dir
94 exclude path/to/file-or-dir
91
95
92 rename path/to/source path/to/destination
96 rename path/to/source path/to/destination
93
97
94 Comment lines start with ``#``. A specified path matches if it
98 Comment lines start with ``#``. A specified path matches if it
95 equals the full relative name of a file or one of its parent
99 equals the full relative name of a file or one of its parent
96 directories. The ``include`` or ``exclude`` directive with the
100 directories. The ``include`` or ``exclude`` directive with the
97 longest matching path applies, so line order does not matter.
101 longest matching path applies, so line order does not matter.
98
102
99 The ``include`` directive causes a file, or all files under a
103 The ``include`` directive causes a file, or all files under a
100 directory, to be included in the destination repository, and the
104 directory, to be included in the destination repository, and the
101 exclusion of all other files and directories not explicitly
105 exclusion of all other files and directories not explicitly
102 included. The ``exclude`` directive causes files or directories to
106 included. The ``exclude`` directive causes files or directories to
103 be omitted. The ``rename`` directive renames a file or directory if
107 be omitted. The ``rename`` directive renames a file or directory if
104 it is converted. To rename from a subdirectory into the root of
108 it is converted. To rename from a subdirectory into the root of
105 the repository, use ``.`` as the path to rename to.
109 the repository, use ``.`` as the path to rename to.
106
110
107 The splicemap is a file that allows insertion of synthetic
111 The splicemap is a file that allows insertion of synthetic
108 history, letting you specify the parents of a revision. This is
112 history, letting you specify the parents of a revision. This is
109 useful if you want to e.g. give a Subversion merge two parents, or
113 useful if you want to e.g. give a Subversion merge two parents, or
110 graft two disconnected series of history together. Each entry
114 graft two disconnected series of history together. Each entry
111 contains a key, followed by a space, followed by one or two
115 contains a key, followed by a space, followed by one or two
112 comma-separated values::
116 comma-separated values::
113
117
114 key parent1, parent2
118 key parent1, parent2
115
119
116 The key is the revision ID in the source
120 The key is the revision ID in the source
117 revision control system whose parents should be modified (same
121 revision control system whose parents should be modified (same
118 format as a key in .hg/shamap). The values are the revision IDs
122 format as a key in .hg/shamap). The values are the revision IDs
119 (in either the source or destination revision control system) that
123 (in either the source or destination revision control system) that
120 should be used as the new parents for that node. For example, if
124 should be used as the new parents for that node. For example, if
121 you have merged "release-1.0" into "trunk", then you should
125 you have merged "release-1.0" into "trunk", then you should
122 specify the revision on "trunk" as the first parent and the one on
126 specify the revision on "trunk" as the first parent and the one on
123 the "release-1.0" branch as the second.
127 the "release-1.0" branch as the second.
124
128
125 The branchmap is a file that allows you to rename a branch when it is
129 The branchmap is a file that allows you to rename a branch when it is
126 being brought in from whatever external repository. When used in
130 being brought in from whatever external repository. When used in
127 conjunction with a splicemap, it allows for a powerful combination
131 conjunction with a splicemap, it allows for a powerful combination
128 to help fix even the most badly mismanaged repositories and turn them
132 to help fix even the most badly mismanaged repositories and turn them
129 into nicely structured Mercurial repositories. The branchmap contains
133 into nicely structured Mercurial repositories. The branchmap contains
130 lines of the form::
134 lines of the form::
131
135
132 original_branch_name new_branch_name
136 original_branch_name new_branch_name
133
137
134 where "original_branch_name" is the name of the branch in the
138 where "original_branch_name" is the name of the branch in the
135 source repository, and "new_branch_name" is the name of the branch
139 source repository, and "new_branch_name" is the name of the branch
136 is the destination repository. No whitespace is allowed in the
140 is the destination repository. No whitespace is allowed in the
137 branch names. This can be used to (for instance) move code in one
141 branch names. This can be used to (for instance) move code in one
138 repository from "default" to a named branch.
142 repository from "default" to a named branch.
139
143
140 Mercurial Source
144 Mercurial Source
141 ################
145 ################
142
146
143 The Mercurial source recognizes the following configuration
147 The Mercurial source recognizes the following configuration
144 options, which you can set on the command line with ``--config``:
148 options, which you can set on the command line with ``--config``:
145
149
146 :convert.hg.ignoreerrors: ignore integrity errors when reading.
150 :convert.hg.ignoreerrors: ignore integrity errors when reading.
147 Use it to fix Mercurial repositories with missing revlogs, by
151 Use it to fix Mercurial repositories with missing revlogs, by
148 converting from and to Mercurial. Default is False.
152 converting from and to Mercurial. Default is False.
149
153
150 :convert.hg.saverev: store original revision ID in changeset
154 :convert.hg.saverev: store original revision ID in changeset
151 (forces target IDs to change). It takes a boolean argument and
155 (forces target IDs to change). It takes a boolean argument and
152 defaults to False.
156 defaults to False.
153
157
154 :convert.hg.startrev: convert start revision and its descendants.
158 :convert.hg.startrev: convert start revision and its descendants.
155 It takes a hg revision identifier and defaults to 0.
159 It takes a hg revision identifier and defaults to 0.
156
160
157 CVS Source
161 CVS Source
158 ##########
162 ##########
159
163
160 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
164 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
161 to indicate the starting point of what will be converted. Direct
165 to indicate the starting point of what will be converted. Direct
162 access to the repository files is not needed, unless of course the
166 access to the repository files is not needed, unless of course the
163 repository is ``:local:``. The conversion uses the top level
167 repository is ``:local:``. The conversion uses the top level
164 directory in the sandbox to find the CVS repository, and then uses
168 directory in the sandbox to find the CVS repository, and then uses
165 CVS rlog commands to find files to convert. This means that unless
169 CVS rlog commands to find files to convert. This means that unless
166 a filemap is given, all files under the starting directory will be
170 a filemap is given, all files under the starting directory will be
167 converted, and that any directory reorganization in the CVS
171 converted, and that any directory reorganization in the CVS
168 sandbox is ignored.
172 sandbox is ignored.
169
173
170 The following options can be used with ``--config``:
174 The following options can be used with ``--config``:
171
175
172 :convert.cvsps.cache: Set to False to disable remote log caching,
176 :convert.cvsps.cache: Set to False to disable remote log caching,
173 for testing and debugging purposes. Default is True.
177 for testing and debugging purposes. Default is True.
174
178
175 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
179 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
176 allowed between commits with identical user and log message in
180 allowed between commits with identical user and log message in
177 a single changeset. When very large files were checked in as
181 a single changeset. When very large files were checked in as
178 part of a changeset then the default may not be long enough.
182 part of a changeset then the default may not be long enough.
179 The default is 60.
183 The default is 60.
180
184
181 :convert.cvsps.mergeto: Specify a regular expression to which
185 :convert.cvsps.mergeto: Specify a regular expression to which
182 commit log messages are matched. If a match occurs, then the
186 commit log messages are matched. If a match occurs, then the
183 conversion process will insert a dummy revision merging the
187 conversion process will insert a dummy revision merging the
184 branch on which this log message occurs to the branch
188 branch on which this log message occurs to the branch
185 indicated in the regex. Default is ``{{mergetobranch
189 indicated in the regex. Default is ``{{mergetobranch
186 ([-\\w]+)}}``
190 ([-\\w]+)}}``
187
191
188 :convert.cvsps.mergefrom: Specify a regular expression to which
192 :convert.cvsps.mergefrom: Specify a regular expression to which
189 commit log messages are matched. If a match occurs, then the
193 commit log messages are matched. If a match occurs, then the
190 conversion process will add the most recent revision on the
194 conversion process will add the most recent revision on the
191 branch indicated in the regex as the second parent of the
195 branch indicated in the regex as the second parent of the
192 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
196 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
193
197
194 :convert.localtimezone: use local time (as determined by the TZ
198 :convert.localtimezone: use local time (as determined by the TZ
195 environment variable) for changeset date/times. The default
199 environment variable) for changeset date/times. The default
196 is False (use UTC).
200 is False (use UTC).
197
201
198 :hooks.cvslog: Specify a Python function to be called at the end of
202 :hooks.cvslog: Specify a Python function to be called at the end of
199 gathering the CVS log. The function is passed a list with the
203 gathering the CVS log. The function is passed a list with the
200 log entries, and can modify the entries in-place, or add or
204 log entries, and can modify the entries in-place, or add or
201 delete them.
205 delete them.
202
206
203 :hooks.cvschangesets: Specify a Python function to be called after
207 :hooks.cvschangesets: Specify a Python function to be called after
204 the changesets are calculated from the CVS log. The
208 the changesets are calculated from the CVS log. The
205 function is passed a list with the changeset entries, and can
209 function is passed a list with the changeset entries, and can
206 modify the changesets in-place, or add or delete them.
210 modify the changesets in-place, or add or delete them.
207
211
208 An additional "debugcvsps" Mercurial command allows the builtin
212 An additional "debugcvsps" Mercurial command allows the builtin
209 changeset merging code to be run without doing a conversion. Its
213 changeset merging code to be run without doing a conversion. Its
210 parameters and output are similar to that of cvsps 2.1. Please see
214 parameters and output are similar to that of cvsps 2.1. Please see
211 the command help for more details.
215 the command help for more details.
212
216
213 Subversion Source
217 Subversion Source
214 #################
218 #################
215
219
216 Subversion source detects classical trunk/branches/tags layouts.
220 Subversion source detects classical trunk/branches/tags layouts.
217 By default, the supplied ``svn://repo/path/`` source URL is
221 By default, the supplied ``svn://repo/path/`` source URL is
218 converted as a single branch. If ``svn://repo/path/trunk`` exists
222 converted as a single branch. If ``svn://repo/path/trunk`` exists
219 it replaces the default branch. If ``svn://repo/path/branches``
223 it replaces the default branch. If ``svn://repo/path/branches``
220 exists, its subdirectories are listed as possible branches. If
224 exists, its subdirectories are listed as possible branches. If
221 ``svn://repo/path/tags`` exists, it is looked for tags referencing
225 ``svn://repo/path/tags`` exists, it is looked for tags referencing
222 converted branches. Default ``trunk``, ``branches`` and ``tags``
226 converted branches. Default ``trunk``, ``branches`` and ``tags``
223 values can be overridden with following options. Set them to paths
227 values can be overridden with following options. Set them to paths
224 relative to the source URL, or leave them blank to disable auto
228 relative to the source URL, or leave them blank to disable auto
225 detection.
229 detection.
226
230
227 The following options can be set with ``--config``:
231 The following options can be set with ``--config``:
228
232
229 :convert.svn.branches: specify the directory containing branches.
233 :convert.svn.branches: specify the directory containing branches.
230 The default is ``branches``.
234 The default is ``branches``.
231
235
232 :convert.svn.tags: specify the directory containing tags. The
236 :convert.svn.tags: specify the directory containing tags. The
233 default is ``tags``.
237 default is ``tags``.
234
238
235 :convert.svn.trunk: specify the name of the trunk branch. The
239 :convert.svn.trunk: specify the name of the trunk branch. The
236 default is ``trunk``.
240 default is ``trunk``.
237
241
238 :convert.localtimezone: use local time (as determined by the TZ
242 :convert.localtimezone: use local time (as determined by the TZ
239 environment variable) for changeset date/times. The default
243 environment variable) for changeset date/times. The default
240 is False (use UTC).
244 is False (use UTC).
241
245
242 Source history can be retrieved starting at a specific revision,
246 Source history can be retrieved starting at a specific revision,
243 instead of being integrally converted. Only single branch
247 instead of being integrally converted. Only single branch
244 conversions are supported.
248 conversions are supported.
245
249
246 :convert.svn.startrev: specify start Subversion revision number.
250 :convert.svn.startrev: specify start Subversion revision number.
247 The default is 0.
251 The default is 0.
248
252
249 Perforce Source
253 Perforce Source
250 ###############
254 ###############
251
255
252 The Perforce (P4) importer can be given a p4 depot path or a
256 The Perforce (P4) importer can be given a p4 depot path or a
253 client specification as source. It will convert all files in the
257 client specification as source. It will convert all files in the
254 source to a flat Mercurial repository, ignoring labels, branches
258 source to a flat Mercurial repository, ignoring labels, branches
255 and integrations. Note that when a depot path is given you then
259 and integrations. Note that when a depot path is given you then
256 usually should specify a target directory, because otherwise the
260 usually should specify a target directory, because otherwise the
257 target may be named ``...-hg``.
261 target may be named ``...-hg``.
258
262
259 It is possible to limit the amount of source history to be
263 It is possible to limit the amount of source history to be
260 converted by specifying an initial Perforce revision:
264 converted by specifying an initial Perforce revision:
261
265
262 :convert.p4.startrev: specify initial Perforce revision (a
266 :convert.p4.startrev: specify initial Perforce revision (a
263 Perforce changelist number).
267 Perforce changelist number).
264
268
265 Mercurial Destination
269 Mercurial Destination
266 #####################
270 #####################
267
271
268 The following options are supported:
272 The following options are supported:
269
273
270 :convert.hg.clonebranches: dispatch source branches in separate
274 :convert.hg.clonebranches: dispatch source branches in separate
271 clones. The default is False.
275 clones. The default is False.
272
276
273 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
277 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
274 ``default``.
278 ``default``.
275
279
276 :convert.hg.usebranchnames: preserve branch names. The default is
280 :convert.hg.usebranchnames: preserve branch names. The default is
277 True.
281 True.
278 """
282 """
279 return convcmd.convert(ui, src, dest, revmapfile, **opts)
283 return convcmd.convert(ui, src, dest, revmapfile, **opts)
280
284
281 def debugsvnlog(ui, **opts):
285 def debugsvnlog(ui, **opts):
282 return subversion.debugsvnlog(ui, **opts)
286 return subversion.debugsvnlog(ui, **opts)
283
287
284 def debugcvsps(ui, *args, **opts):
288 def debugcvsps(ui, *args, **opts):
285 '''create changeset information from CVS
289 '''create changeset information from CVS
286
290
287 This command is intended as a debugging tool for the CVS to
291 This command is intended as a debugging tool for the CVS to
288 Mercurial converter, and can be used as a direct replacement for
292 Mercurial converter, and can be used as a direct replacement for
289 cvsps.
293 cvsps.
290
294
291 Hg debugcvsps reads the CVS rlog for current directory (or any
295 Hg debugcvsps reads the CVS rlog for current directory (or any
292 named directory) in the CVS repository, and converts the log to a
296 named directory) in the CVS repository, and converts the log to a
293 series of changesets based on matching commit log entries and
297 series of changesets based on matching commit log entries and
294 dates.'''
298 dates.'''
295 return cvsps.debugcvsps(ui, *args, **opts)
299 return cvsps.debugcvsps(ui, *args, **opts)
296
300
297 commands.norepo += " convert debugsvnlog debugcvsps"
301 commands.norepo += " convert debugsvnlog debugcvsps"
298
302
299 cmdtable = {
303 cmdtable = {
300 "convert":
304 "convert":
301 (convert,
305 (convert,
302 [('', 'authors', '',
306 [('', 'authors', '',
303 _('username mapping filename (DEPRECATED, use --authormap instead)'),
307 _('username mapping filename (DEPRECATED, use --authormap instead)'),
304 _('FILE')),
308 _('FILE')),
305 ('s', 'source-type', '',
309 ('s', 'source-type', '',
306 _('source repository type'), _('TYPE')),
310 _('source repository type'), _('TYPE')),
307 ('d', 'dest-type', '',
311 ('d', 'dest-type', '',
308 _('destination repository type'), _('TYPE')),
312 _('destination repository type'), _('TYPE')),
309 ('r', 'rev', '',
313 ('r', 'rev', '',
310 _('import up to target revision REV'), _('REV')),
314 _('import up to target revision REV'), _('REV')),
311 ('A', 'authormap', '',
315 ('A', 'authormap', '',
312 _('remap usernames using this file'), _('FILE')),
316 _('remap usernames using this file'), _('FILE')),
313 ('', 'filemap', '',
317 ('', 'filemap', '',
314 _('remap file names using contents of file'), _('FILE')),
318 _('remap file names using contents of file'), _('FILE')),
315 ('', 'splicemap', '',
319 ('', 'splicemap', '',
316 _('splice synthesized history into place'), _('FILE')),
320 _('splice synthesized history into place'), _('FILE')),
317 ('', 'branchmap', '',
321 ('', 'branchmap', '',
318 _('change branch names while converting'), _('FILE')),
322 _('change branch names while converting'), _('FILE')),
319 ('', 'branchsort', None, _('try to sort changesets by branches')),
323 ('', 'branchsort', None, _('try to sort changesets by branches')),
320 ('', 'datesort', None, _('try to sort changesets by date')),
324 ('', 'datesort', None, _('try to sort changesets by date')),
321 ('', 'sourcesort', None, _('preserve source changesets order'))],
325 ('', 'sourcesort', None, _('preserve source changesets order')),
326 ('', 'closesort', None, _('try to reorder closed revisions'))],
322 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
327 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
323 "debugsvnlog":
328 "debugsvnlog":
324 (debugsvnlog,
329 (debugsvnlog,
325 [],
330 [],
326 'hg debugsvnlog'),
331 'hg debugsvnlog'),
327 "debugcvsps":
332 "debugcvsps":
328 (debugcvsps,
333 (debugcvsps,
329 [
334 [
330 # Main options shared with cvsps-2.1
335 # Main options shared with cvsps-2.1
331 ('b', 'branches', [], _('only return changes on specified branches')),
336 ('b', 'branches', [], _('only return changes on specified branches')),
332 ('p', 'prefix', '', _('prefix to remove from file names')),
337 ('p', 'prefix', '', _('prefix to remove from file names')),
333 ('r', 'revisions', [],
338 ('r', 'revisions', [],
334 _('only return changes after or between specified tags')),
339 _('only return changes after or between specified tags')),
335 ('u', 'update-cache', None, _("update cvs log cache")),
340 ('u', 'update-cache', None, _("update cvs log cache")),
336 ('x', 'new-cache', None, _("create new cvs log cache")),
341 ('x', 'new-cache', None, _("create new cvs log cache")),
337 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
342 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
338 ('', 'root', '', _('specify cvsroot')),
343 ('', 'root', '', _('specify cvsroot')),
339 # Options specific to builtin cvsps
344 # Options specific to builtin cvsps
340 ('', 'parents', '', _('show parent changesets')),
345 ('', 'parents', '', _('show parent changesets')),
341 ('', 'ancestors', '',
346 ('', 'ancestors', '',
342 _('show current changeset in ancestor branches')),
347 _('show current changeset in ancestor branches')),
343 # Options that are ignored for compatibility with cvsps-2.1
348 # Options that are ignored for compatibility with cvsps-2.1
344 ('A', 'cvs-direct', None, _('ignored for compatibility')),
349 ('A', 'cvs-direct', None, _('ignored for compatibility')),
345 ],
350 ],
346 _('hg debugcvsps [OPTION]... [PATH]...')),
351 _('hg debugcvsps [OPTION]... [PATH]...')),
347 }
352 }
348
353
349 def kwconverted(ctx, name):
354 def kwconverted(ctx, name):
350 rev = ctx.extra().get('convert_revision', '')
355 rev = ctx.extra().get('convert_revision', '')
351 if rev.startswith('svn:'):
356 if rev.startswith('svn:'):
352 if name == 'svnrev':
357 if name == 'svnrev':
353 return str(subversion.revsplit(rev)[2])
358 return str(subversion.revsplit(rev)[2])
354 elif name == 'svnpath':
359 elif name == 'svnpath':
355 return subversion.revsplit(rev)[1]
360 return subversion.revsplit(rev)[1]
356 elif name == 'svnuuid':
361 elif name == 'svnuuid':
357 return subversion.revsplit(rev)[0]
362 return subversion.revsplit(rev)[0]
358 return rev
363 return rev
359
364
360 def kwsvnrev(repo, ctx, **args):
365 def kwsvnrev(repo, ctx, **args):
361 """:svnrev: String. Converted subversion revision number."""
366 """:svnrev: String. Converted subversion revision number."""
362 return kwconverted(ctx, 'svnrev')
367 return kwconverted(ctx, 'svnrev')
363
368
364 def kwsvnpath(repo, ctx, **args):
369 def kwsvnpath(repo, ctx, **args):
365 """:svnpath: String. Converted subversion revision project path."""
370 """:svnpath: String. Converted subversion revision project path."""
366 return kwconverted(ctx, 'svnpath')
371 return kwconverted(ctx, 'svnpath')
367
372
368 def kwsvnuuid(repo, ctx, **args):
373 def kwsvnuuid(repo, ctx, **args):
369 """:svnuuid: String. Converted subversion revision repository identifier."""
374 """:svnuuid: String. Converted subversion revision repository identifier."""
370 return kwconverted(ctx, 'svnuuid')
375 return kwconverted(ctx, 'svnuuid')
371
376
372 def extsetup(ui):
377 def extsetup(ui):
373 templatekw.keywords['svnrev'] = kwsvnrev
378 templatekw.keywords['svnrev'] = kwsvnrev
374 templatekw.keywords['svnpath'] = kwsvnpath
379 templatekw.keywords['svnpath'] = kwsvnpath
375 templatekw.keywords['svnuuid'] = kwsvnuuid
380 templatekw.keywords['svnuuid'] = kwsvnuuid
376
381
377 # tell hggettext to extract docstrings from these functions:
382 # tell hggettext to extract docstrings from these functions:
378 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
383 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,455 +1,460 b''
1 # common.py - common code for the convert extension
1 # common.py - common code 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 base64, errno, subprocess, os, datetime
8 import base64, errno, subprocess, os, datetime
9 import cPickle as pickle
9 import cPickle as pickle
10 from mercurial import util
10 from mercurial import util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 def encodeargs(args):
15 def encodeargs(args):
16 def encodearg(s):
16 def encodearg(s):
17 lines = base64.encodestring(s)
17 lines = base64.encodestring(s)
18 lines = [l.splitlines()[0] for l in lines]
18 lines = [l.splitlines()[0] for l in lines]
19 return ''.join(lines)
19 return ''.join(lines)
20
20
21 s = pickle.dumps(args)
21 s = pickle.dumps(args)
22 return encodearg(s)
22 return encodearg(s)
23
23
24 def decodeargs(s):
24 def decodeargs(s):
25 s = base64.decodestring(s)
25 s = base64.decodestring(s)
26 return pickle.loads(s)
26 return pickle.loads(s)
27
27
28 class MissingTool(Exception):
28 class MissingTool(Exception):
29 pass
29 pass
30
30
31 def checktool(exe, name=None, abort=True):
31 def checktool(exe, name=None, abort=True):
32 name = name or exe
32 name = name or exe
33 if not util.findexe(exe):
33 if not util.findexe(exe):
34 exc = abort and util.Abort or MissingTool
34 exc = abort and util.Abort or MissingTool
35 raise exc(_('cannot find required "%s" tool') % name)
35 raise exc(_('cannot find required "%s" tool') % name)
36
36
37 class NoRepo(Exception):
37 class NoRepo(Exception):
38 pass
38 pass
39
39
40 SKIPREV = 'SKIP'
40 SKIPREV = 'SKIP'
41
41
42 class commit(object):
42 class commit(object):
43 def __init__(self, author, date, desc, parents, branch=None, rev=None,
43 def __init__(self, author, date, desc, parents, branch=None, rev=None,
44 extra={}, sortkey=None):
44 extra={}, sortkey=None):
45 self.author = author or 'unknown'
45 self.author = author or 'unknown'
46 self.date = date or '0 0'
46 self.date = date or '0 0'
47 self.desc = desc
47 self.desc = desc
48 self.parents = parents
48 self.parents = parents
49 self.branch = branch
49 self.branch = branch
50 self.rev = rev
50 self.rev = rev
51 self.extra = extra
51 self.extra = extra
52 self.sortkey = sortkey
52 self.sortkey = sortkey
53
53
54 class converter_source(object):
54 class converter_source(object):
55 """Conversion source interface"""
55 """Conversion source interface"""
56
56
57 def __init__(self, ui, path=None, rev=None):
57 def __init__(self, ui, path=None, rev=None):
58 """Initialize conversion source (or raise NoRepo("message")
58 """Initialize conversion source (or raise NoRepo("message")
59 exception if path is not a valid repository)"""
59 exception if path is not a valid repository)"""
60 self.ui = ui
60 self.ui = ui
61 self.path = path
61 self.path = path
62 self.rev = rev
62 self.rev = rev
63
63
64 self.encoding = 'utf-8'
64 self.encoding = 'utf-8'
65
65
66 def before(self):
66 def before(self):
67 pass
67 pass
68
68
69 def after(self):
69 def after(self):
70 pass
70 pass
71
71
72 def setrevmap(self, revmap):
72 def setrevmap(self, revmap):
73 """set the map of already-converted revisions"""
73 """set the map of already-converted revisions"""
74 pass
74 pass
75
75
76 def getheads(self):
76 def getheads(self):
77 """Return a list of this repository's heads"""
77 """Return a list of this repository's heads"""
78 raise NotImplementedError
78 raise NotImplementedError
79
79
80 def getfile(self, name, rev):
80 def getfile(self, name, rev):
81 """Return a pair (data, mode) where data is the file content
81 """Return a pair (data, mode) where data is the file content
82 as a string and mode one of '', 'x' or 'l'. rev is the
82 as a string and mode one of '', 'x' or 'l'. rev is the
83 identifier returned by a previous call to getchanges(). Raise
83 identifier returned by a previous call to getchanges(). Raise
84 IOError to indicate that name was deleted in rev.
84 IOError to indicate that name was deleted in rev.
85 """
85 """
86 raise NotImplementedError
86 raise NotImplementedError
87
87
88 def getchanges(self, version):
88 def getchanges(self, version):
89 """Returns a tuple of (files, copies).
89 """Returns a tuple of (files, copies).
90
90
91 files is a sorted list of (filename, id) tuples for all files
91 files is a sorted list of (filename, id) tuples for all files
92 changed between version and its first parent returned by
92 changed between version and its first parent returned by
93 getcommit(). id is the source revision id of the file.
93 getcommit(). id is the source revision id of the file.
94
94
95 copies is a dictionary of dest: source
95 copies is a dictionary of dest: source
96 """
96 """
97 raise NotImplementedError
97 raise NotImplementedError
98
98
99 def getcommit(self, version):
99 def getcommit(self, version):
100 """Return the commit object for version"""
100 """Return the commit object for version"""
101 raise NotImplementedError
101 raise NotImplementedError
102
102
103 def gettags(self):
103 def gettags(self):
104 """Return the tags as a dictionary of name: revision
104 """Return the tags as a dictionary of name: revision
105
105
106 Tag names must be UTF-8 strings.
106 Tag names must be UTF-8 strings.
107 """
107 """
108 raise NotImplementedError
108 raise NotImplementedError
109
109
110 def recode(self, s, encoding=None):
110 def recode(self, s, encoding=None):
111 if not encoding:
111 if not encoding:
112 encoding = self.encoding or 'utf-8'
112 encoding = self.encoding or 'utf-8'
113
113
114 if isinstance(s, unicode):
114 if isinstance(s, unicode):
115 return s.encode("utf-8")
115 return s.encode("utf-8")
116 try:
116 try:
117 return s.decode(encoding).encode("utf-8")
117 return s.decode(encoding).encode("utf-8")
118 except UnicodeError:
118 except UnicodeError:
119 try:
119 try:
120 return s.decode("latin-1").encode("utf-8")
120 return s.decode("latin-1").encode("utf-8")
121 except UnicodeError:
121 except UnicodeError:
122 return s.decode(encoding, "replace").encode("utf-8")
122 return s.decode(encoding, "replace").encode("utf-8")
123
123
124 def getchangedfiles(self, rev, i):
124 def getchangedfiles(self, rev, i):
125 """Return the files changed by rev compared to parent[i].
125 """Return the files changed by rev compared to parent[i].
126
126
127 i is an index selecting one of the parents of rev. The return
127 i is an index selecting one of the parents of rev. The return
128 value should be the list of files that are different in rev and
128 value should be the list of files that are different in rev and
129 this parent.
129 this parent.
130
130
131 If rev has no parents, i is None.
131 If rev has no parents, i is None.
132
132
133 This function is only needed to support --filemap
133 This function is only needed to support --filemap
134 """
134 """
135 raise NotImplementedError
135 raise NotImplementedError
136
136
137 def converted(self, rev, sinkrev):
137 def converted(self, rev, sinkrev):
138 '''Notify the source that a revision has been converted.'''
138 '''Notify the source that a revision has been converted.'''
139 pass
139 pass
140
140
141 def hasnativeorder(self):
141 def hasnativeorder(self):
142 """Return true if this source has a meaningful, native revision
142 """Return true if this source has a meaningful, native revision
143 order. For instance, Mercurial revisions are store sequentially
143 order. For instance, Mercurial revisions are store sequentially
144 while there is no such global ordering with Darcs.
144 while there is no such global ordering with Darcs.
145 """
145 """
146 return False
146 return False
147
147
148 def hasnativeclose(self):
149 """Return true if this source has ability to close branch.
150 """
151 return False
152
148 def lookuprev(self, rev):
153 def lookuprev(self, rev):
149 """If rev is a meaningful revision reference in source, return
154 """If rev is a meaningful revision reference in source, return
150 the referenced identifier in the same format used by getcommit().
155 the referenced identifier in the same format used by getcommit().
151 return None otherwise.
156 return None otherwise.
152 """
157 """
153 return None
158 return None
154
159
155 def getbookmarks(self):
160 def getbookmarks(self):
156 """Return the bookmarks as a dictionary of name: revision
161 """Return the bookmarks as a dictionary of name: revision
157
162
158 Bookmark names are to be UTF-8 strings.
163 Bookmark names are to be UTF-8 strings.
159 """
164 """
160 return {}
165 return {}
161
166
162 class converter_sink(object):
167 class converter_sink(object):
163 """Conversion sink (target) interface"""
168 """Conversion sink (target) interface"""
164
169
165 def __init__(self, ui, path):
170 def __init__(self, ui, path):
166 """Initialize conversion sink (or raise NoRepo("message")
171 """Initialize conversion sink (or raise NoRepo("message")
167 exception if path is not a valid repository)
172 exception if path is not a valid repository)
168
173
169 created is a list of paths to remove if a fatal error occurs
174 created is a list of paths to remove if a fatal error occurs
170 later"""
175 later"""
171 self.ui = ui
176 self.ui = ui
172 self.path = path
177 self.path = path
173 self.created = []
178 self.created = []
174
179
175 def getheads(self):
180 def getheads(self):
176 """Return a list of this repository's heads"""
181 """Return a list of this repository's heads"""
177 raise NotImplementedError
182 raise NotImplementedError
178
183
179 def revmapfile(self):
184 def revmapfile(self):
180 """Path to a file that will contain lines
185 """Path to a file that will contain lines
181 source_rev_id sink_rev_id
186 source_rev_id sink_rev_id
182 mapping equivalent revision identifiers for each system."""
187 mapping equivalent revision identifiers for each system."""
183 raise NotImplementedError
188 raise NotImplementedError
184
189
185 def authorfile(self):
190 def authorfile(self):
186 """Path to a file that will contain lines
191 """Path to a file that will contain lines
187 srcauthor=dstauthor
192 srcauthor=dstauthor
188 mapping equivalent authors identifiers for each system."""
193 mapping equivalent authors identifiers for each system."""
189 return None
194 return None
190
195
191 def putcommit(self, files, copies, parents, commit, source, revmap):
196 def putcommit(self, files, copies, parents, commit, source, revmap):
192 """Create a revision with all changed files listed in 'files'
197 """Create a revision with all changed files listed in 'files'
193 and having listed parents. 'commit' is a commit object
198 and having listed parents. 'commit' is a commit object
194 containing at a minimum the author, date, and message for this
199 containing at a minimum the author, date, and message for this
195 changeset. 'files' is a list of (path, version) tuples,
200 changeset. 'files' is a list of (path, version) tuples,
196 'copies' is a dictionary mapping destinations to sources,
201 'copies' is a dictionary mapping destinations to sources,
197 'source' is the source repository, and 'revmap' is a mapfile
202 'source' is the source repository, and 'revmap' is a mapfile
198 of source revisions to converted revisions. Only getfile() and
203 of source revisions to converted revisions. Only getfile() and
199 lookuprev() should be called on 'source'.
204 lookuprev() should be called on 'source'.
200
205
201 Note that the sink repository is not told to update itself to
206 Note that the sink repository is not told to update itself to
202 a particular revision (or even what that revision would be)
207 a particular revision (or even what that revision would be)
203 before it receives the file data.
208 before it receives the file data.
204 """
209 """
205 raise NotImplementedError
210 raise NotImplementedError
206
211
207 def puttags(self, tags):
212 def puttags(self, tags):
208 """Put tags into sink.
213 """Put tags into sink.
209
214
210 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
215 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
211 Return a pair (tag_revision, tag_parent_revision), or (None, None)
216 Return a pair (tag_revision, tag_parent_revision), or (None, None)
212 if nothing was changed.
217 if nothing was changed.
213 """
218 """
214 raise NotImplementedError
219 raise NotImplementedError
215
220
216 def setbranch(self, branch, pbranches):
221 def setbranch(self, branch, pbranches):
217 """Set the current branch name. Called before the first putcommit
222 """Set the current branch name. Called before the first putcommit
218 on the branch.
223 on the branch.
219 branch: branch name for subsequent commits
224 branch: branch name for subsequent commits
220 pbranches: (converted parent revision, parent branch) tuples"""
225 pbranches: (converted parent revision, parent branch) tuples"""
221 pass
226 pass
222
227
223 def setfilemapmode(self, active):
228 def setfilemapmode(self, active):
224 """Tell the destination that we're using a filemap
229 """Tell the destination that we're using a filemap
225
230
226 Some converter_sources (svn in particular) can claim that a file
231 Some converter_sources (svn in particular) can claim that a file
227 was changed in a revision, even if there was no change. This method
232 was changed in a revision, even if there was no change. This method
228 tells the destination that we're using a filemap and that it should
233 tells the destination that we're using a filemap and that it should
229 filter empty revisions.
234 filter empty revisions.
230 """
235 """
231 pass
236 pass
232
237
233 def before(self):
238 def before(self):
234 pass
239 pass
235
240
236 def after(self):
241 def after(self):
237 pass
242 pass
238
243
239 def putbookmarks(self, bookmarks):
244 def putbookmarks(self, bookmarks):
240 """Put bookmarks into sink.
245 """Put bookmarks into sink.
241
246
242 bookmarks: {bookmarkname: sink_rev_id, ...}
247 bookmarks: {bookmarkname: sink_rev_id, ...}
243 where bookmarkname is an UTF-8 string.
248 where bookmarkname is an UTF-8 string.
244 """
249 """
245 pass
250 pass
246
251
247 def hascommit(self, rev):
252 def hascommit(self, rev):
248 """Return True if the sink contains rev"""
253 """Return True if the sink contains rev"""
249 raise NotImplementedError
254 raise NotImplementedError
250
255
251 class commandline(object):
256 class commandline(object):
252 def __init__(self, ui, command):
257 def __init__(self, ui, command):
253 self.ui = ui
258 self.ui = ui
254 self.command = command
259 self.command = command
255
260
256 def prerun(self):
261 def prerun(self):
257 pass
262 pass
258
263
259 def postrun(self):
264 def postrun(self):
260 pass
265 pass
261
266
262 def _cmdline(self, cmd, *args, **kwargs):
267 def _cmdline(self, cmd, *args, **kwargs):
263 cmdline = [self.command, cmd] + list(args)
268 cmdline = [self.command, cmd] + list(args)
264 for k, v in kwargs.iteritems():
269 for k, v in kwargs.iteritems():
265 if len(k) == 1:
270 if len(k) == 1:
266 cmdline.append('-' + k)
271 cmdline.append('-' + k)
267 else:
272 else:
268 cmdline.append('--' + k.replace('_', '-'))
273 cmdline.append('--' + k.replace('_', '-'))
269 try:
274 try:
270 if len(k) == 1:
275 if len(k) == 1:
271 cmdline.append('' + v)
276 cmdline.append('' + v)
272 else:
277 else:
273 cmdline[-1] += '=' + v
278 cmdline[-1] += '=' + v
274 except TypeError:
279 except TypeError:
275 pass
280 pass
276 cmdline = [util.shellquote(arg) for arg in cmdline]
281 cmdline = [util.shellquote(arg) for arg in cmdline]
277 if not self.ui.debugflag:
282 if not self.ui.debugflag:
278 cmdline += ['2>', os.devnull]
283 cmdline += ['2>', os.devnull]
279 cmdline = ' '.join(cmdline)
284 cmdline = ' '.join(cmdline)
280 return cmdline
285 return cmdline
281
286
282 def _run(self, cmd, *args, **kwargs):
287 def _run(self, cmd, *args, **kwargs):
283 def popen(cmdline):
288 def popen(cmdline):
284 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
289 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
285 close_fds=util.closefds,
290 close_fds=util.closefds,
286 stdout=subprocess.PIPE)
291 stdout=subprocess.PIPE)
287 return p
292 return p
288 return self._dorun(popen, cmd, *args, **kwargs)
293 return self._dorun(popen, cmd, *args, **kwargs)
289
294
290 def _run2(self, cmd, *args, **kwargs):
295 def _run2(self, cmd, *args, **kwargs):
291 return self._dorun(util.popen2, cmd, *args, **kwargs)
296 return self._dorun(util.popen2, cmd, *args, **kwargs)
292
297
293 def _dorun(self, openfunc, cmd, *args, **kwargs):
298 def _dorun(self, openfunc, cmd, *args, **kwargs):
294 cmdline = self._cmdline(cmd, *args, **kwargs)
299 cmdline = self._cmdline(cmd, *args, **kwargs)
295 self.ui.debug('running: %s\n' % (cmdline,))
300 self.ui.debug('running: %s\n' % (cmdline,))
296 self.prerun()
301 self.prerun()
297 try:
302 try:
298 return openfunc(cmdline)
303 return openfunc(cmdline)
299 finally:
304 finally:
300 self.postrun()
305 self.postrun()
301
306
302 def run(self, cmd, *args, **kwargs):
307 def run(self, cmd, *args, **kwargs):
303 p = self._run(cmd, *args, **kwargs)
308 p = self._run(cmd, *args, **kwargs)
304 output = p.communicate()[0]
309 output = p.communicate()[0]
305 self.ui.debug(output)
310 self.ui.debug(output)
306 return output, p.returncode
311 return output, p.returncode
307
312
308 def runlines(self, cmd, *args, **kwargs):
313 def runlines(self, cmd, *args, **kwargs):
309 p = self._run(cmd, *args, **kwargs)
314 p = self._run(cmd, *args, **kwargs)
310 output = p.stdout.readlines()
315 output = p.stdout.readlines()
311 p.wait()
316 p.wait()
312 self.ui.debug(''.join(output))
317 self.ui.debug(''.join(output))
313 return output, p.returncode
318 return output, p.returncode
314
319
315 def checkexit(self, status, output=''):
320 def checkexit(self, status, output=''):
316 if status:
321 if status:
317 if output:
322 if output:
318 self.ui.warn(_('%s error:\n') % self.command)
323 self.ui.warn(_('%s error:\n') % self.command)
319 self.ui.warn(output)
324 self.ui.warn(output)
320 msg = util.explainexit(status)[0]
325 msg = util.explainexit(status)[0]
321 raise util.Abort('%s %s' % (self.command, msg))
326 raise util.Abort('%s %s' % (self.command, msg))
322
327
323 def run0(self, cmd, *args, **kwargs):
328 def run0(self, cmd, *args, **kwargs):
324 output, status = self.run(cmd, *args, **kwargs)
329 output, status = self.run(cmd, *args, **kwargs)
325 self.checkexit(status, output)
330 self.checkexit(status, output)
326 return output
331 return output
327
332
328 def runlines0(self, cmd, *args, **kwargs):
333 def runlines0(self, cmd, *args, **kwargs):
329 output, status = self.runlines(cmd, *args, **kwargs)
334 output, status = self.runlines(cmd, *args, **kwargs)
330 self.checkexit(status, ''.join(output))
335 self.checkexit(status, ''.join(output))
331 return output
336 return output
332
337
333 @propertycache
338 @propertycache
334 def argmax(self):
339 def argmax(self):
335 # POSIX requires at least 4096 bytes for ARG_MAX
340 # POSIX requires at least 4096 bytes for ARG_MAX
336 argmax = 4096
341 argmax = 4096
337 try:
342 try:
338 argmax = os.sysconf("SC_ARG_MAX")
343 argmax = os.sysconf("SC_ARG_MAX")
339 except (AttributeError, ValueError):
344 except (AttributeError, ValueError):
340 pass
345 pass
341
346
342 # Windows shells impose their own limits on command line length,
347 # Windows shells impose their own limits on command line length,
343 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
348 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
344 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
349 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
345 # details about cmd.exe limitations.
350 # details about cmd.exe limitations.
346
351
347 # Since ARG_MAX is for command line _and_ environment, lower our limit
352 # Since ARG_MAX is for command line _and_ environment, lower our limit
348 # (and make happy Windows shells while doing this).
353 # (and make happy Windows shells while doing this).
349 return argmax // 2 - 1
354 return argmax // 2 - 1
350
355
351 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
356 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
352 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
357 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
353 limit = self.argmax - cmdlen
358 limit = self.argmax - cmdlen
354 bytes = 0
359 bytes = 0
355 fl = []
360 fl = []
356 for fn in arglist:
361 for fn in arglist:
357 b = len(fn) + 3
362 b = len(fn) + 3
358 if bytes + b < limit or len(fl) == 0:
363 if bytes + b < limit or len(fl) == 0:
359 fl.append(fn)
364 fl.append(fn)
360 bytes += b
365 bytes += b
361 else:
366 else:
362 yield fl
367 yield fl
363 fl = [fn]
368 fl = [fn]
364 bytes = b
369 bytes = b
365 if fl:
370 if fl:
366 yield fl
371 yield fl
367
372
368 def xargs(self, arglist, cmd, *args, **kwargs):
373 def xargs(self, arglist, cmd, *args, **kwargs):
369 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
374 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
370 self.run0(cmd, *(list(args) + l), **kwargs)
375 self.run0(cmd, *(list(args) + l), **kwargs)
371
376
372 class mapfile(dict):
377 class mapfile(dict):
373 def __init__(self, ui, path):
378 def __init__(self, ui, path):
374 super(mapfile, self).__init__()
379 super(mapfile, self).__init__()
375 self.ui = ui
380 self.ui = ui
376 self.path = path
381 self.path = path
377 self.fp = None
382 self.fp = None
378 self.order = []
383 self.order = []
379 self._read()
384 self._read()
380
385
381 def _read(self):
386 def _read(self):
382 if not self.path:
387 if not self.path:
383 return
388 return
384 try:
389 try:
385 fp = open(self.path, 'r')
390 fp = open(self.path, 'r')
386 except IOError, err:
391 except IOError, err:
387 if err.errno != errno.ENOENT:
392 if err.errno != errno.ENOENT:
388 raise
393 raise
389 return
394 return
390 for i, line in enumerate(fp):
395 for i, line in enumerate(fp):
391 line = line.splitlines()[0].rstrip()
396 line = line.splitlines()[0].rstrip()
392 if not line:
397 if not line:
393 # Ignore blank lines
398 # Ignore blank lines
394 continue
399 continue
395 try:
400 try:
396 key, value = line.rsplit(' ', 1)
401 key, value = line.rsplit(' ', 1)
397 except ValueError:
402 except ValueError:
398 raise util.Abort(
403 raise util.Abort(
399 _('syntax error in %s(%d): key/value pair expected')
404 _('syntax error in %s(%d): key/value pair expected')
400 % (self.path, i + 1))
405 % (self.path, i + 1))
401 if key not in self:
406 if key not in self:
402 self.order.append(key)
407 self.order.append(key)
403 super(mapfile, self).__setitem__(key, value)
408 super(mapfile, self).__setitem__(key, value)
404 fp.close()
409 fp.close()
405
410
406 def __setitem__(self, key, value):
411 def __setitem__(self, key, value):
407 if self.fp is None:
412 if self.fp is None:
408 try:
413 try:
409 self.fp = open(self.path, 'a')
414 self.fp = open(self.path, 'a')
410 except IOError, err:
415 except IOError, err:
411 raise util.Abort(_('could not open map file %r: %s') %
416 raise util.Abort(_('could not open map file %r: %s') %
412 (self.path, err.strerror))
417 (self.path, err.strerror))
413 self.fp.write('%s %s\n' % (key, value))
418 self.fp.write('%s %s\n' % (key, value))
414 self.fp.flush()
419 self.fp.flush()
415 super(mapfile, self).__setitem__(key, value)
420 super(mapfile, self).__setitem__(key, value)
416
421
417 def close(self):
422 def close(self):
418 if self.fp:
423 if self.fp:
419 self.fp.close()
424 self.fp.close()
420 self.fp = None
425 self.fp = None
421
426
422 def parsesplicemap(path):
427 def parsesplicemap(path):
423 """Parse a splicemap, return a child/parents dictionary."""
428 """Parse a splicemap, return a child/parents dictionary."""
424 if not path:
429 if not path:
425 return {}
430 return {}
426 m = {}
431 m = {}
427 try:
432 try:
428 fp = open(path, 'r')
433 fp = open(path, 'r')
429 for i, line in enumerate(fp):
434 for i, line in enumerate(fp):
430 line = line.splitlines()[0].rstrip()
435 line = line.splitlines()[0].rstrip()
431 if not line:
436 if not line:
432 # Ignore blank lines
437 # Ignore blank lines
433 continue
438 continue
434 try:
439 try:
435 child, parents = line.split(' ', 1)
440 child, parents = line.split(' ', 1)
436 parents = parents.replace(',', ' ').split()
441 parents = parents.replace(',', ' ').split()
437 except ValueError:
442 except ValueError:
438 raise util.Abort(_('syntax error in %s(%d): child parent1'
443 raise util.Abort(_('syntax error in %s(%d): child parent1'
439 '[,parent2] expected') % (path, i + 1))
444 '[,parent2] expected') % (path, i + 1))
440 pp = []
445 pp = []
441 for p in parents:
446 for p in parents:
442 if p not in pp:
447 if p not in pp:
443 pp.append(p)
448 pp.append(p)
444 m[child] = pp
449 m[child] = pp
445 except IOError, e:
450 except IOError, e:
446 if e.errno != errno.ENOENT:
451 if e.errno != errno.ENOENT:
447 raise
452 raise
448 return m
453 return m
449
454
450 def makedatetimestamp(t):
455 def makedatetimestamp(t):
451 """Like util.makedate() but for time t instead of current time"""
456 """Like util.makedate() but for time t instead of current time"""
452 delta = (datetime.datetime.utcfromtimestamp(t) -
457 delta = (datetime.datetime.utcfromtimestamp(t) -
453 datetime.datetime.fromtimestamp(t))
458 datetime.datetime.fromtimestamp(t))
454 tz = delta.days * 86400 + delta.seconds
459 tz = delta.days * 86400 + delta.seconds
455 return t, tz
460 return t, tz
@@ -1,470 +1,482 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import svn_source, svn_sink
13 from subversion import svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 from bzr import bzr_source
16 from bzr import bzr_source
17 from p4 import p4_source
17 from p4 import p4_source
18 import filemap, common
18 import filemap, common
19
19
20 import os, shutil
20 import os, shutil
21 from mercurial import hg, util, encoding
21 from mercurial import hg, util, encoding
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24 orig_encoding = 'ascii'
24 orig_encoding = 'ascii'
25
25
26 def recode(s):
26 def recode(s):
27 if isinstance(s, unicode):
27 if isinstance(s, unicode):
28 return s.encode(orig_encoding, 'replace')
28 return s.encode(orig_encoding, 'replace')
29 else:
29 else:
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
31
31
32 source_converters = [
32 source_converters = [
33 ('cvs', convert_cvs, 'branchsort'),
33 ('cvs', convert_cvs, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
36 ('hg', mercurial_source, 'sourcesort'),
36 ('hg', mercurial_source, 'sourcesort'),
37 ('darcs', darcs_source, 'branchsort'),
37 ('darcs', darcs_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
42 ]
42 ]
43
43
44 sink_converters = [
44 sink_converters = [
45 ('hg', mercurial_sink),
45 ('hg', mercurial_sink),
46 ('svn', svn_sink),
46 ('svn', svn_sink),
47 ]
47 ]
48
48
49 def convertsource(ui, path, type, rev):
49 def convertsource(ui, path, type, rev):
50 exceptions = []
50 exceptions = []
51 if type and type not in [s[0] for s in source_converters]:
51 if type and type not in [s[0] for s in source_converters]:
52 raise util.Abort(_('%s: invalid source repository type') % type)
52 raise util.Abort(_('%s: invalid source repository type') % type)
53 for name, source, sortmode in source_converters:
53 for name, source, sortmode in source_converters:
54 try:
54 try:
55 if not type or name == type:
55 if not type or name == type:
56 return source(ui, path, rev), sortmode
56 return source(ui, path, rev), sortmode
57 except (NoRepo, MissingTool), inst:
57 except (NoRepo, MissingTool), inst:
58 exceptions.append(inst)
58 exceptions.append(inst)
59 if not ui.quiet:
59 if not ui.quiet:
60 for inst in exceptions:
60 for inst in exceptions:
61 ui.write("%s\n" % inst)
61 ui.write("%s\n" % inst)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
63
63
64 def convertsink(ui, path, type):
64 def convertsink(ui, path, type):
65 if type and type not in [s[0] for s in sink_converters]:
65 if type and type not in [s[0] for s in sink_converters]:
66 raise util.Abort(_('%s: invalid destination repository type') % type)
66 raise util.Abort(_('%s: invalid destination repository type') % type)
67 for name, sink in sink_converters:
67 for name, sink in sink_converters:
68 try:
68 try:
69 if not type or name == type:
69 if not type or name == type:
70 return sink(ui, path)
70 return sink(ui, path)
71 except NoRepo, inst:
71 except NoRepo, inst:
72 ui.note(_("convert: %s\n") % inst)
72 ui.note(_("convert: %s\n") % inst)
73 except MissingTool, inst:
73 except MissingTool, inst:
74 raise util.Abort('%s\n' % inst)
74 raise util.Abort('%s\n' % inst)
75 raise util.Abort(_('%s: unknown repository type') % path)
75 raise util.Abort(_('%s: unknown repository type') % path)
76
76
77 class progresssource(object):
77 class progresssource(object):
78 def __init__(self, ui, source, filecount):
78 def __init__(self, ui, source, filecount):
79 self.ui = ui
79 self.ui = ui
80 self.source = source
80 self.source = source
81 self.filecount = filecount
81 self.filecount = filecount
82 self.retrieved = 0
82 self.retrieved = 0
83
83
84 def getfile(self, file, rev):
84 def getfile(self, file, rev):
85 self.retrieved += 1
85 self.retrieved += 1
86 self.ui.progress(_('getting files'), self.retrieved,
86 self.ui.progress(_('getting files'), self.retrieved,
87 item=file, total=self.filecount)
87 item=file, total=self.filecount)
88 return self.source.getfile(file, rev)
88 return self.source.getfile(file, rev)
89
89
90 def lookuprev(self, rev):
90 def lookuprev(self, rev):
91 return self.source.lookuprev(rev)
91 return self.source.lookuprev(rev)
92
92
93 def close(self):
93 def close(self):
94 self.ui.progress(_('getting files'), None)
94 self.ui.progress(_('getting files'), None)
95
95
96 class converter(object):
96 class converter(object):
97 def __init__(self, ui, source, dest, revmapfile, opts):
97 def __init__(self, ui, source, dest, revmapfile, opts):
98
98
99 self.source = source
99 self.source = source
100 self.dest = dest
100 self.dest = dest
101 self.ui = ui
101 self.ui = ui
102 self.opts = opts
102 self.opts = opts
103 self.commitcache = {}
103 self.commitcache = {}
104 self.authors = {}
104 self.authors = {}
105 self.authorfile = None
105 self.authorfile = None
106
106
107 # Record converted revisions persistently: maps source revision
107 # Record converted revisions persistently: maps source revision
108 # ID to target revision ID (both strings). (This is how
108 # ID to target revision ID (both strings). (This is how
109 # incremental conversions work.)
109 # incremental conversions work.)
110 self.map = mapfile(ui, revmapfile)
110 self.map = mapfile(ui, revmapfile)
111
111
112 # Read first the dst author map if any
112 # Read first the dst author map if any
113 authorfile = self.dest.authorfile()
113 authorfile = self.dest.authorfile()
114 if authorfile and os.path.exists(authorfile):
114 if authorfile and os.path.exists(authorfile):
115 self.readauthormap(authorfile)
115 self.readauthormap(authorfile)
116 # Extend/Override with new author map if necessary
116 # Extend/Override with new author map if necessary
117 if opts.get('authormap'):
117 if opts.get('authormap'):
118 self.readauthormap(opts.get('authormap'))
118 self.readauthormap(opts.get('authormap'))
119 self.authorfile = self.dest.authorfile()
119 self.authorfile = self.dest.authorfile()
120
120
121 self.splicemap = common.parsesplicemap(opts.get('splicemap'))
121 self.splicemap = common.parsesplicemap(opts.get('splicemap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
123
123
124 def walktree(self, heads):
124 def walktree(self, heads):
125 '''Return a mapping that identifies the uncommitted parents of every
125 '''Return a mapping that identifies the uncommitted parents of every
126 uncommitted changeset.'''
126 uncommitted changeset.'''
127 visit = heads
127 visit = heads
128 known = set()
128 known = set()
129 parents = {}
129 parents = {}
130 while visit:
130 while visit:
131 n = visit.pop(0)
131 n = visit.pop(0)
132 if n in known or n in self.map:
132 if n in known or n in self.map:
133 continue
133 continue
134 known.add(n)
134 known.add(n)
135 self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
135 self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
136 commit = self.cachecommit(n)
136 commit = self.cachecommit(n)
137 parents[n] = []
137 parents[n] = []
138 for p in commit.parents:
138 for p in commit.parents:
139 parents[n].append(p)
139 parents[n].append(p)
140 visit.append(p)
140 visit.append(p)
141 self.ui.progress(_('scanning'), None)
141 self.ui.progress(_('scanning'), None)
142
142
143 return parents
143 return parents
144
144
145 def mergesplicemap(self, parents, splicemap):
145 def mergesplicemap(self, parents, splicemap):
146 """A splicemap redefines child/parent relationships. Check the
146 """A splicemap redefines child/parent relationships. Check the
147 map contains valid revision identifiers and merge the new
147 map contains valid revision identifiers and merge the new
148 links in the source graph.
148 links in the source graph.
149 """
149 """
150 for c in sorted(splicemap):
150 for c in sorted(splicemap):
151 if c not in parents:
151 if c not in parents:
152 if not self.dest.hascommit(self.map.get(c, c)):
152 if not self.dest.hascommit(self.map.get(c, c)):
153 # Could be in source but not converted during this run
153 # Could be in source but not converted during this run
154 self.ui.warn(_('splice map revision %s is not being '
154 self.ui.warn(_('splice map revision %s is not being '
155 'converted, ignoring\n') % c)
155 'converted, ignoring\n') % c)
156 continue
156 continue
157 pc = []
157 pc = []
158 for p in splicemap[c]:
158 for p in splicemap[c]:
159 # We do not have to wait for nodes already in dest.
159 # We do not have to wait for nodes already in dest.
160 if self.dest.hascommit(self.map.get(p, p)):
160 if self.dest.hascommit(self.map.get(p, p)):
161 continue
161 continue
162 # Parent is not in dest and not being converted, not good
162 # Parent is not in dest and not being converted, not good
163 if p not in parents:
163 if p not in parents:
164 raise util.Abort(_('unknown splice map parent: %s') % p)
164 raise util.Abort(_('unknown splice map parent: %s') % p)
165 pc.append(p)
165 pc.append(p)
166 parents[c] = pc
166 parents[c] = pc
167
167
168 def toposort(self, parents, sortmode):
168 def toposort(self, parents, sortmode):
169 '''Return an ordering such that every uncommitted changeset is
169 '''Return an ordering such that every uncommitted changeset is
170 preceded by all its uncommitted ancestors.'''
170 preceded by all its uncommitted ancestors.'''
171
171
172 def mapchildren(parents):
172 def mapchildren(parents):
173 """Return a (children, roots) tuple where 'children' maps parent
173 """Return a (children, roots) tuple where 'children' maps parent
174 revision identifiers to children ones, and 'roots' is the list of
174 revision identifiers to children ones, and 'roots' is the list of
175 revisions without parents. 'parents' must be a mapping of revision
175 revisions without parents. 'parents' must be a mapping of revision
176 identifier to its parents ones.
176 identifier to its parents ones.
177 """
177 """
178 visit = sorted(parents)
178 visit = sorted(parents)
179 seen = set()
179 seen = set()
180 children = {}
180 children = {}
181 roots = []
181 roots = []
182
182
183 while visit:
183 while visit:
184 n = visit.pop(0)
184 n = visit.pop(0)
185 if n in seen:
185 if n in seen:
186 continue
186 continue
187 seen.add(n)
187 seen.add(n)
188 # Ensure that nodes without parents are present in the
188 # Ensure that nodes without parents are present in the
189 # 'children' mapping.
189 # 'children' mapping.
190 children.setdefault(n, [])
190 children.setdefault(n, [])
191 hasparent = False
191 hasparent = False
192 for p in parents[n]:
192 for p in parents[n]:
193 if p not in self.map:
193 if p not in self.map:
194 visit.append(p)
194 visit.append(p)
195 hasparent = True
195 hasparent = True
196 children.setdefault(p, []).append(n)
196 children.setdefault(p, []).append(n)
197 if not hasparent:
197 if not hasparent:
198 roots.append(n)
198 roots.append(n)
199
199
200 return children, roots
200 return children, roots
201
201
202 # Sort functions are supposed to take a list of revisions which
202 # Sort functions are supposed to take a list of revisions which
203 # can be converted immediately and pick one
203 # can be converted immediately and pick one
204
204
205 def makebranchsorter():
205 def makebranchsorter():
206 """If the previously converted revision has a child in the
206 """If the previously converted revision has a child in the
207 eligible revisions list, pick it. Return the list head
207 eligible revisions list, pick it. Return the list head
208 otherwise. Branch sort attempts to minimize branch
208 otherwise. Branch sort attempts to minimize branch
209 switching, which is harmful for Mercurial backend
209 switching, which is harmful for Mercurial backend
210 compression.
210 compression.
211 """
211 """
212 prev = [None]
212 prev = [None]
213 def picknext(nodes):
213 def picknext(nodes):
214 next = nodes[0]
214 next = nodes[0]
215 for n in nodes:
215 for n in nodes:
216 if prev[0] in parents[n]:
216 if prev[0] in parents[n]:
217 next = n
217 next = n
218 break
218 break
219 prev[0] = next
219 prev[0] = next
220 return next
220 return next
221 return picknext
221 return picknext
222
222
223 def makesourcesorter():
223 def makesourcesorter():
224 """Source specific sort."""
224 """Source specific sort."""
225 keyfn = lambda n: self.commitcache[n].sortkey
225 keyfn = lambda n: self.commitcache[n].sortkey
226 def picknext(nodes):
226 def picknext(nodes):
227 return sorted(nodes, key=keyfn)[0]
227 return sorted(nodes, key=keyfn)[0]
228 return picknext
228 return picknext
229
229
230 def makeclosesorter():
231 """Close order sort."""
232 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
233 self.commitcache[n].sortkey)
234 def picknext(nodes):
235 return sorted(nodes, key=keyfn)[0]
236 return picknext
237
230 def makedatesorter():
238 def makedatesorter():
231 """Sort revisions by date."""
239 """Sort revisions by date."""
232 dates = {}
240 dates = {}
233 def getdate(n):
241 def getdate(n):
234 if n not in dates:
242 if n not in dates:
235 dates[n] = util.parsedate(self.commitcache[n].date)
243 dates[n] = util.parsedate(self.commitcache[n].date)
236 return dates[n]
244 return dates[n]
237
245
238 def picknext(nodes):
246 def picknext(nodes):
239 return min([(getdate(n), n) for n in nodes])[1]
247 return min([(getdate(n), n) for n in nodes])[1]
240
248
241 return picknext
249 return picknext
242
250
243 if sortmode == 'branchsort':
251 if sortmode == 'branchsort':
244 picknext = makebranchsorter()
252 picknext = makebranchsorter()
245 elif sortmode == 'datesort':
253 elif sortmode == 'datesort':
246 picknext = makedatesorter()
254 picknext = makedatesorter()
247 elif sortmode == 'sourcesort':
255 elif sortmode == 'sourcesort':
248 picknext = makesourcesorter()
256 picknext = makesourcesorter()
257 elif sortmode == 'closesort':
258 picknext = makeclosesorter()
249 else:
259 else:
250 raise util.Abort(_('unknown sort mode: %s') % sortmode)
260 raise util.Abort(_('unknown sort mode: %s') % sortmode)
251
261
252 children, actives = mapchildren(parents)
262 children, actives = mapchildren(parents)
253
263
254 s = []
264 s = []
255 pendings = {}
265 pendings = {}
256 while actives:
266 while actives:
257 n = picknext(actives)
267 n = picknext(actives)
258 actives.remove(n)
268 actives.remove(n)
259 s.append(n)
269 s.append(n)
260
270
261 # Update dependents list
271 # Update dependents list
262 for c in children.get(n, []):
272 for c in children.get(n, []):
263 if c not in pendings:
273 if c not in pendings:
264 pendings[c] = [p for p in parents[c] if p not in self.map]
274 pendings[c] = [p for p in parents[c] if p not in self.map]
265 try:
275 try:
266 pendings[c].remove(n)
276 pendings[c].remove(n)
267 except ValueError:
277 except ValueError:
268 raise util.Abort(_('cycle detected between %s and %s')
278 raise util.Abort(_('cycle detected between %s and %s')
269 % (recode(c), recode(n)))
279 % (recode(c), recode(n)))
270 if not pendings[c]:
280 if not pendings[c]:
271 # Parents are converted, node is eligible
281 # Parents are converted, node is eligible
272 actives.insert(0, c)
282 actives.insert(0, c)
273 pendings[c] = None
283 pendings[c] = None
274
284
275 if len(s) != len(parents):
285 if len(s) != len(parents):
276 raise util.Abort(_("not all revisions were sorted"))
286 raise util.Abort(_("not all revisions were sorted"))
277
287
278 return s
288 return s
279
289
280 def writeauthormap(self):
290 def writeauthormap(self):
281 authorfile = self.authorfile
291 authorfile = self.authorfile
282 if authorfile:
292 if authorfile:
283 self.ui.status(_('writing author map file %s\n') % authorfile)
293 self.ui.status(_('writing author map file %s\n') % authorfile)
284 ofile = open(authorfile, 'w+')
294 ofile = open(authorfile, 'w+')
285 for author in self.authors:
295 for author in self.authors:
286 ofile.write("%s=%s\n" % (author, self.authors[author]))
296 ofile.write("%s=%s\n" % (author, self.authors[author]))
287 ofile.close()
297 ofile.close()
288
298
289 def readauthormap(self, authorfile):
299 def readauthormap(self, authorfile):
290 afile = open(authorfile, 'r')
300 afile = open(authorfile, 'r')
291 for line in afile:
301 for line in afile:
292
302
293 line = line.strip()
303 line = line.strip()
294 if not line or line.startswith('#'):
304 if not line or line.startswith('#'):
295 continue
305 continue
296
306
297 try:
307 try:
298 srcauthor, dstauthor = line.split('=', 1)
308 srcauthor, dstauthor = line.split('=', 1)
299 except ValueError:
309 except ValueError:
300 msg = _('ignoring bad line in author map file %s: %s\n')
310 msg = _('ignoring bad line in author map file %s: %s\n')
301 self.ui.warn(msg % (authorfile, line.rstrip()))
311 self.ui.warn(msg % (authorfile, line.rstrip()))
302 continue
312 continue
303
313
304 srcauthor = srcauthor.strip()
314 srcauthor = srcauthor.strip()
305 dstauthor = dstauthor.strip()
315 dstauthor = dstauthor.strip()
306 if self.authors.get(srcauthor) in (None, dstauthor):
316 if self.authors.get(srcauthor) in (None, dstauthor):
307 msg = _('mapping author %s to %s\n')
317 msg = _('mapping author %s to %s\n')
308 self.ui.debug(msg % (srcauthor, dstauthor))
318 self.ui.debug(msg % (srcauthor, dstauthor))
309 self.authors[srcauthor] = dstauthor
319 self.authors[srcauthor] = dstauthor
310 continue
320 continue
311
321
312 m = _('overriding mapping for author %s, was %s, will be %s\n')
322 m = _('overriding mapping for author %s, was %s, will be %s\n')
313 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
323 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
314
324
315 afile.close()
325 afile.close()
316
326
317 def cachecommit(self, rev):
327 def cachecommit(self, rev):
318 commit = self.source.getcommit(rev)
328 commit = self.source.getcommit(rev)
319 commit.author = self.authors.get(commit.author, commit.author)
329 commit.author = self.authors.get(commit.author, commit.author)
320 commit.branch = self.branchmap.get(commit.branch, commit.branch)
330 commit.branch = self.branchmap.get(commit.branch, commit.branch)
321 self.commitcache[rev] = commit
331 self.commitcache[rev] = commit
322 return commit
332 return commit
323
333
324 def copy(self, rev):
334 def copy(self, rev):
325 commit = self.commitcache[rev]
335 commit = self.commitcache[rev]
326
336
327 changes = self.source.getchanges(rev)
337 changes = self.source.getchanges(rev)
328 if isinstance(changes, basestring):
338 if isinstance(changes, basestring):
329 if changes == SKIPREV:
339 if changes == SKIPREV:
330 dest = SKIPREV
340 dest = SKIPREV
331 else:
341 else:
332 dest = self.map[changes]
342 dest = self.map[changes]
333 self.map[rev] = dest
343 self.map[rev] = dest
334 return
344 return
335 files, copies = changes
345 files, copies = changes
336 pbranches = []
346 pbranches = []
337 if commit.parents:
347 if commit.parents:
338 for prev in commit.parents:
348 for prev in commit.parents:
339 if prev not in self.commitcache:
349 if prev not in self.commitcache:
340 self.cachecommit(prev)
350 self.cachecommit(prev)
341 pbranches.append((self.map[prev],
351 pbranches.append((self.map[prev],
342 self.commitcache[prev].branch))
352 self.commitcache[prev].branch))
343 self.dest.setbranch(commit.branch, pbranches)
353 self.dest.setbranch(commit.branch, pbranches)
344 try:
354 try:
345 parents = self.splicemap[rev]
355 parents = self.splicemap[rev]
346 self.ui.status(_('spliced in %s as parents of %s\n') %
356 self.ui.status(_('spliced in %s as parents of %s\n') %
347 (parents, rev))
357 (parents, rev))
348 parents = [self.map.get(p, p) for p in parents]
358 parents = [self.map.get(p, p) for p in parents]
349 except KeyError:
359 except KeyError:
350 parents = [b[0] for b in pbranches]
360 parents = [b[0] for b in pbranches]
351 source = progresssource(self.ui, self.source, len(files))
361 source = progresssource(self.ui, self.source, len(files))
352 newnode = self.dest.putcommit(files, copies, parents, commit,
362 newnode = self.dest.putcommit(files, copies, parents, commit,
353 source, self.map)
363 source, self.map)
354 source.close()
364 source.close()
355 self.source.converted(rev, newnode)
365 self.source.converted(rev, newnode)
356 self.map[rev] = newnode
366 self.map[rev] = newnode
357
367
358 def convert(self, sortmode):
368 def convert(self, sortmode):
359 try:
369 try:
360 self.source.before()
370 self.source.before()
361 self.dest.before()
371 self.dest.before()
362 self.source.setrevmap(self.map)
372 self.source.setrevmap(self.map)
363 self.ui.status(_("scanning source...\n"))
373 self.ui.status(_("scanning source...\n"))
364 heads = self.source.getheads()
374 heads = self.source.getheads()
365 parents = self.walktree(heads)
375 parents = self.walktree(heads)
366 self.mergesplicemap(parents, self.splicemap)
376 self.mergesplicemap(parents, self.splicemap)
367 self.ui.status(_("sorting...\n"))
377 self.ui.status(_("sorting...\n"))
368 t = self.toposort(parents, sortmode)
378 t = self.toposort(parents, sortmode)
369 num = len(t)
379 num = len(t)
370 c = None
380 c = None
371
381
372 self.ui.status(_("converting...\n"))
382 self.ui.status(_("converting...\n"))
373 for i, c in enumerate(t):
383 for i, c in enumerate(t):
374 num -= 1
384 num -= 1
375 desc = self.commitcache[c].desc
385 desc = self.commitcache[c].desc
376 if "\n" in desc:
386 if "\n" in desc:
377 desc = desc.splitlines()[0]
387 desc = desc.splitlines()[0]
378 # convert log message to local encoding without using
388 # convert log message to local encoding without using
379 # tolocal() because the encoding.encoding convert()
389 # tolocal() because the encoding.encoding convert()
380 # uses is 'utf-8'
390 # uses is 'utf-8'
381 self.ui.status("%d %s\n" % (num, recode(desc)))
391 self.ui.status("%d %s\n" % (num, recode(desc)))
382 self.ui.note(_("source: %s\n") % recode(c))
392 self.ui.note(_("source: %s\n") % recode(c))
383 self.ui.progress(_('converting'), i, unit=_('revisions'),
393 self.ui.progress(_('converting'), i, unit=_('revisions'),
384 total=len(t))
394 total=len(t))
385 self.copy(c)
395 self.copy(c)
386 self.ui.progress(_('converting'), None)
396 self.ui.progress(_('converting'), None)
387
397
388 tags = self.source.gettags()
398 tags = self.source.gettags()
389 ctags = {}
399 ctags = {}
390 for k in tags:
400 for k in tags:
391 v = tags[k]
401 v = tags[k]
392 if self.map.get(v, SKIPREV) != SKIPREV:
402 if self.map.get(v, SKIPREV) != SKIPREV:
393 ctags[k] = self.map[v]
403 ctags[k] = self.map[v]
394
404
395 if c and ctags:
405 if c and ctags:
396 nrev, tagsparent = self.dest.puttags(ctags)
406 nrev, tagsparent = self.dest.puttags(ctags)
397 if nrev and tagsparent:
407 if nrev and tagsparent:
398 # write another hash correspondence to override the previous
408 # write another hash correspondence to override the previous
399 # one so we don't end up with extra tag heads
409 # one so we don't end up with extra tag heads
400 tagsparents = [e for e in self.map.iteritems()
410 tagsparents = [e for e in self.map.iteritems()
401 if e[1] == tagsparent]
411 if e[1] == tagsparent]
402 if tagsparents:
412 if tagsparents:
403 self.map[tagsparents[0][0]] = nrev
413 self.map[tagsparents[0][0]] = nrev
404
414
405 bookmarks = self.source.getbookmarks()
415 bookmarks = self.source.getbookmarks()
406 cbookmarks = {}
416 cbookmarks = {}
407 for k in bookmarks:
417 for k in bookmarks:
408 v = bookmarks[k]
418 v = bookmarks[k]
409 if self.map.get(v, SKIPREV) != SKIPREV:
419 if self.map.get(v, SKIPREV) != SKIPREV:
410 cbookmarks[k] = self.map[v]
420 cbookmarks[k] = self.map[v]
411
421
412 if c and cbookmarks:
422 if c and cbookmarks:
413 self.dest.putbookmarks(cbookmarks)
423 self.dest.putbookmarks(cbookmarks)
414
424
415 self.writeauthormap()
425 self.writeauthormap()
416 finally:
426 finally:
417 self.cleanup()
427 self.cleanup()
418
428
419 def cleanup(self):
429 def cleanup(self):
420 try:
430 try:
421 self.dest.after()
431 self.dest.after()
422 finally:
432 finally:
423 self.source.after()
433 self.source.after()
424 self.map.close()
434 self.map.close()
425
435
426 def convert(ui, src, dest=None, revmapfile=None, **opts):
436 def convert(ui, src, dest=None, revmapfile=None, **opts):
427 global orig_encoding
437 global orig_encoding
428 orig_encoding = encoding.encoding
438 orig_encoding = encoding.encoding
429 encoding.encoding = 'UTF-8'
439 encoding.encoding = 'UTF-8'
430
440
431 # support --authors as an alias for --authormap
441 # support --authors as an alias for --authormap
432 if not opts.get('authormap'):
442 if not opts.get('authormap'):
433 opts['authormap'] = opts.get('authors')
443 opts['authormap'] = opts.get('authors')
434
444
435 if not dest:
445 if not dest:
436 dest = hg.defaultdest(src) + "-hg"
446 dest = hg.defaultdest(src) + "-hg"
437 ui.status(_("assuming destination %s\n") % dest)
447 ui.status(_("assuming destination %s\n") % dest)
438
448
439 destc = convertsink(ui, dest, opts.get('dest_type'))
449 destc = convertsink(ui, dest, opts.get('dest_type'))
440
450
441 try:
451 try:
442 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
452 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
443 opts.get('rev'))
453 opts.get('rev'))
444 except Exception:
454 except Exception:
445 for path in destc.created:
455 for path in destc.created:
446 shutil.rmtree(path, True)
456 shutil.rmtree(path, True)
447 raise
457 raise
448
458
449 sortmodes = ('branchsort', 'datesort', 'sourcesort')
459 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
450 sortmode = [m for m in sortmodes if opts.get(m)]
460 sortmode = [m for m in sortmodes if opts.get(m)]
451 if len(sortmode) > 1:
461 if len(sortmode) > 1:
452 raise util.Abort(_('more than one sort mode specified'))
462 raise util.Abort(_('more than one sort mode specified'))
453 sortmode = sortmode and sortmode[0] or defaultsort
463 sortmode = sortmode and sortmode[0] or defaultsort
454 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
464 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
455 raise util.Abort(_('--sourcesort is not supported by this data source'))
465 raise util.Abort(_('--sourcesort is not supported by this data source'))
466 if sortmode == 'closesort' and not srcc.hasnativeclose():
467 raise util.Abort(_('--closesort is not supported by this data source'))
456
468
457 fmap = opts.get('filemap')
469 fmap = opts.get('filemap')
458 if fmap:
470 if fmap:
459 srcc = filemap.filemap_source(ui, srcc, fmap)
471 srcc = filemap.filemap_source(ui, srcc, fmap)
460 destc.setfilemapmode(True)
472 destc.setfilemapmode(True)
461
473
462 if not revmapfile:
474 if not revmapfile:
463 try:
475 try:
464 revmapfile = destc.revmapfile()
476 revmapfile = destc.revmapfile()
465 except Exception:
477 except Exception:
466 revmapfile = os.path.join(destc, "map")
478 revmapfile = os.path.join(destc, "map")
467
479
468 c = converter(ui, srcc, destc, revmapfile, opts)
480 c = converter(ui, srcc, destc, revmapfile, opts)
469 c.convert(sortmode)
481 c.convert(sortmode)
470
482
@@ -1,396 +1,399 b''
1 # hg.py - hg backend for convert extension
1 # hg.py - hg backend for 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 # Notes for hg->hg conversion:
8 # Notes for hg->hg conversion:
9 #
9 #
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 # of commit messages, but new versions do. Changesets created by
11 # of commit messages, but new versions do. Changesets created by
12 # those older versions, then converted, may thus have different
12 # those older versions, then converted, may thus have different
13 # hashes for changesets that are otherwise identical.
13 # hashes for changesets that are otherwise identical.
14 #
14 #
15 # * Using "--config convert.hg.saverev=true" will make the source
15 # * Using "--config convert.hg.saverev=true" will make the source
16 # identifier to be stored in the converted revision. This will cause
16 # identifier to be stored in the converted revision. This will cause
17 # the converted revision to have a different identity than the
17 # the converted revision to have a different identity than the
18 # source.
18 # source.
19
19
20
20
21 import os, time, cStringIO
21 import os, time, cStringIO
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 from mercurial.node import bin, hex, nullid
23 from mercurial.node import bin, hex, nullid
24 from mercurial import hg, util, context, bookmarks, error
24 from mercurial import hg, util, context, bookmarks, error
25
25
26 from common import NoRepo, commit, converter_source, converter_sink
26 from common import NoRepo, commit, converter_source, converter_sink
27
27
28 class mercurial_sink(converter_sink):
28 class mercurial_sink(converter_sink):
29 def __init__(self, ui, path):
29 def __init__(self, ui, path):
30 converter_sink.__init__(self, ui, path)
30 converter_sink.__init__(self, ui, path)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 self.lastbranch = None
34 self.lastbranch = None
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 try:
36 try:
37 self.repo = hg.repository(self.ui, path)
37 self.repo = hg.repository(self.ui, path)
38 if not self.repo.local():
38 if not self.repo.local():
39 raise NoRepo(_('%s is not a local Mercurial repository')
39 raise NoRepo(_('%s is not a local Mercurial repository')
40 % path)
40 % path)
41 except error.RepoError, err:
41 except error.RepoError, err:
42 ui.traceback()
42 ui.traceback()
43 raise NoRepo(err.args[0])
43 raise NoRepo(err.args[0])
44 else:
44 else:
45 try:
45 try:
46 ui.status(_('initializing destination %s repository\n') % path)
46 ui.status(_('initializing destination %s repository\n') % path)
47 self.repo = hg.repository(self.ui, path, create=True)
47 self.repo = hg.repository(self.ui, path, create=True)
48 if not self.repo.local():
48 if not self.repo.local():
49 raise NoRepo(_('%s is not a local Mercurial repository')
49 raise NoRepo(_('%s is not a local Mercurial repository')
50 % path)
50 % path)
51 self.created.append(path)
51 self.created.append(path)
52 except error.RepoError:
52 except error.RepoError:
53 ui.traceback()
53 ui.traceback()
54 raise NoRepo(_("could not create hg repository %s as sink")
54 raise NoRepo(_("could not create hg repository %s as sink")
55 % path)
55 % path)
56 self.lock = None
56 self.lock = None
57 self.wlock = None
57 self.wlock = None
58 self.filemapmode = False
58 self.filemapmode = False
59
59
60 def before(self):
60 def before(self):
61 self.ui.debug('run hg sink pre-conversion action\n')
61 self.ui.debug('run hg sink pre-conversion action\n')
62 self.wlock = self.repo.wlock()
62 self.wlock = self.repo.wlock()
63 self.lock = self.repo.lock()
63 self.lock = self.repo.lock()
64
64
65 def after(self):
65 def after(self):
66 self.ui.debug('run hg sink post-conversion action\n')
66 self.ui.debug('run hg sink post-conversion action\n')
67 if self.lock:
67 if self.lock:
68 self.lock.release()
68 self.lock.release()
69 if self.wlock:
69 if self.wlock:
70 self.wlock.release()
70 self.wlock.release()
71
71
72 def revmapfile(self):
72 def revmapfile(self):
73 return self.repo.join("shamap")
73 return self.repo.join("shamap")
74
74
75 def authorfile(self):
75 def authorfile(self):
76 return self.repo.join("authormap")
76 return self.repo.join("authormap")
77
77
78 def getheads(self):
78 def getheads(self):
79 h = self.repo.changelog.heads()
79 h = self.repo.changelog.heads()
80 return [hex(x) for x in h]
80 return [hex(x) for x in h]
81
81
82 def setbranch(self, branch, pbranches):
82 def setbranch(self, branch, pbranches):
83 if not self.clonebranches:
83 if not self.clonebranches:
84 return
84 return
85
85
86 setbranch = (branch != self.lastbranch)
86 setbranch = (branch != self.lastbranch)
87 self.lastbranch = branch
87 self.lastbranch = branch
88 if not branch:
88 if not branch:
89 branch = 'default'
89 branch = 'default'
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 pbranch = pbranches and pbranches[0][1] or 'default'
91 pbranch = pbranches and pbranches[0][1] or 'default'
92
92
93 branchpath = os.path.join(self.path, branch)
93 branchpath = os.path.join(self.path, branch)
94 if setbranch:
94 if setbranch:
95 self.after()
95 self.after()
96 try:
96 try:
97 self.repo = hg.repository(self.ui, branchpath)
97 self.repo = hg.repository(self.ui, branchpath)
98 except Exception:
98 except Exception:
99 self.repo = hg.repository(self.ui, branchpath, create=True)
99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 self.before()
100 self.before()
101
101
102 # pbranches may bring revisions from other branches (merge parents)
102 # pbranches may bring revisions from other branches (merge parents)
103 # Make sure we have them, or pull them.
103 # Make sure we have them, or pull them.
104 missings = {}
104 missings = {}
105 for b in pbranches:
105 for b in pbranches:
106 try:
106 try:
107 self.repo.lookup(b[0])
107 self.repo.lookup(b[0])
108 except Exception:
108 except Exception:
109 missings.setdefault(b[1], []).append(b[0])
109 missings.setdefault(b[1], []).append(b[0])
110
110
111 if missings:
111 if missings:
112 self.after()
112 self.after()
113 for pbranch, heads in sorted(missings.iteritems()):
113 for pbranch, heads in sorted(missings.iteritems()):
114 pbranchpath = os.path.join(self.path, pbranch)
114 pbranchpath = os.path.join(self.path, pbranch)
115 prepo = hg.peer(self.ui, {}, pbranchpath)
115 prepo = hg.peer(self.ui, {}, pbranchpath)
116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
118 self.before()
118 self.before()
119
119
120 def _rewritetags(self, source, revmap, data):
120 def _rewritetags(self, source, revmap, data):
121 fp = cStringIO.StringIO()
121 fp = cStringIO.StringIO()
122 for line in data.splitlines():
122 for line in data.splitlines():
123 s = line.split(' ', 1)
123 s = line.split(' ', 1)
124 if len(s) != 2:
124 if len(s) != 2:
125 continue
125 continue
126 revid = revmap.get(source.lookuprev(s[0]))
126 revid = revmap.get(source.lookuprev(s[0]))
127 if not revid:
127 if not revid:
128 continue
128 continue
129 fp.write('%s %s\n' % (revid, s[1]))
129 fp.write('%s %s\n' % (revid, s[1]))
130 return fp.getvalue()
130 return fp.getvalue()
131
131
132 def putcommit(self, files, copies, parents, commit, source, revmap):
132 def putcommit(self, files, copies, parents, commit, source, revmap):
133
133
134 files = dict(files)
134 files = dict(files)
135 def getfilectx(repo, memctx, f):
135 def getfilectx(repo, memctx, f):
136 v = files[f]
136 v = files[f]
137 data, mode = source.getfile(f, v)
137 data, mode = source.getfile(f, v)
138 if f == '.hgtags':
138 if f == '.hgtags':
139 data = self._rewritetags(source, revmap, data)
139 data = self._rewritetags(source, revmap, data)
140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
141 copies.get(f))
141 copies.get(f))
142
142
143 pl = []
143 pl = []
144 for p in parents:
144 for p in parents:
145 if p not in pl:
145 if p not in pl:
146 pl.append(p)
146 pl.append(p)
147 parents = pl
147 parents = pl
148 nparents = len(parents)
148 nparents = len(parents)
149 if self.filemapmode and nparents == 1:
149 if self.filemapmode and nparents == 1:
150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
151 parent = parents[0]
151 parent = parents[0]
152
152
153 if len(parents) < 2:
153 if len(parents) < 2:
154 parents.append(nullid)
154 parents.append(nullid)
155 if len(parents) < 2:
155 if len(parents) < 2:
156 parents.append(nullid)
156 parents.append(nullid)
157 p2 = parents.pop(0)
157 p2 = parents.pop(0)
158
158
159 text = commit.desc
159 text = commit.desc
160 extra = commit.extra.copy()
160 extra = commit.extra.copy()
161 if self.branchnames and commit.branch:
161 if self.branchnames and commit.branch:
162 extra['branch'] = commit.branch
162 extra['branch'] = commit.branch
163 if commit.rev:
163 if commit.rev:
164 extra['convert_revision'] = commit.rev
164 extra['convert_revision'] = commit.rev
165
165
166 while parents:
166 while parents:
167 p1 = p2
167 p1 = p2
168 p2 = parents.pop(0)
168 p2 = parents.pop(0)
169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
170 getfilectx, commit.author, commit.date, extra)
170 getfilectx, commit.author, commit.date, extra)
171 self.repo.commitctx(ctx)
171 self.repo.commitctx(ctx)
172 text = "(octopus merge fixup)\n"
172 text = "(octopus merge fixup)\n"
173 p2 = hex(self.repo.changelog.tip())
173 p2 = hex(self.repo.changelog.tip())
174
174
175 if self.filemapmode and nparents == 1:
175 if self.filemapmode and nparents == 1:
176 man = self.repo.manifest
176 man = self.repo.manifest
177 mnode = self.repo.changelog.read(bin(p2))[0]
177 mnode = self.repo.changelog.read(bin(p2))[0]
178 closed = 'close' in commit.extra
178 closed = 'close' in commit.extra
179 if not closed and not man.cmp(m1node, man.revision(mnode)):
179 if not closed and not man.cmp(m1node, man.revision(mnode)):
180 self.ui.status(_("filtering out empty revision\n"))
180 self.ui.status(_("filtering out empty revision\n"))
181 self.repo.rollback(force=True)
181 self.repo.rollback(force=True)
182 return parent
182 return parent
183 return p2
183 return p2
184
184
185 def puttags(self, tags):
185 def puttags(self, tags):
186 try:
186 try:
187 parentctx = self.repo[self.tagsbranch]
187 parentctx = self.repo[self.tagsbranch]
188 tagparent = parentctx.node()
188 tagparent = parentctx.node()
189 except error.RepoError:
189 except error.RepoError:
190 parentctx = None
190 parentctx = None
191 tagparent = nullid
191 tagparent = nullid
192
192
193 try:
193 try:
194 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
194 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
195 except Exception:
195 except Exception:
196 oldlines = []
196 oldlines = []
197
197
198 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
198 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
199 if newlines == oldlines:
199 if newlines == oldlines:
200 return None, None
200 return None, None
201 data = "".join(newlines)
201 data = "".join(newlines)
202 def getfilectx(repo, memctx, f):
202 def getfilectx(repo, memctx, f):
203 return context.memfilectx(f, data, False, False, None)
203 return context.memfilectx(f, data, False, False, None)
204
204
205 self.ui.status(_("updating tags\n"))
205 self.ui.status(_("updating tags\n"))
206 date = "%s 0" % int(time.mktime(time.gmtime()))
206 date = "%s 0" % int(time.mktime(time.gmtime()))
207 extra = {'branch': self.tagsbranch}
207 extra = {'branch': self.tagsbranch}
208 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
208 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
209 [".hgtags"], getfilectx, "convert-repo", date,
209 [".hgtags"], getfilectx, "convert-repo", date,
210 extra)
210 extra)
211 self.repo.commitctx(ctx)
211 self.repo.commitctx(ctx)
212 return hex(self.repo.changelog.tip()), hex(tagparent)
212 return hex(self.repo.changelog.tip()), hex(tagparent)
213
213
214 def setfilemapmode(self, active):
214 def setfilemapmode(self, active):
215 self.filemapmode = active
215 self.filemapmode = active
216
216
217 def putbookmarks(self, updatedbookmark):
217 def putbookmarks(self, updatedbookmark):
218 if not len(updatedbookmark):
218 if not len(updatedbookmark):
219 return
219 return
220
220
221 self.ui.status(_("updating bookmarks\n"))
221 self.ui.status(_("updating bookmarks\n"))
222 destmarks = self.repo._bookmarks
222 destmarks = self.repo._bookmarks
223 for bookmark in updatedbookmark:
223 for bookmark in updatedbookmark:
224 destmarks[bookmark] = bin(updatedbookmark[bookmark])
224 destmarks[bookmark] = bin(updatedbookmark[bookmark])
225 destmarks.write()
225 destmarks.write()
226
226
227 def hascommit(self, rev):
227 def hascommit(self, rev):
228 if rev not in self.repo and self.clonebranches:
228 if rev not in self.repo and self.clonebranches:
229 raise util.Abort(_('revision %s not found in destination '
229 raise util.Abort(_('revision %s not found in destination '
230 'repository (lookups with clonebranches=true '
230 'repository (lookups with clonebranches=true '
231 'are not implemented)') % rev)
231 'are not implemented)') % rev)
232 return rev in self.repo
232 return rev in self.repo
233
233
234 class mercurial_source(converter_source):
234 class mercurial_source(converter_source):
235 def __init__(self, ui, path, rev=None):
235 def __init__(self, ui, path, rev=None):
236 converter_source.__init__(self, ui, path, rev)
236 converter_source.__init__(self, ui, path, rev)
237 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
237 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
238 self.ignored = set()
238 self.ignored = set()
239 self.saverev = ui.configbool('convert', 'hg.saverev', False)
239 self.saverev = ui.configbool('convert', 'hg.saverev', False)
240 try:
240 try:
241 self.repo = hg.repository(self.ui, path)
241 self.repo = hg.repository(self.ui, path)
242 # try to provoke an exception if this isn't really a hg
242 # try to provoke an exception if this isn't really a hg
243 # repo, but some other bogus compatible-looking url
243 # repo, but some other bogus compatible-looking url
244 if not self.repo.local():
244 if not self.repo.local():
245 raise error.RepoError
245 raise error.RepoError
246 except error.RepoError:
246 except error.RepoError:
247 ui.traceback()
247 ui.traceback()
248 raise NoRepo(_("%s is not a local Mercurial repository") % path)
248 raise NoRepo(_("%s is not a local Mercurial repository") % path)
249 self.lastrev = None
249 self.lastrev = None
250 self.lastctx = None
250 self.lastctx = None
251 self._changescache = None
251 self._changescache = None
252 self.convertfp = None
252 self.convertfp = None
253 # Restrict converted revisions to startrev descendants
253 # Restrict converted revisions to startrev descendants
254 startnode = ui.config('convert', 'hg.startrev')
254 startnode = ui.config('convert', 'hg.startrev')
255 if startnode is not None:
255 if startnode is not None:
256 try:
256 try:
257 startnode = self.repo.lookup(startnode)
257 startnode = self.repo.lookup(startnode)
258 except error.RepoError:
258 except error.RepoError:
259 raise util.Abort(_('%s is not a valid start revision')
259 raise util.Abort(_('%s is not a valid start revision')
260 % startnode)
260 % startnode)
261 startrev = self.repo.changelog.rev(startnode)
261 startrev = self.repo.changelog.rev(startnode)
262 children = {startnode: 1}
262 children = {startnode: 1}
263 for rev in self.repo.changelog.descendants([startrev]):
263 for rev in self.repo.changelog.descendants([startrev]):
264 children[self.repo.changelog.node(rev)] = 1
264 children[self.repo.changelog.node(rev)] = 1
265 self.keep = children.__contains__
265 self.keep = children.__contains__
266 else:
266 else:
267 self.keep = util.always
267 self.keep = util.always
268
268
269 def changectx(self, rev):
269 def changectx(self, rev):
270 if self.lastrev != rev:
270 if self.lastrev != rev:
271 self.lastctx = self.repo[rev]
271 self.lastctx = self.repo[rev]
272 self.lastrev = rev
272 self.lastrev = rev
273 return self.lastctx
273 return self.lastctx
274
274
275 def parents(self, ctx):
275 def parents(self, ctx):
276 return [p for p in ctx.parents() if p and self.keep(p.node())]
276 return [p for p in ctx.parents() if p and self.keep(p.node())]
277
277
278 def getheads(self):
278 def getheads(self):
279 if self.rev:
279 if self.rev:
280 heads = [self.repo[self.rev].node()]
280 heads = [self.repo[self.rev].node()]
281 else:
281 else:
282 heads = self.repo.heads()
282 heads = self.repo.heads()
283 return [hex(h) for h in heads if self.keep(h)]
283 return [hex(h) for h in heads if self.keep(h)]
284
284
285 def getfile(self, name, rev):
285 def getfile(self, name, rev):
286 try:
286 try:
287 fctx = self.changectx(rev)[name]
287 fctx = self.changectx(rev)[name]
288 return fctx.data(), fctx.flags()
288 return fctx.data(), fctx.flags()
289 except error.LookupError, err:
289 except error.LookupError, err:
290 raise IOError(err)
290 raise IOError(err)
291
291
292 def getchanges(self, rev):
292 def getchanges(self, rev):
293 ctx = self.changectx(rev)
293 ctx = self.changectx(rev)
294 parents = self.parents(ctx)
294 parents = self.parents(ctx)
295 if not parents:
295 if not parents:
296 files = sorted(ctx.manifest())
296 files = sorted(ctx.manifest())
297 # getcopies() is not needed for roots, but it is a simple way to
297 # getcopies() is not needed for roots, but it is a simple way to
298 # detect missing revlogs and abort on errors or populate
298 # detect missing revlogs and abort on errors or populate
299 # self.ignored
299 # self.ignored
300 self.getcopies(ctx, parents, files)
300 self.getcopies(ctx, parents, files)
301 return [(f, rev) for f in files if f not in self.ignored], {}
301 return [(f, rev) for f in files if f not in self.ignored], {}
302 if self._changescache and self._changescache[0] == rev:
302 if self._changescache and self._changescache[0] == rev:
303 m, a, r = self._changescache[1]
303 m, a, r = self._changescache[1]
304 else:
304 else:
305 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
305 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
306 # getcopies() detects missing revlogs early, run it before
306 # getcopies() detects missing revlogs early, run it before
307 # filtering the changes.
307 # filtering the changes.
308 copies = self.getcopies(ctx, parents, m + a)
308 copies = self.getcopies(ctx, parents, m + a)
309 changes = [(name, rev) for name in m + a + r
309 changes = [(name, rev) for name in m + a + r
310 if name not in self.ignored]
310 if name not in self.ignored]
311 return sorted(changes), copies
311 return sorted(changes), copies
312
312
313 def getcopies(self, ctx, parents, files):
313 def getcopies(self, ctx, parents, files):
314 copies = {}
314 copies = {}
315 for name in files:
315 for name in files:
316 if name in self.ignored:
316 if name in self.ignored:
317 continue
317 continue
318 try:
318 try:
319 copysource, copynode = ctx.filectx(name).renamed()
319 copysource, copynode = ctx.filectx(name).renamed()
320 if copysource in self.ignored or not self.keep(copynode):
320 if copysource in self.ignored or not self.keep(copynode):
321 continue
321 continue
322 # Ignore copy sources not in parent revisions
322 # Ignore copy sources not in parent revisions
323 found = False
323 found = False
324 for p in parents:
324 for p in parents:
325 if copysource in p:
325 if copysource in p:
326 found = True
326 found = True
327 break
327 break
328 if not found:
328 if not found:
329 continue
329 continue
330 copies[name] = copysource
330 copies[name] = copysource
331 except TypeError:
331 except TypeError:
332 pass
332 pass
333 except error.LookupError, e:
333 except error.LookupError, e:
334 if not self.ignoreerrors:
334 if not self.ignoreerrors:
335 raise
335 raise
336 self.ignored.add(name)
336 self.ignored.add(name)
337 self.ui.warn(_('ignoring: %s\n') % e)
337 self.ui.warn(_('ignoring: %s\n') % e)
338 return copies
338 return copies
339
339
340 def getcommit(self, rev):
340 def getcommit(self, rev):
341 ctx = self.changectx(rev)
341 ctx = self.changectx(rev)
342 parents = [p.hex() for p in self.parents(ctx)]
342 parents = [p.hex() for p in self.parents(ctx)]
343 if self.saverev:
343 if self.saverev:
344 crev = rev
344 crev = rev
345 else:
345 else:
346 crev = None
346 crev = None
347 return commit(author=ctx.user(),
347 return commit(author=ctx.user(),
348 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
348 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
349 desc=ctx.description(), rev=crev, parents=parents,
349 desc=ctx.description(), rev=crev, parents=parents,
350 branch=ctx.branch(), extra=ctx.extra(),
350 branch=ctx.branch(), extra=ctx.extra(),
351 sortkey=ctx.rev())
351 sortkey=ctx.rev())
352
352
353 def gettags(self):
353 def gettags(self):
354 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
354 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
355 return dict([(name, hex(node)) for name, node in tags
355 return dict([(name, hex(node)) for name, node in tags
356 if self.keep(node)])
356 if self.keep(node)])
357
357
358 def getchangedfiles(self, rev, i):
358 def getchangedfiles(self, rev, i):
359 ctx = self.changectx(rev)
359 ctx = self.changectx(rev)
360 parents = self.parents(ctx)
360 parents = self.parents(ctx)
361 if not parents and i is None:
361 if not parents and i is None:
362 i = 0
362 i = 0
363 changes = [], ctx.manifest().keys(), []
363 changes = [], ctx.manifest().keys(), []
364 else:
364 else:
365 i = i or 0
365 i = i or 0
366 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
366 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
367 changes = [[f for f in l if f not in self.ignored] for l in changes]
367 changes = [[f for f in l if f not in self.ignored] for l in changes]
368
368
369 if i == 0:
369 if i == 0:
370 self._changescache = (rev, changes)
370 self._changescache = (rev, changes)
371
371
372 return changes[0] + changes[1] + changes[2]
372 return changes[0] + changes[1] + changes[2]
373
373
374 def converted(self, rev, destrev):
374 def converted(self, rev, destrev):
375 if self.convertfp is None:
375 if self.convertfp is None:
376 self.convertfp = open(self.repo.join('shamap'), 'a')
376 self.convertfp = open(self.repo.join('shamap'), 'a')
377 self.convertfp.write('%s %s\n' % (destrev, rev))
377 self.convertfp.write('%s %s\n' % (destrev, rev))
378 self.convertfp.flush()
378 self.convertfp.flush()
379
379
380 def before(self):
380 def before(self):
381 self.ui.debug('run hg source pre-conversion action\n')
381 self.ui.debug('run hg source pre-conversion action\n')
382
382
383 def after(self):
383 def after(self):
384 self.ui.debug('run hg source post-conversion action\n')
384 self.ui.debug('run hg source post-conversion action\n')
385
385
386 def hasnativeorder(self):
386 def hasnativeorder(self):
387 return True
387 return True
388
388
389 def hasnativeclose(self):
390 return True
391
389 def lookuprev(self, rev):
392 def lookuprev(self, rev):
390 try:
393 try:
391 return hex(self.repo.lookup(rev))
394 return hex(self.repo.lookup(rev))
392 except error.RepoError:
395 except error.RepoError:
393 return None
396 return None
394
397
395 def getbookmarks(self):
398 def getbookmarks(self):
396 return bookmarks.listbookmarks(self.repo)
399 return bookmarks.listbookmarks(self.repo)
@@ -1,119 +1,214 b''
1
1
2 $ cat >> $HGRCPATH <<EOF
2 $ cat >> $HGRCPATH <<EOF
3 > [extensions]
3 > [extensions]
4 > convert=
4 > convert=
5 > graphlog=
5 > graphlog=
6 > EOF
6 > EOF
7 $ hg init t
7 $ hg init t
8 $ cd t
8 $ cd t
9 $ echo a >> a
9 $ echo a >> a
10 $ hg ci -Am a0 -d '1 0'
10 $ hg ci -Am a0 -d '1 0'
11 adding a
11 adding a
12 $ hg branch brancha
12 $ hg branch brancha
13 marked working directory as branch brancha
13 marked working directory as branch brancha
14 (branches are permanent and global, did you want a bookmark?)
14 (branches are permanent and global, did you want a bookmark?)
15 $ echo a >> a
15 $ echo a >> a
16 $ hg ci -m a1 -d '2 0'
16 $ hg ci -m a1 -d '2 0'
17 $ echo a >> a
17 $ echo a >> a
18 $ hg ci -m a2 -d '3 0'
18 $ hg ci -m a2 -d '3 0'
19 $ echo a >> a
19 $ echo a >> a
20 $ hg ci -m a3 -d '4 0'
20 $ hg ci -m a3 -d '4 0'
21 $ hg up -C 0
21 $ hg up -C 0
22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
22 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
23 $ hg branch branchb
23 $ hg branch branchb
24 marked working directory as branch branchb
24 marked working directory as branch branchb
25 (branches are permanent and global, did you want a bookmark?)
25 (branches are permanent and global, did you want a bookmark?)
26 $ echo b >> b
26 $ echo b >> b
27 $ hg ci -Am b0 -d '6 0'
27 $ hg ci -Am b0 -d '6 0'
28 adding b
28 adding b
29 $ hg up -C brancha
29 $ hg up -C brancha
30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
31 $ echo a >> a
31 $ echo a >> a
32 $ hg ci -m a4 -d '5 0'
32 $ hg ci -m a4 -d '5 0'
33 $ echo a >> a
33 $ echo a >> a
34 $ hg ci -m a5 -d '7 0'
34 $ hg ci -m a5 -d '7 0'
35 $ echo a >> a
35 $ echo a >> a
36 $ hg ci -m a6 -d '8 0'
36 $ hg ci -m a6 -d '8 0'
37 $ hg up -C branchb
37 $ hg up -C branchb
38 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 $ echo b >> b
39 $ echo b >> b
40 $ hg ci -m b1 -d '9 0'
40 $ hg ci -m b1 -d '9 0'
41 $ hg up -C 0
42 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
43 $ echo c >> c
44 $ hg branch branchc
45 marked working directory as branch branchc
46 (branches are permanent and global, did you want a bookmark?)
47 $ hg ci -Am c0 -d '10 0'
48 adding c
49 $ hg up -C brancha
50 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
51 $ hg ci --close-branch -m a7x -d '11 0'
52 $ hg up -C branchb
53 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
54 $ hg ci --close-branch -m b2x -d '12 0'
55 $ hg up -C branchc
56 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
57 $ hg merge branchb
58 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 (branch merge, don't forget to commit)
60 $ hg ci -m c1 -d '13 0'
41 $ cd ..
61 $ cd ..
42
62
43 convert with datesort
63 convert with datesort
44
64
45 $ hg convert --datesort t t-datesort
65 $ hg convert --datesort t t-datesort
46 initializing destination t-datesort repository
66 initializing destination t-datesort repository
47 scanning source...
67 scanning source...
48 sorting...
68 sorting...
49 converting...
69 converting...
50 8 a0
70 12 a0
51 7 a1
71 11 a1
52 6 a2
72 10 a2
53 5 a3
73 9 a3
54 4 a4
74 8 a4
55 3 b0
75 7 b0
56 2 a5
76 6 a5
57 1 a6
77 5 a6
58 0 b1
78 4 b1
79 3 c0
80 2 a7x
81 1 b2x
82 0 c1
59
83
60 graph converted repo
84 graph converted repo
61
85
62 $ hg -R t-datesort glog --template '{rev} "{desc}"\n'
86 $ hg -R t-datesort glog --template '{rev} "{desc}"\n'
63 o 8 "b1"
87 o 12 "c1"
64 |
88 |\
65 | o 7 "a6"
89 | o 11 "b2x"
66 | |
90 | |
67 | o 6 "a5"
91 | | o 10 "a7x"
68 | |
92 | | |
69 o | 5 "b0"
93 o | | 9 "c0"
70 | |
94 | | |
95 | o | 8 "b1"
96 | | |
97 | | o 7 "a6"
98 | | |
99 | | o 6 "a5"
100 | | |
101 | o | 5 "b0"
102 |/ /
71 | o 4 "a4"
103 | o 4 "a4"
72 | |
104 | |
73 | o 3 "a3"
105 | o 3 "a3"
74 | |
106 | |
75 | o 2 "a2"
107 | o 2 "a2"
76 | |
108 | |
77 | o 1 "a1"
109 | o 1 "a1"
78 |/
110 |/
79 o 0 "a0"
111 o 0 "a0"
80
112
81
113
82 convert with datesort (default mode)
114 convert with datesort (default mode)
83
115
84 $ hg convert t t-sourcesort
116 $ hg convert t t-sourcesort
85 initializing destination t-sourcesort repository
117 initializing destination t-sourcesort repository
86 scanning source...
118 scanning source...
87 sorting...
119 sorting...
88 converting...
120 converting...
89 8 a0
121 12 a0
90 7 a1
122 11 a1
91 6 a2
123 10 a2
92 5 a3
124 9 a3
93 4 b0
125 8 b0
94 3 a4
126 7 a4
95 2 a5
127 6 a5
96 1 a6
128 5 a6
97 0 b1
129 4 b1
130 3 c0
131 2 a7x
132 1 b2x
133 0 c1
98
134
99 graph converted repo
135 graph converted repo
100
136
101 $ hg -R t-sourcesort glog --template '{rev} "{desc}"\n'
137 $ hg -R t-sourcesort glog --template '{rev} "{desc}"\n'
102 o 8 "b1"
138 o 12 "c1"
103 |
139 |\
104 | o 7 "a6"
140 | o 11 "b2x"
105 | |
141 | |
106 | o 6 "a5"
142 | | o 10 "a7x"
107 | |
143 | | |
108 | o 5 "a4"
144 o | | 9 "c0"
109 | |
145 | | |
110 o | 4 "b0"
146 | o | 8 "b1"
111 | |
147 | | |
148 | | o 7 "a6"
149 | | |
150 | | o 6 "a5"
151 | | |
152 | | o 5 "a4"
153 | | |
154 | o | 4 "b0"
155 |/ /
112 | o 3 "a3"
156 | o 3 "a3"
113 | |
157 | |
114 | o 2 "a2"
158 | o 2 "a2"
115 | |
159 | |
116 | o 1 "a1"
160 | o 1 "a1"
117 |/
161 |/
118 o 0 "a0"
162 o 0 "a0"
119
163
164
165 convert with closesort
166
167 $ hg convert --closesort t t-closesort
168 initializing destination t-closesort repository
169 scanning source...
170 sorting...
171 converting...
172 12 a0
173 11 a1
174 10 a2
175 9 a3
176 8 b0
177 7 a4
178 6 a5
179 5 a6
180 4 a7x
181 3 b1
182 2 b2x
183 1 c0
184 0 c1
185
186 graph converted repo
187
188 $ hg -R t-closesort glog --template '{rev} "{desc}"\n'
189 o 12 "c1"
190 |\
191 | o 11 "c0"
192 | |
193 o | 10 "b2x"
194 | |
195 o | 9 "b1"
196 | |
197 | | o 8 "a7x"
198 | | |
199 | | o 7 "a6"
200 | | |
201 | | o 6 "a5"
202 | | |
203 | | o 5 "a4"
204 | | |
205 o | | 4 "b0"
206 |/ /
207 | o 3 "a3"
208 | |
209 | o 2 "a2"
210 | |
211 | o 1 "a1"
212 |/
213 o 0 "a0"
214
@@ -1,455 +1,458 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
50 branches, only supported by Mercurial sources.
49
51
50 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
51 ("<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
52 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
53 so:
55 so:
54
56
55 <source ID> <destination ID>
57 <source ID> <destination ID>
56
58
57 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
58 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
59 repeatedly to copy new commits.
61 repeatedly to copy new commits.
60
62
61 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
62 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
63 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
64 the line format is:
66 the line format is:
65
67
66 source author = destination author
68 source author = destination author
67
69
68 Empty lines and lines starting with a "#" are ignored.
70 Empty lines and lines starting with a "#" are ignored.
69
71
70 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
71 directories. Each line can contain one of the following directives:
73 directories. Each line can contain one of the following directives:
72
74
73 include path/to/file-or-dir
75 include path/to/file-or-dir
74
76
75 exclude path/to/file-or-dir
77 exclude path/to/file-or-dir
76
78
77 rename path/to/source path/to/destination
79 rename path/to/source path/to/destination
78
80
79 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
80 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
81 "include" or "exclude" directive with the longest matching path applies,
83 "include" or "exclude" directive with the longest matching path applies,
82 so line order does not matter.
84 so line order does not matter.
83
85
84 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
85 be included in the destination repository, and the exclusion of all other
87 be included in the destination repository, and the exclusion of all other
86 files and directories not explicitly included. The "exclude" directive
88 files and directories not explicitly included. The "exclude" directive
87 causes files or directories to be omitted. The "rename" directive renames
89 causes files or directories to be omitted. The "rename" directive renames
88 a file or directory if it is converted. To rename from a subdirectory into
90 a file or directory if it is converted. To rename from a subdirectory into
89 the root of the repository, use "." as the path to rename to.
91 the root of the repository, use "." as the path to rename to.
90
92
91 The splicemap is a file that allows insertion of synthetic history,
93 The splicemap is a file that allows insertion of synthetic history,
92 letting you specify the parents of a revision. This is useful if you want
94 letting you specify the parents of a revision. This is useful if you want
93 to e.g. give a Subversion merge two parents, or graft two disconnected
95 to e.g. give a Subversion merge two parents, or graft two disconnected
94 series of history together. Each entry contains a key, followed by a
96 series of history together. Each entry contains a key, followed by a
95 space, followed by one or two comma-separated values:
97 space, followed by one or two comma-separated values:
96
98
97 key parent1, parent2
99 key parent1, parent2
98
100
99 The key is the revision ID in the source revision control system whose
101 The key is the revision ID in the source revision control system whose
100 parents should be modified (same format as a key in .hg/shamap). The
102 parents should be modified (same format as a key in .hg/shamap). The
101 values are the revision IDs (in either the source or destination revision
103 values are the revision IDs (in either the source or destination revision
102 control system) that should be used as the new parents for that node. For
104 control system) that should be used as the new parents for that node. For
103 example, if you have merged "release-1.0" into "trunk", then you should
105 example, if you have merged "release-1.0" into "trunk", then you should
104 specify the revision on "trunk" as the first parent and the one on the
106 specify the revision on "trunk" as the first parent and the one on the
105 "release-1.0" branch as the second.
107 "release-1.0" branch as the second.
106
108
107 The branchmap is a file that allows you to rename a branch when it is
109 The branchmap is a file that allows you to rename a branch when it is
108 being brought in from whatever external repository. When used in
110 being brought in from whatever external repository. When used in
109 conjunction with a splicemap, it allows for a powerful combination to help
111 conjunction with a splicemap, it allows for a powerful combination to help
110 fix even the most badly mismanaged repositories and turn them into nicely
112 fix even the most badly mismanaged repositories and turn them into nicely
111 structured Mercurial repositories. The branchmap contains lines of the
113 structured Mercurial repositories. The branchmap contains lines of the
112 form:
114 form:
113
115
114 original_branch_name new_branch_name
116 original_branch_name new_branch_name
115
117
116 where "original_branch_name" is the name of the branch in the source
118 where "original_branch_name" is the name of the branch in the source
117 repository, and "new_branch_name" is the name of the branch is the
119 repository, and "new_branch_name" is the name of the branch is the
118 destination repository. No whitespace is allowed in the branch names. This
120 destination repository. No whitespace is allowed in the branch names. This
119 can be used to (for instance) move code in one repository from "default"
121 can be used to (for instance) move code in one repository from "default"
120 to a named branch.
122 to a named branch.
121
123
122 Mercurial Source
124 Mercurial Source
123 ################
125 ################
124
126
125 The Mercurial source recognizes the following configuration options, which
127 The Mercurial source recognizes the following configuration options, which
126 you can set on the command line with "--config":
128 you can set on the command line with "--config":
127
129
128 convert.hg.ignoreerrors
130 convert.hg.ignoreerrors
129 ignore integrity errors when reading. Use it to fix
131 ignore integrity errors when reading. Use it to fix
130 Mercurial repositories with missing revlogs, by converting
132 Mercurial repositories with missing revlogs, by converting
131 from and to Mercurial. Default is False.
133 from and to Mercurial. Default is False.
132 convert.hg.saverev
134 convert.hg.saverev
133 store original revision ID in changeset (forces target IDs
135 store original revision ID in changeset (forces target IDs
134 to change). It takes a boolean argument and defaults to
136 to change). It takes a boolean argument and defaults to
135 False.
137 False.
136 convert.hg.startrev
138 convert.hg.startrev
137 convert start revision and its descendants. It takes a hg
139 convert start revision and its descendants. It takes a hg
138 revision identifier and defaults to 0.
140 revision identifier and defaults to 0.
139
141
140 CVS Source
142 CVS Source
141 ##########
143 ##########
142
144
143 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
145 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
144 indicate the starting point of what will be converted. Direct access to
146 indicate the starting point of what will be converted. Direct access to
145 the repository files is not needed, unless of course the repository is
147 the repository files is not needed, unless of course the repository is
146 ":local:". The conversion uses the top level directory in the sandbox to
148 ":local:". The conversion uses the top level directory in the sandbox to
147 find the CVS repository, and then uses CVS rlog commands to find files to
149 find the CVS repository, and then uses CVS rlog commands to find files to
148 convert. This means that unless a filemap is given, all files under the
150 convert. This means that unless a filemap is given, all files under the
149 starting directory will be converted, and that any directory
151 starting directory will be converted, and that any directory
150 reorganization in the CVS sandbox is ignored.
152 reorganization in the CVS sandbox is ignored.
151
153
152 The following options can be used with "--config":
154 The following options can be used with "--config":
153
155
154 convert.cvsps.cache
156 convert.cvsps.cache
155 Set to False to disable remote log caching, for testing and
157 Set to False to disable remote log caching, for testing and
156 debugging purposes. Default is True.
158 debugging purposes. Default is True.
157 convert.cvsps.fuzz
159 convert.cvsps.fuzz
158 Specify the maximum time (in seconds) that is allowed
160 Specify the maximum time (in seconds) that is allowed
159 between commits with identical user and log message in a
161 between commits with identical user and log message in a
160 single changeset. When very large files were checked in as
162 single changeset. When very large files were checked in as
161 part of a changeset then the default may not be long enough.
163 part of a changeset then the default may not be long enough.
162 The default is 60.
164 The default is 60.
163 convert.cvsps.mergeto
165 convert.cvsps.mergeto
164 Specify a regular expression to which commit log messages
166 Specify a regular expression to which commit log messages
165 are matched. If a match occurs, then the conversion process
167 are matched. If a match occurs, then the conversion process
166 will insert a dummy revision merging the branch on which
168 will insert a dummy revision merging the branch on which
167 this log message occurs to the branch indicated in the
169 this log message occurs to the branch indicated in the
168 regex. Default is "{{mergetobranch ([-\w]+)}}"
170 regex. Default is "{{mergetobranch ([-\w]+)}}"
169 convert.cvsps.mergefrom
171 convert.cvsps.mergefrom
170 Specify a regular expression to which commit log messages
172 Specify a regular expression to which commit log messages
171 are matched. If a match occurs, then the conversion process
173 are matched. If a match occurs, then the conversion process
172 will add the most recent revision on the branch indicated in
174 will add the most recent revision on the branch indicated in
173 the regex as the second parent of the changeset. Default is
175 the regex as the second parent of the changeset. Default is
174 "{{mergefrombranch ([-\w]+)}}"
176 "{{mergefrombranch ([-\w]+)}}"
175 convert.localtimezone
177 convert.localtimezone
176 use local time (as determined by the TZ environment
178 use local time (as determined by the TZ environment
177 variable) for changeset date/times. The default is False
179 variable) for changeset date/times. The default is False
178 (use UTC).
180 (use UTC).
179 hooks.cvslog Specify a Python function to be called at the end of
181 hooks.cvslog Specify a Python function to be called at the end of
180 gathering the CVS log. The function is passed a list with
182 gathering the CVS log. The function is passed a list with
181 the log entries, and can modify the entries in-place, or add
183 the log entries, and can modify the entries in-place, or add
182 or delete them.
184 or delete them.
183 hooks.cvschangesets
185 hooks.cvschangesets
184 Specify a Python function to be called after the changesets
186 Specify a Python function to be called after the changesets
185 are calculated from the CVS log. The function is passed a
187 are calculated from the CVS log. The function is passed a
186 list with the changeset entries, and can modify the
188 list with the changeset entries, and can modify the
187 changesets in-place, or add or delete them.
189 changesets in-place, or add or delete them.
188
190
189 An additional "debugcvsps" Mercurial command allows the builtin changeset
191 An additional "debugcvsps" Mercurial command allows the builtin changeset
190 merging code to be run without doing a conversion. Its parameters and
192 merging code to be run without doing a conversion. Its parameters and
191 output are similar to that of cvsps 2.1. Please see the command help for
193 output are similar to that of cvsps 2.1. Please see the command help for
192 more details.
194 more details.
193
195
194 Subversion Source
196 Subversion Source
195 #################
197 #################
196
198
197 Subversion source detects classical trunk/branches/tags layouts. By
199 Subversion source detects classical trunk/branches/tags layouts. By
198 default, the supplied "svn://repo/path/" source URL is converted as a
200 default, the supplied "svn://repo/path/" source URL is converted as a
199 single branch. If "svn://repo/path/trunk" exists it replaces the default
201 single branch. If "svn://repo/path/trunk" exists it replaces the default
200 branch. If "svn://repo/path/branches" exists, its subdirectories are
202 branch. If "svn://repo/path/branches" exists, its subdirectories are
201 listed as possible branches. If "svn://repo/path/tags" exists, it is
203 listed as possible branches. If "svn://repo/path/tags" exists, it is
202 looked for tags referencing converted branches. Default "trunk",
204 looked for tags referencing converted branches. Default "trunk",
203 "branches" and "tags" values can be overridden with following options. Set
205 "branches" and "tags" values can be overridden with following options. Set
204 them to paths relative to the source URL, or leave them blank to disable
206 them to paths relative to the source URL, or leave them blank to disable
205 auto detection.
207 auto detection.
206
208
207 The following options can be set with "--config":
209 The following options can be set with "--config":
208
210
209 convert.svn.branches
211 convert.svn.branches
210 specify the directory containing branches. The default is
212 specify the directory containing branches. The default is
211 "branches".
213 "branches".
212 convert.svn.tags
214 convert.svn.tags
213 specify the directory containing tags. The default is
215 specify the directory containing tags. The default is
214 "tags".
216 "tags".
215 convert.svn.trunk
217 convert.svn.trunk
216 specify the name of the trunk branch. The default is
218 specify the name of the trunk branch. The default is
217 "trunk".
219 "trunk".
218 convert.localtimezone
220 convert.localtimezone
219 use local time (as determined by the TZ environment
221 use local time (as determined by the TZ environment
220 variable) for changeset date/times. The default is False
222 variable) for changeset date/times. The default is False
221 (use UTC).
223 (use UTC).
222
224
223 Source history can be retrieved starting at a specific revision, instead
225 Source history can be retrieved starting at a specific revision, instead
224 of being integrally converted. Only single branch conversions are
226 of being integrally converted. Only single branch conversions are
225 supported.
227 supported.
226
228
227 convert.svn.startrev
229 convert.svn.startrev
228 specify start Subversion revision number. The default is 0.
230 specify start Subversion revision number. The default is 0.
229
231
230 Perforce Source
232 Perforce Source
231 ###############
233 ###############
232
234
233 The Perforce (P4) importer can be given a p4 depot path or a client
235 The Perforce (P4) importer can be given a p4 depot path or a client
234 specification as source. It will convert all files in the source to a flat
236 specification as source. It will convert all files in the source to a flat
235 Mercurial repository, ignoring labels, branches and integrations. Note
237 Mercurial repository, ignoring labels, branches and integrations. Note
236 that when a depot path is given you then usually should specify a target
238 that when a depot path is given you then usually should specify a target
237 directory, because otherwise the target may be named "...-hg".
239 directory, because otherwise the target may be named "...-hg".
238
240
239 It is possible to limit the amount of source history to be converted by
241 It is possible to limit the amount of source history to be converted by
240 specifying an initial Perforce revision:
242 specifying an initial Perforce revision:
241
243
242 convert.p4.startrev
244 convert.p4.startrev
243 specify initial Perforce revision (a Perforce changelist
245 specify initial Perforce revision (a Perforce changelist
244 number).
246 number).
245
247
246 Mercurial Destination
248 Mercurial Destination
247 #####################
249 #####################
248
250
249 The following options are supported:
251 The following options are supported:
250
252
251 convert.hg.clonebranches
253 convert.hg.clonebranches
252 dispatch source branches in separate clones. The default is
254 dispatch source branches in separate clones. The default is
253 False.
255 False.
254 convert.hg.tagsbranch
256 convert.hg.tagsbranch
255 branch name for tag revisions, defaults to "default".
257 branch name for tag revisions, defaults to "default".
256 convert.hg.usebranchnames
258 convert.hg.usebranchnames
257 preserve branch names. The default is True.
259 preserve branch names. The default is True.
258
260
259 options:
261 options:
260
262
261 -s --source-type TYPE source repository type
263 -s --source-type TYPE source repository type
262 -d --dest-type TYPE destination repository type
264 -d --dest-type TYPE destination repository type
263 -r --rev REV import up to target revision REV
265 -r --rev REV import up to target revision REV
264 -A --authormap FILE remap usernames using this file
266 -A --authormap FILE remap usernames using this file
265 --filemap FILE remap file names using contents of file
267 --filemap FILE remap file names using contents of file
266 --splicemap FILE splice synthesized history into place
268 --splicemap FILE splice synthesized history into place
267 --branchmap FILE change branch names while converting
269 --branchmap FILE change branch names while converting
268 --branchsort try to sort changesets by branches
270 --branchsort try to sort changesets by branches
269 --datesort try to sort changesets by date
271 --datesort try to sort changesets by date
270 --sourcesort preserve source changesets order
272 --sourcesort preserve source changesets order
273 --closesort try to reorder closed revisions
271
274
272 use "hg -v help convert" to show the global options
275 use "hg -v help convert" to show the global options
273 $ hg init a
276 $ hg init a
274 $ cd a
277 $ cd a
275 $ echo a > a
278 $ echo a > a
276 $ hg ci -d'0 0' -Ama
279 $ hg ci -d'0 0' -Ama
277 adding a
280 adding a
278 $ hg cp a b
281 $ hg cp a b
279 $ hg ci -d'1 0' -mb
282 $ hg ci -d'1 0' -mb
280 $ hg rm a
283 $ hg rm a
281 $ hg ci -d'2 0' -mc
284 $ hg ci -d'2 0' -mc
282 $ hg mv b a
285 $ hg mv b a
283 $ hg ci -d'3 0' -md
286 $ hg ci -d'3 0' -md
284 $ echo a >> a
287 $ echo a >> a
285 $ hg ci -d'4 0' -me
288 $ hg ci -d'4 0' -me
286 $ cd ..
289 $ cd ..
287 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
290 $ hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
288 assuming destination a-hg
291 assuming destination a-hg
289 initializing destination a-hg repository
292 initializing destination a-hg repository
290 scanning source...
293 scanning source...
291 sorting...
294 sorting...
292 converting...
295 converting...
293 4 a
296 4 a
294 3 b
297 3 b
295 2 c
298 2 c
296 1 d
299 1 d
297 0 e
300 0 e
298 $ hg --cwd a-hg pull ../a
301 $ hg --cwd a-hg pull ../a
299 pulling from ../a
302 pulling from ../a
300 searching for changes
303 searching for changes
301 no changes found
304 no changes found
302
305
303 conversion to existing file should fail
306 conversion to existing file should fail
304
307
305 $ touch bogusfile
308 $ touch bogusfile
306 $ hg convert a bogusfile
309 $ hg convert a bogusfile
307 initializing destination bogusfile repository
310 initializing destination bogusfile repository
308 abort: cannot create new bundle repository
311 abort: cannot create new bundle repository
309 [255]
312 [255]
310
313
311 #if unix-permissions
314 #if unix-permissions
312
315
313 conversion to dir without permissions should fail
316 conversion to dir without permissions should fail
314
317
315 $ mkdir bogusdir
318 $ mkdir bogusdir
316 $ chmod 000 bogusdir
319 $ chmod 000 bogusdir
317
320
318 $ hg convert a bogusdir
321 $ hg convert a bogusdir
319 abort: Permission denied: 'bogusdir'
322 abort: Permission denied: 'bogusdir'
320 [255]
323 [255]
321
324
322 user permissions should succeed
325 user permissions should succeed
323
326
324 $ chmod 700 bogusdir
327 $ chmod 700 bogusdir
325 $ hg convert a bogusdir
328 $ hg convert a bogusdir
326 initializing destination bogusdir repository
329 initializing destination bogusdir repository
327 scanning source...
330 scanning source...
328 sorting...
331 sorting...
329 converting...
332 converting...
330 4 a
333 4 a
331 3 b
334 3 b
332 2 c
335 2 c
333 1 d
336 1 d
334 0 e
337 0 e
335
338
336 #endif
339 #endif
337
340
338 test pre and post conversion actions
341 test pre and post conversion actions
339
342
340 $ echo 'include b' > filemap
343 $ echo 'include b' > filemap
341 $ hg convert --debug --filemap filemap a partialb | \
344 $ hg convert --debug --filemap filemap a partialb | \
342 > grep 'run hg'
345 > grep 'run hg'
343 run hg source pre-conversion action
346 run hg source pre-conversion action
344 run hg sink pre-conversion action
347 run hg sink pre-conversion action
345 run hg sink post-conversion action
348 run hg sink post-conversion action
346 run hg source post-conversion action
349 run hg source post-conversion action
347
350
348 converting empty dir should fail "nicely
351 converting empty dir should fail "nicely
349
352
350 $ mkdir emptydir
353 $ mkdir emptydir
351
354
352 override $PATH to ensure p4 not visible; use $PYTHON in case we're
355 override $PATH to ensure p4 not visible; use $PYTHON in case we're
353 running from a devel copy, not a temp installation
356 running from a devel copy, not a temp installation
354
357
355 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
358 $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
356 assuming destination emptydir-hg
359 assuming destination emptydir-hg
357 initializing destination emptydir-hg repository
360 initializing destination emptydir-hg repository
358 emptydir does not look like a CVS checkout
361 emptydir does not look like a CVS checkout
359 emptydir does not look like a Git repository
362 emptydir does not look like a Git repository
360 emptydir does not look like a Subversion repository
363 emptydir does not look like a Subversion repository
361 emptydir is not a local Mercurial repository
364 emptydir is not a local Mercurial repository
362 emptydir does not look like a darcs repository
365 emptydir does not look like a darcs repository
363 emptydir does not look like a monotone repository
366 emptydir does not look like a monotone repository
364 emptydir does not look like a GNU Arch repository
367 emptydir does not look like a GNU Arch repository
365 emptydir does not look like a Bazaar repository
368 emptydir does not look like a Bazaar repository
366 cannot find required "p4" tool
369 cannot find required "p4" tool
367 abort: emptydir: missing or unsupported repository
370 abort: emptydir: missing or unsupported repository
368 [255]
371 [255]
369
372
370 convert with imaginary source type
373 convert with imaginary source type
371
374
372 $ hg convert --source-type foo a a-foo
375 $ hg convert --source-type foo a a-foo
373 initializing destination a-foo repository
376 initializing destination a-foo repository
374 abort: foo: invalid source repository type
377 abort: foo: invalid source repository type
375 [255]
378 [255]
376
379
377 convert with imaginary sink type
380 convert with imaginary sink type
378
381
379 $ hg convert --dest-type foo a a-foo
382 $ hg convert --dest-type foo a a-foo
380 abort: foo: invalid destination repository type
383 abort: foo: invalid destination repository type
381 [255]
384 [255]
382
385
383 testing: convert must not produce duplicate entries in fncache
386 testing: convert must not produce duplicate entries in fncache
384
387
385 $ hg convert a b
388 $ hg convert a b
386 initializing destination b repository
389 initializing destination b repository
387 scanning source...
390 scanning source...
388 sorting...
391 sorting...
389 converting...
392 converting...
390 4 a
393 4 a
391 3 b
394 3 b
392 2 c
395 2 c
393 1 d
396 1 d
394 0 e
397 0 e
395
398
396 contents of fncache file:
399 contents of fncache file:
397
400
398 $ cat b/.hg/store/fncache | sort
401 $ cat b/.hg/store/fncache | sort
399 data/a.i
402 data/a.i
400 data/b.i
403 data/b.i
401
404
402 test bogus URL
405 test bogus URL
403
406
404 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
407 $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
405 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
408 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
406 [255]
409 [255]
407
410
408 test revset converted() lookup
411 test revset converted() lookup
409
412
410 $ hg --config convert.hg.saverev=True convert a c
413 $ hg --config convert.hg.saverev=True convert a c
411 initializing destination c repository
414 initializing destination c repository
412 scanning source...
415 scanning source...
413 sorting...
416 sorting...
414 converting...
417 converting...
415 4 a
418 4 a
416 3 b
419 3 b
417 2 c
420 2 c
418 1 d
421 1 d
419 0 e
422 0 e
420 $ echo f > c/f
423 $ echo f > c/f
421 $ hg -R c ci -d'0 0' -Amf
424 $ hg -R c ci -d'0 0' -Amf
422 adding f
425 adding f
423 created new head
426 created new head
424 $ hg -R c log -r "converted(09d945a62ce6)"
427 $ hg -R c log -r "converted(09d945a62ce6)"
425 changeset: 1:98c3dd46a874
428 changeset: 1:98c3dd46a874
426 user: test
429 user: test
427 date: Thu Jan 01 00:00:01 1970 +0000
430 date: Thu Jan 01 00:00:01 1970 +0000
428 summary: b
431 summary: b
429
432
430 $ hg -R c log -r "converted()"
433 $ hg -R c log -r "converted()"
431 changeset: 0:31ed57b2037c
434 changeset: 0:31ed57b2037c
432 user: test
435 user: test
433 date: Thu Jan 01 00:00:00 1970 +0000
436 date: Thu Jan 01 00:00:00 1970 +0000
434 summary: a
437 summary: a
435
438
436 changeset: 1:98c3dd46a874
439 changeset: 1:98c3dd46a874
437 user: test
440 user: test
438 date: Thu Jan 01 00:00:01 1970 +0000
441 date: Thu Jan 01 00:00:01 1970 +0000
439 summary: b
442 summary: b
440
443
441 changeset: 2:3b9ca06ef716
444 changeset: 2:3b9ca06ef716
442 user: test
445 user: test
443 date: Thu Jan 01 00:00:02 1970 +0000
446 date: Thu Jan 01 00:00:02 1970 +0000
444 summary: c
447 summary: c
445
448
446 changeset: 3:4e0debd37cf2
449 changeset: 3:4e0debd37cf2
447 user: test
450 user: test
448 date: Thu Jan 01 00:00:03 1970 +0000
451 date: Thu Jan 01 00:00:03 1970 +0000
449 summary: d
452 summary: d
450
453
451 changeset: 4:9de3bc9349c5
454 changeset: 4:9de3bc9349c5
452 user: test
455 user: test
453 date: Thu Jan 01 00:00:04 1970 +0000
456 date: Thu Jan 01 00:00:04 1970 +0000
454 summary: e
457 summary: e
455
458
General Comments 0
You need to be logged in to leave comments. Login now