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