/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: Aaron Bentley
  • Date: 2009-06-20 01:17:38 UTC
  • mto: This revision was merged to the branch mainline in revision 4465.
  • Revision ID: aaron@aaronbentley.com-20090620011738-ui5v1f48fluueiix
ContainerPushParser.accept_bytes handles zero-length records correctly.

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,
147
146
 
148
147
    def completed(self, revno, rev_id):
149
148
        self._note('Committed revision %d.', revno)
150
 
        # self._note goes to the console too; so while we want to log the
151
 
        # rev_id, we can't trivially only log it. (See bug 526425). Long
152
 
        # term we should rearrange the reporting structure, but for now
153
 
        # we just mutter seperately. We mutter the revid and revno together
154
 
        # so that concurrent bzr invocations won't lead to confusion.
155
 
        mutter('Committed revid %s as revno %d.', rev_id, revno)
156
149
 
157
150
    def deleted(self, path):
158
151
        self._note('deleted %s', path)
211
204
        """Commit working copy as a new revision.
212
205
 
213
206
        :param message: the commit message (it or message_callback is required)
214
 
        :param message_callback: A callback: message = message_callback(cmt_obj)
215
207
 
216
208
        :param timestamp: if not None, seconds-since-epoch for a
217
209
            postdated/predated commit.
218
210
 
219
 
        :param specific_files: If not None, commit only those files. An empty
220
 
            list means 'commit no files'.
 
211
        :param specific_files: If true, commit only those files.
221
212
 
222
213
        :param rev_id: If set, use this as the new revision id.
223
214
            Useful for test or import commands that need to tightly
241
232
            commit. Pending changes to excluded files will be ignored by the
242
233
            commit.
243
234
        """
244
 
        operation = OperationWithCleanups(self._commit)
245
 
        self.revprops = revprops or {}
246
 
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
247
 
        self.config = config or self.config
248
 
        return operation.run(
249
 
               message=message,
250
 
               timestamp=timestamp,
251
 
               timezone=timezone,
252
 
               committer=committer,
253
 
               specific_files=specific_files,
254
 
               rev_id=rev_id,
255
 
               allow_pointless=allow_pointless,
256
 
               strict=strict,
257
 
               verbose=verbose,
258
 
               working_tree=working_tree,
259
 
               local=local,
260
 
               reporter=reporter,
261
 
               message_callback=message_callback,
262
 
               recursive=recursive,
263
 
               exclude=exclude,
264
 
               possible_master_transports=possible_master_transports)
265
 
 
266
 
    def _commit(self, operation, message, timestamp, timezone, committer,
267
 
            specific_files, rev_id, allow_pointless, strict, verbose,
268
 
            working_tree, local, reporter, message_callback, recursive,
269
 
            exclude, possible_master_transports):
270
235
        mutter('preparing to commit')
271
236
 
272
237
        if working_tree is None:
295
260
            self.exclude = []
296
261
        self.local = local
297
262
        self.master_branch = None
 
263
        self.master_locked = False
298
264
        self.recursive = recursive
299
265
        self.rev_id = None
300
 
        # self.specific_files is None to indicate no filter, or any iterable to
301
 
        # indicate a filter - [] means no files at all, as per iter_changes.
302
266
        if specific_files is not None:
303
267
            self.specific_files = sorted(
304
268
                minimum_path_selection(specific_files))
306
270
            self.specific_files = None
307
271
            
308
272
        self.allow_pointless = allow_pointless
 
273
        self.revprops = revprops
309
274
        self.message_callback = message_callback
310
275
        self.timestamp = timestamp
311
276
        self.timezone = timezone
314
279
        self.verbose = verbose
315
280
 
316
281
        self.work_tree.lock_write()
317
 
        operation.add_cleanup(self.work_tree.unlock)
318
282
        self.parents = self.work_tree.get_parent_ids()
319
283
        # We can use record_iter_changes IFF iter_changes is compatible with
320
284
        # the command line parameters, and the repository has fast delta
321
285
        # generation. See bug 347649.
322
286
        self.use_record_iter_changes = (
 
287
            not self.specific_files and
323
288
            not self.exclude and 
324
289
            not self.branch.repository._format.supports_tree_reference and
325
290
            (self.branch.repository._format.fast_deltas or
326
291
             len(self.parents) < 2))
327
292
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
328
 
        operation.add_cleanup(self.pb.finished)
329
293
        self.basis_revid = self.work_tree.last_revision()
330
294
        self.basis_tree = self.work_tree.basis_tree()
331
295
        self.basis_tree.lock_read()
332
 
        operation.add_cleanup(self.basis_tree.unlock)
