~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Vincent Ladeuil
  • Date: 2009-07-10 07:43:15 UTC
  • mto: (4529.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 4530.
  • Revision ID: v.ladeuil+lp@free.fr-20090710074315-tfbke0r54hk2af6o
Fix TZ-dependent tests.

* bzrlib/tests/test_annotate.py:
(TestAnnotate.create_merged_trees,
TestAnnotate.create_deeply_merged_trees): Isolate from timezone.

* bzrlib/branchbuilder.py:
(BranchBuilder.build_snapshot): timezone is useful to build
TZ-independent tests .

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
65
65
    xml_serializer,
66
66
    )
67
67
from bzrlib.branch import Branch
68
 
from bzrlib.cleanup import OperationWithCleanups
69
68
import bzrlib.config
70
69
from bzrlib.errors import (BzrError, PointlessCommit,
71
70
                           ConflictsInTree,
205
204
        """Commit working copy as a new revision.
206
205
 
207
206
        :param message: the commit message (it or message_callback is required)
208
 
        :param message_callback: A callback: message = message_callback(cmt_obj)
209
207
 
210
208
        :param timestamp: if not None, seconds-since-epoch for a
211
209
            postdated/predated commit.
212
210
 
213
 
        :param specific_files: If not None, commit only those files. An empty
214
 
            list means 'commit no files'.
 
211
        :param specific_files: If true, commit only those files.
215
212
 
216
213
        :param rev_id: If set, use this as the new revision id.
217
214
            Useful for test or import commands that need to tightly
235
232
            commit. Pending changes to excluded files will be ignored by the
236
233
            commit.
237
234
        """
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):
263
235
        mutter('preparing to commit')
264
236
 
265
237
        if working_tree is None:
288
260
            self.exclude = []
289
261
        self.local = local
290
262
        self.master_branch = None
 
263
        self.master_locked = False
291
264
        self.recursive = recursive
292
265
        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.
295
266
        if specific_files is not None:
296
267
            self.specific_files = sorted(
297
268
                minimum_path_selection(specific_files))
308
279
        self.verbose = verbose
309
280
 
310
281
        self.work_tree.lock_write()
311
 
        operation.add_cleanup(self.work_tree.unlock)
312
282
        self.parents = self.work_tree.get_parent_ids()
313
283
        # We can use record_iter_changes IFF iter_changes is compatible with
314
284
        # the command line parameters, and the repository has fast delta
315
285
        # generation. See bug 347649.
316
286
        self.use_record_iter_changes = (
 
287
            not self.specific_files and
317
288
            not self.exclude and 
318
289
            not self.branch.repository._format.supports_tree_reference and
319
290
            (self.branch.repository._format.fast_deltas or
320
291
             len(self.parents) < 2))
321
292
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
322
 
        operation.add_cleanup(self.pb.finished)
323
293
        self.basis_revid = self.work_tree.last_revision()
324
294
        self.basis_tree = self.work_tree.basis_tree()
325
295
        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
 
 
376
296
        try:
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)
 
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()
434
403
        return self.rev_id
435
404
 
436
405
    def _select_reporter(self):
458
427
            return
459
428
        raise PointlessCommit()
460
429
 
461
 
    def _check_bound_branch(self, operation, possible_master_transports=None):
 
430
    def _check_bound_branch(self, possible_master_transports=None):
462
431
        """Check to see if the local branch is bound.
463
432
 
464
433
        If it is bound, then most of the commit will actually be
499
468
        # so grab the lock
500
469
        self.bound_branch = self.branch
501
470
        self.master_branch.lock_write()
502
 
        operation.add_cleanup(self.master_branch.unlock)
 
471
        self.master_locked = True
503
472
 
504
473
    def _check_out_of_date_tree(self):
505
474
        """Check that the working tree is up to date.
590
559
                     old_revno, old_revid, new_revno, self.rev_id,
591
560
                     tree_delta, future_tree)
592
561
 
 
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
 
593
598
    def _gather_parents(self):
594
599
        """Record the parents of a merge for merge detection."""
595
600
        # TODO: Make sure that this list doesn't contain duplicate
610
615
        """Update the commit builder with the data about what has changed.
611
616
        """
612
617
        exclude = self.exclude
613
 
        specific_files = self.specific_files
 
618
        specific_files = self.specific_files or []
614
619
        mutter("Selecting files for commit with filter %s", specific_files)
615
620
 
616
621
        self._check_strict()
617
622
        if self.use_record_iter_changes:
618
 
            iter_changes = self.work_tree.iter_changes(self.basis_tree,
619
 
                specific_files=specific_files)
 
623
            iter_changes = self.work_tree.iter_changes(self.basis_tree)
620
624
            iter_changes = self._filter_iter_changes(iter_changes)
621
625
            for file_id, path, fs_hash in self.builder.record_iter_changes(
622
626
                self.work_tree, self.basis_revid, iter_changes):
675
679
                            reporter.snapshot_change('modified', new_path)
676
680
            self._next_progress_entry()
677
681
        # Unversion IDs that were found to be deleted
678
 
        self.deleted_ids = deleted_ids
 
682
        self.work_tree.unversion(deleted_ids)
679
683
 
680
684
    def _record_unselected(self):
681
685
        # If specific files are selected, then all un-selected files must be
794
798
                # _update_builder_with_changes.
795
799
                continue
796
800
            content_summary = self.work_tree.path_content_summary(path)
797
 
            kind = content_summary[0]
798
801
            # Note that when a filter of specific files is given, we must only
799
802
            # skip/record deleted files matching that filter.
800
803
            if not specific_files or is_inside_any(specific_files, path):
801
 
                if kind == 'missing':
 
804
                if content_summary[0] == 'missing':
802
805
                    if not deleted_paths:
803
806
                        # path won't have been split yet.
804
807
                        path_segments = splitpath(path)
811
814
                    continue
812
815
            # TODO: have the builder do the nested commit just-in-time IF and
813
816
            # only if needed.
814
 
            if kind == 'tree-reference':
 
817
            if content_summary[0] == 'tree-reference':
815
818
                # enforce repository nested tree policy.
816
819
                if (not self.work_tree.supports_tree_reference() or
817
820
                    # repository does not support it either.
818
821
                    not self.branch.repository._format.supports_tree_reference):
819
 
                    kind = 'directory'
820
 
                    content_summary = (kind, None, None, None)
821
 
                elif self.recursive == 'down':
 
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':
822
827
                    nested_revision_id = self._commit_nested_tree(
823
828
                        file_id, path)
824
 
                    content_summary = (kind, None, None, nested_revision_id)
 
829
                    content_summary = content_summary[:3] + (
 
830
                        nested_revision_id,)
825
831
                else:
826
 
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
827
 
                    content_summary = (kind, None, None, nested_revision_id)
 
832
                    content_summary = content_summary[:3] + (
 
833
                        self.work_tree.get_reference_revision(file_id),)
828
834
 
829
835
            # Record an entry for this item
830
836
            # Note: I don't particularly want to have the existing_ie
836
842
                content_summary)
837
843
 
838
844
        # Unversion IDs that were found to be deleted
839
 
        self.deleted_ids = deleted_ids
 
845
        self.work_tree.unversion(deleted_ids)
840
846
 
841
847
    def _commit_nested_tree(self, file_id, path):
842
848
        "Commit a nested tree."