~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branchbuilder.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-11 13:47:06 UTC
  • mfrom: (5051.3.16 use-branch-open)
  • Revision ID: pqm@pqm.ubuntu.com-20100311134706-kaerqhx3lf7xn6rh
(Jelmer) Pass colocated branch names further down the call stack.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2007, 2008, 2009 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Utility for create branches with particular contents."""
18
18
 
19
 
from bzrlib import bzrdir, errors, memorytree
 
19
from bzrlib import (
 
20
    bzrdir,
 
21
    commit,
 
22
    errors,
 
23
    memorytree,
 
24
    )
20
25
 
21
26
 
22
27
class BranchBuilder(object):
23
 
    """A BranchBuilder aids creating Branches with particular shapes.
24
 
    
 
28
    r"""A BranchBuilder aids creating Branches with particular shapes.
 
29
 
25
30
    The expected way to use BranchBuilder is to construct a
26
31
    BranchBuilder on the transport you want your branch on, and then call
27
32
    appropriate build_ methods on it to get the shape of history you want.
30
35
    real data.
31
36
 
32
37
    For instance:
33
 
      builder = BranchBuilder(self.get_transport().clone('relpath'))
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()
40
 
      branch = builder.get_branch()
 
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()
41
51
 
42
52
    :ivar _tree: This is a private member which is not meant to be modified by
43
53
        users of this class. While a 'series' is in progress, it should hold a
46
56
        a series in progress, it should be None.
47
57
    """
48
58
 
49
 
    def __init__(self, transport, format=None):
 
59
    def __init__(self, transport=None, format=None, branch=None):
50
60
        """Construct a BranchBuilder on transport.
51
 
        
 
61
 
52
62
        :param transport: The transport the branch should be created on.
53
63
            If the path of the transport does not exist but its parent does
54
64
            it will be created.
55
65
        :param format: Either a BzrDirFormat, or the name of a format in the
56
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.
57
69
        """
58
 
        if not transport.has('.'):
59
 
            transport.mkdir('.')
60
 
        if format is None:
61
 
            format = 'default'
62
 
        if isinstance(format, str):
63
 
            format = bzrdir.format_registry.make_bzrdir(format)
64
 
        self._branch = bzrdir.BzrDir.create_branch_convenience(transport.base,
65
 
            format=format, force_new_tree=False)
 
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)
66
87
        self._tree = None
67
88
 
68
 
    def build_commit(self):
69
 
        """Build a commit on the branch."""
 
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
        """
70
98
        tree = memorytree.MemoryTree.create_on_branch(self._branch)
71
99
        tree.lock_write()
72
100
        try:
73
101
            tree.add('')
74
 
            return tree.commit('commit %d' % (self._branch.revno() + 1))
 
102
            return self._do_commit(tree, **commit_kwargs)
75
103
        finally:
76
104
            tree.unlock()
77
105
 
78
 
    def _move_branch_pointer(self, new_revision_id):
 
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):
79
116
        """Point self._branch to a different revision id."""
80
117
        self._branch.lock_write()
81
118
        try:
82
119
            # We don't seem to have a simple set_last_revision(), so we
83
120
            # implement it here.
84
121
            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)
 
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
89
131
        finally:
90
132
            self._branch.unlock()
91
133
        if self._tree is not None:
119
161
        self._tree = None
120
162
 
121
163
    def build_snapshot(self, revision_id, parent_ids, actions,
122
 
                       message=None):
 
164
        message=None, timestamp=None, allow_leftmost_as_ghost=False,
 
165
        committer=None, timezone=None, message_callback=None):
123
166
        """Build a commit, shaped in a specific way.
124
167
 
125
168
        :param revision_id: The handle for the new commit, can be None
129
172
            ('add', ('path', 'file-id', 'kind', 'content' or None))
130
173
            ('modify', ('file-id', 'new-content'))
131
174
            ('unversion', 'file-id')
132
 
            # not supported yet: ('rename', ('orig-path', 'new-path'))
 
175
            ('rename', ('orig-path', 'new-path'))
133
176
        :param message: An optional commit message, if not supplied, a default
134
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.
135
186
        :return: The revision_id of the new commit
136
187
        """
137
188
        if parent_ids is not None:
138
189
            base_id = parent_ids[0]
139
190
            if base_id != self._branch.last_revision():
140
 
                self._move_branch_pointer(base_id)
 
191
                self._move_branch_pointer(base_id,
 
192
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
141
193
 
142
194
        if self._tree is not None:
143
195
            tree = self._tree
146
198
        tree.lock_write()
147
199
        try:
148
200
            if parent_ids is not None:
149
 
                tree.set_parent_ids(parent_ids)
 
201
                tree.set_parent_ids(parent_ids,
 
202
                    allow_leftmost_as_ghost=allow_leftmost_as_ghost)
150
203
            # Unfortunately, MemoryTree.add(directory) just creates an
151
204
            # inventory entry. And the only public function to create a
152
205
            # directory is MemoryTree.mkdir() which creates the directory, but
157
210
            to_add_kinds = []
158
211
            new_contents = {}
159
212
            to_unversion_ids = []
160
 
            # TODO: MemoryTree doesn't support rename() or
161
 
            #       apply_inventory_delta, so we'll postpone allowing renames
162
 
            #       for now
163
 
            # to_rename = []
 
213
            to_rename = []
164
214
            for action, info in actions:
165
215
                if action == 'add':
166
216
                    path, file_id, kind, content = info
177
227
                    new_contents[file_id] = content
178
228
                elif action == 'unversion':
179
229
                    to_unversion_ids.append(info)
 
230
                elif action == 'rename':
 
231
                    from_relpath, to_relpath = info
 
232
                    to_rename.append((from_relpath, to_relpath))
180
233
                else:
181
234
                    raise ValueError('Unknown build action: "%s"' % (action,))
182
235
            if to_unversion_ids:
187
240
                    tree.add([path], [file_id], ['directory'])
188
241
                else:
189
242
                    tree.mkdir(path, file_id)
 
243
            for from_relpath, to_relpath in to_rename:
 
244
                tree.rename_one(from_relpath, to_relpath)
190
245
            tree.add(to_add_files, to_add_file_ids, to_add_kinds)
191
246
            for file_id, content in new_contents.iteritems():
192
247
                tree.put_file_bytes_non_atomic(file_id, content)
193
 
 
194
 
            if message is None:
195
 
                message = u'commit %d' % (self._branch.revno() + 1,)
196
 
            return tree.commit(message, rev_id=revision_id)
 
248
            return self._do_commit(tree, message=message, rev_id=revision_id,
 
249
                timestamp=timestamp, timezone=timezone, committer=committer,
 
250
                message_callback=message_callback)
197
251
        finally:
198
252
            tree.unlock()
199
253