/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: Vincent Ladeuil
  • Date: 2012-01-18 14:09:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6468.
  • Revision ID: v.ladeuil+lp@free.fr-20120118140919-rlvdrhpc0nq1lbwi
Change set/remove to require a lock for the branch config files.

This means that tests (or any plugin for that matter) do not requires an
explicit lock on the branch anymore to change a single option. This also
means the optimisation becomes "opt-in" and as such won't be as
spectacular as it may be and/or harder to get right (nothing fails
anymore).

This reduces the diff by ~300 lines.

Code/tests that were updating more than one config option is still taking
a lock to at least avoid some IOs and demonstrate the benefits through
the decreased number of hpss calls.

The duplication between BranchStack and BranchOnlyStack will be removed
once the same sharing is in place for local config files, at which point
the Stack class itself may be able to host the changes.

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
 
19
21
from bzrlib import (
20
 
    bzrdir,
 
22
    controldir,
21
23
    commit,
22
24
    errors,
23
25
    memorytree,
 
26
    revision,
24
27
    )
25
28
 
26
29
 
63
66
            If the path of the transport does not exist but its parent does
64
67
            it will be created.
65
68
        :param format: Either a BzrDirFormat, or the name of a format in the
66
 
            bzrdir format registry for the branch to be built.
 
69
            controldir format registry for the branch to be built.
67
70
        :param branch: An already constructed branch to use.  This param is
68
71
            mutually exclusive with the transport and format params.
69
72
        """
81
84
            if format is None:
82
85
                format = 'default'
83
86
            if isinstance(format, str):
84
 
                format = bzrdir.format_registry.make_bzrdir(format)
85
 
            self._branch = bzrdir.BzrDir.create_branch_convenience(
 
87
                format = controldir.format_registry.make_bzrdir(format)
 
88
            self._branch = controldir.ControlDir.create_branch_convenience(
86
89
                transport.base, format=format, force_new_tree=False)
87
90
        self._tree = None
88
91
 
89
 
    def build_commit(self, **commit_kwargs):
 
92
    def build_commit(self, parent_ids=None, allow_leftmost_as_ghost=False,
 
93
                     **commit_kwargs):
90
94
        """Build a commit on the branch.
91
95
 
92
96
        This makes a commit with no real file content for when you only want
95
99
        :param commit_kwargs: Arguments to pass through to commit, such as
96
100
             timestamp.
97
101
        """
 
102
        if parent_ids is not None:
 
103
            if len(parent_ids) == 0:
 
104
                base_id = revision.NULL_REVISION
 
105
            else:
 
106
                base_id = parent_ids[0]
 
107
            if base_id != self._branch.last_revision():
 
108
                self._move_branch_pointer(base_id,
 
109
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
98
110
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
99
111
        tree.lock_write()
100
112
        try:
 
113
            if parent_ids is not None:
 
114
                tree.set_parent_ids(parent_ids,
 
115
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
101
116
            tree.add('')
102
117
            return self._do_commit(tree, **commit_kwargs)
103
118
        finally:
165
180
        committer=None, timezone=None, message_callback=None):
166
181
        """Build a commit, shaped in a specific way.
167
182
 
 
183
        Most of the actions are self-explanatory.  'flush' is special action to
 
184
        break a series of actions into discrete steps so that complex changes
 
185
        (such as unversioning a file-id and re-adding it with a different kind)
 
186
        can be expressed in a way that will clearly work.
 
187
 
168
188
        :param revision_id: The handle for the new commit, can be None
169
189
        :param parent_ids: A list of parent_ids to use for the commit.
170
190
            It can be None, which indicates to use the last commit.
173
193
            ('modify', ('file-id', 'new-content'))
174
194
            ('unversion', 'file-id')
175
195
            ('rename', ('orig-path', 'new-path'))
 
196
            ('flush', None)
176
197
        :param message: An optional commit message, if not supplied, a default
177
198
            commit message will be written.
178
199
        :param message_callback: A message callback to use for the commit, as
186
207
        :return: The revision_id of the new commit
