##// END OF EJS Templates
configitems: register the 'convert.hg.saverev' config
Boris Feld -
r34167:9bda5ce9 default
parent child Browse files
Show More
@@ -1,558 +1,561 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 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',
73 configitem('convert', 'hg.ignoreerrors',
74 default=False,
74 default=False,
75 )
75 )
76 configitem('convert', 'hg.revs',
76 configitem('convert', 'hg.revs',
77 default=None,
77 default=None,
78 )
78 )
79 configitem('convert', 'hg.saverev',
80 default=False,
81 )
79
82
80 # Commands definition was moved elsewhere to ease demandload job.
83 # Commands definition was moved elsewhere to ease demandload job.
81
84
82 @command('convert',
85 @command('convert',
83 [('', 'authors', '',
86 [('', 'authors', '',
84 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
87 _('username mapping filename (DEPRECATED) (use --authormap instead)'),
85 _('FILE')),
88 _('FILE')),
86 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
89 ('s', 'source-type', '', _('source repository type'), _('TYPE')),
87 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
90 ('d', 'dest-type', '', _('destination repository type'), _('TYPE')),
88 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
91 ('r', 'rev', [], _('import up to source revision REV'), _('REV')),
89 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
92 ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
90 ('', 'filemap', '', _('remap file names using contents of file'),
93 ('', 'filemap', '', _('remap file names using contents of file'),
91 _('FILE')),
94 _('FILE')),
92 ('', 'full', None,
95 ('', 'full', None,
93 _('apply filemap changes by converting all files again')),
96 _('apply filemap changes by converting all files again')),
94 ('', 'splicemap', '', _('splice synthesized history into place'),
97 ('', 'splicemap', '', _('splice synthesized history into place'),
95 _('FILE')),
98 _('FILE')),
96 ('', 'branchmap', '', _('change branch names while converting'),
99 ('', 'branchmap', '', _('change branch names while converting'),
97 _('FILE')),
100 _('FILE')),
98 ('', 'branchsort', None, _('try to sort changesets by branches')),
101 ('', 'branchsort', None, _('try to sort changesets by branches')),
99 ('', 'datesort', None, _('try to sort changesets by date')),
102 ('', 'datesort', None, _('try to sort changesets by date')),
100 ('', 'sourcesort', None, _('preserve source changesets order')),
103 ('', 'sourcesort', None, _('preserve source changesets order')),
101 ('', 'closesort', None, _('try to reorder closed revisions'))],
104 ('', 'closesort', None, _('try to reorder closed revisions'))],
102 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
105 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
103 norepo=True)
106 norepo=True)
104 def convert(ui, src, dest=None, revmapfile=None, **opts):
107 def convert(ui, src, dest=None, revmapfile=None, **opts):
105 """convert a foreign SCM repository to a Mercurial one.
108 """convert a foreign SCM repository to a Mercurial one.
106
109
107 Accepted source formats [identifiers]:
110 Accepted source formats [identifiers]:
108
111
109 - Mercurial [hg]
112 - Mercurial [hg]
110 - CVS [cvs]
113 - CVS [cvs]
111 - Darcs [darcs]
114 - Darcs [darcs]
112 - git [git]
115 - git [git]
113 - Subversion [svn]
116 - Subversion [svn]
114 - Monotone [mtn]
117 - Monotone [mtn]
115 - GNU Arch [gnuarch]
118 - GNU Arch [gnuarch]
116 - Bazaar [bzr]
119 - Bazaar [bzr]
117 - Perforce [p4]
120 - Perforce [p4]
118
121
119 Accepted destination formats [identifiers]:
122 Accepted destination formats [identifiers]:
120
123
121 - Mercurial [hg]
124 - Mercurial [hg]
122 - Subversion [svn] (history on branches is not preserved)
125 - Subversion [svn] (history on branches is not preserved)
123
126
124 If no revision is given, all revisions will be converted.
127 If no revision is given, all revisions will be converted.
125 Otherwise, convert will only import up to the named revision
128 Otherwise, convert will only import up to the named revision
126 (given in a format understood by the source).
129 (given in a format understood by the source).
127
130
128 If no destination directory name is specified, it defaults to the
131 If no destination directory name is specified, it defaults to the
129 basename of the source with ``-hg`` appended. If the destination
132 basename of the source with ``-hg`` appended. If the destination
130 repository doesn't exist, it will be created.
133 repository doesn't exist, it will be created.
131
134
132 By default, all sources except Mercurial will use --branchsort.
135 By default, all sources except Mercurial will use --branchsort.
133 Mercurial uses --sourcesort to preserve original revision numbers
136 Mercurial uses --sourcesort to preserve original revision numbers
134 order. Sort modes have the following effects:
137 order. Sort modes have the following effects:
135
138
136 --branchsort convert from parent to child revision when possible,
139 --branchsort convert from parent to child revision when possible,
137 which means branches are usually converted one after
140 which means branches are usually converted one after
138 the other. It generates more compact repositories.
141 the other. It generates more compact repositories.
139
142
140 --datesort sort revisions by date. Converted repositories have
143 --datesort sort revisions by date. Converted repositories have
141 good-looking changelogs but are often an order of
144 good-looking changelogs but are often an order of
142 magnitude larger than the same ones generated by
145 magnitude larger than the same ones generated by
143 --branchsort.
146 --branchsort.
144
147
145 --sourcesort try to preserve source revisions order, only
148 --sourcesort try to preserve source revisions order, only
146 supported by Mercurial sources.
149 supported by Mercurial sources.
147
150
148 --closesort try to move closed revisions as close as possible
151 --closesort try to move closed revisions as close as possible
149 to parent branches, only supported by Mercurial
152 to parent branches, only supported by Mercurial
150 sources.
153 sources.
151
154
152 If ``REVMAP`` isn't given, it will be put in a default location
155 If ``REVMAP`` isn't given, it will be put in a default location
153 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
156 (``<dest>/.hg/shamap`` by default). The ``REVMAP`` is a simple
154 text file that maps each source commit ID to the destination ID
157 text file that maps each source commit ID to the destination ID
155 for that revision, like so::
158 for that revision, like so::
156
159
157 <source ID> <destination ID>
160 <source ID> <destination ID>
158
161
159 If the file doesn't exist, it's automatically created. It's
162 If the file doesn't exist, it's automatically created. It's
160 updated on each commit copied, so :hg:`convert` can be interrupted
163 updated on each commit copied, so :hg:`convert` can be interrupted
161 and can be run repeatedly to copy new commits.
164 and can be run repeatedly to copy new commits.
162
165
163 The authormap is a simple text file that maps each source commit
166 The authormap is a simple text file that maps each source commit
164 author to a destination commit author. It is handy for source SCMs
167 author to a destination commit author. It is handy for source SCMs
165 that use unix logins to identify authors (e.g.: CVS). One line per
168 that use unix logins to identify authors (e.g.: CVS). One line per
166 author mapping and the line format is::
169 author mapping and the line format is::
167
170
168 source author = destination author
171 source author = destination author
169
172
170 Empty lines and lines starting with a ``#`` are ignored.
173 Empty lines and lines starting with a ``#`` are ignored.
171
174
172 The filemap is a file that allows filtering and remapping of files
175 The filemap is a file that allows filtering and remapping of files
173 and directories. Each line can contain one of the following
176 and directories. Each line can contain one of the following
174 directives::
177 directives::
175
178
176 include path/to/file-or-dir
179 include path/to/file-or-dir
177
180
178 exclude path/to/file-or-dir
181 exclude path/to/file-or-dir
179
182
180 rename path/to/source path/to/destination
183 rename path/to/source path/to/destination
181
184
182 Comment lines start with ``#``. A specified path matches if it
185 Comment lines start with ``#``. A specified path matches if it
183 equals the full relative name of a file or one of its parent
186 equals the full relative name of a file or one of its parent
184 directories. The ``include`` or ``exclude`` directive with the
187 directories. The ``include`` or ``exclude`` directive with the
185 longest matching path applies, so line order does not matter.
188 longest matching path applies, so line order does not matter.
186
189
187 The ``include`` directive causes a file, or all files under a
190 The ``include`` directive causes a file, or all files under a
188 directory, to be included in the destination repository. The default
191 directory, to be included in the destination repository. The default
189 if there are no ``include`` statements is to include everything.
192 if there are no ``include`` statements is to include everything.
190 If there are any ``include`` statements, nothing else is included.
193 If there are any ``include`` statements, nothing else is included.
191 The ``exclude`` directive causes files or directories to
194 The ``exclude`` directive causes files or directories to
192 be omitted. The ``rename`` directive renames a file or directory if
195 be omitted. The ``rename`` directive renames a file or directory if
193 it is converted. To rename from a subdirectory into the root of
196 it is converted. To rename from a subdirectory into the root of
194 the repository, use ``.`` as the path to rename to.
197 the repository, use ``.`` as the path to rename to.
195
198
196 ``--full`` will make sure the converted changesets contain exactly
199 ``--full`` will make sure the converted changesets contain exactly
197 the right files with the right content. It will make a full
200 the right files with the right content. It will make a full
198 conversion of all files, not just the ones that have
201 conversion of all files, not just the ones that have
199 changed. Files that already are correct will not be changed. This
202 changed. Files that already are correct will not be changed. This
200 can be used to apply filemap changes when converting
203 can be used to apply filemap changes when converting
201 incrementally. This is currently only supported for Mercurial and
204 incrementally. This is currently only supported for Mercurial and
202 Subversion.
205 Subversion.
203
206
204 The splicemap is a file that allows insertion of synthetic
207 The splicemap is a file that allows insertion of synthetic
205 history, letting you specify the parents of a revision. This is
208 history, letting you specify the parents of a revision. This is
206 useful if you want to e.g. give a Subversion merge two parents, or
209 useful if you want to e.g. give a Subversion merge two parents, or
207 graft two disconnected series of history together. Each entry
210 graft two disconnected series of history together. Each entry
208 contains a key, followed by a space, followed by one or two
211 contains a key, followed by a space, followed by one or two
209 comma-separated values::
212 comma-separated values::
210
213
211 key parent1, parent2
214 key parent1, parent2
212
215
213 The key is the revision ID in the source
216 The key is the revision ID in the source
214 revision control system whose parents should be modified (same
217 revision control system whose parents should be modified (same
215 format as a key in .hg/shamap). The values are the revision IDs
218 format as a key in .hg/shamap). The values are the revision IDs
216 (in either the source or destination revision control system) that
219 (in either the source or destination revision control system) that
217 should be used as the new parents for that node. For example, if
220 should be used as the new parents for that node. For example, if
218 you have merged "release-1.0" into "trunk", then you should
221 you have merged "release-1.0" into "trunk", then you should
219 specify the revision on "trunk" as the first parent and the one on
222 specify the revision on "trunk" as the first parent and the one on
220 the "release-1.0" branch as the second.
223 the "release-1.0" branch as the second.
221
224
222 The branchmap is a file that allows you to rename a branch when it is
225 The branchmap is a file that allows you to rename a branch when it is
223 being brought in from whatever external repository. When used in
226 being brought in from whatever external repository. When used in
224 conjunction with a splicemap, it allows for a powerful combination
227 conjunction with a splicemap, it allows for a powerful combination
225 to help fix even the most badly mismanaged repositories and turn them
228 to help fix even the most badly mismanaged repositories and turn them
226 into nicely structured Mercurial repositories. The branchmap contains
229 into nicely structured Mercurial repositories. The branchmap contains
227 lines of the form::
230 lines of the form::
228
231
229 original_branch_name new_branch_name
232 original_branch_name new_branch_name
230
233
231 where "original_branch_name" is the name of the branch in the
234 where "original_branch_name" is the name of the branch in the
232 source repository, and "new_branch_name" is the name of the branch
235 source repository, and "new_branch_name" is the name of the branch
233 is the destination repository. No whitespace is allowed in the new
236 is the destination repository. No whitespace is allowed in the new
234 branch name. This can be used to (for instance) move code in one
237 branch name. This can be used to (for instance) move code in one
235 repository from "default" to a named branch.
238 repository from "default" to a named branch.
236
239
237 Mercurial Source
240 Mercurial Source
238 ################
241 ################
239
242
240 The Mercurial source recognizes the following configuration
243 The Mercurial source recognizes the following configuration
241 options, which you can set on the command line with ``--config``:
244 options, which you can set on the command line with ``--config``:
242
245
243 :convert.hg.ignoreerrors: ignore integrity errors when reading.
246 :convert.hg.ignoreerrors: ignore integrity errors when reading.
244 Use it to fix Mercurial repositories with missing revlogs, by
247 Use it to fix Mercurial repositories with missing revlogs, by
245 converting from and to Mercurial. Default is False.
248 converting from and to Mercurial. Default is False.
246
249
247 :convert.hg.saverev: store original revision ID in changeset
250 :convert.hg.saverev: store original revision ID in changeset
248 (forces target IDs to change). It takes a boolean argument and
251 (forces target IDs to change). It takes a boolean argument and
249 defaults to False.
252 defaults to False.
250
253
251 :convert.hg.startrev: specify the initial Mercurial revision.
254 :convert.hg.startrev: specify the initial Mercurial revision.
252 The default is 0.
255 The default is 0.
253
256
254 :convert.hg.revs: revset specifying the source revisions to convert.
257 :convert.hg.revs: revset specifying the source revisions to convert.
255
258
256 CVS Source
259 CVS Source
257 ##########
260 ##########
258
261
259 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
262 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
260 to indicate the starting point of what will be converted. Direct
263 to indicate the starting point of what will be converted. Direct
261 access to the repository files is not needed, unless of course the
264 access to the repository files is not needed, unless of course the
262 repository is ``:local:``. The conversion uses the top level
265 repository is ``:local:``. The conversion uses the top level
263 directory in the sandbox to find the CVS repository, and then uses
266 directory in the sandbox to find the CVS repository, and then uses
264 CVS rlog commands to find files to convert. This means that unless
267 CVS rlog commands to find files to convert. This means that unless
265 a filemap is given, all files under the starting directory will be
268 a filemap is given, all files under the starting directory will be
266 converted, and that any directory reorganization in the CVS
269 converted, and that any directory reorganization in the CVS
267 sandbox is ignored.
270 sandbox is ignored.
268
271
269 The following options can be used with ``--config``:
272 The following options can be used with ``--config``:
270
273
271 :convert.cvsps.cache: Set to False to disable remote log caching,
274 :convert.cvsps.cache: Set to False to disable remote log caching,
272 for testing and debugging purposes. Default is True.
275 for testing and debugging purposes. Default is True.
273
276
274 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
277 :convert.cvsps.fuzz: Specify the maximum time (in seconds) that is
275 allowed between commits with identical user and log message in
278 allowed between commits with identical user and log message in
276 a single changeset. When very large files were checked in as
279 a single changeset. When very large files were checked in as
277 part of a changeset then the default may not be long enough.
280 part of a changeset then the default may not be long enough.
278 The default is 60.
281 The default is 60.
279
282
280 :convert.cvsps.logencoding: Specify encoding name to be used for
283 :convert.cvsps.logencoding: Specify encoding name to be used for
281 transcoding CVS log messages. Multiple encoding names can be
284 transcoding CVS log messages. Multiple encoding names can be
282 specified as a list (see :hg:`help config.Syntax`), but only
285 specified as a list (see :hg:`help config.Syntax`), but only
283 the first acceptable encoding in the list is used per CVS log
286 the first acceptable encoding in the list is used per CVS log
284 entries. This transcoding is executed before cvslog hook below.
287 entries. This transcoding is executed before cvslog hook below.
285
288
286 :convert.cvsps.mergeto: Specify a regular expression to which
289 :convert.cvsps.mergeto: Specify a regular expression to which
287 commit log messages are matched. If a match occurs, then the
290 commit log messages are matched. If a match occurs, then the
288 conversion process will insert a dummy revision merging the
291 conversion process will insert a dummy revision merging the
289 branch on which this log message occurs to the branch
292 branch on which this log message occurs to the branch
290 indicated in the regex. Default is ``{{mergetobranch
293 indicated in the regex. Default is ``{{mergetobranch
291 ([-\\w]+)}}``
294 ([-\\w]+)}}``
292
295
293 :convert.cvsps.mergefrom: Specify a regular expression to which
296 :convert.cvsps.mergefrom: Specify a regular expression to which
294 commit log messages are matched. If a match occurs, then the
297 commit log messages are matched. If a match occurs, then the
295 conversion process will add the most recent revision on the
298 conversion process will add the most recent revision on the
296 branch indicated in the regex as the second parent of the
299 branch indicated in the regex as the second parent of the
297 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
300 changeset. Default is ``{{mergefrombranch ([-\\w]+)}}``
298
301
299 :convert.localtimezone: use local time (as determined by the TZ
302 :convert.localtimezone: use local time (as determined by the TZ
300 environment variable) for changeset date/times. The default
303 environment variable) for changeset date/times. The default
301 is False (use UTC).
304 is False (use UTC).
302
305
303 :hooks.cvslog: Specify a Python function to be called at the end of
306 :hooks.cvslog: Specify a Python function to be called at the end of
304 gathering the CVS log. The function is passed a list with the
307 gathering the CVS log. The function is passed a list with the
305 log entries, and can modify the entries in-place, or add or
308 log entries, and can modify the entries in-place, or add or
306 delete them.
309 delete them.
307
310
308 :hooks.cvschangesets: Specify a Python function to be called after
311 :hooks.cvschangesets: Specify a Python function to be called after
309 the changesets are calculated from the CVS log. The
312 the changesets are calculated from the CVS log. The
310 function is passed a list with the changeset entries, and can
313 function is passed a list with the changeset entries, and can
311 modify the changesets in-place, or add or delete them.
314 modify the changesets in-place, or add or delete them.
312
315
313 An additional "debugcvsps" Mercurial command allows the builtin
316 An additional "debugcvsps" Mercurial command allows the builtin
314 changeset merging code to be run without doing a conversion. Its
317 changeset merging code to be run without doing a conversion. Its
315 parameters and output are similar to that of cvsps 2.1. Please see
318 parameters and output are similar to that of cvsps 2.1. Please see
316 the command help for more details.
319 the command help for more details.
317
320
318 Subversion Source
321 Subversion Source
319 #################
322 #################
320
323
321 Subversion source detects classical trunk/branches/tags layouts.
324 Subversion source detects classical trunk/branches/tags layouts.
322 By default, the supplied ``svn://repo/path/`` source URL is
325 By default, the supplied ``svn://repo/path/`` source URL is
323 converted as a single branch. If ``svn://repo/path/trunk`` exists
326 converted as a single branch. If ``svn://repo/path/trunk`` exists
324 it replaces the default branch. If ``svn://repo/path/branches``
327 it replaces the default branch. If ``svn://repo/path/branches``
325 exists, its subdirectories are listed as possible branches. If
328 exists, its subdirectories are listed as possible branches. If
326 ``svn://repo/path/tags`` exists, it is looked for tags referencing
329 ``svn://repo/path/tags`` exists, it is looked for tags referencing
327 converted branches. Default ``trunk``, ``branches`` and ``tags``
330 converted branches. Default ``trunk``, ``branches`` and ``tags``
328 values can be overridden with following options. Set them to paths
331 values can be overridden with following options. Set them to paths
329 relative to the source URL, or leave them blank to disable auto
332 relative to the source URL, or leave them blank to disable auto
330 detection.
333 detection.
331
334
332 The following options can be set with ``--config``:
335 The following options can be set with ``--config``:
333
336
334 :convert.svn.branches: specify the directory containing branches.
337 :convert.svn.branches: specify the directory containing branches.
335 The default is ``branches``.
338 The default is ``branches``.
336
339
337 :convert.svn.tags: specify the directory containing tags. The
340 :convert.svn.tags: specify the directory containing tags. The
338 default is ``tags``.
341 default is ``tags``.
339
342
340 :convert.svn.trunk: specify the name of the trunk branch. The
343 :convert.svn.trunk: specify the name of the trunk branch. The
341 default is ``trunk``.
344 default is ``trunk``.
342
345
343 :convert.localtimezone: use local time (as determined by the TZ
346 :convert.localtimezone: use local time (as determined by the TZ
344 environment variable) for changeset date/times. The default
347 environment variable) for changeset date/times. The default
345 is False (use UTC).
348 is False (use UTC).
346
349
347 Source history can be retrieved starting at a specific revision,
350 Source history can be retrieved starting at a specific revision,
348 instead of being integrally converted. Only single branch
351 instead of being integrally converted. Only single branch
349 conversions are supported.
352 conversions are supported.
350
353
351 :convert.svn.startrev: specify start Subversion revision number.
354 :convert.svn.startrev: specify start Subversion revision number.
352 The default is 0.
355 The default is 0.
353
356
354 Git Source
357 Git Source
355 ##########
358 ##########
356
359
357 The Git importer converts commits from all reachable branches (refs
360 The Git importer converts commits from all reachable branches (refs
358 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
361 in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
359 Branches are converted to bookmarks with the same name, with the
362 Branches are converted to bookmarks with the same name, with the
360 leading 'refs/heads' stripped. Git submodules are converted to Git
363 leading 'refs/heads' stripped. Git submodules are converted to Git
361 subrepos in Mercurial.
364 subrepos in Mercurial.
362
365
363 The following options can be set with ``--config``:
366 The following options can be set with ``--config``:
364
367
365 :convert.git.similarity: specify how similar files modified in a
368 :convert.git.similarity: specify how similar files modified in a
366 commit must be to be imported as renames or copies, as a
369 commit must be to be imported as renames or copies, as a
367 percentage between ``0`` (disabled) and ``100`` (files must be
370 percentage between ``0`` (disabled) and ``100`` (files must be
368 identical). For example, ``90`` means that a delete/add pair will
371 identical). For example, ``90`` means that a delete/add pair will
369 be imported as a rename if more than 90% of the file hasn't
372 be imported as a rename if more than 90% of the file hasn't
370 changed. The default is ``50``.
373 changed. The default is ``50``.
371
374
372 :convert.git.findcopiesharder: while detecting copies, look at all
375 :convert.git.findcopiesharder: while detecting copies, look at all
373 files in the working copy instead of just changed ones. This
376 files in the working copy instead of just changed ones. This
374 is very expensive for large projects, and is only effective when
377 is very expensive for large projects, and is only effective when
375 ``convert.git.similarity`` is greater than 0. The default is False.
378 ``convert.git.similarity`` is greater than 0. The default is False.
376
379
377 :convert.git.renamelimit: perform rename and copy detection up to this
380 :convert.git.renamelimit: perform rename and copy detection up to this
378 many changed files in a commit. Increasing this will make rename
381 many changed files in a commit. Increasing this will make rename
379 and copy detection more accurate but will significantly slow down
382 and copy detection more accurate but will significantly slow down
380 computation on large projects. The option is only relevant if
383 computation on large projects. The option is only relevant if
381 ``convert.git.similarity`` is greater than 0. The default is
384 ``convert.git.similarity`` is greater than 0. The default is
382 ``400``.
385 ``400``.
383
386
384 :convert.git.committeractions: list of actions to take when processing
387 :convert.git.committeractions: list of actions to take when processing
385 author and committer values.
388 author and committer values.
386
389
387 Git commits have separate author (who wrote the commit) and committer
390 Git commits have separate author (who wrote the commit) and committer
388 (who applied the commit) fields. Not all destinations support separate
391 (who applied the commit) fields. Not all destinations support separate
389 author and committer fields (including Mercurial). This config option
392 author and committer fields (including Mercurial). This config option
390 controls what to do with these author and committer fields during
393 controls what to do with these author and committer fields during
391 conversion.
394 conversion.
392
395
393 A value of ``messagedifferent`` will append a ``committer: ...``
396 A value of ``messagedifferent`` will append a ``committer: ...``
394 line to the commit message if the Git committer is different from the
397 line to the commit message if the Git committer is different from the
395 author. The prefix of that line can be specified using the syntax
398 author. The prefix of that line can be specified using the syntax
396 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
399 ``messagedifferent=<prefix>``. e.g. ``messagedifferent=git-committer:``.
397 When a prefix is specified, a space will always be inserted between the
400 When a prefix is specified, a space will always be inserted between the
398 prefix and the value.
401 prefix and the value.
399
402
400 ``messagealways`` behaves like ``messagedifferent`` except it will
403 ``messagealways`` behaves like ``messagedifferent`` except it will
401 always result in a ``committer: ...`` line being appended to the commit
404 always result in a ``committer: ...`` line being appended to the commit
402 message. This value is mutually exclusive with ``messagedifferent``.
405 message. This value is mutually exclusive with ``messagedifferent``.
403
406
404 ``dropcommitter`` will remove references to the committer. Only
407 ``dropcommitter`` will remove references to the committer. Only
405 references to the author will remain. Actions that add references
408 references to the author will remain. Actions that add references
406 to the committer will have no effect when this is set.
409 to the committer will have no effect when this is set.
407
410
408 ``replaceauthor`` will replace the value of the author field with
411 ``replaceauthor`` will replace the value of the author field with
409 the committer. Other actions that add references to the committer
412 the committer. Other actions that add references to the committer
410 will still take effect when this is set.
413 will still take effect when this is set.
411
414
412 The default is ``messagedifferent``.
415 The default is ``messagedifferent``.
413
416
414 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
417 :convert.git.extrakeys: list of extra keys from commit metadata to copy to
415 the destination. Some Git repositories store extra metadata in commits.
418 the destination. Some Git repositories store extra metadata in commits.
416 By default, this non-default metadata will be lost during conversion.
419 By default, this non-default metadata will be lost during conversion.
417 Setting this config option can retain that metadata. Some built-in
420 Setting this config option can retain that metadata. Some built-in
418 keys such as ``parent`` and ``branch`` are not allowed to be copied.
421 keys such as ``parent`` and ``branch`` are not allowed to be copied.
419
422
420 :convert.git.remoteprefix: remote refs are converted as bookmarks with
423 :convert.git.remoteprefix: remote refs are converted as bookmarks with
421 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
424 ``convert.git.remoteprefix`` as a prefix followed by a /. The default
422 is 'remote'.
425 is 'remote'.
423
426
424 :convert.git.saverev: whether to store the original Git commit ID in the
427 :convert.git.saverev: whether to store the original Git commit ID in the
425 metadata of the destination commit. The default is True.
428 metadata of the destination commit. The default is True.
426
429
427 :convert.git.skipsubmodules: does not convert root level .gitmodules files
430 :convert.git.skipsubmodules: does not convert root level .gitmodules files
428 or files with 160000 mode indicating a submodule. Default is False.
431 or files with 160000 mode indicating a submodule. Default is False.
429
432
430 Perforce Source
433 Perforce Source
431 ###############
434 ###############
432
435
433 The Perforce (P4) importer can be given a p4 depot path or a
436 The Perforce (P4) importer can be given a p4 depot path or a
434 client specification as source. It will convert all files in the
437 client specification as source. It will convert all files in the
435 source to a flat Mercurial repository, ignoring labels, branches
438 source to a flat Mercurial repository, ignoring labels, branches
436 and integrations. Note that when a depot path is given you then
439 and integrations. Note that when a depot path is given you then
437 usually should specify a target directory, because otherwise the
440 usually should specify a target directory, because otherwise the
438 target may be named ``...-hg``.
441 target may be named ``...-hg``.
439
442
440 The following options can be set with ``--config``:
443 The following options can be set with ``--config``:
441
444
442 :convert.p4.encoding: specify the encoding to use when decoding standard
445 :convert.p4.encoding: specify the encoding to use when decoding standard
443 output of the Perforce command line tool. The default is default system
446 output of the Perforce command line tool. The default is default system
444 encoding.
447 encoding.
445
448
446 :convert.p4.startrev: specify initial Perforce revision (a
449 :convert.p4.startrev: specify initial Perforce revision (a
447 Perforce changelist number).
450 Perforce changelist number).
448
451
449 Mercurial Destination
452 Mercurial Destination
450 #####################
453 #####################
451
454
452 The Mercurial destination will recognize Mercurial subrepositories in the
455 The Mercurial destination will recognize Mercurial subrepositories in the
453 destination directory, and update the .hgsubstate file automatically if the
456 destination directory, and update the .hgsubstate file automatically if the
454 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
457 destination subrepositories contain the <dest>/<sub>/.hg/shamap file.
455 Converting a repository with subrepositories requires converting a single
458 Converting a repository with subrepositories requires converting a single
456 repository at a time, from the bottom up.
459 repository at a time, from the bottom up.
457
460
458 .. container:: verbose
461 .. container:: verbose
459
462
460 An example showing how to convert a repository with subrepositories::
463 An example showing how to convert a repository with subrepositories::
461
464
462 # so convert knows the type when it sees a non empty destination
465 # so convert knows the type when it sees a non empty destination
463 $ hg init converted
466 $ hg init converted
464
467
465 $ hg convert orig/sub1 converted/sub1
468 $ hg convert orig/sub1 converted/sub1
466 $ hg convert orig/sub2 converted/sub2
469 $ hg convert orig/sub2 converted/sub2
467 $ hg convert orig converted
470 $ hg convert orig converted
468
471
469 The following options are supported:
472 The following options are supported:
470
473
471 :convert.hg.clonebranches: dispatch source branches in separate
474 :convert.hg.clonebranches: dispatch source branches in separate
472 clones. The default is False.
475 clones. The default is False.
473
476
474 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
477 :convert.hg.tagsbranch: branch name for tag revisions, defaults to
475 ``default``.
478 ``default``.
476
479
477 :convert.hg.usebranchnames: preserve branch names. The default is
480 :convert.hg.usebranchnames: preserve branch names. The default is
478 True.
481 True.
479
482
480 :convert.hg.sourcename: records the given string as a 'convert_source' extra
483 :convert.hg.sourcename: records the given string as a 'convert_source' extra
481 value on each commit made in the target repository. The default is None.
484 value on each commit made in the target repository. The default is None.
482
485
483 All Destinations
486 All Destinations
484 ################
487 ################
485
488
486 All destination types accept the following options:
489 All destination types accept the following options:
487
490
488 :convert.skiptags: does not convert tags from the source repo to the target
491 :convert.skiptags: does not convert tags from the source repo to the target
489 repo. The default is False.
492 repo. The default is False.
490 """
493 """
491 return convcmd.convert(ui, src, dest, revmapfile, **opts)
494 return convcmd.convert(ui, src, dest, revmapfile, **opts)
492
495
493 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
496 @command('debugsvnlog', [], 'hg debugsvnlog', norepo=True)
494 def debugsvnlog(ui, **opts):
497 def debugsvnlog(ui, **opts):
495 return subversion.debugsvnlog(ui, **opts)
498 return subversion.debugsvnlog(ui, **opts)
496
499
497 @command('debugcvsps',
500 @command('debugcvsps',
498 [
501 [
499 # Main options shared with cvsps-2.1
502 # Main options shared with cvsps-2.1
500 ('b', 'branches', [], _('only return changes on specified branches')),
503 ('b', 'branches', [], _('only return changes on specified branches')),
501 ('p', 'prefix', '', _('prefix to remove from file names')),
504 ('p', 'prefix', '', _('prefix to remove from file names')),
502 ('r', 'revisions', [],
505 ('r', 'revisions', [],
503 _('only return changes after or between specified tags')),
506 _('only return changes after or between specified tags')),
504 ('u', 'update-cache', None, _("update cvs log cache")),
507 ('u', 'update-cache', None, _("update cvs log cache")),
505 ('x', 'new-cache', None, _("create new cvs log cache")),
508 ('x', 'new-cache', None, _("create new cvs log cache")),
506 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
509 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
507 ('', 'root', '', _('specify cvsroot')),
510 ('', 'root', '', _('specify cvsroot')),
508 # Options specific to builtin cvsps
511 # Options specific to builtin cvsps
509 ('', 'parents', '', _('show parent changesets')),
512 ('', 'parents', '', _('show parent changesets')),
510 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
513 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
511 # Options that are ignored for compatibility with cvsps-2.1
514 # Options that are ignored for compatibility with cvsps-2.1
512 ('A', 'cvs-direct', None, _('ignored for compatibility')),
515 ('A', 'cvs-direct', None, _('ignored for compatibility')),
513 ],
516 ],
514 _('hg debugcvsps [OPTION]... [PATH]...'),
517 _('hg debugcvsps [OPTION]... [PATH]...'),
515 norepo=True)
518 norepo=True)
516 def debugcvsps(ui, *args, **opts):
519 def debugcvsps(ui, *args, **opts):
517 '''create changeset information from CVS
520 '''create changeset information from CVS
518
521
519 This command is intended as a debugging tool for the CVS to
522 This command is intended as a debugging tool for the CVS to
520 Mercurial converter, and can be used as a direct replacement for
523 Mercurial converter, and can be used as a direct replacement for
521 cvsps.
524 cvsps.
522
525
523 Hg debugcvsps reads the CVS rlog for current directory (or any
526 Hg debugcvsps reads the CVS rlog for current directory (or any
524 named directory) in the CVS repository, and converts the log to a
527 named directory) in the CVS repository, and converts the log to a
525 series of changesets based on matching commit log entries and
528 series of changesets based on matching commit log entries and
526 dates.'''
529 dates.'''
527 return cvsps.debugcvsps(ui, *args, **opts)
530 return cvsps.debugcvsps(ui, *args, **opts)
528
531
529 def kwconverted(ctx, name):
532 def kwconverted(ctx, name):
530 rev = ctx.extra().get('convert_revision', '')
533 rev = ctx.extra().get('convert_revision', '')
531 if rev.startswith('svn:'):
534 if rev.startswith('svn:'):
532 if name == 'svnrev':
535 if name == 'svnrev':
533 return str(subversion.revsplit(rev)[2])
536 return str(subversion.revsplit(rev)[2])
534 elif name == 'svnpath':
537 elif name == 'svnpath':
535 return subversion.revsplit(rev)[1]
538 return subversion.revsplit(rev)[1]
536 elif name == 'svnuuid':
539 elif name == 'svnuuid':
537 return subversion.revsplit(rev)[0]
540 return subversion.revsplit(rev)[0]
538 return rev
541 return rev
539
542
540 templatekeyword = registrar.templatekeyword()
543 templatekeyword = registrar.templatekeyword()
541
544
542 @templatekeyword('svnrev')
545 @templatekeyword('svnrev')
543 def kwsvnrev(repo, ctx, **args):
546 def kwsvnrev(repo, ctx, **args):
544 """String. Converted subversion revision number."""
547 """String. Converted subversion revision number."""
545 return kwconverted(ctx, 'svnrev')
548 return kwconverted(ctx, 'svnrev')
546
549
547 @templatekeyword('svnpath')
550 @templatekeyword('svnpath')
548 def kwsvnpath(repo, ctx, **args):
551 def kwsvnpath(repo, ctx, **args):
549 """String. Converted subversion revision project path."""
552 """String. Converted subversion revision project path."""
550 return kwconverted(ctx, 'svnpath')
553 return kwconverted(ctx, 'svnpath')
551
554
552 @templatekeyword('svnuuid')
555 @templatekeyword('svnuuid')
553 def kwsvnuuid(repo, ctx, **args):
556 def kwsvnuuid(repo, ctx, **args):
554 """String. Converted subversion revision repository identifier."""
557 """String. Converted subversion revision repository identifier."""
555 return kwconverted(ctx, 'svnuuid')
558 return kwconverted(ctx, 'svnuuid')
556
559
557 # tell hggettext to extract docstrings from these functions:
560 # tell hggettext to extract docstrings from these functions:
558 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
561 i18nfunctions = [kwsvnrev, kwsvnpath, kwsvnuuid]
@@ -1,655 +1,655 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 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')
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')
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