~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branchbuilder.py

Streamline _walkdirs_utf8 for utf8 file systems, reducing time to traverse a mozilla tree from 1s to .6 seconds. (Robert Collins)

Show diffs side-by-side

added added

removed removed

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