/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 breezy/views.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-07 02:14:30 UTC
  • mto: This revision was merged to the branch mainline in revision 7492.
  • Revision ID: jelmer@jelmer.uk-20200207021430-m49iq3x4x8xlib6x
Drop python2 support.

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