~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,
5540.1.1 by Gary van der Merwe
Make ``BranchBuilder.build_snapshot`` accept parent_ids == ['null:'].
24
    revision,
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
25
    )
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
26
27
28
class BranchBuilder(object):
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
29
    r"""A BranchBuilder aids creating Branches with particular shapes.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
30
2466.7.7 by Robert Collins
Document basic usage.
31
    The expected way to use BranchBuilder is to construct a
32
    BranchBuilder on the transport you want your branch on, and then call
33
    appropriate build_ methods on it to get the shape of history you want.
34
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
35
    This is meant as a helper for the test suite, not as a general class for
36
    real data.
37
2466.7.7 by Robert Collins
Document basic usage.
38
    For instance:
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
39
40
    >>> from bzrlib.transport.memory import MemoryTransport
41
    >>> builder = BranchBuilder(MemoryTransport("memory:///"))
42
    >>> builder.start_series()
43
    >>> builder.build_snapshot('rev-id', None, [
44
    ...     ('add', ('', 'root-id', 'directory', '')),
5574.6.3 by Vincent Ladeuil
Revert the fix in the doctest so it fails again.
45
    ...     ('add', ('filename', 'f-id', 'file', 'content\n'))])
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
46
    'rev-id'
47
    >>> builder.build_snapshot('rev2-id', ['rev-id'],
5574.6.3 by Vincent Ladeuil
Revert the fix in the doctest so it fails again.
48
    ...     [('modify', ('f-id', 'new-content\n'))])
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
49
    'rev2-id'
50
    >>> builder.finish_series()
51
    >>> branch = builder.get_branch()
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
52
53
    :ivar _tree: This is a private member which is not meant to be modified by
54
        users of this class. While a 'series' is in progress, it should hold a
55
        MemoryTree with the contents of the last commit (ready to be modified
56
        by the next build_snapshot command) with a held write lock. Outside of
57
        a series in progress, it should be None.
2466.7.7 by Robert Collins
Document basic usage.
58
    """
2466.7.3 by Robert Collins
Create bzrlib.branchbuilder.
59
4476.3.59 by Andrew Bennetts
Undo changes that aren't needed anymore.
60
    def __init__(self, transport=None, format=None, branch=None):
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
61
        """Construct a BranchBuilder on transport.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
62
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
63
        :param transport: The transport the branch should be created on.
64
            If the path of the transport does not exist but its parent does
65
            it will be created.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
66
        :param format: Either a BzrDirFormat, or the name of a format in the
67
            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.
68
        :param branch: An already constructed branch to use.  This param is
69
            mutually exclusive with the transport and format params.
2466.7.5 by Robert Collins
Better docstring for BranchBuilder.__init__.
70
        """
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.
71
        if branch is not None:
72
            if format is not None:
73
                raise AssertionError(
74
                    "branch and format kwargs are mutually exclusive")
75
            if transport is not None:
76
                raise AssertionError(
77
                    "branch and transport kwargs are mutually exclusive")
78
            self._branch = branch
79
        else:
80
            if not transport.has('.'):
81
                transport.mkdir('.')
82
            if format is None:
83
                format = 'default'
84
            if isinstance(format, str):
85
                format = bzrdir.format_registry.make_bzrdir(format)
