Implement CustomLayout, more layout fixes.
[jelmer/subvertpy.git] / mapping2.py
1 # Copyright (C) 2005-2007 Jelmer Vernooij <jelmer@samba.org>
2  
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.
7
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.
12
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/>.
15
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
20
21 SVN_PROP_BZR_MERGE = 'bzr:merge'
22
23 class BzrSvnMappingv1(BzrSvnMapping):
24     """This was the initial version of the mappings as used by bzr-svn
25     0.2.
26     
27     It does not support pushing revisions to Subversion as-is, but only 
28     as part of a merge.
29     """
30     name = "v1"
31     roundtripping = False
32
33     def __init__(self, layout):
34         super(BzrSvnMappingv1, self).__init__()
35         self._layout = layout
36
37     @classmethod
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:"):]
42         at = revid.index("@")
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])
47         assert revnum >= 0
48         return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
49
50     @classmethod
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))
54
55     def __eq__(self, other):
56         return type(self) == type(other)
57
58     def is_branch(self, branch_path):
59         return self._layout.is_branch(branch_path)
60
61     def is_tag(self, tag_path):
62         return False
63
64     def import_revision(self, svn_revprops, fileprops, uuid, branch, revnum, rev):
65         parse_svn_revprops(svn_revprops, rev)
66
67     def generate_file_id(self, uuid, revnum, branch, inv_path):
68         if inv_path == u"":
69             return ROOT_ID
70         return "%s-%s" % (self.revision_id_foreign_to_bzr((uuid, revnum, branch)), escape_svn_path(inv_path.encode("utf-8")))
71
72     def import_fileid_map(self, revprops, fileprops):
73         return {}
74
75     def import_text_parents(self, revprops, fileprops):
76         return {}
77
78     def get_rhs_parents(self, branch_path, revprops, fileprops):
79         value = fileprops.get(SVN_PROP_BZR_MERGE, "")
80         if value == "":
81             return ()
82         return (value.splitlines()[-1])
83
84     @classmethod
85     def from_repository(cls, repository, _hinted_branch_path=None):
86         if _hinted_branch_path is None:
87             return cls(TrunkLegacyLayout())
88     
89         return cls(LegacyLayout.from_branch_path(_hinted_branch_path))
90
91     @classmethod
92     def get_test_instance(cls):
93         return cls(TrunkLegacyLayout())
94
95     def get_guessed_layout(self, repository):
96         return self._layout
97
98
99 class BzrSvnMappingv2(BzrSvnMappingv1):
100     """The second version of the mappings as used in the 0.3.x series.
101
102     It does not support pushing revisions to Subversion as-is, but only 
103     as part of a merge.
104     """
105     name = "v2"
106     roundtripping = False
107
108     @classmethod
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])
118         assert revnum >= 0
119         return (uuid, branch_path, revnum, cls(LegacyLayout.from_branch_path(branch_path)))
120
121     def revision_id_foreign_to_bzr(self, (uuid, revnum, path)):
122         return "svn-v2:%d@%s-%s" % (revnum, uuid, escape_svn_path(path))
123
124     def __eq__(self, other):
125         return type(self) == type(other)
126
127
128 class LegacyLayout(RepositoryLayout):
129
130     def get_tag_path(self, name, project=""):
131         return None
132
133     def get_branch_path(self, name, project=""):
134         return None
135
136     @classmethod
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 \
142                parts[i] == "tags":
143                 return TrunkLegacyLayout(level=i)
144
145         return RootLegacyLayout()
146
147
148 class TrunkLegacyLayout(LegacyLayout):
149
150     def __init__(self, level=0):
151         super(TrunkLegacyLayout, self).__init__()
152         self.level = level
153     
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)
158
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("/"))
166         else:
167             raise NotBranchError(path=path)
168
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":
172             return True
173
174         if len(parts) == self.level+2 and \
175            (parts[self.level] == "branches" or parts[self.level] == "tags"):
176             return True
177
178         return False
179
180
181 class RootLegacyLayout(LegacyLayout):
182
183     def parse(self, path):
184         return ("branch", "", "", path)
185
186     def is_branch(self, path, project=None):
187         return path == ""