333
 
        # Cannot commit with conflicts present.
334
 
        if len(self.work_tree.conflicts()) > 0:
335
 
            raise ConflictsInTree
336
 
 
337
 
        # Setup the bound branch variables as needed.
338
 
        self._check_bound_branch(operation, possible_master_transports)
339
 
 
340
 
        # Check that the working tree is up to date
341
 
        old_revno, new_revno = self._check_out_of_date_tree()
342
 
 
343
 
        # Complete configuration setup
344
 
        if reporter is not None:
345
 
            self.reporter = reporter
346
 
        elif self.reporter is None:
347
 
            self.reporter = self._select_reporter()
348
 
        if self.config is None:
349
 
            self.config = self.branch.get_config()
350
 
 
351
 
        self._set_specific_file_ids()
352
 
 
353
 
        # Setup the progress bar. As the number of files that need to be
354
 
        # committed in unknown, progress is reported as stages.
355
 
        # We keep track of entries separately though and include that
356
 
        # information in the progress bar during the relevant stages.
357
 
        self.pb_stage_name = ""
358
 
        self.pb_stage_count = 0
359
 
        self.pb_stage_total = 5
360
 
        if self.bound_branch:
361
 
            self.pb_stage_total += 1
362
 
        self.pb.show_pct = False
363
 
        self.pb.show_spinner = False
364
 
        self.pb.show_eta = False
365
 
        self.pb.show_count = True
366
 
        self.pb.show_bar = True
367
 
 
368
 
        self._gather_parents()
369
 
        # After a merge, a selected file commit is not supported.
370
 
        # See 'bzr help merge' for an explanation as to why.
371
 
        if len(self.parents) > 1 and self.specific_files is not None:
372
 
            raise errors.CannotCommitSelectedFileMerge(self.specific_files)
373
 
        # Excludes are a form of selected file commit.
374
 
        if len(self.parents) > 1 and self.exclude:
375
 
            raise errors.CannotCommitSelectedFileMerge(self.exclude)
376
 
 
377
 
        # Collect the changes
378
 
        self._set_progress_stage("Collecting changes", counter=True)
379
 
        self.builder = self.branch.get_commit_builder(self.parents,
380
 
            self.config, timestamp, timezone, committer, self.revprops, rev_id)
381
 
 
382
296
        try:
383
 
            self.builder.will_record_deletes()
384
 
            # find the location being committed to
385
 
            if self.bound_branch:
386
 
                master_location = self.master_branch.base
387
 
            else:
388
 
                master_location = self.branch.base
389
 
 
390
 
            # report the start of the commit
391
 
            self.reporter.started(new_revno, self.rev_id, master_location)
392
 
 
393
 
            self._update_builder_with_changes()
394
 
            self._check_pointless()
395
 
 
396
 
            # TODO: Now the new inventory is known, check for conflicts.
397
 
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
398
 
            # weave lines, because nothing should be recorded until it is known
399
 
            # that commit will succeed.
400
 
            self._set_progress_stage("Saving data locally")
401
 
            self.builder.finish_inventory()
402
 
 
403
 
            # Prompt the user for a commit message if none provided
404
 
            message = message_callback(self)
405
 
            self.message = message
406
 
 
407
 
            # Add revision data to the local branch
408
 
            self.rev_id = self.builder.commit(self.message)
409
 
 
410
 
        except Exception, e:
411
 
            mutter("aborting commit write group because of exception:")
412
 
            trace.log_exception_quietly()
413
 
            note("aborting commit write group: %r" % (e,))
414
 
            self.builder.abort()
415
 
            raise
416
 
 
417
 
        self._process_pre_hooks(old_revno, new_revno)
418
 
 
419
 
        # Upload revision data to the master.
420
 
        # this will propagate merged revisions too if needed.
421
 
        if self.bound_branch:
422
 
            self._set_progress_stage("Uploading data to master branch")
423
 
            # 'commit' to the master first so a timeout here causes the
424
 
            # local branch to be out of date
425
 
            self.master_branch.import_last_revision_info(
426
 
                self.branch.repository, new_revno, self.rev_id)
427
 
 
428
 
        # and now do the commit locally.
429
 
        self.branch.set_last_revision_info(new_revno, self.rev_id)
430
 
 
431
 
        # Make the working tree be up to date with the branch. This
432
 
        # includes automatic changes scheduled to be made to the tree, such
433
 
        # as updating its basis and unversioning paths that were missing.
434
 
        self.work_tree.unversion(self.deleted_ids)
435
 
        self._set_progress_stage("Updating the working tree")
