~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Martin Pool
  • Date: 2010-01-29 14:09:05 UTC
  • mto: This revision was merged to the branch mainline in revision 4992.
  • Revision ID: mbp@sourcefrog.net-20100129140905-2uiarb6p8di1ywsr
Correction to url

from review: https://code.edge.launchpad.net/~mbp/bzr/doc/+merge/18250

Show diffs side-by-side

added added

removed removed

Lines of Context:
65
65
    xml_serializer,
66
66
    )
67
67
from bzrlib.branch import Branch
 
68
from bzrlib.cleanup import OperationWithCleanups
68
69
import bzrlib.config
69
70
from bzrlib.errors import (BzrError, PointlessCommit,
70
71
                           ConflictsInTree,
209
210
        :param timestamp: if not None, seconds-since-epoch for a
210
211
            postdated/predated commit.
211
212
 
212
 
        :param specific_files: If true, commit only those files.
 
213
        :param specific_files: If not None, commit only those files. An empty
 
214
            list means 'commit no files'.
213
215
 
214
216
        :param rev_id: If set, use this as the new revision id.
215
217
            Useful for test or import commands that need to tightly
233
235
            commit. Pending changes to excluded files will be ignored by the
234
236
            commit.
235
237
        """
 
238
        operation = OperationWithCleanups(self._commit)
 
239
        return operation.run(
 
240
               message=message,
 
241
               timestamp=timestamp,
 
242
               timezone=timezone,
 
243
               committer=committer,
 
244
               specific_files=specific_files,
 
245
               rev_id=rev_id,
 
246
               allow_pointless=allow_pointless,
 
247
               strict=strict,
 
248
               verbose=verbose,
 
249
               revprops=revprops,
 
250
               working_tree=working_tree,
 
251
               local=local,
 
252
               reporter=reporter,
 
253
               config=config,
 
254
               message_callback=message_callback,
 
255
               recursive=recursive,
 
256
               exclude=exclude,
 
257
               possible_master_transports=possible_master_transports)
 
258
 
 
259
    def _commit(self, operation, message, timestamp, timezone, committer,
 
260
            specific_files, rev_id, allow_pointless, strict, verbose, revprops,
 
261
            working_tree, local, reporter, config, message_callback, recursive,
 
262
            exclude, possible_master_transports):
236
263
        mutter('preparing to commit')
237
264
 
238
265
        if working_tree is None:
261
288
            self.exclude = []
262
289
        self.local = local
263
290
        self.master_branch = None
264
 
        self.master_locked = False
265
291
        self.recursive = recursive
266
292
        self.rev_id = None
 
293
        # self.specific_files is None to indicate no filter, or any iterable to
 
294
        # indicate a filter - [] means no files at all, as per iter_changes.
267
295
        if specific_files is not None:
268
296
            self.specific_files = sorted(
269
297
                minimum_path_selection(specific_files))
280
308
        self.verbose = verbose
281
309
 
282
310
        self.work_tree.lock_write()
 
311
        operation.add_cleanup(self.work_tree.unlock)
283
312
        self.parents = self.work_tree.get_parent_ids()
284
313
        # We can use record_iter_changes IFF iter_changes is compatible with
285
314
        # the command line parameters, and the repository has fast delta
286
315
        # generation. See bug 347649.
287
316
        self.use_record_iter_changes = (
288
 
            not self.specific_files and
289
317
            not self.exclude and 
290
318
            not self.branch.repository._format.supports_tree_reference and
291
319
            (self.branch.repository._format.fast_deltas or
292
320
             len(self.parents) < 2))
293
321
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
322
        operation.add_cleanup(self.pb.finished)
294
323
        self.basis_revid = self.work_tree.last_revision()
295
324
        self.basis_tree = self.work_tree.basis_tree()
296
325
        self.basis_tree.lock_read()
 
326
        operation.add_cleanup(self.basis_tree.unlock)
 
327
        # Cannot commit with conflicts present.
 
328
        if len(self.work_tree.conflicts()) > 0:
 
329
            raise ConflictsInTree
 
330
 
 
331
        # Setup the bound branch variables as needed.
 
332
        self._check_bound_branch(operation, possible_master_transports)
 
333
 
 
334
        # Check that the working tree is up to date
 
335
        old_revno, new_revno = self._check_out_of_date_tree()
 
336
 
 
337
        # Complete configuration setup
 
338
        if reporter is not None:
 
339
            self.reporter = reporter
 
340
        elif self.reporter is None:
 
341
            self.reporter = self._select_reporter()
 
342
        if self.config is None:
 
343
            self.config = self.branch.get_config()
 
344
 
 
345
        self._set_specific_file_ids()
 
346
 
 
347
        # Setup the progress bar. As the number of files that need to be
 
348
        # committed in unknown, progress is reported as stages.
 
349
        # We keep track of entries separately though and include that
 
350
        # information in the progress bar during the relevant stages.
 
351
        self.pb_stage_name = ""
 
352
        self.pb_stage_count = 0
 
353
        self.pb_stage_total = 5
 
354
        if self.bound_branch:
 
355
            self.pb_stage_total += 1
 
356
        self.pb.show_pct = False
 
357
        self.pb.show_spinner = False
 
358
        self.pb.show_eta = False
 
359
        self.pb.show_count = True
 
360
        self.pb.show_bar = True
 
361
 
 
362
        self._gather_parents()
 
363
        # After a merge, a selected file commit is not supported.
 
364
        # See 'bzr help merge' for an explanation as to why.
 
365
        if len(self.parents) > 1 and self.specific_files is not None:
 
366
            raise errors.CannotCommitSelectedFileMerge(self.specific_files)
 
367
        # Excludes are a form of selected file commit.
 
368
        if len(self.parents) > 1 and self.exclude:
 
369
            raise errors.CannotCommitSelectedFileMerge(self.exclude)
 
370
 
 
371
        # Collect the changes
 
372
        self._set_progress_stage("Collecting changes", counter=True)
 
373
        self.builder = self.branch.get_commit_builder(self.parents,
 
374
            self.config, timestamp, timezone, committer, revprops, rev_id)
 
375
 
297
376
        try:
298
 
            # Cannot commit with conflicts present.
299
 
            if len(self.work_tree.conflicts()) > 0:
300
 
                raise ConflictsInTree
301
 
 
302
 
            # Setup the bound branch variables as needed.
303
 
            self._check_bound_branch(possible_master_transports)
304
 
 
305
 
            # Check that the working tree is up to date
306
 
            old_revno, new_revno = self._check_out_of_date_tree()
307
 
 
308
 
            # Complete configuration setup
309
 
            if reporter is not None:
310
 
                self.reporter = reporter
311
 
            elif self.reporter is None:
312
 
                self.reporter = self._select_reporter()
313
 
            if self.config is None:
314
 
                self.config = self.branch.get_config()
315
 
 
316
 
            self._set_specific_file_ids()
317
 
 
318
 
            # Setup the progress bar. As the number of files that need to be
319
 
            # committed in unknown, progress is reported as stages.
320
 
            # We keep track of entries separately though and include that
321
 
            # information in the progress bar during the relevant stages.
322
 
            self.pb_stage_name = ""
323
 
            self.pb_stage_count = 0
324
 
            self.pb_stage_total = 5
325
 
            if self.bound_branch:
326
 
                self.pb_stage_total += 1
327
 
            self.pb.show_pct = False
328
 
            self.pb.show_spinner = False
329
 
            self.pb.show_eta = False
330
 
            self.pb.show_count = True
331
 
            self.pb.show_bar = True
332
 
 
333
 
            self._gather_parents()
334
 
            # After a merge, a selected file commit is not supported.
335
 
            # See 'bzr help merge' for an explanation as to why.
336
 
            if len(self.parents) > 1 and self.specific_files:
337
 
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
338
 
            # Excludes are a form of selected file commit.
339
 
            if len(self.parents) > 1 and self.exclude:
340
 
                raise errors.CannotCommitSelectedFileMerge(self.exclude)
341
 
 
342
 
            # Collect the changes
343
 
            self._set_progress_stage("Collecting changes", counter=True)
344
 
            self.builder = self.branch.get_commit_builder(self.parents,
345
 
                self.config, timestamp, timezone, committer, revprops, rev_id)
346
 
 
347
 
            try:
348
 
                self.builder.will_record_deletes()
349
 
                # find the location being committed to
350
 
                if self.bound_branch:
351
 
                    master_location = self.master_branch.base
352
 
                else:
353
 
                    master_location = self.branch.base
354
 
 
355
 
                # report the start of the commit
356
 
                self.reporter.started(new_revno, self.rev_id, master_location)
357
 
 
358
 
                self._update_builder_with_changes()
359
 
                self._check_pointless()
360
 
 
361
 
                # TODO: Now the new inventory is known, check for conflicts.
362
 
                # ADHB 2006-08-08: If this is done, populate_new_inv should not add
363
 
                # weave lines, because nothing should be recorded until it is known
364
 
                # that commit will succeed.
365
 
                self._set_progress_stage("Saving data locally")
366
 
                self.builder.finish_inventory()
367
 
 
368
 
                # Prompt the user for a commit message if none provided
369
 
                message = message_callback(self)
370
 
                self.message = message
371
 
 
372
 
                # Add revision data to the local branch
373
 
                self.rev_id = self.builder.commit(self.message)
374
 
 
375
 
            except Exception, e:
376
 
                mutter("aborting commit write group because of exception:")
377
 
                trace.log_exception_quietly()
378
 
                note("aborting commit write group: %r" % (e,))
379
 
                self.builder.abort()
380
 
                raise
381
 
 
382
 
            self._process_pre_hooks(old_revno, new_revno)
383
 
 
384
 
            # Upload revision data to the master.
385
 
            # this will propagate merged revisions too if needed.
386
 
            if self.bound_branch:
387
 
                self._set_progress_stage("Uploading data to master branch")
388
 
                # 'commit' to the master first so a timeout here causes the
389
 
                # local branch to be out of date
390
 
                self.master_branch.import_last_revision_info(
391
 
                    self.branch.repository, new_revno, self.rev_id)
392
 
 
393
 
            # and now do the commit locally.
394
 
            self.branch.set_last_revision_info(new_revno, self.rev_id)
395
 
 
396
 
            # Make the working tree be up to date with the branch. This
397
 
            # includes automatic changes scheduled to be made to the tree, such
398
 
            # as updating its basis and unversioning paths that were missing.
399
 
            self.work_tree.unversion(self.deleted_ids)
400
 
            self._set_progress_stage("Updating the working tree")
401
 
            self.work_tree.update_basis_by_delta(self.rev_id,
402
 
                 self.builder.get_basis_delta())
403
 
            self.reporter.completed(new_revno, self.rev_id)
404
 
            self._process_post_hooks(old_revno, new_revno)
405
 
        finally:
406
 
            self._cleanup()
 
377
            self.builder.will_record_deletes()
 
378
            # find the location being committed to
 
379
            if self.bound_branch:
 
380
                master_location = self.master_branch.base
 
381
            else:
 
382
                master_location = self.branch.base
 
383
 
 
384
            # report the start of the commit
 
385
            self.reporter.started(new_revno, self.rev_id, master_location)
 
386
 
 
387
            self._update_builder_with_changes()
 
388
            self._check_pointless()
 
389
 
 
390
            # TODO: Now the new inventory is known, check for conflicts.
 
391
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
 
392
            # weave lines, because nothing should be recorded until it is known
 
393
            # that commit will succeed.
 
394
            self._set_progress_stage("Saving data locally")
 
395
            self.builder.finish_inventory()
 
396
 
 
397
            # Prompt the user for a commit message if none provided
 
398
            message = message_callback(self)
 
399
            self.message = message
 
400
 
 
401
            # Add revision data to the local branch
 
402
            self.rev_id = self.builder.commit(self.message)
 
403
 
 
404
        except Exception, e:
 
405
            mutter("aborting commit write group because of exception:")
 
406
            trace.log_exception_quietly()
 
407
            note("aborting commit write group: %r" % (e,))
 
408
            self.builder.abort()
 
409
            raise
 
410
 
 
411
        self._process_pre_hooks(old_revno, new_revno)
 
412
 
 
413
        # Upload revision data to the master.
 
414
        # this will propagate merged revisions too if needed.
 
415
        if self.bound_branch:
 
416
            self._set_progress_stage("Uploading data to master branch")
 
417
            # 'commit' to the master first so a timeout here causes the
 
418
            # local branch to be out of date
 
419
            self.master_branch.import_last_revision_info(
 
420
                self.branch.repository, new_revno, self.rev_id)
 
421
 
 
422
        # and now do the commit locally.
 
423
        self.branch.set_last_revision_info(new_revno, self.rev_id)
 
424
 
 
425
        # Make the working tree be up to date with the branch. This
 
426
        # includes automatic changes scheduled to be made to the tree, such
 
427
        # as updating its basis and unversioning paths that were missing.
 
428
        self.work_tree.unversion(self.deleted_ids)
 
429
        self._set_progress_stage("Updating the working tree")
 
430
        self.work_tree.update_basis_by_delta(self.rev_id,
 
431
             self.builder.get_basis_delta())
 
432
        self.reporter.completed(new_revno, self.rev_id)
 
433
        self._process_post_hooks(old_revno, new_revno)
407
434
        return self.rev_id
408
435
 
409
436
    def _select_reporter(self):
431
458
            return
432
459
        raise PointlessCommit()
433
460
 
434
 
    def _check_bound_branch(self, possible_master_transports=None):
 
461
    def _check_bound_branch(self, operation, possible_master_transports=None):
435
462
        """Check to see if the local branch is bound.
436
463
 
437
464
        If it is bound, then most of the commit will actually be
472
499
        # so grab the lock
473
500
        self.bound_branch = self.branch
474
501
        self.master_branch.lock_write()
475
 
        self.master_locked = True
 
502
        operation.add_cleanup(self.master_branch.unlock)
476
503
 
477
504
    def _check_out_of_date_tree(self):
478
505
        """Check that the working tree is up to date.
563
590
                     old_revno, old_revid, new_revno, self.rev_id,
564
591
                     tree_delta, future_tree)
565
592
 
566
 
    def _cleanup(self):
567
 
        """Cleanup any open locks, progress bars etc."""
568
 
        cleanups = [self._cleanup_bound_branch,
569
 
                    self.basis_tree.unlock,
570
 
                    self.work_tree.unlock,
571
 
                    self.pb.finished]
572
 
        found_exception = None
573
 
        for cleanup in cleanups:
574
 
            try:
575
 
                cleanup()
576
 
            # we want every cleanup to run no matter what.
577
 
            # so we have a catchall here, but we will raise the
578
 
            # last encountered exception up the stack: and
579
 
            # typically this will be useful enough.
580
 
            except Exception, e:
581
 
                found_exception = e
582
 
        if found_exception is not None:
583
 
            # don't do a plan raise, because the last exception may have been
584
 
            # trashed, e is our sure-to-work exception even though it loses the
585
 
            # full traceback. XXX: RBC 20060421 perhaps we could check the
586
 
            # exc_info and if its the same one do a plain raise otherwise
587
 
            # 'raise e' as we do now.
588
 
            raise e
589
 
 
590
 
    def _cleanup_bound_branch(self):
591
 
        """Executed at the end of a try/finally to cleanup a bound branch.
592
 
 
593
 
        If the branch wasn't bound, this is a no-op.
594
 
        If it was, it resents self.branch to the local branch, instead
595
 
        of being the master.
596
 
        """
597
 
        if not self.bound_branch:
598
 
            return
599
 
        if self.master_locked:
600
 
            self.master_branch.unlock()
601
 
 
602
593
    def _gather_parents(self):
603
594
        """Record the parents of a merge for merge detection."""
604
595
        # TODO: Make sure that this list doesn't contain duplicate
619
610
        """Update the commit builder with the data about what has changed.
620
611
        """
621
612
        exclude = self.exclude
622
 
        specific_files = self.specific_files or []
 
613
        specific_files = self.specific_files
623
614
        mutter("Selecting files for commit with filter %s", specific_files)
624
615
 
625
616
        self._check_strict()
626
617
        if self.use_record_iter_changes:
627
 
            iter_changes = self.work_tree.iter_changes(self.basis_tree)
 
618
            iter_changes = self.work_tree.iter_changes(self.basis_tree,
 
619
                specific_files=specific_files)
628
620
            iter_changes = self._filter_iter_changes(iter_changes)
629
621
            for file_id, path, fs_hash in self.builder.record_iter_changes(
630
622
                self.work_tree, self.basis_revid, iter_changes):
802
794
                # _update_builder_with_changes.
803
795
                continue
804
796
            content_summary = self.work_tree.path_content_summary(path)
 
797
            kind = content_summary[0]
805
798
            # Note that when a filter of specific files is given, we must only
806
799
            # skip/record deleted files matching that filter.
807
800
            if not specific_files or is_inside_any(specific_files, path):
808
 
                if content_summary[0] == 'missing':
 
801
                if kind == 'missing':
809
802
                    if not deleted_paths:
810
803
                        # path won't have been split yet.
811
804
                        path_segments = splitpath(path)
818
811
                    continue
819
812
            # TODO: have the builder do the nested commit just-in-time IF and
820
813
            # only if needed.
821
 
            if content_summary[0] == 'tree-reference':
 
814
            if kind == 'tree-reference':
822
815
                # enforce repository nested tree policy.
823
816
                if (not self.work_tree.supports_tree_reference() or
824
817
                    # repository does not support it either.
825
818
                    not self.branch.repository._format.supports_tree_reference):
826
 
                    content_summary = ('directory',) + content_summary[1:]
827
 
            kind = content_summary[0]
828
 
            # TODO: specific_files filtering before nested tree processing
829
 
            if kind == 'tree-reference':
830
 
                if self.recursive == 'down':
 
819
                    kind = 'directory'
 
820
                    content_summary = (kind, None, None, None)
 
821
                elif self.recursive == 'down':
831
822
                    nested_revision_id = self._commit_nested_tree(
832
823
                        file_id, path)
833
 
                    content_summary = content_summary[:3] + (
834
 
                        nested_revision_id,)
 
824
                    content_summary = (kind, None, None, nested_revision_id)
835
825
                else:
836
 
                    content_summary = content_summary[:3] + (
837
 
                        self.work_tree.get_reference_revision(file_id),)
 
826
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
 
827
                    content_summary = (kind, None, None, nested_revision_id)
838
828
 
839
829
            # Record an entry for this item
840
830
            # Note: I don't particularly want to have the existing_ie