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