187
208
        """
188
209
        if parent_ids is not None:
189
 
            base_id = parent_ids[0]
 
210
            if len(parent_ids) == 0:
 
211
                base_id = revision.NULL_REVISION
 
212
            else:
 
213
                base_id = parent_ids[0]
190
214
            if base_id != self._branch.last_revision():
191
215
                self._move_branch_pointer(base_id,
192
216
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
204
228
            # inventory entry. And the only public function to create a
205
229
            # directory is MemoryTree.mkdir() which creates the directory, but
206
230
            # also always adds it. So we have to use a multi-pass setup.
207
 
            to_add_directories = []
208
 
            to_add_files = []
209
 
            to_add_file_ids = []
210
 
            to_add_kinds = []
211
 
            new_contents = {}
212
 
            to_unversion_ids = []
213
 
            to_rename = []
 
231
            pending = _PendingActions()
214
232
            for action, info in actions:
215
233
                if action == 'add':
216
234
                    path, file_id, kind, content = info
217
235
                    if kind == 'directory':
218
 
                        to_add_directories.append((path, file_id))
 
236
                        pending.to_add_directories.append((path, file_id))
219
237
                    else:
220
 
                        to_add_files.append(path)
221
 
                        to_add_file_ids.append(file_id)
222
 
                        to_add_kinds.append(kind)
 
238
                        pending.to_add_files.append(path)
 
239
                        pending.to_add_file_ids.append(file_id)
 
240
                        pending.to_add_kinds.append(kind)
223
241
                        if content is not None:
224
 
                            new_contents[file_id] = content
 
242
                            pending.new_contents[file_id] = content
225
243
                elif action == 'modify':
226
244
                    file_id, content = info
227
 
                    new_contents[file_id] = content
 
245
                    pending.new_contents[file_id] = content
228
246
                elif action == 'unversion':
229
 
                    to_unversion_ids.append(info)
 
247
                    pending.to_unversion_ids.add(info)
230
248
                elif action == 'rename':
231
249
                    from_relpath, to_relpath = info
232
 
                    to_rename.append((from_relpath, to_relpath))
 
250
                    pending.to_rename.append((from_relpath, to_relpath))
 
251
                elif action == 'flush':
 
252
                    self._flush_pending(tree, pending)
 
253
                    pending = _PendingActions()
233
254
                else:
234
255
                    raise ValueError('Unknown build action: "%s"' % (action,))
235
 
            if to_unversion_ids:
236
 
                tree.unversion(to_unversion_ids)
237
 
            for path, file_id in to_add_directories:
238
 
                if path == '':
239
 
                    # Special case, because the path already exists
240
 
                    tree.add([path], [file_id], ['directory'])
241
 
                else:
242
 
                    tree.mkdir(path, file_id)
243
 
            for from_relpath, to_relpath in to_rename:
244
 
                tree.rename_one(from_relpath, to_relpath)
245
 
            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
246
 
            for file_id, content in new_contents.iteritems():
247
 
                tree.put_file_bytes_non_atomic(file_id, content)
 
256
            self._flush_pending(tree, pending)
248
257
            return self._do_commit(tree, message=message, rev_id=revision_id,
249
258
                timestamp=timestamp, timezone=timezone, committer=committer,
250
259
                message_callback=message_callback)
251
260
        finally:
252
261
            tree.unlock()
253
262
 
 
263
    def _flush_pending(self, tree, pending):
 
264
        """Flush the pending actions in 'pending', i.e. apply them to 'tree'."""
 
265
        for path, file_id in pending.to_add_directories:
 
266
            if path == '':
 
267
                old_id = tree.path2id(path)
 
268
                if old_id is not None and old_id in pending.to_unversion_ids:
 
269
                    # We're overwriting this path, no need to unversion
 
270
                    pending.to_unversion_ids.discard(old_id)
 
271
                # Special case, because the path already exists
 
272
                tree.add([path], [file_id], ['directory'])
 
273
            else:
 
274
                tree.mkdir(path, file_id)
 
275
        for from_relpath, to_relpath in pending.to_rename:
 
276
            tree.rename_one(from_relpath, to_relpath)
 
277
        if pending.to_unversion_ids:
 
278
            tree.unversion(pending.to_unversion_ids)
 
279
        tree.add(pending.to_add_files, pending.to_add_file_ids, pending.to_add_kinds)
 
280
        for file_id, content in pending.new_contents.iteritems():
 
281
            tree.put_file_bytes_non_atomic(file_id, content)
 
282
 
254
283
    def get_branch(self):
255
284
        """Return the branch created by the builder."""
256
285
        return self._branch
 
286
 
 
287
 
 
288
class _PendingActions(object):
 
289
    """Pending actions for build_snapshot to take.
 
290
 
 
291
    This is just a simple class to hold a bunch of the intermediate state of
 
292
    build_snapshot in single object.
 
293
    """
 
294
 
 
295
    def __init__(self):
 
296
        self.to_add_directories = []
 
297
        self.to_add_files = []
 
298
        self.to_add_file_ids = []
 
299
        self.to_add_kinds = []
 
300
        self.new_contents = {}
 
301
        self.to_unversion_ids = set()
 
302
        self.to_rename = []
 
303