15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
# XXX: Can we do any better about making interrupted commits change
21
# TODO: Separate 'prepare' phase where we find a list of potentially
22
# committed files. We then can then pause the commit to prompt for a
23
# commit message, knowing the summary will be the same as what's
24
# actually used for the commit. (But perhaps simpler to simply get
25
# the tree status, then use that for a selective commit?)
18
27
# The newly committed revision is going to have a shape corresponding
19
28
# to that of the working inventory. Files that are not in the
20
29
# working tree and that were in the predecessor are reported as
46
55
# merges from, then it should still be reported as newly added
47
56
# relative to the basis revision.
58
# TODO: Do checks that the tree can be committed *before* running the
59
# editor; this should include checks for a pointless commit and for
60
# unknown or missing files.
62
# TODO: If commit fails, leave the message in a file somewhere.
49
64
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
65
# the rest of the code; add a deprecation of the old name.
73
86
from bzrlib.testament import Testament
74
87
from bzrlib.trace import mutter, note, warning
75
88
from bzrlib.xml5 import serializer_v5
76
from bzrlib.inventory import Inventory, InventoryEntry
89
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
77
90
from bzrlib import symbol_versioning
78
91
from bzrlib.symbol_versioning import (deprecated_passed,
79
92
deprecated_function,
80
93
DEPRECATED_PARAMETER)
81
94
from bzrlib.workingtree import WorkingTree
85
97
class NullCommitReporter(object):
114
126
def snapshot_change(self, change, path):
115
127
if change == 'unchanged':
117
if change == 'added' and path == '':
119
129
note("%s %s", change, path)
121
131
def completed(self, revno, rev_id):
172
182
working_tree=None,
176
message_callback=None):
177
186
"""Commit working copy as a new revision.
179
188
branch -- the deprecated branch to commit to. New callers should pass in
180
189
working_tree instead
182
message -- the commit message (it or message_callback is required)
191
message -- the commit message, a mandatory parameter
184
193
timestamp -- if not None, seconds-since-epoch for a
185
194
postdated/predated commit.
215
224
self.work_tree = working_tree
216
225
self.branch = self.work_tree.branch
217
if message_callback is None:
218
if message is not None:
219
if isinstance(message, str):
220
message = message.decode(bzrlib.user_encoding)
221
message_callback = lambda x: message
223
raise BzrError("The message or message_callback keyword"
224
" parameter is required for commit().")
227
raise BzrError("The message keyword parameter is required for commit().")
226
229
self.bound_branch = None
227
230
self.local = local
239
242
self.work_tree.lock_write()
240
243
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
241
self.basis_tree = self.work_tree.basis_tree()
242
self.basis_tree.lock_read()
244
245
# Cannot commit with conflicts present.
245
246
if len(self.work_tree.conflicts())>0:
256
257
# this is so that we still consier the master branch
257
258
# - in a checkout scenario the tree may have no
258
259
# parents but the branch may do.
259
first_tree_parent = bzrlib.revision.NULL_REVISION
260
old_revno, master_last = self.master_branch.last_revision_info()
261
if master_last != first_tree_parent:
262
if master_last != bzrlib.revision.NULL_REVISION:
263
raise errors.OutOfDateTree(self.work_tree)
264
if self.branch.repository.has_revision(first_tree_parent):
265
new_revno = old_revno + 1
267
# ghost parents never appear in revision history.
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):
264
raise errors.OutOfDateTree(self.work_tree)
270
267
# raise an exception as soon as we find a single unknown.
271
268
for unknown in self.work_tree.unknowns():
274
271
if self.config is None:
275
272
self.config = self.branch.get_config()
274
if isinstance(message, str):
275
message = message.decode(bzrlib.user_encoding)
276
assert isinstance(message, unicode), type(message)
277
self.message = message
278
self._escape_commit_message()
277
280
self.work_inv = self.work_tree.inventory
281
self.basis_tree = self.work_tree.basis_tree()
278
282
self.basis_inv = self.basis_tree.inventory
279
283
if specific_files is not None:
280
284
# Ensure specified files are versioned
311
315
# that commit will succeed.
312
316
self.builder.finish_inventory()
313
317
self._emit_progress_update()
314
message = message_callback(self)
315
assert isinstance(message, unicode), type(message)
316
self.message = message
317
self._escape_commit_message()
319
318
self.rev_id = self.builder.commit(self.message)
320
319
self._emit_progress_update()
321
320
# revision data is in the local branch now.
328
327
# now the master has the revision data
329
328
# 'commit' to the master first so a timeout here causes the local
330
329
# branch to be out of date
331
self.master_branch.set_last_revision_info(new_revno,
330
self.master_branch.append_revision(self.rev_id)
334
332
# and now do the commit locally.
335
self.branch.set_last_revision_info(new_revno, self.rev_id)
333
self.branch.append_revision(self.rev_id)
337
rev_tree = self.builder.revision_tree()
338
self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
335
# if the builder gave us the revisiontree it created back, we
336
# could use it straight away here.
337
# TODO: implement this.
338
self.work_tree.set_parent_trees([(self.rev_id,
339
self.branch.repository.revision_tree(self.rev_id))])
339
340
# now the work tree is up to date with the branch
341
self.reporter.completed(new_revno, self.rev_id)
342
# old style commit hooks - should be deprecated ? (obsoleted in
342
self.reporter.completed(self.branch.revno(), self.rev_id)
344
343
if self.config.post_commit() is not None:
345
344
hooks = self.config.post_commit().split(' ')
346
345
# this would be nicer with twisted.python.reflect.namedAny
349
348
{'branch':self.branch,
351
350
'rev_id':self.rev_id})
352
# new style commit hooks:
353
if not self.bound_branch:
354
hook_master = self.branch
357
hook_master = self.master_branch
358
hook_local = self.branch
359
# With bound branches, when the master is behind the local branch,
360
# the 'old_revno' and old_revid values here are incorrect.
361
# XXX: FIXME ^. RBC 20060206
363
old_revid = self.parents[0]
365
old_revid = bzrlib.revision.NULL_REVISION
366
for hook in Branch.hooks['post_commit']:
367
hook(hook_local, hook_master, old_revno, old_revid, new_revno,
369
351
self._emit_progress_update()
372
354
return self.rev_id
374
def _any_real_changes(self):
375
"""Are there real changes between new_inventory and basis?
377
For trees without rich roots, inv.root.revision changes every commit.
378
But if that is the only change, we want to treat it as though there
381
new_entries = self.builder.new_inventory.iter_entries()
382
basis_entries = self.basis_inv.iter_entries()
383
new_path, new_root_ie = new_entries.next()
384
basis_path, basis_root_ie = basis_entries.next()
386
# This is a copy of InventoryEntry.__eq__ only leaving out .revision
387
def ie_equal_no_revision(this, other):
388
return ((this.file_id == other.file_id)
389
and (this.name == other.name)
390
and (this.symlink_target == other.symlink_target)
391
and (this.text_sha1 == other.text_sha1)
392
and (this.text_size == other.text_size)
393
and (this.text_id == other.text_id)
394
and (this.parent_id == other.parent_id)
395
and (this.kind == other.kind)
396
and (this.executable == other.executable)
398
if not ie_equal_no_revision(new_root_ie, basis_root_ie):
401
for new_ie, basis_ie in zip(new_entries, basis_entries):
402
if new_ie != basis_ie:
405
# No actual changes present
408
356
def _check_pointless(self):
409
357
if self.allow_pointless:
414
362
# work around the fact that a newly-initted tree does differ from its
416
if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
417
raise PointlessCommit()
418
# Shortcut, if the number of entries changes, then we obviously have
420
364
if len(self.builder.new_inventory) != len(self.basis_inv):
422
# If length == 1, then we only have the root entry. Which means
423
# that there is no real difference (only the root could be different)
424
if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
366
if (len(self.builder.new_inventory) != 1 and
367
self.builder.new_inventory != self.basis_inv):
426
369
raise PointlessCommit()
457
400
# Make sure the local branch is identical to the master
458
master_info = self.master_branch.last_revision_info()
459
local_info = self.branch.last_revision_info()
460
if local_info != master_info:
401
master_rh = self.master_branch.revision_history()
402
local_rh = self.branch.revision_history()
403
if local_rh != master_rh:
461
404
raise errors.BoundBranchOutOfDate(self.branch,
462
405
self.master_branch)
526
468
# TODO: Make sure that this list doesn't contain duplicate
527
469
# entries and the order is preserved when doing this.
528
470
self.parents = self.work_tree.get_parent_ids()
529
self.parent_invs = [self.basis_inv]
530
for revision in self.parents[1:]:
471
self.parent_invs = []
472
for revision in self.parents:
531
473
if self.branch.repository.has_revision(revision):
532
474
mutter('commit parent revision {%s}', revision)
533
475
inventory = self.branch.repository.get_inventory(revision)
576
518
# in bugs like #46635. Any reason not to use/enhance Tree.changes_from?
577
519
# ADHB 11-07-2006
578
520
mutter("Selecting files for commit with filter %s", self.specific_files)
579
assert self.work_inv.root is not None
580
521
entries = self.work_inv.iter_entries()
581
522
if not self.builder.record_root_entry:
582
523
symbol_versioning.warn('CommitBuilders should support recording'
588
529
for path, new_ie in entries:
589
530
self._emit_progress_update()
590
531
file_id = new_ie.file_id
592
kind = self.work_tree.kind(file_id)
593
if kind != new_ie.kind:
594
new_ie = inventory.make_entry(kind, new_ie.name,
595
new_ie.parent_id, file_id)
596
except errors.NoSuchFile:
598
532
# mutter('check %s {%s}', path, file_id)
599
533
if (not self.specific_files or
600
534
is_inside_or_parent_of_any(self.specific_files, path)):