283
282
:param lossy: When committing to a foreign VCS, ignore any
284
283
data that can not be natively represented.
286
operation = OperationWithCleanups(self._commit)
287
self.revprops = revprops or {}
288
# XXX: Can be set on __init__ or passed in - this is a bit ugly.
289
self.config_stack = config or self.config_stack
290
return operation.run(
295
specific_files=specific_files,
297
allow_pointless=allow_pointless,
300
working_tree=working_tree,
303
message_callback=message_callback,
306
possible_master_transports=possible_master_transports,
309
def _commit(self, operation, message, timestamp, timezone, committer,
310
specific_files, rev_id, allow_pointless, strict, verbose,
311
working_tree, local, reporter, message_callback, recursive,
312
exclude, possible_master_transports, lossy):
313
mutter('preparing to commit')
315
if working_tree is None:
316
raise BzrError("working_tree must be passed into commit().")
318
self.work_tree = working_tree
319
self.branch = self.work_tree.branch
320
if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
321
if not self.branch.repository.supports_rich_root():
322
raise errors.RootNotRich()
323
if message_callback is None:
324
if message is not None:
325
if isinstance(message, bytes):
326
message = message.decode(get_user_encoding())
328
def message_callback(x):
331
raise BzrError("The message or message_callback keyword"
332
" parameter is required for commit().")
334
self.bound_branch = None
335
self.any_entries_deleted = False
336
if exclude is not None:
337
self.exclude = sorted(
338
minimum_path_selection(exclude))
342
self.master_branch = None
343
self.recursive = recursive
345
# self.specific_files is None to indicate no filter, or any iterable to
346
# indicate a filter - [] means no files at all, as per iter_changes.
347
if specific_files is not None:
348
self.specific_files = sorted(
349
minimum_path_selection(specific_files))
351
self.specific_files = None
353
self.allow_pointless = allow_pointless
354
self.message_callback = message_callback
355
self.timestamp = timestamp
356
self.timezone = timezone
357
self.committer = committer
359
self.verbose = verbose
361
self.work_tree.lock_write()
362
operation.add_cleanup(self.work_tree.unlock)
363
self.parents = self.work_tree.get_parent_ids()
364
self.pb = ui.ui_factory.nested_progress_bar()
365
operation.add_cleanup(self.pb.finished)
366
self.basis_revid = self.work_tree.last_revision()
367
self.basis_tree = self.work_tree.basis_tree()
368
self.basis_tree.lock_read()
369
operation.add_cleanup(self.basis_tree.unlock)
370
# Cannot commit with conflicts present.
371
if len(self.work_tree.conflicts()) > 0:
372
raise ConflictsInTree
374
# Setup the bound branch variables as needed.
375
self._check_bound_branch(operation, possible_master_transports)
377
# Check that the working tree is up to date
378
old_revno, old_revid, new_revno = self._check_out_of_date_tree()
380
# Complete configuration setup
381
if reporter is not None:
382
self.reporter = reporter
383
elif self.reporter is None:
384
self.reporter = self._select_reporter()
385
if self.config_stack is None:
386
self.config_stack = self.work_tree.get_config_stack()
388
# Setup the progress bar. As the number of files that need to be
389
# committed in unknown, progress is reported as stages.
390
# We keep track of entries separately though and include that
391
# information in the progress bar during the relevant stages.
392
self.pb_stage_name = ""
393
self.pb_stage_count = 0
394
self.pb_stage_total = 5
395
if self.bound_branch:
396
# 2 extra stages: "Uploading data to master branch" and "Merging
397
# tags to master branch"
398
self.pb_stage_total += 2
399
self.pb.show_pct = False
400
self.pb.show_spinner = False
401
self.pb.show_eta = False
402
self.pb.show_count = True
403
self.pb.show_bar = True
405
# After a merge, a selected file commit is not supported.
406
# See 'bzr help merge' for an explanation as to why.
407
if len(self.parents) > 1 and self.specific_files is not None:
408
raise CannotCommitSelectedFileMerge(self.specific_files)
409
# Excludes are a form of selected file commit.
410
if len(self.parents) > 1 and self.exclude:
411
raise CannotCommitSelectedFileMerge(self.exclude)
413
# Collect the changes
414
self._set_progress_stage("Collecting changes", counter=True)
416
self.builder = self.branch.get_commit_builder(
417
self.parents, self.config_stack, timestamp, timezone, committer,
418
self.revprops, rev_id, lossy=lossy)
420
if self.builder.updates_branch and self.bound_branch:
422
raise AssertionError(
423
"bound branches not supported for commit builders "
424
"that update the branch")
427
# find the location being committed to
285
with ExitStack() as stack:
286
self.revprops = revprops or {}
287
# XXX: Can be set on __init__ or passed in - this is a bit ugly.
288
self.config_stack = config or self.config_stack
289
mutter('preparing to commit')
291
if working_tree is None:
292
raise BzrError("working_tree must be passed into commit().")
294
self.work_tree = working_tree
295
self.branch = self.work_tree.branch
296
if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
297
if not self.branch.repository.supports_rich_root():
298
raise errors.RootNotRich()
299
if message_callback is None:
300
if message is not None:
301
if isinstance(message, bytes):
302
message = message.decode(get_user_encoding())
304
def message_callback(x):
307
raise BzrError("The message or message_callback keyword"
308
" parameter is required for commit().")
310
self.bound_branch = None
311
self.any_entries_deleted = False
312
if exclude is not None:
313
self.exclude = sorted(
314
minimum_path_selection(exclude))
318
self.master_branch = None
319
self.recursive = recursive
321
# self.specific_files is None to indicate no filter, or any iterable to
322
# indicate a filter - [] means no files at all, as per iter_changes.
323
if specific_files is not None:
324
self.specific_files = sorted(
325
minimum_path_selection(specific_files))
327
self.specific_files = None
329
self.allow_pointless = allow_pointless
330
self.message_callback = message_callback
331
self.timestamp = timestamp
332
self.timezone = timezone
333
self.committer = committer
335
self.verbose = verbose
337
stack.enter_context(self.work_tree.lock_write())
338
self.parents = self.work_tree.get_parent_ids()
339
self.pb = ui.ui_factory.nested_progress_bar()
340
stack.callback(self.pb.finished)
341
self.basis_revid = self.work_tree.last_revision()
342
self.basis_tree = self.work_tree.basis_tree()
343
stack.enter_context(self.basis_tree.lock_read())
344
# Cannot commit with conflicts present.
345
if len(self.work_tree.conflicts()) > 0:
346
raise ConflictsInTree
348
# Setup the bound branch variables as needed.
349
self._check_bound_branch(stack, possible_master_transports)
350
if self.config_stack is None:
351
self.config_stack = self.work_tree.get_config_stack()
353
# Check that the working tree is up to date
354
old_revno, old_revid, new_revno = self._check_out_of_date_tree()
356
# Complete configuration setup
357
if reporter is not None:
358
self.reporter = reporter
359
elif self.reporter is None:
360
self.reporter = self._select_reporter()
362
# Setup the progress bar. As the number of files that need to be
363
# committed in unknown, progress is reported as stages.
364
# We keep track of entries separately though and include that
365
# information in the progress bar during the relevant stages.
366
self.pb_stage_name = ""
367
self.pb_stage_count = 0
368
self.pb_stage_total = 5
428
369
if self.bound_branch:
429
master_location = self.master_branch.base
431
master_location = self.branch.base
433
# report the start of the commit
434
self.reporter.started(new_revno, self.rev_id, master_location)
436
self._update_builder_with_changes()
437
self._check_pointless()
439
# TODO: Now the new inventory is known, check for conflicts.
440
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
441
# weave lines, because nothing should be recorded until it is known
442
# that commit will succeed.
443
self._set_progress_stage("Saving data locally")
444
self.builder.finish_inventory()
446
# Prompt the user for a commit message if none provided
447
message = message_callback(self)
448
self.message = message
450
# Add revision data to the local branch
451
self.rev_id = self.builder.commit(self.message)
454
mutter("aborting commit write group because of exception:")
455
trace.log_exception_quietly()
459
self._update_branches(old_revno, old_revid, new_revno)
461
# Make the working tree be up to date with the branch. This
462
# includes automatic changes scheduled to be made to the tree, such
463
# as updating its basis and unversioning paths that were missing.
464
self.work_tree.unversion(self.deleted_paths)
465
self._set_progress_stage("Updating the working tree")
466
self.work_tree.update_basis_by_delta(self.rev_id,
467
self.builder.get_basis_delta())
468
self.reporter.completed(new_revno, self.rev_id)
469
self._process_post_hooks(old_revno, new_revno)
370
# 2 extra stages: "Uploading data to master branch" and "Merging
371
# tags to master branch"
372
self.pb_stage_total += 2
373
self.pb.show_pct = False
374
self.pb.show_spinner = False
375
self.pb.show_eta = False
376
self.pb.show_count = True
377
self.pb.show_bar = True
379
# After a merge, a selected file commit is not supported.
380
# See 'bzr help merge' for an explanation as to why.
381
if len(self.parents) > 1 and self.specific_files is not None:
382
raise CannotCommitSelectedFileMerge(self.specific_files)
383
# Excludes are a form of selected file commit.
384
if len(self.parents) > 1 and self.exclude:
385
raise CannotCommitSelectedFileMerge(self.exclude)
387
# Collect the changes
388
self._set_progress_stage("Collecting changes", counter=True)
390
self.builder = self.branch.get_commit_builder(
391
self.parents, self.config_stack, timestamp, timezone, committer,
392
self.revprops, rev_id, lossy=lossy)
394
if self.builder.updates_branch and self.bound_branch:
396
raise AssertionError(
397
"bound branches not supported for commit builders "
398
"that update the branch")
401
# find the location being committed to
402
if self.bound_branch:
403
master_location = self.master_branch.base
405
master_location = self.branch.base
407
# report the start of the commit
408
self.reporter.started(new_revno, self.rev_id, master_location)
410
self._update_builder_with_changes()
411
self._check_pointless()
413
# TODO: Now the new inventory is known, check for conflicts.
414
# ADHB 2006-08-08: If this is done, populate_new_inv should not add
415
# weave lines, because nothing should be recorded until it is known
416
# that commit will succeed.
417
self._set_progress_stage("Saving data locally")
418
self.builder.finish_inventory()
420
# Prompt the user for a commit message if none provided
421
message = message_callback(self)
422
self.message = message
424
# Add revision data to the local branch
425
self.rev_id = self.builder.commit(self.message)
428
mutter("aborting commit write group because of exception:")
429
trace.log_exception_quietly()
433
self._update_branches(old_revno, old_revid, new_revno)
435
# Make the working tree be up to date with the branch. This
436
# includes automatic changes scheduled to be made to the tree, such
437
# as updating its basis and unversioning paths that were missing.
438
self.work_tree.unversion(self.deleted_paths)
439
self._set_progress_stage("Updating the working tree")
440
self.work_tree.update_basis_by_delta(
441
self.rev_id, self.builder.get_basis_delta())
442
self.reporter.completed(new_revno, self.rev_id)
443
self._process_post_hooks(old_revno, new_revno)
472
446
def _update_branches(self, old_revno, old_revid, new_revno):
473
447
"""Update the master and local branch to the new revision.