1 # Copyright (C) 2005-2008 Jelmer Vernooij <jelmer@samba.org>
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 from bzrlib import osutils, ui
17 from bzrlib.errors import InvalidRevisionId
18 from bzrlib.trace import mutter
20 from bzrlib.plugins.svn import mapping, properties
21 from bzrlib.plugins.svn.core import SubversionException, NODE_DIR
22 from bzrlib.plugins.svn.errors import ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND
23 from bzrlib.plugins.svn.layout import RepositoryLayout
24 from bzrlib.plugins.svn.mapping3.scheme import (BranchingScheme, guess_scheme_from_branch_path,
25 guess_scheme_from_history, ListBranchingScheme,
26 parse_list_scheme_text, NoBranchingScheme,
27 TrunkBranchingScheme, ListBranchingScheme)
30 SVN_PROP_BZR_BRANCHING_SCHEME = 'bzr:branching-scheme'
32 # Number of revisions to evaluate when guessing the branching scheme
33 SCHEME_GUESS_SAMPLE_SIZE = 2000
35 def expand_branch_pattern(begin, todo, check_path, get_children):
36 path = "/".join(begin)
42 if not "*" in todo[0]:
43 return expand_branch_pattern(begin+[todo[0]], todo[1:], check_path, get_children)
44 children = get_children(path)
50 ret.append("/".join(begin+[c]))
52 ret += expand_branch_pattern(begin+[c], todo[1:], check_path, get_children)
56 class SchemeDerivedLayout(RepositoryLayout):
57 def __init__(self, repository, scheme):
58 self.repository = repository
61 def parse(self, path):
62 (bp, rp) = self.scheme.unprefix(path)
63 if self.scheme.is_tag(bp):
67 return (type, "", bp, rp)
69 def get_branches(self, revnum, project=""):
71 return self.repository.transport.check_path(path, revnum) == NODE_DIR
72 def find_children(path):
74 assert not path.startswith("/")
75 dirents = self.repository.transport.get_dir(path, revnum)[0]
76 except SubversionException, (msg, num):
77 if num in (ERR_FS_NOT_DIRECTORY, ERR_FS_NOT_FOUND, ERR_RA_DAV_PATH_NOT_FOUND):
82 for pattern in self.scheme.branch_list:
83 for bp in expand_branch_pattern([], pattern.split("/"), check_path,
85 yield "", bp, bp.split("/")[-1]
87 def is_branch_parent(self, path):
89 return self.scheme.is_branch_parent(path)
91 def is_tag_parent(self, path):
93 return self.scheme.is_tag_parent(path)
96 def get_stored_scheme(repository):
97 """Retrieve the stored branching scheme, either in the repository
98 or in the configuration file.
100 scheme = repository.get_config().get_branching_scheme()
101 if scheme is not None:
102 return (scheme, repository.get_config().branching_scheme_is_mandatory())
104 last_revnum = repository.get_latest_revnum()
105 scheme = get_property_scheme(repository, last_revnum)
106 if scheme is not None:
107 return (scheme, True)
112 def get_property_scheme(repository, revnum=None):
114 revnum = repository.get_latest_revnum()
115 text = repository.branchprop_list.get_properties("", revnum).get(SVN_PROP_BZR_BRANCHING_SCHEME, None)
118 return ListBranchingScheme(parse_list_scheme_text(text))
121 def set_property_scheme(repository, scheme):
122 editor = repository.transport.get_commit_editor(
123 {properties.PROP_REVISION_LOG: "Updating branching scheme for Bazaar."})
124 root = editor.open_root()
125 root.change_prop(SVN_PROP_BZR_BRANCHING_SCHEME,
126 "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
131 def repository_guess_scheme(repository, last_revnum, branch_path=None):
132 pb = ui.ui_factory.nested_progress_bar()
134 scheme = guess_scheme_from_history(
135 repository._log.iter_changes(None, last_revnum, max(0, last_revnum-SCHEME_GUESS_SAMPLE_SIZE), pb=pb), last_revnum, branch_path)
138 mutter("Guessed branching scheme: %r" % scheme)
142 def config_set_scheme(repository, scheme, mandatory=False):
143 repository.get_config().set_branching_scheme(str(scheme),
146 def set_branching_scheme(repository, scheme, mandatory=False):
147 repository.get_mapping().scheme = scheme
148 config_set_scheme(repository, scheme, mandatory)
151 class BzrSvnMappingv3(mapping.BzrSvnMapping):
152 """The third version of the mappings as used in the 0.4.x series.
156 upgrade_suffix = "-svn3"
157 revid_prefix = "svn-v3-"
159 def __init__(self, scheme):
160 mapping.BzrSvnMapping.__init__(self)
162 assert not isinstance(scheme, str)
164 def get_mandated_layout(self, repository):
165 return SchemeDerivedLayout(repository, self.scheme)
168 def from_repository(cls, repository, _hinted_branch_path=None):
169 (scheme, mandatory) = get_stored_scheme(repository)
173 if scheme is not None:
174 if (_hinted_branch_path is None or
175 scheme.is_branch(_hinted_branch_path)):
178 last_revnum = repository.get_latest_revnum()
179 scheme = repository_guess_scheme(repository, last_revnum, _hinted_branch_path)
181 config_set_scheme(repository, scheme, mandatory=False)
186 return "%s(%r)" % (self.__class__.__name__, self.scheme)
188 def generate_file_id(self, uuid, revnum, branch, inv_path):
189 assert isinstance(uuid, str)
190 assert isinstance(revnum, int)
191 assert isinstance(branch, str)
192 assert isinstance(inv_path, unicode)
193 inv_path = inv_path.encode("utf-8")
194 ret = "%d@%s:%s:%s" % (revnum, uuid, mapping.escape_svn_path(branch),
195 mapping.escape_svn_path(inv_path))
197 ret = "%d@%s:%s;%s" % (revnum, uuid,
198 mapping.escape_svn_path(branch),
199 sha.new(inv_path).hexdigest())
200 assert isinstance(ret, str)
201 return osutils.safe_file_id(ret)
204 def supports_roundtripping():
208 def _parse_revision_id(cls, revid):
209 assert isinstance(revid, str)
211 if not revid.startswith(cls.revid_prefix):
212 raise InvalidRevisionId(revid, "")
215 (version, uuid, branch_path, srevnum) = revid.split(":")
217 raise InvalidRevisionId(revid, "")
219 scheme = version[len(cls.revid_prefix):]
221 branch_path = mapping.unescape_svn_path(branch_path)
223 return (uuid, branch_path, int(srevnum), scheme)
226 def parse_revision_id(cls, revid):
227 (uuid, branch_path, srevnum, scheme) = cls._parse_revision_id(revid)
228 # Some older versions of bzr-svn 0.4 did not always set a branching
229 # scheme but set "undefined" instead.
230 if scheme == "undefined":
231 scheme = guess_scheme_from_branch_path(branch_path)
233 scheme = BranchingScheme.find_scheme(scheme)
235 return (uuid, branch_path, srevnum, cls(scheme))
237 def is_branch(self, branch_path):
238 return (self.scheme.is_branch(branch_path) or
239 self.scheme.is_tag(branch_path))
241 def is_tag(self, tag_path):
242 return self.scheme.is_tag(tag_path)
245 def _generate_revision_id(cls, uuid, revnum, path, scheme):
246 assert isinstance(revnum, int)
247 assert isinstance(path, str)
249 assert revnum > 0 or path == "", \
250 "Trying to generate revid for (%r,%r)" % (path, revnum)
251 return "%s%s:%s:%s:%d" % (cls.revid_prefix, scheme, uuid, \
252 mapping.escape_svn_path(path.strip("/")), revnum)
254 def generate_revision_id(self, uuid, revnum, path):
255 return self._generate_revision_id(uuid, revnum, path, self.scheme)
257 def unprefix(self, branch_path, repos_path):
258 (bp, np) = self.scheme.unprefix(repos_path)
259 assert branch_path == bp
262 def __eq__(self, other):
263 return type(self) == type(other) and self.scheme == other.scheme
265 class BzrSvnMappingv3FileProps(mapping.BzrSvnMappingFileProps, BzrSvnMappingv3):
269 class BzrSvnMappingv3RevProps(mapping.BzrSvnMappingRevProps, BzrSvnMappingv3):
273 class BzrSvnMappingv3Hybrid(BzrSvnMappingv3):
274 def __init__(self, scheme):
275 BzrSvnMappingv3.__init__(self, scheme)
276 self.revprops = BzrSvnMappingv3RevProps(scheme)
277 self.fileprops = BzrSvnMappingv3FileProps(scheme)
279 def get_rhs_parents(self, branch_path, svn_revprops, fileprops):
280 if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
281 return self.revprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
283 return self.fileprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
285 def get_revision_id(self, branch_path, revprops, fileprops):
286 if revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
287 return self.revprops.get_revision_id(branch_path, revprops, fileprops)
289 return self.fileprops.get_revision_id(branch_path, revprops, fileprops)
291 def import_fileid_map(self, svn_revprops, fileprops):
292 if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
293 return self.revprops.import_fileid_map(svn_revprops, fileprops)
295 return self.fileprops.import_fileid_map(svn_revprops, fileprops)
297 def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id,
298 revno, merges, fileprops):
299 (_, fileprops) = self.fileprops.export_revision(branch_root, timestamp, timezone, committer,
300 revprops, revision_id, revno, merges, fileprops)
301 (revprops, _) = self.revprops.export_revision(branch_root, timestamp, timezone, committer,
302 revprops, revision_id, revno, merges, fileprops)
303 return (revprops, fileprops)
305 def export_fileid_map(self, fileids, revprops, fileprops):
306 self.fileprops.export_fileid_map(fileids, revprops, fileprops)
307 self.revprops.export_fileid_map(fileids, revprops, fileprops)
309 def import_revision(self, svn_revprops, fileprops, rev):
310 self.fileprops.import_revision(svn_revprops, fileprops, rev)
311 self.revprops.import_revision(svn_revprops, fileprops, rev)