##// END OF EJS Templates
convert: add bzr source
Marek Kubica -
r7053:209ef5f3 default
parent child Browse files
Show More
@@ -0,0 +1,216 b''
1 # bzr support for the convert extension
2 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
3 # it cannot access 'bar' repositories, but they were never used very much
4
5 import os
6 from mercurial import demandimport
7 # these do not work with demandimport, blacklist
8 demandimport.ignore.extend([
9 'bzrlib.transactions',
10 'bzrlib.urlutils',
11 ])
12
13 from mercurial.i18n import _
14 from mercurial import util
15 from common import NoRepo, commit, converter_source
16
17 try:
18 # bazaar imports
19 from bzrlib import branch, revision, errors
20 from bzrlib.revisionspec import RevisionSpec
21 except ImportError:
22 pass
23
24 class bzr_source(converter_source):
25 """Reads Bazaar repositories by using the Bazaar Python libraries"""
26
27 def __init__(self, ui, path, rev=None):
28 super(bzr_source, self).__init__(ui, path, rev=rev)
29
30 try:
31 # access bzrlib stuff
32 branch
33 except NameError:
34 raise NoRepo('Bazaar modules could not be loaded')
35
36 if not os.path.exists(os.path.join(path, '.bzr')):
37 raise NoRepo('%s does not look like a Bazaar repo' % path)
38
39 path = os.path.abspath(path)
40 self.branch = branch.Branch.open(path)
41 self.sourcerepo = self.branch.repository
42 self._parentids = {}
43
44 def before(self):
45 """Before the conversion begins, acquire a read lock
46 for all the operations that might need it. Fortunately
47 read locks don't block other reads or writes to the
48 repository, so this shouldn't have any impact on the usage of
49 the source repository.
50
51 The alternative would be locking on every operation that
52 needs locks (there are currently two: getting the file and
53 getting the parent map) and releasing immediately after,
54 but this approach can take even 40% longer."""
55 self.sourcerepo.lock_read()
56
57 def after(self):
58 self.sourcerepo.unlock()
59
60 def getheads(self):
61 if not self.rev:
62 return [self.branch.last_revision()]
63 try:
64 r = RevisionSpec.from_string(self.rev)
65 info = r.in_history(self.branch)
66 except errors.BzrError:
67 raise util.Abort(_('%s is not a valid revision in current branch')
68 % self.rev)
69 return [info.rev_id]
70
71 def getfile(self, name, rev):
72 revtree = self.sourcerepo.revision_tree(rev)
73 fileid = revtree.path2id(name)
74 if fileid is None:
75 # the file is not available anymore - was deleted
76 raise IOError(_('%s is not available in %s anymore') %
77 (name, rev))
78 sio = revtree.get_file(fileid)
79 return sio.read()
80
81 def getmode(self, name, rev):
82 return self._modecache[(name, rev)]
83
84 def getchanges(self, version):
85 # set up caches: modecache and revtree
86 self._modecache = {}
87 self._revtree = self.sourcerepo.revision_tree(version)
88 # get the parentids from the cache
89 parentids = self._parentids.pop(version)
90 # only diff against first parent id
91 prevtree = self.sourcerepo.revision_tree(parentids[0])
92 return self._gettreechanges(self._revtree, prevtree)
93
94 def getcommit(self, version):
95 rev = self.sourcerepo.get_revision(version)
96 # populate parent id cache
97 if not rev.parent_ids:
98 parents = []
99 self._parentids[version] = (revision.NULL_REVISION,)
100 else:
101 parents = self._filterghosts(rev.parent_ids)
102 self._parentids[version] = parents
103
104 return commit(parents=parents,
105 # bzr uses 1 second timezone precision
106 date='%d %d' % (rev.timestamp, rev.timezone / 3600),
107 author=self.recode(rev.committer),
108 # bzr returns bytestrings or unicode, depending on the content
109 desc=self.recode(rev.message),
110 rev=version)
111
112 def gettags(self):
113 if not self.branch.supports_tags():
114 return {}
115 tagdict = self.branch.tags.get_tag_dict()
116 bytetags = {}
117 for name, rev in tagdict.iteritems():
118 bytetags[self.recode(name)] = rev
119 return bytetags
120
121 def getchangedfiles(self, rev, i):
122 self._modecache = {}
123 curtree = self.sourcerepo.revision_tree(rev)
124 parentids = self._parentids.pop(rev)
125 if i is not None:
126 parentid = parentids[i]
127 else:
128 # no parent id, get the empty revision
129 parentid = revision.NULL_REVISION
130
131 prevtree = self.sourcerepo.revision_tree(parentid)
132 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
133 return changes
134
135 def _gettreechanges(self, current, origin):
136 revid = current._revision_id;
137 changes = []
138 renames = {}
139 for (fileid, paths, changed_content, versioned, parent, name,
140 kind, executable) in current.iter_changes(origin):
141
142 if paths[0] == u'' or paths[1] == u'':
143 # ignore changes to tree root
144 continue
145
146 # bazaar tracks directories, mercurial does not, so
147 # we have to rename the directory contents
148 if kind[1] == 'directory':
149 if None not in paths and paths[0] != paths[1]:
150 # neither an add nor an delete - a move
151 # rename all directory contents manually
152 subdir = origin.inventory.path2id(paths[0])
153 # get all child-entries of the directory
154 for name, entry in origin.inventory.iter_entries(subdir):
155 # hg does not track directory renames
156 if entry.kind == 'directory':
157 continue
158 frompath = self.recode(paths[0] + '/' + name)
159 topath = self.recode(paths[1] + '/' + name)
160 # register the files as changed
161 changes.append((frompath, revid))
162 changes.append((topath, revid))
163 # add to mode cache
164 mode = ((entry.executable and 'x') or (entry.kind == 'symlink' and 's')
165 or '')
166 self._modecache[(topath, revid)] = mode
167 # register the change as move
168 renames[topath] = frompath
169
170 # no futher changes, go to the next change
171 continue
172
173 # we got unicode paths, need to convert them
174 path, topath = [self.recode(part) for part in paths]
175
176 if topath is None:
177 # file deleted
178 changes.append((path, revid))
179 continue
180
181 # renamed
182 if path and path != topath:
183 renames[topath] = path
184
185 # populate the mode cache
186 kind, executable = [e[1] for e in (kind, executable)]
187 mode = ((executable and 'x') or (kind == 'symlink' and 's')
188 or '')
189 self._modecache[(topath, revid)] = mode
190 changes.append((topath, revid))
191
192 return changes, renames
193
194 def _filterghosts(self, ids):
195 """Filters out ghost revisions which hg does not support, see
196 <http://bazaar-vcs.org/GhostRevision>
197 """
198 parentmap = self.sourcerepo.get_parent_map(ids)
199 parents = tuple(parent for parent in ids if parent in parentmap)
200 return parents
201
202 def recode(self, s, encoding=None):
203 """This version of recode tries to encode unicode to bytecode,
204 and preferably using the UTF-8 codec.
205 Other types than Unicode are silently returned, this is by
206 intention, e.g. the None-type is not going to be encoded but instead
207 just passed through
208 """
209 if not encoding:
210 encoding = self.encoding or 'utf-8'
211
212 if isinstance(s, unicode):
213 return s.encode(encoding)
214 else:
215 # leave it alone
216 return s
@@ -0,0 +1,18 b''
1 # this file holds the definitions that are used in various bzr tests
2
3 "$TESTDIR/hghave" bzr || exit 80
4
5 echo '[extensions]' >> $HGRCPATH
6 echo 'convert = ' >> $HGRCPATH
7 echo 'hgext.graphlog = ' >> $HGRCPATH
8
9 glog()
10 {
11 hg glog --template '#rev# "#desc|firstline#" files: #files#\n' "$@"
12 }
13
14 manifest()
15 {
16 echo "% manifest of $2"
17 hg -R $1 manifest -v -r $2
18 }
@@ -0,0 +1,81 b''
1 #!/bin/sh
2
3 source "$TESTDIR/bzr-definitions"
4
5 echo % create and rename on the same file in the same step
6 mkdir test-createandrename
7 cd test-createandrename
8 bzr init -q source
9 cd source
10 echo a > a
11 bzr add -q a
12 bzr commit -q -m 'Initial add: a'
13 bzr mv a b
14 echo a2 >> a
15 bzr add -q a
16 bzr commit -q -m 'rename a into b, create a'
17 cd ..
18 hg convert source source-hg
19 glog -R source-hg
20 echo "% test --rev option"
21 hg convert -r 1 source source-1-hg
22 glog -R source-1-hg
23 cd ..
24
25 echo % merge
26 mkdir test-merge
27 cd test-merge
28
29 cat > helper.py <<EOF
30 import sys
31 from bzrlib import workingtree
32 wt = workingtree.WorkingTree.open('.')
33
34 message, stamp = sys.argv[1:]
35 wt.commit(message, timestamp=int(stamp))
36 EOF
37
38 bzr init -q source
39 cd source
40 echo content > a
41 echo content2 > b
42 bzr add -q a b
43 bzr commit -q -m 'Initial add'
44 cd ..
45 bzr branch -q source source-improve
46 cd source
47 echo more >> a
48 python ../helper.py 'Editing a' 100
49 cd ../source-improve
50 echo content3 >> b
51 python ../helper.py 'Editing b' 200
52 cd ../source
53 bzr merge -q ../source-improve
54 bzr commit -q -m 'Merged improve branch'
55 cd ..
56 hg convert --datesort source source-hg
57 glog -R source-hg
58 cd ..
59
60 echo % symlinks and executable files
61 mkdir test-symlinks
62 cd test-symlinks
63 bzr init -q source
64 cd source
65 touch program
66 chmod +x program
67 ln -s program altname
68 bzr add -q altname program
69 bzr commit -q -m 'Initial setup'
70 touch newprog
71 chmod +x newprog
72 rm altname
73 ln -s newprog altname
74 chmod -x program
75 bzr add -q newprog
76 bzr commit -q -m 'Symlink changed, x bits changed'
77 cd ..
78 hg convert source source-hg
79 manifest source-hg 0
80 manifest source-hg tip
81 cd ..
@@ -0,0 +1,93 b''
1 #!/bin/sh
2
3 source "$TESTDIR/bzr-definitions"
4
5 echo % empty directory
6 mkdir test-empty
7 cd test-empty
8 bzr init -q source
9 cd source
10 echo content > a
11 bzr add -q a
12 bzr commit -q -m 'Initial add'
13 mkdir empty
14 bzr add -q empty
15 bzr commit -q -m 'Empty directory added'
16 echo content > empty/something
17 bzr add -q empty/something
18 bzr commit -q -m 'Added file into directory'
19 cd ..
20 hg convert source source-hg
21 manifest source-hg 1
22 manifest source-hg tip
23 cd ..
24
25 echo % directory renames
26 mkdir test-dir-rename
27 cd test-dir-rename
28 bzr init -q source
29 cd source
30 mkdir tpyo
31 echo content > tpyo/something
32 bzr add -q tpyo
33 bzr commit -q -m 'Added directory'
34 bzr mv tpyo typo
35 bzr commit -q -m 'Oops, typo'
36 cd ..
37 hg convert source source-hg
38 manifest source-hg 0
39 manifest source-hg tip
40 cd ..
41
42 echo % nested directory renames
43 mkdir test-nested-dir-rename
44 cd test-nested-dir-rename
45 bzr init -q source
46 cd source
47 mkdir -p firstlevel/secondlevel/thirdlevel
48 echo content > firstlevel/secondlevel/file
49 echo this_needs_to_be_there_too > firstlevel/secondlevel/thirdlevel/stuff
50 bzr add -q firstlevel
51 bzr commit -q -m 'Added nested directories'
52 bzr mv firstlevel/secondlevel secondlevel
53 bzr commit -q -m 'Moved secondlevel one level up'
54 cd ..
55 hg convert source source-hg
56 manifest source-hg tip
57 cd ..
58
59 echo % directory remove
60 mkdir test-dir-remove
61 cd test-dir-remove
62 bzr init -q source
63 cd source
64 mkdir src
65 echo content > src/sourcecode
66 bzr add -q src
67 bzr commit -q -m 'Added directory'
68 bzr rm -q src
69 bzr commit -q -m 'Removed directory'
70 cd ..
71 hg convert source source-hg
72 manifest source-hg 0
73 manifest source-hg tip
74 cd ..
75
76 echo % directory replace
77 mkdir test-dir-replace
78 cd test-dir-replace
79 bzr init -q source
80 cd source
81 mkdir first second
82 echo content > first/file
83 echo morecontent > first/dummy
84 echo othercontent > second/something
85 bzr add -q first second
86 bzr commit -q -m 'Initial layout'
87 bzr mv first/file second/file
88 bzr mv first third
89 bzr commit -q -m 'Some conflicting moves'
90 cd ..
91 hg convert source source-hg
92 manifest source-hg tip
93 cd ..
@@ -0,0 +1,59 b''
1 % empty directory
2 initializing destination source-hg repository
3 scanning source...
4 sorting...
5 converting...
6 2 Initial add
7 1 Empty directory added
8 0 Added file into directory
9 % manifest of 1
10 644 a
11 % manifest of tip
12 644 a
13 644 empty/something
14 % directory renames
15 tpyo => typo
16 initializing destination source-hg repository
17 scanning source...
18 sorting...
19 converting...
20 1 Added directory
21 0 Oops, typo
22 % manifest of 0
23 644 tpyo/something
24 % manifest of tip
25 644 typo/something
26 % nested directory renames
27 firstlevel/secondlevel => secondlevel
28 initializing destination source-hg repository
29 scanning source...
30 sorting...
31 converting...
32 1 Added nested directories
33 0 Moved secondlevel one level up
34 % manifest of tip
35 644 secondlevel/file
36 644 secondlevel/thirdlevel/stuff
37 % directory remove
38 initializing destination source-hg repository
39 scanning source...
40 sorting...
41 converting...
42 1 Added directory
43 0 Removed directory
44 % manifest of 0
45 644 src/sourcecode
46 % manifest of tip
47 % directory replace
48 first/file => second/file
49 first => third
50 initializing destination source-hg repository
51 scanning source...
52 sorting...
53 converting...
54 1 Initial layout
55 0 Some conflicting moves
56 % manifest of tip
57 644 second/file
58 644 second/something
59 644 third/dummy
@@ -0,0 +1,27 b''
1 #!/bin/sh
2
3 source "$TESTDIR/bzr-definitions"
4
5 cat > ghostcreator.py <<EOF
6 import sys
7 from bzrlib import workingtree
8 wt = workingtree.WorkingTree.open('.')
9
10 message, ghostrev = sys.argv[1:]
11 wt.set_parent_ids(wt.get_parent_ids() + [ghostrev])
12 wt.commit(message)
13 EOF
14
15 echo % ghost revisions
16 mkdir test-ghost-revisions
17 cd test-ghost-revisions
18 bzr init -q source
19 cd source
20 echo content > somefile
21 bzr add -q somefile
22 bzr commit -q -m 'Initial layout setup'
23 echo morecontent >> somefile
24 python ../../ghostcreator.py 'Commit with ghost revision' ghostrev
25 cd ..
26 hg convert source source-hg
27 glog -R source-hg
@@ -0,0 +1,11 b''
1 % ghost revisions
2 initializing destination source-hg repository
3 scanning source...
4 sorting...
5 converting...
6 1 Initial layout setup
7 0 Commit with ghost revision
8 o 1 "Commit with ghost revision" files: somefile
9 |
10 o 0 "Initial layout setup" files: somefile
11
@@ -0,0 +1,37 b''
1 #!/bin/sh
2
3 source "$TESTDIR/bzr-definitions"
4
5 echo % test multiple merges at once
6 mkdir test-multimerge
7 cd test-multimerge
8 bzr init -q source
9 cd source
10 echo content > file
11 bzr add -q file
12 bzr commit -q -m 'Initial add'
13 cd ..
14 bzr branch -q source source-branch1
15 cd source-branch1
16 echo morecontent >> file
17 echo evenmorecontent > file-branch1
18 bzr add -q file-branch1
19 bzr commit -q -m 'Added branch1 file'
20 cd ../source
21 echo content > file-parent
22 bzr add -q file-parent
23 bzr commit -q -m 'Added parent file'
24 cd ..
25 bzr branch -q source source-branch2
26 cd source-branch2
27 echo somecontent > file-branch2
28 bzr add -q file-branch2
29 bzr commit -q -m 'Added brach2 file'
30 cd ../source
31 bzr merge -q ../source-branch1
32 bzr merge -q --force ../source-branch2
33 bzr commit -q -m 'Merged branches'
34 cd ..
35 hg convert --datesort source source-hg
36 glog -R source-hg
37 manifest source-hg tip
@@ -0,0 +1,27 b''
1 % test multiple merges at once
2 initializing destination source-hg repository
3 scanning source...
4 sorting...
5 converting...
6 4 Initial add
7 3 Added branch1 file
8 2 Added parent file
9 1 Added brach2 file
10 0 Merged branches
11 o 5 "(octopus merge fixup)" files:
12 |\
13 | o 4 "Merged branches" files: file-branch2
14 | |\
15 o---+ 3 "Added brach2 file" files: file-branch2
16 / /
17 | o 2 "Added parent file" files: file-parent
18 | |
19 o | 1 "Added branch1 file" files: file file-branch1
20 |/
21 o 0 "Initial add" files: file
22
23 % manifest of tip
24 644 file
25 644 file-branch1
26 644 file-branch2
27 644 file-parent
@@ -0,0 +1,26 b''
1 #!/bin/sh
2
3 source "$TESTDIR/bzr-definitions"
4
5 cat > treeset.py <<EOF
6 import sys
7 from bzrlib import workingtree
8 wt = workingtree.WorkingTree.open('.')
9
10 message, rootid = sys.argv[1:]
11 wt.set_root_id('tree_root-%s' % rootid)
12 wt.commit(message)
13 EOF
14
15 echo % change the id of the tree root
16 mkdir test-change-treeroot-id
17 cd test-change-treeroot-id
18 bzr init -q source
19 cd source
20 echo content > file
21 bzr add -q file
22 bzr commit -q -m 'Initial add'
23 python ../../treeset.py 'Changed root' new
24 cd ..
25 hg convert source source-hg
26 manifest source-hg tip
@@ -0,0 +1,9 b''
1 % change the id of the tree root
2 initializing destination source-hg repository
3 scanning source...
4 sorting...
5 converting...
6 1 Initial add
7 0 Changed root
8 % manifest of tip
9 644 file
@@ -0,0 +1,51 b''
1 % create and rename on the same file in the same step
2 a => b
3 initializing destination source-hg repository
4 scanning source...
5 sorting...
6 converting...
7 1 Initial add: a
8 0 rename a into b, create a
9 o 1 "rename a into b, create a" files: a b
10 |
11 o 0 "Initial add: a" files: a
12
13 % test --rev option
14 initializing destination source-1-hg repository
15 scanning source...
16 sorting...
17 converting...
18 0 Initial add: a
19 o 0 "Initial add: a" files: a
20
21 % merge
22 initializing destination source-hg repository
23 scanning source...
24 sorting...
25 converting...
26 3 Initial add
27 2 Editing a
28 1 Editing b
29 0 Merged improve branch
30 o 3 "Merged improve branch" files:
31 |\
32 | o 2 "Editing b" files: b
33 | |
34 o | 1 "Editing a" files: a
35 |/
36 o 0 "Initial add" files: a b
37
38 % symlinks and executable files
39 initializing destination source-hg repository
40 scanning source...
41 sorting...
42 converting...
43 1 Initial setup
44 0 Symlink changed, x bits changed
45 % manifest of 0
46 644 altname
47 755 * program
48 % manifest of tip
49 644 altname
50 755 * newprog
51 644 program
@@ -1,197 +1,198 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 '''converting foreign VCS repositories to Mercurial'''
7 '''converting foreign VCS repositories to Mercurial'''
8
8
9 import convcmd
9 import convcmd
10 from mercurial import commands
10 from mercurial import commands
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 # Commands definition was moved elsewhere to ease demandload job.
13 # Commands definition was moved elsewhere to ease demandload job.
14
14
15 def convert(ui, src, dest=None, revmapfile=None, **opts):
15 def convert(ui, src, dest=None, revmapfile=None, **opts):
16 """Convert a foreign SCM repository to a Mercurial one.
16 """Convert a foreign SCM repository to a Mercurial one.
17
17
18 Accepted source formats [identifiers]:
18 Accepted source formats [identifiers]:
19 - Mercurial [hg]
19 - Mercurial [hg]
20 - CVS [cvs]
20 - CVS [cvs]
21 - Darcs [darcs]
21 - Darcs [darcs]
22 - git [git]
22 - git [git]
23 - Subversion [svn]
23 - Subversion [svn]
24 - Monotone [mtn]
24 - Monotone [mtn]
25 - GNU Arch [gnuarch]
25 - GNU Arch [gnuarch]
26 - Bazaar [bzr]
26
27
27 Accepted destination formats [identifiers]:
28 Accepted destination formats [identifiers]:
28 - Mercurial [hg]
29 - Mercurial [hg]
29 - Subversion [svn] (history on branches is not preserved)
30 - Subversion [svn] (history on branches is not preserved)
30
31
31 If no revision is given, all revisions will be converted. Otherwise,
32 If no revision is given, all revisions will be converted. Otherwise,
32 convert will only import up to the named revision (given in a format
33 convert will only import up to the named revision (given in a format
33 understood by the source).
34 understood by the source).
34
35
35 If no destination directory name is specified, it defaults to the
36 If no destination directory name is specified, it defaults to the
36 basename of the source with '-hg' appended. If the destination
37 basename of the source with '-hg' appended. If the destination
37 repository doesn't exist, it will be created.
38 repository doesn't exist, it will be created.
38
39
39 If <REVMAP> isn't given, it will be put in a default location
40 If <REVMAP> isn't given, it will be put in a default location
40 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
41 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
41 file that maps each source commit ID to the destination ID for
42 file that maps each source commit ID to the destination ID for
42 that revision, like so:
43 that revision, like so:
43 <source ID> <destination ID>
44 <source ID> <destination ID>
44
45
45 If the file doesn't exist, it's automatically created. It's updated
46 If the file doesn't exist, it's automatically created. It's updated
46 on each commit copied, so convert-repo can be interrupted and can
47 on each commit copied, so convert-repo can be interrupted and can
47 be run repeatedly to copy new commits.
48 be run repeatedly to copy new commits.
48
49
49 The [username mapping] file is a simple text file that maps each source
50 The [username mapping] file is a simple text file that maps each source
50 commit author to a destination commit author. It is handy for source SCMs
51 commit author to a destination commit author. It is handy for source SCMs
51 that use unix logins to identify authors (eg: CVS). One line per author
52 that use unix logins to identify authors (eg: CVS). One line per author
52 mapping and the line format is:
53 mapping and the line format is:
53 srcauthor=whatever string you want
54 srcauthor=whatever string you want
54
55
55 The filemap is a file that allows filtering and remapping of files
56 The filemap is a file that allows filtering and remapping of files
56 and directories. Comment lines start with '#'. Each line can
57 and directories. Comment lines start with '#'. Each line can
57 contain one of the following directives:
58 contain one of the following directives:
58
59
59 include path/to/file
60 include path/to/file
60
61
61 exclude path/to/file
62 exclude path/to/file
62
63
63 rename from/file to/file
64 rename from/file to/file
64
65
65 The 'include' directive causes a file, or all files under a
66 The 'include' directive causes a file, or all files under a
66 directory, to be included in the destination repository, and the
67 directory, to be included in the destination repository, and the
67 exclusion of all other files and dirs not explicitely included.
68 exclusion of all other files and dirs not explicitely included.
68 The 'exclude' directive causes files or directories to be omitted.
69 The 'exclude' directive causes files or directories to be omitted.
69 The 'rename' directive renames a file or directory. To rename from a
70 The 'rename' directive renames a file or directory. To rename from a
70 subdirectory into the root of the repository, use '.' as the path to
71 subdirectory into the root of the repository, use '.' as the path to
71 rename to.
72 rename to.
72
73
73 The splicemap is a file that allows insertion of synthetic
74 The splicemap is a file that allows insertion of synthetic
74 history, letting you specify the parents of a revision. This is
75 history, letting you specify the parents of a revision. This is
75 useful if you want to e.g. give a Subversion merge two parents, or
76 useful if you want to e.g. give a Subversion merge two parents, or
76 graft two disconnected series of history together. Each entry
77 graft two disconnected series of history together. Each entry
77 contains a key, followed by a space, followed by one or two
78 contains a key, followed by a space, followed by one or two
78 values, separated by spaces. The key is the revision ID in the
79 values, separated by spaces. The key is the revision ID in the
79 source revision control system whose parents should be modified
80 source revision control system whose parents should be modified
80 (same format as a key in .hg/shamap). The values are the revision
81 (same format as a key in .hg/shamap). The values are the revision
81 IDs (in either the source or destination revision control system)
82 IDs (in either the source or destination revision control system)
82 that should be used as the new parents for that node.
83 that should be used as the new parents for that node.
83
84
84 Mercurial Source
85 Mercurial Source
85 -----------------
86 -----------------
86
87
87 --config convert.hg.saverev=True (boolean)
88 --config convert.hg.saverev=True (boolean)
88 allow target to preserve source revision ID
89 allow target to preserve source revision ID
89 --config convert.hg.startrev=0 (hg revision identifier)
90 --config convert.hg.startrev=0 (hg revision identifier)
90 convert start revision and its descendants
91 convert start revision and its descendants
91
92
92 CVS Source
93 CVS Source
93 ----------
94 ----------
94
95
95 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
96 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
96 to indicate the starting point of what will be converted. Direct
97 to indicate the starting point of what will be converted. Direct
97 access to the repository files is not needed, unless of course
98 access to the repository files is not needed, unless of course
98 the repository is :local:. The conversion uses the top level
99 the repository is :local:. The conversion uses the top level
99 directory in the sandbox to find the CVS repository, and then uses
100 directory in the sandbox to find the CVS repository, and then uses
100 CVS rlog commands to find files to convert. This means that unless
101 CVS rlog commands to find files to convert. This means that unless
101 a filemap is given, all files under the starting directory will be
102 a filemap is given, all files under the starting directory will be
102 converted, and that any directory reorganisation in the CVS
103 converted, and that any directory reorganisation in the CVS
103 sandbox is ignored.
104 sandbox is ignored.
104
105
105 Because CVS does not have changesets, it is necessary to collect
106 Because CVS does not have changesets, it is necessary to collect
106 individual commits to CVS and merge them into changesets. CVS source
107 individual commits to CVS and merge them into changesets. CVS source
107 can use the external 'cvsps' program (this is a legacy option and may
108 can use the external 'cvsps' program (this is a legacy option and may
108 be removed in future) or use its internal changeset merging code.
109 be removed in future) or use its internal changeset merging code.
109 External cvsps is default, and options may be passed to it by setting
110 External cvsps is default, and options may be passed to it by setting
110 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
111 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
111 The options shown are the defaults.
112 The options shown are the defaults.
112
113
113 Internal cvsps is selected by setting
114 Internal cvsps is selected by setting
114 --config convert.cvsps=builtin
115 --config convert.cvsps=builtin
115 and has a few more configurable options:
116 and has a few more configurable options:
116 --config convert.cvsps.fuzz=60 (integer)
117 --config convert.cvsps.fuzz=60 (integer)
117 Specify the maximum time (in seconds) that is allowed between
118 Specify the maximum time (in seconds) that is allowed between
118 commits with identical user and log message in a single
119 commits with identical user and log message in a single
119 changeset. When very large files were checked in as part
120 changeset. When very large files were checked in as part
120 of a changeset then the default may not be long enough.
121 of a changeset then the default may not be long enough.
121 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
122 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
122 Specify a regular expression to which commit log messages are
123 Specify a regular expression to which commit log messages are
123 matched. If a match occurs, then the conversion process will
124 matched. If a match occurs, then the conversion process will
124 insert a dummy revision merging the branch on which this log
125 insert a dummy revision merging the branch on which this log
125 message occurs to the branch indicated in the regex.
126 message occurs to the branch indicated in the regex.
126 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
127 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
127 Specify a regular expression to which commit log messages are
128 Specify a regular expression to which commit log messages are
128 matched. If a match occurs, then the conversion process will
129 matched. If a match occurs, then the conversion process will
129 add the most recent revision on the branch indicated in the
130 add the most recent revision on the branch indicated in the
130 regex as the second parent of the changeset.
131 regex as the second parent of the changeset.
131
132
132 The hgext/convert/cvsps wrapper script allows the builtin changeset
133 The hgext/convert/cvsps wrapper script allows the builtin changeset
133 merging code to be run without doing a conversion. Its parameters and
134 merging code to be run without doing a conversion. Its parameters and
134 output are similar to that of cvsps 2.1.
135 output are similar to that of cvsps 2.1.
135
136
136 Subversion Source
137 Subversion Source
137 -----------------
138 -----------------
138
139
139 Subversion source detects classical trunk/branches/tags layouts.
140 Subversion source detects classical trunk/branches/tags layouts.
140 By default, the supplied "svn://repo/path/" source URL is
141 By default, the supplied "svn://repo/path/" source URL is
141 converted as a single branch. If "svn://repo/path/trunk" exists
142 converted as a single branch. If "svn://repo/path/trunk" exists
142 it replaces the default branch. If "svn://repo/path/branches"
143 it replaces the default branch. If "svn://repo/path/branches"
143 exists, its subdirectories are listed as possible branches. If
144 exists, its subdirectories are listed as possible branches. If
144 "svn://repo/path/tags" exists, it is looked for tags referencing
145 "svn://repo/path/tags" exists, it is looked for tags referencing
145 converted branches. Default "trunk", "branches" and "tags" values
146 converted branches. Default "trunk", "branches" and "tags" values
146 can be overriden with following options. Set them to paths
147 can be overriden with following options. Set them to paths
147 relative to the source URL, or leave them blank to disable
148 relative to the source URL, or leave them blank to disable
148 autodetection.
149 autodetection.
149
150
150 --config convert.svn.branches=branches (directory name)
151 --config convert.svn.branches=branches (directory name)
151 specify the directory containing branches
152 specify the directory containing branches
152 --config convert.svn.tags=tags (directory name)
153 --config convert.svn.tags=tags (directory name)
153 specify the directory containing tags
154 specify the directory containing tags
154 --config convert.svn.trunk=trunk (directory name)
155 --config convert.svn.trunk=trunk (directory name)
155 specify the name of the trunk branch
156 specify the name of the trunk branch
156
157
157 Source history can be retrieved starting at a specific revision,
158 Source history can be retrieved starting at a specific revision,
158 instead of being integrally converted. Only single branch
159 instead of being integrally converted. Only single branch
159 conversions are supported.
160 conversions are supported.
160
161
161 --config convert.svn.startrev=0 (svn revision number)
162 --config convert.svn.startrev=0 (svn revision number)
162 specify start Subversion revision.
163 specify start Subversion revision.
163
164
164 Mercurial Destination
165 Mercurial Destination
165 ---------------------
166 ---------------------
166
167
167 --config convert.hg.clonebranches=False (boolean)
168 --config convert.hg.clonebranches=False (boolean)
168 dispatch source branches in separate clones.
169 dispatch source branches in separate clones.
169 --config convert.hg.tagsbranch=default (branch name)
170 --config convert.hg.tagsbranch=default (branch name)
170 tag revisions branch name
171 tag revisions branch name
171 --config convert.hg.usebranchnames=True (boolean)
172 --config convert.hg.usebranchnames=True (boolean)
172 preserve branch names
173 preserve branch names
173
174
174 """
175 """
175 return convcmd.convert(ui, src, dest, revmapfile, **opts)
176 return convcmd.convert(ui, src, dest, revmapfile, **opts)
176
177
177 def debugsvnlog(ui, **opts):
178 def debugsvnlog(ui, **opts):
178 return convcmd.debugsvnlog(ui, **opts)
179 return convcmd.debugsvnlog(ui, **opts)
179
180
180 commands.norepo += " convert debugsvnlog"
181 commands.norepo += " convert debugsvnlog"
181
182
182 cmdtable = {
183 cmdtable = {
183 "convert":
184 "convert":
184 (convert,
185 (convert,
185 [('A', 'authors', '', _('username mapping filename')),
186 [('A', 'authors', '', _('username mapping filename')),
186 ('d', 'dest-type', '', _('destination repository type')),
187 ('d', 'dest-type', '', _('destination repository type')),
187 ('', 'filemap', '', _('remap file names using contents of file')),
188 ('', 'filemap', '', _('remap file names using contents of file')),
188 ('r', 'rev', '', _('import up to target revision REV')),
189 ('r', 'rev', '', _('import up to target revision REV')),
189 ('s', 'source-type', '', _('source repository type')),
190 ('s', 'source-type', '', _('source repository type')),
190 ('', 'splicemap', '', _('splice synthesized history into place')),
191 ('', 'splicemap', '', _('splice synthesized history into place')),
191 ('', 'datesort', None, _('try to sort changesets by date'))],
192 ('', 'datesort', None, _('try to sort changesets by date'))],
192 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
193 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
193 "debugsvnlog":
194 "debugsvnlog":
194 (debugsvnlog,
195 (debugsvnlog,
195 [],
196 [],
196 'hg debugsvnlog'),
197 'hg debugsvnlog'),
197 }
198 }
@@ -1,337 +1,339 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 from bzr import bzr_source
16 import filemap
17 import filemap
17
18
18 import os, shutil
19 import os, shutil
19 from mercurial import hg, util
20 from mercurial import hg, util
20 from mercurial.i18n import _
21 from mercurial.i18n import _
21
22
22 orig_encoding = 'ascii'
23 orig_encoding = 'ascii'
23
24
24 def recode(s):
25 def recode(s):
25 if isinstance(s, unicode):
26 if isinstance(s, unicode):
26 return s.encode(orig_encoding, 'replace')
27 return s.encode(orig_encoding, 'replace')
27 else:
28 else:
28 return s.decode('utf-8').encode(orig_encoding, 'replace')
29 return s.decode('utf-8').encode(orig_encoding, 'replace')
29
30
30 source_converters = [
31 source_converters = [
31 ('cvs', convert_cvs),
32 ('cvs', convert_cvs),
32 ('git', convert_git),
33 ('git', convert_git),
33 ('svn', svn_source),
34 ('svn', svn_source),
34 ('hg', mercurial_source),
35 ('hg', mercurial_source),
35 ('darcs', darcs_source),
36 ('darcs', darcs_source),
36 ('mtn', monotone_source),
37 ('mtn', monotone_source),
37 ('gnuarch', gnuarch_source),
38 ('gnuarch', gnuarch_source),
39 ('bzr', bzr_source),
38 ]
40 ]
39
41
40 sink_converters = [
42 sink_converters = [
41 ('hg', mercurial_sink),
43 ('hg', mercurial_sink),
42 ('svn', svn_sink),
44 ('svn', svn_sink),
43 ]
45 ]
44
46
45 def convertsource(ui, path, type, rev):
47 def convertsource(ui, path, type, rev):
46 exceptions = []
48 exceptions = []
47 for name, source in source_converters:
49 for name, source in source_converters:
48 try:
50 try:
49 if not type or name == type:
51 if not type or name == type:
50 return source(ui, path, rev)
52 return source(ui, path, rev)
51 except (NoRepo, MissingTool), inst:
53 except (NoRepo, MissingTool), inst:
52 exceptions.append(inst)
54 exceptions.append(inst)
53 if not ui.quiet:
55 if not ui.quiet:
54 for inst in exceptions:
56 for inst in exceptions:
55 ui.write("%s\n" % inst)
57 ui.write("%s\n" % inst)
56 raise util.Abort(_('%s: missing or unsupported repository') % path)
58 raise util.Abort(_('%s: missing or unsupported repository') % path)
57
59
58 def convertsink(ui, path, type):
60 def convertsink(ui, path, type):
59 for name, sink in sink_converters:
61 for name, sink in sink_converters:
60 try:
62 try:
61 if not type or name == type:
63 if not type or name == type:
62 return sink(ui, path)
64 return sink(ui, path)
63 except NoRepo, inst:
65 except NoRepo, inst:
64 ui.note(_("convert: %s\n") % inst)
66 ui.note(_("convert: %s\n") % inst)
65 raise util.Abort(_('%s: unknown repository type') % path)
67 raise util.Abort(_('%s: unknown repository type') % path)
66
68
67 class converter(object):
69 class converter(object):
68 def __init__(self, ui, source, dest, revmapfile, opts):
70 def __init__(self, ui, source, dest, revmapfile, opts):
69
71
70 self.source = source
72 self.source = source
71 self.dest = dest
73 self.dest = dest
72 self.ui = ui
74 self.ui = ui
73 self.opts = opts
75 self.opts = opts
74 self.commitcache = {}
76 self.commitcache = {}
75 self.authors = {}
77 self.authors = {}
76 self.authorfile = None
78 self.authorfile = None
77
79
78 self.map = mapfile(ui, revmapfile)
80 self.map = mapfile(ui, revmapfile)
79
81
80 # Read first the dst author map if any
82 # Read first the dst author map if any
81 authorfile = self.dest.authorfile()
83 authorfile = self.dest.authorfile()
82 if authorfile and os.path.exists(authorfile):
84 if authorfile and os.path.exists(authorfile):
83 self.readauthormap(authorfile)
85 self.readauthormap(authorfile)
84 # Extend/Override with new author map if necessary
86 # Extend/Override with new author map if necessary
85 if opts.get('authors'):
87 if opts.get('authors'):
86 self.readauthormap(opts.get('authors'))
88 self.readauthormap(opts.get('authors'))
87 self.authorfile = self.dest.authorfile()
89 self.authorfile = self.dest.authorfile()
88
90
89 self.splicemap = mapfile(ui, opts.get('splicemap'))
91 self.splicemap = mapfile(ui, opts.get('splicemap'))
90
92
91 def walktree(self, heads):
93 def walktree(self, heads):
92 '''Return a mapping that identifies the uncommitted parents of every
94 '''Return a mapping that identifies the uncommitted parents of every
93 uncommitted changeset.'''
95 uncommitted changeset.'''
94 visit = heads
96 visit = heads
95 known = {}
97 known = {}
96 parents = {}
98 parents = {}
97 while visit:
99 while visit:
98 n = visit.pop(0)
100 n = visit.pop(0)
99 if n in known or n in self.map: continue
101 if n in known or n in self.map: continue
100 known[n] = 1
102 known[n] = 1
101 commit = self.cachecommit(n)
103 commit = self.cachecommit(n)
102 parents[n] = []
104 parents[n] = []
103 for p in commit.parents:
105 for p in commit.parents:
104 parents[n].append(p)
106 parents[n].append(p)
105 visit.append(p)
107 visit.append(p)
106
108
107 return parents
109 return parents
108
110
109 def toposort(self, parents):
111 def toposort(self, parents):
110 '''Return an ordering such that every uncommitted changeset is
112 '''Return an ordering such that every uncommitted changeset is
111 preceeded by all its uncommitted ancestors.'''
113 preceeded by all its uncommitted ancestors.'''
112 visit = parents.keys()
114 visit = parents.keys()
113 seen = {}
115 seen = {}
114 children = {}
116 children = {}
115 actives = []
117 actives = []
116
118
117 while visit:
119 while visit:
118 n = visit.pop(0)
120 n = visit.pop(0)
119 if n in seen: continue
121 if n in seen: continue
120 seen[n] = 1
122 seen[n] = 1
121 # Ensure that nodes without parents are present in the 'children'
123 # Ensure that nodes without parents are present in the 'children'
122 # mapping.
124 # mapping.
123 children.setdefault(n, [])
125 children.setdefault(n, [])
124 hasparent = False
126 hasparent = False
125 for p in parents[n]:
127 for p in parents[n]:
126 if not p in self.map:
128 if not p in self.map:
127 visit.append(p)
129 visit.append(p)
128 hasparent = True
130 hasparent = True
129 children.setdefault(p, []).append(n)
131 children.setdefault(p, []).append(n)
130 if not hasparent:
132 if not hasparent:
131 actives.append(n)
133 actives.append(n)
132
134
133 del seen
135 del seen
134 del visit
136 del visit
135
137
136 if self.opts.get('datesort'):
138 if self.opts.get('datesort'):
137 dates = {}
139 dates = {}
138 def getdate(n):
140 def getdate(n):
139 if n not in dates:
141 if n not in dates:
140 dates[n] = util.parsedate(self.commitcache[n].date)
142 dates[n] = util.parsedate(self.commitcache[n].date)
141 return dates[n]
143 return dates[n]
142
144
143 def picknext(nodes):
145 def picknext(nodes):
144 return min([(getdate(n), n) for n in nodes])[1]
146 return min([(getdate(n), n) for n in nodes])[1]
145 else:
147 else:
146 prev = [None]
148 prev = [None]
147 def picknext(nodes):
149 def picknext(nodes):
148 # Return the first eligible child of the previously converted
150 # Return the first eligible child of the previously converted
149 # revision, or any of them.
151 # revision, or any of them.
150 next = nodes[0]
152 next = nodes[0]
151 for n in nodes:
153 for n in nodes:
152 if prev[0] in parents[n]:
154 if prev[0] in parents[n]:
153 next = n
155 next = n
154 break
156 break
155 prev[0] = next
157 prev[0] = next
156 return next
158 return next
157
159
158 s = []
160 s = []
159 pendings = {}
161 pendings = {}
160 while actives:
162 while actives:
161 n = picknext(actives)
163 n = picknext(actives)
162 actives.remove(n)
164 actives.remove(n)
163 s.append(n)
165 s.append(n)
164
166
165 # Update dependents list
167 # Update dependents list
166 for c in children.get(n, []):
168 for c in children.get(n, []):
167 if c not in pendings:
169 if c not in pendings:
168 pendings[c] = [p for p in parents[c] if p not in self.map]
170 pendings[c] = [p for p in parents[c] if p not in self.map]
169 try:
171 try:
170 pendings[c].remove(n)
172 pendings[c].remove(n)
171 except ValueError:
173 except ValueError:
172 raise util.Abort(_('cycle detected between %s and %s')
174 raise util.Abort(_('cycle detected between %s and %s')
173 % (recode(c), recode(n)))
175 % (recode(c), recode(n)))
174 if not pendings[c]:
176 if not pendings[c]:
175 # Parents are converted, node is eligible
177 # Parents are converted, node is eligible
176 actives.insert(0, c)
178 actives.insert(0, c)
177 pendings[c] = None
179 pendings[c] = None
178
180
179 if len(s) != len(parents):
181 if len(s) != len(parents):
180 raise util.Abort(_("not all revisions were sorted"))
182 raise util.Abort(_("not all revisions were sorted"))
181
183
182 return s
184 return s
183
185
184 def writeauthormap(self):
186 def writeauthormap(self):
185 authorfile = self.authorfile
187 authorfile = self.authorfile
186 if authorfile:
188 if authorfile:
187 self.ui.status(_('Writing author map file %s\n') % authorfile)
189 self.ui.status(_('Writing author map file %s\n') % authorfile)
188 ofile = open(authorfile, 'w+')
190 ofile = open(authorfile, 'w+')
189 for author in self.authors:
191 for author in self.authors:
190 ofile.write("%s=%s\n" % (author, self.authors[author]))
192 ofile.write("%s=%s\n" % (author, self.authors[author]))
191 ofile.close()
193 ofile.close()
192
194
193 def readauthormap(self, authorfile):
195 def readauthormap(self, authorfile):
194 afile = open(authorfile, 'r')
196 afile = open(authorfile, 'r')
195 for line in afile:
197 for line in afile:
196 if line.strip() == '':
198 if line.strip() == '':
197 continue
199 continue
198 try:
200 try:
199 srcauthor, dstauthor = line.split('=', 1)
201 srcauthor, dstauthor = line.split('=', 1)
200 srcauthor = srcauthor.strip()
202 srcauthor = srcauthor.strip()
201 dstauthor = dstauthor.strip()
203 dstauthor = dstauthor.strip()
202 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
204 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
203 self.ui.status(
205 self.ui.status(
204 _('Overriding mapping for author %s, was %s, will be %s\n')
206 _('Overriding mapping for author %s, was %s, will be %s\n')
205 % (srcauthor, self.authors[srcauthor], dstauthor))
207 % (srcauthor, self.authors[srcauthor], dstauthor))
206 else:
208 else:
207 self.ui.debug(_('Mapping author %s to %s\n')
209 self.ui.debug(_('Mapping author %s to %s\n')
208 % (srcauthor, dstauthor))
210 % (srcauthor, dstauthor))
209 self.authors[srcauthor] = dstauthor
211 self.authors[srcauthor] = dstauthor
210 except IndexError:
212 except IndexError:
211 self.ui.warn(
213 self.ui.warn(
212 _('Ignoring bad line in author map file %s: %s\n')
214 _('Ignoring bad line in author map file %s: %s\n')
213 % (authorfile, line.rstrip()))
215 % (authorfile, line.rstrip()))
214 afile.close()
216 afile.close()
215
217
216 def cachecommit(self, rev):
218 def cachecommit(self, rev):
217 commit = self.source.getcommit(rev)
219 commit = self.source.getcommit(rev)
218 commit.author = self.authors.get(commit.author, commit.author)
220 commit.author = self.authors.get(commit.author, commit.author)
219 self.commitcache[rev] = commit
221 self.commitcache[rev] = commit
220 return commit
222 return commit
221
223
222 def copy(self, rev):
224 def copy(self, rev):
223 commit = self.commitcache[rev]
225 commit = self.commitcache[rev]
224
226
225 changes = self.source.getchanges(rev)
227 changes = self.source.getchanges(rev)
226 if isinstance(changes, basestring):
228 if isinstance(changes, basestring):
227 if changes == SKIPREV:
229 if changes == SKIPREV:
228 dest = SKIPREV
230 dest = SKIPREV
229 else:
231 else:
230 dest = self.map[changes]
232 dest = self.map[changes]
231 self.map[rev] = dest
233 self.map[rev] = dest
232 return
234 return
233 files, copies = changes
235 files, copies = changes
234 pbranches = []
236 pbranches = []
235 if commit.parents:
237 if commit.parents:
236 for prev in commit.parents:
238 for prev in commit.parents:
237 if prev not in self.commitcache:
239 if prev not in self.commitcache:
238 self.cachecommit(prev)
240 self.cachecommit(prev)
239 pbranches.append((self.map[prev],
241 pbranches.append((self.map[prev],
240 self.commitcache[prev].branch))
242 self.commitcache[prev].branch))
241 self.dest.setbranch(commit.branch, pbranches)
243 self.dest.setbranch(commit.branch, pbranches)
242 try:
244 try:
243 parents = self.splicemap[rev].replace(',', ' ').split()
245 parents = self.splicemap[rev].replace(',', ' ').split()
244 self.ui.status(_('spliced in %s as parents of %s\n') %
246 self.ui.status(_('spliced in %s as parents of %s\n') %
245 (parents, rev))
247 (parents, rev))
246 parents = [self.map.get(p, p) for p in parents]
248 parents = [self.map.get(p, p) for p in parents]
247 except KeyError:
249 except KeyError:
248 parents = [b[0] for b in pbranches]
250 parents = [b[0] for b in pbranches]
249 newnode = self.dest.putcommit(files, copies, parents, commit, self.source)
251 newnode = self.dest.putcommit(files, copies, parents, commit, self.source)
250 self.source.converted(rev, newnode)
252 self.source.converted(rev, newnode)
251 self.map[rev] = newnode
253 self.map[rev] = newnode
252
254
253 def convert(self):
255 def convert(self):
254
256
255 try:
257 try:
256 self.source.before()
258 self.source.before()
257 self.dest.before()
259 self.dest.before()
258 self.source.setrevmap(self.map)
260 self.source.setrevmap(self.map)
259 self.ui.status(_("scanning source...\n"))
261 self.ui.status(_("scanning source...\n"))
260 heads = self.source.getheads()
262 heads = self.source.getheads()
261 parents = self.walktree(heads)
263 parents = self.walktree(heads)
262 self.ui.status(_("sorting...\n"))
264 self.ui.status(_("sorting...\n"))
263 t = self.toposort(parents)
265 t = self.toposort(parents)
264 num = len(t)
266 num = len(t)
265 c = None
267 c = None
266
268
267 self.ui.status(_("converting...\n"))
269 self.ui.status(_("converting...\n"))
268 for c in t:
270 for c in t:
269 num -= 1
271 num -= 1
270 desc = self.commitcache[c].desc
272 desc = self.commitcache[c].desc
271 if "\n" in desc:
273 if "\n" in desc:
272 desc = desc.splitlines()[0]
274 desc = desc.splitlines()[0]
273 # convert log message to local encoding without using
275 # convert log message to local encoding without using
274 # tolocal() because util._encoding conver() use it as
276 # tolocal() because util._encoding conver() use it as
275 # 'utf-8'
277 # 'utf-8'
276 self.ui.status("%d %s\n" % (num, recode(desc)))
278 self.ui.status("%d %s\n" % (num, recode(desc)))
277 self.ui.note(_("source: %s\n") % recode(c))
279 self.ui.note(_("source: %s\n") % recode(c))
278 self.copy(c)
280 self.copy(c)
279
281
280 tags = self.source.gettags()
282 tags = self.source.gettags()
281 ctags = {}
283 ctags = {}
282 for k in tags:
284 for k in tags:
283 v = tags[k]
285 v = tags[k]
284 if self.map.get(v, SKIPREV) != SKIPREV:
286 if self.map.get(v, SKIPREV) != SKIPREV:
285 ctags[k] = self.map[v]
287 ctags[k] = self.map[v]
286
288
287 if c and ctags:
289 if c and ctags:
288 nrev = self.dest.puttags(ctags)
290 nrev = self.dest.puttags(ctags)
289 # write another hash correspondence to override the previous
291 # write another hash correspondence to override the previous
290 # one so we don't end up with extra tag heads
292 # one so we don't end up with extra tag heads
291 if nrev:
293 if nrev:
292 self.map[c] = nrev
294 self.map[c] = nrev
293
295
294 self.writeauthormap()
296 self.writeauthormap()
295 finally:
297 finally:
296 self.cleanup()
298 self.cleanup()
297
299
298 def cleanup(self):
300 def cleanup(self):
299 try:
301 try:
300 self.dest.after()
302 self.dest.after()
301 finally:
303 finally:
302 self.source.after()
304 self.source.after()
303 self.map.close()
305 self.map.close()
304
306
305 def convert(ui, src, dest=None, revmapfile=None, **opts):
307 def convert(ui, src, dest=None, revmapfile=None, **opts):
306 global orig_encoding
308 global orig_encoding
307 orig_encoding = util._encoding
309 orig_encoding = util._encoding
308 util._encoding = 'UTF-8'
310 util._encoding = 'UTF-8'
309
311
310 if not dest:
312 if not dest:
311 dest = hg.defaultdest(src) + "-hg"
313 dest = hg.defaultdest(src) + "-hg"
312 ui.status(_("assuming destination %s\n") % dest)
314 ui.status(_("assuming destination %s\n") % dest)
313
315
314 destc = convertsink(ui, dest, opts.get('dest_type'))
316 destc = convertsink(ui, dest, opts.get('dest_type'))
315
317
316 try:
318 try:
317 srcc = convertsource(ui, src, opts.get('source_type'),
319 srcc = convertsource(ui, src, opts.get('source_type'),
318 opts.get('rev'))
320 opts.get('rev'))
319 except Exception:
321 except Exception:
320 for path in destc.created:
322 for path in destc.created:
321 shutil.rmtree(path, True)
323 shutil.rmtree(path, True)
322 raise
324 raise
323
325
324 fmap = opts.get('filemap')
326 fmap = opts.get('filemap')
325 if fmap:
327 if fmap:
326 srcc = filemap.filemap_source(ui, srcc, fmap)
328 srcc = filemap.filemap_source(ui, srcc, fmap)
327 destc.setfilemapmode(True)
329 destc.setfilemapmode(True)
328
330
329 if not revmapfile:
331 if not revmapfile:
330 try:
332 try:
331 revmapfile = destc.revmapfile()
333 revmapfile = destc.revmapfile()
332 except:
334 except:
333 revmapfile = os.path.join(destc, "map")
335 revmapfile = os.path.join(destc, "map")
334
336
335 c = converter(ui, srcc, destc, revmapfile, opts)
337 c = converter(ui, srcc, destc, revmapfile, opts)
336 c.convert()
338 c.convert()
337
339
@@ -1,214 +1,218 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Test the running system for features availability. Exit with zero
2 """Test the running system for features availability. Exit with zero
3 if all features are there, non-zero otherwise. If a feature name is
3 if all features are there, non-zero otherwise. If a feature name is
4 prefixed with "no-", the absence of feature is tested.
4 prefixed with "no-", the absence of feature is tested.
5 """
5 """
6 import optparse
6 import optparse
7 import os
7 import os
8 import re
8 import re
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 def matchoutput(cmd, regexp, ignorestatus=False):
14 def matchoutput(cmd, regexp, ignorestatus=False):
15 """Return True if cmd executes successfully and its output
15 """Return True if cmd executes successfully and its output
16 is matched by the supplied regular expression.
16 is matched by the supplied regular expression.
17 """
17 """
18 r = re.compile(regexp)
18 r = re.compile(regexp)
19 fh = os.popen(cmd)
19 fh = os.popen(cmd)
20 s = fh.read()
20 s = fh.read()
21 ret = fh.close()
21 ret = fh.close()
22 return (ignorestatus or ret is None) and r.search(s)
22 return (ignorestatus or ret is None) and r.search(s)
23
23
24 def has_baz():
24 def has_baz():
25 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
25 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
26
26
27 def has_bzr():
28 return matchoutput('bzr --version 2>&1', r'Bazaar \(bzr\)')
29
27 def has_cvs():
30 def has_cvs():
28 re = r'Concurrent Versions System.*?server'
31 re = r'Concurrent Versions System.*?server'
29 return matchoutput('cvs --version 2>&1', re)
32 return matchoutput('cvs --version 2>&1', re)
30
33
31 def has_cvsps():
34 def has_cvsps():
32 return matchoutput('cvsps -h -q 2>&1', r'cvsps version', True)
35 return matchoutput('cvsps -h -q 2>&1', r'cvsps version', True)
33
36
34 def has_darcs():
37 def has_darcs():
35 return matchoutput('darcs', r'darcs version', True)
38 return matchoutput('darcs', r'darcs version', True)
36
39
37 def has_mtn():
40 def has_mtn():
38 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
41 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
39 'mtn --version', r'monotone 0\.(\d|[12]\d|3[01])[^\d]', True)
42 'mtn --version', r'monotone 0\.(\d|[12]\d|3[01])[^\d]', True)
40
43
41 def has_eol_in_paths():
44 def has_eol_in_paths():
42 try:
45 try:
43 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
46 fd, path = tempfile.mkstemp(prefix=tempprefix, suffix='\n\r')
44 os.close(fd)
47 os.close(fd)
45 os.remove(path)
48 os.remove(path)
46 return True
49 return True
47 except:
50 except:
48 return False
51 return False
49
52
50 def has_executablebit():
53 def has_executablebit():
51 fd, path = tempfile.mkstemp(prefix=tempprefix)
54 fd, path = tempfile.mkstemp(prefix=tempprefix)
52 os.close(fd)
55 os.close(fd)
53 try:
56 try:
54 s = os.lstat(path).st_mode
57 s = os.lstat(path).st_mode
55 os.chmod(path, s | 0100)
58 os.chmod(path, s | 0100)
56 return (os.lstat(path).st_mode & 0100 != 0)
59 return (os.lstat(path).st_mode & 0100 != 0)
57 finally:
60 finally:
58 os.remove(path)
61 os.remove(path)
59
62
60 def has_icasefs():
63 def has_icasefs():
61 # Stolen from mercurial.util
64 # Stolen from mercurial.util
62 fd, path = tempfile.mkstemp(prefix=tempprefix)
65 fd, path = tempfile.mkstemp(prefix=tempprefix)
63 os.close(fd)
66 os.close(fd)
64 try:
67 try:
65 s1 = os.stat(path)
68 s1 = os.stat(path)
66 d, b = os.path.split(path)
69 d, b = os.path.split(path)
67 p2 = os.path.join(d, b.upper())
70 p2 = os.path.join(d, b.upper())
68 if path == p2:
71 if path == p2:
69 p2 = os.path.join(d, b.lower())
72 p2 = os.path.join(d, b.lower())
70 try:
73 try:
71 s2 = os.stat(p2)
74 s2 = os.stat(p2)
72 return s2 == s1
75 return s2 == s1
73 except:
76 except:
74 return False
77 return False
75 finally:
78 finally:
76 os.remove(path)
79 os.remove(path)
77
80
78 def has_inotify():
81 def has_inotify():
79 try:
82 try:
80 import hgext.inotify.linux.watcher
83 import hgext.inotify.linux.watcher
81 return True
84 return True
82 except ImportError:
85 except ImportError:
83 return False
86 return False
84
87
85 def has_fifo():
88 def has_fifo():
86 return hasattr(os, "mkfifo")
89 return hasattr(os, "mkfifo")
87
90
88 def has_hotshot():
91 def has_hotshot():
89 try:
92 try:
90 # hotshot.stats tests hotshot and many problematic dependencies
93 # hotshot.stats tests hotshot and many problematic dependencies
91 # like profile.
94 # like profile.
92 import hotshot.stats
95 import hotshot.stats
93 return True
96 return True
94 except ImportError:
97 except ImportError:
95 return False
98 return False
96
99
97 def has_lsprof():
100 def has_lsprof():
98 try:
101 try:
99 import _lsprof
102 import _lsprof
100 return True
103 return True
101 except ImportError:
104 except ImportError:
102 return False
105 return False
103
106
104 def has_git():
107 def has_git():
105 return matchoutput('git --version 2>&1', r'^git version')
108 return matchoutput('git --version 2>&1', r'^git version')
106
109
107 def has_svn():
110 def has_svn():
108 return matchoutput('svn --version 2>&1', r'^svn, version') and \
111 return matchoutput('svn --version 2>&1', r'^svn, version') and \
109 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
112 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
110
113
111 def has_svn_bindings():
114 def has_svn_bindings():
112 try:
115 try:
113 import svn.core
116 import svn.core
114 return True
117 return True
115 except ImportError:
118 except ImportError:
116 return False
119 return False
117
120
118 def has_symlink():
121 def has_symlink():
119 return hasattr(os, "symlink")
122 return hasattr(os, "symlink")
120
123
121 def has_tla():
124 def has_tla():
122 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
125 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
123
126
124 def has_unix_permissions():
127 def has_unix_permissions():
125 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
128 d = tempfile.mkdtemp(prefix=tempprefix, dir=".")
126 try:
129 try:
127 fname = os.path.join(d, 'foo')
130 fname = os.path.join(d, 'foo')
128 for umask in (077, 007, 022):
131 for umask in (077, 007, 022):
129 os.umask(umask)
132 os.umask(umask)
130 f = open(fname, 'w')
133 f = open(fname, 'w')
131 f.close()
134 f.close()
132 mode = os.stat(fname).st_mode
135 mode = os.stat(fname).st_mode
133 os.unlink(fname)
136 os.unlink(fname)
134 if mode & 0777 != ~umask & 0666:
137 if mode & 0777 != ~umask & 0666:
135 return False
138 return False
136 return True
139 return True
137 finally:
140 finally:
138 os.rmdir(d)
141 os.rmdir(d)
139
142
140 def has_pygments():
143 def has_pygments():
141 try:
144 try:
142 import pygments
145 import pygments
143 return True
146 return True
144 except ImportError:
147 except ImportError:
145 return False
148 return False
146
149
147 checks = {
150 checks = {
148 "baz": (has_baz, "GNU Arch baz client"),
151 "baz": (has_baz, "GNU Arch baz client"),
152 "bzr": (has_bzr, "Canonical's Bazaar client"),
149 "cvs": (has_cvs, "cvs client/server"),
153 "cvs": (has_cvs, "cvs client/server"),
150 "cvsps": (has_cvsps, "cvsps utility"),
154 "cvsps": (has_cvsps, "cvsps utility"),
151 "darcs": (has_darcs, "darcs client"),
155 "darcs": (has_darcs, "darcs client"),
152 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
156 "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
153 "execbit": (has_executablebit, "executable bit"),
157 "execbit": (has_executablebit, "executable bit"),
154 "fifo": (has_fifo, "named pipes"),
158 "fifo": (has_fifo, "named pipes"),
155 "git": (has_git, "git command line client"),
159 "git": (has_git, "git command line client"),
156 "hotshot": (has_hotshot, "python hotshot module"),
160 "hotshot": (has_hotshot, "python hotshot module"),
157 "icasefs": (has_icasefs, "case insensitive file system"),
161 "icasefs": (has_icasefs, "case insensitive file system"),
158 "inotify": (has_inotify, "inotify extension support"),
162 "inotify": (has_inotify, "inotify extension support"),
159 "lsprof": (has_lsprof, "python lsprof module"),
163 "lsprof": (has_lsprof, "python lsprof module"),
160 "mtn": (has_mtn, "monotone client (> 0.31)"),
164 "mtn": (has_mtn, "monotone client (> 0.31)"),
161 "pygments": (has_pygments, "Pygments source highlighting library"),
165 "pygments": (has_pygments, "Pygments source highlighting library"),
162 "svn": (has_svn, "subversion client and admin tools"),
166 "svn": (has_svn, "subversion client and admin tools"),
163 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
167 "svn-bindings": (has_svn_bindings, "subversion python bindings"),
164 "symlink": (has_symlink, "symbolic links"),
168 "symlink": (has_symlink, "symbolic links"),
165 "tla": (has_tla, "GNU Arch tla client"),
169 "tla": (has_tla, "GNU Arch tla client"),
166 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
170 "unix-permissions": (has_unix_permissions, "unix-style permissions"),
167 }
171 }
168
172
169 def list_features():
173 def list_features():
170 for name, feature in checks.iteritems():
174 for name, feature in checks.iteritems():
171 desc = feature[1]
175 desc = feature[1]
172 print name + ':', desc
176 print name + ':', desc
173
177
174 parser = optparse.OptionParser("%prog [options] [features]")
178 parser = optparse.OptionParser("%prog [options] [features]")
175 parser.add_option("--list-features", action="store_true",
179 parser.add_option("--list-features", action="store_true",
176 help="list available features")
180 help="list available features")
177 parser.add_option("-q", "--quiet", action="store_true",
181 parser.add_option("-q", "--quiet", action="store_true",
178 help="check features silently")
182 help="check features silently")
179
183
180 if __name__ == '__main__':
184 if __name__ == '__main__':
181 options, args = parser.parse_args()
185 options, args = parser.parse_args()
182 if options.list_features:
186 if options.list_features:
183 list_features()
187 list_features()
184 sys.exit(0)
188 sys.exit(0)
185
189
186 quiet = options.quiet
190 quiet = options.quiet
187
191
188 failures = 0
192 failures = 0
189
193
190 def error(msg):
194 def error(msg):
191 global failures
195 global failures
192 if not quiet:
196 if not quiet:
193 sys.stderr.write(msg + '\n')
197 sys.stderr.write(msg + '\n')
194 failures += 1
198 failures += 1
195
199
196 for feature in args:
200 for feature in args:
197 negate = feature.startswith('no-')
201 negate = feature.startswith('no-')
198 if negate:
202 if negate:
199 feature = feature[3:]
203 feature = feature[3:]
200
204
201 if feature not in checks:
205 if feature not in checks:
202 error('skipped: unknown feature: ' + feature)
206 error('skipped: unknown feature: ' + feature)
203 continue
207 continue
204
208
205 check, desc = checks[feature]
209 check, desc = checks[feature]
206 if not negate and not check():
210 if not negate and not check():
207 error('skipped: missing feature: ' + desc)
211 error('skipped: missing feature: ' + desc)
208 elif negate and check():
212 elif negate and check():
209 error('skipped: system supports %s' % desc)
213 error('skipped: system supports %s' % desc)
210
214
211 if failures != 0:
215 if failures != 0:
212 sys.exit(1)
216 sys.exit(1)
213
217
214
218
@@ -1,205 +1,206 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 Convert a foreign SCM repository to a Mercurial one.
3 Convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6 - Mercurial [hg]
6 - Mercurial [hg]
7 - CVS [cvs]
7 - CVS [cvs]
8 - Darcs [darcs]
8 - Darcs [darcs]
9 - git [git]
9 - git [git]
10 - Subversion [svn]
10 - Subversion [svn]
11 - Monotone [mtn]
11 - Monotone [mtn]
12 - GNU Arch [gnuarch]
12 - GNU Arch [gnuarch]
13 - Bazaar [bzr]
13
14
14 Accepted destination formats [identifiers]:
15 Accepted destination formats [identifiers]:
15 - Mercurial [hg]
16 - Mercurial [hg]
16 - Subversion [svn] (history on branches is not preserved)
17 - Subversion [svn] (history on branches is not preserved)
17
18
18 If no revision is given, all revisions will be converted. Otherwise,
19 If no revision is given, all revisions will be converted. Otherwise,
19 convert will only import up to the named revision (given in a format
20 convert will only import up to the named revision (given in a format
20 understood by the source).
21 understood by the source).
21
22
22 If no destination directory name is specified, it defaults to the
23 If no destination directory name is specified, it defaults to the
23 basename of the source with '-hg' appended. If the destination
24 basename of the source with '-hg' appended. If the destination
24 repository doesn't exist, it will be created.
25 repository doesn't exist, it will be created.
25
26
26 If <REVMAP> isn't given, it will be put in a default location
27 If <REVMAP> isn't given, it will be put in a default location
27 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
28 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
28 file that maps each source commit ID to the destination ID for
29 file that maps each source commit ID to the destination ID for
29 that revision, like so:
30 that revision, like so:
30 <source ID> <destination ID>
31 <source ID> <destination ID>
31
32
32 If the file doesn't exist, it's automatically created. It's updated
33 If the file doesn't exist, it's automatically created. It's updated
33 on each commit copied, so convert-repo can be interrupted and can
34 on each commit copied, so convert-repo can be interrupted and can
34 be run repeatedly to copy new commits.
35 be run repeatedly to copy new commits.
35
36
36 The [username mapping] file is a simple text file that maps each source
37 The [username mapping] file is a simple text file that maps each source
37 commit author to a destination commit author. It is handy for source SCMs
38 commit author to a destination commit author. It is handy for source SCMs
38 that use unix logins to identify authors (eg: CVS). One line per author
39 that use unix logins to identify authors (eg: CVS). One line per author
39 mapping and the line format is:
40 mapping and the line format is:
40 srcauthor=whatever string you want
41 srcauthor=whatever string you want
41
42
42 The filemap is a file that allows filtering and remapping of files
43 The filemap is a file that allows filtering and remapping of files
43 and directories. Comment lines start with '#'. Each line can
44 and directories. Comment lines start with '#'. Each line can
44 contain one of the following directives:
45 contain one of the following directives:
45
46
46 include path/to/file
47 include path/to/file
47
48
48 exclude path/to/file
49 exclude path/to/file
49
50
50 rename from/file to/file
51 rename from/file to/file
51
52
52 The 'include' directive causes a file, or all files under a
53 The 'include' directive causes a file, or all files under a
53 directory, to be included in the destination repository, and the
54 directory, to be included in the destination repository, and the
54 exclusion of all other files and dirs not explicitely included.
55 exclusion of all other files and dirs not explicitely included.
55 The 'exclude' directive causes files or directories to be omitted.
56 The 'exclude' directive causes files or directories to be omitted.
56 The 'rename' directive renames a file or directory. To rename from a
57 The 'rename' directive renames a file or directory. To rename from a
57 subdirectory into the root of the repository, use '.' as the path to
58 subdirectory into the root of the repository, use '.' as the path to
58 rename to.
59 rename to.
59
60
60 The splicemap is a file that allows insertion of synthetic
61 The splicemap is a file that allows insertion of synthetic
61 history, letting you specify the parents of a revision. This is
62 history, letting you specify the parents of a revision. This is
62 useful if you want to e.g. give a Subversion merge two parents, or
63 useful if you want to e.g. give a Subversion merge two parents, or
63 graft two disconnected series of history together. Each entry
64 graft two disconnected series of history together. Each entry
64 contains a key, followed by a space, followed by one or two
65 contains a key, followed by a space, followed by one or two
65 values, separated by spaces. The key is the revision ID in the
66 values, separated by spaces. The key is the revision ID in the
66 source revision control system whose parents should be modified
67 source revision control system whose parents should be modified
67 (same format as a key in .hg/shamap). The values are the revision
68 (same format as a key in .hg/shamap). The values are the revision
68 IDs (in either the source or destination revision control system)
69 IDs (in either the source or destination revision control system)
69 that should be used as the new parents for that node.
70 that should be used as the new parents for that node.
70
71
71 Mercurial Source
72 Mercurial Source
72 -----------------
73 -----------------
73
74
74 --config convert.hg.saverev=True (boolean)
75 --config convert.hg.saverev=True (boolean)
75 allow target to preserve source revision ID
76 allow target to preserve source revision ID
76 --config convert.hg.startrev=0 (hg revision identifier)
77 --config convert.hg.startrev=0 (hg revision identifier)
77 convert start revision and its descendants
78 convert start revision and its descendants
78
79
79 CVS Source
80 CVS Source
80 ----------
81 ----------
81
82
82 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
83 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
83 to indicate the starting point of what will be converted. Direct
84 to indicate the starting point of what will be converted. Direct
84 access to the repository files is not needed, unless of course
85 access to the repository files is not needed, unless of course
85 the repository is :local:. The conversion uses the top level
86 the repository is :local:. The conversion uses the top level
86 directory in the sandbox to find the CVS repository, and then uses
87 directory in the sandbox to find the CVS repository, and then uses
87 CVS rlog commands to find files to convert. This means that unless
88 CVS rlog commands to find files to convert. This means that unless
88 a filemap is given, all files under the starting directory will be
89 a filemap is given, all files under the starting directory will be
89 converted, and that any directory reorganisation in the CVS
90 converted, and that any directory reorganisation in the CVS
90 sandbox is ignored.
91 sandbox is ignored.
91
92
92 Because CVS does not have changesets, it is necessary to collect
93 Because CVS does not have changesets, it is necessary to collect
93 individual commits to CVS and merge them into changesets. CVS source
94 individual commits to CVS and merge them into changesets. CVS source
94 can use the external 'cvsps' program (this is a legacy option and may
95 can use the external 'cvsps' program (this is a legacy option and may
95 be removed in future) or use its internal changeset merging code.
96 be removed in future) or use its internal changeset merging code.
96 External cvsps is default, and options may be passed to it by setting
97 External cvsps is default, and options may be passed to it by setting
97 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
98 --config convert.cvsps='cvsps -A -u --cvs-direct -q'
98 The options shown are the defaults.
99 The options shown are the defaults.
99
100
100 Internal cvsps is selected by setting
101 Internal cvsps is selected by setting
101 --config convert.cvsps=builtin
102 --config convert.cvsps=builtin
102 and has a few more configurable options:
103 and has a few more configurable options:
103 --config convert.cvsps.fuzz=60 (integer)
104 --config convert.cvsps.fuzz=60 (integer)
104 Specify the maximum time (in seconds) that is allowed between
105 Specify the maximum time (in seconds) that is allowed between
105 commits with identical user and log message in a single
106 commits with identical user and log message in a single
106 changeset. When very large files were checked in as part
107 changeset. When very large files were checked in as part
107 of a changeset then the default may not be long enough.
108 of a changeset then the default may not be long enough.
108 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
109 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
109 Specify a regular expression to which commit log messages are
110 Specify a regular expression to which commit log messages are
110 matched. If a match occurs, then the conversion process will
111 matched. If a match occurs, then the conversion process will
111 insert a dummy revision merging the branch on which this log
112 insert a dummy revision merging the branch on which this log
112 message occurs to the branch indicated in the regex.
113 message occurs to the branch indicated in the regex.
113 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
114 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
114 Specify a regular expression to which commit log messages are
115 Specify a regular expression to which commit log messages are
115 matched. If a match occurs, then the conversion process will
116 matched. If a match occurs, then the conversion process will
116 add the most recent revision on the branch indicated in the
117 add the most recent revision on the branch indicated in the
117 regex as the second parent of the changeset.
118 regex as the second parent of the changeset.
118
119
119 The hgext/convert/cvsps wrapper script allows the builtin changeset
120 The hgext/convert/cvsps wrapper script allows the builtin changeset
120 merging code to be run without doing a conversion. Its parameters and
121 merging code to be run without doing a conversion. Its parameters and
121 output are similar to that of cvsps 2.1.
122 output are similar to that of cvsps 2.1.
122
123
123 Subversion Source
124 Subversion Source
124 -----------------
125 -----------------
125
126
126 Subversion source detects classical trunk/branches/tags layouts.
127 Subversion source detects classical trunk/branches/tags layouts.
127 By default, the supplied "svn://repo/path/" source URL is
128 By default, the supplied "svn://repo/path/" source URL is
128 converted as a single branch. If "svn://repo/path/trunk" exists
129 converted as a single branch. If "svn://repo/path/trunk" exists
129 it replaces the default branch. If "svn://repo/path/branches"
130 it replaces the default branch. If "svn://repo/path/branches"
130 exists, its subdirectories are listed as possible branches. If
131 exists, its subdirectories are listed as possible branches. If
131 "svn://repo/path/tags" exists, it is looked for tags referencing
132 "svn://repo/path/tags" exists, it is looked for tags referencing
132 converted branches. Default "trunk", "branches" and "tags" values
133 converted branches. Default "trunk", "branches" and "tags" values
133 can be overriden with following options. Set them to paths
134 can be overriden with following options. Set them to paths
134 relative to the source URL, or leave them blank to disable
135 relative to the source URL, or leave them blank to disable
135 autodetection.
136 autodetection.
136
137
137 --config convert.svn.branches=branches (directory name)
138 --config convert.svn.branches=branches (directory name)
138 specify the directory containing branches
139 specify the directory containing branches
139 --config convert.svn.tags=tags (directory name)
140 --config convert.svn.tags=tags (directory name)
140 specify the directory containing tags
141 specify the directory containing tags
141 --config convert.svn.trunk=trunk (directory name)
142 --config convert.svn.trunk=trunk (directory name)
142 specify the name of the trunk branch
143 specify the name of the trunk branch
143
144
144 Source history can be retrieved starting at a specific revision,
145 Source history can be retrieved starting at a specific revision,
145 instead of being integrally converted. Only single branch
146 instead of being integrally converted. Only single branch
146 conversions are supported.
147 conversions are supported.
147
148
148 --config convert.svn.startrev=0 (svn revision number)
149 --config convert.svn.startrev=0 (svn revision number)
149 specify start Subversion revision.
150 specify start Subversion revision.
150
151
151 Mercurial Destination
152 Mercurial Destination
152 ---------------------
153 ---------------------
153
154
154 --config convert.hg.clonebranches=False (boolean)
155 --config convert.hg.clonebranches=False (boolean)
155 dispatch source branches in separate clones.
156 dispatch source branches in separate clones.
156 --config convert.hg.tagsbranch=default (branch name)
157 --config convert.hg.tagsbranch=default (branch name)
157 tag revisions branch name
158 tag revisions branch name
158 --config convert.hg.usebranchnames=True (boolean)
159 --config convert.hg.usebranchnames=True (boolean)
159 preserve branch names
160 preserve branch names
160
161
161 options:
162 options:
162
163
163 -A --authors username mapping filename
164 -A --authors username mapping filename
164 -d --dest-type destination repository type
165 -d --dest-type destination repository type
165 --filemap remap file names using contents of file
166 --filemap remap file names using contents of file
166 -r --rev import up to target revision REV
167 -r --rev import up to target revision REV
167 -s --source-type source repository type
168 -s --source-type source repository type
168 --splicemap splice synthesized history into place
169 --splicemap splice synthesized history into place
169 --datesort try to sort changesets by date
170 --datesort try to sort changesets by date
170
171
171 use "hg -v help convert" to show global options
172 use "hg -v help convert" to show global options
172 adding a
173 adding a
173 assuming destination a-hg
174 assuming destination a-hg
174 initializing destination a-hg repository
175 initializing destination a-hg repository
175 scanning source...
176 scanning source...
176 sorting...
177 sorting...
177 converting...
178 converting...
178 4 a
179 4 a
179 3 b
180 3 b
180 2 c
181 2 c
181 1 d
182 1 d
182 0 e
183 0 e
183 pulling from ../a
184 pulling from ../a
184 searching for changes
185 searching for changes
185 no changes found
186 no changes found
186 % should fail
187 % should fail
187 initializing destination bogusfile repository
188 initializing destination bogusfile repository
188 abort: cannot create new bundle repository
189 abort: cannot create new bundle repository
189 % should fail
190 % should fail
190 abort: Permission denied: bogusdir
191 abort: Permission denied: bogusdir
191 % should succeed
192 % should succeed
192 initializing destination bogusdir repository
193 initializing destination bogusdir repository
193 scanning source...
194 scanning source...
194 sorting...
195 sorting...
195 converting...
196 converting...
196 4 a
197 4 a
197 3 b
198 3 b
198 2 c
199 2 c
199 1 d
200 1 d
200 0 e
201 0 e
201 % test pre and post conversion actions
202 % test pre and post conversion actions
202 run hg source pre-conversion action
203 run hg source pre-conversion action
203 run hg sink pre-conversion action
204 run hg sink pre-conversion action
204 run hg sink post-conversion action
205 run hg sink post-conversion action
205 run hg source post-conversion action
206 run hg source post-conversion action
General Comments 0
You need to be logged in to leave comments. Login now