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