18
18
# XXX: Can we do any better about making interrupted commits change
19
# nothing? Perhaps the best approach is to integrate commit of
20
# AtomicFiles with releasing the lock on the Branch.
21
22
# TODO: Separate 'prepare' phase where we find a list of potentially
22
23
# committed files. We then can then pause the commit to prompt for a
62
63
# TODO: If commit fails, leave the message in a file somewhere.
64
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
65
# the rest of the code; add a deprecation of the old name.
72
72
from cStringIO import StringIO
74
from bzrlib.atomicfile import AtomicFile
78
75
import bzrlib.config
76
import bzrlib.errors as errors
79
77
from bzrlib.errors import (BzrError, PointlessCommit,
87
85
from bzrlib.trace import mutter, note, warning
88
86
from bzrlib.xml5 import serializer_v5
89
87
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
90
from bzrlib import symbol_versioning
91
88
from bzrlib.symbol_versioning import (deprecated_passed,
92
89
deprecated_function,
93
91
DEPRECATED_PARAMETER)
94
92
from bzrlib.workingtree import WorkingTree
95
@deprecated_function(zero_seven)
96
def commit(*args, **kwargs):
97
"""Commit a new revision to a branch.
99
Function-style interface for convenience of old callers.
101
New code should use the Commit class instead.
103
## XXX: Remove this in favor of WorkingTree.commit?
104
Commit().commit(*args, **kwargs)
97
107
class NullCommitReporter(object):
98
108
"""I report on progress of a commit."""
213
223
mutter('preparing to commit')
215
225
if deprecated_passed(branch):
216
symbol_versioning.warn("Commit.commit (branch, ...): The branch parameter is "
226
warnings.warn("Commit.commit (branch, ...): The branch parameter is "
217
227
"deprecated as of bzr 0.8. Please use working_tree= instead.",
218
228
DeprecationWarning, stacklevel=2)
219
229
self.branch = branch
250
260
self._check_bound_branch()
252
262
# check for out of date working trees
254
first_tree_parent = self.work_tree.get_parent_ids()[0]
256
# if there are no parents, treat our parent as 'None'
257
# this is so that we still consier the master branch
258
# - in a checkout scenario the tree may have no
259
# parents but the branch may do.
260
first_tree_parent = None
261
master_last = self.master_branch.last_revision()
262
if (master_last is not None and
263
master_last != first_tree_parent):
263
# if we are bound, then self.branch is the master branch and this
264
# test is thus all we need.
265
if self.work_tree.last_revision() != self.master_branch.last_revision():
264
266
raise errors.OutOfDateTree(self.work_tree)
280
282
self.work_inv = self.work_tree.inventory
281
283
self.basis_tree = self.work_tree.basis_tree()
282
284
self.basis_inv = self.basis_tree.inventory
283
if specific_files is not None:
284
# Ensure specified files are versioned
285
# (We don't actually need the ids here)
286
tree.find_ids_across_trees(specific_files,
287
[self.basis_tree, self.work_tree])
288
285
# one to finish, one for rev and inventory, and one for each
289
286
# inventory entry, and the same for the new inventory.
290
287
# note that this estimate is too long when we do a partial tree
297
294
if len(self.parents) > 1 and self.specific_files:
298
295
raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
299
296
self.specific_files)
297
self._check_parents_present()
301
298
self.builder = self.branch.get_commit_builder(self.parents,
302
299
self.config, timestamp, timezone, committer, revprops, rev_id)
305
302
self._populate_new_inv()
306
303
self._report_deletes()
308
self._check_pointless()
305
if not (self.allow_pointless
306
or len(self.parents) > 1
307
or self.builder.new_inventory != self.basis_inv):
308
raise PointlessCommit()
310
310
self._emit_progress_update()
311
# TODO: Now the new inventory is known, check for conflicts and
312
# prompt the user for a commit message.
313
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
314
# weave lines, because nothing should be recorded until it is known
315
# that commit will succeed.
311
# TODO: Now the new inventory is known, check for conflicts and prompt the
312
# user for a commit message.
316
313
self.builder.finish_inventory()
317
314
self._emit_progress_update()
318
315
self.rev_id = self.builder.commit(self.message)
332
329
# and now do the commit locally.
333
330
self.branch.append_revision(self.rev_id)
335
rev_tree = self.builder.revision_tree()
336
self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
332
self.work_tree.set_pending_merges([])
333
self.work_tree.set_last_revision(self.rev_id)
337
334
# now the work tree is up to date with the branch
339
336
self.reporter.completed(self.branch.revno(), self.rev_id)
351
348
return self.rev_id
353
def _any_real_changes(self):
354
"""Are there real changes between new_inventory and basis?
356
For trees without rich roots, inv.root.revision changes every commit.
357
But if that is the only change, we want to treat it as though there
360
new_entries = self.builder.new_inventory.iter_entries()
361
basis_entries = self.basis_inv.iter_entries()
362
new_path, new_root_ie = new_entries.next()
363
basis_path, basis_root_ie = basis_entries.next()
365
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
366
def ie_equal_no_revision(this, other):
367
return ((this.file_id == other.file_id)
368
and (this.name == other.name)
369
and (this.symlink_target == other.symlink_target)
370
and (this.text_sha1 == other.text_sha1)
371
and (this.text_size == other.text_size)
372
and (this.text_id == other.text_id)
373
and (this.parent_id == other.parent_id)
374
and (this.kind == other.kind)
375
and (this.executable == other.executable)
377
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
380
for new_ie, basis_ie in zip(new_entries, basis_entries):
381
if new_ie != basis_ie:
384
# No actual changes present
387
def _check_pointless(self):
388
if self.allow_pointless:
390
# A merge with no effect on files
391
if len(self.parents) > 1:
393
# Shortcut, if the number of entries changes, then we obviously have
395
if len(self.builder.new_inventory) != len(self.basis_inv):
397
# If length == 1, then we only have the root entry. Which means
398
# that there is no real difference (only the root could be different)
399
if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
401
raise PointlessCommit()
403
350
def _check_bound_branch(self):
404
351
"""Check to see if the local branch is bound.
503
450
self.parent_invs = []
504
451
for revision in self.parents:
505
452
if self.branch.repository.has_revision(revision):
506
mutter('commit parent revision {%s}', revision)
507
453
inventory = self.branch.repository.get_inventory(revision)
508
454
self.parent_invs.append(inventory)
510
mutter('commit parent ghost revision {%s}', revision)
456
def _check_parents_present(self):
457
for parent_id in self.parents:
458
mutter('commit parent revision {%s}', parent_id)
459
if not self.branch.repository.has_revision(parent_id):
460
if parent_id == self.branch.last_revision():
461
warning("parent is missing %r", parent_id)
462
raise BzrCheckError("branch %s is missing revision {%s}"
463
% (self.branch, parent_id))
512
465
def _remove_deleted(self):
513
466
"""Remove deleted files from the working inventories.
523
476
specific = self.specific_files
525
deleted_paths = set()
526
478
for path, ie in self.work_inv.iter_entries():
527
if is_inside_any(deleted_paths, path):
528
# The tree will delete the required ids recursively.
530
479
if specific and not is_inside_any(specific, path):
532
481
if not self.work_tree.has_filename(path):
533
deleted_paths.add(path)
534
482
self.reporter.missing(path)
535
deleted_ids.append(ie.file_id)
536
self.work_tree.unversion(deleted_ids)
483
deleted_ids.append((path, ie.file_id))
485
deleted_ids.sort(reverse=True)
486
for path, file_id in deleted_ids:
487
del self.work_inv[file_id]
488
self.work_tree._write_inventory(self.work_inv)
538
490
def _populate_new_inv(self):
539
491
"""Build revision inventory.
550
502
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
551
503
# ADHB 11-07-2006
552
504
mutter("Selecting files for commit with filter %s", self.specific_files)
505
# at this point we dont copy the root entry:
553
506
entries = self.work_inv.iter_entries()
554
if not self.builder.record_root_entry:
555
symbol_versioning.warn('CommitBuilders should support recording'
556
' the root entry as of bzr 0.10.', DeprecationWarning,
558
self.builder.new_inventory.add(self.basis_inv.root.copy())
560
self._emit_progress_update()
508
self._emit_progress_update()
561
509
for path, new_ie in entries:
562
510
self._emit_progress_update()
563
511
file_id = new_ie.file_id