Add news entry for FAQ.
[jelmer/subvertpy.git] / branchprops.py
1 # Copyright (C) 2006-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 2 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, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17 """Branch property access and caching."""
18
19 from bzrlib.errors import NoSuchRevision
20 from bzrlib.trace import mutter
21
22 from svn.core import SubversionException, Pool
23 import svn.core
24
25 class BranchPropertyList:
26     """Simple class that retrieves file properties set on branches."""
27     def __init__(self, log, cachedb):
28         self.log = log
29         self.cachedb = cachedb
30
31         self.cachedb.executescript("""
32             create table if not exists branchprop (name text, value text, branchpath text, revnum integer);
33             create index if not exists branch_path_revnum on branchprop (branchpath, revnum);
34             create index if not exists branch_path_revnum_name on branchprop (branchpath, revnum, name);
35         """)
36
37         self.pool = Pool()
38
39     def _get_dir_props(self, path, revnum):
40         """Obtain all the directory properties set on a path/revnum pair.
41
42         :param path: Subversion path
43         :param revnum: Subversion revision number
44         :return: Dictionary with properties
45         """
46         assert isinstance(path, str)
47         path = path.lstrip("/")
48
49         try:
50             (_, _, props) = self.log._get_transport().get_dir(path, 
51                 revnum, pool=self.pool)
52         except SubversionException, (_, num):
53             if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
54                 raise NoSuchRevision(self, revnum)
55             raise
56
57         return props
58
59     def get_properties(self, path, origrevnum):
60         """Obtain the file properties set on a path/revnum pair.
61
62         Will use the cache.
63
64         :param path: Subversion path.
65         :param origrevnum: Subversion revision number.
66         :return: Dictionary with properties
67         """
68         assert path is not None
69         assert isinstance(path, str)
70         assert isinstance(origrevnum, int) and origrevnum >= 0
71         revnum = self.log.find_latest_change(path, origrevnum, 
72                                              include_parents=True)
73         assert revnum is not None, \
74                 "can't find latest change for %r:%r" % (path, origrevnum)
75
76         if revnum == 0: # no properties are set in revision 0
77             return {}
78
79         proplist = {}
80         for (name, value) in self.cachedb.execute("select name, value from branchprop where revnum=%d and branchpath='%s'" % (revnum, path)):
81             proplist[name] = value.encode("utf-8")
82
83         if proplist != {}:
84             return proplist
85
86         proplist = self._get_dir_props(path, revnum)
87         for name in proplist:
88             self.cachedb.execute("insert into branchprop (name, value, revnum, branchpath) values (?,?,?,?)", (name, proplist[name], revnum, path))
89         self.cachedb.commit()
90
91         return proplist
92
93     def get_property(self, path, revnum, name, default=None):
94         """Get the contents of a Subversion file property.
95
96         Will use the cache.
97
98         :param path: Subversion path.
99         :param revnum: Subversion revision number.
100         :param default: Default value to return if property wasn't found.
101         :return: Contents of property or default if property didn't exist.
102         """
103         assert isinstance(revnum, int)
104         assert isinstance(path, str)
105         props = self.get_properties(path, revnum)
106         if props.has_key(name):
107             return props[name]
108         return default
109
110     def touches_property(self, path, revnum, name):
111         """Check whether a property was modified in a revision."""
112         assert isinstance(path, str)
113         assert isinstance(revnum, int)
114         assert isinstance(name, str)
115         # If the path this property is set on didn't change, then 
116         # the property can't have changed.
117         if not self.log.touches_path(path, revnum):
118             return ""
119
120         current = self.get_property(path, revnum, name, None)
121         (prev_path, prev_revnum) = self.log.get_previous(path, revnum)
122         if prev_path is None and prev_revnum == -1:
123             return (current is not None)
124         previous = self.get_property(prev_path.encode("utf-8"), 
125                                      prev_revnum, name, None)
126         return (previous != current)
127
128     def get_property_diff(self, path, revnum, name):
129         """Returns the new lines that were added to a particular property."""
130         assert isinstance(path, str)
131         # If the path this property is set on didn't change, then 
132         # the property can't have changed.
133         if not self.log.touches_path(path, revnum):
134             return ""
135
136         current = self.get_property(path, revnum, name, "")
137         (prev_path, prev_revnum) = self.log.get_previous(path, revnum)
138         if prev_path is None and prev_revnum == -1:
139             previous = ""
140         else:
141             previous = self.get_property(prev_path.encode("utf-8"), 
142                                          prev_revnum, name, "")
143         if len(previous) > len(current) or current[0:len(previous)] != previous:
144             mutter('original part changed for %r between %s:%d -> %s:%d' % (name, prev_path, prev_revnum, path, revnum))
145             return ""
146         return current[len(previous):]