##// END OF EJS Templates
convert/bzr: make it work with filemaps (issue1631)...
Patrick Mezard -
r8165:78658990 default
parent child Browse files
Show More
@@ -1,224 +1,223 b''
1 # bzr support for the convert extension
1 # bzr support for the convert extension
2 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
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
3 # it cannot access 'bar' repositories, but they were never used very much
4
4
5 import os
5 import os
6 from mercurial import demandimport
6 from mercurial import demandimport
7 # these do not work with demandimport, blacklist
7 # these do not work with demandimport, blacklist
8 demandimport.ignore.extend([
8 demandimport.ignore.extend([
9 'bzrlib.transactions',
9 'bzrlib.transactions',
10 'bzrlib.urlutils',
10 'bzrlib.urlutils',
11 ])
11 ])
12
12
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial import util
14 from mercurial import util
15 from common import NoRepo, commit, converter_source
15 from common import NoRepo, commit, converter_source
16
16
17 try:
17 try:
18 # bazaar imports
18 # bazaar imports
19 from bzrlib import branch, revision, errors
19 from bzrlib import branch, revision, errors
20 from bzrlib.revisionspec import RevisionSpec
20 from bzrlib.revisionspec import RevisionSpec
21 except ImportError:
21 except ImportError:
22 pass
22 pass
23
23
24 supportedkinds = ('file', 'symlink')
24 supportedkinds = ('file', 'symlink')
25
25
26 class bzr_source(converter_source):
26 class bzr_source(converter_source):
27 """Reads Bazaar repositories by using the Bazaar Python libraries"""
27 """Reads Bazaar repositories by using the Bazaar Python libraries"""
28
28
29 def __init__(self, ui, path, rev=None):
29 def __init__(self, ui, path, rev=None):
30 super(bzr_source, self).__init__(ui, path, rev=rev)
30 super(bzr_source, self).__init__(ui, path, rev=rev)
31
31
32 try:
32 try:
33 # access bzrlib stuff
33 # access bzrlib stuff
34 branch
34 branch
35 except NameError:
35 except NameError:
36 raise NoRepo('Bazaar modules could not be loaded')
36 raise NoRepo('Bazaar modules could not be loaded')
37
37
38 if not os.path.exists(os.path.join(path, '.bzr')):
38 if not os.path.exists(os.path.join(path, '.bzr')):
39 raise NoRepo('%s does not look like a Bazaar repo' % path)
39 raise NoRepo('%s does not look like a Bazaar repo' % path)
40
40
41 path = os.path.abspath(path)
41 path = os.path.abspath(path)
42 self.branch = branch.Branch.open(path)
42 self.branch = branch.Branch.open(path)
43 self.sourcerepo = self.branch.repository
43 self.sourcerepo = self.branch.repository
44 self._parentids = {}
44 self._parentids = {}
45
45
46 def before(self):
46 def before(self):
47 """Before the conversion begins, acquire a read lock
47 """Before the conversion begins, acquire a read lock
48 for all the operations that might need it. Fortunately
48 for all the operations that might need it. Fortunately
49 read locks don't block other reads or writes to the
49 read locks don't block other reads or writes to the
50 repository, so this shouldn't have any impact on the usage of
50 repository, so this shouldn't have any impact on the usage of
51 the source repository.
51 the source repository.
52
52
53 The alternative would be locking on every operation that
53 The alternative would be locking on every operation that
54 needs locks (there are currently two: getting the file and
54 needs locks (there are currently two: getting the file and
55 getting the parent map) and releasing immediately after,
55 getting the parent map) and releasing immediately after,
56 but this approach can take even 40% longer."""
56 but this approach can take even 40% longer."""
57 self.sourcerepo.lock_read()
57 self.sourcerepo.lock_read()
58
58
59 def after(self):
59 def after(self):
60 self.sourcerepo.unlock()
60 self.sourcerepo.unlock()
61
61
62 def getheads(self):
62 def getheads(self):
63 if not self.rev:
63 if not self.rev:
64 return [self.branch.last_revision()]
64 return [self.branch.last_revision()]
65 try:
65 try:
66 r = RevisionSpec.from_string(self.rev)
66 r = RevisionSpec.from_string(self.rev)
67 info = r.in_history(self.branch)
67 info = r.in_history(self.branch)
68 except errors.BzrError:
68 except errors.BzrError:
69 raise util.Abort(_('%s is not a valid revision in current branch')
69 raise util.Abort(_('%s is not a valid revision in current branch')
70 % self.rev)
70 % self.rev)
71 return [info.rev_id]
71 return [info.rev_id]
72
72
73 def getfile(self, name, rev):
73 def getfile(self, name, rev):
74 revtree = self.sourcerepo.revision_tree(rev)
74 revtree = self.sourcerepo.revision_tree(rev)
75 fileid = revtree.path2id(name)
75 fileid = revtree.path2id(name)
76 if fileid is None or revtree.kind(fileid) not in supportedkinds:
76 if fileid is None or revtree.kind(fileid) not in supportedkinds:
77 # the file is not available anymore - was deleted
77 # the file is not available anymore - was deleted
78 raise IOError(_('%s is not available in %s anymore') %
78 raise IOError(_('%s is not available in %s anymore') %
79 (name, rev))
79 (name, rev))
80 sio = revtree.get_file(fileid)
80 sio = revtree.get_file(fileid)
81 return sio.read()
81 return sio.read()
82
82
83 def getmode(self, name, rev):
83 def getmode(self, name, rev):
84 return self._modecache[(name, rev)]
84 return self._modecache[(name, rev)]
85
85
86 def getchanges(self, version):
86 def getchanges(self, version):
87 # set up caches: modecache and revtree
87 # set up caches: modecache and revtree
88 self._modecache = {}
88 self._modecache = {}
89 self._revtree = self.sourcerepo.revision_tree(version)
89 self._revtree = self.sourcerepo.revision_tree(version)
90 # get the parentids from the cache
90 # get the parentids from the cache
91 parentids = self._parentids.pop(version)
91 parentids = self._parentids.pop(version)
92 # only diff against first parent id
92 # only diff against first parent id
93 prevtree = self.sourcerepo.revision_tree(parentids[0])
93 prevtree = self.sourcerepo.revision_tree(parentids[0])
94 return self._gettreechanges(self._revtree, prevtree)
94 return self._gettreechanges(self._revtree, prevtree)
95
95
96 def getcommit(self, version):
96 def getcommit(self, version):
97 rev = self.sourcerepo.get_revision(version)
97 rev = self.sourcerepo.get_revision(version)
98 # populate parent id cache
98 # populate parent id cache
99 if not rev.parent_ids:
99 if not rev.parent_ids:
100 parents = []
100 parents = []
101 self._parentids[version] = (revision.NULL_REVISION,)
101 self._parentids[version] = (revision.NULL_REVISION,)
102 else:
102 else:
103 parents = self._filterghosts(rev.parent_ids)
103 parents = self._filterghosts(rev.parent_ids)
104 self._parentids[version] = parents
104 self._parentids[version] = parents
105
105
106 return commit(parents=parents,
106 return commit(parents=parents,
107 # bzr uses 1 second timezone precision
107 # bzr uses 1 second timezone precision
108 date='%d %d' % (rev.timestamp, rev.timezone / 3600),
108 date='%d %d' % (rev.timestamp, rev.timezone / 3600),
109 author=self.recode(rev.committer),
109 author=self.recode(rev.committer),
110 # bzr returns bytestrings or unicode, depending on the content
110 # bzr returns bytestrings or unicode, depending on the content
111 desc=self.recode(rev.message),
111 desc=self.recode(rev.message),
112 rev=version)
112 rev=version)
113
113
114 def gettags(self):
114 def gettags(self):
115 if not self.branch.supports_tags():
115 if not self.branch.supports_tags():
116 return {}
116 return {}
117 tagdict = self.branch.tags.get_tag_dict()
117 tagdict = self.branch.tags.get_tag_dict()
118 bytetags = {}
118 bytetags = {}
119 for name, rev in tagdict.iteritems():
119 for name, rev in tagdict.iteritems():
120 bytetags[self.recode(name)] = rev
120 bytetags[self.recode(name)] = rev
121 return bytetags
121 return bytetags
122
122
123 def getchangedfiles(self, rev, i):
123 def getchangedfiles(self, rev, i):
124 self._modecache = {}
124 self._modecache = {}
125 curtree = self.sourcerepo.revision_tree(rev)
125 curtree = self.sourcerepo.revision_tree(rev)
126 parentids = self._parentids.pop(rev)
127 if i is not None:
126 if i is not None:
128 parentid = parentids[i]
127 parentid = self._parentids[rev][i]
129 else:
128 else:
130 # no parent id, get the empty revision
129 # no parent id, get the empty revision
131 parentid = revision.NULL_REVISION
130 parentid = revision.NULL_REVISION
132
131
133 prevtree = self.sourcerepo.revision_tree(parentid)
132 prevtree = self.sourcerepo.revision_tree(parentid)
134 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
133 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
135 return changes
134 return changes
136
135
137 def _gettreechanges(self, current, origin):
136 def _gettreechanges(self, current, origin):
138 revid = current._revision_id;
137 revid = current._revision_id;
139 changes = []
138 changes = []
140 renames = {}
139 renames = {}
141 for (fileid, paths, changed_content, versioned, parent, name,
140 for (fileid, paths, changed_content, versioned, parent, name,
142 kind, executable) in current.iter_changes(origin):
141 kind, executable) in current.iter_changes(origin):
143
142
144 if paths[0] == u'' or paths[1] == u'':
143 if paths[0] == u'' or paths[1] == u'':
145 # ignore changes to tree root
144 # ignore changes to tree root
146 continue
145 continue
147
146
148 # bazaar tracks directories, mercurial does not, so
147 # bazaar tracks directories, mercurial does not, so
149 # we have to rename the directory contents
148 # we have to rename the directory contents
150 if kind[1] == 'directory':
149 if kind[1] == 'directory':
151 if kind[0] not in (None, 'directory'):
150 if kind[0] not in (None, 'directory'):
152 # Replacing 'something' with a directory, record it
151 # Replacing 'something' with a directory, record it
153 # so it can be removed.
152 # so it can be removed.
154 changes.append((self.recode(paths[0]), revid))
153 changes.append((self.recode(paths[0]), revid))
155
154
156 if None not in paths and paths[0] != paths[1]:
155 if None not in paths and paths[0] != paths[1]:
157 # neither an add nor an delete - a move
156 # neither an add nor an delete - a move
158 # rename all directory contents manually
157 # rename all directory contents manually
159 subdir = origin.inventory.path2id(paths[0])
158 subdir = origin.inventory.path2id(paths[0])
160 # get all child-entries of the directory
159 # get all child-entries of the directory
161 for name, entry in origin.inventory.iter_entries(subdir):
160 for name, entry in origin.inventory.iter_entries(subdir):
162 # hg does not track directory renames
161 # hg does not track directory renames
163 if entry.kind == 'directory':
162 if entry.kind == 'directory':
164 continue
163 continue
165 frompath = self.recode(paths[0] + '/' + name)
164 frompath = self.recode(paths[0] + '/' + name)
166 topath = self.recode(paths[1] + '/' + name)
165 topath = self.recode(paths[1] + '/' + name)
167 # register the files as changed
166 # register the files as changed
168 changes.append((frompath, revid))
167 changes.append((frompath, revid))
169 changes.append((topath, revid))
168 changes.append((topath, revid))
170 # add to mode cache
169 # add to mode cache
171 mode = ((entry.executable and 'x') or (entry.kind == 'symlink' and 's')
170 mode = ((entry.executable and 'x') or (entry.kind == 'symlink' and 's')
172 or '')
171 or '')
173 self._modecache[(topath, revid)] = mode
172 self._modecache[(topath, revid)] = mode
174 # register the change as move
173 # register the change as move
175 renames[topath] = frompath
174 renames[topath] = frompath
176
175
177 # no futher changes, go to the next change
176 # no futher changes, go to the next change
178 continue
177 continue
179
178
180 # we got unicode paths, need to convert them
179 # we got unicode paths, need to convert them
181 path, topath = [self.recode(part) for part in paths]
180 path, topath = [self.recode(part) for part in paths]
182
181
183 if topath is None:
182 if topath is None:
184 # file deleted
183 # file deleted
185 changes.append((path, revid))
184 changes.append((path, revid))
186 continue
185 continue
187
186
188 # renamed
187 # renamed
189 if path and path != topath:
188 if path and path != topath:
190 renames[topath] = path
189 renames[topath] = path
191 changes.append((path, revid))
190 changes.append((path, revid))
192
191
193 # populate the mode cache
192 # populate the mode cache
194 kind, executable = [e[1] for e in (kind, executable)]
193 kind, executable = [e[1] for e in (kind, executable)]
195 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
194 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
196 or '')
195 or '')
197 self._modecache[(topath, revid)] = mode
196 self._modecache[(topath, revid)] = mode
198 changes.append((topath, revid))
197 changes.append((topath, revid))
199
198
200 return changes, renames
199 return changes, renames
201
200
202 def _filterghosts(self, ids):
201 def _filterghosts(self, ids):
203 """Filters out ghost revisions which hg does not support, see
202 """Filters out ghost revisions which hg does not support, see
204 <http://bazaar-vcs.org/GhostRevision>
203 <http://bazaar-vcs.org/GhostRevision>
205 """
204 """
206 parentmap = self.sourcerepo.get_parent_map(ids)
205 parentmap = self.sourcerepo.get_parent_map(ids)
207 parents = tuple([parent for parent in ids if parent in parentmap])
206 parents = tuple([parent for parent in ids if parent in parentmap])
208 return parents
207 return parents
209
208
210 def recode(self, s, encoding=None):
209 def recode(self, s, encoding=None):
211 """This version of recode tries to encode unicode to bytecode,
210 """This version of recode tries to encode unicode to bytecode,
212 and preferably using the UTF-8 codec.
211 and preferably using the UTF-8 codec.
213 Other types than Unicode are silently returned, this is by
212 Other types than Unicode are silently returned, this is by
214 intention, e.g. the None-type is not going to be encoded but instead
213 intention, e.g. the None-type is not going to be encoded but instead
215 just passed through
214 just passed through
216 """
215 """
217 if not encoding:
216 if not encoding:
218 encoding = self.encoding or 'utf-8'
217 encoding = self.encoding or 'utf-8'
219
218
220 if isinstance(s, unicode):
219 if isinstance(s, unicode):
221 return s.encode(encoding)
220 return s.encode(encoding)
222 else:
221 else:
223 # leave it alone
222 # leave it alone
224 return s
223 return s
@@ -1,91 +1,97 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 . "$TESTDIR/bzr-definitions"
3 . "$TESTDIR/bzr-definitions"
4
4
5 echo % create and rename on the same file in the same step
5 echo % create and rename on the same file in the same step
6 mkdir test-createandrename
6 mkdir test-createandrename
7 cd test-createandrename
7 cd test-createandrename
8 bzr init -q source
8 bzr init -q source
9 cd source
9 cd source
10 echo a > a
10 echo a > a
11 echo c > c
11 echo c > c
12 echo e > e
12 echo e > e
13 bzr add -q a c e
13 bzr add -q a c e
14 bzr commit -q -m 'Initial add: a, c, e'
14 bzr commit -q -m 'Initial add: a, c, e'
15 bzr mv a b
15 bzr mv a b
16 bzr mv c d
16 bzr mv c d
17 bzr mv e f
17 bzr mv e f
18 echo a2 >> a
18 echo a2 >> a
19 mkdir e
19 mkdir e
20 bzr add -q a e
20 bzr add -q a e
21 bzr commit -q -m 'rename a into b, create a, rename c into d'
21 bzr commit -q -m 'rename a into b, create a, rename c into d'
22 cd ..
22 cd ..
23 hg convert source source-hg
23 hg convert source source-hg
24 glog -R source-hg
24 glog -R source-hg
25 echo "% manifest"
25 echo "% manifest"
26 hg manifest -R source-hg -r tip
26 hg manifest -R source-hg -r tip
27 echo "% test --rev option"
27 echo "% test --rev option"
28 hg convert -r 1 source source-1-hg
28 hg convert -r 1 source source-1-hg
29 glog -R source-1-hg
29 glog -R source-1-hg
30 echo "% test with filemap"
31 cat > filemap <<EOF
32 exclude a
33 EOF
34 hg convert --filemap filemap source source-filemap-hg
35 hg -R source-filemap-hg manifest -r tip
30 cd ..
36 cd ..
31
37
32 echo % merge
38 echo % merge
33 mkdir test-merge
39 mkdir test-merge
34 cd test-merge
40 cd test-merge
35
41
36 cat > helper.py <<EOF
42 cat > helper.py <<EOF
37 import sys
43 import sys
38 from bzrlib import workingtree
44 from bzrlib import workingtree
39 wt = workingtree.WorkingTree.open('.')
45 wt = workingtree.WorkingTree.open('.')
40
46
41 message, stamp = sys.argv[1:]
47 message, stamp = sys.argv[1:]
42 wt.commit(message, timestamp=int(stamp))
48 wt.commit(message, timestamp=int(stamp))
43 EOF
49 EOF
44
50
45 bzr init -q source
51 bzr init -q source
46 cd source
52 cd source
47 echo content > a
53 echo content > a
48 echo content2 > b
54 echo content2 > b
49 bzr add -q a b
55 bzr add -q a b
50 bzr commit -q -m 'Initial add'
56 bzr commit -q -m 'Initial add'
51 cd ..
57 cd ..
52 bzr branch -q source source-improve
58 bzr branch -q source source-improve
53 cd source
59 cd source
54 echo more >> a
60 echo more >> a
55 python ../helper.py 'Editing a' 100
61 python ../helper.py 'Editing a' 100
56 cd ../source-improve
62 cd ../source-improve
57 echo content3 >> b
63 echo content3 >> b
58 python ../helper.py 'Editing b' 200
64 python ../helper.py 'Editing b' 200
59 cd ../source
65 cd ../source
60 bzr merge -q ../source-improve
66 bzr merge -q ../source-improve
61 bzr commit -q -m 'Merged improve branch'
67 bzr commit -q -m 'Merged improve branch'
62 cd ..
68 cd ..
63 hg convert --datesort source source-hg
69 hg convert --datesort source source-hg
64 glog -R source-hg
70 glog -R source-hg
65 cd ..
71 cd ..
66
72
67 echo % symlinks and executable files
73 echo % symlinks and executable files
68 mkdir test-symlinks
74 mkdir test-symlinks
69 cd test-symlinks
75 cd test-symlinks
70 bzr init -q source
76 bzr init -q source
71 cd source
77 cd source
72 touch program
78 touch program
73 chmod +x program
79 chmod +x program
74 ln -s program altname
80 ln -s program altname
75 mkdir d
81 mkdir d
76 echo a > d/a
82 echo a > d/a
77 ln -s a syma
83 ln -s a syma
78 bzr add -q altname program syma d/a
84 bzr add -q altname program syma d/a
79 bzr commit -q -m 'Initial setup'
85 bzr commit -q -m 'Initial setup'
80 touch newprog
86 touch newprog
81 chmod +x newprog
87 chmod +x newprog
82 rm altname
88 rm altname
83 ln -s newprog altname
89 ln -s newprog altname
84 chmod -x program
90 chmod -x program
85 bzr add -q newprog
91 bzr add -q newprog
86 bzr commit -q -m 'Symlink changed, x bits changed'
92 bzr commit -q -m 'Symlink changed, x bits changed'
87 cd ..
93 cd ..
88 hg convert source source-hg
94 hg convert source source-hg
89 manifest source-hg 0
95 manifest source-hg 0
90 manifest source-hg tip
96 manifest source-hg tip
91 cd ..
97 cd ..
@@ -1,62 +1,72 b''
1 % create and rename on the same file in the same step
1 % create and rename on the same file in the same step
2 a => b
2 a => b
3 c => d
3 c => d
4 e => f
4 e => f
5 initializing destination source-hg repository
5 initializing destination source-hg repository
6 scanning source...
6 scanning source...
7 sorting...
7 sorting...
8 converting...
8 converting...
9 1 Initial add: a, c, e
9 1 Initial add: a, c, e
10 0 rename a into b, create a, rename c into d
10 0 rename a into b, create a, rename c into d
11 o 1 "rename a into b, create a, rename c into d" files: a b c d e f
11 o 1 "rename a into b, create a, rename c into d" files: a b c d e f
12 |
12 |
13 o 0 "Initial add: a, c, e" files: a c e
13 o 0 "Initial add: a, c, e" files: a c e
14
14
15 % manifest
15 % manifest
16 a
16 a
17 b
17 b
18 d
18 d
19 f
19 f
20 % test --rev option
20 % test --rev option
21 initializing destination source-1-hg repository
21 initializing destination source-1-hg repository
22 scanning source...
22 scanning source...
23 sorting...
23 sorting...
24 converting...
24 converting...
25 0 Initial add: a, c, e
25 0 Initial add: a, c, e
26 o 0 "Initial add: a, c, e" files: a c e
26 o 0 "Initial add: a, c, e" files: a c e
27
27
28 % test with filemap
29 initializing destination source-filemap-hg repository
30 scanning source...
31 sorting...
32 converting...
33 1 Initial add: a, c, e
34 0 rename a into b, create a, rename c into d
35 b
36 d
37 f
28 % merge
38 % merge
29 initializing destination source-hg repository
39 initializing destination source-hg repository
30 scanning source...
40 scanning source...
31 sorting...
41 sorting...
32 converting...
42 converting...
33 3 Initial add
43 3 Initial add
34 2 Editing a
44 2 Editing a
35 1 Editing b
45 1 Editing b
36 0 Merged improve branch
46 0 Merged improve branch
37 o 3 "Merged improve branch" files:
47 o 3 "Merged improve branch" files:
38 |\
48 |\
39 | o 2 "Editing b" files: b
49 | o 2 "Editing b" files: b
40 | |
50 | |
41 o | 1 "Editing a" files: a
51 o | 1 "Editing a" files: a
42 |/
52 |/
43 o 0 "Initial add" files: a b
53 o 0 "Initial add" files: a b
44
54
45 % symlinks and executable files
55 % symlinks and executable files
46 initializing destination source-hg repository
56 initializing destination source-hg repository
47 scanning source...
57 scanning source...
48 sorting...
58 sorting...
49 converting...
59 converting...
50 1 Initial setup
60 1 Initial setup
51 0 Symlink changed, x bits changed
61 0 Symlink changed, x bits changed
52 % manifest of 0
62 % manifest of 0
53 644 @ altname
63 644 @ altname
54 644 d/a
64 644 d/a
55 755 * program
65 755 * program
56 644 @ syma
66 644 @ syma
57 % manifest of tip
67 % manifest of tip
58 644 @ altname
68 644 @ altname
59 644 d/a
69 644 d/a
60 755 * newprog
70 755 * newprog
61 644 program
71 644 program
62 644 @ syma
72 644 @ syma
General Comments 0
You need to be logged in to leave comments. Login now