/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/branchbuilder.py

  • Committer: Andrew Bennetts
  • Date: 2009-10-21 11:13:40 UTC
  • mto: This revision was merged to the branch mainline in revision 4762.
  • Revision ID: andrew.bennetts@canonical.com-20091021111340-w7x4d5yf83qwjncc
Add test that WSGI glue allows request handlers to access paths above that request's. backing transport, so long as it is within the WSGI app's backing transport.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Utility for create branches with particular contents."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
 
from . import (
22
 
    controldir,
 
19
from bzrlib import (
 
20
    bzrdir,
23
21
    commit,
24
22
    errors,
25
23
    memorytree,
26
 
    revision,
27
 
    )
28
 
from .sixish import (
29
 
    viewitems,
30
24
    )
31
25
 
32
26
 
42
36
 
43
37
    For instance:
44
38
 
45
 
    >>> from breezy.transport.memory import MemoryTransport
 
39
    >>> from bzrlib.transport.memory import MemoryTransport
46
40
    >>> builder = BranchBuilder(MemoryTransport("memory:///"))
47
41
    >>> builder.start_series()
48
 
    >>> builder.build_snapshot(None, [
49
 
    ...     ('add', ('', b'root-id', 'directory', '')),
50
 
    ...     ('add', ('filename', b'f-id', 'file', 'content\n'))],
51
 
    ...     revision_id=b'rev-id')
 
42
    >>> builder.build_snapshot('rev-id', None, [
 
43
    ...     ('add', ('', 'root-id', 'directory', '')),
 
44
    ...     ('add', ('filename', 'f-id', 'file', 'content\n'))])
52
45
    'rev-id'
53
 
    >>> builder.build_snapshot([b'rev-id'],
54
 
    ...     [('modify', (b'f-id', 'new-content\n'))],
55
 
    ...     revision_id=b'rev2-id')
 
46
    >>> builder.build_snapshot('rev2-id', ['rev-id'],
 
47
    ...     [('modify', ('f-id', 'new-content\n'))])
56
48
    'rev2-id'
57
49
    >>> builder.finish_series()
58
50
    >>> branch = builder.get_branch()
71
63
            If the path of the transport does not exist but its parent does
72
64
            it will be created.
73
65
        :param format: Either a BzrDirFormat, or the name of a format in the
74
 
            controldir format registry for the branch to be built.
 
66
            bzrdir format registry for the branch to be built.
75
67
        :param branch: An already constructed branch to use.  This param is
76
68
            mutually exclusive with the transport and format params.
77
69
        """
89
81
            if format is None:
90
82
                format = 'default'
91
83
            if isinstance(format, str):
92
 
                format = controldir.format_registry.make_controldir(format)
93
 
            self._branch = controldir.ControlDir.create_branch_convenience(
 
84
                format = bzrdir.format_registry.make_bzrdir(format)
 
85
            self._branch = bzrdir.BzrDir.create_branch_convenience(
94
86
                transport.base, format=format, force_new_tree=False)
95
87
        self._tree = None
96
88
 
97
 
    def build_commit(self, parent_ids=None, allow_leftmost_as_ghost=False,
98
 
                     **commit_kwargs):
 
89
    def build_commit(self, **commit_kwargs):
99
90
        """Build a commit on the branch.
100
91
 
101
92
        This makes a commit with no real file content for when you only want
104
95
        :param commit_kwargs: Arguments to pass through to commit, such as
105
96
             timestamp.
106
97
        """
107
 
        if parent_ids is not None:
108
 
            if len(parent_ids) == 0:
109
 
                base_id = revision.NULL_REVISION
110
 
            else:
111
 
                base_id = parent_ids[0]
112
 
            if base_id != self._branch.last_revision():
113
 
                self._move_branch_pointer(base_id,
114
 
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
115
 
        tree = self._branch.create_memorytree()
116
 
        with tree.lock_write():
117
 
            if parent_ids is not None:
118
 
                tree.set_parent_ids(parent_ids,
119
 
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
98
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
99
        tree.lock_write()
 
100
        try:
120
101
            tree.add('')
121
102
            return self._do_commit(tree, **commit_kwargs)
 
103
        finally:
 
104
            tree.unlock()
122
105
 
123
 
    def _do_commit(self, tree, message=None, message_callback=None, **kwargs):
 
106
    def _do_commit(self, tree, message=None, **kwargs):
124
107
        reporter = commit.NullCommitReporter()
125
 
        if message is None and message_callback is None:
 
108
        if message is None:
126
109
            message = u'commit %d' % (self._branch.revno() + 1,)
127
 
        return tree.commit(message, message_callback=message_callback,
 
110
        return tree.commit(message,
128
111
            reporter=reporter,
129
112
            **kwargs)
130
113
 
131
114
    def _move_branch_pointer(self, new_revision_id,
132
115
        allow_leftmost_as_ghost=False):
133
116
        """Point self._branch to a different revision id."""
134
 
        with self._branch.lock_write():
 
117
        self._branch.lock_write()
 
118
        try:
135
119
            # We don't seem to have a simple set_last_revision(), so we
136
120
            # implement it here.
137
121
            cur_revno, cur_revision_id = self._branch.last_revision_info()
144
128
                if not allow_leftmost_as_ghost:
145
129
                    raise
146
130
                new_revno = 1
 
131
        finally:
 
132
            self._branch.unlock()
147
133
        if self._tree is not None:
148
134
            # We are currently processing a series, but when switching branch
149
135
            # pointers, it is easiest to just create a new memory tree.
151
137
            # We are cheating a little bit here, and locking the new tree
152
138
            # before the old tree is unlocked. But that way the branch stays
153
139
            # locked throughout.
154
 
            new_tree = self._branch.create_memorytree()
 
140
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
155
141
            new_tree.lock_write()
156
142
            self._tree.unlock()
157
143
            self._tree = new_tree
166
152
        if self._tree is not None:
167
153
            raise AssertionError('You cannot start a new series while a'
168
154
                                 ' series is already going.')
169
 
        self._tree = self._branch.create_memorytree()
 
155
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
170
156
        self._tree.lock_write()
171
157
 
172
158
    def finish_series(self):
174
160
        self._tree.unlock()
175
161
        self._tree = None
176
162
 
177
 
    def build_snapshot(self, parent_ids, actions, message=None, timestamp=None,
178
 
            allow_leftmost_as_ghost=False, committer=None, timezone=None,
179
 
            message_callback=None, revision_id=None):
 
163
    def build_snapshot(self, revision_id, parent_ids, actions,
 
164
        message=None, timestamp=None, allow_leftmost_as_ghost=False,
 
165
        committer=None, timezone=None):
180
166
        """Build a commit, shaped in a specific way.
181
167
 
182
 
        Most of the actions are self-explanatory.  'flush' is special action to
183
 
        break a series of actions into discrete steps so that complex changes
184
 
        (such as unversioning a file-id and re-adding it with a different kind)
185
 
        can be expressed in a way that will clearly work.
186
 
 
 
168
        :param revision_id: The handle for the new commit, can be None
187
169
        :param parent_ids: A list of parent_ids to use for the commit.
188
170
            It can be None, which indicates to use the last commit.
189
171
        :param actions: A list of actions to perform. Supported actions are:
190
 
            ('add', ('path', b'file-id', 'kind', 'content' or None))
191
 
            ('modify', ('path', 'new-content'))
192
 
            ('unversion', 'path')
 
172
            ('add', ('path', 'file-id', 'kind', 'content' or None))
 
173
            ('modify', ('file-id', 'new-content'))
 
174
            ('unversion', 'file-id')
193
175
            ('rename', ('orig-path', 'new-path'))
194
 
            ('flush', None)
195
176
        :param message: An optional commit message, if not supplied, a default
196
177
            commit message will be written.
197
 
        :param message_callback: A message callback to use for the commit, as
198
 
            per mutabletree.commit.
199
178
        :param timestamp: If non-None, set the timestamp of the commit to this
200
179
            value.
201
180
        :param timezone: An optional timezone for timestamp.
202
181
        :param committer: An optional username to use for commit
203
182
        :param allow_leftmost_as_ghost: True if the leftmost parent should be
204
183
            permitted to be a ghost.
205
 
        :param revision_id: The handle for the new commit, can be None
206
184
        :return: The revision_id of the new commit
207
185
        """
208
186
        if parent_ids is not None:
209
 
            if len(parent_ids) == 0:
210
 
                base_id = revision.NULL_REVISION
211
 
            else:
212
 
                base_id = parent_ids[0]
 
187
            base_id = parent_ids[0]
213
188
            if base_id != self._branch.last_revision():
214
189
                self._move_branch_pointer(base_id,
215
190
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
217
192
        if self._tree is not None:
218
193
            tree = self._tree
219
194
        else:
220
 
            tree = self._branch.create_memorytree()
221
 
        with tree.lock_write():
 
195
            tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
196
        tree.lock_write()
 
197
        try:
222
198
            if parent_ids is not None:
223
199
                tree.set_parent_ids(parent_ids,
224
200
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
226
202
            # inventory entry. And the only public function to create a
227
203
            # directory is MemoryTree.mkdir() which creates the directory, but
228
204
            # also always adds it. So we have to use a multi-pass setup.
229
 
            pending = _PendingActions()
 
205
            to_add_directories = []
 
206
            to_add_files = []
 
207
            to_add_file_ids = []
 
208
            to_add_kinds = []
 
209
            new_contents = {}
 
210
            to_unversion_ids = []
 
211
            to_rename = []
230
212
            for action, info in actions:
231
213
                if action == 'add':
232
214
                    path, file_id, kind, content = info
233
215
                    if kind == 'directory':
234
 
                        pending.to_add_directories.append((path, file_id))
 
216
                        to_add_directories.append((path, file_id))
235
217
                    else:
236
 
                        pending.to_add_files.append(path)
237
 
                        pending.to_add_file_ids.append(file_id)
238
 
                        pending.to_add_kinds.append(kind)
 
218
                        to_add_files.append(path)
 
219
                        to_add_file_ids.append(file_id)
 
220
                        to_add_kinds.append(kind)
239
221
                        if content is not None:
240
 
                            pending.new_contents[path] = content
 
222
                            new_contents[file_id] = content
241
223
                elif action == 'modify':
242
 
                    path, content = info
243
 
                    pending.new_contents[path] = content
 
224
                    file_id, content = info
 
225
                    new_contents[file_id] = content
244
226
                elif action == 'unversion':
245
 
                    pending.to_unversion_paths.add(info)
 
227
                    to_unversion_ids.append(info)
246
228
                elif action == 'rename':
247
229
                    from_relpath, to_relpath = info
248
 
                    pending.to_rename.append((from_relpath, to_relpath))
249
 
                elif action == 'flush':
250
 
                    self._flush_pending(tree, pending)
251
 
                    pending = _PendingActions()
 
230
                    to_rename.append((from_relpath, to_relpath))
252
231
                else:
253
232
                    raise ValueError('Unknown build action: "%s"' % (action,))
254
 
            self._flush_pending(tree, pending)
 
233
            if to_unversion_ids:
 
234
                tree.unversion(to_unversion_ids)
 
235
            for path, file_id in to_add_directories:
 
236
                if path == '':
 
237
                    # Special case, because the path already exists
 
238
                    tree.add([path], [file_id], ['directory'])
 
239
                else:
 
240
                    tree.mkdir(path, file_id)
 
241
            for from_relpath, to_relpath in to_rename:
 
242
                tree.rename_one(from_relpath, to_relpath)
 
243
            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
 
244
            for file_id, content in new_contents.iteritems():
 
245
                tree.put_file_bytes_non_atomic(file_id, content)
255
246
            return self._do_commit(tree, message=message, rev_id=revision_id,
256
 
                timestamp=timestamp, timezone=timezone, committer=committer,
257
 
                message_callback=message_callback)
258
 
 
259
 
    def _flush_pending(self, tree, pending):
260
 
        """Flush the pending actions in 'pending', i.e. apply them to 'tree'."""
261
 
        for path, file_id in pending.to_add_directories:
262
 
            if path == '':
263
 
                if tree.has_filename(path) and path in pending.to_unversion_paths:
264
 
                    # We're overwriting this path, no need to unversion
265
 
                    pending.to_unversion_paths.discard(path)
266
 
                # Special case, because the path already exists
267
 
                tree.add([path], [file_id], ['directory'])
268
 
            else:
269
 
                tree.mkdir(path, file_id)
270
 
        for from_relpath, to_relpath in pending.to_rename:
271
 
            tree.rename_one(from_relpath, to_relpath)
272
 
        if pending.to_unversion_paths:
273
 
            tree.unversion(pending.to_unversion_paths)
274
 
        tree.add(pending.to_add_files, pending.to_add_file_ids, pending.to_add_kinds)
275
 
        for path, content in viewitems(pending.new_contents):
276
 
            tree.put_file_bytes_non_atomic(path, content)
 
247
                timestamp=timestamp, timezone=timezone, committer=committer)
 
248
        finally:
 
249
            tree.unlock()
277
250
 
278
251
    def get_branch(self):
279
252
        """Return the branch created by the builder."""
280
253
        return self._branch
281
 
 
282
 
 
283
 
class _PendingActions(object):
284
 
    """Pending actions for build_snapshot to take.
285
 
 
286
 
    This is just a simple class to hold a bunch of the intermediate state of
287
 
    build_snapshot in single object.
288
 
    """
289
 
 
290
 
    def __init__(self):
291
 
        self.to_add_directories = []
292
 
        self.to_add_files = []
293
 
        self.to_add_file_ids = []
294
 
        self.to_add_kinds = []
295
 
        self.new_contents = {}
296
 
        self.to_unversion_paths = set()
297
 
        self.to_rename = []
298