86
            self._branch = bzrdir.BzrDir.create_branch_convenience(
87
                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.
88
        self._tree = None
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
89
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
90
    def build_commit(self, **commit_kwargs):
91
        """Build a commit on the branch.
4070.4.12 by Martin Pool
Kill trailing whitespace
92
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
93
        This makes a commit with no real file content for when you only want
94
        to look at the revision graph structure.
95
96
        :param commit_kwargs: Arguments to pass through to commit, such as
97
             timestamp.
98
        """
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
99
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
100
        tree.lock_write()
2466.7.9 by Robert Collins
Return the commited revision id from BranchBuilder.build_commit to save later instrospection.
101
        try:
102
            tree.add('')
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
103
            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.
104
        finally:
105
            tree.unlock()
2466.7.6 by Robert Collins
Add BranchBuilder.build_commit.
106
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
107
    def _do_commit(self, tree, message=None, message_callback=None, **kwargs):
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
108
        reporter = commit.NullCommitReporter()
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
109
        if message is None and message_callback is None:
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
110
            message = u'commit %d' % (self._branch.revno() + 1,)
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
111
        return tree.commit(message, message_callback=message_callback,
3825.3.2 by Martin Pool
Correct example of branchbuilder and change to a doctest, and refactor
112
            reporter=reporter,
113
            **kwargs)
114
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
115
    def _move_branch_pointer(self, new_revision_id,
116
        allow_leftmost_as_ghost=False):
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
117
        """Point self._branch to a different revision id."""
118
        self._branch.lock_write()
119
        try:
120
            # We don't seem to have a simple set_last_revision(), so we
121
            # implement it here.
122
            cur_revno, cur_revision_id = self._branch.last_revision_info()
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
123
            try:
124
                g = self._branch.repository.get_graph()
125
                new_revno = g.find_distance_to_null(new_revision_id,
126
                    [(cur_revision_id, cur_revno)])
127
                self._branch.set_last_revision_info(new_revno, new_revision_id)
128
            except errors.GhostRevisionsHaveNoRevno:
129
                if not allow_leftmost_as_ghost:
130
                    raise
131
                new_revno = 1
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
132
        finally:
133
            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.
134
        if self._tree is not None:
135
            # We are currently processing a series, but when switching branch
136
            # pointers, it is easiest to just create a new memory tree.
137
            # That way we are sure to have the right files-on-disk
138
            # We are cheating a little bit here, and locking the new tree
139
            # before the old tree is unlocked. But that way the branch stays
140
            # locked throughout.
141
            new_tree = memorytree.MemoryTree.create_on_branch(self._branch)
142
            new_tree.lock_write()
143
            self._tree.unlock()
144
            self._tree = new_tree
145
146
    def start_series(self):
147
        """We will be creating a series of commits.
148
149
        This allows us to hold open the locks while we are processing.
150
151
        Make sure to call 'finish_series' when you are done.
152
        """
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
153
        if self._tree is not None:
154
            raise AssertionError('You cannot start a new series while a'
155
                                 ' 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.
156
        self._tree = memorytree.MemoryTree.create_on_branch(self._branch)
157
        self._tree.lock_write()
158
159
    def finish_series(self):
160
        """Call this after start_series to unlock the various objects."""
161
        self._tree.unlock()
162
        self._tree = None
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
163
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
164
    def build_snapshot(self, revision_id, parent_ids, actions,
4454.3.24 by John Arbash Meinel
update BranchBuilder to support 'committer'
165
        message=None, timestamp=None, allow_leftmost_as_ghost=False,
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
166
        committer=None, timezone=None, message_callback=None):
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
167
        """Build a commit, shaped in a specific way.
168
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
169
        Most of the actions are self-explanatory.  'flush' is special action to
170
        break a series of actions into discrete steps so that complex changes
171
        (such as unversioning a file-id and re-adding it with a different kind)
172
        can be expressed in a way that will clearly work.
173
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
174
        :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.
175
        :param parent_ids: A list of parent_ids to use for the commit.
176
            It can be None, which indicates to use the last commit.
177
        :param actions: A list of actions to perform. Supported actions are:
178
            ('add', ('path', 'file-id', 'kind', 'content' or None))
179
            ('modify', ('file-id', 'new-content'))
180
            ('unversion', 'file-id')
3567.5.2 by John Arbash Meinel
'rename' is a supported action.
181
            ('rename', ('orig-path', 'new-path'))
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
182
            ('flush', None)
3567.4.15 by John Arbash Meinel
Allow setting the commit message.
183
        :param message: An optional commit message, if not supplied, a default
184
            commit message will be written.
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
185
        :param message_callback: A message callback to use for the commit, as
186
            per mutabletree.commit.
4070.4.12 by Martin Pool
Kill trailing whitespace
187
        :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
188
            value.
4523.2.1 by Vincent Ladeuil
Fix TZ-dependent tests.
189
        :param timezone: An optional timezone for timestamp.
4454.3.24 by John Arbash Meinel
update BranchBuilder to support 'committer'
190
        :param committer: An optional username to use for commit
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
191
        :param allow_leftmost_as_ghost: True if the leftmost parent should be
192
            permitted to be a ghost.
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
193
        :return: The revision_id of the new commit
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
194
        """
3567.4.8 by John Arbash Meinel
Add the ability to force a basis for a revision.
195
        if parent_ids is not None:
5540.2.1 by Gary van der Merwe
Allow for BranchBuilder.build_snapshot to accept parent_ids = [], rather than ['null:'].
196
            if len(parent_ids) == 0:
197
                base_id = revision.NULL_REVISION
198
            else:
199
                base_id = parent_ids[0]
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
200
            if base_id != self._branch.last_revision():
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
201
                self._move_branch_pointer(base_id,
202
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
3567.4.10 by John Arbash Meinel
Clean up the build_snapshot api a bit.
203
3567.4.17 by John Arbash Meinel
Add the ability to define a series of commits, which allows us to hold open the locks.
204
        if self._tree is not None:
205
            tree = self._tree
206
        else:
207
            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.
208
        tree.lock_write()
209
        try:
5540.2.1 by Gary van der Merwe
Allow for BranchBuilder.build_snapshot to accept parent_ids = [], rather than ['null:'].
210
            if parent_ids is not None:
4324.3.1 by Robert Collins
When adding rich root data follow the standard revision graph rules, so it does not create 'inconstent parents'.
211
                tree.set_parent_ids(parent_ids,
212
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
213
            # Unfortunately, MemoryTree.add(directory) just creates an
214
            # inventory entry. And the only public function to create a
215
            # directory is MemoryTree.mkdir() which creates the directory, but
216
            # also always adds it. So we have to use a multi-pass setup.
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
217
            pending = _PendingActions()
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
218
            for action, info in actions:
219
                if action == 'add':
220
                    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().
221
                    if kind == 'directory':
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
222
                        pending.to_add_directories.append((path, file_id))
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
223
                    else:
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
224
                        pending.to_add_files.append(path)
225
                        pending.to_add_file_ids.append(file_id)
226
                        pending.to_add_kinds.append(kind)
3567.4.7 by John Arbash Meinel
Revert back to using MemoryTree.mkdir() rather than creating the directory during add().
227
                        if content is not None:
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
228
                            pending.new_contents[file_id] = content
3567.4.3 by John Arbash Meinel
Add an action for modifying an existing file
229
                elif action == 'modify':
230
                    file_id, content = info
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
231
                    pending.new_contents[file_id] = content
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
232
                elif action == 'unversion':
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
233
                    pending.to_unversion_ids.add(info)
3567.5.1 by John Arbash Meinel
Implement rename_one on MemoryTree, and expose that in the Branch Builder
234
                elif action == 'rename':
235
                    from_relpath, to_relpath = info
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
236
                    pending.to_rename.append((from_relpath, to_relpath))
237
                elif action == 'flush':
238
                    self._flush_pending(tree, pending)
239
                    pending = _PendingActions()
3567.4.4 by John Arbash Meinel
Add the ability to 'unversion' files, and handle unknown actions.
240
                else:
3567.4.18 by John Arbash Meinel
Apply the review changes from Martin to the exact patch he approved.
241
                    raise ValueError('Unknown build action: "%s"' % (action,))
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
242
            self._flush_pending(tree, pending)
4070.5.1 by Martin Pool
BranchBuilder now takes a timestamp for commits
243
            return self._do_commit(tree, message=message, rev_id=revision_id,
5060.1.1 by Robert Collins
``bzrlib.branchbuilder.BranchBuilder.build_snapshot`` now accepts a
244
                timestamp=timestamp, timezone=timezone, committer=committer,
245
                message_callback=message_callback)
3567.4.1 by John Arbash Meinel
Initial work to have BranchBuilder allow us to do tree-shape work.
246
        finally:
247
            tree.unlock()
248
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
249
    def _flush_pending(self, tree, pending):
250
        """Flush the pending actions in 'pending', i.e. apply them to 'tree'."""
251
        for path, file_id in pending.to_add_directories:
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
252
            if path == '':
253
                old_id = tree.path2id(path)
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
254
                if old_id is not None and old_id in pending.to_unversion_ids:
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
255
                    # We're overwriting this path, no need to unversion
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
256
                    pending.to_unversion_ids.discard(old_id)
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
257
                # Special case, because the path already exists
258
                tree.add([path], [file_id], ['directory'])
259
            else:
260
                tree.mkdir(path, file_id)
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
261
        for from_relpath, to_relpath in pending.to_rename:
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
262
            tree.rename_one(from_relpath, to_relpath)
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
263
        if pending.to_unversion_ids:
264
            tree.unversion(pending.to_unversion_ids)
265
        tree.add(pending.to_add_files, pending.to_add_file_ids, pending.to_add_kinds)
266
        for file_id, content in pending.new_contents.iteritems():
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
267
            tree.put_file_bytes_non_atomic(file_id, content)
268
2466.7.4 by Robert Collins
Add BranchBuilder.get_branch().
269
    def get_branch(self):
270
        """Return the branch created by the builder."""
271
        return self._branch
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
272
273
6008.2.5 by Andrew Bennetts
Rename 'checkpoint' to 'flush', add some unit tests and more comments.
274
class _PendingActions(object):
275
    """Pending actions for build_snapshot to take.
276
277
    This is just a simple class to hold a bunch of the intermediate state of
278
    build_snapshot in single object.
279
    """
6008.2.4 by Andrew Bennetts
Add ('checkpoint', None) action to BranchBuilder.build_snapshot to allow complex set of adds/deletes/renames to work without reinventing TreeTransform.
280
281
    def __init__(self):
282
        self.to_add_directories = []
283
        self.to_add_files = []
284
        self.to_add_file_ids = []
285
        self.to_add_kinds = []
286
        self.new_contents = {}
287
        self.to_unversion_ids = set()
288
        self.to_rename = []
289