/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: 2010-01-25 15:55:48 UTC
  • mto: (4985.1.4 add-attr-cleanup)
  • mto: This revision was merged to the branch mainline in revision 4988.
  • Revision ID: v.ladeuil+lp@free.fr-20100125155548-0l352pujvt5bzl5e
Deploy addAttrCleanup on the whole test suite.

Several use case worth mentioning:

- setting a module or any other object attribute is the majority
by far. In some cases the setting itself is deferred but most of
the time we want to set at the same time we add the cleanup.

- there multiple occurrences of protecting hooks or ui factory
which are now useless (the test framework takes care of that now),

- there was some lambda uses that can now be avoided.

That first cleanup already simplifies things a lot.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007, 2008, 2009 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
"""Utility for create branches with particular contents."""
 
18
 
 
19
from bzrlib import (
 
20
    bzrdir,
 
21
    commit,
 
22
    errors,
 
23
    memorytree,
 
24
    )
 
25
 
 
26
 
 
27
class BranchBuilder(object):
 
28
    r"""A BranchBuilder aids creating Branches with particular shapes.
 
29
 
 
30
    The expected way to use BranchBuilder is to construct a
 
31
    BranchBuilder on the transport you want your branch on, and then call
 
32
    appropriate build_ methods on it to get the shape of history you want.
 
33
 
 
34
    This is meant as a helper for the test suite, not as a general class for
 
35
    real data.
 
36
 
 
37
    For instance:
 
38
 
 
39
    >>> from bzrlib.transport.memory import MemoryTransport
 
40
    >>> builder = BranchBuilder(MemoryTransport("memory:///"))
 
41
    >>> builder.start_series()
 
42
    >>> builder.build_snapshot('rev-id', None, [
 
43
    ...     ('add', ('', 'root-id', 'directory', '')),
 
44
    ...     ('add', ('filename', 'f-id', 'file', 'content\n'))])
 
45
    'rev-id'
 
46
    >>> builder.build_snapshot('rev2-id', ['rev-id'],
 
47
    ...     [('modify', ('f-id', 'new-content\n'))])
 
48
    'rev2-id'
 
49
    >>> builder.finish_series()
 
50
    >>> branch = builder.get_branch()
 
51
 
 
52
    :ivar _tree: This is a private member which is not meant to be modified by
 
53
        users of this class. While a 'series' is in progress, it should hold a
 
54
        MemoryTree with the contents of the last commit (ready to be modified
 
55
        by the next build_snapshot command) with a held write lock. Outside of
 
56
        a series in progress, it should be None.
 
57
    """
 
58
 
 
59
    def __init__(self, transport=None, format=None, branch=None):
 
60
        """Construct a BranchBuilder on transport.
 
61
 
 
62
        :param transport: The transport the branch should be created on.
 
63
            If the path of the transport does not exist but its parent does
 
64
            it will be created.
 
65
        :param format: Either a BzrDirFormat, or the name of a format in the
 
66
            bzrdir format registry for the branch to be built.
 
67
        :param branch: An already constructed branch to use.  This param is
 
68
            mutually exclusive with the transport and format params.
 
69
        """
 
70
        if branch is not None:
 
71
            if format is not None:
 
72
                raise AssertionError(
 
73
                    "branch and format kwargs are mutually exclusive")
 
74
            if transport is not None:
 
75
                raise AssertionError(
 
76
                    "branch and transport kwargs are mutually exclusive")
 
77
            self._branch = branch
 
78
        else:
 
79
            if not transport.has('.'):
 
80
                transport.mkdir('.')
 
81
            if format is None:
 
82
                format = 'default'
 
83
            if isinstance(format, str):
 
84
                format = bzrdir.format_registry.make_bzrdir(format)
 
85
            self._branch = bzrdir.BzrDir.create_branch_convenience(
 
86
                transport.base, format=format, force_new_tree=False)
 
87
        self._tree = None
 
88
 
 
89
    def build_commit(self, **commit_kwargs):
 
90
        """Build a commit on the branch.
 
91
 
 
92
        This makes a commit with no real file content for when you only want
 
93
        to look at the revision graph structure.
 
94
 
 
95
        :param commit_kwargs: Arguments to pass through to commit, such as
 
96
             timestamp.
 
97
        """
 
98
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
99
        tree.lock_write()
 
100
        try:
 
101
            tree.add('')
 
102
            return self._do_commit(tree, **commit_kwargs)
 
103
        finally:
 
104
            tree.unlock()
 
105
 
 
106
    def _do_commit(self, tree, message=None, **kwargs):
 
107
        reporter = commit.NullCommitReporter()
 
108
        if message is None:
 
109
            message = u'commit %d' % (self._branch.revno() + 1,)
 
110
        return tree.commit(message,
 
111
            reporter=reporter,
 
112
            **kwargs)
 
113
 
 
114
    def _move_branch_pointer(self, new_revision_id,
 
115
        allow_leftmost_as_ghost=False):
 
116
        """Point self._branch to a different revision id."""
 
