##// END OF EJS Templates
configitems: register the 'convert.hg.ignoreerrors' config
Boris Feld -
r34165:79c01348 default
parent child Browse files
Show More
@@ -1,552 +1,555
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''import revisions from foreign VCS repositories into Mercurial'''
8 '''import revisions from foreign VCS repositories into Mercurial'''
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial import (
13 from mercurial import (
14 registrar,
14 registrar,
15 )
15 )
16
16
17 from . import (
17 from . import (
18 convcmd,
18 convcmd,
19 cvsps,
19 cvsps,
20 subversion,
20 subversion,
21 )
21 )
22
22
23 cmdtable = {}
23 cmdtable = {}
24 command = registrar.command(cmdtable)
24 command = registrar.command(cmdtable)
25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
25 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
26 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
27 # be specifying the version(s) of Mercurial they are tested with, or
27 # be specifying the version(s) of Mercurial they are tested with, or
28 # leave the attribute unspecified.
28 # leave the attribute unspecified.
29 testedwith = 'ships-with-hg-core'
29 testedwith = 'ships-with-hg-core'
30
30
31 configtable = {}
31 configtable = {}
32 configitem = registrar.configitem(configtable)
32 configitem = registrar.configitem(configtable)
33
33
34 configitem('convert', 'cvsps.cache',
34 configitem('convert', 'cvsps.cache',
35 default=True,
35 default=True,
36 )
36 )
37 configitem('convert', 'cvsps.fuzz',
37 configitem('convert', 'cvsps.fuzz',
38 default=60,
38 default=60,
39 )
39 )
40 configitem('convert', 'cvsps.mergefrom',
40 configitem('convert', 'cvsps.mergefrom',
41 default=None,
41 default=None,
42 )
42 )
43 configitem('convert', 'cvsps.mergeto',
43 configitem('convert', 'cvsps.mergeto',
44 default=None,
44 default=None,
45 )
45 )
46 configitem('convert', 'git.committeractions',
46 configitem('convert', 'git.committeractions',
47 default=lambda: ['messagedifferent'],
47 default=lambda: ['messagedifferent'],
48 )
48 )
49 configitem('convert', 'git.extrakeys',
49 configitem('convert', 'git.extrakeys',
50 default=list,
50 default=list,
51 )
51 )
52 configitem('convert', 'git.findcopiesharder',
52 configitem('convert', 'git.findcopiesharder',
53 default=False,
53 default=False,
54 )
54 )
55 configitem('convert', 'git.remoteprefix',
55 configitem('convert', 'git.remoteprefix',
56 default='remote',
56 default='remote',
57 )
57 )
58 configitem('convert', 'git.renamelimit',
58 configitem('convert', 'git.renamelimit',
59 default=400,
59 default=400,
60 )
60 )
61 configitem('convert', 'git.saverev',
61 configitem('convert', 'git.saverev',
62 default=True,
62 default=True,
63 )
63 )
64 configitem('convert', 'git.similarity',
64 configitem('convert', 'git.similarity',
65 default=50,
65 default=50,
66 )
66 )
67 configitem('convert', 'git.skipsubmodules',
67 configitem('convert', 'git.skipsubmodules',
68 default=False,
68 default=False,
69 )
69 )
70 configitem('convert', 'hg.clonebranches',
70 configitem('convert', 'hg.clonebranches',
71 default=False,
71 default=False,
72 )
72 )
73 configitem('convert', 'hg.ignoreerrors',
74 default=False,
75 )
73
76
74 # Commands definition was moved elsewhere to ease demandload job.
77 # Commands definition was moved elsewhere to ease demandload job.
75
78
76 @command('convert',
79 @command('convert',
77 [('', 'authors', '',
80 [('', 'authors', '',
78 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
81 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
79 _('FILE')),
82 _('FILE')),
80 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
83 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
81 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
84 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
82 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
85 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
83 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
86 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
84 ('', 'filemap', '', _('remap file names using contents of file'),
87 ('', 'filemap', '', _('remap file names using contents of file'),
85 _('FILE')),
88 _('FILE')),
86 ('', 'full', None,
89 ('', 'full', None,
87 _('apply filemap changes by converting all files again')),
90 _('apply filemap changes by converting all files again')),
88 ('', 'splicemap', '', _('splice synthesized history into place'),
91 ('', 'splicemap', '', _('splice synthesized history into place'),
89 _('FILE')),
92 _('FILE')),
90 ('', 'branchmap', '', _('change branch names while converting'),
93 ('', 'branchmap', '', _('change branch names while converting'),
91 _('FILE')),
94 _('FILE')),
92 ('', 'branchsort', None, _('try to sort changesets by branches')),
95 ('', 'branchsort', None, _('try to sort changesets by branches')),
93 ('', 'datesort', None, _('try to sort changesets by date')),
96 ('', 'datesort', None, _('try to sort changesets by date')),
94 ('', 'sourcesort', None, _('preserve source changesets order')),
97 ('', 'sourcesort', None, _('preserve source changesets order')),
95 ('', 'closesort', None, _('try to reorder closed revisions'))],
98 ('', 'closesort', None, _('try to reorder closed revisions'))],
96 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
99 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
97 norepo=True)
100 norepo=True)
98 def convert(ui, src, dest=None, revmapfile=None, **opts):
101 def convert(ui, src, dest=None, revmapfile=None, **opts):
99 """convert a foreign SCM repository to a Mercurial one.
102 """convert a foreign SCM repository to a Mercurial one.
100
103
101 Accepted source formats [identifiers]:
104 Accepted source formats [identifiers]:
102
105
103 - Mercurial [hg]
106 - Mercurial [hg]
104 - CVS [cvs]
107 - CVS [cvs]
105 - Darcs [darcs]
108 - Darcs [darcs]
106 - git [git]
109 - git [git]
107 - Subversion [svn]
110 - Subversion [svn]
108 - Monotone [mtn]
111 - Monotone [mtn]
109 - GNU Arch [gnuarch]
112 - GNU Arch [gnuarch]
110 - Bazaar [bzr]
113 - Bazaar [bzr]
111 - Perforce [p4]
114 - Perforce [p4]
112
115
113 Accepted destination formats [identifiers]:
116 Accepted destination formats [identifiers]:
114
117
115 - Mercurial [hg]
118 - Mercurial [hg]
116 - Subversion [svn] (history on branches is not preserved)
119 - Subversion [svn] (history on branches is not preserved)
117
120
118 If no revision is given, all revisions will be converted.
121 If no revision is given, all revisions will be converted.
119 Otherwise, convert will only import up to the named revision
122 Otherwise, convert will only import up to the named revision
120 (given in a format understood by the source).
123 (given in a format understood by the source).
121
124
122 If no destination directory name is specified, it defaults to the
125 If no destination directory name is specified, it defaults to the
123 basename of the source with ``-hg`` appended. If the destination
126 basename of the source with ``-hg`` appended. If the destination
124 repository doesn't exist, it will be created.
127 repository doesn't exist, it will be created.
125
128
126 By default, all sources except Mercurial will use --branchsort.
129 By default, all sources except Mercurial will use --branchsort.
127 Mercurial uses --sourcesort to preserve original revision numbers
130 Mercurial uses --sourcesort to preserve original revision numbers
128 order. Sort modes have the following effects:
131 order. Sort modes have the following effects:
129
132
130 --branchsort convert from parent to child revision when possible,
133 --branchsort convert from parent to child revision when possible,
131 which means branches are usually converted one after
134 which means branches are usually converted one after
132 the other. It generates more compact repositories.
135 the other. It generates more compact repositories.
133
136
134 --datesort sort revisions by date. Converted repositories have
137 --datesort sort revisions by date. Converted repositories have
135 good-looking changelogs but are often an order of
138 good-looking changelogs but are often an order of
136 magnitude larger than the same ones generated by
139 magnitude larger than the same ones generated by
137 --branchsort.
140 --branchsort.
138
141
139 --sourcesort try to preserve source revisions order, only
142 --sourcesort try to preserve source revisions order, only
140 supported by Mercurial sources.
143 supported by Mercurial sources.
141
144
142 --closesort try to move closed revisions as close as possible
145 --closesort try to move closed revisions as close as possible
143 to parent branches, only supported by Mercurial
146 to parent branches, only supported by Mercurial
144 sources.
147 sources.
145
148
146 If ``REVMAP`` isn't given, it will be put in a default location
149 If ``REVMAP`` isn't given, it will be put in a default location
147 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
150 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
148 text file that maps each source commit ID to the destination ID
151 text file that maps each source commit ID to the destination ID
149 for that revision, like so::
152 for that revision, like so::
150
153
151 <source ID> <destination ID>
154 <source ID> <destination ID>
152
155
153 If the file doesn't exist, it's automatically created. It's
156 If the file doesn't exist, it's automatically created. It's
154 updated on each commit copied, so :hg:`convert` can be interrupted
157 updated on each commit copied, so :hg:`convert` can be interrupted
155 and can be run repeatedly to copy new commits.
158 and can be run repeatedly to copy new commits.
156
159
157 The authormap is a simple text file that maps each source commit
160 The authormap is a simple text file that maps each source commit
158 author to a destination commit author. It is handy for source SCMs
161 author to a destination commit author. It is handy for source SCMs
159 that use unix logins to identify authors (e.g.: CVS). One line per
162 that use unix logins to identify authors (e.g.: CVS). One line per
160 author mapping and the line format is::
163 author mapping and the line format is::
161
164
162 source author = destination author
165 source author = destination author
163
166
164 Empty lines and lines starting with a ``#`` are ignored.
167 Empty lines and lines starting with a ``#`` are ignored.
165
168
166 The filemap is a file that allows filtering and remapping of files
169 The filemap is a file that allows filtering and remapping of files
167 and directories. Each line can contain one of the following
170 and directories. Each line can contain one of the following
168 directives::
171 directives::
169
172
170 include path/to/file-or-dir
173 include path/to/file-or-dir
171
174
172 exclude path/to/file-or-dir
175 exclude path/to/file-or-dir
173
176
174 rename path/to/source path/to/destination
177 rename path/to/source path/to/destination
175
178
176 Comment lines start with ``#``. A specified path matches if it
179 Comment lines start with ``#``. A specified path matches if it
177 equals the full relative name of a file or one of its parent
180 equals the full relative name of a file or one of its parent
178 directories. The ``include`` or ``exclude`` directive with the
181 directories. The ``include`` or ``exclude`` directive with the
179 longest matching path applies, so line order does not matter.
182 longest matching path applies, so line order does not matter.
180
183
181 The ``include`` directive causes a file, or all files under a
184 The ``include`` directive causes a file, or all files under a
182 directory, to be included in the destination repository. The default
185 directory, to be included in the destination repository. The default
183 if there are no ``include`` statements is to include everything.
186 if there are no ``include`` statements is to include everything.
184 If there are any ``include`` statements, nothing else is included.
187 If there are any ``include`` statements, nothing else is included.
185 The ``exclude`` directive causes files or directories to
188 The ``exclude`` directive causes files or directories to
186 be omitted. The ``rename`` directive renames a file or directory if
189 be omitted. The ``rename`` directive renames a file or directory if
187 it is converted. To rename from a subdirectory into the root of
190 it is converted. To rename from a subdirectory into the root of
188 the repository, use ``.`` as the path to rename to.
191 the repository, use ``.`` as the path to rename to.
189
192
190 ``--full`` will make sure the converted changesets contain exactly
193 ``--full`` will make sure the converted changesets contain exactly
191 the right files with the right content. It will make a full
194 the right files with the right content. It will make a full
192 conversion of all files, not just the ones that have
195 conversion of all files, not just the ones that have
193 changed. Files that already are correct will not be changed. This
196 changed. Files that already are correct will not be changed. This
194 can be used to apply filemap changes when converting
197 can be used to apply filemap changes when converting
195 incrementally. This is currently only supported for Mercurial and
198 incrementally. This is currently only supported for Mercurial and
196 Subversion.
199 Subversion.
197
200
198 The splicemap is a file that allows insertion of synthetic
201 The splicemap is a file that allows insertion of synthetic
199 history, letting you specify the parents of a revision. This is
202 history, letting you specify the parents of a revision. This is
200 useful if you want to e.g. give a Subversion merge two parents, or
203 useful if you want to e.g. give a Subversion merge two parents, or
201 graft two disconnected series of history together. Each entry
204 graft two disconnected series of history together. Each entry
202 contains a key, followed by a space, followed by one or two
205 contains a key, followed by a space, followed by one or two
203 comma-separated values::
206 comma-separated values::
204
207
205 key parent1, parent2
208 key parent1, parent2
206
209
207 The key is the revision ID in the source
210 The key is the revision ID in the source
208 revision control system whose parents should be modified (same
211 revision control system whose parents should be modified (same
209 format as a key in .hg/shamap). The values are the revision IDs
212 format as a key in .hg/shamap). The values are the revision IDs
210 (in either the source or destination revision control system) that
213 (in either the source or destination revision control system) that
211 should be used as the new parents for that node. For example, if
214 should be used as the new parents for that node. For example, if
212 you have merged "release-1.0" into "trunk", then you should
215 you have merged "release-1.0" into "trunk", then you should
213 specify the revision on "trunk" as the first parent and the one on
216 specify the revision on "trunk" as the first parent and the one on
214 the "release-1.0" branch as the second.
217 the "release-1.0" branch as the second.
215
218
216 The branchmap is a file that allows you to rename a branch when it is
219 The branchmap is a file that allows you to rename a branch when it is
217 being brought in from whatever external repository. When used in
220 being brought in from whatever external repository. When used in
218 conjunction with a splicemap, it allows for a powerful combination
221 conjunction with a splicemap, it allows for a powerful combination
219 to help fix even the most badly mismanaged repositories and turn them
222 to help fix even the most badly mismanaged repositories and turn them
220 into nicely structured Mercurial repositories. The branchmap contains
223 into nicely structured Mercurial repositories. The branchmap contains
221 lines of the form::
224 lines of the form::
222
225
223 original_branch_name new_branch_name
226 original_branch_name new_branch_name
224
227
225 where "original_branch_name" is the name of the branch in the
228 where "original_branch_name" is the name of the branch in the
226 source repository, and "new_branch_name" is the name of the branch
229 source repository, and "new_branch_name" is the name of the branch
227 is the destination repository. No whitespace is allowed in the new
230 is the destination repository. No whitespace is allowed in the new
228 branch name. This can be used to (for instance) move code in one
231 branch name. This can be used to (for instance) move code in one
229 repository from "default" to a named branch.
232 repository from "default" to a named branch.
230
233
231 Mercurial Source
234 Mercurial Source
232 ################
235 ################
233
236
234 The Mercurial source recognizes the following configuration
237 The Mercurial source recognizes the following configuration
235 options, which you can set on the command line with ``--config``:
238 options, which you can set on the command line with ``--config``:
236
239
237 :convert.hg.ignoreerrors: ignore integrity errors when reading.
240 :convert.hg.ignoreerrors: ignore integrity errors when reading.
238 Use it to fix Mercurial repositories with missing revlogs, by
241 Use it to fix Mercurial repositories with missing revlogs, by
239 converting from and to Mercurial. Default is False.
242 converting from and to Mercurial. Default is False.
240
243
241 :convert.hg.saverev: store original revision ID in changeset
244 :convert.hg.saverev: store original revision ID in changeset
242 (forces target IDs to change). It takes a boolean argument and
245 (forces target IDs to change). It takes a boolean argument and
243 defaults to False.
246 defaults to False.
244
247
245 :convert.hg.startrev: specify the initial Mercurial revision.
248 :convert.hg.startrev: specify the initial Mercurial revision.
246 The default is 0.
249 The default is 0.
247
250
248 :convert.hg.revs: revset specifying the source revisions to convert.
251 :convert.hg.revs: revset specifying the source revisions to convert.
249
252
250 CVS Source
253 CVS Source
251 ##########
254 ##########
252
255
253 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
256 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
254 to indicate the starting point of what will be converted. Direct
257 to indicate the starting point of what will be converted. Direct
255 access to the repository files is not needed, unless of course the
258 access to the repository files is not needed, unless of course the
256 repository is ``:local:``. The conversion uses the top level
259 repository is ``:local:``. The conversion uses the top level
257 directory in the sandbox to find the CVS repository, and then uses
260 directory in the sandbox to find the CVS repository, and then uses
258 CVS rlog commands to find files to convert. This means that unless
261 CVS rlog commands to find files to convert. This means that unless
259 a filemap is given, all files under the starting directory will be
262 a filemap is given, all files under the starting directory will be
260 converted, and that any directory reorganization in the CVS
263 converted, and that any directory reorganization in the CVS
261 sandbox is ignored.
264 sandbox is ignored.
262
265
263 The following options can be used with ``--config``:
266 The following options can be used with ``--config``:
264
267
265 :convert.cvsps.cache: Set to False to disable remote log caching,
268 :convert.cvsps.cache: Set to False to disable remote log caching,
266 for testing and debugging purposes. Default is True.
269 for testing and debugging purposes. Default is True.
267
270
268 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
271 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
269 allowed between commits with identical user and log message in
272 allowed between commits with identical user and log message in
270 a single changeset. When very large files were checked in as
273 a single changeset. When very large files were checked in as
271 part of a changeset then the default may not be long enough.
274 part of a changeset then the default may not be long enough.
272 The default is 60.
275 The default is 60.
273
276
274 :convert.cvsps.logencoding: Specify encoding name to be used for
277 :convert.cvsps.logencoding: Specify encoding name to be used for
275 transcoding CVS log messages. Multiple encoding names can be
278 transcoding CVS log messages. Multiple encoding names can be
276 specified as a list (see :hg:`help config.Syntax`), but only
279 specified as a list (see :hg:`help config.Syntax`), but only
277 the first acceptable encoding in the list is used per CVS log
280 the first acceptable encoding in the list is used per CVS log
278 entries. This transcoding is executed before cvslog hook below.
281 entries. This transcoding is executed before cvslog hook below.
279
282
280 :convert.cvsps.mergeto: Specify a regular expression to which
283 :convert.cvsps.mergeto: Specify a regular expression to which
281 commit log messages are matched. If a match occurs, then the
284 commit log messages are matched. If a match occurs, then the
282 conversion process will insert a dummy revision merging the
285 conversion process will insert a dummy revision merging the
283 branch on which this log message occurs to the branch
286 branch on which this log message occurs to the branch
284 indicated in the regex. Default is ``{{mergetobranch
287 indicated in the regex. Default is ``{{mergetobranch
285 ([-\\w]+)}}``
288 ([-\\w]+)}}``
286
289
287 :convert.cvsps.mergefrom: Specify a regular expression to which
290 :convert.cvsps.mergefrom: Specify a regular expression to which
288 commit log messages are matched. If a match occurs, then the
291 commit log messages are matched. If a match occurs, then the
289 conversion process will add the most recent revision on the
292 conversion process will add the most recent revision on the
290 branch indicated in the regex as the second parent of the
293 branch indicated in the regex as the second parent of the
291 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
294 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
292
295
293 :convert.localtimezone: use local time (as determined by the TZ
296 :convert.localtimezone: use local time (as determined by the TZ
294 environment variable) for changeset date/times. The default
297 environment variable) for changeset date/times. The default
295 is False (use UTC).
298 is False (use UTC).
296
299
297 :hooks.cvslog: Specify a Python function to be called at the end of
300 :hooks.cvslog: Specify a Python function to be called at the end of
298 gathering the CVS log. The function is passed a list with the
301 gathering the CVS log. The function is passed a list with the
299 log entries, and can modify the entries in-place, or add or
302 log entries, and can modify the entries in-place, or add or
300 delete them.
303 delete them.
301
304
302 :hooks.cvschangesets: Specify a Python function to be called after
305 :hooks.cvschangesets: Specify a Python function to be called after
303 the changesets are calculated from the CVS log. The
306 the changesets are calculated from the CVS log. The
304 function is passed a list with the changeset entries, and can
307 function is passed a list with the changeset entries, and can
305 modify the changesets in-place, or add or delete them.
308 modify the changesets in-place, or add or delete them.
306
309
307 An additional "debugcvsps" Mercurial command allows the builtin
310 An additional "debugcvsps" Mercurial command allows the builtin
308 changeset merging code to be run without doing a conversion. Its
311 changeset merging code to be run without doing a conversion. Its
309 parameters and output are similar to that of cvsps 2.1. Please see
312 parameters and output are similar to that of cvsps 2.1. Please see
310 the command help for more details.
313 the command help for more details.
311
314
312 Subversion Source
315 Subversion Source
313 #################
316 #################
314
317
315 Subversion source detects classical trunk/branches/tags layouts.
318 Subversion source detects classical trunk/branches/tags layouts.
316 By default, the supplied ``svn://repo/path/`` source URL is
319 By default, the supplied ``svn://repo/path/`` source URL is
317 converted as a single branch. If ``svn://repo/path/trunk`` exists
320 converted as a single branch. If ``svn://repo/path/trunk`` exists
318 it replaces the default branch. If ``svn://repo/path/branches``
321 it replaces the default branch. If ``svn://repo/path/branches``
319 exists, its subdirectories are listed as possible branches. If
322 exists, its subdirectories are listed as possible branches. If
320 ``svn://repo/path/tags`` exists, it is looked for tags referencing
323 ``svn://repo/path/tags`` exists, it is looked for tags referencing
321 converted branches. Default ``trunk``, ``branches`` and ``tags``
324 converted branches. Default ``trunk``, ``branches`` and ``tags``
322 values can be overridden with following options. Set them to paths
325 values can be overridden with following options. Set them to paths
323 relative to the source URL, or leave them blank to disable auto
326 relative to the source URL, or leave them blank to disable auto
324 detection.
327 detection.
325
328
326 The following options can be set with ``--config``:
329 The following options can be set with ``--config``:
327
330
328 :convert.svn.branches: specify the directory containing branches.
331 :convert.svn.branches: specify the directory containing branches.
329 The default is ``branches``.
332 The default is ``branches``.
330
333
331 :convert.svn.tags: specify the directory containing tags. The
334 :convert.svn.tags: specify the directory containing tags. The
332 default is ``tags``.
335 default is ``tags``.
333
336
334 :convert.svn.trunk: specify the name of the trunk branch. The
337 :convert.svn.trunk: specify the name of the trunk branch. The
335 default is ``trunk``.
338 default is ``trunk``.
336
339
337 :convert.localtimezone: use local time (as determined by the TZ
340 :convert.localtimezone: use local time (as determined by the TZ
338 environment variable) for changeset date/times. The default
341 environment variable) for changeset date/times. The default
339 is False (use UTC).
342 is False (use UTC).
340
343
341 Source history can be retrieved starting at a specific revision,
344 Source history can be retrieved starting at a specific revision,
342 instead of being integrally converted. Only single branch
345 instead of being integrally converted. Only single branch
343 conversions are supported.
346 conversions are supported.
344
347
345 :convert.svn.startrev: specify start Subversion revision number.
348 :convert.svn.startrev: specify start Subversion revision number.
346 The default is 0.
349 The default is 0.
347
350
348 Git Source
351 Git Source
349 ##########
352 ##########
350
353
351 The Git importer converts commits from all reachable branches (refs
354 The Git importer converts commits from all reachable branches (refs
352 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
355 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
353 Branches are converted to bookmarks with the same name, with the
356 Branches are converted to bookmarks with the same name, with the
354 leading 'refs/heads' stripped. Git submodules are converted to Git
357 leading 'refs/heads' stripped. Git submodules are converted to Git
355 subrepos in Mercurial.
358 subrepos in Mercurial.
356
359
357 The following options can be set with ``--config``:
360 The following options can be set with ``--config``:
358
361
359 :convert.git.similarity: specify how similar files modified in a
362 :convert.git.similarity: specify how similar files modified in a
360 commit must be to be imported as renames or copies, as a
363 commit must be to be imported as renames or copies, as a
361 percentage between ``0`` (disabled) and ``100`` (files must be
364 percentage between ``0`` (disabled) and ``100`` (files must be
362 identical). For example, ``90`` means that a delete/add pair will
365 identical). For example, ``90`` means that a delete/add pair will
363 be imported as a rename if more than 90% of the file hasn't
366 be imported as a rename if more than 90% of the file hasn't
364 changed. The default is ``50``.
367 changed. The default is ``50``.
365
368
366 :convert.git.findcopiesharder: while detecting copies, look at all
369 :convert.git.findcopiesharder: while detecting copies, look at all
367 files in the working copy instead of just changed ones. This
370 files in the working copy instead of just changed ones. This
368 is very expensive for large projects, and is only effective when
371 is very expensive for large projects, and is only effective when
369 ``convert.git.similarity`` is greater than 0. The default is False.
372 ``convert.git.similarity`` is greater than 0. The default is False.
370
373
371 :convert.git.renamelimit: perform rename and copy detection up to this
374 :convert.git.renamelimit: perform rename and copy detection up to this
372 many changed files in a commit. Increasing this will make rename
375 many changed files in a commit. Increasing this will make rename
373 and copy detection more accurate but will significantly slow down
376 and copy detection more accurate but will significantly slow down
374 computation on large projects. The option is only relevant if
377 computation on large projects. The option is only relevant if
375 ``convert.git.similarity`` is greater than 0. The default is
378 ``convert.git.similarity`` is greater than 0. The default is
376 ``400``.
379 ``400``.
377
380
378 :convert.git.committeractions: list of actions to take when processing
381 :convert.git.committeractions: list of actions to take when processing
379 author and committer values.
382 author and committer values.
380
383
381 Git commits have separate author (who wrote the commit) and committer
384 Git commits have separate author (who wrote the commit) and committer
382 (who applied the commit) fields. Not all destinations support separate
385 (who applied the commit) fields. Not all destinations support separate
383 author and committer fields (including Mercurial). This config option
386 author and committer fields (including Mercurial). This config option
384 controls what to do with these author and committer fields during
387 controls what to do with these author and committer fields during
385 conversion.
388 conversion.
386
389
387 A value of ``messagedifferent`` will append a ``committer: ...``
390 A value of ``messagedifferent`` will append a ``committer: ...``
388 line to the commit message if the Git committer is different from the
391 line to the commit message if the Git committer is different from the
389 author. The prefix of that line can be specified using the syntax
392 author. The prefix of that line can be specified using the syntax
390 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
393 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
391 When a prefix is specified, a space will always be inserted between the
394 When a prefix is specified, a space will always be inserted between the
392 prefix and the value.
395 prefix and the value.
393
396
394 ``messagealways`` behaves like ``messagedifferent`` except it will
397 ``messagealways`` behaves like ``messagedifferent`` except it will
395 always result in a ``committer: ...`` line being appended to the commit
398 always result in a ``committer: ...`` line being appended to the commit
396 message. This value is mutually exclusive with ``messagedifferent``.
399 message. This value is mutually exclusive with ``messagedifferent``.
397
400
398 ``dropcommitter`` will remove references to the committer. Only
401 ``dropcommitter`` will remove references to the committer. Only
399 references to the author will remain. Actions that add references
402 references to the author will remain. Actions that add references
400 to the committer will have no effect when this is set.
403 to the committer will have no effect when this is set.
401
404
402 ``replaceauthor`` will replace the value of the author field with
405 ``replaceauthor`` will replace the value of the author field with
403 the committer. Other actions that add references to the committer
406 the committer. Other actions that add references to the committer
404 will still take effect when this is set.
407 will still take effect when this is set.
405
408
406 The default is ``messagedifferent``.
409 The default is ``messagedifferent``.
407
410
408 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
411 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
409 the destination. Some Git repositories store extra metadata in commits.
412 the destination. Some Git repositories store extra metadata in commits.
410 By default, this non-default metadata will be lost during conversion.
413 By default, this non-default metadata will be lost during conversion.
411 Setting this config option can retain that metadata. Some built-in
414 Setting this config option can retain that metadata. Some built-in
412 keys such as ``parent`` and ``branch`` are not allowed to be copied.
415 keys such as ``parent`` and ``branch`` are not allowed to be copied.
413
416
414 :convert.git.remoteprefix: remote refs are converted as bookmarks with
417 :convert.git.remoteprefix: remote refs are converted as bookmarks with
415 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
418 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
416 is 'remote'.
419 is 'remote'.
417
420
418 :convert.git.saverev: whether to store the original Git commit ID in the
421 :convert.git.saverev: whether to store the original Git commit ID in the
419 metadata of the destination commit. The default is True.
422 metadata of the destination commit. The default is True.
420
423
421 :convert.git.skipsubmodules: does not convert root level .gitmodules files
424 :convert.git.skipsubmodules: does not convert root level .gitmodules files
422 or files with 160000 mode indicating a submodule. Default is False.
425 or files with 160000 mode indicating a submodule. Default is False.
423
426
424 Perforce Source
427 Perforce Source
425 ###############
428 ###############
426
429
427 The Perforce (P4) importer can be given a p4 depot path or a
430 The Perforce (P4) importer can be given a p4 depot path or a
428 client specification as source. It will convert all files in the
431 client specification as source. It will convert all files in the
429 source to a flat Mercurial repository, ignoring labels, branches
432 source to a flat Mercurial repository, ignoring labels, branches
430 and integrations. Note that when a depot path is given you then
433 and integrations. Note that when a depot path is given you then
431 usually should specify a target directory, because otherwise the
434 usually should specify a target directory, because otherwise the
432 target may be named ``...-hg``.
435 target may be named ``...-hg``.
433
436
434 The following options can be set with ``--config``:
437 The following options can be set with ``--config``:
435
438
436 :convert.p4.encoding: specify the encoding to use when decoding standard
439 :convert.p4.encoding: specify the encoding to use when decoding standard
437 output of the Perforce command line tool. The default is default system
440 output of the Perforce command line tool. The default is default system
438 encoding.
441 encoding.
439
442
440 :convert.p4.startrev: specify initial Perforce revision (a
443 :convert.p4.startrev: specify initial Perforce revision (a
441 Perforce changelist number).
444 Perforce changelist number).
442
445
443 Mercurial Destination
446 Mercurial Destination
444 #####################
447 #####################
445
448
446 The Mercurial destination will recognize Mercurial subrepositories in the
449 The Mercurial destination will recognize Mercurial subrepositories in the
447 destination directory, and update the .hgsubstate file automatically if the
450 destination directory, and update the .hgsubstate file automatically if the
448 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
451 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
449 Converting a repository with subrepositories requires converting a single
452 Converting a repository with subrepositories requires converting a single
450 repository at a time, from the bottom up.
453 repository at a time, from the bottom up.
451
454
452 .. container:: verbose
455 .. container:: verbose
453
456
454 An example showing how to convert a repository with subrepositories::
457 An example showing how to convert a repository with subrepositories::
455
458
456 # so convert knows the type when it sees a non empty destination
459 # so convert knows the type when it sees a non empty destination
457 $ hg init converted
460 $ hg init converted
458
461
459 $ hg convert orig/sub1 converted/sub1
462 $ hg convert orig/sub1 converted/sub1
460 $ hg convert orig/sub2 converted/sub2
463 $ hg convert orig/sub2 converted/sub2
461 $ hg convert orig converted
464 $ hg convert orig converted
462
465
463 The following options are supported:
466 The following options are supported:
464
467
465 :convert.hg.clonebranches: dispatch source branches in separate
468 :convert.hg.clonebranches: dispatch source branches in separate
466 clones. The default is False.
469 clones. The default is False.
467
470
468 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
471 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
469 ``default``.
472 ``default``.
470
473
471 :convert.hg.usebranchnames: preserve branch names. The default is
474 :convert.hg.usebranchnames: preserve branch names. The default is
472 True.
475 True.
473
476
474 :convert.hg.sourcename: records the given string as a 'convert_source' extra
477 :convert.hg.sourcename: records the given string as a 'convert_source' extra
475 value on each commit made in the target repository. The default is None.
478 value on each commit made in the target repository. The default is None.
476
479
477 All Destinations
480 All Destinations
478 ################
481 ################
479
482
480 All destination types accept the following options:
483 All destination types accept the following options:
481
484
482 :convert.skiptags: does not convert tags from the source repo to the target
485 :convert.skiptags: does not convert tags from the source repo to the target
483 repo. The default is False.
486 repo. The default is False.
484 """
487 """
485 return convcmd.convert(ui, src, dest, revmapfile, **opts)
488 return convcmd.convert(ui, src, dest, revmapfile, **opts)
486
489
487 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
490 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
488 def debugsvnlog(ui, **opts):
491 def debugsvnlog(ui, **opts):
489 return subversion.debugsvnlog(ui, **opts)
492 return subversion.debugsvnlog(ui, **opts)
490
493
491 @command('debugcvsps',
494 @command('debugcvsps',
492 [
495 [
493 # Main options shared with cvsps-2.1
496 # Main options shared with cvsps-2.1
494 ('b', 'branches', [], _('only return changes on specified branches')),
497 ('b', 'branches', [], _('only return changes on specified branches')),
495 ('p', 'prefix', '', _('prefix to remove from file names')),
498 ('p', 'prefix', '', _('prefix to remove from file names')),
496 ('r', 'revisions', [],
499 ('r', 'revisions', [],
497 _('only return changes after or between specified tags')),
500 _('only return changes after or between specified tags')),
498 ('u', 'update-cache', None, _("update cvs log cache")),
501 ('u', 'update-cache', None, _("update cvs log cache")),
499 ('x', 'new-cache', None, _("create new cvs log cache")),
502 ('x', 'new-cache', None, _("create new cvs log cache")),
500 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
503 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
501 ('', 'root', '', _('specify cvsroot')),
504 ('', 'root', '', _('specify cvsroot')),
502 # Options specific to builtin cvsps
505 # Options specific to builtin cvsps
503 ('', 'parents', '', _('show parent changesets')),
506 ('', 'parents', '', _('show parent changesets')),
504 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
507 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
505 # Options that are ignored for compatibility with cvsps-2.1
508 # Options that are ignored for compatibility with cvsps-2.1
506 ('A', 'cvs-direct', None, _('ignored for compatibility')),
509 ('A', 'cvs-direct', None, _('ignored for compatibility')),
507 ],
510 ],
508 _('hg debugcvsps [OPTION]... [PATH]...'),
511 _('hg debugcvsps [OPTION]... [PATH]...'),
509 norepo=True)
512 norepo=True)
510 def debugcvsps(ui, *args, **opts):
513 def debugcvsps(ui, *args, **opts):
511 '''create changeset information from CVS
514 '''create changeset information from CVS
512
515
513 This command is intended as a debugging tool for the CVS to
516 This command is intended as a debugging tool for the CVS to
514 Mercurial converter, and can be used as a direct replacement for
517 Mercurial converter, and can be used as a direct replacement for
515 cvsps.
518 cvsps.
516
519
517 Hg debugcvsps reads the CVS rlog for current directory (or any
520 Hg debugcvsps reads the CVS rlog for current directory (or any
518 named directory) in the CVS repository, and converts the log to a
521 named directory) in the CVS repository, and converts the log to a
519 series of changesets based on matching commit log entries and
522 series of changesets based on matching commit log entries and
520 dates.'''
523 dates.'''
521 return cvsps.debugcvsps(ui, *args, **opts)
524 return cvsps.debugcvsps(ui, *args, **opts)
522
525
523 def kwconverted(ctx, name):
526 def kwconverted(ctx, name):
524 rev = ctx.extra().get('convert_revision', '')
527 rev = ctx.extra().get('convert_revision', '')
525 if rev.startswith('svn:'):
528 if rev.startswith('svn:'):
526 if name == 'svnrev':
529 if name == 'svnrev':
527 return str(subversion.revsplit(rev)[2])
530 return str(subversion.revsplit(rev)[2])
528 elif name == 'svnpath':
531 elif name == 'svnpath':
529 return subversion.revsplit(rev)[1]
532 return subversion.revsplit(rev)[1]
530 elif name == 'svnuuid':
533 elif name == 'svnuuid':
531 return subversion.revsplit(rev)[0]
534 return subversion.revsplit(rev)[0]
532 return rev
535 return rev
533
536
534 templatekeyword = registrar.templatekeyword()
537 templatekeyword = registrar.templatekeyword()
535
538
536 @templatekeyword('svnrev')
539 @templatekeyword('svnrev')
537 def kwsvnrev(repo, ctx, **args):
540 def kwsvnrev(repo, ctx, **args):
538 """String. Converted subversion revision number."""
541 """String. Converted subversion revision number."""
539 return kwconverted(ctx, 'svnrev')
542 return kwconverted(ctx, 'svnrev')
540
543
541 @templatekeyword('svnpath')
544 @templatekeyword('svnpath')
542 def kwsvnpath(repo, ctx, **args):
545 def kwsvnpath(repo, ctx, **args):
543 """String. Converted subversion revision project path."""
546 """String. Converted subversion revision project path."""
544 return kwconverted(ctx, 'svnpath')
547 return kwconverted(ctx, 'svnpath')
545
548
546 @templatekeyword('svnuuid')
549 @templatekeyword('svnuuid')
547 def kwsvnuuid(repo, ctx, **args):
550 def kwsvnuuid(repo, ctx, **args):
548 """String. Converted subversion revision repository identifier."""
551 """String. Converted subversion revision repository identifier."""
549 return kwconverted(ctx, 'svnuuid')
552 return kwconverted(ctx, 'svnuuid')
550
553
551 # tell hggettext to extract docstrings from these functions:
554 # tell hggettext to extract docstrings from these functions:
552 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
555 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,655 +1,655
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 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 import os
21 import os
22 import re
22 import re
23 import time
23 import time
24
24
25 from mercurial.i18n import _
25 from mercurial.i18n import _
26 from mercurial import (
26 from mercurial import (
27 bookmarks,
27 bookmarks,
28 context,
28 context,
29 error,
29 error,
30 exchange,
30 exchange,
31 hg,
31 hg,
32 lock as lockmod,
32 lock as lockmod,
33 merge as mergemod,
33 merge as mergemod,
34 node as nodemod,
34 node as nodemod,
35 phases,
35 phases,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39 stringio = util.stringio
39 stringio = util.stringio
40
40
41 from . import common
41 from . import common
42 mapfile = common.mapfile
42 mapfile = common.mapfile
43 NoRepo = common.NoRepo
43 NoRepo = common.NoRepo
44
44
45 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
45 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
46
46
47 class mercurial_sink(common.converter_sink):
47 class mercurial_sink(common.converter_sink):
48 def __init__(self, ui, path):
48 def __init__(self, ui, path):
49 common.converter_sink.__init__(self, ui, path)
49 common.converter_sink.__init__(self, ui, path)
50 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
50 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
51 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
51 self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
52 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
52 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
53 self.lastbranch = None
53 self.lastbranch = None
54 if os.path.isdir(path) and len(os.listdir(path)) > 0:
54 if os.path.isdir(path) and len(os.listdir(path)) > 0:
55 try:
55 try:
56 self.repo = hg.repository(self.ui, path)
56 self.repo = hg.repository(self.ui, path)
57 if not self.repo.local():
57 if not self.repo.local():
58 raise NoRepo(_('%s is not a local Mercurial repository')
58 raise NoRepo(_('%s is not a local Mercurial repository')
59 % path)
59 % path)
60 except error.RepoError as err:
60 except error.RepoError as err:
61 ui.traceback()
61 ui.traceback()
62 raise NoRepo(err.args[0])
62 raise NoRepo(err.args[0])
63 else:
63 else:
64 try:
64 try:
65 ui.status(_('initializing destination %s repository\n') % path)
65 ui.status(_('initializing destination %s repository\n') % path)
66 self.repo = hg.repository(self.ui, path, create=True)
66 self.repo = hg.repository(self.ui, path, create=True)
67 if not self.repo.local():
67 if not self.repo.local():
68 raise NoRepo(_('%s is not a local Mercurial repository')
68 raise NoRepo(_('%s is not a local Mercurial repository')
69 % path)
69 % path)
70 self.created.append(path)
70 self.created.append(path)
71 except error.RepoError:
71 except error.RepoError:
72 ui.traceback()
72 ui.traceback()
73 raise NoRepo(_("could not create hg repository %s as sink")
73 raise NoRepo(_("could not create hg repository %s as sink")
74 % path)
74 % path)
75 self.lock = None
75 self.lock = None
76 self.wlock = None
76 self.wlock = None
77 self.filemapmode = False
77 self.filemapmode = False
78 self.subrevmaps = {}
78 self.subrevmaps = {}
79
79
80 def before(self):
80 def before(self):
81 self.ui.debug('run hg sink pre-conversion action\n')
81 self.ui.debug('run hg sink pre-conversion action\n')
82 self.wlock = self.repo.wlock()
82 self.wlock = self.repo.wlock()
83 self.lock = self.repo.lock()
83 self.lock = self.repo.lock()
84
84
85 def after(self):
85 def after(self):
86 self.ui.debug('run hg sink post-conversion action\n')
86 self.ui.debug('run hg sink post-conversion action\n')
87 if self.lock:
87 if self.lock:
88 self.lock.release()
88 self.lock.release()
89 if self.wlock:
89 if self.wlock:
90 self.wlock.release()
90 self.wlock.release()
91
91
92 def revmapfile(self):
92 def revmapfile(self):
93 return self.repo.vfs.join("shamap")
93 return self.repo.vfs.join("shamap")
94
94
95 def authorfile(self):
95 def authorfile(self):
96 return self.repo.vfs.join("authormap")
96 return self.repo.vfs.join("authormap")
97
97
98 def setbranch(self, branch, pbranches):
98 def setbranch(self, branch, pbranches):
99 if not self.clonebranches:
99 if not self.clonebranches:
100 return
100 return
101
101
102 setbranch = (branch != self.lastbranch)
102 setbranch = (branch != self.lastbranch)
103 self.lastbranch = branch
103 self.lastbranch = branch
104 if not branch:
104 if not branch:
105 branch = 'default'
105 branch = 'default'
106 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
106 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
107 if pbranches:
107 if pbranches:
108 pbranch = pbranches[0][1]
108 pbranch = pbranches[0][1]
109 else:
109 else:
110 pbranch = 'default'
110 pbranch = 'default'
111
111
112 branchpath = os.path.join(self.path, branch)
112 branchpath = os.path.join(self.path, branch)
113 if setbranch:
113 if setbranch:
114 self.after()
114 self.after()
115 try:
115 try:
116 self.repo = hg.repository(self.ui, branchpath)
116 self.repo = hg.repository(self.ui, branchpath)
117 except Exception:
117 except Exception:
118 self.repo = hg.repository(self.ui, branchpath, create=True)
118 self.repo = hg.repository(self.ui, branchpath, create=True)
119 self.before()
119 self.before()
120
120
121 # pbranches may bring revisions from other branches (merge parents)
121 # pbranches may bring revisions from other branches (merge parents)
122 # Make sure we have them, or pull them.
122 # Make sure we have them, or pull them.
123 missings = {}
123 missings = {}
124 for b in pbranches:
124 for b in pbranches:
125 try:
125 try:
126 self.repo.lookup(b[0])
126 self.repo.lookup(b[0])
127 except Exception:
127 except Exception:
128 missings.setdefault(b[1], []).append(b[0])
128 missings.setdefault(b[1], []).append(b[0])
129
129
130 if missings:
130 if missings:
131 self.after()
131 self.after()
132 for pbranch, heads in sorted(missings.iteritems()):
132 for pbranch, heads in sorted(missings.iteritems()):
133 pbranchpath = os.path.join(self.path, pbranch)
133 pbranchpath = os.path.join(self.path, pbranch)
134 prepo = hg.peer(self.ui, {}, pbranchpath)
134 prepo = hg.peer(self.ui, {}, pbranchpath)
135 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
135 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
136 exchange.pull(self.repo, prepo,
136 exchange.pull(self.repo, prepo,
137 [prepo.lookup(h) for h in heads])
137 [prepo.lookup(h) for h in heads])
138 self.before()
138 self.before()
139
139
140 def _rewritetags(self, source, revmap, data):
140 def _rewritetags(self, source, revmap, data):
141 fp = stringio()
141 fp = stringio()
142 for line in data.splitlines():
142 for line in data.splitlines():
143 s = line.split(' ', 1)
143 s = line.split(' ', 1)
144 if len(s) != 2:
144 if len(s) != 2:
145 continue
145 continue
146 revid = revmap.get(source.lookuprev(s[0]))
146 revid = revmap.get(source.lookuprev(s[0]))
147 if not revid:
147 if not revid:
148 if s[0] == nodemod.nullhex:
148 if s[0] == nodemod.nullhex:
149 revid = s[0]
149 revid = s[0]
150 else:
150 else:
151 continue
151 continue
152 fp.write('%s %s\n' % (revid, s[1]))
152 fp.write('%s %s\n' % (revid, s[1]))
153 return fp.getvalue()
153 return fp.getvalue()
154
154
155 def _rewritesubstate(self, source, data):
155 def _rewritesubstate(self, source, data):
156 fp = stringio()
156 fp = stringio()
157 for line in data.splitlines():
157 for line in data.splitlines():
158 s = line.split(' ', 1)
158 s = line.split(' ', 1)
159 if len(s) != 2:
159 if len(s) != 2:
160 continue
160 continue
161
161
162 revid = s[0]
162 revid = s[0]
163 subpath = s[1]
163 subpath = s[1]
164 if revid != nodemod.nullhex:
164 if revid != nodemod.nullhex:
165 revmap = self.subrevmaps.get(subpath)
165 revmap = self.subrevmaps.get(subpath)
166 if revmap is None:
166 if revmap is None:
167 revmap = mapfile(self.ui,
167 revmap = mapfile(self.ui,
168 self.repo.wjoin(subpath, '.hg/shamap'))
168 self.repo.wjoin(subpath, '.hg/shamap'))
169 self.subrevmaps[subpath] = revmap
169 self.subrevmaps[subpath] = revmap
170
170
171 # It is reasonable that one or more of the subrepos don't
171 # It is reasonable that one or more of the subrepos don't
172 # need to be converted, in which case they can be cloned
172 # need to be converted, in which case they can be cloned
173 # into place instead of converted. Therefore, only warn
173 # into place instead of converted. Therefore, only warn
174 # once.
174 # once.
175 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
175 msg = _('no ".hgsubstate" updates will be made for "%s"\n')
176 if len(revmap) == 0:
176 if len(revmap) == 0:
177 sub = self.repo.wvfs.reljoin(subpath, '.hg')
177 sub = self.repo.wvfs.reljoin(subpath, '.hg')
178
178
179 if self.repo.wvfs.exists(sub):
179 if self.repo.wvfs.exists(sub):
180 self.ui.warn(msg % subpath)
180 self.ui.warn(msg % subpath)
181
181
182 newid = revmap.get(revid)
182 newid = revmap.get(revid)
183 if not newid:
183 if not newid:
184 if len(revmap) > 0:
184 if len(revmap) > 0:
185 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
185 self.ui.warn(_("%s is missing from %s/.hg/shamap\n") %
186 (revid, subpath))
186 (revid, subpath))
187 else:
187 else:
188 revid = newid
188 revid = newid
189
189
190 fp.write('%s %s\n' % (revid, subpath))
190 fp.write('%s %s\n' % (revid, subpath))
191
191
192 return fp.getvalue()
192 return fp.getvalue()
193
193
194 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
194 def _calculatemergedfiles(self, source, p1ctx, p2ctx):
195 """Calculates the files from p2 that we need to pull in when merging p1
195 """Calculates the files from p2 that we need to pull in when merging p1
196 and p2, given that the merge is coming from the given source.
196 and p2, given that the merge is coming from the given source.
197
197
198 This prevents us from losing files that only exist in the target p2 and
198 This prevents us from losing files that only exist in the target p2 and
199 that don't come from the source repo (like if you're merging multiple
199 that don't come from the source repo (like if you're merging multiple
200 repositories together).
200 repositories together).
201 """
201 """
202 anc = [p1ctx.ancestor(p2ctx)]
202 anc = [p1ctx.ancestor(p2ctx)]
203 # Calculate what files are coming from p2
203 # Calculate what files are coming from p2
204 actions, diverge, rename = mergemod.calculateupdates(
204 actions, diverge, rename = mergemod.calculateupdates(
205 self.repo, p1ctx, p2ctx, anc,
205 self.repo, p1ctx, p2ctx, anc,
206 True, # branchmerge
206 True, # branchmerge
207 True, # force
207 True, # force
208 False, # acceptremote
208 False, # acceptremote
209 False, # followcopies
209 False, # followcopies
210 )
210 )
211
211
212 for file, (action, info, msg) in actions.iteritems():
212 for file, (action, info, msg) in actions.iteritems():
213 if source.targetfilebelongstosource(file):
213 if source.targetfilebelongstosource(file):
214 # If the file belongs to the source repo, ignore the p2
214 # If the file belongs to the source repo, ignore the p2
215 # since it will be covered by the existing fileset.
215 # since it will be covered by the existing fileset.
216 continue
216 continue
217
217
218 # If the file requires actual merging, abort. We don't have enough
218 # If the file requires actual merging, abort. We don't have enough
219 # context to resolve merges correctly.
219 # context to resolve merges correctly.
220 if action in ['m', 'dm', 'cd', 'dc']:
220 if action in ['m', 'dm', 'cd', 'dc']:
221 raise error.Abort(_("unable to convert merge commit "
221 raise error.Abort(_("unable to convert merge commit "
222 "since target parents do not merge cleanly (file "
222 "since target parents do not merge cleanly (file "
223 "%s, parents %s and %s)") % (file, p1ctx,
223 "%s, parents %s and %s)") % (file, p1ctx,
224 p2ctx))
224 p2ctx))
225 elif action == 'k':
225 elif action == 'k':
226 # 'keep' means nothing changed from p1
226 # 'keep' means nothing changed from p1
227 continue
227 continue
228 else:
228 else:
229 # Any other change means we want to take the p2 version
229 # Any other change means we want to take the p2 version
230 yield file
230 yield file
231
231
232 def putcommit(self, files, copies, parents, commit, source, revmap, full,
232 def putcommit(self, files, copies, parents, commit, source, revmap, full,
233 cleanp2):
233 cleanp2):
234 files = dict(files)
234 files = dict(files)
235
235
236 def getfilectx(repo, memctx, f):
236 def getfilectx(repo, memctx, f):
237 if p2ctx and f in p2files and f not in copies:
237 if p2ctx and f in p2files and f not in copies:
238 self.ui.debug('reusing %s from p2\n' % f)
238 self.ui.debug('reusing %s from p2\n' % f)
239 try:
239 try:
240 return p2ctx[f]
240 return p2ctx[f]
241 except error.ManifestLookupError:
241 except error.ManifestLookupError:
242 # If the file doesn't exist in p2, then we're syncing a
242 # If the file doesn't exist in p2, then we're syncing a
243 # delete, so just return None.
243 # delete, so just return None.
244 return None
244 return None
245 try:
245 try:
246 v = files[f]
246 v = files[f]
247 except KeyError:
247 except KeyError:
248 return None
248 return None
249 data, mode = source.getfile(f, v)
249 data, mode = source.getfile(f, v)
250 if data is None:
250 if data is None:
251 return None
251 return None
252 if f == '.hgtags':
252 if f == '.hgtags':
253 data = self._rewritetags(source, revmap, data)
253 data = self._rewritetags(source, revmap, data)
254 if f == '.hgsubstate':
254 if f == '.hgsubstate':
255 data = self._rewritesubstate(source, data)
255 data = self._rewritesubstate(source, data)
256 return context.memfilectx(self.repo, f, data, 'l' in mode,
256 return context.memfilectx(self.repo, f, data, 'l' in mode,
257 'x' in mode, copies.get(f))
257 'x' in mode, copies.get(f))
258
258
259 pl = []
259 pl = []
260 for p in parents:
260 for p in parents:
261 if p not in pl:
261 if p not in pl:
262 pl.append(p)
262 pl.append(p)
263 parents = pl
263 parents = pl
264 nparents = len(parents)
264 nparents = len(parents)
265 if self.filemapmode and nparents == 1:
265 if self.filemapmode and nparents == 1:
266 m1node = self.repo.changelog.read(nodemod.bin(parents[0]))[0]
266 m1node = self.repo.changelog.read(nodemod.bin(parents[0]))[0]
267 parent = parents[0]
267 parent = parents[0]
268
268
269 if len(parents) < 2:
269 if len(parents) < 2:
270 parents.append(nodemod.nullid)
270 parents.append(nodemod.nullid)
271 if len(parents) < 2:
271 if len(parents) < 2:
272 parents.append(nodemod.nullid)
272 parents.append(nodemod.nullid)
273 p2 = parents.pop(0)
273 p2 = parents.pop(0)
274
274
275 text = commit.desc
275 text = commit.desc
276
276
277 sha1s = re.findall(sha1re, text)
277 sha1s = re.findall(sha1re, text)
278 for sha1 in sha1s:
278 for sha1 in sha1s:
279 oldrev = source.lookuprev(sha1)
279 oldrev = source.lookuprev(sha1)
280 newrev = revmap.get(oldrev)
280 newrev = revmap.get(oldrev)
281 if newrev is not None:
281 if newrev is not None:
282 text = text.replace(sha1, newrev[:len(sha1)])
282 text = text.replace(sha1, newrev[:len(sha1)])
283
283
284 extra = commit.extra.copy()
284 extra = commit.extra.copy()
285
285
286 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
286 sourcename = self.repo.ui.config('convert', 'hg.sourcename')
287 if sourcename:
287 if sourcename:
288 extra['convert_source'] = sourcename
288 extra['convert_source'] = sourcename
289
289
290 for label in ('source', 'transplant_source', 'rebase_source',
290 for label in ('source', 'transplant_source', 'rebase_source',
291 'intermediate-source'):
291 'intermediate-source'):
292 node = extra.get(label)
292 node = extra.get(label)
293
293
294 if node is None:
294 if node is None:
295 continue
295 continue
296
296
297 # Only transplant stores its reference in binary
297 # Only transplant stores its reference in binary
298 if label == 'transplant_source':
298 if label == 'transplant_source':
299 node = nodemod.hex(node)
299 node = nodemod.hex(node)
300
300
301 newrev = revmap.get(node)
301 newrev = revmap.get(node)
302 if newrev is not None:
302 if newrev is not None:
303 if label == 'transplant_source':
303 if label == 'transplant_source':
304 newrev = nodemod.bin(newrev)
304 newrev = nodemod.bin(newrev)
305
305
306 extra[label] = newrev
306 extra[label] = newrev
307
307
308 if self.branchnames and commit.branch:
308 if self.branchnames and commit.branch:
309 extra['branch'] = commit.branch
309 extra['branch'] = commit.branch
310 if commit.rev and commit.saverev:
310 if commit.rev and commit.saverev:
311 extra['convert_revision'] = commit.rev
311 extra['convert_revision'] = commit.rev
312
312
313 while parents:
313 while parents:
314 p1 = p2
314 p1 = p2
315 p2 = parents.pop(0)
315 p2 = parents.pop(0)
316 p1ctx = self.repo[p1]
316 p1ctx = self.repo[p1]
317 p2ctx = None
317 p2ctx = None
318 if p2 != nodemod.nullid:
318 if p2 != nodemod.nullid:
319 p2ctx = self.repo[p2]
319 p2ctx = self.repo[p2]
320 fileset = set(files)
320 fileset = set(files)
321 if full:
321 if full:
322 fileset.update(self.repo[p1])
322 fileset.update(self.repo[p1])
323 fileset.update(self.repo[p2])
323 fileset.update(self.repo[p2])
324
324
325 if p2ctx:
325 if p2ctx:
326 p2files = set(cleanp2)
326 p2files = set(cleanp2)
327 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
327 for file in self._calculatemergedfiles(source, p1ctx, p2ctx):
328 p2files.add(file)
328 p2files.add(file)
329 fileset.add(file)
329 fileset.add(file)
330
330
331 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
331 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
332 getfilectx, commit.author, commit.date, extra)
332 getfilectx, commit.author, commit.date, extra)
333
333
334 # We won't know if the conversion changes the node until after the
334 # We won't know if the conversion changes the node until after the
335 # commit, so copy the source's phase for now.
335 # commit, so copy the source's phase for now.
336 self.repo.ui.setconfig('phases', 'new-commit',
336 self.repo.ui.setconfig('phases', 'new-commit',
337 phases.phasenames[commit.phase], 'convert')
337 phases.phasenames[commit.phase], 'convert')
338
338
339 with self.repo.transaction("convert") as tr:
339 with self.repo.transaction("convert") as tr:
340 node = nodemod.hex(self.repo.commitctx(ctx))
340 node = nodemod.hex(self.repo.commitctx(ctx))
341
341
342 # If the node value has changed, but the phase is lower than
342 # If the node value has changed, but the phase is lower than
343 # draft, set it back to draft since it hasn't been exposed
343 # draft, set it back to draft since it hasn't been exposed
344 # anywhere.
344 # anywhere.
345 if commit.rev != node:
345 if commit.rev != node:
346 ctx = self.repo[node]
346 ctx = self.repo[node]
347 if ctx.phase() < phases.draft:
347 if ctx.phase() < phases.draft:
348 phases.registernew(self.repo, tr, phases.draft,
348 phases.registernew(self.repo, tr, phases.draft,
349 [ctx.node()])
349 [ctx.node()])
350
350
351 text = "(octopus merge fixup)\n"
351 text = "(octopus merge fixup)\n"
352 p2 = node
352 p2 = node
353
353
354 if self.filemapmode and nparents == 1:
354 if self.filemapmode and nparents == 1:
355 man = self.repo.manifestlog._revlog
355 man = self.repo.manifestlog._revlog
356 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
356 mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
357 closed = 'close' in commit.extra
357 closed = 'close' in commit.extra
358 if not closed and not man.cmp(m1node, man.revision(mnode)):
358 if not closed and not man.cmp(m1node, man.revision(mnode)):
359 self.ui.status(_("filtering out empty revision\n"))
359 self.ui.status(_("filtering out empty revision\n"))
360 self.repo.rollback(force=True)
360 self.repo.rollback(force=True)
361 return parent
361 return parent
362 return p2
362 return p2
363
363
364 def puttags(self, tags):
364 def puttags(self, tags):
365 try:
365 try:
366 parentctx = self.repo[self.tagsbranch]
366 parentctx = self.repo[self.tagsbranch]
367 tagparent = parentctx.node()
367 tagparent = parentctx.node()
368 except error.RepoError:
368 except error.RepoError:
369 parentctx = None
369 parentctx = None
370 tagparent = nodemod.nullid
370 tagparent = nodemod.nullid
371
371
372 oldlines = set()
372 oldlines = set()
373 for branch, heads in self.repo.branchmap().iteritems():
373 for branch, heads in self.repo.branchmap().iteritems():
374 for h in heads:
374 for h in heads:
375 if '.hgtags' in self.repo[h]:
375 if '.hgtags' in self.repo[h]:
376 oldlines.update(
376 oldlines.update(
377 set(self.repo[h]['.hgtags'].data().splitlines(True)))
377 set(self.repo[h]['.hgtags'].data().splitlines(True)))
378 oldlines = sorted(list(oldlines))
378 oldlines = sorted(list(oldlines))
379
379
380 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
380 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
381 if newlines == oldlines:
381 if newlines == oldlines:
382 return None, None
382 return None, None
383
383
384 # if the old and new tags match, then there is nothing to update
384 # if the old and new tags match, then there is nothing to update
385 oldtags = set()
385 oldtags = set()
386 newtags = set()
386 newtags = set()
387 for line in oldlines:
387 for line in oldlines:
388 s = line.strip().split(' ', 1)
388 s = line.strip().split(' ', 1)
389 if len(s) != 2:
389 if len(s) != 2:
390 continue
390 continue
391 oldtags.add(s[1])
391 oldtags.add(s[1])
392 for line in newlines:
392 for line in newlines:
393 s = line.strip().split(' ', 1)
393 s = line.strip().split(' ', 1)
394 if len(s) != 2:
394 if len(s) != 2:
395 continue
395 continue
396 if s[1] not in oldtags:
396 if s[1] not in oldtags:
397 newtags.add(s[1].strip())
397 newtags.add(s[1].strip())
398
398
399 if not newtags:
399 if not newtags:
400 return None, None
400 return None, None
401
401
402 data = "".join(newlines)
402 data = "".join(newlines)
403 def getfilectx(repo, memctx, f):
403 def getfilectx(repo, memctx, f):
404 return context.memfilectx(repo, f, data, False, False, None)
404 return context.memfilectx(repo, f, data, False, False, None)
405
405
406 self.ui.status(_("updating tags\n"))
406 self.ui.status(_("updating tags\n"))
407 date = "%s 0" % int(time.mktime(time.gmtime()))
407 date = "%s 0" % int(time.mktime(time.gmtime()))
408 extra = {'branch': self.tagsbranch}
408 extra = {'branch': self.tagsbranch}
409 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
409 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
410 [".hgtags"], getfilectx, "convert-repo", date,
410 [".hgtags"], getfilectx, "convert-repo", date,
411 extra)
411 extra)
412 node = self.repo.commitctx(ctx)
412 node = self.repo.commitctx(ctx)
413 return nodemod.hex(node), nodemod.hex(tagparent)
413 return nodemod.hex(node), nodemod.hex(tagparent)
414
414
415 def setfilemapmode(self, active):
415 def setfilemapmode(self, active):
416 self.filemapmode = active
416 self.filemapmode = active
417
417
418 def putbookmarks(self, updatedbookmark):
418 def putbookmarks(self, updatedbookmark):
419 if not len(updatedbookmark):
419 if not len(updatedbookmark):
420 return
420 return
421 wlock = lock = tr = None
421 wlock = lock = tr = None
422 try:
422 try:
423 wlock = self.repo.wlock()
423 wlock = self.repo.wlock()
424 lock = self.repo.lock()
424 lock = self.repo.lock()
425 tr = self.repo.transaction('bookmark')
425 tr = self.repo.transaction('bookmark')
426 self.ui.status(_("updating bookmarks\n"))
426 self.ui.status(_("updating bookmarks\n"))
427 destmarks = self.repo._bookmarks
427 destmarks = self.repo._bookmarks
428 changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark]))
428 changes = [(bookmark, nodemod.bin(updatedbookmark[bookmark]))
429 for bookmark in updatedbookmark]
429 for bookmark in updatedbookmark]
430 destmarks.applychanges(self.repo, tr, changes)
430 destmarks.applychanges(self.repo, tr, changes)
431 tr.close()
431 tr.close()
432 finally:
432 finally:
433 lockmod.release(lock, wlock, tr)
433 lockmod.release(lock, wlock, tr)
434
434
435 def hascommitfrommap(self, rev):
435 def hascommitfrommap(self, rev):
436 # the exact semantics of clonebranches is unclear so we can't say no
436 # the exact semantics of clonebranches is unclear so we can't say no
437 return rev in self.repo or self.clonebranches
437 return rev in self.repo or self.clonebranches
438
438
439 def hascommitforsplicemap(self, rev):
439 def hascommitforsplicemap(self, rev):
440 if rev not in self.repo and self.clonebranches:
440 if rev not in self.repo and self.clonebranches:
441 raise error.Abort(_('revision %s not found in destination '
441 raise error.Abort(_('revision %s not found in destination '
442 'repository (lookups with clonebranches=true '
442 'repository (lookups with clonebranches=true '
443 'are not implemented)') % rev)
443 'are not implemented)') % rev)
444 return rev in self.repo
444 return rev in self.repo
445
445
446 class mercurial_source(common.converter_source):
446 class mercurial_source(common.converter_source):
447 def __init__(self, ui, path, revs=None):
447 def __init__(self, ui, path, revs=None):
448 common.converter_source.__init__(self, ui, path, revs)
448 common.converter_source.__init__(self, ui, path, revs)
449 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
449 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
450 self.ignored = set()
450 self.ignored = set()
451 self.saverev = ui.configbool('convert', 'hg.saverev', False)
451 self.saverev = ui.configbool('convert', 'hg.saverev', False)
452 try:
452 try:
453 self.repo = hg.repository(self.ui, path)
453 self.repo = hg.repository(self.ui, path)
454 # try to provoke an exception if this isn't really a hg
454 # try to provoke an exception if this isn't really a hg
455 # repo, but some other bogus compatible-looking url
455 # repo, but some other bogus compatible-looking url
456 if not self.repo.local():
456 if not self.repo.local():
457 raise error.RepoError
457 raise error.RepoError
458 except error.RepoError:
458 except error.RepoError:
459 ui.traceback()
459 ui.traceback()
460 raise NoRepo(_("%s is not a local Mercurial repository") % path)
460 raise NoRepo(_("%s is not a local Mercurial repository") % path)
461 self.lastrev = None
461 self.lastrev = None
462 self.lastctx = None
462 self.lastctx = None
463 self._changescache = None, None
463 self._changescache = None, None
464 self.convertfp = None
464 self.convertfp = None
465 # Restrict converted revisions to startrev descendants
465 # Restrict converted revisions to startrev descendants
466 startnode = ui.config('convert', 'hg.startrev')
466 startnode = ui.config('convert', 'hg.startrev')
467 hgrevs = ui.config('convert', 'hg.revs')
467 hgrevs = ui.config('convert', 'hg.revs')
468 if hgrevs is None:
468 if hgrevs is None:
469 if startnode is not None:
469 if startnode is not None:
470 try:
470 try:
471 startnode = self.repo.lookup(startnode)
471 startnode = self.repo.lookup(startnode)
472 except error.RepoError:
472 except error.RepoError:
473 raise error.Abort(_('%s is not a valid start revision')
473 raise error.Abort(_('%s is not a valid start revision')
474 % startnode)
474 % startnode)
475 startrev = self.repo.changelog.rev(startnode)
475 startrev = self.repo.changelog.rev(startnode)
476 children = {startnode: 1}
476 children = {startnode: 1}
477 for r in self.repo.changelog.descendants([startrev]):
477 for r in self.repo.changelog.descendants([startrev]):
478 children[self.repo.changelog.node(r)] = 1
478 children[self.repo.changelog.node(r)] = 1
479 self.keep = children.__contains__
479 self.keep = children.__contains__
480 else:
480 else:
481 self.keep = util.always
481 self.keep = util.always
482 if revs:
482 if revs:
483 self._heads = [self.repo[r].node() for r in revs]
483 self._heads = [self.repo[r].node() for r in revs]
484 else:
484 else:
485 self._heads = self.repo.heads()
485 self._heads = self.repo.heads()
486 else:
486 else:
487 if revs or startnode is not None:
487 if revs or startnode is not None:
488 raise error.Abort(_('hg.revs cannot be combined with '
488 raise error.Abort(_('hg.revs cannot be combined with '
489 'hg.startrev or --rev'))
489 'hg.startrev or --rev'))
490 nodes = set()
490 nodes = set()
491 parents = set()
491 parents = set()
492 for r in scmutil.revrange(self.repo, [hgrevs]):
492 for r in scmutil.revrange(self.repo, [hgrevs]):
493 ctx = self.repo[r]
493 ctx = self.repo[r]
494 nodes.add(ctx.node())
494 nodes.add(ctx.node())
495 parents.update(p.node() for p in ctx.parents())
495 parents.update(p.node() for p in ctx.parents())
496 self.keep = nodes.__contains__
496 self.keep = nodes.__contains__
497 self._heads = nodes - parents
497 self._heads = nodes - parents
498
498
499 def _changectx(self, rev):
499 def _changectx(self, rev):
500 if self.lastrev != rev:
500 if self.lastrev != rev:
501 self.lastctx = self.repo[rev]
501 self.lastctx = self.repo[rev]
502 self.lastrev = rev
502 self.lastrev = rev
503 return self.lastctx
503 return self.lastctx
504
504
505 def _parents(self, ctx):
505 def _parents(self, ctx):
506 return [p for p in ctx.parents() if p and self.keep(p.node())]
506 return [p for p in ctx.parents() if p and self.keep(p.node())]
507
507
508 def getheads(self):
508 def getheads(self):
509 return [nodemod.hex(h) for h in self._heads if self.keep(h)]
509 return [nodemod.hex(h) for h in self._heads if self.keep(h)]
510
510
511 def getfile(self, name, rev):
511 def getfile(self, name, rev):
512 try:
512 try:
513 fctx = self._changectx(rev)[name]
513 fctx = self._changectx(rev)[name]
514 return fctx.data(), fctx.flags()
514 return fctx.data(), fctx.flags()
515 except error.LookupError:
515 except error.LookupError:
516 return None, None
516 return None, None
517
517
518 def _changedfiles(self, ctx1, ctx2):
518 def _changedfiles(self, ctx1, ctx2):
519 ma, r = [], []
519 ma, r = [], []
520 maappend = ma.append
520 maappend = ma.append
521 rappend = r.append
521 rappend = r.append
522 d = ctx1.manifest().diff(ctx2.manifest())
522 d = ctx1.manifest().diff(ctx2.manifest())
523 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
523 for f, ((node1, flag1), (node2, flag2)) in d.iteritems():
524 if node2 is None:
524 if node2 is None:
525 rappend(f)
525 rappend(f)
526 else:
526 else:
527 maappend(f)
527 maappend(f)
528 return ma, r
528 return ma, r
529
529
530 def getchanges(self, rev, full):
530 def getchanges(self, rev, full):
531 ctx = self._changectx(rev)
531 ctx = self._changectx(rev)
532 parents = self._parents(ctx)
532 parents = self._parents(ctx)
533 if full or not parents:
533 if full or not parents:
534 files = copyfiles = ctx.manifest()
534 files = copyfiles = ctx.manifest()
535 if parents:
535 if parents:
536 if self._changescache[0] == rev:
536 if self._changescache[0] == rev:
537 ma, r = self._changescache[1]
537 ma, r = self._changescache[1]
538 else:
538 else:
539 ma, r = self._changedfiles(parents[0], ctx)
539 ma, r = self._changedfiles(parents[0], ctx)
540 if not full:
540 if not full:
541 files = ma + r
541 files = ma + r
542 copyfiles = ma
542 copyfiles = ma
543 # _getcopies() is also run for roots and before filtering so missing
543 # _getcopies() is also run for roots and before filtering so missing
544 # revlogs are detected early
544 # revlogs are detected early
545 copies = self._getcopies(ctx, parents, copyfiles)
545 copies = self._getcopies(ctx, parents, copyfiles)
546 cleanp2 = set()
546 cleanp2 = set()
547 if len(parents) == 2:
547 if len(parents) == 2:
548 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
548 d = parents[1].manifest().diff(ctx.manifest(), clean=True)
549 for f, value in d.iteritems():
549 for f, value in d.iteritems():
550 if value is None:
550 if value is None:
551 cleanp2.add(f)
551 cleanp2.add(f)
552 changes = [(f, rev) for f in files if f not in self.ignored]
552 changes = [(f, rev) for f in files if f not in self.ignored]
553 changes.sort()
553 changes.sort()
554 return changes, copies, cleanp2
554 return changes, copies, cleanp2
555
555
556 def _getcopies(self, ctx, parents, files):
556 def _getcopies(self, ctx, parents, files):
557 copies = {}
557 copies = {}
558 for name in files:
558 for name in files:
559 if name in self.ignored:
559 if name in self.ignored:
560 continue
560 continue
561 try:
561 try:
562 copysource, _copynode = ctx.filectx(name).renamed()
562 copysource, _copynode = ctx.filectx(name).renamed()
563 if copysource in self.ignored:
563 if copysource in self.ignored:
564 continue
564 continue
565 # Ignore copy sources not in parent revisions
565 # Ignore copy sources not in parent revisions
566 found = False
566 found = False
567 for p in parents:
567 for p in parents:
568 if copysource in p:
568 if copysource in p:
569 found = True
569 found = True
570 break
570 break
571 if not found:
571 if not found:
572 continue
572 continue
573 copies[name] = copysource
573 copies[name] = copysource
574 except TypeError:
574 except TypeError:
575 pass
575 pass
576 except error.LookupError as e:
576 except error.LookupError as e:
577 if not self.ignoreerrors:
577 if not self.ignoreerrors:
578 raise
578 raise
579 self.ignored.add(name)
579 self.ignored.add(name)
580 self.ui.warn(_('ignoring: %s\n') % e)
580 self.ui.warn(_('ignoring: %s\n') % e)
581 return copies
581 return copies
582
582
583 def getcommit(self, rev):
583 def getcommit(self, rev):
584 ctx = self._changectx(rev)
584 ctx = self._changectx(rev)
585 _parents = self._parents(ctx)
585 _parents = self._parents(ctx)
586 parents = [p.hex() for p in _parents]
586 parents = [p.hex() for p in _parents]
587 optparents = [p.hex() for p in ctx.parents() if p and p not in _parents]
587 optparents = [p.hex() for p in ctx.parents() if p and p not in _parents]
588 crev = rev
588 crev = rev
589
589
590 return common.commit(author=ctx.user(),
590 return common.commit(author=ctx.user(),
591 date=util.datestr(ctx.date(),
591 date=util.datestr(ctx.date(),
592 '%Y-%m-%d %H:%M:%S %1%2'),
592 '%Y-%m-%d %H:%M:%S %1%2'),
593 desc=ctx.description(),
593 desc=ctx.description(),
594 rev=crev,
594 rev=crev,
595 parents=parents,
595 parents=parents,
596 optparents=optparents,
596 optparents=optparents,
597 branch=ctx.branch(),
597 branch=ctx.branch(),
598 extra=ctx.extra(),
598 extra=ctx.extra(),
599 sortkey=ctx.rev(),
599 sortkey=ctx.rev(),
600 saverev=self.saverev,
600 saverev=self.saverev,
601 phase=ctx.phase())
601 phase=ctx.phase())
602
602
603 def gettags(self):
603 def gettags(self):
604 # This will get written to .hgtags, filter non global tags out.
604 # This will get written to .hgtags, filter non global tags out.
605 tags = [t for t in self.repo.tagslist()
605 tags = [t for t in self.repo.tagslist()
606 if self.repo.tagtype(t[0]) == 'global']
606 if self.repo.tagtype(t[0]) == 'global']
607 return dict([(name, nodemod.hex(node)) for name, node in tags
607 return dict([(name, nodemod.hex(node)) for name, node in tags
608 if self.keep(node)])
608 if self.keep(node)])
609
609
610 def getchangedfiles(self, rev, i):
610 def getchangedfiles(self, rev, i):
611 ctx = self._changectx(rev)
611 ctx = self._changectx(rev)
612 parents = self._parents(ctx)
612 parents = self._parents(ctx)
613 if not parents and i is None:
613 if not parents and i is None:
614 i = 0
614 i = 0
615 ma, r = ctx.manifest().keys(), []
615 ma, r = ctx.manifest().keys(), []
616 else:
616 else:
617 i = i or 0
617 i = i or 0
618 ma, r = self._changedfiles(parents[i], ctx)
618 ma, r = self._changedfiles(parents[i], ctx)
619 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
619 ma, r = [[f for f in l if f not in self.ignored] for l in (ma, r)]
620
620
621 if i == 0:
621 if i == 0:
622 self._changescache = (rev, (ma, r))
622 self._changescache = (rev, (ma, r))
623
623
624 return ma + r
624 return ma + r
625
625
626 def converted(self, rev, destrev):
626 def converted(self, rev, destrev):
627 if self.convertfp is None:
627 if self.convertfp is None:
628 self.convertfp = open(self.repo.vfs.join('shamap'), 'a')
628 self.convertfp = open(self.repo.vfs.join('shamap'), 'a')
629 self.convertfp.write('%s %s\n' % (destrev, rev))
629 self.convertfp.write('%s %s\n' % (destrev, rev))
630 self.convertfp.flush()
630 self.convertfp.flush()
631
631
632 def before(self):
632 def before(self):
633 self.ui.debug('run hg source pre-conversion action\n')
633 self.ui.debug('run hg source pre-conversion action\n')
634
634
635 def after(self):
635 def after(self):
636 self.ui.debug('run hg source post-conversion action\n')
636 self.ui.debug('run hg source post-conversion action\n')
637
637
638 def hasnativeorder(self):
638 def hasnativeorder(self):
639 return True
639 return True
640
640
641 def hasnativeclose(self):
641 def hasnativeclose(self):
642 return True
642 return True
643
643
644 def lookuprev(self, rev):
644 def lookuprev(self, rev):
645 try:
645 try:
646 return nodemod.hex(self.repo.lookup(rev))
646 return nodemod.hex(self.repo.lookup(rev))
647 except (error.RepoError, error.LookupError):
647 except (error.RepoError, error.LookupError):
648 return None
648 return None
649
649
650 def getbookmarks(self):
650 def getbookmarks(self):
651 return bookmarks.listbookmarks(self.repo)
651 return bookmarks.listbookmarks(self.repo)
652
652
653 def checkrevformat(self, revstr, mapname='splicemap'):
653 def checkrevformat(self, revstr, mapname='splicemap'):
654 """ Mercurial, revision string is a 40 byte hex """
654 """ Mercurial, revision string is a 40 byte hex """
655 self.checkhexformat(revstr, mapname)
655 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now