~bzr-pqm/bzr/bzr.dev

3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
1
# Copyright (C) 2007, 2008 Canonical Ltd
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
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
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
19
from bzrlib import bzrdir, errors, memorytree
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
20
21
22
class BranchBuilder(object):
2466.7.7 by Robert Collins
Document basic usage.
23
    """A BranchBuilder aids creating Branches with particular shapes.
24
    
25
    The expected way to use BranchBuilder is to construct a
26
    BranchBuilder on the transport you want your branch on, and then call
27
    appropriate build_ methods on it to get the shape of history you want.
28
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
29
    This is meant as a helper for the test suite, not as a general class for
30
    real data.
31
2466.7.7 by Robert Collins
Document basic usage.
32
    For instance:
33
      builder = BranchBuilder(self.get_transport().clone('relpath'))
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
34
      builder.start_series()
35
      builder.build_snapshot('rev-id', [],
36
        [('add', ('filename', 'f-id', 'file', 'content\n'))])
37
      builder.build_snapshot('rev2-id', ['rev-id'],
38
        [('modify', ('f-id', 'new-content\n'))])
39
      builder.finish_series()
2466.7.7 by Robert Collins
Document basic usage.
40
      branch = builder.get_branch()
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
41
42
    :ivar _tree: This is a private member which is not meant to be modified by
43
        users of this class. While a 'series' is in progress, it should hold a
44
        MemoryTree with the contents of the last commit (ready to be modified
45
        by the next build_snapshot command) with a held write lock. Outside of
46
        a series in progress, it should be None.
2466.7.7 by Robert Collins
Document basic usage.
47
    """
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
48
2466.7.10 by Robert Collins
Add a format parameter to BranchBuilder.
49
    def __init__(self, transport, format=None):
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
50
        """Construct a BranchBuilder on transport.
51
        
52
        :param transport: The transport the branch should be created on.
53
            If the path of the transport does not exist but its parent does
54
            it will be created.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
55
        :param format: Either a BzrDirFormat, or the name of a format in the
56
            bzrdir format registry for the branch to be built.
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
57
        """
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
58
        if not transport.has('.'):
59
            transport.mkdir('.')
2466.7.10 by Robert Collins
Add a format parameter to BranchBuilder.
60
        if format is None:
61
            format = 'default'
3567.4.12 by John Arbash Meinel
Expose the branch building framework to the test suite.
62
        if isinstance(format, str):
63
            format = bzrdir.format_registry.make_bzrdir(format)
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
64
        self._branch = bzrdir.BzrDir.create_branch_convenience(transport.base,
3567.4.13 by John Arbash Meinel
Test that make_branch_builder works on a real filesystem.
65
            format=format, force_new_tree=False)
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
66
        self._tree = None
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
67
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
68
    def build_commit(self):
69
        """Build a commit on the branch."""
70
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
71
        tree.lock_write()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
72
        try:
73
            tree.add('')
74
            return tree.commit('commit %d' % (self._branch.revno() + 1))
75
        finally:
76
            tree.unlock()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
77
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
78
    def _move_branch_pointer(self, new_revision_id):
79
        """Point self._branch to a different revision id."""
80
        self._branch.lock_write()
81
        try:
82
            # We don't seem to have a simple set_last_revision(), so we
83
            # implement it here.
84
            cur_revno, cur_revision_id = self._branch.last_revision_info()
85
            g = self._branch.repository.get_graph()
86
            new_revno = g.find_distance_to_null(new_revision_id,
87
                                                [(cur_revision_id, cur_revno)])
88
            self._branch.set_last_revision_info(new_revno, new_revision_id)
89
        finally:
90
            self._branch.unlock()
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
91
        if self._tree is not None:
92
            # We are currently processing a series, but when switching branch
93
            # pointers, it is easiest to just create a new memory tree.
94
            # That way we are sure to have the right files-on-disk
95
            # We are cheating a little bit here, and locking the new tree
96
            # before the old tree is unlocked. But that way the branch stays
97
            # locked throughout.
98
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
99
            new_tree.lock_write()
100
            self._tree.unlock()
101
            self._tree = new_tree
