##// END OF EJS Templates
convert: implement two hooks in builtin cvsps
Frank Kingswood -
r10095:69ce7a10 default
parent child Browse files
Show More
@@ -1,285 +1,294
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, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 '''import revisions from foreign VCS repositories into Mercurial'''
8 '''import revisions from foreign VCS repositories into Mercurial'''
9
9
10 import convcmd
10 import convcmd
11 import cvsps
11 import cvsps
12 import subversion
12 import subversion
13 from mercurial import commands
13 from mercurial import commands
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Commands definition was moved elsewhere to ease demandload job.
16 # Commands definition was moved elsewhere to ease demandload job.
17
17
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
19 """convert a foreign SCM repository to a Mercurial one.
19 """convert a foreign SCM repository to a Mercurial one.
20
20
21 Accepted source formats [identifiers]:
21 Accepted source formats [identifiers]:
22
22
23 - Mercurial [hg]
23 - Mercurial [hg]
24 - CVS [cvs]
24 - CVS [cvs]
25 - Darcs [darcs]
25 - Darcs [darcs]
26 - git [git]
26 - git [git]
27 - Subversion [svn]
27 - Subversion [svn]
28 - Monotone [mtn]
28 - Monotone [mtn]
29 - GNU Arch [gnuarch]
29 - GNU Arch [gnuarch]
30 - Bazaar [bzr]
30 - Bazaar [bzr]
31 - Perforce [p4]
31 - Perforce [p4]
32
32
33 Accepted destination formats [identifiers]:
33 Accepted destination formats [identifiers]:
34
34
35 - Mercurial [hg]
35 - Mercurial [hg]
36 - Subversion [svn] (history on branches is not preserved)
36 - Subversion [svn] (history on branches is not preserved)
37
37
38 If no revision is given, all revisions will be converted.
38 If no revision is given, all revisions will be converted.
39 Otherwise, convert will only import up to the named revision
39 Otherwise, convert will only import up to the named revision
40 (given in a format understood by the source).
40 (given in a format understood by the source).
41
41
42 If no destination directory name is specified, it defaults to the
42 If no destination directory name is specified, it defaults to the
43 basename of the source with '-hg' appended. If the destination
43 basename of the source with '-hg' appended. If the destination
44 repository doesn't exist, it will be created.
44 repository doesn't exist, it will be created.
45
45
46 By default, all sources except Mercurial will use --branchsort.
46 By default, all sources except Mercurial will use --branchsort.
47 Mercurial uses --sourcesort to preserve original revision numbers
47 Mercurial uses --sourcesort to preserve original revision numbers
48 order. Sort modes have the following effects:
48 order. Sort modes have the following effects:
49
49
50 --branchsort convert from parent to child revision when possible,
50 --branchsort convert from parent to child revision when possible,
51 which means branches are usually converted one after
51 which means branches are usually converted one after
52 the other. It generates more compact repositories.
52 the other. It generates more compact repositories.
53
53
54 --datesort sort revisions by date. Converted repositories have
54 --datesort sort revisions by date. Converted repositories have
55 good-looking changelogs but are often an order of
55 good-looking changelogs but are often an order of
56 magnitude larger than the same ones generated by
56 magnitude larger than the same ones generated by
57 --branchsort.
57 --branchsort.
58
58
59 --sourcesort try to preserve source revisions order, only
59 --sourcesort try to preserve source revisions order, only
60 supported by Mercurial sources.
60 supported by Mercurial sources.
61
61
62 If <REVMAP> isn't given, it will be put in a default location
62 If <REVMAP> isn't given, it will be put in a default location
63 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
63 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
64 that maps each source commit ID to the destination ID for that
64 that maps each source commit ID to the destination ID for that
65 revision, like so::
65 revision, like so::
66
66
67 <source ID> <destination ID>
67 <source ID> <destination ID>
68
68
69 If the file doesn't exist, it's automatically created. It's
69 If the file doesn't exist, it's automatically created. It's
70 updated on each commit copied, so convert-repo can be interrupted
70 updated on each commit copied, so convert-repo can be interrupted
71 and can be run repeatedly to copy new commits.
71 and can be run repeatedly to copy new commits.
72
72
73 The [username mapping] file is a simple text file that maps each
73 The [username mapping] file is a simple text file that maps each
74 source commit author to a destination commit author. It is handy
74 source commit author to a destination commit author. It is handy
75 for source SCMs that use unix logins to identify authors (eg:
75 for source SCMs that use unix logins to identify authors (eg:
76 CVS). One line per author mapping and the line format is:
76 CVS). One line per author mapping and the line format is:
77 srcauthor=whatever string you want
77 srcauthor=whatever string you want
78
78
79 The filemap is a file that allows filtering and remapping of files
79 The filemap is a file that allows filtering and remapping of files
80 and directories. Comment lines start with '#'. Each line can
80 and directories. Comment lines start with '#'. Each line can
81 contain one of the following directives::
81 contain one of the following directives::
82
82
83 include path/to/file
83 include path/to/file
84
84
85 exclude path/to/file
85 exclude path/to/file
86
86
87 rename from/file to/file
87 rename from/file to/file
88
88
89 The 'include' directive causes a file, or all files under a
89 The 'include' directive causes a file, or all files under a
90 directory, to be included in the destination repository, and the
90 directory, to be included in the destination repository, and the
91 exclusion of all other files and directories not explicitly
91 exclusion of all other files and directories not explicitly
92 included. The 'exclude' directive causes files or directories to
92 included. The 'exclude' directive causes files or directories to
93 be omitted. The 'rename' directive renames a file or directory. To
93 be omitted. The 'rename' directive renames a file or directory. To
94 rename from a subdirectory into the root of the repository, use
94 rename from a subdirectory into the root of the repository, use
95 '.' as the path to rename to.
95 '.' as the path to rename to.
96
96
97 The splicemap is a file that allows insertion of synthetic
97 The splicemap is a file that allows insertion of synthetic
98 history, letting you specify the parents of a revision. This is
98 history, letting you specify the parents of a revision. This is
99 useful if you want to e.g. give a Subversion merge two parents, or
99 useful if you want to e.g. give a Subversion merge two parents, or
100 graft two disconnected series of history together. Each entry
100 graft two disconnected series of history together. Each entry
101 contains a key, followed by a space, followed by one or two
101 contains a key, followed by a space, followed by one or two
102 comma-separated values. The key is the revision ID in the source
102 comma-separated values. The key is the revision ID in the source
103 revision control system whose parents should be modified (same
103 revision control system whose parents should be modified (same
104 format as a key in .hg/shamap). The values are the revision IDs
104 format as a key in .hg/shamap). The values are the revision IDs
105 (in either the source or destination revision control system) that
105 (in either the source or destination revision control system) that
106 should be used as the new parents for that node. For example, if
106 should be used as the new parents for that node. For example, if
107 you have merged "release-1.0" into "trunk", then you should
107 you have merged "release-1.0" into "trunk", then you should
108 specify the revision on "trunk" as the first parent and the one on
108 specify the revision on "trunk" as the first parent and the one on
109 the "release-1.0" branch as the second.
109 the "release-1.0" branch as the second.
110
110
111 The branchmap is a file that allows you to rename a branch when it is
111 The branchmap is a file that allows you to rename a branch when it is
112 being brought in from whatever external repository. When used in
112 being brought in from whatever external repository. When used in
113 conjunction with a splicemap, it allows for a powerful combination
113 conjunction with a splicemap, it allows for a powerful combination
114 to help fix even the most badly mismanaged repositories and turn them
114 to help fix even the most badly mismanaged repositories and turn them
115 into nicely structured Mercurial repositories. The branchmap contains
115 into nicely structured Mercurial repositories. The branchmap contains
116 lines of the form "original_branch_name new_branch_name".
116 lines of the form "original_branch_name new_branch_name".
117 "original_branch_name" is the name of the branch in the source
117 "original_branch_name" is the name of the branch in the source
118 repository, and "new_branch_name" is the name of the branch is the
118 repository, and "new_branch_name" is the name of the branch is the
119 destination repository. This can be used to (for instance) move code
119 destination repository. This can be used to (for instance) move code
120 in one repository from "default" to a named branch.
120 in one repository from "default" to a named branch.
121
121
122 Mercurial Source
122 Mercurial Source
123 ----------------
123 ----------------
124
124
125 --config convert.hg.ignoreerrors=False (boolean)
125 --config convert.hg.ignoreerrors=False (boolean)
126 ignore integrity errors when reading. Use it to fix Mercurial
126 ignore integrity errors when reading. Use it to fix Mercurial
127 repositories with missing revlogs, by converting from and to
127 repositories with missing revlogs, by converting from and to
128 Mercurial.
128 Mercurial.
129 --config convert.hg.saverev=False (boolean)
129 --config convert.hg.saverev=False (boolean)
130 store original revision ID in changeset (forces target IDs to
130 store original revision ID in changeset (forces target IDs to
131 change)
131 change)
132 --config convert.hg.startrev=0 (hg revision identifier)
132 --config convert.hg.startrev=0 (hg revision identifier)
133 convert start revision and its descendants
133 convert start revision and its descendants
134
134
135 CVS Source
135 CVS Source
136 ----------
136 ----------
137
137
138 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
138 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
139 to indicate the starting point of what will be converted. Direct
139 to indicate the starting point of what will be converted. Direct
140 access to the repository files is not needed, unless of course the
140 access to the repository files is not needed, unless of course the
141 repository is :local:. The conversion uses the top level directory
141 repository is :local:. The conversion uses the top level directory
142 in the sandbox to find the CVS repository, and then uses CVS rlog
142 in the sandbox to find the CVS repository, and then uses CVS rlog
143 commands to find files to convert. This means that unless a
143 commands to find files to convert. This means that unless a
144 filemap is given, all files under the starting directory will be
144 filemap is given, all files under the starting directory will be
145 converted, and that any directory reorganization in the CVS
145 converted, and that any directory reorganization in the CVS
146 sandbox is ignored.
146 sandbox is ignored.
147
147
148 The options shown are the defaults.
148 The options shown are the defaults.
149
149
150 --config convert.cvsps.cache=True (boolean)
150 --config convert.cvsps.cache=True (boolean)
151 Set to False to disable remote log caching, for testing and
151 Set to False to disable remote log caching, for testing and
152 debugging purposes.
152 debugging purposes.
153 --config convert.cvsps.fuzz=60 (integer)
153 --config convert.cvsps.fuzz=60 (integer)
154 Specify the maximum time (in seconds) that is allowed between
154 Specify the maximum time (in seconds) that is allowed between
155 commits with identical user and log message in a single
155 commits with identical user and log message in a single
156 changeset. When very large files were checked in as part of a
156 changeset. When very large files were checked in as part of a
157 changeset then the default may not be long enough.
157 changeset then the default may not be long enough.
158 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
158 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
159 Specify a regular expression to which commit log messages are
159 Specify a regular expression to which commit log messages are
160 matched. If a match occurs, then the conversion process will
160 matched. If a match occurs, then the conversion process will
161 insert a dummy revision merging the branch on which this log
161 insert a dummy revision merging the branch on which this log
162 message occurs to the branch indicated in the regex.
162 message occurs to the branch indicated in the regex.
163 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
163 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
164 Specify a regular expression to which commit log messages are
164 Specify a regular expression to which commit log messages are
165 matched. If a match occurs, then the conversion process will
165 matched. If a match occurs, then the conversion process will
166 add the most recent revision on the branch indicated in the
166 add the most recent revision on the branch indicated in the
167 regex as the second parent of the changeset.
167 regex as the second parent of the changeset.
168 --config hook.cvslog
169 Specify a Python function to be called at the end of gathering
170 the CVS log. The function is passed a list with the log entries,
171 and can modify the entries in-place, or add or delete them.
172 --config hook.cvschangesets
173 Specify a Python function to be called after the changesets
174 are calculated from the the CVS log. The function is passed
175 a list with the changeset entries, and can modify the changesets
176 in-place, or add or delete them.
168
177
169 An additional "debugcvsps" Mercurial command allows the builtin
178 An additional "debugcvsps" Mercurial command allows the builtin
170 changeset merging code to be run without doing a conversion. Its
179 changeset merging code to be run without doing a conversion. Its
171 parameters and output are similar to that of cvsps 2.1. Please see
180 parameters and output are similar to that of cvsps 2.1. Please see
172 the command help for more details.
181 the command help for more details.
173
182
174 Subversion Source
183 Subversion Source
175 -----------------
184 -----------------
176
185
177 Subversion source detects classical trunk/branches/tags layouts.
186 Subversion source detects classical trunk/branches/tags layouts.
178 By default, the supplied "svn://repo/path/" source URL is
187 By default, the supplied "svn://repo/path/" source URL is
179 converted as a single branch. If "svn://repo/path/trunk" exists it
188 converted as a single branch. If "svn://repo/path/trunk" exists it
180 replaces the default branch. If "svn://repo/path/branches" exists,
189 replaces the default branch. If "svn://repo/path/branches" exists,
181 its subdirectories are listed as possible branches. If
190 its subdirectories are listed as possible branches. If
182 "svn://repo/path/tags" exists, it is looked for tags referencing
191 "svn://repo/path/tags" exists, it is looked for tags referencing
183 converted branches. Default "trunk", "branches" and "tags" values
192 converted branches. Default "trunk", "branches" and "tags" values
184 can be overridden with following options. Set them to paths
193 can be overridden with following options. Set them to paths
185 relative to the source URL, or leave them blank to disable auto
194 relative to the source URL, or leave them blank to disable auto
186 detection.
195 detection.
187
196
188 --config convert.svn.branches=branches (directory name)
197 --config convert.svn.branches=branches (directory name)
189 specify the directory containing branches
198 specify the directory containing branches
190 --config convert.svn.tags=tags (directory name)
199 --config convert.svn.tags=tags (directory name)
191 specify the directory containing tags
200 specify the directory containing tags
192 --config convert.svn.trunk=trunk (directory name)
201 --config convert.svn.trunk=trunk (directory name)
193 specify the name of the trunk branch
202 specify the name of the trunk branch
194
203
195 Source history can be retrieved starting at a specific revision,
204 Source history can be retrieved starting at a specific revision,
196 instead of being integrally converted. Only single branch
205 instead of being integrally converted. Only single branch
197 conversions are supported.
206 conversions are supported.
198
207
199 --config convert.svn.startrev=0 (svn revision number)
208 --config convert.svn.startrev=0 (svn revision number)
200 specify start Subversion revision.
209 specify start Subversion revision.
201
210
202 Perforce Source
211 Perforce Source
203 ---------------
212 ---------------
204
213
205 The Perforce (P4) importer can be given a p4 depot path or a
214 The Perforce (P4) importer can be given a p4 depot path or a
206 client specification as source. It will convert all files in the
215 client specification as source. It will convert all files in the
207 source to a flat Mercurial repository, ignoring labels, branches
216 source to a flat Mercurial repository, ignoring labels, branches
208 and integrations. Note that when a depot path is given you then
217 and integrations. Note that when a depot path is given you then
209 usually should specify a target directory, because otherwise the
218 usually should specify a target directory, because otherwise the
210 target may be named ...-hg.
219 target may be named ...-hg.
211
220
212 It is possible to limit the amount of source history to be
221 It is possible to limit the amount of source history to be
213 converted by specifying an initial Perforce revision.
222 converted by specifying an initial Perforce revision.
214
223
215 --config convert.p4.startrev=0 (perforce changelist number)
224 --config convert.p4.startrev=0 (perforce changelist number)
216 specify initial Perforce revision.
225 specify initial Perforce revision.
217
226
218 Mercurial Destination
227 Mercurial Destination
219 ---------------------
228 ---------------------
220
229
221 --config convert.hg.clonebranches=False (boolean)
230 --config convert.hg.clonebranches=False (boolean)
222 dispatch source branches in separate clones.
231 dispatch source branches in separate clones.
223 --config convert.hg.tagsbranch=default (branch name)
232 --config convert.hg.tagsbranch=default (branch name)
224 tag revisions branch name
233 tag revisions branch name
225 --config convert.hg.usebranchnames=True (boolean)
234 --config convert.hg.usebranchnames=True (boolean)
226 preserve branch names
235 preserve branch names
227
236
228 """
237 """
229 return convcmd.convert(ui, src, dest, revmapfile, **opts)
238 return convcmd.convert(ui, src, dest, revmapfile, **opts)
230
239
231 def debugsvnlog(ui, **opts):
240 def debugsvnlog(ui, **opts):
232 return subversion.debugsvnlog(ui, **opts)
241 return subversion.debugsvnlog(ui, **opts)
233
242
234 def debugcvsps(ui, *args, **opts):
243 def debugcvsps(ui, *args, **opts):
235 '''create changeset information from CVS
244 '''create changeset information from CVS
236
245
237 This command is intended as a debugging tool for the CVS to
246 This command is intended as a debugging tool for the CVS to
238 Mercurial converter, and can be used as a direct replacement for
247 Mercurial converter, and can be used as a direct replacement for
239 cvsps.
248 cvsps.
240
249
241 Hg debugcvsps reads the CVS rlog for current directory (or any
250 Hg debugcvsps reads the CVS rlog for current directory (or any
242 named directory) in the CVS repository, and converts the log to a
251 named directory) in the CVS repository, and converts the log to a
243 series of changesets based on matching commit log entries and
252 series of changesets based on matching commit log entries and
244 dates.'''
253 dates.'''
245 return cvsps.debugcvsps(ui, *args, **opts)
254 return cvsps.debugcvsps(ui, *args, **opts)
246
255
247 commands.norepo += " convert debugsvnlog debugcvsps"
256 commands.norepo += " convert debugsvnlog debugcvsps"
248
257
249 cmdtable = {
258 cmdtable = {
250 "convert":
259 "convert":
251 (convert,
260 (convert,
252 [('A', 'authors', '', _('username mapping filename')),
261 [('A', 'authors', '', _('username mapping filename')),
253 ('d', 'dest-type', '', _('destination repository type')),
262 ('d', 'dest-type', '', _('destination repository type')),
254 ('', 'filemap', '', _('remap file names using contents of file')),
263 ('', 'filemap', '', _('remap file names using contents of file')),
255 ('r', 'rev', '', _('import up to target revision REV')),
264 ('r', 'rev', '', _('import up to target revision REV')),
256 ('s', 'source-type', '', _('source repository type')),
265 ('s', 'source-type', '', _('source repository type')),
257 ('', 'splicemap', '', _('splice synthesized history into place')),
266 ('', 'splicemap', '', _('splice synthesized history into place')),
258 ('', 'branchmap', '', _('change branch names while converting')),
267 ('', 'branchmap', '', _('change branch names while converting')),
259 ('', 'branchsort', None, _('try to sort changesets by branches')),
268 ('', 'branchsort', None, _('try to sort changesets by branches')),
260 ('', 'datesort', None, _('try to sort changesets by date')),
269 ('', 'datesort', None, _('try to sort changesets by date')),
261 ('', 'sourcesort', None, _('preserve source changesets order'))],
270 ('', 'sourcesort', None, _('preserve source changesets order'))],
262 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
271 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
263 "debugsvnlog":
272 "debugsvnlog":
264 (debugsvnlog,
273 (debugsvnlog,
265 [],
274 [],
266 'hg debugsvnlog'),
275 'hg debugsvnlog'),
267 "debugcvsps":
276 "debugcvsps":
268 (debugcvsps,
277 (debugcvsps,
269 [
278 [
270 # Main options shared with cvsps-2.1
279 # Main options shared with cvsps-2.1
271 ('b', 'branches', [], _('only return changes on specified branches')),
280 ('b', 'branches', [], _('only return changes on specified branches')),
272 ('p', 'prefix', '', _('prefix to remove from file names')),
281 ('p', 'prefix', '', _('prefix to remove from file names')),
273 ('r', 'revisions', [], _('only return changes after or between specified tags')),
282 ('r', 'revisions', [], _('only return changes after or between specified tags')),
274 ('u', 'update-cache', None, _("update cvs log cache")),
283 ('u', 'update-cache', None, _("update cvs log cache")),
275 ('x', 'new-cache', None, _("create new cvs log cache")),
284 ('x', 'new-cache', None, _("create new cvs log cache")),
276 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
285 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
277 ('', 'root', '', _('specify cvsroot')),
286 ('', 'root', '', _('specify cvsroot')),
278 # Options specific to builtin cvsps
287 # Options specific to builtin cvsps
279 ('', 'parents', '', _('show parent changesets')),
288 ('', 'parents', '', _('show parent changesets')),
280 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
289 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
281 # Options that are ignored for compatibility with cvsps-2.1
290 # Options that are ignored for compatibility with cvsps-2.1
282 ('A', 'cvs-direct', None, _('ignored for compatibility')),
291 ('A', 'cvs-direct', None, _('ignored for compatibility')),
283 ],
292 ],
284 _('hg debugcvsps [OPTION]... [PATH]...')),
293 _('hg debugcvsps [OPTION]... [PATH]...')),
285 }
294 }
@@ -1,831 +1,836
1 #
1 #
2 # Mercurial built-in replacement for cvsps.
2 # Mercurial built-in replacement for cvsps.
3 #
3 #
4 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
4 # Copyright 2008, Frank Kingswood <frank@kingswood-consulting.co.uk>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
7 # GNU General Public License version 2, incorporated herein by reference.
8
8
9 import os
9 import os
10 import re
10 import re
11 import cPickle as pickle
11 import cPickle as pickle
12 from mercurial import util
12 from mercurial import util
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial import hook
14
15
15 class logentry(object):
16 class logentry(object):
16 '''Class logentry has the following attributes:
17 '''Class logentry has the following attributes:
17 .author - author name as CVS knows it
18 .author - author name as CVS knows it
18 .branch - name of branch this revision is on
19 .branch - name of branch this revision is on
19 .branches - revision tuple of branches starting at this revision
20 .branches - revision tuple of branches starting at this revision
20 .comment - commit message
21 .comment - commit message
21 .date - the commit date as a (time, tz) tuple
22 .date - the commit date as a (time, tz) tuple
22 .dead - true if file revision is dead
23 .dead - true if file revision is dead
23 .file - Name of file
24 .file - Name of file
24 .lines - a tuple (+lines, -lines) or None
25 .lines - a tuple (+lines, -lines) or None
25 .parent - Previous revision of this entry
26 .parent - Previous revision of this entry
26 .rcs - name of file as returned from CVS
27 .rcs - name of file as returned from CVS
27 .revision - revision number as tuple
28 .revision - revision number as tuple
28 .tags - list of tags on the file
29 .tags - list of tags on the file
29 .synthetic - is this a synthetic "file ... added on ..." revision?
30 .synthetic - is this a synthetic "file ... added on ..." revision?
30 .mergepoint- the branch that has been merged from
31 .mergepoint- the branch that has been merged from
31 (if present in rlog output)
32 (if present in rlog output)
32 .branchpoints- the branches that start at the current entry
33 .branchpoints- the branches that start at the current entry
33 '''
34 '''
34 def __init__(self, **entries):
35 def __init__(self, **entries):
35 self.__dict__.update(entries)
36 self.__dict__.update(entries)
36
37
37 def __repr__(self):
38 def __repr__(self):
38 return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
39 return "<%s at 0x%x: %s %s>" % (self.__class__.__name__,
39 id(self),
40 id(self),
40 self.file,
41 self.file,
41 ".".join(map(str, self.revision)))
42 ".".join(map(str, self.revision)))
42
43
43 class logerror(Exception):
44 class logerror(Exception):
44 pass
45 pass
45
46
46 def getrepopath(cvspath):
47 def getrepopath(cvspath):
47 """Return the repository path from a CVS path.
48 """Return the repository path from a CVS path.
48
49
49 >>> getrepopath('/foo/bar')
50 >>> getrepopath('/foo/bar')
50 '/foo/bar'
51 '/foo/bar'
51 >>> getrepopath('c:/foo/bar')
52 >>> getrepopath('c:/foo/bar')
52 'c:/foo/bar'
53 'c:/foo/bar'
53 >>> getrepopath(':pserver:10/foo/bar')
54 >>> getrepopath(':pserver:10/foo/bar')
54 '/foo/bar'
55 '/foo/bar'
55 >>> getrepopath(':pserver:10c:/foo/bar')
56 >>> getrepopath(':pserver:10c:/foo/bar')
56 '/foo/bar'
57 '/foo/bar'
57 >>> getrepopath(':pserver:/foo/bar')
58 >>> getrepopath(':pserver:/foo/bar')
58 '/foo/bar'
59 '/foo/bar'
59 >>> getrepopath(':pserver:c:/foo/bar')
60 >>> getrepopath(':pserver:c:/foo/bar')
60 'c:/foo/bar'
61 'c:/foo/bar'
61 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
62 >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
62 '/foo/bar'
63 '/foo/bar'
63 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
64 >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
64 'c:/foo/bar'
65 'c:/foo/bar'
65 """
66 """
66 # According to CVS manual, CVS paths are expressed like:
67 # According to CVS manual, CVS paths are expressed like:
67 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
68 # [:method:][[user][:password]@]hostname[:[port]]/path/to/repository
68 #
69 #
69 # Unfortunately, Windows absolute paths start with a drive letter
70 # Unfortunately, Windows absolute paths start with a drive letter
70 # like 'c:' making it harder to parse. Here we assume that drive
71 # like 'c:' making it harder to parse. Here we assume that drive
71 # letters are only one character long and any CVS component before
72 # letters are only one character long and any CVS component before
72 # the repository path is at least 2 characters long, and use this
73 # the repository path is at least 2 characters long, and use this
73 # to disambiguate.
74 # to disambiguate.
74 parts = cvspath.split(':')
75 parts = cvspath.split(':')
75 if len(parts) == 1:
76 if len(parts) == 1:
76 return parts[0]
77 return parts[0]
77 # Here there is an ambiguous case if we have a port number
78 # Here there is an ambiguous case if we have a port number
78 # immediately followed by a Windows driver letter. We assume this
79 # immediately followed by a Windows driver letter. We assume this
79 # never happens and decide it must be CVS path component,
80 # never happens and decide it must be CVS path component,
80 # therefore ignoring it.
81 # therefore ignoring it.
81 if len(parts[-2]) > 1:
82 if len(parts[-2]) > 1:
82 return parts[-1].lstrip('0123456789')
83 return parts[-1].lstrip('0123456789')
83 return parts[-2] + ':' + parts[-1]
84 return parts[-2] + ':' + parts[-1]
84
85
85 def createlog(ui, directory=None, root="", rlog=True, cache=None):
86 def createlog(ui, directory=None, root="", rlog=True, cache=None):
86 '''Collect the CVS rlog'''
87 '''Collect the CVS rlog'''
87
88
88 # Because we store many duplicate commit log messages, reusing strings
89 # Because we store many duplicate commit log messages, reusing strings
89 # saves a lot of memory and pickle storage space.
90 # saves a lot of memory and pickle storage space.
90 _scache = {}
91 _scache = {}
91 def scache(s):
92 def scache(s):
92 "return a shared version of a string"
93 "return a shared version of a string"
93 return _scache.setdefault(s, s)
94 return _scache.setdefault(s, s)
94
95
95 ui.status(_('collecting CVS rlog\n'))
96 ui.status(_('collecting CVS rlog\n'))
96
97
97 log = [] # list of logentry objects containing the CVS state
98 log = [] # list of logentry objects containing the CVS state
98
99
99 # patterns to match in CVS (r)log output, by state of use
100 # patterns to match in CVS (r)log output, by state of use
100 re_00 = re.compile('RCS file: (.+)$')
101 re_00 = re.compile('RCS file: (.+)$')
101 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
102 re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
102 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
103 re_02 = re.compile('cvs (r?log|server): (.+)\n$')
103 re_03 = re.compile("(Cannot access.+CVSROOT)|"
104 re_03 = re.compile("(Cannot access.+CVSROOT)|"
104 "(can't create temporary directory.+)$")
105 "(can't create temporary directory.+)$")
105 re_10 = re.compile('Working file: (.+)$')
106 re_10 = re.compile('Working file: (.+)$')
106 re_20 = re.compile('symbolic names:')
107 re_20 = re.compile('symbolic names:')
107 re_30 = re.compile('\t(.+): ([\\d.]+)$')
108 re_30 = re.compile('\t(.+): ([\\d.]+)$')
108 re_31 = re.compile('----------------------------$')
109 re_31 = re.compile('----------------------------$')
109 re_32 = re.compile('======================================='
110 re_32 = re.compile('======================================='
110 '======================================$')
111 '======================================$')
111 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
112 re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
112 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
113 re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
113 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
114 r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
114 r'(.*mergepoint:\s+([^;]+);)?')
115 r'(.*mergepoint:\s+([^;]+);)?')
115 re_70 = re.compile('branches: (.+);$')
116 re_70 = re.compile('branches: (.+);$')
116
117
117 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
118 file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
118
119
119 prefix = '' # leading path to strip of what we get from CVS
120 prefix = '' # leading path to strip of what we get from CVS
120
121
121 if directory is None:
122 if directory is None:
122 # Current working directory
123 # Current working directory
123
124
124 # Get the real directory in the repository
125 # Get the real directory in the repository
125 try:
126 try:
126 prefix = open(os.path.join('CVS','Repository')).read().strip()
127 prefix = open(os.path.join('CVS','Repository')).read().strip()
127 if prefix == ".":
128 if prefix == ".":
128 prefix = ""
129 prefix = ""
129 directory = prefix
130 directory = prefix
130 except IOError:
131 except IOError:
131 raise logerror('Not a CVS sandbox')
132 raise logerror('Not a CVS sandbox')
132
133
133 if prefix and not prefix.endswith(os.sep):
134 if prefix and not prefix.endswith(os.sep):
134 prefix += os.sep
135 prefix += os.sep
135
136
136 # Use the Root file in the sandbox, if it exists
137 # Use the Root file in the sandbox, if it exists
137 try:
138 try:
138 root = open(os.path.join('CVS','Root')).read().strip()
139 root = open(os.path.join('CVS','Root')).read().strip()
139 except IOError:
140 except IOError:
140 pass
141 pass
141
142
142 if not root:
143 if not root:
143 root = os.environ.get('CVSROOT', '')
144 root = os.environ.get('CVSROOT', '')
144
145
145 # read log cache if one exists
146 # read log cache if one exists
146 oldlog = []
147 oldlog = []
147 date = None
148 date = None
148
149
149 if cache:
150 if cache:
150 cachedir = os.path.expanduser('~/.hg.cvsps')
151 cachedir = os.path.expanduser('~/.hg.cvsps')
151 if not os.path.exists(cachedir):
152 if not os.path.exists(cachedir):
152 os.mkdir(cachedir)
153 os.mkdir(cachedir)
153
154
154 # The cvsps cache pickle needs a uniquified name, based on the
155 # The cvsps cache pickle needs a uniquified name, based on the
155 # repository location. The address may have all sort of nasties
156 # repository location. The address may have all sort of nasties
156 # in it, slashes, colons and such. So here we take just the
157 # in it, slashes, colons and such. So here we take just the
157 # alphanumerics, concatenated in a way that does not mix up the
158 # alphanumerics, concatenated in a way that does not mix up the
158 # various components, so that
159 # various components, so that
159 # :pserver:user@server:/path
160 # :pserver:user@server:/path
160 # and
161 # and
161 # /pserver/user/server/path
162 # /pserver/user/server/path
162 # are mapped to different cache file names.
163 # are mapped to different cache file names.
163 cachefile = root.split(":") + [directory, "cache"]
164 cachefile = root.split(":") + [directory, "cache"]
164 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
165 cachefile = ['-'.join(re.findall(r'\w+', s)) for s in cachefile if s]
165 cachefile = os.path.join(cachedir,
166 cachefile = os.path.join(cachedir,
166 '.'.join([s for s in cachefile if s]))
167 '.'.join([s for s in cachefile if s]))
167
168
168 if cache == 'update':
169 if cache == 'update':
169 try:
170 try:
170 ui.note(_('reading cvs log cache %s\n') % cachefile)
171 ui.note(_('reading cvs log cache %s\n') % cachefile)
171 oldlog = pickle.load(open(cachefile))
172 oldlog = pickle.load(open(cachefile))
172 ui.note(_('cache has %d log entries\n') % len(oldlog))
173 ui.note(_('cache has %d log entries\n') % len(oldlog))
173 except Exception, e:
174 except Exception, e:
174 ui.note(_('error reading cache: %r\n') % e)
175 ui.note(_('error reading cache: %r\n') % e)
175
176
176 if oldlog:
177 if oldlog:
177 date = oldlog[-1].date # last commit date as a (time,tz) tuple
178 date = oldlog[-1].date # last commit date as a (time,tz) tuple
178 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
179 date = util.datestr(date, '%Y/%m/%d %H:%M:%S %1%2')
179
180
180 # build the CVS commandline
181 # build the CVS commandline
181 cmd = ['cvs', '-q']
182 cmd = ['cvs', '-q']
182 if root:
183 if root:
183 cmd.append('-d%s' % root)
184 cmd.append('-d%s' % root)
184 p = util.normpath(getrepopath(root))
185 p = util.normpath(getrepopath(root))
185 if not p.endswith('/'):
186 if not p.endswith('/'):
186 p += '/'
187 p += '/'
187 prefix = p + util.normpath(prefix)
188 prefix = p + util.normpath(prefix)
188 cmd.append(['log', 'rlog'][rlog])
189 cmd.append(['log', 'rlog'][rlog])
189 if date:
190 if date:
190 # no space between option and date string
191 # no space between option and date string
191 cmd.append('-d>%s' % date)
192 cmd.append('-d>%s' % date)
192 cmd.append(directory)
193 cmd.append(directory)
193
194
194 # state machine begins here
195 # state machine begins here
195 tags = {} # dictionary of revisions on current file with their tags
196 tags = {} # dictionary of revisions on current file with their tags
196 branchmap = {} # mapping between branch names and revision numbers
197 branchmap = {} # mapping between branch names and revision numbers
197 state = 0
198 state = 0
198 store = False # set when a new record can be appended
199 store = False # set when a new record can be appended
199
200
200 cmd = [util.shellquote(arg) for arg in cmd]
201 cmd = [util.shellquote(arg) for arg in cmd]
201 ui.note(_("running %s\n") % (' '.join(cmd)))
202 ui.note(_("running %s\n") % (' '.join(cmd)))
202 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
203 ui.debug("prefix=%r directory=%r root=%r\n" % (prefix, directory, root))
203
204
204 pfp = util.popen(' '.join(cmd))
205 pfp = util.popen(' '.join(cmd))
205 peek = pfp.readline()
206 peek = pfp.readline()
206 while True:
207 while True:
207 line = peek
208 line = peek
208 if line == '':
209 if line == '':
209 break
210 break
210 peek = pfp.readline()
211 peek = pfp.readline()
211 if line.endswith('\n'):
212 if line.endswith('\n'):
212 line = line[:-1]
213 line = line[:-1]
213 #ui.debug('state=%d line=%r\n' % (state, line))
214 #ui.debug('state=%d line=%r\n' % (state, line))
214
215
215 if state == 0:
216 if state == 0:
216 # initial state, consume input until we see 'RCS file'
217 # initial state, consume input until we see 'RCS file'
217 match = re_00.match(line)
218 match = re_00.match(line)
218 if match:
219 if match:
219 rcs = match.group(1)
220 rcs = match.group(1)
220 tags = {}
221 tags = {}
221 if rlog:
222 if rlog:
222 filename = util.normpath(rcs[:-2])
223 filename = util.normpath(rcs[:-2])
223 if filename.startswith(prefix):
224 if filename.startswith(prefix):
224 filename = filename[len(prefix):]
225 filename = filename[len(prefix):]
225 if filename.startswith('/'):
226 if filename.startswith('/'):
226 filename = filename[1:]
227 filename = filename[1:]
227 if filename.startswith('Attic/'):
228 if filename.startswith('Attic/'):
228 filename = filename[6:]
229 filename = filename[6:]
229 else:
230 else:
230 filename = filename.replace('/Attic/', '/')
231 filename = filename.replace('/Attic/', '/')
231 state = 2
232 state = 2
232 continue
233 continue
233 state = 1
234 state = 1
234 continue
235 continue
235 match = re_01.match(line)
236 match = re_01.match(line)
236 if match:
237 if match:
237 raise Exception(match.group(1))
238 raise Exception(match.group(1))
238 match = re_02.match(line)
239 match = re_02.match(line)
239 if match:
240 if match:
240 raise Exception(match.group(2))
241 raise Exception(match.group(2))
241 if re_03.match(line):
242 if re_03.match(line):
242 raise Exception(line)
243 raise Exception(line)
243
244
244 elif state == 1:
245 elif state == 1:
245 # expect 'Working file' (only when using log instead of rlog)
246 # expect 'Working file' (only when using log instead of rlog)
246 match = re_10.match(line)
247 match = re_10.match(line)
247 assert match, _('RCS file must be followed by working file')
248 assert match, _('RCS file must be followed by working file')
248 filename = util.normpath(match.group(1))
249 filename = util.normpath(match.group(1))
249 state = 2
250 state = 2
250
251
251 elif state == 2:
252 elif state == 2:
252 # expect 'symbolic names'
253 # expect 'symbolic names'
253 if re_20.match(line):
254 if re_20.match(line):
254 branchmap = {}
255 branchmap = {}
255 state = 3
256 state = 3
256
257
257 elif state == 3:
258 elif state == 3:
258 # read the symbolic names and store as tags
259 # read the symbolic names and store as tags
259 match = re_30.match(line)
260 match = re_30.match(line)
260 if match:
261 if match:
261 rev = [int(x) for x in match.group(2).split('.')]
262 rev = [int(x) for x in match.group(2).split('.')]
262
263
263 # Convert magic branch number to an odd-numbered one
264 # Convert magic branch number to an odd-numbered one
264 revn = len(rev)
265 revn = len(rev)
265 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
266 if revn > 3 and (revn % 2) == 0 and rev[-2] == 0:
266 rev = rev[:-2] + rev[-1:]
267 rev = rev[:-2] + rev[-1:]
267 rev = tuple(rev)
268 rev = tuple(rev)
268
269
269 if rev not in tags:
270 if rev not in tags:
270 tags[rev] = []
271 tags[rev] = []
271 tags[rev].append(match.group(1))
272 tags[rev].append(match.group(1))
272 branchmap[match.group(1)] = match.group(2)
273 branchmap[match.group(1)] = match.group(2)
273
274
274 elif re_31.match(line):
275 elif re_31.match(line):
275 state = 5
276 state = 5
276 elif re_32.match(line):
277 elif re_32.match(line):
277 state = 0
278 state = 0
278
279
279 elif state == 4:
280 elif state == 4:
280 # expecting '------' separator before first revision
281 # expecting '------' separator before first revision
281 if re_31.match(line):
282 if re_31.match(line):
282 state = 5
283 state = 5
283 else:
284 else:
284 assert not re_32.match(line), _('must have at least '
285 assert not re_32.match(line), _('must have at least '
285 'some revisions')
286 'some revisions')
286
287
287 elif state == 5:
288 elif state == 5:
288 # expecting revision number and possibly (ignored) lock indication
289 # expecting revision number and possibly (ignored) lock indication
289 # we create the logentry here from values stored in states 0 to 4,
290 # we create the logentry here from values stored in states 0 to 4,
290 # as this state is re-entered for subsequent revisions of a file.
291 # as this state is re-entered for subsequent revisions of a file.
291 match = re_50.match(line)
292 match = re_50.match(line)
292 assert match, _('expected revision number')
293 assert match, _('expected revision number')
293 e = logentry(rcs=scache(rcs), file=scache(filename),
294 e = logentry(rcs=scache(rcs), file=scache(filename),
294 revision=tuple([int(x) for x in match.group(1).split('.')]),
295 revision=tuple([int(x) for x in match.group(1).split('.')]),
295 branches=[], parent=None,
296 branches=[], parent=None,
296 synthetic=False)
297 synthetic=False)
297 state = 6
298 state = 6
298
299
299 elif state == 6:
300 elif state == 6:
300 # expecting date, author, state, lines changed
301 # expecting date, author, state, lines changed
301 match = re_60.match(line)
302 match = re_60.match(line)
302 assert match, _('revision must be followed by date line')
303 assert match, _('revision must be followed by date line')
303 d = match.group(1)
304 d = match.group(1)
304 if d[2] == '/':
305 if d[2] == '/':
305 # Y2K
306 # Y2K
306 d = '19' + d
307 d = '19' + d
307
308
308 if len(d.split()) != 3:
309 if len(d.split()) != 3:
309 # cvs log dates always in GMT
310 # cvs log dates always in GMT
310 d = d + ' UTC'
311 d = d + ' UTC'
311 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
312 e.date = util.parsedate(d, ['%y/%m/%d %H:%M:%S',
312 '%Y/%m/%d %H:%M:%S',
313 '%Y/%m/%d %H:%M:%S',
313 '%Y-%m-%d %H:%M:%S'])
314 '%Y-%m-%d %H:%M:%S'])
314 e.author = scache(match.group(2))
315 e.author = scache(match.group(2))
315 e.dead = match.group(3).lower() == 'dead'
316 e.dead = match.group(3).lower() == 'dead'
316
317
317 if match.group(5):
318 if match.group(5):
318 if match.group(6):
319 if match.group(6):
319 e.lines = (int(match.group(5)), int(match.group(6)))
320 e.lines = (int(match.group(5)), int(match.group(6)))
320 else:
321 else:
321 e.lines = (int(match.group(5)), 0)
322 e.lines = (int(match.group(5)), 0)
322 elif match.group(6):
323 elif match.group(6):
323 e.lines = (0, int(match.group(6)))
324 e.lines = (0, int(match.group(6)))
324 else:
325 else:
325 e.lines = None
326 e.lines = None
326
327
327 if match.group(7): # cvsnt mergepoint
328 if match.group(7): # cvsnt mergepoint
328 myrev = match.group(8).split('.')
329 myrev = match.group(8).split('.')
329 if len(myrev) == 2: # head
330 if len(myrev) == 2: # head
330 e.mergepoint = 'HEAD'
331 e.mergepoint = 'HEAD'
331 else:
332 else:
332 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
333 myrev = '.'.join(myrev[:-2] + ['0', myrev[-2]])
333 branches = [b for b in branchmap if branchmap[b] == myrev]
334 branches = [b for b in branchmap if branchmap[b] == myrev]
334 assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
335 assert len(branches) == 1, 'unknown branch: %s' % e.mergepoint
335 e.mergepoint = branches[0]
336 e.mergepoint = branches[0]
336 else:
337 else:
337 e.mergepoint = None
338 e.mergepoint = None
338 e.comment = []
339 e.comment = []
339 state = 7
340 state = 7
340
341
341 elif state == 7:
342 elif state == 7:
342 # read the revision numbers of branches that start at this revision
343 # read the revision numbers of branches that start at this revision
343 # or store the commit log message otherwise
344 # or store the commit log message otherwise
344 m = re_70.match(line)
345 m = re_70.match(line)
345 if m:
346 if m:
346 e.branches = [tuple([int(y) for y in x.strip().split('.')])
347 e.branches = [tuple([int(y) for y in x.strip().split('.')])
347 for x in m.group(1).split(';')]
348 for x in m.group(1).split(';')]
348 state = 8
349 state = 8
349 elif re_31.match(line) and re_50.match(peek):
350 elif re_31.match(line) and re_50.match(peek):
350 state = 5
351 state = 5
351 store = True
352 store = True
352 elif re_32.match(line):
353 elif re_32.match(line):
353 state = 0
354 state = 0
354 store = True
355 store = True
355 else:
356 else:
356 e.comment.append(line)
357 e.comment.append(line)
357
358
358 elif state == 8:
359 elif state == 8:
359 # store commit log message
360 # store commit log message
360 if re_31.match(line):
361 if re_31.match(line):
361 state = 5
362 state = 5
362 store = True
363 store = True
363 elif re_32.match(line):
364 elif re_32.match(line):
364 state = 0
365 state = 0
365 store = True
366 store = True
366 else:
367 else:
367 e.comment.append(line)
368 e.comment.append(line)
368
369
369 # When a file is added on a branch B1, CVS creates a synthetic
370 # When a file is added on a branch B1, CVS creates a synthetic
370 # dead trunk revision 1.1 so that the branch has a root.
371 # dead trunk revision 1.1 so that the branch has a root.
371 # Likewise, if you merge such a file to a later branch B2 (one
372 # Likewise, if you merge such a file to a later branch B2 (one
372 # that already existed when the file was added on B1), CVS
373 # that already existed when the file was added on B1), CVS
373 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
374 # creates a synthetic dead revision 1.1.x.1 on B2. Don't drop
374 # these revisions now, but mark them synthetic so
375 # these revisions now, but mark them synthetic so
375 # createchangeset() can take care of them.
376 # createchangeset() can take care of them.
376 if (store and
377 if (store and
377 e.dead and
378 e.dead and
378 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
379 e.revision[-1] == 1 and # 1.1 or 1.1.x.1
379 len(e.comment) == 1 and
380 len(e.comment) == 1 and
380 file_added_re.match(e.comment[0])):
381 file_added_re.match(e.comment[0])):
381 ui.debug('found synthetic revision in %s: %r\n'
382 ui.debug('found synthetic revision in %s: %r\n'
382 % (e.rcs, e.comment[0]))
383 % (e.rcs, e.comment[0]))
383 e.synthetic = True
384 e.synthetic = True
384
385
385 if store:
386 if store:
386 # clean up the results and save in the log.
387 # clean up the results and save in the log.
387 store = False
388 store = False
388 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
389 e.tags = sorted([scache(x) for x in tags.get(e.revision, [])])
389 e.comment = scache('\n'.join(e.comment))
390 e.comment = scache('\n'.join(e.comment))
390
391
391 revn = len(e.revision)
392 revn = len(e.revision)
392 if revn > 3 and (revn % 2) == 0:
393 if revn > 3 and (revn % 2) == 0:
393 e.branch = tags.get(e.revision[:-1], [None])[0]
394 e.branch = tags.get(e.revision[:-1], [None])[0]
394 else:
395 else:
395 e.branch = None
396 e.branch = None
396
397
397 # find the branches starting from this revision
398 # find the branches starting from this revision
398 branchpoints = set()
399 branchpoints = set()
399 for branch, revision in branchmap.iteritems():
400 for branch, revision in branchmap.iteritems():
400 revparts = tuple([int(i) for i in revision.split('.')])
401 revparts = tuple([int(i) for i in revision.split('.')])
401 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
402 if revparts[-2] == 0 and revparts[-1] % 2 == 0:
402 # normal branch
403 # normal branch
403 if revparts[:-2] == e.revision:
404 if revparts[:-2] == e.revision:
404 branchpoints.add(branch)
405 branchpoints.add(branch)
405 elif revparts == (1,1,1): # vendor branch
406 elif revparts == (1,1,1): # vendor branch
406 if revparts in e.branches:
407 if revparts in e.branches:
407 branchpoints.add(branch)
408 branchpoints.add(branch)
408 e.branchpoints = branchpoints
409 e.branchpoints = branchpoints
409
410
410 log.append(e)
411 log.append(e)
411
412
412 if len(log) % 100 == 0:
413 if len(log) % 100 == 0:
413 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
414 ui.status(util.ellipsis('%d %s' % (len(log), e.file), 80)+'\n')
414
415
415 log.sort(key=lambda x: (x.rcs, x.revision))
416 log.sort(key=lambda x: (x.rcs, x.revision))
416
417
417 # find parent revisions of individual files
418 # find parent revisions of individual files
418 versions = {}
419 versions = {}
419 for e in log:
420 for e in log:
420 branch = e.revision[:-1]
421 branch = e.revision[:-1]
421 p = versions.get((e.rcs, branch), None)
422 p = versions.get((e.rcs, branch), None)
422 if p is None:
423 if p is None:
423 p = e.revision[:-2]
424 p = e.revision[:-2]
424 e.parent = p
425 e.parent = p
425 versions[(e.rcs, branch)] = e.revision
426 versions[(e.rcs, branch)] = e.revision
426
427
427 # update the log cache
428 # update the log cache
428 if cache:
429 if cache:
429 if log:
430 if log:
430 # join up the old and new logs
431 # join up the old and new logs
431 log.sort(key=lambda x: x.date)
432 log.sort(key=lambda x: x.date)
432
433
433 if oldlog and oldlog[-1].date >= log[0].date:
434 if oldlog and oldlog[-1].date >= log[0].date:
434 raise logerror('Log cache overlaps with new log entries,'
435 raise logerror('Log cache overlaps with new log entries,'
435 ' re-run without cache.')
436 ' re-run without cache.')
436
437
437 log = oldlog + log
438 log = oldlog + log
438
439
439 # write the new cachefile
440 # write the new cachefile
440 ui.note(_('writing cvs log cache %s\n') % cachefile)
441 ui.note(_('writing cvs log cache %s\n') % cachefile)
441 pickle.dump(log, open(cachefile, 'w'))
442 pickle.dump(log, open(cachefile, 'w'))
442 else:
443 else:
443 log = oldlog
444 log = oldlog
444
445
445 ui.status(_('%d log entries\n') % len(log))
446 ui.status(_('%d log entries\n') % len(log))
446
447
448 hook.hook(ui, None, "cvslog", True, log=log)
449
447 return log
450 return log
448
451
449
452
450 class changeset(object):
453 class changeset(object):
451 '''Class changeset has the following attributes:
454 '''Class changeset has the following attributes:
452 .id - integer identifying this changeset (list index)
455 .id - integer identifying this changeset (list index)
453 .author - author name as CVS knows it
456 .author - author name as CVS knows it
454 .branch - name of branch this changeset is on, or None
457 .branch - name of branch this changeset is on, or None
455 .comment - commit message
458 .comment - commit message
456 .date - the commit date as a (time,tz) tuple
459 .date - the commit date as a (time,tz) tuple
457 .entries - list of logentry objects in this changeset
460 .entries - list of logentry objects in this changeset
458 .parents - list of one or two parent changesets
461 .parents - list of one or two parent changesets
459 .tags - list of tags on this changeset
462 .tags - list of tags on this changeset
460 .synthetic - from synthetic revision "file ... added on branch ..."
463 .synthetic - from synthetic revision "file ... added on branch ..."
461 .mergepoint- the branch that has been merged from
464 .mergepoint- the branch that has been merged from
462 (if present in rlog output)
465 (if present in rlog output)
463 .branchpoints- the branches that start at the current entry
466 .branchpoints- the branches that start at the current entry
464 '''
467 '''
465 def __init__(self, **entries):
468 def __init__(self, **entries):
466 self.__dict__.update(entries)
469 self.__dict__.update(entries)
467
470
468 def __repr__(self):
471 def __repr__(self):
469 return "<%s at 0x%x: %s>" % (self.__class__.__name__,
472 return "<%s at 0x%x: %s>" % (self.__class__.__name__,
470 id(self),
473 id(self),
471 getattr(self, 'id', "(no id)"))
474 getattr(self, 'id', "(no id)"))
472
475
473 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
476 def createchangeset(ui, log, fuzz=60, mergefrom=None, mergeto=None):
474 '''Convert log into changesets.'''
477 '''Convert log into changesets.'''
475
478
476 ui.status(_('creating changesets\n'))
479 ui.status(_('creating changesets\n'))
477
480
478 # Merge changesets
481 # Merge changesets
479
482
480 log.sort(key=lambda x: (x.comment, x.author, x.branch, x.date))
483 log.sort(key=lambda x: (x.comment, x.author, x.branch, x.date))
481
484
482 changesets = []
485 changesets = []
483 files = set()
486 files = set()
484 c = None
487 c = None
485 for i, e in enumerate(log):
488 for i, e in enumerate(log):
486
489
487 # Check if log entry belongs to the current changeset or not.
490 # Check if log entry belongs to the current changeset or not.
488
491
489 # Since CVS is file centric, two different file revisions with
492 # Since CVS is file centric, two different file revisions with
490 # different branchpoints should be treated as belonging to two
493 # different branchpoints should be treated as belonging to two
491 # different changesets (and the ordering is important and not
494 # different changesets (and the ordering is important and not
492 # honoured by cvsps at this point).
495 # honoured by cvsps at this point).
493 #
496 #
494 # Consider the following case:
497 # Consider the following case:
495 # foo 1.1 branchpoints: [MYBRANCH]
498 # foo 1.1 branchpoints: [MYBRANCH]
496 # bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
499 # bar 1.1 branchpoints: [MYBRANCH, MYBRANCH2]
497 #
500 #
498 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
501 # Here foo is part only of MYBRANCH, but not MYBRANCH2, e.g. a
499 # later version of foo may be in MYBRANCH2, so foo should be the
502 # later version of foo may be in MYBRANCH2, so foo should be the
500 # first changeset and bar the next and MYBRANCH and MYBRANCH2
503 # first changeset and bar the next and MYBRANCH and MYBRANCH2
501 # should both start off of the bar changeset. No provisions are
504 # should both start off of the bar changeset. No provisions are
502 # made to ensure that this is, in fact, what happens.
505 # made to ensure that this is, in fact, what happens.
503 if not (c and
506 if not (c and
504 e.comment == c.comment and
507 e.comment == c.comment and
505 e.author == c.author and
508 e.author == c.author and
506 e.branch == c.branch and
509 e.branch == c.branch and
507 (not hasattr(e, 'branchpoints') or
510 (not hasattr(e, 'branchpoints') or
508 not hasattr (c, 'branchpoints') or
511 not hasattr (c, 'branchpoints') or
509 e.branchpoints == c.branchpoints) and
512 e.branchpoints == c.branchpoints) and
510 ((c.date[0] + c.date[1]) <=
513 ((c.date[0] + c.date[1]) <=
511 (e.date[0] + e.date[1]) <=
514 (e.date[0] + e.date[1]) <=
512 (c.date[0] + c.date[1]) + fuzz) and
515 (c.date[0] + c.date[1]) + fuzz) and
513 e.file not in files):
516 e.file not in files):
514 c = changeset(comment=e.comment, author=e.author,
517 c = changeset(comment=e.comment, author=e.author,
515 branch=e.branch, date=e.date, entries=[],
518 branch=e.branch, date=e.date, entries=[],
516 mergepoint=getattr(e, 'mergepoint', None),
519 mergepoint=getattr(e, 'mergepoint', None),
517 branchpoints=getattr(e, 'branchpoints', set()))
520 branchpoints=getattr(e, 'branchpoints', set()))
518 changesets.append(c)
521 changesets.append(c)
519 files = set()
522 files = set()
520 if len(changesets) % 100 == 0:
523 if len(changesets) % 100 == 0:
521 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
524 t = '%d %s' % (len(changesets), repr(e.comment)[1:-1])
522 ui.status(util.ellipsis(t, 80) + '\n')
525 ui.status(util.ellipsis(t, 80) + '\n')
523
526
524 c.entries.append(e)
527 c.entries.append(e)
525 files.add(e.file)
528 files.add(e.file)
526 c.date = e.date # changeset date is date of latest commit in it
529 c.date = e.date # changeset date is date of latest commit in it
527
530
528 # Mark synthetic changesets
531 # Mark synthetic changesets
529
532
530 for c in changesets:
533 for c in changesets:
531 # Synthetic revisions always get their own changeset, because
534 # Synthetic revisions always get their own changeset, because
532 # the log message includes the filename. E.g. if you add file3
535 # the log message includes the filename. E.g. if you add file3
533 # and file4 on a branch, you get four log entries and three
536 # and file4 on a branch, you get four log entries and three
534 # changesets:
537 # changesets:
535 # "File file3 was added on branch ..." (synthetic, 1 entry)
538 # "File file3 was added on branch ..." (synthetic, 1 entry)
536 # "File file4 was added on branch ..." (synthetic, 1 entry)
539 # "File file4 was added on branch ..." (synthetic, 1 entry)
537 # "Add file3 and file4 to fix ..." (real, 2 entries)
540 # "Add file3 and file4 to fix ..." (real, 2 entries)
538 # Hence the check for 1 entry here.
541 # Hence the check for 1 entry here.
539 synth = getattr(c.entries[0], 'synthetic', None)
542 synth = getattr(c.entries[0], 'synthetic', None)
540 c.synthetic = (len(c.entries) == 1 and synth)
543 c.synthetic = (len(c.entries) == 1 and synth)
541
544
542 # Sort files in each changeset
545 # Sort files in each changeset
543
546
544 for c in changesets:
547 for c in changesets:
545 def pathcompare(l, r):
548 def pathcompare(l, r):
546 'Mimic cvsps sorting order'
549 'Mimic cvsps sorting order'
547 l = l.split('/')
550 l = l.split('/')
548 r = r.split('/')
551 r = r.split('/')
549 nl = len(l)
552 nl = len(l)
550 nr = len(r)
553 nr = len(r)
551 n = min(nl, nr)
554 n = min(nl, nr)
552 for i in range(n):
555 for i in range(n):
553 if i + 1 == nl and nl < nr:
556 if i + 1 == nl and nl < nr:
554 return -1
557 return -1
555 elif i + 1 == nr and nl > nr:
558 elif i + 1 == nr and nl > nr:
556 return +1
559 return +1
557 elif l[i] < r[i]:
560 elif l[i] < r[i]:
558 return -1
561 return -1
559 elif l[i] > r[i]:
562 elif l[i] > r[i]:
560 return +1
563 return +1
561 return 0
564 return 0
562 def entitycompare(l, r):
565 def entitycompare(l, r):
563 return pathcompare(l.file, r.file)
566 return pathcompare(l.file, r.file)
564
567
565 c.entries.sort(entitycompare)
568 c.entries.sort(entitycompare)
566
569
567 # Sort changesets by date
570 # Sort changesets by date
568
571
569 def cscmp(l, r):
572 def cscmp(l, r):
570 d = sum(l.date) - sum(r.date)
573 d = sum(l.date) - sum(r.date)
571 if d:
574 if d:
572 return d
575 return d
573
576
574 # detect vendor branches and initial commits on a branch
577 # detect vendor branches and initial commits on a branch
575 le = {}
578 le = {}
576 for e in l.entries:
579 for e in l.entries:
577 le[e.rcs] = e.revision
580 le[e.rcs] = e.revision
578 re = {}
581 re = {}
579 for e in r.entries:
582 for e in r.entries:
580 re[e.rcs] = e.revision
583 re[e.rcs] = e.revision
581
584
582 d = 0
585 d = 0
583 for e in l.entries:
586 for e in l.entries:
584 if re.get(e.rcs, None) == e.parent:
587 if re.get(e.rcs, None) == e.parent:
585 assert not d
588 assert not d
586 d = 1
589 d = 1
587 break
590 break
588
591
589 for e in r.entries:
592 for e in r.entries:
590 if le.get(e.rcs, None) == e.parent:
593 if le.get(e.rcs, None) == e.parent:
591 assert not d
594 assert not d
592 d = -1
595 d = -1
593 break
596 break
594
597
595 return d
598 return d
596
599
597 changesets.sort(cscmp)
600 changesets.sort(cscmp)
598
601
599 # Collect tags
602 # Collect tags
600
603
601 globaltags = {}
604 globaltags = {}
602 for c in changesets:
605 for c in changesets:
603 for e in c.entries:
606 for e in c.entries:
604 for tag in e.tags:
607 for tag in e.tags:
605 # remember which is the latest changeset to have this tag
608 # remember which is the latest changeset to have this tag
606 globaltags[tag] = c
609 globaltags[tag] = c
607
610
608 for c in changesets:
611 for c in changesets:
609 tags = set()
612 tags = set()
610 for e in c.entries:
613 for e in c.entries:
611 tags.update(e.tags)
614 tags.update(e.tags)
612 # remember tags only if this is the latest changeset to have it
615 # remember tags only if this is the latest changeset to have it
613 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
616 c.tags = sorted(tag for tag in tags if globaltags[tag] is c)
614
617
615 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
618 # Find parent changesets, handle {{mergetobranch BRANCHNAME}}
616 # by inserting dummy changesets with two parents, and handle
619 # by inserting dummy changesets with two parents, and handle
617 # {{mergefrombranch BRANCHNAME}} by setting two parents.
620 # {{mergefrombranch BRANCHNAME}} by setting two parents.
618
621
619 if mergeto is None:
622 if mergeto is None:
620 mergeto = r'{{mergetobranch ([-\w]+)}}'
623 mergeto = r'{{mergetobranch ([-\w]+)}}'
621 if mergeto:
624 if mergeto:
622 mergeto = re.compile(mergeto)
625 mergeto = re.compile(mergeto)
623
626
624 if mergefrom is None:
627 if mergefrom is None:
625 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
628 mergefrom = r'{{mergefrombranch ([-\w]+)}}'
626 if mergefrom:
629 if mergefrom:
627 mergefrom = re.compile(mergefrom)
630 mergefrom = re.compile(mergefrom)
628
631
629 versions = {} # changeset index where we saw any particular file version
632 versions = {} # changeset index where we saw any particular file version
630 branches = {} # changeset index where we saw a branch
633 branches = {} # changeset index where we saw a branch
631 n = len(changesets)
634 n = len(changesets)
632 i = 0
635 i = 0
633 while i<n:
636 while i<n:
634 c = changesets[i]
637 c = changesets[i]
635
638
636 for f in c.entries:
639 for f in c.entries:
637 versions[(f.rcs, f.revision)] = i
640 versions[(f.rcs, f.revision)] = i
638
641
639 p = None
642 p = None
640 if c.branch in branches:
643 if c.branch in branches:
641 p = branches[c.branch]
644 p = branches[c.branch]
642 else:
645 else:
643 # first changeset on a new branch
646 # first changeset on a new branch
644 # the parent is a changeset with the branch in its
647 # the parent is a changeset with the branch in its
645 # branchpoints such that it is the latest possible
648 # branchpoints such that it is the latest possible
646 # commit without any intervening, unrelated commits.
649 # commit without any intervening, unrelated commits.
647
650
648 for candidate in xrange(i):
651 for candidate in xrange(i):
649 if c.branch not in changesets[candidate].branchpoints:
652 if c.branch not in changesets[candidate].branchpoints:
650 if p is not None:
653 if p is not None:
651 break
654 break
652 continue
655 continue
653 p = candidate
656 p = candidate
654
657
655 c.parents = []
658 c.parents = []
656 if p is not None:
659 if p is not None:
657 p = changesets[p]
660 p = changesets[p]
658
661
659 # Ensure no changeset has a synthetic changeset as a parent.
662 # Ensure no changeset has a synthetic changeset as a parent.
660 while p.synthetic:
663 while p.synthetic:
661 assert len(p.parents) <= 1, \
664 assert len(p.parents) <= 1, \
662 _('synthetic changeset cannot have multiple parents')
665 _('synthetic changeset cannot have multiple parents')
663 if p.parents:
666 if p.parents:
664 p = p.parents[0]
667 p = p.parents[0]
665 else:
668 else:
666 p = None
669 p = None
667 break
670 break
668
671
669 if p is not None:
672 if p is not None:
670 c.parents.append(p)
673 c.parents.append(p)
671
674
672 if c.mergepoint:
675 if c.mergepoint:
673 if c.mergepoint == 'HEAD':
676 if c.mergepoint == 'HEAD':
674 c.mergepoint = None
677 c.mergepoint = None
675 c.parents.append(changesets[branches[c.mergepoint]])
678 c.parents.append(changesets[branches[c.mergepoint]])
676
679
677 if mergefrom:
680 if mergefrom:
678 m = mergefrom.search(c.comment)
681 m = mergefrom.search(c.comment)
679 if m:
682 if m:
680 m = m.group(1)
683 m = m.group(1)
681 if m == 'HEAD':
684 if m == 'HEAD':
682 m = None
685 m = None
683 try:
686 try:
684 candidate = changesets[branches[m]]
687 candidate = changesets[branches[m]]
685 except KeyError:
688 except KeyError:
686 ui.warn(_("warning: CVS commit message references "
689 ui.warn(_("warning: CVS commit message references "
687 "non-existent branch %r:\n%s\n")
690 "non-existent branch %r:\n%s\n")
688 % (m, c.comment))
691 % (m, c.comment))
689 if m in branches and c.branch != m and not candidate.synthetic:
692 if m in branches and c.branch != m and not candidate.synthetic:
690 c.parents.append(candidate)
693 c.parents.append(candidate)
691
694
692 if mergeto:
695 if mergeto:
693 m = mergeto.search(c.comment)
696 m = mergeto.search(c.comment)
694 if m:
697 if m:
695 try:
698 try:
696 m = m.group(1)
699 m = m.group(1)
697 if m == 'HEAD':
700 if m == 'HEAD':
698 m = None
701 m = None
699 except:
702 except:
700 m = None # if no group found then merge to HEAD
703 m = None # if no group found then merge to HEAD
701 if m in branches and c.branch != m:
704 if m in branches and c.branch != m:
702 # insert empty changeset for merge
705 # insert empty changeset for merge
703 cc = changeset(author=c.author, branch=m, date=c.date,
706 cc = changeset(author=c.author, branch=m, date=c.date,
704 comment='convert-repo: CVS merge from branch %s' % c.branch,
707 comment='convert-repo: CVS merge from branch %s' % c.branch,
705 entries=[], tags=[], parents=[changesets[branches[m]], c])
708 entries=[], tags=[], parents=[changesets[branches[m]], c])
706 changesets.insert(i + 1, cc)
709 changesets.insert(i + 1, cc)
707 branches[m] = i + 1
710 branches[m] = i + 1
708
711
709 # adjust our loop counters now we have inserted a new entry
712 # adjust our loop counters now we have inserted a new entry
710 n += 1
713 n += 1
711 i += 2
714 i += 2
712 continue
715 continue
713
716
714 branches[c.branch] = i
717 branches[c.branch] = i
715 i += 1
718 i += 1
716
719
717 # Drop synthetic changesets (safe now that we have ensured no other
720 # Drop synthetic changesets (safe now that we have ensured no other
718 # changesets can have them as parents).
721 # changesets can have them as parents).
719 i = 0
722 i = 0
720 while i < len(changesets):
723 while i < len(changesets):
721 if changesets[i].synthetic:
724 if changesets[i].synthetic:
722 del changesets[i]
725 del changesets[i]
723 else:
726 else:
724 i += 1
727 i += 1
725
728
726 # Number changesets
729 # Number changesets
727
730
728 for i, c in enumerate(changesets):
731 for i, c in enumerate(changesets):
729 c.id = i + 1
732 c.id = i + 1
730
733
731 ui.status(_('%d changeset entries\n') % len(changesets))
734 ui.status(_('%d changeset entries\n') % len(changesets))
732
735
736 hook.hook(ui, None, "cvschangesets", True, changesets=changesets)
737
733 return changesets
738 return changesets
734
739
735
740
736 def debugcvsps(ui, *args, **opts):
741 def debugcvsps(ui, *args, **opts):
737 '''Read CVS rlog for current directory or named path in
742 '''Read CVS rlog for current directory or named path in
738 repository, and convert the log to changesets based on matching
743 repository, and convert the log to changesets based on matching
739 commit log entries and dates.
744 commit log entries and dates.
740 '''
745 '''
741 if opts["new_cache"]:
746 if opts["new_cache"]:
742 cache = "write"
747 cache = "write"
743 elif opts["update_cache"]:
748 elif opts["update_cache"]:
744 cache = "update"
749 cache = "update"
745 else:
750 else:
746 cache = None
751 cache = None
747
752
748 revisions = opts["revisions"]
753 revisions = opts["revisions"]
749
754
750 try:
755 try:
751 if args:
756 if args:
752 log = []
757 log = []
753 for d in args:
758 for d in args:
754 log += createlog(ui, d, root=opts["root"], cache=cache)
759 log += createlog(ui, d, root=opts["root"], cache=cache)
755 else:
760 else:
756 log = createlog(ui, root=opts["root"], cache=cache)
761 log = createlog(ui, root=opts["root"], cache=cache)
757 except logerror, e:
762 except logerror, e:
758 ui.write("%r\n"%e)
763 ui.write("%r\n"%e)
759 return
764 return
760
765
761 changesets = createchangeset(ui, log, opts["fuzz"])
766 changesets = createchangeset(ui, log, opts["fuzz"])
762 del log
767 del log
763
768
764 # Print changesets (optionally filtered)
769 # Print changesets (optionally filtered)
765
770
766 off = len(revisions)
771 off = len(revisions)
767 branches = {} # latest version number in each branch
772 branches = {} # latest version number in each branch
768 ancestors = {} # parent branch
773 ancestors = {} # parent branch
769 for cs in changesets:
774 for cs in changesets:
770
775
771 if opts["ancestors"]:
776 if opts["ancestors"]:
772 if cs.branch not in branches and cs.parents and cs.parents[0].id:
777 if cs.branch not in branches and cs.parents and cs.parents[0].id:
773 ancestors[cs.branch] = (changesets[cs.parents[0].id-1].branch,
778 ancestors[cs.branch] = (changesets[cs.parents[0].id-1].branch,
774 cs.parents[0].id)
779 cs.parents[0].id)
775 branches[cs.branch] = cs.id
780 branches[cs.branch] = cs.id
776
781
777 # limit by branches
782 # limit by branches
778 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
783 if opts["branches"] and (cs.branch or 'HEAD') not in opts["branches"]:
779 continue
784 continue
780
785
781 if not off:
786 if not off:
782 # Note: trailing spaces on several lines here are needed to have
787 # Note: trailing spaces on several lines here are needed to have
783 # bug-for-bug compatibility with cvsps.
788 # bug-for-bug compatibility with cvsps.
784 ui.write('---------------------\n')
789 ui.write('---------------------\n')
785 ui.write('PatchSet %d \n' % cs.id)
790 ui.write('PatchSet %d \n' % cs.id)
786 ui.write('Date: %s\n' % util.datestr(cs.date,
791 ui.write('Date: %s\n' % util.datestr(cs.date,
787 '%Y/%m/%d %H:%M:%S %1%2'))
792 '%Y/%m/%d %H:%M:%S %1%2'))
788 ui.write('Author: %s\n' % cs.author)
793 ui.write('Author: %s\n' % cs.author)
789 ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
794 ui.write('Branch: %s\n' % (cs.branch or 'HEAD'))
790 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1],
795 ui.write('Tag%s: %s \n' % (['', 's'][len(cs.tags)>1],
791 ','.join(cs.tags) or '(none)'))
796 ','.join(cs.tags) or '(none)'))
792 branchpoints = getattr(cs, 'branchpoints', None)
797 branchpoints = getattr(cs, 'branchpoints', None)
793 if branchpoints:
798 if branchpoints:
794 ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
799 ui.write('Branchpoints: %s \n' % ', '.join(branchpoints))
795 if opts["parents"] and cs.parents:
800 if opts["parents"] and cs.parents:
796 if len(cs.parents)>1:
801 if len(cs.parents)>1:
797 ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents])))
802 ui.write('Parents: %s\n' % (','.join([str(p.id) for p in cs.parents])))
798 else:
803 else:
799 ui.write('Parent: %d\n' % cs.parents[0].id)
804 ui.write('Parent: %d\n' % cs.parents[0].id)
800
805
801 if opts["ancestors"]:
806 if opts["ancestors"]:
802 b = cs.branch
807 b = cs.branch
803 r = []
808 r = []
804 while b:
809 while b:
805 b, c = ancestors[b]
810 b, c = ancestors[b]
806 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
811 r.append('%s:%d:%d' % (b or "HEAD", c, branches[b]))
807 if r:
812 if r:
808 ui.write('Ancestors: %s\n' % (','.join(r)))
813 ui.write('Ancestors: %s\n' % (','.join(r)))
809
814
810 ui.write('Log:\n')
815 ui.write('Log:\n')
811 ui.write('%s\n\n' % cs.comment)
816 ui.write('%s\n\n' % cs.comment)
812 ui.write('Members: \n')
817 ui.write('Members: \n')
813 for f in cs.entries:
818 for f in cs.entries:
814 fn = f.file
819 fn = f.file
815 if fn.startswith(opts["prefix"]):
820 if fn.startswith(opts["prefix"]):
816 fn = fn[len(opts["prefix"]):]
821 fn = fn[len(opts["prefix"]):]
817 ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
822 ui.write('\t%s:%s->%s%s \n' % (fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
818 '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]))
823 '.'.join([str(x) for x in f.revision]), ['', '(DEAD)'][f.dead]))
819 ui.write('\n')
824 ui.write('\n')
820
825
821 # have we seen the start tag?
826 # have we seen the start tag?
822 if revisions and off:
827 if revisions and off:
823 if revisions[0] == str(cs.id) or \
828 if revisions[0] == str(cs.id) or \
824 revisions[0] in cs.tags:
829 revisions[0] in cs.tags:
825 off = False
830 off = False
826
831
827 # see if we reached the end tag
832 # see if we reached the end tag
828 if len(revisions)>1 and not off:
833 if len(revisions)>1 and not off:
829 if revisions[1] == str(cs.id) or \
834 if revisions[1] == str(cs.id) or \
830 revisions[1] in cs.tags:
835 revisions[1] in cs.tags:
831 break
836 break
@@ -1,120 +1,133
1 #!/bin/sh
1 #!/bin/sh
2
2
3 "$TESTDIR/hghave" cvs || exit 80
3 "$TESTDIR/hghave" cvs || exit 80
4
4
5 cvscall()
5 cvscall()
6 {
6 {
7 cvs -f "$@"
7 cvs -f "$@"
8 }
8 }
9
9
10 hgcat()
10 hgcat()
11 {
11 {
12 hg --cwd src-hg cat -r tip "$1"
12 hg --cwd src-hg cat -r tip "$1"
13 }
13 }
14
14
15 echo "[extensions]" >> $HGRCPATH
15 echo "[extensions]" >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
16 echo "convert = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
17 echo "graphlog = " >> $HGRCPATH
18
18
19 cat > cvshooks.py <<EOF
20 def cvslog(ui,repo,hooktype,log):
21 print "%s hook: %d entries"%(hooktype,len(log))
22
23 def cvschangesets(ui,repo,hooktype,changesets):
24 print "%s hook: %d changesets"%(hooktype,len(changesets))
25 EOF
26 hookpath=$PWD
27
28 echo "[hooks]" >> $HGRCPATH
29 echo "cvslog=python:$hookpath/cvshooks.py:cvslog" >> $HGRCPATH
30 echo "cvschangesets=python:$hookpath/cvshooks.py:cvschangesets" >> $HGRCPATH
31
19 echo % create cvs repository
32 echo % create cvs repository
20 mkdir cvsrepo
33 mkdir cvsrepo
21 cd cvsrepo
34 cd cvsrepo
22 CVSROOT=`pwd`
35 CVSROOT=$PWD
23 export CVSROOT
36 export CVSROOT
24 CVS_OPTIONS=-f
37 CVS_OPTIONS=-f
25 export CVS_OPTIONS
38 export CVS_OPTIONS
26 cd ..
39 cd ..
27
40
28 cvscall -q -d "$CVSROOT" init
41 cvscall -q -d "$CVSROOT" init
29
42
30 echo % create source directory
43 echo % create source directory
31 mkdir src-temp
44 mkdir src-temp
32 cd src-temp
45 cd src-temp
33 echo a > a
46 echo a > a
34 mkdir b
47 mkdir b
35 cd b
48 cd b
36 echo c > c
49 echo c > c
37 cd ..
50 cd ..
38
51
39 echo % import source directory
52 echo % import source directory
40 cvscall -q import -m import src INITIAL start
53 cvscall -q import -m import src INITIAL start
41 cd ..
54 cd ..
42
55
43 echo % checkout source directory
56 echo % checkout source directory
44 cvscall -q checkout src
57 cvscall -q checkout src
45
58
46 echo % commit a new revision changing b/c
59 echo % commit a new revision changing b/c
47 cd src
60 cd src
48 sleep 1
61 sleep 1
49 echo c >> b/c
62 echo c >> b/c
50 cvscall -q commit -mci0 . | grep '<--' |\
63 cvscall -q commit -mci0 . | grep '<--' |\
51 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
64 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
52 cd ..
65 cd ..
53
66
54 echo % convert fresh repo
67 echo % convert fresh repo
55 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
68 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
56 hgcat a
69 hgcat a
57 hgcat b/c
70 hgcat b/c
58
71
59 echo % convert fresh repo with --filemap
72 echo % convert fresh repo with --filemap
60 echo include b/c > filemap
73 echo include b/c > filemap
61 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
74 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
62 hgcat b/c
75 hgcat b/c
63 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
76 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
64
77
65 echo % commit new file revisions
78 echo % commit new file revisions
66 cd src
79 cd src
67 echo a >> a
80 echo a >> a
68 echo c >> b/c
81 echo c >> b/c
69 cvscall -q commit -mci1 . | grep '<--' |\
82 cvscall -q commit -mci1 . | grep '<--' |\
70 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
83 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
71 cd ..
84 cd ..
72
85
73 echo % convert again
86 echo % convert again
74 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
87 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
75 hgcat a
88 hgcat a
76 hgcat b/c
89 hgcat b/c
77
90
78 echo % convert again with --filemap
91 echo % convert again with --filemap
79 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
92 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
80 hgcat b/c
93 hgcat b/c
81 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
94 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
82
95
83 echo % commit branch
96 echo % commit branch
84 cd src
97 cd src
85 cvs -q update -r1.1 b/c
98 cvs -q update -r1.1 b/c
86 cvs -q tag -b branch
99 cvs -q tag -b branch
87 cvs -q update -r branch > /dev/null
100 cvs -q update -r branch > /dev/null
88 echo d >> b/c
101 echo d >> b/c
89 cvs -q commit -mci2 . | grep '<--' |\
102 cvs -q commit -mci2 . | grep '<--' |\
90 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
103 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
91 cd ..
104 cd ..
92
105
93 echo % convert again
106 echo % convert again
94 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
107 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
95 hgcat b/c
108 hgcat b/c
96
109
97 echo % convert again with --filemap
110 echo % convert again with --filemap
98 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
111 hg convert --filemap filemap src src-filemap | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
99 hgcat b/c
112 hgcat b/c
100 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
113 hg -R src-filemap log --template '{rev} {desc} files: {files}\n'
101
114
102 echo % commit a new revision with funny log message
115 echo % commit a new revision with funny log message
103 cd src
116 cd src
104 sleep 1
117 sleep 1
105 echo e >> a
118 echo e >> a
106 cvscall -q commit -m'funny
119 cvscall -q commit -m'funny
107 ----------------------------
120 ----------------------------
108 log message' . | grep '<--' |\
121 log message' . | grep '<--' |\
109 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
122 sed -e 's:.*src/\(.*\),v.*:checking in src/\1,v:g'
110 cd ..
123 cd ..
111
124
112 echo % convert again
125 echo % convert again
113 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
126 hg convert src src-hg | sed -e 's/connecting to.*cvsrepo/connecting to cvsrepo/g'
114
127
115 echo "graphlog = " >> $HGRCPATH
128 echo "graphlog = " >> $HGRCPATH
116 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
129 hg -R src-hg glog --template '{rev} ({branches}) {desc} files: {files}\n'
117
130
118 echo % testing debugcvsps
131 echo % testing debugcvsps
119 cd src
132 cd src
120 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
133 hg debugcvsps | sed -e 's/Author:.*/Author:/' -e 's/Date:.*/Date:/'
@@ -1,254 +1,270
1 % create cvs repository
1 % create cvs repository
2 % create source directory
2 % create source directory
3 % import source directory
3 % import source directory
4 N src/a
4 N src/a
5 N src/b/c
5 N src/b/c
6
6
7 No conflicts created by this import
7 No conflicts created by this import
8
8
9 % checkout source directory
9 % checkout source directory
10 U src/a
10 U src/a
11 U src/b/c
11 U src/b/c
12 % commit a new revision changing b/c
12 % commit a new revision changing b/c
13 checking in src/b/c,v
13 checking in src/b/c,v
14 % convert fresh repo
14 % convert fresh repo
15 initializing destination src-hg repository
15 initializing destination src-hg repository
16 connecting to cvsrepo
16 connecting to cvsrepo
17 scanning source...
17 scanning source...
18 collecting CVS rlog
18 collecting CVS rlog
19 5 log entries
19 5 log entries
20 cvslog hook: 5 entries
20 creating changesets
21 creating changesets
21 3 changeset entries
22 3 changeset entries
23 cvschangesets hook: 3 changesets
22 sorting...
24 sorting...
23 converting...
25 converting...
24 2 Initial revision
26 2 Initial revision
25 1 import
27 1 import
26 0 ci0
28 0 ci0
27 updating tags
29 updating tags
28 a
30 a
29 c
31 c
30 c
32 c
31 % convert fresh repo with --filemap
33 % convert fresh repo with --filemap
32 initializing destination src-filemap repository
34 initializing destination src-filemap repository
33 connecting to cvsrepo
35 connecting to cvsrepo
34 scanning source...
36 scanning source...
35 collecting CVS rlog
37 collecting CVS rlog
36 5 log entries
38 5 log entries
39 cvslog hook: 5 entries
37 creating changesets
40 creating changesets
38 3 changeset entries
41 3 changeset entries
42 cvschangesets hook: 3 changesets
39 sorting...
43 sorting...
40 converting...
44 converting...
41 2 Initial revision
45 2 Initial revision
42 1 import
46 1 import
43 filtering out empty revision
47 filtering out empty revision
44 rolling back last transaction
48 rolling back last transaction
45 0 ci0
49 0 ci0
46 updating tags
50 updating tags
47 c
51 c
48 c
52 c
49 2 update tags files: .hgtags
53 2 update tags files: .hgtags
50 1 ci0 files: b/c
54 1 ci0 files: b/c
51 0 Initial revision files: b/c
55 0 Initial revision files: b/c
52 % commit new file revisions
56 % commit new file revisions
53 checking in src/a,v
57 checking in src/a,v
54 checking in src/b/c,v
58 checking in src/b/c,v
55 % convert again
59 % convert again
56 connecting to cvsrepo
60 connecting to cvsrepo
57 scanning source...
61 scanning source...
58 collecting CVS rlog
62 collecting CVS rlog
59 7 log entries
63 7 log entries
64 cvslog hook: 7 entries
60 creating changesets
65 creating changesets
61 4 changeset entries
66 4 changeset entries
67 cvschangesets hook: 4 changesets
62 sorting...
68 sorting...
63 converting...
69 converting...
64 0 ci1
70 0 ci1
65 a
71 a
66 a
72 a
67 c
73 c
68 c
74 c
69 c
75 c
70 % convert again with --filemap
76 % convert again with --filemap
71 connecting to cvsrepo
77 connecting to cvsrepo
72 scanning source...
78 scanning source...
73 collecting CVS rlog
79 collecting CVS rlog
74 7 log entries
80 7 log entries
81 cvslog hook: 7 entries
75 creating changesets
82 creating changesets
76 4 changeset entries
83 4 changeset entries
84 cvschangesets hook: 4 changesets
77 sorting...
85 sorting...
78 converting...
86 converting...
79 0 ci1
87 0 ci1
80 c
88 c
81 c
89 c
82 c
90 c
83 3 ci1 files: b/c
91 3 ci1 files: b/c
84 2 update tags files: .hgtags
92 2 update tags files: .hgtags
85 1 ci0 files: b/c
93 1 ci0 files: b/c
86 0 Initial revision files: b/c
94 0 Initial revision files: b/c
87 % commit branch
95 % commit branch
88 U b/c
96 U b/c
89 T a
97 T a
90 T b/c
98 T b/c
91 checking in src/b/c,v
99 checking in src/b/c,v
92 % convert again
100 % convert again
93 connecting to cvsrepo
101 connecting to cvsrepo
94 scanning source...
102 scanning source...
95 collecting CVS rlog
103 collecting CVS rlog
96 8 log entries
104 8 log entries
105 cvslog hook: 8 entries
97 creating changesets
106 creating changesets
98 5 changeset entries
107 5 changeset entries
108 cvschangesets hook: 5 changesets
99 sorting...
109 sorting...
100 converting...
110 converting...
101 0 ci2
111 0 ci2
102 c
112 c
103 d
113 d
104 % convert again with --filemap
114 % convert again with --filemap
105 connecting to cvsrepo
115 connecting to cvsrepo
106 scanning source...
116 scanning source...
107 collecting CVS rlog
117 collecting CVS rlog
108 8 log entries
118 8 log entries
119 cvslog hook: 8 entries
109 creating changesets
120 creating changesets
110 5 changeset entries
121 5 changeset entries
122 cvschangesets hook: 5 changesets
111 sorting...
123 sorting...
112 converting...
124 converting...
113 0 ci2
125 0 ci2
114 c
126 c
115 d
127 d
116 4 ci2 files: b/c
128 4 ci2 files: b/c
117 3 ci1 files: b/c
129 3 ci1 files: b/c
118 2 update tags files: .hgtags
130 2 update tags files: .hgtags
119 1 ci0 files: b/c
131 1 ci0 files: b/c
120 0 Initial revision files: b/c
132 0 Initial revision files: b/c
121 % commit a new revision with funny log message
133 % commit a new revision with funny log message
122 checking in src/a,v
134 checking in src/a,v
123 % convert again
135 % convert again
124 connecting to cvsrepo
136 connecting to cvsrepo
125 scanning source...
137 scanning source...
126 collecting CVS rlog
138 collecting CVS rlog
127 9 log entries
139 9 log entries
140 cvslog hook: 9 entries
128 creating changesets
141 creating changesets
129 6 changeset entries
142 6 changeset entries
143 cvschangesets hook: 6 changesets
130 sorting...
144 sorting...
131 converting...
145 converting...
132 0 funny
146 0 funny
133 o 6 (branch) funny
147 o 6 (branch) funny
134 | ----------------------------
148 | ----------------------------
135 | log message files: a
149 | log message files: a
136 o 5 (branch) ci2 files: b/c
150 o 5 (branch) ci2 files: b/c
137
151
138 o 4 () ci1 files: a b/c
152 o 4 () ci1 files: a b/c
139 |
153 |
140 o 3 () update tags files: .hgtags
154 o 3 () update tags files: .hgtags
141 |
155 |
142 o 2 () ci0 files: b/c
156 o 2 () ci0 files: b/c
143 |
157 |
144 | o 1 (INITIAL) import files:
158 | o 1 (INITIAL) import files:
145 |/
159 |/
146 o 0 () Initial revision files: a b/c
160 o 0 () Initial revision files: a b/c
147
161
148 % testing debugcvsps
162 % testing debugcvsps
149 collecting CVS rlog
163 collecting CVS rlog
150 9 log entries
164 9 log entries
165 cvslog hook: 9 entries
151 creating changesets
166 creating changesets
152 8 changeset entries
167 8 changeset entries
168 cvschangesets hook: 8 changesets
153 ---------------------
169 ---------------------
154 PatchSet 1
170 PatchSet 1
155 Date:
171 Date:
156 Author:
172 Author:
157 Branch: HEAD
173 Branch: HEAD
158 Tag: (none)
174 Tag: (none)
159 Branchpoints: INITIAL
175 Branchpoints: INITIAL
160 Log:
176 Log:
161 Initial revision
177 Initial revision
162
178
163 Members:
179 Members:
164 a:INITIAL->1.1
180 a:INITIAL->1.1
165
181
166 ---------------------
182 ---------------------
167 PatchSet 2
183 PatchSet 2
168 Date:
184 Date:
169 Author:
185 Author:
170 Branch: HEAD
186 Branch: HEAD
171 Tag: (none)
187 Tag: (none)
172 Branchpoints: INITIAL, branch
188 Branchpoints: INITIAL, branch
173 Log:
189 Log:
174 Initial revision
190 Initial revision
175
191
176 Members:
192 Members:
177 b/c:INITIAL->1.1
193 b/c:INITIAL->1.1
178
194
179 ---------------------
195 ---------------------
180 PatchSet 3
196 PatchSet 3
181 Date:
197 Date:
182 Author:
198 Author:
183 Branch: INITIAL
199 Branch: INITIAL
184 Tag: start
200 Tag: start
185 Log:
201 Log:
186 import
202 import
187
203
188 Members:
204 Members:
189 a:1.1->1.1.1.1
205 a:1.1->1.1.1.1
190 b/c:1.1->1.1.1.1
206 b/c:1.1->1.1.1.1
191
207
192 ---------------------
208 ---------------------
193 PatchSet 4
209 PatchSet 4
194 Date:
210 Date:
195 Author:
211 Author:
196 Branch: HEAD
212 Branch: HEAD
197 Tag: (none)
213 Tag: (none)
198 Log:
214 Log:
199 ci0
215 ci0
200
216
201 Members:
217 Members:
202 b/c:1.1->1.2
218 b/c:1.1->1.2
203
219
204 ---------------------
220 ---------------------
205 PatchSet 5
221 PatchSet 5
206 Date:
222 Date:
207 Author:
223 Author:
208 Branch: HEAD
224 Branch: HEAD
209 Tag: (none)
225 Tag: (none)
210 Branchpoints: branch
226 Branchpoints: branch
211 Log:
227 Log:
212 ci1
228 ci1
213
229
214 Members:
230 Members:
215 a:1.1->1.2
231 a:1.1->1.2
216
232
217 ---------------------
233 ---------------------
218 PatchSet 6
234 PatchSet 6
219 Date:
235 Date:
220 Author:
236 Author:
221 Branch: HEAD
237 Branch: HEAD
222 Tag: (none)
238 Tag: (none)
223 Log:
239 Log:
224 ci1
240 ci1
225
241
226 Members:
242 Members:
227 b/c:1.2->1.3
243 b/c:1.2->1.3
228
244
229 ---------------------
245 ---------------------
230 PatchSet 7
246 PatchSet 7
231 Date:
247 Date:
232 Author:
248 Author:
233 Branch: branch
249 Branch: branch
234 Tag: (none)
250 Tag: (none)
235 Log:
251 Log:
236 ci2
252 ci2
237
253
238 Members:
254 Members:
239 b/c:1.1->1.1.2.1
255 b/c:1.1->1.1.2.1
240
256
241 ---------------------
257 ---------------------
242 PatchSet 8
258 PatchSet 8
243 Date:
259 Date:
244 Author:
260 Author:
245 Branch: branch
261 Branch: branch
246 Tag: (none)
262 Tag: (none)
247 Log:
263 Log:
248 funny
264 funny
249 ----------------------------
265 ----------------------------
250 log message
266 log message
251
267
252 Members:
268 Members:
253 a:1.2->1.2.2.1
269 a:1.2->1.2.2.1
254
270
@@ -1,266 +1,275
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
6
7 - Mercurial [hg]
7 - Mercurial [hg]
8 - CVS [cvs]
8 - CVS [cvs]
9 - Darcs [darcs]
9 - Darcs [darcs]
10 - git [git]
10 - git [git]
11 - Subversion [svn]
11 - Subversion [svn]
12 - Monotone [mtn]
12 - Monotone [mtn]
13 - GNU Arch [gnuarch]
13 - GNU Arch [gnuarch]
14 - Bazaar [bzr]
14 - Bazaar [bzr]
15 - Perforce [p4]
15 - Perforce [p4]
16
16
17 Accepted destination formats [identifiers]:
17 Accepted destination formats [identifiers]:
18
18
19 - Mercurial [hg]
19 - Mercurial [hg]
20 - Subversion [svn] (history on branches is not preserved)
20 - Subversion [svn] (history on branches is not preserved)
21
21
22 If no revision is given, all revisions will be converted. Otherwise,
22 If no revision is given, all revisions will be converted. Otherwise,
23 convert will only import up to the named revision (given in a format
23 convert will only import up to the named revision (given in a format
24 understood by the source).
24 understood by the source).
25
25
26 If no destination directory name is specified, it defaults to the basename
26 If no destination directory name is specified, it defaults to the basename
27 of the source with '-hg' appended. If the destination repository doesn't
27 of the source with '-hg' appended. If the destination repository doesn't
28 exist, it will be created.
28 exist, it will be created.
29
29
30 By default, all sources except Mercurial will use --branchsort. Mercurial
30 By default, all sources except Mercurial will use --branchsort. Mercurial
31 uses --sourcesort to preserve original revision numbers order. Sort modes
31 uses --sourcesort to preserve original revision numbers order. Sort modes
32 have the following effects:
32 have the following effects:
33
33
34 --branchsort convert from parent to child revision when possible, which
34 --branchsort convert from parent to child revision when possible, which
35 means branches are usually converted one after the other. It
35 means branches are usually converted one after the other. It
36 generates more compact repositories.
36 generates more compact repositories.
37 --datesort sort revisions by date. Converted repositories have good-
37 --datesort sort revisions by date. Converted repositories have good-
38 looking changelogs but are often an order of magnitude
38 looking changelogs but are often an order of magnitude
39 larger than the same ones generated by --branchsort.
39 larger than the same ones generated by --branchsort.
40 --sourcesort try to preserve source revisions order, only supported by
40 --sourcesort try to preserve source revisions order, only supported by
41 Mercurial sources.
41 Mercurial sources.
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 that
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
45 maps each source commit ID to the destination ID for that revision, like
45 maps each source commit ID to the destination ID for that revision, like
46 so:
46 so:
47
47
48 <source ID> <destination ID>
48 <source ID> <destination ID>
49
49
50 If the file doesn't exist, it's automatically created. It's updated on
50 If the file doesn't exist, it's automatically created. It's updated on
51 each commit copied, so convert-repo can be interrupted and can be run
51 each commit copied, so convert-repo can be interrupted and can be run
52 repeatedly to copy new commits.
52 repeatedly to copy new commits.
53
53
54 The [username mapping] file is a simple text file that maps each source
54 The [username mapping] file is a simple text file that maps each source
55 commit author to a destination commit author. It is handy for source SCMs
55 commit author to a destination commit author. It is handy for source SCMs
56 that use unix logins to identify authors (eg: CVS). One line per author
56 that use unix logins to identify authors (eg: CVS). One line per author
57 mapping and the line format is: srcauthor=whatever string you want
57 mapping and the line format is: srcauthor=whatever string you want
58
58
59 The filemap is a file that allows filtering and remapping of files and
59 The filemap is a file that allows filtering and remapping of files and
60 directories. Comment lines start with '#'. Each line can contain one of
60 directories. Comment lines start with '#'. Each line can contain one of
61 the following directives:
61 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 directory, to
69 The 'include' directive causes a file, or all files under a directory, to
70 be included in the destination repository, and the exclusion of all other
70 be included in the destination repository, and the exclusion of all other
71 files and directories not explicitly included. The 'exclude' directive
71 files and directories not explicitly included. The 'exclude' directive
72 causes files or directories to be omitted. The 'rename' directive renames
72 causes files or directories to be omitted. The 'rename' directive renames
73 a file or directory. To rename from a subdirectory into the root of the
73 a file or directory. To rename from a subdirectory into the root of the
74 repository, use '.' as the path to rename to.
74 repository, use '.' as the path to rename to.
75
75
76 The splicemap is a file that allows insertion of synthetic history,
76 The splicemap is a file that allows insertion of synthetic history,
77 letting you specify the parents of a revision. This is useful if you want
77 letting you specify the parents of a revision. This is useful if you want
78 to e.g. give a Subversion merge two parents, or graft two disconnected
78 to e.g. give a Subversion merge two parents, or graft two disconnected
79 series of history together. Each entry contains a key, followed by a
79 series of history together. Each entry contains a key, followed by a
80 space, followed by one or two comma-separated values. The key is the
80 space, followed by one or two comma-separated values. The key is the
81 revision ID in the source revision control system whose parents should be
81 revision ID in the source revision control system whose parents should be
82 modified (same format as a key in .hg/shamap). The values are the revision
82 modified (same format as a key in .hg/shamap). The values are the revision
83 IDs (in either the source or destination revision control system) that
83 IDs (in either the source or destination revision control system) that
84 should be used as the new parents for that node. For example, if you have
84 should be used as the new parents for that node. For example, if you have
85 merged "release-1.0" into "trunk", then you should specify the revision on
85 merged "release-1.0" into "trunk", then you should specify the revision on
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
87 second.
87 second.
88
88
89 The branchmap is a file that allows you to rename a branch when it is
89 The branchmap is a file that allows you to rename a branch when it is
90 being brought in from whatever external repository. When used in
90 being brought in from whatever external repository. When used in
91 conjunction with a splicemap, it allows for a powerful combination to help
91 conjunction with a splicemap, it allows for a powerful combination to help
92 fix even the most badly mismanaged repositories and turn them into nicely
92 fix even the most badly mismanaged repositories and turn them into nicely
93 structured Mercurial repositories. The branchmap contains lines of the
93 structured Mercurial repositories. The branchmap contains lines of the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
96 name of the branch is the destination repository. This can be used to (for
96 name of the branch is the destination repository. This can be used to (for
97 instance) move code in one repository from "default" to a named branch.
97 instance) move code in one repository from "default" to a named branch.
98
98
99 Mercurial Source
99 Mercurial Source
100 ----------------
100 ----------------
101
101
102 --config convert.hg.ignoreerrors=False (boolean)
102 --config convert.hg.ignoreerrors=False (boolean)
103 ignore integrity errors when reading. Use it to fix Mercurial
103 ignore integrity errors when reading. Use it to fix Mercurial
104 repositories with missing revlogs, by converting from and to
104 repositories with missing revlogs, by converting from and to
105 Mercurial.
105 Mercurial.
106 --config convert.hg.saverev=False (boolean)
106 --config convert.hg.saverev=False (boolean)
107 store original revision ID in changeset (forces target IDs to change)
107 store original revision ID in changeset (forces target IDs to change)
108 --config convert.hg.startrev=0 (hg revision identifier)
108 --config convert.hg.startrev=0 (hg revision identifier)
109 convert start revision and its descendants
109 convert start revision and its descendants
110
110
111 CVS Source
111 CVS Source
112 ----------
112 ----------
113
113
114 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
114 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
115 indicate the starting point of what will be converted. Direct access to
115 indicate the starting point of what will be converted. Direct access to
116 the repository files is not needed, unless of course the repository is
116 the repository files is not needed, unless of course the repository is
117 :local:. The conversion uses the top level directory in the sandbox to
117 :local:. The conversion uses the top level directory in the sandbox to
118 find the CVS repository, and then uses CVS rlog commands to find files to
118 find the CVS repository, and then uses CVS rlog commands to find files to
119 convert. This means that unless a filemap is given, all files under the
119 convert. This means that unless a filemap is given, all files under the
120 starting directory will be converted, and that any directory
120 starting directory will be converted, and that any directory
121 reorganization in the CVS sandbox is ignored.
121 reorganization in the CVS sandbox is ignored.
122
122
123 The options shown are the defaults.
123 The options shown are the defaults.
124
124
125 --config convert.cvsps.cache=True (boolean)
125 --config convert.cvsps.cache=True (boolean)
126 Set to False to disable remote log caching, for testing and debugging
126 Set to False to disable remote log caching, for testing and debugging
127 purposes.
127 purposes.
128 --config convert.cvsps.fuzz=60 (integer)
128 --config convert.cvsps.fuzz=60 (integer)
129 Specify the maximum time (in seconds) that is allowed between commits
129 Specify the maximum time (in seconds) that is allowed between commits
130 with identical user and log message in a single changeset. When very
130 with identical user and log message in a single changeset. When very
131 large files were checked in as part of a changeset then the default
131 large files were checked in as part of a changeset then the default
132 may not be long enough.
132 may not be long enough.
133 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
133 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
134 Specify a regular expression to which commit log messages are matched.
134 Specify a regular expression to which commit log messages are matched.
135 If a match occurs, then the conversion process will insert a dummy
135 If a match occurs, then the conversion process will insert a dummy
136 revision merging the branch on which this log message occurs to the
136 revision merging the branch on which this log message occurs to the
137 branch indicated in the regex.
137 branch indicated in the regex.
138 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
138 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
139 Specify a regular expression to which commit log messages are matched.
139 Specify a regular expression to which commit log messages are matched.
140 If a match occurs, then the conversion process will add the most
140 If a match occurs, then the conversion process will add the most
141 recent revision on the branch indicated in the regex as the second
141 recent revision on the branch indicated in the regex as the second
142 parent of the changeset.
142 parent of the changeset.
143 --config hook.cvslog
144 Specify a Python function to be called at the end of gathering the CVS
145 log. The function is passed a list with the log entries, and can
146 modify the entries in-place, or add or delete them.
147 --config hook.cvschangesets
148 Specify a Python function to be called after the changesets are
149 calculated from the the CVS log. The function is passed a list with
150 the changeset entries, and can modify the changesets in-place, or add
151 or delete them.
143
152
144 An additional "debugcvsps" Mercurial command allows the builtin changeset
153 An additional "debugcvsps" Mercurial command allows the builtin changeset
145 merging code to be run without doing a conversion. Its parameters and
154 merging code to be run without doing a conversion. Its parameters and
146 output are similar to that of cvsps 2.1. Please see the command help for
155 output are similar to that of cvsps 2.1. Please see the command help for
147 more details.
156 more details.
148
157
149 Subversion Source
158 Subversion Source
150 -----------------
159 -----------------
151
160
152 Subversion source detects classical trunk/branches/tags layouts. By
161 Subversion source detects classical trunk/branches/tags layouts. By
153 default, the supplied "svn://repo/path/" source URL is converted as a
162 default, the supplied "svn://repo/path/" source URL is converted as a
154 single branch. If "svn://repo/path/trunk" exists it replaces the default
163 single branch. If "svn://repo/path/trunk" exists it replaces the default
155 branch. If "svn://repo/path/branches" exists, its subdirectories are
164 branch. If "svn://repo/path/branches" exists, its subdirectories are
156 listed as possible branches. If "svn://repo/path/tags" exists, it is
165 listed as possible branches. If "svn://repo/path/tags" exists, it is
157 looked for tags referencing converted branches. Default "trunk",
166 looked for tags referencing converted branches. Default "trunk",
158 "branches" and "tags" values can be overridden with following options. Set
167 "branches" and "tags" values can be overridden with following options. Set
159 them to paths relative to the source URL, or leave them blank to disable
168 them to paths relative to the source URL, or leave them blank to disable
160 auto detection.
169 auto detection.
161
170
162 --config convert.svn.branches=branches (directory name)
171 --config convert.svn.branches=branches (directory name)
163 specify the directory containing branches
172 specify the directory containing branches
164 --config convert.svn.tags=tags (directory name)
173 --config convert.svn.tags=tags (directory name)
165 specify the directory containing tags
174 specify the directory containing tags
166 --config convert.svn.trunk=trunk (directory name)
175 --config convert.svn.trunk=trunk (directory name)
167 specify the name of the trunk branch
176 specify the name of the trunk branch
168
177
169 Source history can be retrieved starting at a specific revision, instead
178 Source history can be retrieved starting at a specific revision, instead
170 of being integrally converted. Only single branch conversions are
179 of being integrally converted. Only single branch conversions are
171 supported.
180 supported.
172
181
173 --config convert.svn.startrev=0 (svn revision number)
182 --config convert.svn.startrev=0 (svn revision number)
174 specify start Subversion revision.
183 specify start Subversion revision.
175
184
176 Perforce Source
185 Perforce Source
177 ---------------
186 ---------------
178
187
179 The Perforce (P4) importer can be given a p4 depot path or a client
188 The Perforce (P4) importer can be given a p4 depot path or a client
180 specification as source. It will convert all files in the source to a flat
189 specification as source. It will convert all files in the source to a flat
181 Mercurial repository, ignoring labels, branches and integrations. Note
190 Mercurial repository, ignoring labels, branches and integrations. Note
182 that when a depot path is given you then usually should specify a target
191 that when a depot path is given you then usually should specify a target
183 directory, because otherwise the target may be named ...-hg.
192 directory, because otherwise the target may be named ...-hg.
184
193
185 It is possible to limit the amount of source history to be converted by
194 It is possible to limit the amount of source history to be converted by
186 specifying an initial Perforce revision.
195 specifying an initial Perforce revision.
187
196
188 --config convert.p4.startrev=0 (perforce changelist number)
197 --config convert.p4.startrev=0 (perforce changelist number)
189 specify initial Perforce revision.
198 specify initial Perforce revision.
190
199
191 Mercurial Destination
200 Mercurial Destination
192 ---------------------
201 ---------------------
193
202
194 --config convert.hg.clonebranches=False (boolean)
203 --config convert.hg.clonebranches=False (boolean)
195 dispatch source branches in separate clones.
204 dispatch source branches in separate clones.
196 --config convert.hg.tagsbranch=default (branch name)
205 --config convert.hg.tagsbranch=default (branch name)
197 tag revisions branch name
206 tag revisions branch name
198 --config convert.hg.usebranchnames=True (boolean)
207 --config convert.hg.usebranchnames=True (boolean)
199 preserve branch names
208 preserve branch names
200
209
201 options:
210 options:
202
211
203 -A --authors username mapping filename
212 -A --authors username mapping filename
204 -d --dest-type destination repository type
213 -d --dest-type destination repository type
205 --filemap remap file names using contents of file
214 --filemap remap file names using contents of file
206 -r --rev import up to target revision REV
215 -r --rev import up to target revision REV
207 -s --source-type source repository type
216 -s --source-type source repository type
208 --splicemap splice synthesized history into place
217 --splicemap splice synthesized history into place
209 --branchmap change branch names while converting
218 --branchmap change branch names while converting
210 --branchsort try to sort changesets by branches
219 --branchsort try to sort changesets by branches
211 --datesort try to sort changesets by date
220 --datesort try to sort changesets by date
212 --sourcesort preserve source changesets order
221 --sourcesort preserve source changesets order
213
222
214 use "hg -v help convert" to show global options
223 use "hg -v help convert" to show global options
215 adding a
224 adding a
216 assuming destination a-hg
225 assuming destination a-hg
217 initializing destination a-hg repository
226 initializing destination a-hg repository
218 scanning source...
227 scanning source...
219 sorting...
228 sorting...
220 converting...
229 converting...
221 4 a
230 4 a
222 3 b
231 3 b
223 2 c
232 2 c
224 1 d
233 1 d
225 0 e
234 0 e
226 pulling from ../a
235 pulling from ../a
227 searching for changes
236 searching for changes
228 no changes found
237 no changes found
229 % should fail
238 % should fail
230 initializing destination bogusfile repository
239 initializing destination bogusfile repository
231 abort: cannot create new bundle repository
240 abort: cannot create new bundle repository
232 % should fail
241 % should fail
233 abort: Permission denied: bogusdir
242 abort: Permission denied: bogusdir
234 % should succeed
243 % should succeed
235 initializing destination bogusdir repository
244 initializing destination bogusdir repository
236 scanning source...
245 scanning source...
237 sorting...
246 sorting...
238 converting...
247 converting...
239 4 a
248 4 a
240 3 b
249 3 b
241 2 c
250 2 c
242 1 d
251 1 d
243 0 e
252 0 e
244 % test pre and post conversion actions
253 % test pre and post conversion actions
245 run hg source pre-conversion action
254 run hg source pre-conversion action
246 run hg sink pre-conversion action
255 run hg sink pre-conversion action
247 run hg sink post-conversion action
256 run hg sink post-conversion action
248 run hg source post-conversion action
257 run hg source post-conversion action
249 % converting empty dir should fail nicely
258 % converting empty dir should fail nicely
250 assuming destination emptydir-hg
259 assuming destination emptydir-hg
251 initializing destination emptydir-hg repository
260 initializing destination emptydir-hg repository
252 emptydir does not look like a CVS checkout
261 emptydir does not look like a CVS checkout
253 emptydir does not look like a Git repo
262 emptydir does not look like a Git repo
254 emptydir does not look like a Subversion repo
263 emptydir does not look like a Subversion repo
255 emptydir is not a local Mercurial repo
264 emptydir is not a local Mercurial repo
256 emptydir does not look like a darcs repo
265 emptydir does not look like a darcs repo
257 emptydir does not look like a monotone repo
266 emptydir does not look like a monotone repo
258 emptydir does not look like a GNU Arch repo
267 emptydir does not look like a GNU Arch repo
259 emptydir does not look like a Bazaar repo
268 emptydir does not look like a Bazaar repo
260 cannot find required "p4" tool
269 cannot find required "p4" tool
261 abort: emptydir: missing or unsupported repository
270 abort: emptydir: missing or unsupported repository
262 % convert with imaginary source type
271 % convert with imaginary source type
263 initializing destination a-foo repository
272 initializing destination a-foo repository
264 abort: foo: invalid source repository type
273 abort: foo: invalid source repository type
265 % convert with imaginary sink type
274 % convert with imaginary sink type
266 abort: foo: invalid destination repository type
275 abort: foo: invalid destination repository type
General Comments 0
You need to be logged in to leave comments. Login now