##// END OF EJS Templates
convert/bzr: ignore nested repos when listing branches (issue3254)...
Patrick Mezard -
r16099:4e4c416a default
parent child Browse files
Show More
@@ -1,282 +1,285
1 1 # bzr.py - bzr support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Marek Kubica <marek@xivilization.net> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # This module is for handling 'bzr', that was formerly known as Bazaar-NG;
9 9 # it cannot access 'bar' repositories, but they were never used very much
10 10
11 11 import os
12 12 from mercurial import demandimport
13 13 # these do not work with demandimport, blacklist
14 14 demandimport.ignore.extend([
15 15 'bzrlib.transactions',
16 16 'bzrlib.urlutils',
17 17 'ElementPath',
18 18 ])
19 19
20 20 from mercurial.i18n import _
21 21 from mercurial import util
22 22 from common import NoRepo, commit, converter_source
23 23
24 24 try:
25 25 # bazaar imports
26 26 from bzrlib import bzrdir, revision, errors
27 27 from bzrlib.revisionspec import RevisionSpec
28 28 except ImportError:
29 29 pass
30 30
31 31 supportedkinds = ('file', 'symlink')
32 32
33 33 class bzr_source(converter_source):
34 34 """Reads Bazaar repositories by using the Bazaar Python libraries"""
35 35
36 36 def __init__(self, ui, path, rev=None):
37 37 super(bzr_source, self).__init__(ui, path, rev=rev)
38 38
39 39 if not os.path.exists(os.path.join(path, '.bzr')):
40 40 raise NoRepo(_('%s does not look like a Bazaar repository')
41 41 % path)
42 42
43 43 try:
44 44 # access bzrlib stuff
45 45 bzrdir
46 46 except NameError:
47 47 raise NoRepo(_('Bazaar modules could not be loaded'))
48 48
49 49 path = os.path.abspath(path)
50 50 self._checkrepotype(path)
51 51 try:
52 52 self.sourcerepo = bzrdir.BzrDir.open(path).open_repository()
53 53 except errors.NoRepositoryPresent:
54 54 raise NoRepo(_('%s does not look like a Bazaar repository')
55 55 % path)
56 56 self._parentids = {}
57 57
58 58 def _checkrepotype(self, path):
59 59 # Lightweight checkouts detection is informational but probably
60 60 # fragile at API level. It should not terminate the conversion.
61 61 try:
62 62 from bzrlib import bzrdir
63 63 dir = bzrdir.BzrDir.open_containing(path)[0]
64 64 try:
65 65 tree = dir.open_workingtree(recommend_upgrade=False)
66 66 branch = tree.branch
67 67 except (errors.NoWorkingTree, errors.NotLocalUrl):
68 68 tree = None
69 69 branch = dir.open_branch()
70 70 if (tree is not None and tree.bzrdir.root_transport.base !=
71 71 branch.bzrdir.root_transport.base):
72 72 self.ui.warn(_('warning: lightweight checkouts may cause '
73 73 'conversion failures, try with a regular '
74 74 'branch instead.\n'))
75 75 except:
76 76 self.ui.note(_('bzr source type could not be determined\n'))
77 77
78 78 def before(self):
79 79 """Before the conversion begins, acquire a read lock
80 80 for all the operations that might need it. Fortunately
81 81 read locks don't block other reads or writes to the
82 82 repository, so this shouldn't have any impact on the usage of
83 83 the source repository.
84 84
85 85 The alternative would be locking on every operation that
86 86 needs locks (there are currently two: getting the file and
87 87 getting the parent map) and releasing immediately after,
88 88 but this approach can take even 40% longer."""
89 89 self.sourcerepo.lock_read()
90 90
91 91 def after(self):
92 92 self.sourcerepo.unlock()
93 93
94 def _bzrbranches(self):
95 return self.sourcerepo.find_branches(using=True)
96
94 97 def getheads(self):
95 98 if not self.rev:
96 heads = sorted([b.last_revision()
97 for b in self.sourcerepo.find_branches()])
99 # Set using=True to avoid nested repositories (see issue3254)
100 heads = sorted([b.last_revision() for b in self._bzrbranches()])
98 101 else:
99 102 revid = None
100 for branch in self.sourcerepo.find_branches():
103 for branch in self._bzrbranches():
101 104 try:
102 105 r = RevisionSpec.from_string(self.rev)
103 106 info = r.in_history(branch)
104 107 except errors.BzrError:
105 108 pass
106 109 revid = info.rev_id
107 110 if revid is None:
108 111 raise util.Abort(_('%s is not a valid revision') % self.rev)
109 112 heads = [revid]
110 113 # Empty repositories return 'null:', which cannot be retrieved
111 114 heads = [h for h in heads if h != 'null:']
112 115 return heads
113 116
114 117 def getfile(self, name, rev):
115 118 revtree = self.sourcerepo.revision_tree(rev)
116 119 fileid = revtree.path2id(name.decode(self.encoding or 'utf-8'))
117 120 kind = None
118 121 if fileid is not None:
119 122 kind = revtree.kind(fileid)
120 123 if kind not in supportedkinds:
121 124 # the file is not available anymore - was deleted
122 125 raise IOError(_('%s is not available in %s anymore') %
123 126 (name, rev))
124 127 mode = self._modecache[(name, rev)]
125 128 if kind == 'symlink':
126 129 target = revtree.get_symlink_target(fileid)
127 130 if target is None:
128 131 raise util.Abort(_('%s.%s symlink has no target')
129 132 % (name, rev))
130 133 return target, mode
131 134 else:
132 135 sio = revtree.get_file(fileid)
133 136 return sio.read(), mode
134 137
135 138 def getchanges(self, version):
136 139 # set up caches: modecache and revtree
137 140 self._modecache = {}
138 141 self._revtree = self.sourcerepo.revision_tree(version)
139 142 # get the parentids from the cache
140 143 parentids = self._parentids.pop(version)
141 144 # only diff against first parent id
142 145 prevtree = self.sourcerepo.revision_tree(parentids[0])
143 146 return self._gettreechanges(self._revtree, prevtree)
144 147
145 148 def getcommit(self, version):
146 149 rev = self.sourcerepo.get_revision(version)
147 150 # populate parent id cache
148 151 if not rev.parent_ids:
149 152 parents = []
150 153 self._parentids[version] = (revision.NULL_REVISION,)
151 154 else:
152 155 parents = self._filterghosts(rev.parent_ids)
153 156 self._parentids[version] = parents
154 157
155 158 branch = self.recode(rev.properties.get('branch-nick', u'default'))
156 159 if branch == 'trunk':
157 160 branch = 'default'
158 161 return commit(parents=parents,
159 162 date='%d %d' % (rev.timestamp, -rev.timezone),
160 163 author=self.recode(rev.committer),
161 164 desc=self.recode(rev.message),
162 165 branch=branch,
163 166 rev=version)
164 167
165 168 def gettags(self):
166 169 bytetags = {}
167 for branch in self.sourcerepo.find_branches():
170 for branch in self._bzrbranches():
168 171 if not branch.supports_tags():
169 172 return {}
170 173 tagdict = branch.tags.get_tag_dict()
171 174 for name, rev in tagdict.iteritems():
172 175 bytetags[self.recode(name)] = rev
173 176 return bytetags
174 177
175 178 def getchangedfiles(self, rev, i):
176 179 self._modecache = {}
177 180 curtree = self.sourcerepo.revision_tree(rev)
178 181 if i is not None:
179 182 parentid = self._parentids[rev][i]
180 183 else:
181 184 # no parent id, get the empty revision
182 185 parentid = revision.NULL_REVISION
183 186
184 187 prevtree = self.sourcerepo.revision_tree(parentid)
185 188 changes = [e[0] for e in self._gettreechanges(curtree, prevtree)[0]]
186 189 return changes
187 190
188 191 def _gettreechanges(self, current, origin):
189 192 revid = current._revision_id
190 193 changes = []
191 194 renames = {}
192 195 seen = set()
193 196 # Process the entries by reverse lexicographic name order to
194 197 # handle nested renames correctly, most specific first.
195 198 curchanges = sorted(current.iter_changes(origin),
196 199 key=lambda c: c[1][0] or c[1][1],
197 200 reverse=True)
198 201 for (fileid, paths, changed_content, versioned, parent, name,
199 202 kind, executable) in curchanges:
200 203
201 204 if paths[0] == u'' or paths[1] == u'':
202 205 # ignore changes to tree root
203 206 continue
204 207
205 208 # bazaar tracks directories, mercurial does not, so
206 209 # we have to rename the directory contents
207 210 if kind[1] == 'directory':
208 211 if kind[0] not in (None, 'directory'):
209 212 # Replacing 'something' with a directory, record it
210 213 # so it can be removed.
211 214 changes.append((self.recode(paths[0]), revid))
212 215
213 216 if kind[0] == 'directory' and None not in paths:
214 217 renaming = paths[0] != paths[1]
215 218 # neither an add nor an delete - a move
216 219 # rename all directory contents manually
217 220 subdir = origin.inventory.path2id(paths[0])
218 221 # get all child-entries of the directory
219 222 for name, entry in origin.inventory.iter_entries(subdir):
220 223 # hg does not track directory renames
221 224 if entry.kind == 'directory':
222 225 continue
223 226 frompath = self.recode(paths[0] + '/' + name)
224 227 if frompath in seen:
225 228 # Already handled by a more specific change entry
226 229 # This is important when you have:
227 230 # a => b
228 231 # a/c => a/c
229 232 # Here a/c must not be renamed into b/c
230 233 continue
231 234 seen.add(frompath)
232 235 if not renaming:
233 236 continue
234 237 topath = self.recode(paths[1] + '/' + name)
235 238 # register the files as changed
236 239 changes.append((frompath, revid))
237 240 changes.append((topath, revid))
238 241 # add to mode cache
239 242 mode = ((entry.executable and 'x')
240 243 or (entry.kind == 'symlink' and 's')
241 244 or '')
242 245 self._modecache[(topath, revid)] = mode
243 246 # register the change as move
244 247 renames[topath] = frompath
245 248
246 249 # no futher changes, go to the next change
247 250 continue
248 251
249 252 # we got unicode paths, need to convert them
250 253 path, topath = paths
251 254 if path is not None:
252 255 path = self.recode(path)
253 256 if topath is not None:
254 257 topath = self.recode(topath)
255 258 seen.add(path or topath)
256 259
257 260 if topath is None:
258 261 # file deleted
259 262 changes.append((path, revid))
260 263 continue
261 264
262 265 # renamed
263 266 if path and path != topath:
264 267 renames[topath] = path
265 268 changes.append((path, revid))
266 269
267 270 # populate the mode cache
268 271 kind, executable = [e[1] for e in (kind, executable)]
269 272 mode = ((executable and 'x') or (kind == 'symlink' and 'l')
270 273 or '')
271 274 self._modecache[(topath, revid)] = mode
272 275 changes.append((topath, revid))
273 276
274 277 return changes, renames
275 278
276 279 def _filterghosts(self, ids):
277 280 """Filters out ghost revisions which hg does not support, see
278 281 <http://bazaar-vcs.org/GhostRevision>
279 282 """
280 283 parentmap = self.sourcerepo.get_parent_map(ids)
281 284 parents = tuple([parent for parent in ids if parent in parentmap])
282 285 return parents
@@ -1,263 +1,284
1 1 $ "$TESTDIR/hghave" symlink execbit || exit 80
2 2
3 3 $ . "$TESTDIR/bzr-definitions"
4 4
5 5 create and rename on the same file in the same step
6 6
7 7 $ mkdir test-createandrename
8 8 $ cd test-createandrename
9 9 $ bzr init -q source
10 10
11 11 test empty repo conversion (issue3233)
12 12
13 13 $ hg convert source source-hg
14 14 initializing destination source-hg repository
15 15 scanning source...
16 16 sorting...
17 17 converting...
18 18
19 19 back to the rename stuff
20 20
21 21 $ cd source
22 22 $ echo a > a
23 23 $ echo c > c
24 24 $ echo e > e
25 25 $ bzr add -q a c e
26 26 $ bzr commit -q -m 'Initial add: a, c, e'
27 27 $ bzr mv a b
28 28 a => b
29 29 $ bzr mv c d
30 30 c => d
31 31 $ bzr mv e f
32 32 e => f
33 33 $ echo a2 >> a
34 34 $ mkdir e
35 35 $ bzr add -q a e
36 36 $ bzr commit -q -m 'rename a into b, create a, rename c into d'
37 37 $ cd ..
38 38 $ hg convert source source-hg
39 39 scanning source...
40 40 sorting...
41 41 converting...
42 42 1 Initial add: a, c, e
43 43 0 rename a into b, create a, rename c into d
44 44 $ glog -R source-hg
45 45 o 1@source "rename a into b, create a, rename c into d" files: a b c d e f
46 46 |
47 47 o 0@source "Initial add: a, c, e" files: a c e
48 48
49 49
50 50 manifest
51 51
52 52 $ hg manifest -R source-hg -r tip
53 53 a
54 54 b
55 55 d
56 56 f
57 57
58 58 test --rev option
59 59
60 60 $ hg convert -r 1 source source-1-hg
61 61 initializing destination source-1-hg repository
62 62 scanning source...
63 63 sorting...
64 64 converting...
65 65 0 Initial add: a, c, e
66 66 $ glog -R source-1-hg
67 67 o 0@source "Initial add: a, c, e" files: a c e
68 68
69 69
70 70 test with filemap
71 71
72 72 $ cat > filemap <<EOF
73 73 > exclude a
74 74 > EOF
75 75 $ hg convert --filemap filemap source source-filemap-hg
76 76 initializing destination source-filemap-hg repository
77 77 scanning source...
78 78 sorting...
79 79 converting...
80 80 1 Initial add: a, c, e
81 81 0 rename a into b, create a, rename c into d
82 82 $ hg -R source-filemap-hg manifest -r tip
83 83 b
84 84 d
85 85 f
86 86
87 87 convert from lightweight checkout
88 88
89 89 $ bzr checkout --lightweight source source-light
90 90 $ hg convert -s bzr source-light source-light-hg
91 91 initializing destination source-light-hg repository
92 92 warning: lightweight checkouts may cause conversion failures, try with a regular branch instead.
93 93 $TESTTMP/test-createandrename/source-light does not look like a Bazaar repository
94 94 abort: source-light: missing or unsupported repository
95 95 [255]
96 96
97 97 extract timestamps that look just like hg's {date|isodate}:
98 98 yyyy-mm-dd HH:MM zzzz (no seconds!)
99 99 compare timestamps
100 100
101 101 $ cd source
102 102 $ bzr log | \
103 103 > sed '/timestamp/!d;s/.\{15\}\([0-9: -]\{16\}\):.. \(.[0-9]\{4\}\)/\1 \2/' \
104 104 > > ../bzr-timestamps
105 105 $ cd ..
106 106 $ hg -R source-hg log --template "{date|isodate}\n" > hg-timestamps
107 107 $ diff -u bzr-timestamps hg-timestamps
108 108 $ cd ..
109 109
110 110 merge
111 111
112 112 $ mkdir test-merge
113 113 $ cd test-merge
114 114 $ cat > helper.py <<EOF
115 115 > import sys
116 116 > from bzrlib import workingtree
117 117 > wt = workingtree.WorkingTree.open('.')
118 118 >
119 119 > message, stamp = sys.argv[1:]
120 120 > wt.commit(message, timestamp=int(stamp))
121 121 > EOF
122 122 $ bzr init -q source
123 123 $ cd source
124 124 $ echo content > a
125 125 $ echo content2 > b
126 126 $ bzr add -q a b
127 127 $ bzr commit -q -m 'Initial add'
128 128 $ cd ..
129 129 $ bzr branch -q source source-improve
130 130 $ cd source
131 131 $ echo more >> a
132 132 $ python ../helper.py 'Editing a' 100
133 133 $ cd ../source-improve
134 134 $ echo content3 >> b
135 135 $ python ../helper.py 'Editing b' 200
136 136 $ cd ../source
137 137 $ bzr merge -q ../source-improve
138 138 $ bzr commit -q -m 'Merged improve branch'
139 139 $ cd ..
140 140 $ hg convert --datesort source source-hg
141 141 initializing destination source-hg repository
142 142 scanning source...
143 143 sorting...
144 144 converting...
145 145 3 Initial add
146 146 2 Editing a
147 147 1 Editing b
148 148 0 Merged improve branch
149 149 $ glog -R source-hg
150 150 o 3@source "Merged improve branch" files:
151 151 |\
152 152 | o 2@source-improve "Editing b" files: b
153 153 | |
154 154 o | 1@source "Editing a" files: a
155 155 |/
156 156 o 0@source "Initial add" files: a b
157 157
158 158 $ cd ..
159 159
160 160 symlinks and executable files
161 161
162 162 $ mkdir test-symlinks
163 163 $ cd test-symlinks
164 164 $ bzr init -q source
165 165 $ cd source
166 166 $ touch program
167 167 $ chmod +x program
168 168 $ ln -s program altname
169 169 $ mkdir d
170 170 $ echo a > d/a
171 171 $ ln -s a syma
172 172 $ bzr add -q altname program syma d/a
173 173 $ bzr commit -q -m 'Initial setup'
174 174 $ touch newprog
175 175 $ chmod +x newprog
176 176 $ rm altname
177 177 $ ln -s newprog altname
178 178 $ chmod -x program
179 179 $ bzr add -q newprog
180 180 $ bzr commit -q -m 'Symlink changed, x bits changed'
181 181 $ cd ..
182 182 $ hg convert source source-hg
183 183 initializing destination source-hg repository
184 184 scanning source...
185 185 sorting...
186 186 converting...
187 187 1 Initial setup
188 188 0 Symlink changed, x bits changed
189 189 $ manifest source-hg 0
190 190 % manifest of 0
191 191 644 @ altname
192 192 644 d/a
193 193 755 * program
194 194 644 @ syma
195 195 $ manifest source-hg tip
196 196 % manifest of tip
197 197 644 @ altname
198 198 644 d/a
199 199 755 * newprog
200 200 644 program
201 201 644 @ syma
202 202 $ cd source-hg
203 203
204 204 test the symlinks can be recreated
205 205
206 206 $ hg up
207 207 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 208 $ hg cat syma; echo
209 209 a
210 210
211 211 Multiple branches
212 212
213 213 $ bzr init-repo -q --no-trees repo
214 214 $ bzr init -q repo/trunk
215 215 $ bzr co repo/trunk repo-trunk
216 216 $ cd repo-trunk
217 217 $ echo a > a
218 218 $ bzr add a
219 219 adding a
220 220 $ bzr ci -qm adda --commit-time '2012-01-01 00:00:01 +0000'
221 221 $ bzr tag trunk-tag
222 222 Created tag trunk-tag.
223 223 $ bzr switch -b branch
224 224 Tree is up to date at revision 1.
225 225 Switched to branch: *repo/branch/ (glob)
226 226 $ echo b > b
227 227 $ bzr add b
228 228 adding b
229 229 $ bzr ci -qm addb --commit-time '2012-01-01 00:00:02 +0000'
230 230 $ bzr tag branch-tag
231 231 Created tag branch-tag.
232 232 $ bzr switch --force ../repo/trunk
233 233 Updated to revision 1.
234 234 Switched to branch: */repo/trunk/ (glob)
235 235 $ echo a >> a
236 236 $ bzr ci -qm changea --commit-time '2012-01-01 00:00:03 +0000'
237 237 $ cd ..
238 238 $ hg convert --datesort repo repo-bzr
239 239 initializing destination repo-bzr repository
240 240 scanning source...
241 241 sorting...
242 242 converting...
243 243 2 adda
244 244 1 addb
245 245 0 changea
246 246 updating tags
247 247 $ (cd repo-bzr; glog)
248 248 o 3@default "update tags" files: .hgtags
249 249 |
250 250 o 2@default "changea" files: a
251 251 |
252 252 | o 1@branch "addb" files: b
253 253 |/
254 254 o 0@default "adda" files: a
255 255
256 256
257 257 Test tags (converted identifiers are not stable because bzr ones are
258 258 not and get incorporated in extra fields).
259 259
260 260 $ hg -R repo-bzr tags
261 261 tip 3:* (glob)
262 262 branch-tag 1:* (glob)
263 263 trunk-tag 0:* (glob)
264
265 Nested repositories (issue3254)
266
267 $ bzr init-repo -q --no-trees repo/inner
268 $ bzr init -q repo/inner/trunk
269 $ bzr co repo/inner/trunk inner-trunk
270 $ cd inner-trunk
271 $ echo b > b
272 $ bzr add b
273 adding b
274 $ bzr ci -qm addb
275 $ cd ..
276 $ hg convert --datesort repo noinner-bzr
277 initializing destination noinner-bzr repository
278 scanning source...
279 sorting...
280 converting...
281 2 adda
282 1 addb
283 0 changea
284 updating tags
General Comments 0
You need to be logged in to leave comments. Login now