102
103
    def start_series(self):
104
        """We will be creating a series of commits.
105
106
        This allows us to hold open the locks while we are processing.
107
108
        Make sure to call 'finish_series' when you are done.
109
        """
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
110
        if self._tree is not None:
111
            raise AssertionError('You cannot start a new series while a'
112
                                 ' series is already going.')
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
113
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
114
        self._tree.lock_write()
115
116
    def finish_series(self):
117
        """Call this after start_series to unlock the various objects."""
118
        self._tree.unlock()
119
        self._tree = None
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
120
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
121
    def build_snapshot(self, revision_id, parent_ids, actions,
122
                       message=None):
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
123
        """Build a commit, shaped in a specific way.
124
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
125
        :param revision_id: The handle for the new commit, can be None
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
126
        :param parent_ids: A list of parent_ids to use for the commit.
127
            It can be None, which indicates to use the last commit.
128
        :param actions: A list of actions to perform. Supported actions are:
129
            ('add', ('path', 'file-id', 'kind', 'content' or None))
130
            ('modify', ('file-id', 'new-content'))
131
            ('unversion', 'file-id')
3567.5.2 by John Arbash Meinel
'rename' is a supported action.
132
            ('rename', ('orig-path', 'new-path'))
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
133
        :param message: An optional commit message, if not supplied, a default
134
            commit message will be written.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
135
        :return: The revision_id of the new commit
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
136
        """
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
137
        if parent_ids is not None:
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
138
            base_id = parent_ids[0]
139
            if base_id != self._branch.last_revision():
140
                self._move_branch_pointer(base_id)
141
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
142
        if self._tree is not None:
143
            tree = self._tree
144
        else:
145
            tree = memorytree.MemoryTree.create_on_branch(self._branch)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
146
        tree.lock_write()
147
        try:
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
148
            if parent_ids is not None:
149
                tree.set_parent_ids(parent_ids)
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
150
            # Unfortunately, MemoryTree.add(directory) just creates an
151
            # inventory entry. And the only public function to create a
152
            # directory is MemoryTree.mkdir() which creates the directory, but
153
            # also always adds it. So we have to use a multi-pass setup.
154
            to_add_directories = []
155
            to_add_files = []
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
156
            to_add_file_ids = []
157
            to_add_kinds = []
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
158
            new_contents = {}
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
159
            to_unversion_ids = []
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
160
            to_rename = []
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
161
            for action, info in actions:
162
                if action == 'add':
163
                    path, file_id, kind, content = info
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
164
                    if kind == 'directory':
165
                        to_add_directories.append((path, file_id))
166
                    else:
167
                        to_add_files.append(path)
168
                        to_add_file_ids.append(file_id)
169
                        to_add_kinds.append(kind)
170
                        if content is not None:
171
                            new_contents[file_id] = content
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
172
                elif action == 'modify':
173
                    file_id, content = info
174
                    new_contents[file_id] = content
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
175
                elif action == 'unversion':
176
                    to_unversion_ids.append(info)
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
177
                elif action == 'rename':
178
                    from_relpath, to_relpath = info
179
                    to_rename.append((from_relpath, to_relpath))
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
180
                else:
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
181
                    raise ValueError('Unknown build action: "%s"' % (action,))
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
182
            if to_unversion_ids:
183
                tree.unversion(to_unversion_ids)
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
184
            for path, file_id in to_add_directories:
185
                if path == '':
186
                    # Special case, because the path already exists
187
                    tree.add([path], [file_id], ['directory'])
188
                else:
189
                    tree.mkdir(path, file_id)
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
190
            for from_relpath, to_relpath in to_rename:
191
                tree.rename_one(from_relpath, to_relpath)
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
192
            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
193
            for file_id, content in new_contents.iteritems():
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
194
                tree.put_file_bytes_non_atomic(file_id, content)
195
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
196
            if message is None:
197
                message = u'commit %d' % (self._branch.revno() + 1,)
198
            return tree.commit(message, rev_id=revision_id)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
199
        finally:
200
            tree.unlock()
201
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
202
    def get_branch(self):
203
        """Return the branch created by the builder."""
204
        return self._branch