/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/views.py

  • Committer: Jelmer Vernooij
  • Date: 2011-12-19 10:58:39 UTC
  • mfrom: (6383 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6386.
  • Revision ID: jelmer@canonical.com-20111219105839-uji05ck4rkm1mj4j
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 Canonical Ltd
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
from __future__ import absolute_import
 
18
 
 
19
"""View management.
 
20
 
 
21
Views are contained within a working tree and normally constructed
 
22
when first accessed.  Clients should do, for example, ...
 
23
 
 
24
  tree.views.lookup_view()
 
25
"""
 
26
 
 
27
 
 
28
import re
 
29
 
 
30
from bzrlib import (
 
31
    errors,
 
32
    osutils,
 
33
    )
 
34
 
 
35
 
 
36
_VIEWS_FORMAT_MARKER_RE = re.compile(r'Bazaar views format (\d+)')
 
37
_VIEWS_FORMAT1_MARKER = "Bazaar views format 1\n"
 
38
 
 
39
 
 
40
class _Views(object):
 
41
    """Base class for View managers."""
 
42
 
 
43
    def supports_views(self):
 
44
        raise NotImplementedError(self.supports_views)
 
45
 
 
46
 
 
47
class PathBasedViews(_Views):
 
48
    """View storage in an unversioned tree control file.
 
49
 
 
50
    Views are stored in terms of paths relative to the tree root.
 
51
 
 
52
    The top line of the control file is a format marker in the format:
 
53
 
 
54
      Bazaar views format X
 
55
 
 
56
    where X is an integer number. After this top line, version 1 format is
 
57
    stored as follows:
 
58
 
 
59
     * optional name-values pairs in the format 'name=value'
 
60
 
 
61
     * optional view definitions, one per line in the format
 
62
 
 
63
       views:
 
64
       name file1 file2 ...
 
65
       name file1 file2 ...
 
66
 
 
67
    where the fields are separated by a nul character (\0). The views file
 
68
    is encoded in utf-8. The only supported keyword in version 1 is
 
69
    'current' which stores the name of the current view, if any.
 
70
    """
 
71
 
 
72
    def __init__(self, tree):
 
73
        self.tree = tree
 
74
        self._loaded = False
 
75
        self._current = None
 
76
        self._views = {}
 
77
 
 
78
    def supports_views(self):
 
79
        return True
 
80
 
 
81
    def get_view_info(self):
 
82
        """Get the current view and dictionary of views.
 
83
 
 
84
        :return: current, views where
 
85
          current = the name of the current view or None if no view is enabled
 
86
          views = a map from view name to list of files/directories
 
87
        """
 
88
        self._load_view_info()
 
89
        return self._current, self._views
 
90
 
 
91
    def set_view_info(self, current, views):
 
92
        """Set the current view and dictionary of views.
 
93
 
 
94
        :param current: the name of the current view or None if no view is
 
95
          enabled
 
96
        :param views: a map from view name to list of files/directories
 
97
        """
 
98
        if current is not None and current not in views:
 
99
            raise errors.NoSuchView(current)
 
100
        self.tree.lock_write()
 
101
        try:
 
102
            self._current = current
 
103
            self._views = views
 
104
            self._save_view_info()
 
105
        finally:
 
106
            self.tree.unlock()
 
107
 
 
108
    def lookup_view(self, view_name=None):
 
109
        """Return the contents of a view.
 
110
 
 
111
        :param view_Name: name of the view or None to lookup the current view
 
112
        :return: the list of files/directories in the requested view
 
113
        """
 
114
        self._load_view_info()
 
115
        try:
 
116
            if view_name is None:
 
117
                if self._current:
 
118
                    view_name = self._current
 
119
                else:
 
120
                    return []
 
121
            return self._views[view_name]
 
122
        except KeyError:
 
123
            raise errors.NoSuchView(view_name)
 
124
 
 
125
    def set_view(self, view_name, view_files, make_current=True):
 
126
        """Add or update a view definition.
 
127
 
 
128
        :param view_name: the name of the view
 
129
        :param view_files: the list of files/directories in the view
 
130
        :param make_current: make this view the current one or not
 
131
        """
 
132
        self.tree.lock_write()
 
133
        try:
 
134
            self._load_view_info()
 
135
            self._views[view_name] = view_files
 
136
            if make_current:
 
137
                self._current = view_name
 
138
            self._save_view_info()
 
139
        finally:
 
140
            self.tree.unlock()
 
141
 
 
142
    def delete_view(self, view_name):
 
143
        """Delete a view definition.
 
144
 
 
145
        If the view deleted is the current one, the current view is reset.
 
146
        """
 
147
        self.tree.lock_write()
 
148
        try:
 
149
            self._load_view_info()
 
150
            try:
 
151
                del self._views[view_name]
 
152
            except KeyError:
 
153
                raise errors.NoSuchView(view_name)
 
154
            if view_name == self._current:
 
155
                self._current = None
 
156
            self._save_view_info()
 
157
        finally:
 
158
            self.tree.unlock()
 
159
 
 
160
    def _save_view_info(self):
 
161
        """Save the current view and all view definitions.
 
162
 
 
163
        Be sure to have initialised self._current and self._views before
 
164
        calling this method.
 
165
        """
 
166
        self.tree.lock_write()
 
167
        try:
 
168
            if self._current is None:
 
169
                keywords = {}
 
170
            else:
 
171
                keywords = {'current': self._current}
 
172
            self.tree._transport.put_bytes('views',
 
173
                self._serialize_view_content(keywords, self._views))
 
174
        finally:
 
175
            self.tree.unlock()
 
176
 
 
177
    def _load_view_info(self):
 
178
        """Load the current view and dictionary of view definitions."""
 
179
        if not self._loaded:
 
180
            self.tree.lock_read()
 
181
            try:
 
182
                try:
 
183
                    view_content = self.tree._transport.get_bytes('views')
 
184
                except errors.NoSuchFile, e:
 
185
                    self._current, self._views = None, {}
 
186
                else:
 
187
                    keywords, self._views = \
 
188
                        self._deserialize_view_content(view_content)
 
189
                    self._current = keywords.get('current')
 
190
            finally:
 
191
                self.tree.unlock()
 
192
            self._loaded = True
 
193
 
 
194
    def _serialize_view_content(self, keywords, view_dict):
 
195
        """Convert view keywords and a view dictionary into a stream."""
 
196
        lines = [_VIEWS_FORMAT1_MARKER]
 
197
        for key in keywords:
 
198
            line = "%s=%s\n" % (key,keywords[key])
 
199
            lines.append(line.encode('utf-8'))
 
200
        if view_dict:
 
201
            lines.append("views:\n".encode('utf-8'))
 
202
            for view in sorted(view_dict):
 
203
                view_data = "%s\0%s\n" % (view, "\0".join(view_dict[view]))
 
204
                lines.append(view_data.encode('utf-8'))
 
205
        return "".join(lines)
 
206
 
 
207
    def _deserialize_view_content(self, view_content):
 
208
        """Convert a stream into view keywords and a dictionary of views."""
 
209
        # as a special case to make initialization easy, an empty definition
 
210
        # maps to no current view and an empty view dictionary
 
211
        if view_content == '':
 
212
            return {}, {}
 
213
        lines = view_content.splitlines()
 
214
        match = _VIEWS_FORMAT_MARKER_RE.match(lines[0])
 
215
        if not match:
 
216
            raise ValueError(
 
217
                "format marker missing from top of views file")
 
218
        elif match.group(1) != '1':
 
219
            raise ValueError(
 
220
                "cannot decode views format %s" % match.group(1))
 
221
        try:
 
222
            keywords = {}
 
223
            views = {}
 
224
            in_views = False
 
225
            for line in lines[1:]:
 
226
                text = line.decode('utf-8')
 
227
                if in_views:
 
228
                    parts = text.split('\0')
 
229
                    view = parts.pop(0)
 
230
                    views[view] = parts
 
231
                elif text == 'views:':
 
232
                    in_views = True
 
233
                    continue
 
234
                elif text.find('=') >= 0:
 
235
                    # must be a name-value pair
 
236
                    keyword, value = text.split('=', 1)
 
237
                    keywords[keyword] = value
 
238
                else:
 
239
                    raise ValueError("failed to deserialize views line %s",
 
240
                        text)
 
241
            return keywords, views
 
242
        except ValueError, e:
 
243
            raise ValueError("failed to deserialize views content %r: %s"
 
244
                % (view_content, e))
 
245
 
 
246
 
 
247
class DisabledViews(_Views):
 
248
    """View storage that refuses to store anything.
 
249
 
 
250
    This is used by older formats that can't store views.
 
251
    """
 
252
 
 
253
    def __init__(self, tree):
 
254
        self.tree = tree
 
255
 
 
256
    def supports_views(self):
 
257
        return False
 
258
 
 
259
    def _not_supported(self, *a, **k):
 
260
        raise errors.ViewsNotSupported(self.tree)
 
261
 
 
262
    get_view_info = _not_supported
 
263
    set_view_info = _not_supported
 
264
    lookup_view = _not_supported
 
265
    set_view = _not_supported
 
266
    delete_view = _not_supported
 
267
 
 
268
 
 
269
def view_display_str(view_files, encoding=None):
 
270
    """Get the display string for a list of view files.
 
271
 
 
272
    :param view_files: the list of file names
 
273
    :param encoding: the encoding to display the files in
 
274
    """
 
275
    if encoding is None:
 
276
        return ", ".join(view_files)
 
277
    else:
 
278
        return ", ".join([v.encode(encoding, 'replace') for v in view_files])
 
279
 
 
280
 
 
281
def check_path_in_view(tree, relpath):
 
282
    """If a working tree has a view enabled, check the path is within it."""
 
283
    if tree.supports_views():
 
284
        view_files = tree.views.lookup_view()
 
285
        if  view_files and not osutils.is_inside_any(view_files, relpath):
 
286
            raise errors.FileOutsideView(relpath, view_files)