##// END OF EJS Templates
convert/cvs: add an option to disable remote log caching...
Patrick Mezard -
r8169:6584953b default
parent child Browse files
Show More
@@ -1,258 +1,261 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
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 '''converting foreign VCS repositories to Mercurial'''
7 '''converting foreign VCS repositories to Mercurial'''
8
8
9 import convcmd
9 import convcmd
10 import cvsps
10 import cvsps
11 import subversion
11 import subversion
12 from mercurial import commands
12 from mercurial import commands
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14
14
15 # Commands definition was moved elsewhere to ease demandload job.
15 # Commands definition was moved elsewhere to ease demandload job.
16
16
17 def convert(ui, src, dest=None, revmapfile=None, **opts):
17 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 """convert a foreign SCM repository to a Mercurial one.
18 """convert a foreign SCM repository to a Mercurial one.
19
19
20 Accepted source formats [identifiers]:
20 Accepted source formats [identifiers]:
21 - Mercurial [hg]
21 - Mercurial [hg]
22 - CVS [cvs]
22 - CVS [cvs]
23 - Darcs [darcs]
23 - Darcs [darcs]
24 - git [git]
24 - git [git]
25 - Subversion [svn]
25 - Subversion [svn]
26 - Monotone [mtn]
26 - Monotone [mtn]
27 - GNU Arch [gnuarch]
27 - GNU Arch [gnuarch]
28 - Bazaar [bzr]
28 - Bazaar [bzr]
29 - Perforce [p4]
29 - Perforce [p4]
30
30
31 Accepted destination formats [identifiers]:
31 Accepted destination formats [identifiers]:
32 - Mercurial [hg]
32 - Mercurial [hg]
33 - Subversion [svn] (history on branches is not preserved)
33 - Subversion [svn] (history on branches is not preserved)
34
34
35 If no revision is given, all revisions will be converted.
35 If no revision is given, all revisions will be converted.
36 Otherwise, convert will only import up to the named revision
36 Otherwise, convert will only import up to the named revision
37 (given in a format understood by the source).
37 (given in a format understood by the source).
38
38
39 If no destination directory name is specified, it defaults to the
39 If no destination directory name is specified, it defaults to the
40 basename of the source with '-hg' appended. If the destination
40 basename of the source with '-hg' appended. If the destination
41 repository doesn't exist, it will be created.
41 repository doesn't exist, it will be created.
42
42
43 If <REVMAP> isn't given, it will be put in a default location
43 If <REVMAP> isn't given, it will be put in a default location
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
45 that maps each source commit ID to the destination ID for that
45 that maps each source commit ID to the destination ID for that
46 revision, like so:
46 revision, like so:
47 <source ID> <destination ID>
47 <source ID> <destination ID>
48
48
49 If the file doesn't exist, it's automatically created. It's
49 If the file doesn't exist, it's automatically created. It's
50 updated on each commit copied, so convert-repo can be interrupted
50 updated on each commit copied, so convert-repo can be interrupted
51 and can be run repeatedly to copy new commits.
51 and can be run repeatedly to copy new commits.
52
52
53 The [username mapping] file is a simple text file that maps each
53 The [username mapping] file is a simple text file that maps each
54 source commit author to a destination commit author. It is handy
54 source commit author to a destination commit author. It is handy
55 for source SCMs that use unix logins to identify authors (eg:
55 for source SCMs that use unix logins to identify authors (eg:
56 CVS). One line per author mapping and the line format is:
56 CVS). One line per author mapping and the line format is:
57 srcauthor=whatever string you want
57 srcauthor=whatever string you want
58
58
59 The filemap is a file that allows filtering and remapping of files
59 The filemap is a file that allows filtering and remapping of files
60 and directories. Comment lines start with '#'. Each line can
60 and directories. Comment lines start with '#'. Each line can
61 contain one of the following directives:
61 contain one of the following directives:
62
62
63 include path/to/file
63 include path/to/file
64
64
65 exclude path/to/file
65 exclude path/to/file
66
66
67 rename from/file to/file
67 rename from/file to/file
68
68
69 The 'include' directive causes a file, or all files under a
69 The 'include' directive causes a file, or all files under a
70 directory, to be included in the destination repository, and the
70 directory, to be included in the destination repository, and the
71 exclusion of all other files and directories not explicitely included.
71 exclusion of all other files and directories not explicitely included.
72 The 'exclude' directive causes files or directories to be omitted.
72 The 'exclude' directive causes files or directories to be omitted.
73 The 'rename' directive renames a file or directory. To rename from
73 The 'rename' directive renames a file or directory. To rename from
74 a subdirectory into the root of the repository, use '.' as the
74 a subdirectory into the root of the repository, use '.' as the
75 path to rename to.
75 path to rename to.
76
76
77 The splicemap is a file that allows insertion of synthetic
77 The splicemap is a file that allows insertion of synthetic
78 history, letting you specify the parents of a revision. This is
78 history, letting you specify the parents of a revision. This is
79 useful if you want to e.g. give a Subversion merge two parents, or
79 useful if you want to e.g. give a Subversion merge two parents, or
80 graft two disconnected series of history together. Each entry
80 graft two disconnected series of history together. Each entry
81 contains a key, followed by a space, followed by one or two
81 contains a key, followed by a space, followed by one or two
82 comma-separated values. The key is the revision ID in the source
82 comma-separated values. The key is the revision ID in the source
83 revision control system whose parents should be modified (same
83 revision control system whose parents should be modified (same
84 format as a key in .hg/shamap). The values are the revision IDs
84 format as a key in .hg/shamap). The values are the revision IDs
85 (in either the source or destination revision control system) that
85 (in either the source or destination revision control system) that
86 should be used as the new parents for that node.
86 should be used as the new parents for that node.
87
87
88 Mercurial Source
88 Mercurial Source
89 -----------------
89 -----------------
90
90
91 --config convert.hg.ignoreerrors=False (boolean)
91 --config convert.hg.ignoreerrors=False (boolean)
92 ignore integrity errors when reading. Use it to fix Mercurial
92 ignore integrity errors when reading. Use it to fix Mercurial
93 repositories with missing revlogs, by converting from and to
93 repositories with missing revlogs, by converting from and to
94 Mercurial.
94 Mercurial.
95 --config convert.hg.saverev=False (boolean)
95 --config convert.hg.saverev=False (boolean)
96 store original revision ID in changeset (forces target IDs to
96 store original revision ID in changeset (forces target IDs to
97 change)
97 change)
98 --config convert.hg.startrev=0 (hg revision identifier)
98 --config convert.hg.startrev=0 (hg revision identifier)
99 convert start revision and its descendants
99 convert start revision and its descendants
100
100
101 CVS Source
101 CVS Source
102 ----------
102 ----------
103
103
104 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
104 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
105 to indicate the starting point of what will be converted. Direct
105 to indicate the starting point of what will be converted. Direct
106 access to the repository files is not needed, unless of course the
106 access to the repository files is not needed, unless of course the
107 repository is :local:. The conversion uses the top level directory
107 repository is :local:. The conversion uses the top level directory
108 in the sandbox to find the CVS repository, and then uses CVS rlog
108 in the sandbox to find the CVS repository, and then uses CVS rlog
109 commands to find files to convert. This means that unless a
109 commands to find files to convert. This means that unless a
110 filemap is given, all files under the starting directory will be
110 filemap is given, all files under the starting directory will be
111 converted, and that any directory reorganisation in the CVS
111 converted, and that any directory reorganisation in the CVS
112 sandbox is ignored.
112 sandbox is ignored.
113
113
114 Because CVS does not have changesets, it is necessary to collect
114 Because CVS does not have changesets, it is necessary to collect
115 individual commits to CVS and merge them into changesets. CVS
115 individual commits to CVS and merge them into changesets. CVS
116 source uses its internal changeset merging code by default but can
116 source uses its internal changeset merging code by default but can
117 be configured to call the external 'cvsps' program by setting:
117 be configured to call the external 'cvsps' program by setting:
118 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
118 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
119 This is a legacy option and may be removed in future.
119 This is a legacy option and may be removed in future.
120
120
121 The options shown are the defaults.
121 The options shown are the defaults.
122
122
123 Internal cvsps is selected by setting
123 Internal cvsps is selected by setting
124 --config convert.cvsps=builtin
124 --config convert.cvsps=builtin
125 and has a few more configurable options:
125 and has a few more configurable options:
126 --config convert.cvsps.cache=True (boolean)
127 Set to False to disable remote log caching, for testing and
128 debugging purposes.
126 --config convert.cvsps.fuzz=60 (integer)
129 --config convert.cvsps.fuzz=60 (integer)
127 Specify the maximum time (in seconds) that is allowed
130 Specify the maximum time (in seconds) that is allowed
128 between commits with identical user and log message in a
131 between commits with identical user and log message in a
129 single changeset. When very large files were checked in as
132 single changeset. When very large files were checked in as
130 part of a changeset then the default may not be long
133 part of a changeset then the default may not be long
131 enough.
134 enough.
132 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
135 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
133 Specify a regular expression to which commit log messages
136 Specify a regular expression to which commit log messages
134 are matched. If a match occurs, then the conversion
137 are matched. If a match occurs, then the conversion
135 process will insert a dummy revision merging the branch on
138 process will insert a dummy revision merging the branch on
136 which this log message occurs to the branch indicated in
139 which this log message occurs to the branch indicated in
137 the regex.
140 the regex.
138 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
141 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
139 Specify a regular expression to which commit log messages
142 Specify a regular expression to which commit log messages
140 are matched. If a match occurs, then the conversion
143 are matched. If a match occurs, then the conversion
141 process will add the most recent revision on the branch
144 process will add the most recent revision on the branch
142 indicated in the regex as the second parent of the
145 indicated in the regex as the second parent of the
143 changeset.
146 changeset.
144
147
145 The hgext/convert/cvsps wrapper script allows the builtin
148 The hgext/convert/cvsps wrapper script allows the builtin
146 changeset merging code to be run without doing a conversion. Its
149 changeset merging code to be run without doing a conversion. Its
147 parameters and output are similar to that of cvsps 2.1.
150 parameters and output are similar to that of cvsps 2.1.
148
151
149 Subversion Source
152 Subversion Source
150 -----------------
153 -----------------
151
154
152 Subversion source detects classical trunk/branches/tags layouts.
155 Subversion source detects classical trunk/branches/tags layouts.
153 By default, the supplied "svn://repo/path/" source URL is
156 By default, the supplied "svn://repo/path/" source URL is
154 converted as a single branch. If "svn://repo/path/trunk" exists it
157 converted as a single branch. If "svn://repo/path/trunk" exists it
155 replaces the default branch. If "svn://repo/path/branches" exists,
158 replaces the default branch. If "svn://repo/path/branches" exists,
156 its subdirectories are listed as possible branches. If
159 its subdirectories are listed as possible branches. If
157 "svn://repo/path/tags" exists, it is looked for tags referencing
160 "svn://repo/path/tags" exists, it is looked for tags referencing
158 converted branches. Default "trunk", "branches" and "tags" values
161 converted branches. Default "trunk", "branches" and "tags" values
159 can be overriden with following options. Set them to paths
162 can be overriden with following options. Set them to paths
160 relative to the source URL, or leave them blank to disable
163 relative to the source URL, or leave them blank to disable
161 autodetection.
164 autodetection.
162
165
163 --config convert.svn.branches=branches (directory name)
166 --config convert.svn.branches=branches (directory name)
164 specify the directory containing branches
167 specify the directory containing branches
165 --config convert.svn.tags=tags (directory name)
168 --config convert.svn.tags=tags (directory name)
166 specify the directory containing tags
169 specify the directory containing tags
167 --config convert.svn.trunk=trunk (directory name)
170 --config convert.svn.trunk=trunk (directory name)
168 specify the name of the trunk branch
171 specify the name of the trunk branch
169
172
170 Source history can be retrieved starting at a specific revision,
173 Source history can be retrieved starting at a specific revision,
171 instead of being integrally converted. Only single branch
174 instead of being integrally converted. Only single branch
172 conversions are supported.
175 conversions are supported.
173
176
174 --config convert.svn.startrev=0 (svn revision number)
177 --config convert.svn.startrev=0 (svn revision number)
175 specify start Subversion revision.
178 specify start Subversion revision.
176
179
177 Perforce Source
180 Perforce Source
178 ---------------
181 ---------------
179
182
180 The Perforce (P4) importer can be given a p4 depot path or a
183 The Perforce (P4) importer can be given a p4 depot path or a
181 client specification as source. It will convert all files in the
184 client specification as source. It will convert all files in the
182 source to a flat Mercurial repository, ignoring labels, branches
185 source to a flat Mercurial repository, ignoring labels, branches
183 and integrations. Note that when a depot path is given you then
186 and integrations. Note that when a depot path is given you then
184 usually should specify a target directory, because otherwise the
187 usually should specify a target directory, because otherwise the
185 target may be named ...-hg.
188 target may be named ...-hg.
186
189
187 It is possible to limit the amount of source history to be
190 It is possible to limit the amount of source history to be
188 converted by specifying an initial Perforce revision.
191 converted by specifying an initial Perforce revision.
189
192
190 --config convert.p4.startrev=0 (perforce changelist number)
193 --config convert.p4.startrev=0 (perforce changelist number)
191 specify initial Perforce revision.
194 specify initial Perforce revision.
192
195
193
196
194 Mercurial Destination
197 Mercurial Destination
195 ---------------------
198 ---------------------
196
199
197 --config convert.hg.clonebranches=False (boolean)
200 --config convert.hg.clonebranches=False (boolean)
198 dispatch source branches in separate clones.
201 dispatch source branches in separate clones.
199 --config convert.hg.tagsbranch=default (branch name)
202 --config convert.hg.tagsbranch=default (branch name)
200 tag revisions branch name
203 tag revisions branch name
201 --config convert.hg.usebranchnames=True (boolean)
204 --config convert.hg.usebranchnames=True (boolean)
202 preserve branch names
205 preserve branch names
203
206
204 """
207 """
205 return convcmd.convert(ui, src, dest, revmapfile, **opts)
208 return convcmd.convert(ui, src, dest, revmapfile, **opts)
206
209
207 def debugsvnlog(ui, **opts):
210 def debugsvnlog(ui, **opts):
208 return subversion.debugsvnlog(ui, **opts)
211 return subversion.debugsvnlog(ui, **opts)
209
212
210 def debugcvsps(ui, *args, **opts):
213 def debugcvsps(ui, *args, **opts):
211 '''create changeset information from CVS
214 '''create changeset information from CVS
212
215
213 This command is intended as a debugging tool for the CVS to
216 This command is intended as a debugging tool for the CVS to
214 Mercurial converter, and can be used as a direct replacement for
217 Mercurial converter, and can be used as a direct replacement for
215 cvsps.
218 cvsps.
216
219
217 Hg debugcvsps reads the CVS rlog for current directory (or any
220 Hg debugcvsps reads the CVS rlog for current directory (or any
218 named directory) in the CVS repository, and converts the log to a
221 named directory) in the CVS repository, and converts the log to a
219 series of changesets based on matching commit log entries and
222 series of changesets based on matching commit log entries and
220 dates.'''
223 dates.'''
221 return cvsps.debugcvsps(ui, *args, **opts)
224 return cvsps.debugcvsps(ui, *args, **opts)
222
225
223 commands.norepo += " convert debugsvnlog debugcvsps"
226 commands.norepo += " convert debugsvnlog debugcvsps"
224
227
225 cmdtable = {
228 cmdtable = {
226 "convert":
229 "convert":
227 (convert,
230 (convert,
228 [('A', 'authors', '', _('username mapping filename')),
231 [('A', 'authors', '', _('username mapping filename')),
229 ('d', 'dest-type', '', _('destination repository type')),
232 ('d', 'dest-type', '', _('destination repository type')),
230 ('', 'filemap', '', _('remap file names using contents of file')),
233 ('', 'filemap', '', _('remap file names using contents of file')),
231 ('r', 'rev', '', _('import up to target revision REV')),
234 ('r', 'rev', '', _('import up to target revision REV')),
232 ('s', 'source-type', '', _('source repository type')),
235 ('s', 'source-type', '', _('source repository type')),
233 ('', 'splicemap', '', _('splice synthesized history into place')),
236 ('', 'splicemap', '', _('splice synthesized history into place')),
234 ('', 'datesort', None, _('try to sort changesets by date'))],
237 ('', 'datesort', None, _('try to sort changesets by date'))],
235 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
238 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
236 "debugsvnlog":
239 "debugsvnlog":
237 (debugsvnlog,
240 (debugsvnlog,
238 [],
241 [],
239 'hg debugsvnlog'),
242 'hg debugsvnlog'),
240 "debugcvsps":
243 "debugcvsps":
241 (debugcvsps,
244 (debugcvsps,
242 [
245 [
243 # Main options shared with cvsps-2.1
246 # Main options shared with cvsps-2.1
244 ('b', 'branches', [], _('only return changes on specified branches')),
247 ('b', 'branches', [], _('only return changes on specified branches')),
245 ('p', 'prefix', '', _('prefix to remove from file names')),
248 ('p', 'prefix', '', _('prefix to remove from file names')),
246 ('r', 'revisions', [], _('only return changes after or between specified tags')),
249 ('r', 'revisions', [], _('only return changes after or between specified tags')),
247 ('u', 'update-cache', None, _("update cvs log cache")),
250 ('u', 'update-cache', None, _("update cvs log cache")),
248 ('x', 'new-cache', None, _("create new cvs log cache")),
251 ('x', 'new-cache', None, _("create new cvs log cache")),
249 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
252 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
250 ('', 'root', '', _('specify cvsroot')),
253 ('', 'root', '', _('specify cvsroot')),
251 # Options specific to builtin cvsps
254 # Options specific to builtin cvsps
252 ('', 'parents', '', _('show parent changesets')),
255 ('', 'parents', '', _('show parent changesets')),
253 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
256 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
254 # Options that are ignored for compatibility with cvsps-2.1
257 # Options that are ignored for compatibility with cvsps-2.1
255 ('A', 'cvs-direct', None, _('ignored for compatibility')),
258 ('A', 'cvs-direct', None, _('ignored for compatibility')),
256 ],
259 ],
257 _('hg debugcvsps [OPTION]... [PATH]...')),
260 _('hg debugcvsps [OPTION]... [PATH]...')),
258 }
261 }
@@ -1,359 +1,362 b''
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
2
2
3 import os, locale, re, socket, errno
3 import os, locale, re, socket, errno
4 from cStringIO import StringIO
4 from cStringIO import StringIO
5 from mercurial import util
5 from mercurial import util
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 from common import NoRepo, commit, converter_source, checktool
8 from common import NoRepo, commit, converter_source, checktool
9 import cvsps
9 import cvsps
10
10
11 class convert_cvs(converter_source):
11 class convert_cvs(converter_source):
12 def __init__(self, ui, path, rev=None):
12 def __init__(self, ui, path, rev=None):
13 super(convert_cvs, self).__init__(ui, path, rev=rev)
13 super(convert_cvs, self).__init__(ui, path, rev=rev)
14
14
15 cvs = os.path.join(path, "CVS")
15 cvs = os.path.join(path, "CVS")
16 if not os.path.exists(cvs):
16 if not os.path.exists(cvs):
17 raise NoRepo("%s does not look like a CVS checkout" % path)
17 raise NoRepo("%s does not look like a CVS checkout" % path)
18
18
19 checktool('cvs')
19 checktool('cvs')
20 self.cmd = ui.config('convert', 'cvsps', 'builtin')
20 self.cmd = ui.config('convert', 'cvsps', 'builtin')
21 cvspsexe = self.cmd.split(None, 1)[0]
21 cvspsexe = self.cmd.split(None, 1)[0]
22 self.builtin = cvspsexe == 'builtin'
22 self.builtin = cvspsexe == 'builtin'
23
23
24 if not self.builtin:
24 if not self.builtin:
25 checktool(cvspsexe)
25 checktool(cvspsexe)
26
26
27 self.changeset = None
27 self.changeset = None
28 self.files = {}
28 self.files = {}
29 self.tags = {}
29 self.tags = {}
30 self.lastbranch = {}
30 self.lastbranch = {}
31 self.parent = {}
31 self.parent = {}
32 self.socket = None
32 self.socket = None
33 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
33 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
34 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
34 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
35 self.encoding = locale.getpreferredencoding()
35 self.encoding = locale.getpreferredencoding()
36
36
37 self._connect()
37 self._connect()
38
38
39 def _parse(self):
39 def _parse(self):
40 if self.changeset is not None:
40 if self.changeset is not None:
41 return
41 return
42 self.changeset = {}
42 self.changeset = {}
43
43
44 maxrev = 0
44 maxrev = 0
45 cmd = self.cmd
45 cmd = self.cmd
46 if self.rev:
46 if self.rev:
47 # TODO: handle tags
47 # TODO: handle tags
48 try:
48 try:
49 # patchset number?
49 # patchset number?
50 maxrev = int(self.rev)
50 maxrev = int(self.rev)
51 except ValueError:
51 except ValueError:
52 try:
52 try:
53 # date
53 # date
54 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
54 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
55 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
55 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
56 except util.Abort:
56 except util.Abort:
57 raise util.Abort(_('revision %s is not a patchset number or date') % self.rev)
57 raise util.Abort(_('revision %s is not a patchset number or date') % self.rev)
58
58
59 d = os.getcwd()
59 d = os.getcwd()
60 try:
60 try:
61 os.chdir(self.path)
61 os.chdir(self.path)
62 id = None
62 id = None
63 state = 0
63 state = 0
64 filerevids = {}
64 filerevids = {}
65
65
66 if self.builtin:
66 if self.builtin:
67 # builtin cvsps code
67 # builtin cvsps code
68 self.ui.status(_('using builtin cvsps\n'))
68 self.ui.status(_('using builtin cvsps\n'))
69
69
70 db = cvsps.createlog(self.ui, cache='update')
70 cache = 'update'
71 if not self.ui.configbool('convert', 'cvsps.cache', True):
72 cache = None
73 db = cvsps.createlog(self.ui, cache=cache)
71 db = cvsps.createchangeset(self.ui, db,
74 db = cvsps.createchangeset(self.ui, db,
72 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
75 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
73 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
76 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
74 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
77 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
75
78
76 for cs in db:
79 for cs in db:
77 if maxrev and cs.id>maxrev:
80 if maxrev and cs.id>maxrev:
78 break
81 break
79 id = str(cs.id)
82 id = str(cs.id)
80 cs.author = self.recode(cs.author)
83 cs.author = self.recode(cs.author)
81 self.lastbranch[cs.branch] = id
84 self.lastbranch[cs.branch] = id
82 cs.comment = self.recode(cs.comment)
85 cs.comment = self.recode(cs.comment)
83 date = util.datestr(cs.date)
86 date = util.datestr(cs.date)
84 self.tags.update(dict.fromkeys(cs.tags, id))
87 self.tags.update(dict.fromkeys(cs.tags, id))
85
88
86 files = {}
89 files = {}
87 for f in cs.entries:
90 for f in cs.entries:
88 files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]),
91 files[f.file] = "%s%s" % ('.'.join([str(x) for x in f.revision]),
89 ['', '(DEAD)'][f.dead])
92 ['', '(DEAD)'][f.dead])
90
93
91 # add current commit to set
94 # add current commit to set
92 c = commit(author=cs.author, date=date,
95 c = commit(author=cs.author, date=date,
93 parents=[str(p.id) for p in cs.parents],
96 parents=[str(p.id) for p in cs.parents],
94 desc=cs.comment, branch=cs.branch or '')
97 desc=cs.comment, branch=cs.branch or '')
95 self.changeset[id] = c
98 self.changeset[id] = c
96 self.files[id] = files
99 self.files[id] = files
97 else:
100 else:
98 # external cvsps
101 # external cvsps
99 for l in util.popen(cmd):
102 for l in util.popen(cmd):
100 if state == 0: # header
103 if state == 0: # header
101 if l.startswith("PatchSet"):
104 if l.startswith("PatchSet"):
102 id = l[9:-2]
105 id = l[9:-2]
103 if maxrev and int(id) > maxrev:
106 if maxrev and int(id) > maxrev:
104 # ignore everything
107 # ignore everything
105 state = 3
108 state = 3
106 elif l.startswith("Date:"):
109 elif l.startswith("Date:"):
107 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
110 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
108 date = util.datestr(date)
111 date = util.datestr(date)
109 elif l.startswith("Branch:"):
112 elif l.startswith("Branch:"):
110 branch = l[8:-1]
113 branch = l[8:-1]
111 self.parent[id] = self.lastbranch.get(branch, 'bad')
114 self.parent[id] = self.lastbranch.get(branch, 'bad')
112 self.lastbranch[branch] = id
115 self.lastbranch[branch] = id
113 elif l.startswith("Ancestor branch:"):
116 elif l.startswith("Ancestor branch:"):
114 ancestor = l[17:-1]
117 ancestor = l[17:-1]
115 # figure out the parent later
118 # figure out the parent later
116 self.parent[id] = self.lastbranch[ancestor]
119 self.parent[id] = self.lastbranch[ancestor]
117 elif l.startswith("Author:"):
120 elif l.startswith("Author:"):
118 author = self.recode(l[8:-1])
121 author = self.recode(l[8:-1])
119 elif l.startswith("Tag:") or l.startswith("Tags:"):
122 elif l.startswith("Tag:") or l.startswith("Tags:"):
120 t = l[l.index(':')+1:]
123 t = l[l.index(':')+1:]
121 t = [ut.strip() for ut in t.split(',')]
124 t = [ut.strip() for ut in t.split(',')]
122 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
125 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
123 self.tags.update(dict.fromkeys(t, id))
126 self.tags.update(dict.fromkeys(t, id))
124 elif l.startswith("Log:"):
127 elif l.startswith("Log:"):
125 # switch to gathering log
128 # switch to gathering log
126 state = 1
129 state = 1
127 log = ""
130 log = ""
128 elif state == 1: # log
131 elif state == 1: # log
129 if l == "Members: \n":
132 if l == "Members: \n":
130 # switch to gathering members
133 # switch to gathering members
131 files = {}
134 files = {}
132 oldrevs = []
135 oldrevs = []
133 log = self.recode(log[:-1])
136 log = self.recode(log[:-1])
134 state = 2
137 state = 2
135 else:
138 else:
136 # gather log
139 # gather log
137 log += l
140 log += l
138 elif state == 2: # members
141 elif state == 2: # members
139 if l == "\n": # start of next entry
142 if l == "\n": # start of next entry
140 state = 0
143 state = 0
141 p = [self.parent[id]]
144 p = [self.parent[id]]
142 if id == "1":
145 if id == "1":
143 p = []
146 p = []
144 if branch == "HEAD":
147 if branch == "HEAD":
145 branch = ""
148 branch = ""
146 if branch:
149 if branch:
147 latest = 0
150 latest = 0
148 # the last changeset that contains a base
151 # the last changeset that contains a base
149 # file is our parent
152 # file is our parent
150 for r in oldrevs:
153 for r in oldrevs:
151 latest = max(filerevids.get(r, 0), latest)
154 latest = max(filerevids.get(r, 0), latest)
152 if latest:
155 if latest:
153 p = [latest]
156 p = [latest]
154
157
155 # add current commit to set
158 # add current commit to set
156 c = commit(author=author, date=date, parents=p,
159 c = commit(author=author, date=date, parents=p,
157 desc=log, branch=branch)
160 desc=log, branch=branch)
158 self.changeset[id] = c
161 self.changeset[id] = c
159 self.files[id] = files
162 self.files[id] = files
160 else:
163 else:
161 colon = l.rfind(':')
164 colon = l.rfind(':')
162 file = l[1:colon]
165 file = l[1:colon]
163 rev = l[colon+1:-2]
166 rev = l[colon+1:-2]
164 oldrev, rev = rev.split("->")
167 oldrev, rev = rev.split("->")
165 files[file] = rev
168 files[file] = rev
166
169
167 # save some information for identifying branch points
170 # save some information for identifying branch points
168 oldrevs.append("%s:%s" % (oldrev, file))
171 oldrevs.append("%s:%s" % (oldrev, file))
169 filerevids["%s:%s" % (rev, file)] = id
172 filerevids["%s:%s" % (rev, file)] = id
170 elif state == 3:
173 elif state == 3:
171 # swallow all input
174 # swallow all input
172 continue
175 continue
173
176
174 self.heads = self.lastbranch.values()
177 self.heads = self.lastbranch.values()
175 finally:
178 finally:
176 os.chdir(d)
179 os.chdir(d)
177
180
178 def _connect(self):
181 def _connect(self):
179 root = self.cvsroot
182 root = self.cvsroot
180 conntype = None
183 conntype = None
181 user, host = None, None
184 user, host = None, None
182 cmd = ['cvs', 'server']
185 cmd = ['cvs', 'server']
183
186
184 self.ui.status(_("connecting to %s\n") % root)
187 self.ui.status(_("connecting to %s\n") % root)
185
188
186 if root.startswith(":pserver:"):
189 if root.startswith(":pserver:"):
187 root = root[9:]
190 root = root[9:]
188 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
191 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
189 root)
192 root)
190 if m:
193 if m:
191 conntype = "pserver"
194 conntype = "pserver"
192 user, passw, serv, port, root = m.groups()
195 user, passw, serv, port, root = m.groups()
193 if not user:
196 if not user:
194 user = "anonymous"
197 user = "anonymous"
195 if not port:
198 if not port:
196 port = 2401
199 port = 2401
197 else:
200 else:
198 port = int(port)
201 port = int(port)
199 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
202 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
200 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
203 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
201
204
202 if not passw:
205 if not passw:
203 passw = "A"
206 passw = "A"
204 cvspass = os.path.expanduser("~/.cvspass")
207 cvspass = os.path.expanduser("~/.cvspass")
205 try:
208 try:
206 pf = open(cvspass)
209 pf = open(cvspass)
207 for line in pf.read().splitlines():
210 for line in pf.read().splitlines():
208 part1, part2 = line.split(' ', 1)
211 part1, part2 = line.split(' ', 1)
209 if part1 == '/1':
212 if part1 == '/1':
210 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
213 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
211 part1, part2 = part2.split(' ', 1)
214 part1, part2 = part2.split(' ', 1)
212 format = format1
215 format = format1
213 else:
216 else:
214 # :pserver:user@example.com:/cvsroot/foo Ah<Z
217 # :pserver:user@example.com:/cvsroot/foo Ah<Z
215 format = format0
218 format = format0
216 if part1 == format:
219 if part1 == format:
217 passw = part2
220 passw = part2
218 break
221 break
219 pf.close()
222 pf.close()
220 except IOError, inst:
223 except IOError, inst:
221 if inst.errno != errno.ENOENT:
224 if inst.errno != errno.ENOENT:
222 if not getattr(inst, 'filename', None):
225 if not getattr(inst, 'filename', None):
223 inst.filename = cvspass
226 inst.filename = cvspass
224 raise
227 raise
225
228
226 sck = socket.socket()
229 sck = socket.socket()
227 sck.connect((serv, port))
230 sck.connect((serv, port))
228 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
231 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
229 "END AUTH REQUEST", ""]))
232 "END AUTH REQUEST", ""]))
230 if sck.recv(128) != "I LOVE YOU\n":
233 if sck.recv(128) != "I LOVE YOU\n":
231 raise util.Abort(_("CVS pserver authentication failed"))
234 raise util.Abort(_("CVS pserver authentication failed"))
232
235
233 self.writep = self.readp = sck.makefile('r+')
236 self.writep = self.readp = sck.makefile('r+')
234
237
235 if not conntype and root.startswith(":local:"):
238 if not conntype and root.startswith(":local:"):
236 conntype = "local"
239 conntype = "local"
237 root = root[7:]
240 root = root[7:]
238
241
239 if not conntype:
242 if not conntype:
240 # :ext:user@host/home/user/path/to/cvsroot
243 # :ext:user@host/home/user/path/to/cvsroot
241 if root.startswith(":ext:"):
244 if root.startswith(":ext:"):
242 root = root[5:]
245 root = root[5:]
243 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
246 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
244 # Do not take Windows path "c:\foo\bar" for a connection strings
247 # Do not take Windows path "c:\foo\bar" for a connection strings
245 if os.path.isdir(root) or not m:
248 if os.path.isdir(root) or not m:
246 conntype = "local"
249 conntype = "local"
247 else:
250 else:
248 conntype = "rsh"
251 conntype = "rsh"
249 user, host, root = m.group(1), m.group(2), m.group(3)
252 user, host, root = m.group(1), m.group(2), m.group(3)
250
253
251 if conntype != "pserver":
254 if conntype != "pserver":
252 if conntype == "rsh":
255 if conntype == "rsh":
253 rsh = os.environ.get("CVS_RSH") or "ssh"
256 rsh = os.environ.get("CVS_RSH") or "ssh"
254 if user:
257 if user:
255 cmd = [rsh, '-l', user, host] + cmd
258 cmd = [rsh, '-l', user, host] + cmd
256 else:
259 else:
257 cmd = [rsh, host] + cmd
260 cmd = [rsh, host] + cmd
258
261
259 # popen2 does not support argument lists under Windows
262 # popen2 does not support argument lists under Windows
260 cmd = [util.shellquote(arg) for arg in cmd]
263 cmd = [util.shellquote(arg) for arg in cmd]
261 cmd = util.quotecommand(' '.join(cmd))
264 cmd = util.quotecommand(' '.join(cmd))
262 self.writep, self.readp = util.popen2(cmd, 'b')
265 self.writep, self.readp = util.popen2(cmd, 'b')
263
266
264 self.realroot = root
267 self.realroot = root
265
268
266 self.writep.write("Root %s\n" % root)
269 self.writep.write("Root %s\n" % root)
267 self.writep.write("Valid-responses ok error Valid-requests Mode"
270 self.writep.write("Valid-responses ok error Valid-requests Mode"
268 " M Mbinary E Checked-in Created Updated"
271 " M Mbinary E Checked-in Created Updated"
269 " Merged Removed\n")
272 " Merged Removed\n")
270 self.writep.write("valid-requests\n")
273 self.writep.write("valid-requests\n")
271 self.writep.flush()
274 self.writep.flush()
272 r = self.readp.readline()
275 r = self.readp.readline()
273 if not r.startswith("Valid-requests"):
276 if not r.startswith("Valid-requests"):
274 raise util.Abort(_("server sucks"))
277 raise util.Abort(_("server sucks"))
275 if "UseUnchanged" in r:
278 if "UseUnchanged" in r:
276 self.writep.write("UseUnchanged\n")
279 self.writep.write("UseUnchanged\n")
277 self.writep.flush()
280 self.writep.flush()
278 r = self.readp.readline()
281 r = self.readp.readline()
279
282
280 def getheads(self):
283 def getheads(self):
281 self._parse()
284 self._parse()
282 return self.heads
285 return self.heads
283
286
284 def _getfile(self, name, rev):
287 def _getfile(self, name, rev):
285
288
286 def chunkedread(fp, count):
289 def chunkedread(fp, count):
287 # file-objects returned by socked.makefile() do not handle
290 # file-objects returned by socked.makefile() do not handle
288 # large read() requests very well.
291 # large read() requests very well.
289 chunksize = 65536
292 chunksize = 65536
290 output = StringIO()
293 output = StringIO()
291 while count > 0:
294 while count > 0:
292 data = fp.read(min(count, chunksize))
295 data = fp.read(min(count, chunksize))
293 if not data:
296 if not data:
294 raise util.Abort(_("%d bytes missing from remote file") % count)
297 raise util.Abort(_("%d bytes missing from remote file") % count)
295 count -= len(data)
298 count -= len(data)
296 output.write(data)
299 output.write(data)
297 return output.getvalue()
300 return output.getvalue()
298
301
299 if rev.endswith("(DEAD)"):
302 if rev.endswith("(DEAD)"):
300 raise IOError
303 raise IOError
301
304
302 args = ("-N -P -kk -r %s --" % rev).split()
305 args = ("-N -P -kk -r %s --" % rev).split()
303 args.append(self.cvsrepo + '/' + name)
306 args.append(self.cvsrepo + '/' + name)
304 for x in args:
307 for x in args:
305 self.writep.write("Argument %s\n" % x)
308 self.writep.write("Argument %s\n" % x)
306 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
309 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
307 self.writep.flush()
310 self.writep.flush()
308
311
309 data = ""
312 data = ""
310 while 1:
313 while 1:
311 line = self.readp.readline()
314 line = self.readp.readline()
312 if line.startswith("Created ") or line.startswith("Updated "):
315 if line.startswith("Created ") or line.startswith("Updated "):
313 self.readp.readline() # path
316 self.readp.readline() # path
314 self.readp.readline() # entries
317 self.readp.readline() # entries
315 mode = self.readp.readline()[:-1]
318 mode = self.readp.readline()[:-1]
316 count = int(self.readp.readline()[:-1])
319 count = int(self.readp.readline()[:-1])
317 data = chunkedread(self.readp, count)
320 data = chunkedread(self.readp, count)
318 elif line.startswith(" "):
321 elif line.startswith(" "):
319 data += line[1:]
322 data += line[1:]
320 elif line.startswith("M "):
323 elif line.startswith("M "):
321 pass
324 pass
322 elif line.startswith("Mbinary "):
325 elif line.startswith("Mbinary "):
323 count = int(self.readp.readline()[:-1])
326 count = int(self.readp.readline()[:-1])
324 data = chunkedread(self.readp, count)
327 data = chunkedread(self.readp, count)
325 else:
328 else:
326 if line == "ok\n":
329 if line == "ok\n":
327 return (data, "x" in mode and "x" or "")
330 return (data, "x" in mode and "x" or "")
328 elif line.startswith("E "):
331 elif line.startswith("E "):
329 self.ui.warn(_("cvs server: %s\n") % line[2:])
332 self.ui.warn(_("cvs server: %s\n") % line[2:])
330 elif line.startswith("Remove"):
333 elif line.startswith("Remove"):
331 self.readp.readline()
334 self.readp.readline()
332 else:
335 else:
333 raise util.Abort(_("unknown CVS response: %s") % line)
336 raise util.Abort(_("unknown CVS response: %s") % line)
334
337
335 def getfile(self, file, rev):
338 def getfile(self, file, rev):
336 self._parse()
339 self._parse()
337 data, mode = self._getfile(file, rev)
340 data, mode = self._getfile(file, rev)
338 self.modecache[(file, rev)] = mode
341 self.modecache[(file, rev)] = mode
339 return data
342 return data
340
343
341 def getmode(self, file, rev):
344 def getmode(self, file, rev):
342 return self.modecache[(file, rev)]
345 return self.modecache[(file, rev)]
343
346
344 def getchanges(self, rev):
347 def getchanges(self, rev):
345 self._parse()
348 self._parse()
346 self.modecache = {}
349 self.modecache = {}
347 return util.sort(self.files[rev].items()), {}
350 return util.sort(self.files[rev].items()), {}
348
351
349 def getcommit(self, rev):
352 def getcommit(self, rev):
350 self._parse()
353 self._parse()
351 return self.changeset[rev]
354 return self.changeset[rev]
352
355
353 def gettags(self):
356 def gettags(self):
354 self._parse()
357 self._parse()
355 return self.tags
358 return self.tags
356
359
357 def getchangedfiles(self, rev, i):
360 def getchangedfiles(self, rev, i):
358 self._parse()
361 self._parse()
359 return util.sort(self.files[rev].keys())
362 return util.sort(self.files[rev].keys())
@@ -1,61 +1,62 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 # This is http://www.selenic.com/mercurial/bts/issue1148
3 # This is http://www.selenic.com/mercurial/bts/issue1148
4
4
5 "$TESTDIR/hghave" cvs || exit 80
5 "$TESTDIR/hghave" cvs || exit 80
6
6
7 cvscall()
7 cvscall()
8 {
8 {
9 cvs -f "$@"
9 cvs -f "$@"
10 }
10 }
11
11
12 echo "[extensions]" >> $HGRCPATH
12 echo "[extensions]" >> $HGRCPATH
13 echo "convert = " >> $HGRCPATH
13 echo "convert = " >> $HGRCPATH
14 echo "graphlog = " >> $HGRCPATH
14 echo "graphlog = " >> $HGRCPATH
15 echo "[convert]" >> $HGRCPATH
15 echo "[convert]" >> $HGRCPATH
16 echo "cvsps=builtin" >> $HGRCPATH
16 echo "cvsps=builtin" >> $HGRCPATH
17 echo "cvsps.cache=0" >> $HGRCPATH
17
18
18 echo % create cvs repository
19 echo % create cvs repository
19 mkdir cvsrepo
20 mkdir cvsrepo
20 cd cvsrepo
21 cd cvsrepo
21 export CVSROOT=`pwd`
22 export CVSROOT=`pwd`
22 export CVS_OPTIONS=-f
23 export CVS_OPTIONS=-f
23 cd ..
24 cd ..
24
25
25 cvscall -q -d "$CVSROOT" init
26 cvscall -q -d "$CVSROOT" init
26
27
27 echo % Create a new project
28 echo % Create a new project
28
29
29 mkdir src
30 mkdir src
30 cd src
31 cd src
31 echo "1" > a
32 echo "1" > a
32 echo "1" > b
33 echo "1" > b
33 cvscall import -m "init" src v0 r0 | sort
34 cvscall import -m "init" src v0 r0 | sort
34 cd ..
35 cd ..
35 cvscall co src
36 cvscall co src
36 cd src
37 cd src
37
38
38 echo % Branch the project
39 echo % Branch the project
39
40
40 cvscall tag -b BRANCH
41 cvscall tag -b BRANCH
41 cvscall up -r BRANCH > /dev/null
42 cvscall up -r BRANCH > /dev/null
42
43
43 echo % Modify file a, then b, then a
44 echo % Modify file a, then b, then a
44
45
45 echo "2" > a
46 echo "2" > a
46 cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
47 cvscall ci -m "mod a" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
47
48
48 echo "2" > b
49 echo "2" > b
49 cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
50 cvscall ci -m "mod b" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
50
51
51 echo "3" > a
52 echo "3" > a
52 cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
53 cvscall ci -m "mod a again" | grep '<--' | sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
53
54
54 echo % Convert
55 echo % Convert
55
56
56 cd ..
57 cd ..
57 hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
58 hg convert src | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
58
59
59 echo % Check the result
60 echo % Check the result
60
61
61 hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n'
62 hg -R src-hg glog --template '#rev# (#branches#) #desc# files: #files#\n'
@@ -1,246 +1,249 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 convert a foreign SCM repository to a Mercurial one.
3 convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6 - Mercurial [hg]
6 - Mercurial [hg]
7 - CVS [cvs]
7 - CVS [cvs]
8 - Darcs [darcs]
8 - Darcs [darcs]
9 - git [git]
9 - git [git]
10 - Subversion [svn]
10 - Subversion [svn]
11 - Monotone [mtn]
11 - Monotone [mtn]
12 - GNU Arch [gnuarch]
12 - GNU Arch [gnuarch]
13 - Bazaar [bzr]
13 - Bazaar [bzr]
14 - Perforce [p4]
14 - Perforce [p4]
15
15
16 Accepted destination formats [identifiers]:
16 Accepted destination formats [identifiers]:
17 - Mercurial [hg]
17 - Mercurial [hg]
18 - Subversion [svn] (history on branches is not preserved)
18 - Subversion [svn] (history on branches is not preserved)
19
19
20 If no revision is given, all revisions will be converted.
20 If no revision is given, all revisions will be converted.
21 Otherwise, convert will only import up to the named revision
21 Otherwise, convert will only import up to the named revision
22 (given in a format understood by the source).
22 (given in a format understood by the source).
23
23
24 If no destination directory name is specified, it defaults to the
24 If no destination directory name is specified, it defaults to the
25 basename of the source with '-hg' appended. If the destination
25 basename of the source with '-hg' appended. If the destination
26 repository doesn't exist, it will be created.
26 repository doesn't exist, it will be created.
27
27
28 If <REVMAP> isn't given, it will be put in a default location
28 If <REVMAP> isn't given, it will be put in a default location
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
29 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
30 that maps each source commit ID to the destination ID for that
30 that maps each source commit ID to the destination ID for that
31 revision, like so:
31 revision, like so:
32 <source ID> <destination ID>
32 <source ID> <destination ID>
33
33
34 If the file doesn't exist, it's automatically created. It's
34 If the file doesn't exist, it's automatically created. It's
35 updated on each commit copied, so convert-repo can be interrupted
35 updated on each commit copied, so convert-repo can be interrupted
36 and can be run repeatedly to copy new commits.
36 and can be run repeatedly to copy new commits.
37
37
38 The [username mapping] file is a simple text file that maps each
38 The [username mapping] file is a simple text file that maps each
39 source commit author to a destination commit author. It is handy
39 source commit author to a destination commit author. It is handy
40 for source SCMs that use unix logins to identify authors (eg:
40 for source SCMs that use unix logins to identify authors (eg:
41 CVS). One line per author mapping and the line format is:
41 CVS). One line per author mapping and the line format is:
42 srcauthor=whatever string you want
42 srcauthor=whatever string you want
43
43
44 The filemap is a file that allows filtering and remapping of files
44 The filemap is a file that allows filtering and remapping of files
45 and directories. Comment lines start with '#'. Each line can
45 and directories. Comment lines start with '#'. Each line can
46 contain one of the following directives:
46 contain one of the following directives:
47
47
48 include path/to/file
48 include path/to/file
49
49
50 exclude path/to/file
50 exclude path/to/file
51
51
52 rename from/file to/file
52 rename from/file to/file
53
53
54 The 'include' directive causes a file, or all files under a
54 The 'include' directive causes a file, or all files under a
55 directory, to be included in the destination repository, and the
55 directory, to be included in the destination repository, and the
56 exclusion of all other files and directories not explicitely included.
56 exclusion of all other files and directories not explicitely included.
57 The 'exclude' directive causes files or directories to be omitted.
57 The 'exclude' directive causes files or directories to be omitted.
58 The 'rename' directive renames a file or directory. To rename from
58 The 'rename' directive renames a file or directory. To rename from
59 a subdirectory into the root of the repository, use '.' as the
59 a subdirectory into the root of the repository, use '.' as the
60 path to rename to.
60 path to rename to.
61
61
62 The splicemap is a file that allows insertion of synthetic
62 The splicemap is a file that allows insertion of synthetic
63 history, letting you specify the parents of a revision. This is
63 history, letting you specify the parents of a revision. This is
64 useful if you want to e.g. give a Subversion merge two parents, or
64 useful if you want to e.g. give a Subversion merge two parents, or
65 graft two disconnected series of history together. Each entry
65 graft two disconnected series of history together. Each entry
66 contains a key, followed by a space, followed by one or two
66 contains a key, followed by a space, followed by one or two
67 comma-separated values. The key is the revision ID in the source
67 comma-separated values. The key is the revision ID in the source
68 revision control system whose parents should be modified (same
68 revision control system whose parents should be modified (same
69 format as a key in .hg/shamap). The values are the revision IDs
69 format as a key in .hg/shamap). The values are the revision IDs
70 (in either the source or destination revision control system) that
70 (in either the source or destination revision control system) that
71 should be used as the new parents for that node.
71 should be used as the new parents for that node.
72
72
73 Mercurial Source
73 Mercurial Source
74 -----------------
74 -----------------
75
75
76 --config convert.hg.ignoreerrors=False (boolean)
76 --config convert.hg.ignoreerrors=False (boolean)
77 ignore integrity errors when reading. Use it to fix Mercurial
77 ignore integrity errors when reading. Use it to fix Mercurial
78 repositories with missing revlogs, by converting from and to
78 repositories with missing revlogs, by converting from and to
79 Mercurial.
79 Mercurial.
80 --config convert.hg.saverev=False (boolean)
80 --config convert.hg.saverev=False (boolean)
81 store original revision ID in changeset (forces target IDs to
81 store original revision ID in changeset (forces target IDs to
82 change)
82 change)
83 --config convert.hg.startrev=0 (hg revision identifier)
83 --config convert.hg.startrev=0 (hg revision identifier)
84 convert start revision and its descendants
84 convert start revision and its descendants
85
85
86 CVS Source
86 CVS Source
87 ----------
87 ----------
88
88
89 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
89 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
90 to indicate the starting point of what will be converted. Direct
90 to indicate the starting point of what will be converted. Direct
91 access to the repository files is not needed, unless of course the
91 access to the repository files is not needed, unless of course the
92 repository is :local:. The conversion uses the top level directory
92 repository is :local:. The conversion uses the top level directory
93 in the sandbox to find the CVS repository, and then uses CVS rlog
93 in the sandbox to find the CVS repository, and then uses CVS rlog
94 commands to find files to convert. This means that unless a
94 commands to find files to convert. This means that unless a
95 filemap is given, all files under the starting directory will be
95 filemap is given, all files under the starting directory will be
96 converted, and that any directory reorganisation in the CVS
96 converted, and that any directory reorganisation in the CVS
97 sandbox is ignored.
97 sandbox is ignored.
98
98
99 Because CVS does not have changesets, it is necessary to collect
99 Because CVS does not have changesets, it is necessary to collect
100 individual commits to CVS and merge them into changesets. CVS
100 individual commits to CVS and merge them into changesets. CVS
101 source uses its internal changeset merging code by default but can
101 source uses its internal changeset merging code by default but can
102 be configured to call the external 'cvsps' program by setting:
102 be configured to call the external 'cvsps' program by setting:
103 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
103 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
104 This is a legacy option and may be removed in future.
104 This is a legacy option and may be removed in future.
105
105
106 The options shown are the defaults.
106 The options shown are the defaults.
107
107
108 Internal cvsps is selected by setting
108 Internal cvsps is selected by setting
109 --config convert.cvsps=builtin
109 --config convert.cvsps=builtin
110 and has a few more configurable options:
110 and has a few more configurable options:
111 --config convert.cvsps.cache=True (boolean)
112 Set to False to disable remote log caching, for testing and
113 debugging purposes.
111 --config convert.cvsps.fuzz=60 (integer)
114 --config convert.cvsps.fuzz=60 (integer)
112 Specify the maximum time (in seconds) that is allowed
115 Specify the maximum time (in seconds) that is allowed
113 between commits with identical user and log message in a
116 between commits with identical user and log message in a
114 single changeset. When very large files were checked in as
117 single changeset. When very large files were checked in as
115 part of a changeset then the default may not be long
118 part of a changeset then the default may not be long
116 enough.
119 enough.
117 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
120 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
118 Specify a regular expression to which commit log messages
121 Specify a regular expression to which commit log messages
119 are matched. If a match occurs, then the conversion
122 are matched. If a match occurs, then the conversion
120 process will insert a dummy revision merging the branch on
123 process will insert a dummy revision merging the branch on
121 which this log message occurs to the branch indicated in
124 which this log message occurs to the branch indicated in
122 the regex.
125 the regex.
123 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
126 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
124 Specify a regular expression to which commit log messages
127 Specify a regular expression to which commit log messages
125 are matched. If a match occurs, then the conversion
128 are matched. If a match occurs, then the conversion
126 process will add the most recent revision on the branch
129 process will add the most recent revision on the branch
127 indicated in the regex as the second parent of the
130 indicated in the regex as the second parent of the
128 changeset.
131 changeset.
129
132
130 The hgext/convert/cvsps wrapper script allows the builtin
133 The hgext/convert/cvsps wrapper script allows the builtin
131 changeset merging code to be run without doing a conversion. Its
134 changeset merging code to be run without doing a conversion. Its
132 parameters and output are similar to that of cvsps 2.1.
135 parameters and output are similar to that of cvsps 2.1.
133
136
134 Subversion Source
137 Subversion Source
135 -----------------
138 -----------------
136
139
137 Subversion source detects classical trunk/branches/tags layouts.
140 Subversion source detects classical trunk/branches/tags layouts.
138 By default, the supplied "svn://repo/path/" source URL is
141 By default, the supplied "svn://repo/path/" source URL is
139 converted as a single branch. If "svn://repo/path/trunk" exists it
142 converted as a single branch. If "svn://repo/path/trunk" exists it
140 replaces the default branch. If "svn://repo/path/branches" exists,
143 replaces the default branch. If "svn://repo/path/branches" exists,
141 its subdirectories are listed as possible branches. If
144 its subdirectories are listed as possible branches. If
142 "svn://repo/path/tags" exists, it is looked for tags referencing
145 "svn://repo/path/tags" exists, it is looked for tags referencing
143 converted branches. Default "trunk", "branches" and "tags" values
146 converted branches. Default "trunk", "branches" and "tags" values
144 can be overriden with following options. Set them to paths
147 can be overriden with following options. Set them to paths
145 relative to the source URL, or leave them blank to disable
148 relative to the source URL, or leave them blank to disable
146 autodetection.
149 autodetection.
147
150
148 --config convert.svn.branches=branches (directory name)
151 --config convert.svn.branches=branches (directory name)
149 specify the directory containing branches
152 specify the directory containing branches
150 --config convert.svn.tags=tags (directory name)
153 --config convert.svn.tags=tags (directory name)
151 specify the directory containing tags
154 specify the directory containing tags
152 --config convert.svn.trunk=trunk (directory name)
155 --config convert.svn.trunk=trunk (directory name)
153 specify the name of the trunk branch
156 specify the name of the trunk branch
154
157
155 Source history can be retrieved starting at a specific revision,
158 Source history can be retrieved starting at a specific revision,
156 instead of being integrally converted. Only single branch
159 instead of being integrally converted. Only single branch
157 conversions are supported.
160 conversions are supported.
158
161
159 --config convert.svn.startrev=0 (svn revision number)
162 --config convert.svn.startrev=0 (svn revision number)
160 specify start Subversion revision.
163 specify start Subversion revision.
161
164
162 Perforce Source
165 Perforce Source
163 ---------------
166 ---------------
164
167
165 The Perforce (P4) importer can be given a p4 depot path or a
168 The Perforce (P4) importer can be given a p4 depot path or a
166 client specification as source. It will convert all files in the
169 client specification as source. It will convert all files in the
167 source to a flat Mercurial repository, ignoring labels, branches
170 source to a flat Mercurial repository, ignoring labels, branches
168 and integrations. Note that when a depot path is given you then
171 and integrations. Note that when a depot path is given you then
169 usually should specify a target directory, because otherwise the
172 usually should specify a target directory, because otherwise the
170 target may be named ...-hg.
173 target may be named ...-hg.
171
174
172 It is possible to limit the amount of source history to be
175 It is possible to limit the amount of source history to be
173 converted by specifying an initial Perforce revision.
176 converted by specifying an initial Perforce revision.
174
177
175 --config convert.p4.startrev=0 (perforce changelist number)
178 --config convert.p4.startrev=0 (perforce changelist number)
176 specify initial Perforce revision.
179 specify initial Perforce revision.
177
180
178
181
179 Mercurial Destination
182 Mercurial Destination
180 ---------------------
183 ---------------------
181
184
182 --config convert.hg.clonebranches=False (boolean)
185 --config convert.hg.clonebranches=False (boolean)
183 dispatch source branches in separate clones.
186 dispatch source branches in separate clones.
184 --config convert.hg.tagsbranch=default (branch name)
187 --config convert.hg.tagsbranch=default (branch name)
185 tag revisions branch name
188 tag revisions branch name
186 --config convert.hg.usebranchnames=True (boolean)
189 --config convert.hg.usebranchnames=True (boolean)
187 preserve branch names
190 preserve branch names
188
191
189 options:
192 options:
190
193
191 -A --authors username mapping filename
194 -A --authors username mapping filename
192 -d --dest-type destination repository type
195 -d --dest-type destination repository type
193 --filemap remap file names using contents of file
196 --filemap remap file names using contents of file
194 -r --rev import up to target revision REV
197 -r --rev import up to target revision REV
195 -s --source-type source repository type
198 -s --source-type source repository type
196 --splicemap splice synthesized history into place
199 --splicemap splice synthesized history into place
197 --datesort try to sort changesets by date
200 --datesort try to sort changesets by date
198
201
199 use "hg -v help convert" to show global options
202 use "hg -v help convert" to show global options
200 adding a
203 adding a
201 assuming destination a-hg
204 assuming destination a-hg
202 initializing destination a-hg repository
205 initializing destination a-hg repository
203 scanning source...
206 scanning source...
204 sorting...
207 sorting...
205 converting...
208 converting...
206 4 a
209 4 a
207 3 b
210 3 b
208 2 c
211 2 c
209 1 d
212 1 d
210 0 e
213 0 e
211 pulling from ../a
214 pulling from ../a
212 searching for changes
215 searching for changes
213 no changes found
216 no changes found
214 % should fail
217 % should fail
215 initializing destination bogusfile repository
218 initializing destination bogusfile repository
216 abort: cannot create new bundle repository
219 abort: cannot create new bundle repository
217 % should fail
220 % should fail
218 abort: Permission denied: bogusdir
221 abort: Permission denied: bogusdir
219 % should succeed
222 % should succeed
220 initializing destination bogusdir repository
223 initializing destination bogusdir repository
221 scanning source...
224 scanning source...
222 sorting...
225 sorting...
223 converting...
226 converting...
224 4 a
227 4 a
225 3 b
228 3 b
226 2 c
229 2 c
227 1 d
230 1 d
228 0 e
231 0 e
229 % test pre and post conversion actions
232 % test pre and post conversion actions
230 run hg source pre-conversion action
233 run hg source pre-conversion action
231 run hg sink pre-conversion action
234 run hg sink pre-conversion action
232 run hg sink post-conversion action
235 run hg sink post-conversion action
233 run hg source post-conversion action
236 run hg source post-conversion action
234 % converting empty dir should fail nicely
237 % converting empty dir should fail nicely
235 assuming destination emptydir-hg
238 assuming destination emptydir-hg
236 initializing destination emptydir-hg repository
239 initializing destination emptydir-hg repository
237 emptydir does not look like a CVS checkout
240 emptydir does not look like a CVS checkout
238 emptydir does not look like a Git repo
241 emptydir does not look like a Git repo
239 emptydir does not look like a Subversion repo
242 emptydir does not look like a Subversion repo
240 emptydir is not a local Mercurial repo
243 emptydir is not a local Mercurial repo
241 emptydir does not look like a darcs repo
244 emptydir does not look like a darcs repo
242 emptydir does not look like a monotone repo
245 emptydir does not look like a monotone repo
243 emptydir does not look like a GNU Arch repo
246 emptydir does not look like a GNU Arch repo
244 emptydir does not look like a Bazaar repo
247 emptydir does not look like a Bazaar repo
245 emptydir does not look like a P4 repo
248 emptydir does not look like a P4 repo
246 abort: emptydir: missing or unsupported repository
249 abort: emptydir: missing or unsupported repository
General Comments 0
You need to be logged in to leave comments. Login now