436
 
        self.work_tree.update_basis_by_delta(self.rev_id,
437
 
             self.builder.get_basis_delta())
438
 
        self.reporter.completed(new_revno, self.rev_id)
439
 
        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()
440
403
        return self.rev_id
441
404
 
442
405
    def _select_reporter(self):
464
427
            return
465
428
        raise PointlessCommit()
466
429
 
467
 
    def _check_bound_branch(self, operation, possible_master_transports=None):
 
430
    def _check_bound_branch(self, possible_master_transports=None):
468
431
        """Check to see if the local branch is bound.
469
432
 
470
433
        If it is bound, then most of the commit will actually be
505
468
        # so grab the lock
506
469
        self.bound_branch = self.branch
507
470
        self.master_branch.lock_write()
508
 
        operation.add_cleanup(self.master_branch.unlock)
 
471
        self.master_locked = True
509
472
 
510
473
    def _check_out_of_date_tree(self):
511
474
        """Check that the working tree is up to date.
596
559
                     old_revno, old_revid, new_revno, self.rev_id,
597
560
                     tree_delta, future_tree)
598
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
 
599
598
    def _gather_parents(self):
600
599
        """Record the parents of a merge for merge detection."""
601
600
        # TODO: Make sure that this list doesn't contain duplicate
616
615
        """Update the commit builder with the data about what has changed.
617
616
        """
618
617
        exclude = self.exclude
619
 
        specific_files = self.specific_files
 
618
        specific_files = self.specific_files or []
620
619
        mutter("Selecting files for commit with filter %s", specific_files)
621
620
 
622
621
        self._check_strict()
623
622
        if self.use_record_iter_changes:
624
 
            iter_changes = self.work_tree.iter_changes(self.basis_tree,
625
 
                specific_files=specific_files)
 
623
            iter_changes = self.work_tree.iter_changes(self.basis_tree)
626
624
            iter_changes = self._filter_iter_changes(iter_changes)
627
625
            for file_id, path, fs_hash in self.builder.record_iter_changes(
628
626
                self.work_tree, self.basis_revid, iter_changes):
681
679
                            reporter.snapshot_change('modified', new_path)
682
680
            self._next_progress_entry()
683
681
        # Unversion IDs that were found to be deleted
684
 
        self.deleted_ids = deleted_ids
 
682
        self.work_tree.unversion(deleted_ids)
685
683
 
686
684
    def _record_unselected(self):
687
685
        # If specific files are selected, then all un-selected files must be
800
798
                # _update_builder_with_changes.
801
799
                continue
802
800
            content_summary = self.work_tree.path_content_summary(path)
803
 
            kind = content_summary[0]
804
801
            # Note that when a filter of specific files is given, we must only
805
802
            # skip/record deleted files matching that filter.
806
803
            if not specific_files or is_inside_any(specific_files, path):
807
 
                if kind == 'missing':
 
804
                if content_summary[0] == 'missing':
808
805
                    if not deleted_paths:
809
806
                        # path won't have been split yet.
810
807
                        path_segments = splitpath(path)
817
814
                    continue
818
815
            # TODO: have the builder do the nested commit just-in-time IF and
819
816
            # only if needed.
820
 
            if kind == 'tree-reference':
 
817
            if content_summary[0] == 'tree-reference':
821
818
                # enforce repository nested tree policy.
822
819
                if (not self.work_tree.supports_tree_reference() or
823
820
                    # repository does not support it either.
824
821
                    not self.branch.repository._format.supports_tree_reference):
825
 
                    kind = 'directory'
826
 
                    content_summary = (kind, None, None, None)
827
 
                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':
828
827
                    nested_revision_id = self._commit_nested_tree(
829
828
                        file_id, path)
830
 
                    content_summary = (kind, None, None, nested_revision_id)
 
829
                    content_summary = content_summary[:3] + (
 
830
                        nested_revision_id,)
831
831
                else:
832
 
                    nested_revision_id = self.work_tree.get_reference_revision(file_id)
833
 
                    content_summary = (kind, None, None, nested_revision_id)
 
832
                    content_summary = content_summary[:3] + (
 
833
                        self.work_tree.get_reference_revision(file_id),)
834
834
 
835
835
            # Record an entry for this item
836
836
            # Note: I don't particularly want to have the existing_ie
842
842
                content_summary)
843
843
 
844
844
        # Unversion IDs that were found to be deleted
845
 
        self.deleted_ids = deleted_ids
 
845
        self.work_tree.unversion(deleted_ids)
846
846
 
847
847
    def _commit_nested_tree(self, file_id, path):
848
848
        "Commit a nested tree."