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
17
17
"""Utility for create branches with particular contents."""
19
from bzrlib import bzrdir, errors, memorytree
22
28
class BranchBuilder(object):
23
"""A BranchBuilder aids creating Branches with particular shapes.
29
r"""A BranchBuilder aids creating Branches with particular shapes.
25
31
The expected way to use BranchBuilder is to construct a
26
32
BranchBuilder on the transport you want your branch on, and then call
27
33
appropriate build_ methods on it to get the shape of history you want.
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()
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', '')),
45
... ('add', ('filename', 'f-id', 'file', 'content\n'))])
47
>>> builder.build_snapshot('rev2-id', ['rev-id'],
48
... [('modify', ('f-id', 'new-content\n'))])
50
>>> builder.finish_series()
51
>>> branch = builder.get_branch()
42
53
:ivar _tree: This is a private member which is not meant to be modified by
43
54
users of this class. While a 'series' is in progress, it should hold a
46
57
a series in progress, it should be None.
49
def __init__(self, transport, format=None):
60
def __init__(self, transport=None, format=None, branch=None):
50
61
"""Construct a BranchBuilder on transport.
52
63
:param transport: The transport the branch should be created on.
53
64
If the path of the transport does not exist but its parent does
54
65
it will be created.
55
66
:param format: Either a BzrDirFormat, or the name of a format in the
56
67
bzrdir format registry for the branch to be built.
68
:param branch: An already constructed branch to use. This param is
69
mutually exclusive with the transport and format params.
58
if not transport.has('.'):
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)
71
if branch is not None:
72
if format is not None:
74
"branch and format kwargs are mutually exclusive")
75
if transport is not None:
77
"branch and transport kwargs are mutually exclusive")
80
if not transport.has('.'):
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)
68
def build_commit(self):
69
"""Build a commit on the branch."""
90
def build_commit(self, **commit_kwargs):
91
"""Build a commit on the branch.
93
This makes a commit with no real file content for when you only want
94
to look at the revision graph structure.
96
:param commit_kwargs: Arguments to pass through to commit, such as
70
99
tree = memorytree.MemoryTree.create_on_branch(self._branch)
74
return tree.commit('commit %d' % (self._branch.revno() + 1))
103
return self._do_commit(tree, **commit_kwargs)
78
def _move_branch_pointer(self, new_revision_id):
107
def _do_commit(self, tree, message=None, message_callback=None, **kwargs):
108
reporter = commit.NullCommitReporter()
109
if message is None and message_callback is None:
110
message = u'commit %d' % (self._branch.revno() + 1,)
111
return tree.commit(message, message_callback=message_callback,
115
def _move_branch_pointer(self, new_revision_id,
116
allow_leftmost_as_ghost=False):
79
117
"""Point self._branch to a different revision id."""
80
118
self._branch.lock_write()
82
120
# We don't seem to have a simple set_last_revision(), so we
83
121
# implement it here.
84
122
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)
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:
90
133
self._branch.unlock()
91
134
if self._tree is not None:
129
173
('add', ('path', 'file-id', 'kind', 'content' or None))
130
174
('modify', ('file-id', 'new-content'))
131
175
('unversion', 'file-id')
132
# not supported yet: ('rename', ('orig-path', 'new-path'))
176
('rename', ('orig-path', 'new-path'))
133
177
:param message: An optional commit message, if not supplied, a default
134
178
commit message will be written.
179
:param message_callback: A message callback to use for the commit, as
180
per mutabletree.commit.
181
:param timestamp: If non-None, set the timestamp of the commit to this
183
:param timezone: An optional timezone for timestamp.
184
:param committer: An optional username to use for commit
185
:param allow_leftmost_as_ghost: True if the leftmost parent should be
186
permitted to be a ghost.
135
187
:return: The revision_id of the new commit
137
189
if parent_ids is not None:
138
base_id = parent_ids[0]
190
if len(parent_ids) == 0:
191
base_id = revision.NULL_REVISION
193
base_id = parent_ids[0]
139
194
if base_id != self._branch.last_revision():
140
self._move_branch_pointer(base_id)
195
self._move_branch_pointer(base_id,
196
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
142
198
if self._tree is not None:
143
199
tree = self._tree
187
244
tree.add([path], [file_id], ['directory'])
189
246
tree.mkdir(path, file_id)
247
for from_relpath, to_relpath in to_rename:
248
tree.rename_one(from_relpath, to_relpath)
190
249
tree.add(to_add_files, to_add_file_ids, to_add_kinds)
191
250
for file_id, content in new_contents.iteritems():
192
251
tree.put_file_bytes_non_atomic(file_id, content)
195
message = u'commit %d' % (self._branch.revno() + 1,)
196
return tree.commit(message, rev_id=revision_id)
252
return self._do_commit(tree, message=message, rev_id=revision_id,
253
timestamp=timestamp, timezone=timezone, committer=committer,
254
message_callback=message_callback)