~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branchbuilder.py

  • Committer: Robert Collins
  • Date: 2005-10-16 00:22:17 UTC
  • mto: This revision was merged to the branch mainline in revision 1457.
  • Revision ID: robertc@lifelesslap.robertcollins.net-20051016002217-aa38f9c1eb13ee48
Plugins are now loaded under bzrlib.plugins, not bzrlib.plugin.

Plugins are also made available for other plugins to use by making them 
accessible via import bzrlib.plugins.NAME. You should not import other
plugins during the __init__ of your plugin though, as no ordering is
guaranteed, and the plugins directory is not on the python path.

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, message_callback=None, **kwargs):
107
 
        reporter = commit.NullCommitReporter()
108
 
        if message is None and message_callback is None:
109
 
            message = u'commit %d' % (self._branch.revno() + 1,)
110
 
        return tree.commit(message, message_callback=message_callback,
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, message_callback=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 message_callback: A message callback to use for the commit, as
179
 
            per mutabletree.commit.
180
 
        :param timestamp: If non-None, set the timestamp of the commit to this
181
 
            value.
182
 
        :param timezone: An optional timezone for timestamp.
183
 
        :param committer: An optional username to use for commit
184
 
        :param allow_leftmost_as_ghost: True if the leftmost parent should be
185
 
            permitted to be a ghost.
186
 
        :return: The revision_id of the new commit
187
 
        """
188
 
        if parent_ids is not None:
189
 
            base_id = parent_ids[0]
190
 
            if base_id != self._branch.last_revision():
191
 
                self._move_branch_pointer(base_id,
192
 
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
193
 
 
194
 
        if self._tree is not None:
195
 
            tree = self._tree
196
 
        else:
197
 
            tree = memorytree.MemoryTree.create_on_branch(self._branch)
198
 
        tree.lock_write()
199
 
        try:
200
 
            if parent_ids is not None:
201
 
                tree.set_parent_ids(parent_ids,
202
 
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
203
 
            # Unfortunately, MemoryTree.add(directory) just creates an
204
 
            # inventory entry. And the only public function to create a
205
 
            # directory is MemoryTree.mkdir() which creates the directory, but
206
 
            # 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 = []
214
 
            for action, info in actions:
215
 
                if action == 'add':
216
 
                    path, file_id, kind, content = info
217
 
                    if kind == 'directory':
218
 
                        to_add_directories.append((path, file_id))
219
 
                    else:
220
 
                        to_add_files.append(path)
221
 
                        to_add_file_ids.append(file_id)
222
 
                        to_add_kinds.append(kind)
223
 
                        if content is not None:
224
 
                            new_contents[file_id] = content
225
 
                elif action == 'modify':
226
 
                    file_id, content = info
227
 
                    new_contents[file_id] = content
228
 
                elif action == 'unversion':
229
 
                    to_unversion_ids.append(info)
230
 
                elif action == 'rename':
231
 
                    from_relpath, to_relpath = info
232
 
                    to_rename.append((from_relpath, to_relpath))
233
 
                else:
234
 
                    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)
248
 
            return self._do_commit(tree, message=message, rev_id=revision_id,
249
 
                timestamp=timestamp, timezone=timezone, committer=committer,
250
 
                message_callback=message_callback)
251
 
        finally:
252
 
            tree.unlock()
253
 
 
254
 
    def get_branch(self):
255
 
        """Return the branch created by the builder."""
256
 
        return self._branch