72
72
from binascii import hexlify
73
73
from cStringIO import StringIO
75
from bzrlib.atomicfile import AtomicFile
75
76
from bzrlib.osutils import (local_time_offset,
76
77
rand_bytes, compact_date,
77
78
kind_marker, is_inside_any, quotefn,
78
sha_string, sha_strings, sha_file, isdir, isfile,
79
sha_file, isdir, isfile,
80
from bzrlib.branch import gen_file_id
81
81
import bzrlib.config
82
import bzrlib.errors as errors
82
83
from bzrlib.errors import (BzrError, PointlessCommit,
87
import bzrlib.gpg as gpg
88
88
from bzrlib.revision import Revision
89
89
from bzrlib.testament import Testament
90
90
from bzrlib.trace import mutter, note, warning
91
91
from bzrlib.xml5 import serializer_v5
92
92
from bzrlib.inventory import Inventory, ROOT_ID
93
from bzrlib.weave import Weave
94
from bzrlib.weavefile import read_weave, write_weave_v5
95
from bzrlib.atomicfile import AtomicFile
93
from bzrlib.symbol_versioning import *
94
from bzrlib.workingtree import WorkingTree
97
@deprecated_function(zero_seven)
98
98
def commit(*args, **kwargs):
99
99
"""Commit a new revision to a branch.
196
205
contains unknown files.
198
207
revprops -- Properties for new revision
208
:param local: Perform a local only commit.
200
210
mutter('preparing to commit')
203
self.weave_store = branch.weave_store
212
if deprecated_passed(branch):
213
warn("Commit.commit (branch, ...): The branch parameter is "
214
"deprecated as of bzr 0.8. Please use working_tree= instead.",
215
DeprecationWarning, stacklevel=2)
217
self.work_tree = self.branch.bzrdir.open_workingtree()
218
elif working_tree is None:
219
raise BzrError("One of branch and working_tree must be passed into commit().")
221
self.work_tree = working_tree
222
self.branch = self.work_tree.branch
224
raise BzrError("The message keyword parameter is required for commit().")
226
self.weave_store = self.branch.repository.weave_store
227
self.bound_branch = None
229
self.master_branch = None
204
230
self.rev_id = rev_id
205
231
self.specific_files = specific_files
206
232
self.allow_pointless = allow_pointless
207
self.revprops = revprops
209
if strict and branch.unknowns():
210
raise StrictCommitFailed()
212
if timestamp is None:
213
self.timestamp = time.time()
215
self.timestamp = long(timestamp)
217
if self.config is None:
218
self.config = bzrlib.config.BranchConfig(self.branch)
221
self.rev_id = _gen_revision_id(self.config, self.timestamp)
225
if committer is None:
226
self.committer = self.config.username()
228
assert isinstance(committer, basestring), type(committer)
229
self.committer = committer
232
self.timezone = local_time_offset()
234
self.timezone = int(timezone)
236
assert isinstance(message, basestring), type(message)
237
self.message = message
238
self._escape_commit_message()
240
self.branch.lock_write()
234
if revprops is not None:
235
self.revprops.update(revprops)
237
self.work_tree.lock_write()
242
self.work_tree = self.branch.working_tree()
239
# setup the bound branch variables as needed.
240
self._check_bound_branch()
242
# check for out of date working trees
243
# if we are bound, then self.branch is the master branch and this
244
# test is thus all we need.
245
if self.work_tree.last_revision() != self.master_branch.last_revision():
246
raise errors.OutOfDateTree(self.work_tree)
249
# raise an exception as soon as we find a single unknown.
250
for unknown in self.work_tree.unknowns():
251
raise StrictCommitFailed()
253
if timestamp is None:
254
self.timestamp = time.time()
256
self.timestamp = long(timestamp)
258
if self.config is None:
259
self.config = bzrlib.config.BranchConfig(self.branch)
262
self.rev_id = _gen_revision_id(self.config, self.timestamp)
266
if committer is None:
267
self.committer = self.config.username()
269
assert isinstance(committer, basestring), type(committer)
270
self.committer = committer
273
self.timezone = local_time_offset()
275
self.timezone = int(timezone)
277
if isinstance(message, str):
278
message = message.decode(bzrlib.user_encoding)
279
assert isinstance(message, unicode), type(message)
280
self.message = message
281
self._escape_commit_message()
243
283
self.work_inv = self.work_tree.inventory
244
self.basis_tree = self.branch.basis_tree()
284
self.basis_tree = self.work_tree.basis_tree()
245
285
self.basis_inv = self.basis_tree.inventory
247
287
self._gather_parents()
262
302
if len(list(self.work_tree.iter_conflicts()))>0:
263
303
raise ConflictsInTree
265
self._record_inventory()
305
self.inv_sha1 = self.branch.repository.add_inventory(
266
310
self._make_revision()
311
# revision data is in the local branch now.
313
# upload revision data to the master.
314
# this will propogate merged revisions too if needed.
315
if self.bound_branch:
316
self.master_branch.repository.fetch(self.branch.repository,
317
revision_id=self.rev_id)
318
# now the master has the revision data
319
# 'commit' to the master first so a timeout here causes the local
320
# branch to be out of date
321
self.master_branch.append_revision(self.rev_id)
323
# and now do the commit locally.
324
self.branch.append_revision(self.rev_id)
326
self.work_tree.set_pending_merges([])
327
if len(self.parents):
328
precursor = self.parents[0]
331
self.work_tree.set_last_revision(self.rev_id, precursor)
332
# now the work tree is up to date with the branch
267
334
self.reporter.completed(self.branch.revno()+1, self.rev_id)
268
self.branch.append_revision(self.rev_id)
269
self.branch.set_pending_merges([])
335
if self.config.post_commit() is not None:
336
hooks = self.config.post_commit().split(' ')
337
# this would be nicer with twisted.python.reflect.namedAny
339
result = eval(hook + '(branch, rev_id)',
340
{'branch':self.branch,
342
'rev_id':self.rev_id})
273
def _record_inventory(self):
274
"""Store the inventory for the new revision."""
275
inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
276
self.inv_sha1 = sha_string(inv_text)
277
s = self.branch.control_weaves
278
s.add_text('inventory', self.rev_id,
279
split_lines(inv_text), self.present_parents,
280
self.branch.get_transaction())
344
self._cleanup_bound_branch()
345
self.work_tree.unlock()
347
def _check_bound_branch(self):
348
"""Check to see if the local branch is bound.
350
If it is bound, then most of the commit will actually be
351
done using the remote branch as the target branch.
352
Only at the end will the local branch be updated.
354
if self.local and not self.branch.get_bound_location():
355
raise errors.LocalRequiresBoundBranch()
358
self.master_branch = self.branch.get_master_branch()
360
if not self.master_branch:
361
# make this branch the reference branch for out of date checks.
362
self.master_branch = self.branch
365
# If the master branch is bound, we must fail
366
master_bound_location = self.master_branch.get_bound_location()
367
if master_bound_location:
368
raise errors.CommitToDoubleBoundBranch(self.branch,
369
self.master_branch, master_bound_location)
371
# TODO: jam 20051230 We could automatically push local
372
# commits to the remote branch if they would fit.
373
# But for now, just require remote to be identical
376
# Make sure the local branch is identical to the master
377
master_rh = self.master_branch.revision_history()
378
local_rh = self.branch.revision_history()
379
if local_rh != master_rh:
380
raise errors.BoundBranchOutOfDate(self.branch,
383
# Now things are ready to change the master branch
385
self.bound_branch = self.branch
386
self.master_branch.lock_write()
388
#### # Check to see if we have any pending merges. If we do
389
#### # those need to be pushed into the master branch
390
#### pending_merges = self.work_tree.pending_merges()
391
#### if pending_merges:
392
#### for revision_id in pending_merges:
393
#### self.master_branch.repository.fetch(self.bound_branch.repository,
394
#### revision_id=revision_id)
396
def _cleanup_bound_branch(self):
397
"""Executed at the end of a try/finally to cleanup a bound branch.
399
If the branch wasn't bound, this is a no-op.
400
If it was, it resents self.branch to the local branch, instead
403
if not self.bound_branch:
405
self.master_branch.unlock()
282
407
def _escape_commit_message(self):
283
408
"""Replace xml-incompatible control characters."""
309
428
self.parents.append(precursor_id)
310
429
self.parents += pending_merges
311
430
for revision in self.parents:
312
if self.branch.has_revision(revision):
313
self.parent_invs.append(self.branch.get_inventory(revision))
431
if self.branch.repository.has_revision(revision):
432
inventory = self.branch.repository.get_inventory(revision)
433
self.parent_invs.append(inventory)
314
434
self.present_parents.append(revision)
316
436
def _check_parents_present(self):
317
437
for parent_id in self.parents:
318
438
mutter('commit parent revision {%s}', parent_id)
319
if not self.branch.has_revision(parent_id):
439
if not self.branch.repository.has_revision(parent_id):
320
440
if parent_id == self.branch.last_revision():
321
441
warning("parent is missing %r", parent_id)
322
442
raise HistoryMissing(self.branch, 'revision', parent_id)
326
446
def _make_revision(self):
327
447
"""Record a new revision object for this commit."""
328
self.rev = Revision(timestamp=self.timestamp,
329
timezone=self.timezone,
330
committer=self.committer,
331
message=self.message,
332
inventory_sha1=self.inv_sha1,
333
revision_id=self.rev_id,
334
properties=self.revprops)
335
self.rev.parent_ids = self.parents
337
serializer_v5.write_revision(self.rev, rev_tmp)
339
if self.config.signature_needed():
340
plaintext = Testament(self.rev, self.new_inv).as_short_text()
341
self.branch.store_revision_signature(gpg.GPGStrategy(self.config),
342
plaintext, self.rev_id)
343
self.branch.revision_store.add(rev_tmp, self.rev_id)
344
mutter('new revision_id is {%s}', self.rev_id)
448
rev = Revision(timestamp=self.timestamp,
449
timezone=self.timezone,
450
committer=self.committer,
451
message=self.message,
452
inventory_sha1=self.inv_sha1,
453
revision_id=self.rev_id,
454
properties=self.revprops)
455
rev.parent_ids = self.parents
456
self.branch.repository.add_revision(self.rev_id, rev, self.new_inv, self.config)
346
458
def _remove_deleted(self):
347
459
"""Remove deleted files from the working inventories.