~bzr-pqm/bzr/bzr.dev

4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
1
# Copyright (C) 2007, 2008, 2009 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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
16
17
"""Utility for create branches with particular contents."""
18
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
19
from bzrlib import (
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
20
    bzrdir,
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
21
    commit,
22
    errors,
23
    memorytree,
24
    )
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
25
26
27
class BranchBuilder(object):
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
28
    r"""A BranchBuilder aids creating Branches with particular shapes.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
29
2466.7.7 by Robert Collins
Document basic usage.
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
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
34
    This is meant as a helper for the test suite, not as a general class for
35
    real data.
36
2466.7.7 by Robert Collins
Document basic usage.
37
    For instance:
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
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()
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
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.
2466.7.7 by Robert Collins
Document basic usage.
57
    """
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
58
4257.3.8 by Andrew Bennetts
Fix TestCase.make_branch_builder to make a branch in the specified format. Also add an interrepo test scenario for KnitPack1 -> KnitPack6RichRoot, which fails.
59
    def __init__(self, transport=None, format=None, branch=None):
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
60
        """Construct a BranchBuilder on transport.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
61
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
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.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
65
        :param format: Either a BzrDirFormat, or the name of a format in the
66
            bzrdir format registry for the branch to be built.
4257.3.8 by Andrew Bennetts
Fix TestCase.make_branch_builder to make a branch in the specified format. Also add an interrepo test scenario for KnitPack1 -> KnitPack6RichRoot, which fails.
67
        :param branch: An already constructed branch to use.  This param is
68
            mutually exclusive with the transport and format params.
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
69
        """
4257.3.8 by Andrew Bennetts
Fix TestCase.make_branch_builder to make a branch in the specified format. Also add an interrepo test scenario for KnitPack1 -> KnitPack6RichRoot, which fails.
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)
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
87
        self._tree = None
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
88
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
89
    def build_commit(self, **commit_kwargs):
90
        """Build a commit on the branch.
4070.4.12 by Martin Pool
Kill trailing whitespace
91
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
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
        """
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
98
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
99
        tree.lock_write()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
100
        try:
101
            tree.add('')
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
102
            return self._do_commit(tree, **commit_kwargs)
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
103
        finally:
104
            tree.unlock()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
105
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
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
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
114
    def _move_branch_pointer(self, new_revision_id):
115
        """Point self._branch to a different revision id."""
116
        self._branch.lock_write()
117
        try:
118
            # We don't seem to have a simple set_last_revision(), so we
119
            # implement it here.
120
            cur_revno, cur_revision_id = self._branch.last_revision_info()
121
            g = self._branch.repository.get_graph()
122
            new_revno = g.find_distance_to_null(new_revision_id,
123
                                                [(cur_revision_id, cur_revno)])
124
            self._branch.set_last_revision_info(new_revno, new_revision_id)
125
        finally:
126
            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.
127
        if self._tree is not None:
128
            # We are currently processing a series, but when switching branch
129
            # pointers, it is easiest to just create a new memory tree.
130
            # That way we are sure to have the right files-on-disk
131
            # We are cheating a little bit here, and locking the new tree
132
            # before the old tree is unlocked. But that way the branch stays
133
            # locked throughout.
134
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
135
            new_tree.lock_write()
136
            self._tree.unlock()
137
            self._tree = new_tree
138
139
    def start_series(self):
140
        """We will be creating a series of commits.
141
142
        This allows us to hold open the locks while we are processing.
143
144
        Make sure to call 'finish_series' when you are done.
145
        """
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
146
        if self._tree is not None:
147
            raise AssertionError('You cannot start a new series while a'
148
                                 ' 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.
149
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
150
        self._tree.lock_write()
151
152
    def finish_series(self):
153
        """Call this after start_series to unlock the various objects."""
154
        self._tree.unlock()
155
        self._tree = None
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
156
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
157
    def build_snapshot(self, revision_id, parent_ids, actions,
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
158
                       message=None, timestamp=None):
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
159
        """Build a commit, shaped in a specific way.
160
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
161
        :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.
162
        :param parent_ids: A list of parent_ids to use for the commit.
163
            It can be None, which indicates to use the last commit.
164
        :param actions: A list of actions to perform. Supported actions are:
165
            ('add', ('path', 'file-id', 'kind', 'content' or None))
166
            ('modify', ('file-id', 'new-content'))
167
            ('unversion', 'file-id')
3567.5.2 by John Arbash Meinel
'rename' is a supported action.
168
            ('rename', ('orig-path', 'new-path'))
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
169
        :param message: An optional commit message, if not supplied, a default
170
            commit message will be written.
4070.4.12 by Martin Pool
Kill trailing whitespace
171
        :param timestamp: If non-None, set the timestamp of the commit to this
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
172
            value.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
173
        :return: The revision_id of the new commit
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
174
        """
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
175
        if parent_ids is not None:
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
176
            base_id = parent_ids[0]
177
            if base_id != self._branch.last_revision():
178
                self._move_branch_pointer(base_id)
179
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
180
        if self._tree is not None:
181
            tree = self._tree
182
        else:
183
            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.
184
        tree.lock_write()
185
        try:
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
186
            if parent_ids is not None:
187
                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().
188
            # Unfortunately, MemoryTree.add(directory) just creates an
189
            # inventory entry. And the only public function to create a
190
            # directory is MemoryTree.mkdir() which creates the directory, but
191
            # also always adds it. So we have to use a multi-pass setup.
192
            to_add_directories = []
193
            to_add_files = []
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
194
            to_add_file_ids = []
195
            to_add_kinds = []
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
196
            new_contents = {}
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
197
            to_unversion_ids = []
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
198
            to_rename = []
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
199
            for action, info in actions:
200
                if action == 'add':
201
                    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().
202
                    if kind == 'directory':
203
                        to_add_directories.append((path, file_id))
204
                    else:
205
                        to_add_files.append(path)
206
                        to_add_file_ids.append(file_id)
207
                        to_add_kinds.append(kind)
208
                        if content is not None:
209
                            new_contents[file_id] = content
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
210
                elif action == 'modify':
211
                    file_id, content = info
212
                    new_contents[file_id] = content
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
213
                elif action == 'unversion':
214
                    to_unversion_ids.append(info)
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
215
                elif action == 'rename':
216
                    from_relpath, to_relpath = info
217
                    to_rename.append((from_relpath, to_relpath))
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
218
                else:
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
219
                    raise ValueError('Unknown build action: "%s"' % (action,))
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
220
            if to_unversion_ids:
221
                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().
222
            for path, file_id in to_add_directories:
223
                if path == '':
224
                    # Special case, because the path already exists
225
                    tree.add([path], [file_id], ['directory'])
226
                else:
227
                    tree.mkdir(path, file_id)
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
228
            for from_relpath, to_relpath in to_rename:
229
                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().
230
            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
231
            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.
232
                tree.put_file_bytes_non_atomic(file_id, content)
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
233
            return self._do_commit(tree, message=message, rev_id=revision_id,
234
                timestamp=timestamp)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
235
        finally:
236
            tree.unlock()
237
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
238
    def get_branch(self):
239
        """Return the branch created by the builder."""
240
        return self._branch