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