56
56
# merges from, then it should still be reported as newly added
57
57
# relative to the basis revision.
59
# TODO: Do checks that the tree can be committed *before* running the
60
# editor; this should include checks for a pointless commit and for
61
# unknown or missing files.
63
# TODO: If commit fails, leave the message in a file somewhere.
66
72
from binascii import hexlify
67
73
from cStringIO import StringIO
75
from bzrlib.atomicfile import AtomicFile
69
76
from bzrlib.osutils import (local_time_offset,
70
77
rand_bytes, compact_date,
71
78
kind_marker, is_inside_any, quotefn,
72
79
sha_string, sha_strings, sha_file, isdir, isfile,
74
from bzrlib.branch import gen_file_id
75
81
import bzrlib.config
76
82
from bzrlib.errors import (BzrError, PointlessCommit,
87
import bzrlib.gpg as gpg
80
88
from bzrlib.revision import Revision
89
from bzrlib.testament import Testament
81
90
from bzrlib.trace import mutter, note, warning
82
91
from bzrlib.xml5 import serializer_v5
83
92
from bzrlib.inventory import Inventory, ROOT_ID
84
93
from bzrlib.weave import Weave
85
94
from bzrlib.weavefile import read_weave, write_weave_v5
86
from bzrlib.atomicfile import AtomicFile
95
from bzrlib.workingtree import WorkingTree
89
98
def commit(*args, **kwargs):
178
194
allow_pointless -- If true (default), commit even if nothing
179
195
has changed and no merges are recorded.
197
strict -- If true, don't allow a commit if the working tree
198
contains unknown files.
181
200
revprops -- Properties for new revision
183
202
mutter('preparing to commit')
185
204
self.branch = branch
186
self.weave_store = branch.weave_store
205
self.weave_store = branch.repository.weave_store
187
206
self.rev_id = rev_id
188
207
self.specific_files = specific_files
189
208
self.allow_pointless = allow_pointless
190
self.revprops = revprops
209
self.revprops = {'branch-nick': branch.nick}
211
self.revprops.update(revprops)
212
self.work_tree = WorkingTree(branch.base, branch)
215
# raise an exception as soon as we find a single unknown.
216
for unknown in self.work_tree.unknowns():
217
raise StrictCommitFailed()
192
219
if timestamp is None:
193
220
self.timestamp = time.time()
195
222
self.timestamp = long(timestamp)
224
if self.config is None:
225
self.config = bzrlib.config.BranchConfig(self.branch)
197
227
if rev_id is None:
198
self.rev_id = _gen_revision_id(self.branch, self.timestamp)
228
self.rev_id = _gen_revision_id(self.config, self.timestamp)
200
230
self.rev_id = rev_id
202
232
if committer is None:
203
self.committer = bzrlib.config.username(self.branch)
233
self.committer = self.config.username()
205
235
assert isinstance(committer, basestring), type(committer)
206
236
self.committer = committer
211
241
self.timezone = int(timezone)
213
assert isinstance(message, basestring), type(message)
243
if isinstance(message, str):
244
message = message.decode(bzrlib.user_encoding)
245
assert isinstance(message, unicode), type(message)
214
246
self.message = message
215
247
self._escape_commit_message()
217
249
self.branch.lock_write()
219
self.work_tree = self.branch.working_tree()
220
251
self.work_inv = self.work_tree.inventory
221
252
self.basis_tree = self.branch.basis_tree()
222
253
self.basis_inv = self.basis_tree.inventory
242
273
self._record_inventory()
243
274
self._make_revision()
275
self.work_tree.set_pending_merges([])
276
self.branch.append_revision(self.rev_id)
244
277
self.reporter.completed(self.branch.revno()+1, self.rev_id)
245
self.branch.append_revision(self.rev_id)
246
self.branch.set_pending_merges([])
278
if self.config.post_commit() is not None:
279
hooks = self.config.post_commit().split(' ')
280
# this would be nicer with twisted.python.reflect.namedAny
282
result = eval(hook + '(branch, rev_id)',
283
{'branch':self.branch,
285
'rev_id':self.rev_id})
248
287
self.branch.unlock()
251
290
"""Store the inventory for the new revision."""
252
291
inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
253
292
self.inv_sha1 = sha_string(inv_text)
254
s = self.branch.control_weaves
293
s = self.branch.repository.control_weaves
255
294
s.add_text('inventory', self.rev_id,
256
295
split_lines(inv_text), self.present_parents,
257
296
self.branch.get_transaction())
262
301
# represented in well-formed XML; escape characters that
263
302
# aren't listed in the XML specification
264
303
# (http://www.w3.org/TR/REC-xml/#NT-Char).
265
if isinstance(self.message, unicode):
266
char_pattern = u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]'
268
# Use a regular 'str' as pattern to avoid having re.subn
269
# return 'unicode' results.
270
char_pattern = '[^x09\x0A\x0D\x20-\xFF]'
271
304
self.message, escape_count = re.subn(
305
u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
273
306
lambda match: match.group(0).encode('unicode_escape'),
278
311
def _gather_parents(self):
279
312
"""Record the parents of a merge for merge detection."""
280
pending_merges = self.branch.pending_merges()
313
pending_merges = self.work_tree.pending_merges()
281
314
self.parents = []
282
315
self.parent_invs = []
283
316
self.present_parents = []
286
319
self.parents.append(precursor_id)
287
320
self.parents += pending_merges
288
321
for revision in self.parents:
289
if self.branch.has_revision(revision):
290
self.parent_invs.append(self.branch.get_inventory(revision))
322
if self.branch.repository.has_revision(revision):
323
inventory = self.branch.repository.get_inventory(revision)
324
self.parent_invs.append(inventory)
291
325
self.present_parents.append(revision)
293
327
def _check_parents_present(self):
294
328
for parent_id in self.parents:
295
329
mutter('commit parent revision {%s}', parent_id)
296
if not self.branch.has_revision(parent_id):
330
if not self.branch.repository.has_revision(parent_id):
297
331
if parent_id == self.branch.last_revision():
298
332
warning("parent is missing %r", parent_id)
299
333
raise HistoryMissing(self.branch, 'revision', parent_id)
313
347
rev_tmp = StringIO()
314
348
serializer_v5.write_revision(self.rev, rev_tmp)
316
self.branch.revision_store.add(rev_tmp, self.rev_id)
350
if self.config.signature_needed():
351
plaintext = Testament(self.rev, self.new_inv).as_short_text()
352
self.branch.repository.store_revision_signature(
353
gpg.GPGStrategy(self.config), plaintext, self.rev_id)
354
self.branch.repository.revision_store.add(rev_tmp, self.rev_id)
317
355
mutter('new revision_id is {%s}', self.rev_id)
319
357
def _remove_deleted(self):
339
377
deleted_ids.sort(reverse=True)
340
378
for path, file_id in deleted_ids:
341
379
del self.work_inv[file_id]
342
self.branch._write_inventory(self.work_inv)
380
self.work_tree._write_inventory(self.work_inv)
344
382
def _store_snapshot(self):
345
383
"""Pass over inventory and record a snapshot.
354
392
for path, ie in self.new_inv.iter_entries():
355
393
previous_entries = ie.find_previous_heads(
356
394
self.parent_invs,
357
self.weave_store.get_weave_or_empty(ie.file_id,
395
self.weave_store.get_weave_prelude_or_empty(ie.file_id,
358
396
self.branch.get_transaction()))
359
397
if ie.revision is None:
360
398
change = ie.snapshot(self.rev_id, path, previous_entries,
419
457
if file_id not in self.new_inv:
420
458
self.reporter.deleted(self.basis_inv.id2path(file_id))
422
def _gen_revision_id(branch, when):
460
def _gen_revision_id(config, when):
423
461
"""Return new revision-id."""
424
s = '%s-%s-' % (bzrlib.config.user_email(branch), compact_date(when))
462
s = '%s-%s-' % (config.user_email(), compact_date(when))
425
463
s += hexlify(rand_bytes(8))