117
        self._branch.lock_write()
 
118
        try:
 
119
            # We don't seem to have a simple set_last_revision(), so we
 
120
            # implement it here.
 
121
            cur_revno, cur_revision_id = self._branch.last_revision_info()
 
122
            try:
 
123
                g = self._branch.repository.get_graph()
 
124
                new_revno = g.find_distance_to_null(new_revision_id,
 
125
                    [(cur_revision_id, cur_revno)])
 
126
                self._branch.set_last_revision_info(new_revno, new_revision_id)
 
127
            except errors.GhostRevisionsHaveNoRevno:
 
128
                if not allow_leftmost_as_ghost:
 
129
                    raise
 
130
                new_revno = 1
 
131
        finally:
 
132
            self._branch.unlock()
 
133
        if self._tree is not None:
 
134
            # We are currently processing a series, but when switching branch
 
135
            # pointers, it is easiest to just create a new memory tree.
 
136
            # That way we are sure to have the right files-on-disk
 
137
            # We are cheating a little bit here, and locking the new tree
 
138
            # before the old tree is unlocked. But that way the branch stays
 
139
            # locked throughout.
 
140
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
141
            new_tree.lock_write()
 
142
            self._tree.unlock()
 
143
            self._tree = new_tree
 
144
 
 
145
    def start_series(self):
 
146
        """We will be creating a series of commits.
 
147
 
 
148
        This allows us to hold open the locks while we are processing.
 
149
 
 
150
        Make sure to call 'finish_series' when you are done.
 
151
        """
 
152
        if self._tree is not None:
 
153
            raise AssertionError('You cannot start a new series while a'
 
154
                                 ' series is already going.')
 
155
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
156
        self._tree.lock_write()
 
157
 
 
158
    def finish_series(self):
 
159
        """Call this after start_series to unlock the various objects."""
 
160
        self._tree.unlock()
 
161
        self._tree = None
 
162
 
 
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):
 
166
        """Build a commit, shaped in a specific way.
 
167
 
 
168
        :param revision_id: The handle for the new commit, can be None
 
169
        :param parent_ids: A list of parent_ids to use for the commit.
 
170
            It can be None, which indicates to use the last commit.
 
171
        :param actions: A list of actions to perform. Supported actions are:
 
172
            ('add', ('path', 'file-id', 'kind', 'content' or None))
 
173
            ('modify', ('file-id', 'new-content'))
 
174
            ('unversion', 'file-id')
 
175
            ('rename', ('orig-path', 'new-path'))
 
176
        :param message: An optional commit message, if not supplied, a default
 
177
            commit message will be written.
 
178
        :param timestamp: If non-None, set the timestamp of the commit to this
 
179
            value.
 
180
        :param timezone: An optional timezone for timestamp.
 
181
        :param committer: An optional username to use for commit
 
182
        :param allow_leftmost_as_ghost: True if the leftmost parent should be
 
183
            permitted to be a ghost.
 
184
        :return: The revision_id of the new commit
 
185
        """
 
186
        if parent_ids is not None:
 
187
            base_id = parent_ids[0]
 
188
            if base_id != self._branch.last_revision():
 
189
                self._move_branch_pointer(base_id,
 
190
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
191
 
 
192
        if self._tree is not None:
 
193
            tree = self._tree
 
194
        else:
 
195
            tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
196
        tree.lock_write()
 
197
        try:
 
198
            if parent_ids is not None:
 
199
                tree.set_parent_ids(parent_ids,
 
200
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
 
201
            # Unfortunately, MemoryTree.add(directory) just creates an
 
202
            # inventory entry. And the only public function to create a
 
203
            # directory is MemoryTree.mkdir() which creates the directory, but
 
204
            # also always adds it. So we have to use a multi-pass setup.
 
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 = []
 
212
            for action, info in actions:
 
213
                if action == 'add':
 
214
                    path, file_id, kind, content = info
 
215
                    if kind == 'directory':
 
216
                        to_add_directories.append((path, file_id))
 
217
                    else:
 
218
                        to_add_files.append(path)
 
219
                        to_add_file_ids.append(file_id)
 
220
                        to_add_kinds.append(kind)
 
221
                        if content is not None:
 
222
                            new_contents[file_id] = content
 
223
                elif action == 'modify':
 
224
                    file_id, content = info
 
225
                    new_contents[file_id] = content
 
226
                elif action == 'unversion':
 
227
                    to_unversion_ids.append(info)
 
228
                elif action == 'rename':
 
229
                    from_relpath, to_relpath = info
 
230
                    to_rename.append((from_relpath, to_relpath))
 
231
                else:
 
232
                    raise ValueError('Unknown build action: "%s"' % (action,))
 
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)
 
246
            return self._do_commit(tree, message=message, rev_id=revision_id,
 
247
                timestamp=timestamp, timezone=timezone, committer=committer)
 
248
        finally:
 
249
            tree.unlock()
 
250
 
 
251
    def get_branch(self):
 
252
        """Return the branch created by the builder."""
 
253
        return self._branch