Merge 0.4.
[jelmer/subvertpy.git] / mapping3 / __init__.py
1 # Copyright (C) 2005-2008 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 import osutils, ui
17 from bzrlib.errors import InvalidRevisionId
18 from bzrlib.trace import mutter
19
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)
28 import sha
29
30 SVN_PROP_BZR_BRANCHING_SCHEME = 'bzr:branching-scheme'
31
32 # Number of revisions to evaluate when guessing the branching scheme
33 SCHEME_GUESS_SAMPLE_SIZE = 2000
34
35 def expand_branch_pattern(begin, todo, check_path, get_children):
36     path = "/".join(begin)
37     if len(todo) == 0:
38         if check_path(path):
39             return [path]
40         else:
41             return []
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)
45     if children is None:
46         return []
47     ret = []
48     for c in children:
49         if len(todo) == 1:
50             ret.append("/".join(begin+[c]))
51         else:
52             ret += expand_branch_pattern(begin+[c], todo[1:], check_path, get_children)
53     return ret
54
55
56 class SchemeDerivedLayout(RepositoryLayout):
57     def __init__(self, repository, scheme):
58         self.repository = repository
59         self.scheme = scheme
60
61     def parse(self, path):
62         (bp, rp) = self.scheme.unprefix(path)
63         if self.scheme.is_tag(bp):
64             type = "tag"
65         else:
66             type = "branch"
67         return (type, "", bp, rp)
68
69     def get_branches(self, revnum, project=""):
70         def check_path(path):
71             return self.repository.transport.check_path(path, revnum) == NODE_DIR
72         def find_children(path):
73             try:
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):
78                     return None
79                 raise
80             return dirents.keys()
81
82         for pattern in self.scheme.branch_list:
83             for bp in expand_branch_pattern([], pattern.split("/"), check_path,
84                     find_children):
85                 yield "", bp, bp.split("/")[-1]
86
87     def is_branch_parent(self, path):
88         # Na, na, na...
89         return self.scheme.is_branch_parent(path)
90
91     def is_tag_parent(self, path):
92         # Na, na, na...
93         return self.scheme.is_tag_parent(path)
94
95
96 def get_stored_scheme(repository):
97     """Retrieve the stored branching scheme, either in the repository 
98     or in the configuration file.
99     """
100     scheme = repository.get_config().get_branching_scheme()
101     if scheme is not None:
102         return (scheme, repository.get_config().branching_scheme_is_mandatory())
103
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)
108
109     return (None, False)
110
111
112 def get_property_scheme(repository, revnum=None):
113     if revnum is None:
114         revnum = repository.get_latest_revnum()
115     text = repository.branchprop_list.get_properties("", revnum).get(SVN_PROP_BZR_BRANCHING_SCHEME, None)
116     if text is None:
117         return None
118     return ListBranchingScheme(parse_list_scheme_text(text))
119
120
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             done, None, False)
125     root = editor.open_root()
126     root.change_prop(SVN_PROP_BZR_BRANCHING_SCHEME, 
127             "".join(map(lambda x: x+"\n", scheme.branch_list)).encode("utf-8"))
128     root.close()
129     editor.close()
130
131
132 def repository_guess_scheme(repository, last_revnum, branch_path=None):
133     pb = ui.ui_factory.nested_progress_bar()
134     try:
135         scheme = guess_scheme_from_history(
136             repository._log.iter_changes(None, last_revnum, max(0, last_revnum-SCHEME_GUESS_SAMPLE_SIZE), pb=pb), last_revnum, branch_path)
137     finally:
138         pb.finished()
139     mutter("Guessed branching scheme: %r" % scheme)
140     return scheme
141
142
143 def config_set_scheme(repository, scheme, mandatory=False):
144     repository.get_config().set_branching_scheme(str(scheme), 
145                                                  mandatory=mandatory)
146
147 def set_branching_scheme(repository, scheme, mandatory=False):
148     repository.get_mapping().scheme = scheme
149     config_set_scheme(repository, scheme, mandatory)
150
151
152 class BzrSvnMappingv3(mapping.BzrSvnMapping):
153     """The third version of the mappings as used in the 0.4.x series.
154
155     """
156     experimental = False
157     upgrade_suffix = "-svn3"
158     revid_prefix = "svn-v3-"
159
160     def __init__(self, scheme):
161         mapping.BzrSvnMapping.__init__(self)
162         self.scheme = scheme
163         assert not isinstance(scheme, str)
164
165     def get_mandated_layout(self, repository):
166         return SchemeDerivedLayout(repository, self.scheme)
167
168     @classmethod
169     def from_repository(cls, repository, _hinted_branch_path=None):
170         (scheme, mandatory) = get_stored_scheme(repository)
171         if mandatory:
172             return cls(scheme) 
173
174         if scheme is not None:
175             if (_hinted_branch_path is None or 
176                 scheme.is_branch(_hinted_branch_path)):
177                 return cls(scheme)
178
179         last_revnum = repository.get_latest_revnum()
180         scheme = repository_guess_scheme(repository, last_revnum, _hinted_branch_path)
181         if last_revnum > 20:
182             config_set_scheme(repository, scheme, mandatory=False)
183
184         return cls(scheme)
185
186     def __repr__(self):
187         return "%s(%r)" % (self.__class__.__name__, self.scheme)
188
189     def generate_file_id(self, uuid, revnum, branch, inv_path):
190         assert isinstance(uuid, str)
191         assert isinstance(revnum, int)
192         assert isinstance(branch, str)
193         assert isinstance(inv_path, unicode)
194         inv_path = inv_path.encode("utf-8")
195         ret = "%d@%s:%s:%s" % (revnum, uuid, mapping.escape_svn_path(branch), 
196                                mapping.escape_svn_path(inv_path))
197         if len(ret) > 150:
198             ret = "%d@%s:%s;%s" % (revnum, uuid, 
199                                 mapping.escape_svn_path(branch),
200                                 sha.new(inv_path).hexdigest())
201         assert isinstance(ret, str)
202         return osutils.safe_file_id(ret)
203
204     @staticmethod
205     def supports_roundtripping():
206         return True
207
208     @classmethod
209     def _parse_revision_id(cls, revid):
210         assert isinstance(revid, str)
211
212         if not revid.startswith(cls.revid_prefix):
213             raise InvalidRevisionId(revid, "")
214
215         try:
216             (version, uuid, branch_path, srevnum) = revid.split(":")
217         except ValueError:
218             raise InvalidRevisionId(revid, "")
219
220         scheme = version[len(cls.revid_prefix):]
221
222         branch_path = mapping.unescape_svn_path(branch_path)
223
224         return (uuid, branch_path, int(srevnum), scheme)
225
226     @classmethod
227     def parse_revision_id(cls, revid):
228         (uuid, branch_path, srevnum, scheme) = cls._parse_revision_id(revid)
229         # Some older versions of bzr-svn 0.4 did not always set a branching
230         # scheme but set "undefined" instead.
231         if scheme == "undefined":
232             scheme = guess_scheme_from_branch_path(branch_path)
233         else:
234             scheme = BranchingScheme.find_scheme(scheme)
235
236         return (uuid, branch_path, srevnum, cls(scheme))
237
238     def is_branch(self, branch_path):
239         return (self.scheme.is_branch(branch_path) or 
240                 self.scheme.is_tag(branch_path))
241
242     def is_tag(self, tag_path):
243         return self.scheme.is_tag(tag_path)
244
245     @classmethod
246     def _generate_revision_id(cls, uuid, revnum, path, scheme):
247         assert isinstance(revnum, int)
248         assert isinstance(path, str)
249         assert revnum >= 0
250         assert revnum > 0 or path == "", \
251                 "Trying to generate revid for (%r,%r)" % (path, revnum)
252         return "%s%s:%s:%s:%d" % (cls.revid_prefix, scheme, uuid, \
253                        mapping.escape_svn_path(path.strip("/")), revnum)
254
255     def generate_revision_id(self, uuid, revnum, path):
256         return self._generate_revision_id(uuid, revnum, path, self.scheme)
257
258     def unprefix(self, branch_path, repos_path):
259         (bp, np) = self.scheme.unprefix(repos_path)
260         assert branch_path == bp
261         return np
262
263     def __eq__(self, other):
264         return type(self) == type(other) and self.scheme == other.scheme
265
266 class BzrSvnMappingv3FileProps(mapping.BzrSvnMappingFileProps, BzrSvnMappingv3):
267     pass
268
269
270 class BzrSvnMappingv3RevProps(mapping.BzrSvnMappingRevProps, BzrSvnMappingv3):
271     pass
272
273
274 class BzrSvnMappingv3Hybrid(BzrSvnMappingv3):
275     def __init__(self, scheme):
276         BzrSvnMappingv3.__init__(self, scheme)
277         self.revprops = BzrSvnMappingv3RevProps(scheme)
278         self.fileprops = BzrSvnMappingv3FileProps(scheme)
279
280     def get_rhs_parents(self, branch_path, svn_revprops, fileprops):
281         if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
282             return self.revprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
283         else:
284             return self.fileprops.get_rhs_parents(branch_path, svn_revprops, fileprops)
285
286     def get_revision_id(self, branch_path, revprops, fileprops):
287         if revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
288             return self.revprops.get_revision_id(branch_path, revprops, fileprops)
289         else:
290             return self.fileprops.get_revision_id(branch_path, revprops, fileprops)
291
292     def import_fileid_map(self, svn_revprops, fileprops):
293         if svn_revprops.has_key(mapping.SVN_REVPROP_BZR_MAPPING_VERSION):
294             return self.revprops.import_fileid_map(svn_revprops, fileprops)
295         else:
296             return self.fileprops.import_fileid_map(svn_revprops, fileprops)
297
298     def export_revision(self, branch_root, timestamp, timezone, committer, revprops, revision_id, 
299                         revno, merges, fileprops):
300         (_, fileprops) = self.fileprops.export_revision(branch_root, timestamp, timezone, committer, 
301                                       revprops, revision_id, revno, merges, fileprops)
302         (revprops, _) = self.revprops.export_revision(branch_root, timestamp, timezone, committer, 
303                                       revprops, revision_id, revno, merges, fileprops)
304         return (revprops, fileprops)
305
306     def export_fileid_map(self, fileids, revprops, fileprops):
307         self.fileprops.export_fileid_map(fileids, revprops, fileprops)
308         self.revprops.export_fileid_map(fileids, revprops, fileprops)
309
310     def import_revision(self, svn_revprops, fileprops, rev):
311         self.fileprops.import_revision(svn_revprops, fileprops, rev)
312         self.revprops.import_revision(svn_revprops, fileprops, rev)
313
314