1 # Copyright (C) 2005-2007 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.errors import InvalidRevisionId, NotBranchError
17 from bzrlib.inventory import ROOT_ID
18 from bzrlib.plugins.svn.layout import RepositoryLayout
19 from bzrlib.plugins.svn.mapping import BzrSvnMapping, escape_svn_path, unescape_svn_path, parse_svn_revprops
21 SVN_PROP_BZR_MERGE = 'bzr:merge'
23 class BzrSvnMappingv1(BzrSvnMapping):
24 """This was the initial version of the mappings as used by bzr-svn
27 It does not support pushing revisions to Subversion as-is, but only
33 def __init__(self, layout):
34 super(BzrSvnMappingv1, self).__init__()
38 def revision_id_bzr_to_foreign(cls, revid):
39 if not revid.startswith("svn-v1:"):
40 raise InvalidRevisionId(revid, "")
41 revid = revid[len("svn-v1:"):]
43 fash = revid.rindex("-")
44 uuid = revid[at+1:fash]
45 branch_path = unescape_svn_path(revid[fash+1:])
46 revnum = int(revid[0:at])
48 return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
51 def revision_id_foreign_to_bzr(cls, (uuid, revnum, path)):
52 assert isinstance(path, str)
53 return "svn-v1:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
55 def __eq__(self, other):
56 return type(self) == type(other)
58 def is_branch(self, branch_path):
59 return self._layout.is_branch(branch_path)
61 def is_tag(self, tag_path):
64 def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
65 parse_svn_revprops(svn_revprops, rev)
67 def generate_file_id(self, uuid, revnum, branch, inv_path):
70 return "%s-%s" % (self.revision_id_foreign_to_bzr((uuid, revnum, branch)), escape_svn_path(inv_path.encode("utf-8")))
72 def import_fileid_map(self, revprops, fileprops):
75 def import_text_parents(self, revprops, fileprops):
78 def get_rhs_parents(self, branch_path, revprops, fileprops):
79 value = fileprops.get(SVN_PROP_BZR_MERGE, "")
82 return (value.splitlines()[-1])
85 def from_repository(cls, repository, _hinted_branch_path=None):
86 if _hinted_branch_path is None:
87 return cls(TrunkLegacyLayout())
89 return cls(LegacyLayout.from_branch_path(_hinted_branch_path))
92 def get_test_instance(cls):
93 return cls(TrunkLegacyLayout())
95 def get_guessed_layout(self, repository):
99 class BzrSvnMappingv2(BzrSvnMappingv1):
100 """The second version of the mappings as used in the 0.3.x series.
102 It does not support pushing revisions to Subversion as-is, but only
106 roundtripping = False
109 def revision_id_bzr_to_foreign(cls, revid):
110 if not revid.startswith("svn-v2:"):
111 raise InvalidRevisionId(revid, "")
112 revid = revid[len("svn-v2:"):]
113 at = revid.index("@")
114 fash = revid.rindex("-")
115 uuid = revid[at+1:fash]
116 branch_path = unescape_svn_path(revid[fash+1:])
117 revnum = int(revid[0:at])
119 return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
121 def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
122 return "svn-v2:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
124 def __eq__(self, other):
125 return type(self) == type(other)
128 class LegacyLayout(RepositoryLayout):
130 def get_tag_path(self, name, project=""):
133 def get_branch_path(self, name, project=""):
137 def from_branch_path(cls, path):
138 parts = path.strip("/").split("/")
139 for i in range(0,len(parts)):
140 if parts[i] == "trunk" or \
141 parts[i] == "branches" or \
143 return TrunkLegacyLayout(level=i)
145 return RootLegacyLayout()
148 class TrunkLegacyLayout(LegacyLayout):
150 def __init__(self, level=0):
151 super(TrunkLegacyLayout, self).__init__()
154 def parse(self, path):
155 parts = path.strip("/").split("/")
156 if len(parts) == 0 or self.level >= len(parts):
157 raise NotBranchError(path=path)
159 if parts[self.level] == "trunk" or parts[self.level] == "hooks":
160 return ("branch", "/".join(parts[0:self.level]), "/".join(parts[0:self.level+1]).strip("/"),
161 "/".join(parts[self.level+1:]).strip("/"))
162 elif ((parts[self.level] == "tags" or parts[self.level] == "branches") and
163 len(parts) >= self.level+2):
164 return ("branch", "/".join(parts[0:self.level]), "/".join(parts[0:self.level+2]).strip("/"),
165 "/".join(parts[self.level+2:]).strip("/"))
167 raise NotBranchError(path=path)
169 def is_branch(self, path, project=None):
170 parts = path.strip("/").split("/")
171 if len(parts) == self.level+1 and parts[self.level] == "trunk":
174 if len(parts) == self.level+2 and \
175 (parts[self.level] == "branches" or parts[self.level] == "tags"):
181 class RootLegacyLayout(LegacyLayout):
183 def parse(self, path):
184 return ("branch", "", "", path)
186 def is_branch(self, path, project=None):