88
import bzrlib.gpg as gpg
89
88
from bzrlib.revision import Revision
90
89
from bzrlib.testament import Testament
91
90
from bzrlib.trace import mutter, note, warning
92
91
from bzrlib.xml5 import serializer_v5
93
92
from bzrlib.inventory import Inventory, ROOT_ID
94
93
from bzrlib.symbol_versioning import *
95
from bzrlib.weave import Weave
96
from bzrlib.weavefile import read_weave, write_weave_v5
97
94
from bzrlib.workingtree import WorkingTree
131
128
class ReportCommitToLog(NullCommitReporter):
133
130
def snapshot_change(self, change, path):
131
if change == 'unchanged':
134
133
note("%s %s", change, path)
136
135
def completed(self, revno, rev_id):
137
note('committed r%d {%s}', revno, rev_id)
136
note('Committed revision %d.', revno)
139
138
def deleted(self, file_id):
140
139
note('deleted %s', file_id)
225
228
raise BzrError("The message keyword parameter is required for commit().")
227
230
self.weave_store = self.branch.repository.weave_store
231
self.bound_branch = None
233
self.master_branch = None
234
self.master_locked = False
228
235
self.rev_id = rev_id
229
236
self.specific_files = specific_files
230
237
self.allow_pointless = allow_pointless
231
self.revprops = {'branch-nick': self.branch.nick}
239
if revprops is not None:
233
240
self.revprops.update(revprops)
235
# check for out of date working trees
236
if self.work_tree.last_revision() != self.branch.last_revision():
237
raise errors.OutOfDateTree(self.work_tree)
240
# raise an exception as soon as we find a single unknown.
241
for unknown in self.work_tree.unknowns():
242
raise StrictCommitFailed()
244
if timestamp is None:
245
self.timestamp = time.time()
247
self.timestamp = long(timestamp)
249
if self.config is None:
250
self.config = bzrlib.config.BranchConfig(self.branch)
253
self.rev_id = _gen_revision_id(self.config, self.timestamp)
257
if committer is None:
258
self.committer = self.config.username()
260
assert isinstance(committer, basestring), type(committer)
261
self.committer = committer
264
self.timezone = local_time_offset()
266
self.timezone = int(timezone)
268
if isinstance(message, str):
269
message = message.decode(bzrlib.user_encoding)
270
assert isinstance(message, unicode), type(message)
271
self.message = message
272
self._escape_commit_message()
274
self.branch.lock_write()
242
if reporter is None and self.reporter is None:
243
self.reporter = NullCommitReporter()
244
elif reporter is not None:
245
self.reporter = reporter
247
self.work_tree.lock_write()
249
# setup the bound branch variables as needed.
250
self._check_bound_branch()
252
# check for out of date working trees
253
# if we are bound, then self.branch is the master branch and this
254
# test is thus all we need.
255
if self.work_tree.last_revision() != self.master_branch.last_revision():
256
raise errors.OutOfDateTree(self.work_tree)
259
# raise an exception as soon as we find a single unknown.
260
for unknown in self.work_tree.unknowns():
261
raise StrictCommitFailed()
263
if timestamp is None:
264
self.timestamp = time.time()
266
self.timestamp = long(timestamp)
268
if self.config is None:
269
self.config = bzrlib.config.BranchConfig(self.branch)
272
self.rev_id = _gen_revision_id(self.config, self.timestamp)
276
if committer is None:
277
self.committer = self.config.username()
279
assert isinstance(committer, basestring), type(committer)
280
self.committer = committer
283
self.timezone = local_time_offset()
285
self.timezone = int(timezone)
287
if isinstance(message, str):
288
message = message.decode(bzrlib.user_encoding)
289
assert isinstance(message, unicode), type(message)
290
self.message = message
291
self._escape_commit_message()
276
293
self.work_inv = self.work_tree.inventory
277
294
self.basis_tree = self.work_tree.basis_tree()
278
295
self.basis_inv = self.basis_tree.inventory
292
309
or self.new_inv != self.basis_inv):
293
310
raise PointlessCommit()
295
if len(list(self.work_tree.iter_conflicts()))>0:
312
if len(self.work_tree.conflicts())>0:
296
313
raise ConflictsInTree
298
self._record_inventory()
315
self.inv_sha1 = self.branch.repository.add_inventory(
299
320
self._make_revision()
321
# revision data is in the local branch now.
323
# upload revision data to the master.
324
# this will propogate merged revisions too if needed.
325
if self.bound_branch:
326
self.master_branch.repository.fetch(self.branch.repository,
327
revision_id=self.rev_id)
328
# now the master has the revision data
329
# 'commit' to the master first so a timeout here causes the local
330
# branch to be out of date
331
self.master_branch.append_revision(self.rev_id)
333
# and now do the commit locally.
334
self.branch.append_revision(self.rev_id)
300
336
self.work_tree.set_pending_merges([])
301
self.branch.append_revision(self.rev_id)
302
if len(self.parents):
303
precursor = self.parents[0]
306
self.work_tree.set_last_revision(self.rev_id, precursor)
307
self.reporter.completed(self.branch.revno()+1, self.rev_id)
337
self.work_tree.set_last_revision(self.rev_id)
338
# now the work tree is up to date with the branch
340
self.reporter.completed(self.branch.revno(), self.rev_id)
308
341
if self.config.post_commit() is not None:
309
342
hooks = self.config.post_commit().split(' ')
310
343
# this would be nicer with twisted.python.reflect.namedAny
315
348
'rev_id':self.rev_id})
319
def _record_inventory(self):
320
"""Store the inventory for the new revision."""
321
inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
322
self.inv_sha1 = sha_string(inv_text)
323
s = self.branch.repository.control_weaves
324
s.add_text('inventory', self.rev_id,
325
split_lines(inv_text), self.present_parents,
326
self.branch.get_transaction())
350
self._cleanup_bound_branch()
351
self.work_tree.unlock()
353
def _check_bound_branch(self):
354
"""Check to see if the local branch is bound.
356
If it is bound, then most of the commit will actually be
357
done using the remote branch as the target branch.
358
Only at the end will the local branch be updated.
360
if self.local and not self.branch.get_bound_location():
361
raise errors.LocalRequiresBoundBranch()
364
self.master_branch = self.branch.get_master_branch()
366
if not self.master_branch:
367
# make this branch the reference branch for out of date checks.
368
self.master_branch = self.branch
371
# If the master branch is bound, we must fail
372
master_bound_location = self.master_branch.get_bound_location()
373
if master_bound_location:
374
raise errors.CommitToDoubleBoundBranch(self.branch,
375
self.master_branch, master_bound_location)
377
# TODO: jam 20051230 We could automatically push local
378
# commits to the remote branch if they would fit.
379
# But for now, just require remote to be identical
382
# Make sure the local branch is identical to the master
383
master_rh = self.master_branch.revision_history()
384
local_rh = self.branch.revision_history()
385
if local_rh != master_rh:
386
raise errors.BoundBranchOutOfDate(self.branch,
389
# Now things are ready to change the master branch
391
self.bound_branch = self.branch
392
self.master_branch.lock_write()
393
self.master_locked = True
395
#### # Check to see if we have any pending merges. If we do
396
#### # those need to be pushed into the master branch
397
#### pending_merges = self.work_tree.pending_merges()
398
#### if pending_merges:
399
#### for revision_id in pending_merges:
400
#### self.master_branch.repository.fetch(self.bound_branch.repository,
401
#### revision_id=revision_id)
403
def _cleanup_bound_branch(self):
404
"""Executed at the end of a try/finally to cleanup a bound branch.
406
If the branch wasn't bound, this is a no-op.
407
If it was, it resents self.branch to the local branch, instead
410
if not self.bound_branch:
412
if self.master_locked:
413
self.master_branch.unlock()
328
415
def _escape_commit_message(self):
329
416
"""Replace xml-incompatible control characters."""
417
# FIXME: RBC 20060419 this should be done by the revision
418
# serialiser not by commit. Then we can also add an unescaper
419
# in the deserializer and start roundtripping revision messages
420
# precisely. See repository_implementations/test_repository.py
330
422
# Python strings can include characters that can't be
331
423
# represented in well-formed XML; escape characters that
332
424
# aren't listed in the XML specification
367
459
def _make_revision(self):
368
460
"""Record a new revision object for this commit."""
369
self.rev = Revision(timestamp=self.timestamp,
370
timezone=self.timezone,
371
committer=self.committer,
372
message=self.message,
373
inventory_sha1=self.inv_sha1,
374
revision_id=self.rev_id,
375
properties=self.revprops)
376
self.rev.parent_ids = self.parents
378
serializer_v5.write_revision(self.rev, rev_tmp)
380
if self.config.signature_needed():
381
plaintext = Testament(self.rev, self.new_inv).as_short_text()
382
self.branch.repository.store_revision_signature(
383
gpg.GPGStrategy(self.config), plaintext, self.rev_id)
384
self.branch.repository.revision_store.add(rev_tmp, self.rev_id)
385
mutter('new revision_id is {%s}', self.rev_id)
461
rev = Revision(timestamp=self.timestamp,
462
timezone=self.timezone,
463
committer=self.committer,
464
message=self.message,
465
inventory_sha1=self.inv_sha1,
466
revision_id=self.rev_id,
467
properties=self.revprops)
468
rev.parent_ids = self.parents
469
self.branch.repository.add_revision(self.rev_id, rev, self.new_inv, self.config)
387
471
def _remove_deleted(self):
388
472
"""Remove deleted files from the working inventories.
422
506
for path, ie in self.new_inv.iter_entries():
423
507
previous_entries = ie.find_previous_heads(
425
self.weave_store.get_weave_prelude_or_empty(ie.file_id,
426
self.branch.get_transaction()))
510
self.branch.repository.get_transaction())
427
511
if ie.revision is None:
428
512
change = ie.snapshot(self.rev_id, path, previous_entries,
429
513
self.work_tree, self.weave_store,