/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Jonathan Lange
  • Date: 2009-12-09 09:20:42 UTC
  • mfrom: (4881 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4907.
  • Revision ID: jml@canonical.com-20091209092042-s2zgqcf8f39yzxpj
Merge trunk.

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