/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: John Arbash Meinel
  • Date: 2008-11-25 18:51:48 UTC
  • mto: This revision was merged to the branch mainline in revision 3854.
  • Revision ID: john@arbash-meinel.com-20081125185148-jsfkqnzfjjqsleds
It seems we have some direct tests that don't use strings and expect a value error as well.

They would be sanitized later on by Revision. We could use that code, but this test
depends on the serializer, which Revision wouldn't know about.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2007, 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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, format=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
        """
 
68
        if not transport.has('.'):
 
69
            transport.mkdir('.')
 
70
        if format is None:
 
71
            format = 'default'
 
72
        if isinstance(format, str):
 
73
            format = bzrdir.format_registry.make_bzrdir(format)
 
74
        self._branch = bzrdir.BzrDir.create_branch_convenience(transport.base,
 
75
            format=format, force_new_tree=False)
 
76
        self._tree = None
 
77
 
 
78
    def build_commit(self):
 
79
        """Build a commit on the branch."""
 
80
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
81
        tree.lock_write()
 
82
        try:
 
83
            tree.add('')
 
84
            return self._do_commit(tree)
 
85
        finally:
 
86
            tree.unlock()
 
87
 
 
88
    def _do_commit(self, tree, message=None, **kwargs):
 
89
        reporter = commit.NullCommitReporter()
 
90
        if message is None:
 
91
            message = u'commit %d' % (self._branch.revno() + 1,)
 
92
        return tree.commit(message,
 
93
            reporter=reporter,
 
94
            **kwargs)
 
95
 
 
96
    def _move_branch_pointer(self, new_revision_id):
 
97
        """Point self._branch to a different revision id."""
 
98
        self._branch.lock_write()
 
99
        try:
 
100
            # We don't seem to have a simple set_last_revision(), so we
 
101
            # implement it here.
 
102
            cur_revno, cur_revision_id = self._branch.last_revision_info()
 
103
            g = self._branch.repository.get_graph()
 
104
            new_revno = g.find_distance_to_null(new_revision_id,
 
105
                                                [(cur_revision_id, cur_revno)])
 
106
            self._branch.set_last_revision_info(new_revno, new_revision_id)
 
107
        finally:
 
108
            self._branch.unlock()
 
109
        if self._tree is not None:
 
110
            # We are currently processing a series, but when switching branch
 
111
            # pointers, it is easiest to just create a new memory tree.
 
112
            # That way we are sure to have the right files-on-disk
 
113
            # We are cheating a little bit here, and locking the new tree
 
114
            # before the old tree is unlocked. But that way the branch stays
 
115
            # locked throughout.
 
116
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
117
            new_tree.lock_write()
 
118
            self._tree.unlock()
 
119
            self._tree = new_tree
 
120
 
 
121
    def start_series(self):
 
122
        """We will be creating a series of commits.
 
123
 
 
124
        This allows us to hold open the locks while we are processing.
 
125
 
 
126
        Make sure to call 'finish_series' when you are done.
 
127
        """
 
128
        if self._tree is not None:
 
129
            raise AssertionError('You cannot start a new series while a'
 
130
                                 ' series is already going.')
 
131
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
132
        self._tree.lock_write()
 
133
 
 
134
    def finish_series(self):
 
135
        """Call this after start_series to unlock the various objects."""
 
136
        self._tree.unlock()
 
137
        self._tree = None
 
138
 
 
139
    def build_snapshot(self, revision_id, parent_ids, actions,
 
140
                       message=None):
 
141
        """Build a commit, shaped in a specific way.
 
142
 
 
143
        :param revision_id: The handle for the new commit, can be None
 
144
        :param parent_ids: A list of parent_ids to use for the commit.
 
145
            It can be None, which indicates to use the last commit.
 
146
        :param actions: A list of actions to perform. Supported actions are:
 
147
            ('add', ('path', 'file-id', 'kind', 'content' or None))
 
148
            ('modify', ('file-id', 'new-content'))
 
149
            ('unversion', 'file-id')
 
150
            ('rename', ('orig-path', 'new-path'))
 
151
        :param message: An optional commit message, if not supplied, a default
 
152
            commit message will be written.
 
153
        :return: The revision_id of the new commit
 
154
        """
 
155
        if parent_ids is not None:
 
156
            base_id = parent_ids[0]
 
157
            if base_id != self._branch.last_revision():
 
158
                self._move_branch_pointer(base_id)
 
159
 
 
160
        if self._tree is not None:
 
161
            tree = self._tree
 
162
        else:
 
163
            tree = memorytree.MemoryTree.create_on_branch(self._branch)
 
164
        tree.lock_write()
 
165
        try:
 
166
            if parent_ids is not None:
 
167
                tree.set_parent_ids(parent_ids)
 
168
            # Unfortunately, MemoryTree.add(directory) just creates an
 
169
            # inventory entry. And the only public function to create a
 
170
            # directory is MemoryTree.mkdir() which creates the directory, but
 
171
            # also always adds it. So we have to use a multi-pass setup.
 
172
            to_add_directories = []
 
173
            to_add_files = []
 
174
            to_add_file_ids = []
 
175
            to_add_kinds = []
 
176
            new_contents = {}
 
177
            to_unversion_ids = []
 
178
            to_rename = []
 
179
            for action, info in actions:
 
180
                if action == 'add':
 
181
                    path, file_id, kind, content = info
 
182
                    if kind == 'directory':
 
183
                        to_add_directories.append((path, file_id))
 
184
                    else:
 
185
                        to_add_files.append(path)
 
186
                        to_add_file_ids.append(file_id)
 
187
                        to_add_kinds.append(kind)
 
188
                        if content is not None:
 
189
                            new_contents[file_id] = content
 
190
                elif action == 'modify':
 
191
                    file_id, content = info
 
192
                    new_contents[file_id] = content
 
193
                elif action == 'unversion':
 
194
                    to_unversion_ids.append(info)
 
195
                elif action == 'rename':
 
196
                    from_relpath, to_relpath = info
 
197
                    to_rename.append((from_relpath, to_relpath))
 
198
                else:
 
199
                    raise ValueError('Unknown build action: "%s"' % (action,))
 
200
            if to_unversion_ids:
 
201
                tree.unversion(to_unversion_ids)
 
202
            for path, file_id in to_add_directories:
 
203
                if path == '':
 
204
                    # Special case, because the path already exists
 
205
                    tree.add([path], [file_id], ['directory'])
 
206
                else:
 
207
                    tree.mkdir(path, file_id)
 
208
            for from_relpath, to_relpath in to_rename:
 
209
                tree.rename_one(from_relpath, to_relpath)
 
210
            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
 
211
            for file_id, content in new_contents.iteritems():
 
212
                tree.put_file_bytes_non_atomic(file_id, content)
 
213
            return self._do_commit(tree, message=message, rev_id=revision_id) 
 
214
        finally:
 
215
            tree.unlock()
 
216
 
 
217
    def get_branch(self):
 
218
        """Return the branch created by the builder."""
 
219
        return self._branch