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
82
import bzrlib.errors as errors
76
83
from bzrlib.errors import (BzrError, PointlessCommit,
88
import bzrlib.gpg as gpg
80
89
from bzrlib.revision import Revision
90
from bzrlib.testament import Testament
81
91
from bzrlib.trace import mutter, note, warning
82
92
from bzrlib.xml5 import serializer_v5
83
93
from bzrlib.inventory import Inventory, ROOT_ID
94
from bzrlib.symbol_versioning import *
84
95
from bzrlib.weave import Weave
85
96
from bzrlib.weavefile import read_weave, write_weave_v5
86
from bzrlib.atomicfile import AtomicFile
97
from bzrlib.workingtree import WorkingTree
100
@deprecated_function(zero_seven)
89
101
def commit(*args, **kwargs):
90
102
"""Commit a new revision to a branch.
145
159
working inventory.
147
161
def __init__(self,
149
164
if reporter is not None:
150
165
self.reporter = reporter
152
167
self.reporter = NullCommitReporter()
168
if config is not None:
174
branch=DEPRECATED_PARAMETER, message=None,
160
178
specific_files=None,
162
180
allow_pointless=True,
165
185
"""Commit working copy as a new revision.
187
branch -- the deprecated branch to commit to. New callers should pass in
190
message -- the commit message, a mandatory parameter
167
192
timestamp -- if not None, seconds-since-epoch for a
168
193
postdated/predated commit.
178
203
allow_pointless -- If true (default), commit even if nothing
179
204
has changed and no merges are recorded.
206
strict -- If true, don't allow a commit if the working tree
207
contains unknown files.
181
209
revprops -- Properties for new revision
183
211
mutter('preparing to commit')
186
self.weave_store = branch.weave_store
213
if deprecated_passed(branch):
214
warn("Commit.commit (branch, ...): The branch parameter is "
215
"deprecated as of bzr 0.8. Please use working_tree= instead.",
216
DeprecationWarning, stacklevel=2)
218
self.work_tree = self.branch.bzrdir.open_workingtree()
219
elif working_tree is None:
220
raise BzrError("One of branch and working_tree must be passed into commit().")
222
self.work_tree = working_tree
223
self.branch = self.work_tree.branch
225
raise BzrError("The message keyword parameter is required for commit().")
227
self.weave_store = self.branch.repository.weave_store
187
228
self.rev_id = rev_id
188
229
self.specific_files = specific_files
189
230
self.allow_pointless = allow_pointless
190
self.revprops = revprops
231
self.revprops = {'branch-nick': self.branch.nick}
233
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()
192
244
if timestamp is None:
193
245
self.timestamp = time.time()
195
247
self.timestamp = long(timestamp)
249
if self.config is None:
250
self.config = bzrlib.config.BranchConfig(self.branch)
197
252
if rev_id is None:
198
self.rev_id = _gen_revision_id(self.branch, self.timestamp)
253
self.rev_id = _gen_revision_id(self.config, self.timestamp)
200
255
self.rev_id = rev_id
202
257
if committer is None:
203
self.committer = bzrlib.config.username(self.branch)
258
self.committer = self.config.username()
205
260
assert isinstance(committer, basestring), type(committer)
206
261
self.committer = committer
211
266
self.timezone = int(timezone)
213
assert isinstance(message, basestring), type(message)
268
if isinstance(message, str):
269
message = message.decode(bzrlib.user_encoding)
270
assert isinstance(message, unicode), type(message)
214
271
self.message = message
215
272
self._escape_commit_message()
217
274
self.branch.lock_write()
219
self.work_tree = self.branch.working_tree()
220
276
self.work_inv = self.work_tree.inventory
221
self.basis_tree = self.branch.basis_tree()
277
self.basis_tree = self.work_tree.basis_tree()
222
278
self.basis_inv = self.basis_tree.inventory
224
280
self._gather_parents()
242
298
self._record_inventory()
243
299
self._make_revision()
300
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)
244
307
self.reporter.completed(self.branch.revno()+1, self.rev_id)
245
self.branch.append_revision(self.rev_id)
246
self.branch.set_pending_merges([])
308
if self.config.post_commit() is not None:
309
hooks = self.config.post_commit().split(' ')
310
# this would be nicer with twisted.python.reflect.namedAny
312
result = eval(hook + '(branch, rev_id)',
313
{'branch':self.branch,
315
'rev_id':self.rev_id})
248
317
self.branch.unlock()
251
320
"""Store the inventory for the new revision."""
252
321
inv_text = serializer_v5.write_inventory_to_string(self.new_inv)
253
322
self.inv_sha1 = sha_string(inv_text)
254
s = self.branch.control_weaves
323
s = self.branch.repository.control_weaves
255
324
s.add_text('inventory', self.rev_id,
256
325
split_lines(inv_text), self.present_parents,
257
326
self.branch.get_transaction())
262
331
# represented in well-formed XML; escape characters that
263
332
# aren't listed in the XML specification
264
333
# (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
334
self.message, escape_count = re.subn(
335
u'[^\x09\x0A\x0D\u0020-\uD7FF\uE000-\uFFFD]+',
273
336
lambda match: match.group(0).encode('unicode_escape'),
278
341
def _gather_parents(self):
279
342
"""Record the parents of a merge for merge detection."""
280
pending_merges = self.branch.pending_merges()
343
pending_merges = self.work_tree.pending_merges()
281
344
self.parents = []
282
345
self.parent_invs = []
283
346
self.present_parents = []
286
349
self.parents.append(precursor_id)
287
350
self.parents += pending_merges
288
351
for revision in self.parents:
289
if self.branch.has_revision(revision):
290
self.parent_invs.append(self.branch.get_inventory(revision))
352
if self.branch.repository.has_revision(revision):
353
inventory = self.branch.repository.get_inventory(revision)
354
self.parent_invs.append(inventory)
291
355
self.present_parents.append(revision)
293
357
def _check_parents_present(self):
294
358
for parent_id in self.parents:
295
359
mutter('commit parent revision {%s}', parent_id)
296
if not self.branch.has_revision(parent_id):
360
if not self.branch.repository.has_revision(parent_id):
297
361
if parent_id == self.branch.last_revision():
298
362
warning("parent is missing %r", parent_id)
299
363
raise HistoryMissing(self.branch, 'revision', parent_id)
313
377
rev_tmp = StringIO()
314
378
serializer_v5.write_revision(self.rev, rev_tmp)
316
self.branch.revision_store.add(rev_tmp, self.rev_id)
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)
317
385
mutter('new revision_id is {%s}', self.rev_id)
319
387
def _remove_deleted(self):
339
407
deleted_ids.sort(reverse=True)
340
408
for path, file_id in deleted_ids:
341
409
del self.work_inv[file_id]
342
self.branch._write_inventory(self.work_inv)
410
self.work_tree._write_inventory(self.work_inv)
344
412
def _store_snapshot(self):
345
413
"""Pass over inventory and record a snapshot.
354
422
for path, ie in self.new_inv.iter_entries():
355
423
previous_entries = ie.find_previous_heads(
356
424
self.parent_invs,
357
self.weave_store.get_weave_or_empty(ie.file_id,
425
self.weave_store.get_weave_prelude_or_empty(ie.file_id,
358
426
self.branch.get_transaction()))
359
427
if ie.revision is None:
360
428
change = ie.snapshot(self.rev_id, path, previous_entries,
419
487
if file_id not in self.new_inv:
420
488
self.reporter.deleted(self.basis_inv.id2path(file_id))
422
def _gen_revision_id(branch, when):
490
def _gen_revision_id(config, when):
423
491
"""Return new revision-id."""
424
s = '%s-%s-' % (bzrlib.config.user_email(branch), compact_date(when))
492
s = '%s-%s-' % (config.user_email(), compact_date(when))
425
493
s += hexlify(rand_bytes(8))