162
161
self._tree = None
164
163
def build_snapshot(self, revision_id, parent_ids, actions,
165
message=None, timestamp=None, allow_leftmost_as_ghost=False,
166
committer=None, timezone=None, message_callback=None):
164
message=None, timestamp=None, allow_leftmost_as_ghost=False):
167
165
"""Build a commit, shaped in a specific way.
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.
174
167
:param revision_id: The handle for the new commit, can be None
175
168
:param parent_ids: A list of parent_ids to use for the commit.
176
169
It can be None, which indicates to use the last commit.
179
172
('modify', ('file-id', 'new-content'))
180
173
('unversion', 'file-id')
181
174
('rename', ('orig-path', 'new-path'))
183
175
:param message: An optional commit message, if not supplied, a default
184
176
commit message will be written.
185
:param message_callback: A message callback to use for the commit, as
186
per mutabletree.commit.
187
177
:param timestamp: If non-None, set the timestamp of the commit to this
189
:param timezone: An optional timezone for timestamp.
190
:param committer: An optional username to use for commit
191
179
:param allow_leftmost_as_ghost: True if the leftmost parent should be
192
180
permitted to be a ghost.
193
181
:return: The revision_id of the new commit
195
183
if parent_ids is not None:
196
if len(parent_ids) == 0:
197
base_id = revision.NULL_REVISION
199
base_id = parent_ids[0]
184
base_id = parent_ids[0]
200
185
if base_id != self._branch.last_revision():
201
186
self._move_branch_pointer(base_id,
202
187
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
214
199
# inventory entry. And the only public function to create a
215
200
# directory is MemoryTree.mkdir() which creates the directory, but
216
201
# also always adds it. So we have to use a multi-pass setup.
217
pending = _PendingActions()
202
to_add_directories = []
207
to_unversion_ids = []
218
209
for action, info in actions:
219
210
if action == 'add':
220
211
path, file_id, kind, content = info
221
212
if kind == 'directory':
222
pending.to_add_directories.append((path, file_id))
213
to_add_directories.append((path, file_id))
224
pending.to_add_files.append(path)
225
pending.to_add_file_ids.append(file_id)
226
pending.to_add_kinds.append(kind)
215
to_add_files.append(path)
216
to_add_file_ids.append(file_id)
217
to_add_kinds.append(kind)
227
218
if content is not None:
228
pending.new_contents[file_id] = content
219
new_contents[file_id] = content
229
220
elif action == 'modify':
230
221
file_id, content = info
231
pending.new_contents[file_id] = content
222
new_contents[file_id] = content
232
223
elif action == 'unversion':
233
pending.to_unversion_ids.add(info)
224
to_unversion_ids.append(info)
234
225
elif action == 'rename':
235
226
from_relpath, to_relpath = info
236
pending.to_rename.append((from_relpath, to_relpath))
237
elif action == 'flush':
238
self._flush_pending(tree, pending)
239
pending = _PendingActions()
227
to_rename.append((from_relpath, to_relpath))
241
229
raise ValueError('Unknown build action: "%s"' % (action,))
242
self._flush_pending(tree, pending)
231
tree.unversion(to_unversion_ids)
232
for path, file_id in to_add_directories:
234
# Special case, because the path already exists
235
tree.add([path], [file_id], ['directory'])
237
tree.mkdir(path, file_id)
238
for from_relpath, to_relpath in to_rename:
239
tree.rename_one(from_relpath, to_relpath)
240
tree.add(to_add_files, to_add_file_ids, to_add_kinds)
241
for file_id, content in new_contents.iteritems():
242
tree.put_file_bytes_non_atomic(file_id, content)
243
243
return self._do_commit(tree, message=message, rev_id=revision_id,
244
timestamp=timestamp, timezone=timezone, committer=committer,
245
message_callback=message_callback)
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:
253
old_id = tree.path2id(path)
254
if old_id is not None and old_id in pending.to_unversion_ids:
255
# We're overwriting this path, no need to unversion
256
pending.to_unversion_ids.discard(old_id)
257
# Special case, because the path already exists
258
tree.add([path], [file_id], ['directory'])
260
tree.mkdir(path, file_id)
261
for from_relpath, to_relpath in pending.to_rename:
262
tree.rename_one(from_relpath, to_relpath)
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():
267
tree.put_file_bytes_non_atomic(file_id, content)
269
248
def get_branch(self):
270
249
"""Return the branch created by the builder."""
271
250
return self._branch
274
class _PendingActions(object):
275
"""Pending actions for build_snapshot to take.
277
This is just a simple class to hold a bunch of the intermediate state of
278
build_snapshot in single object.
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()