/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 breezy/commit.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
# The newly committed revision is going to have a shape corresponding
20
18
# to that of the working tree.  Files that are not in the
21
19
# working tree and that were in the predecessor are reported as
57
55
    ui,
58
56
    )
59
57
from .branch import Branch
60
 
from .cleanup import OperationWithCleanups
 
58
from contextlib import ExitStack
61
59
import breezy.config
62
60
from .errors import (BzrError,
63
61
                     ConflictsInTree,
64
62
                     StrictCommitFailed
65
63
                     )
66
64
from .osutils import (get_user_encoding,
 
65
                      has_symlinks,
67
66
                      is_inside_any,
68
67
                      minimum_path_selection,
69
68
                      )
95
94
    :return: iter_changes function
96
95
    """
97
96
    for change in iter_changes:
98
 
        old_path = change[1][0]
99
 
        new_path = change[1][1]
100
 
 
101
 
        new_excluded = (new_path is not None and
102
 
                        is_inside_any(exclude, new_path))
103
 
 
104
 
        old_excluded = (old_path is not None and
105
 
                        is_inside_any(exclude, old_path))
 
97
        new_excluded = (change.path[1] is not None and
 
98
                        is_inside_any(exclude, change.path[1]))
 
99
 
 
100
        old_excluded = (change.path[0] is not None and
 
101
                        is_inside_any(exclude, change.path[0]))
106
102
 
107
103
        if old_excluded and new_excluded:
108
104
            continue
159
155
            unescape_for_display(location, 'utf-8'))
160
156
 
161
157
    def completed(self, revno, rev_id):
162
 
        self._note(gettext('Committed revision %d.'), revno)
163
 
        # self._note goes to the console too; so while we want to log the
164
 
        # rev_id, we can't trivially only log it. (See bug 526425). Long
165
 
        # term we should rearrange the reporting structure, but for now
166
 
        # we just mutter seperately. We mutter the revid and revno together
167
 
        # so that concurrent bzr invocations won't lead to confusion.
168
 
        mutter('Committed revid %s as revno %d.', rev_id, revno)
 
158
        if revno is not None:
 
159
            self._note(gettext('Committed revision %d.'), revno)
 
160
            # self._note goes to the console too; so while we want to log the
 
161
            # rev_id, we can't trivially only log it. (See bug 526425). Long
 
162
            # term we should rearrange the reporting structure, but for now
 
163
            # we just mutter seperately. We mutter the revid and revno together
 
164
            # so that concurrent bzr invocations won't lead to confusion.
 
165
            mutter('Committed revid %s as revno %d.', rev_id, revno)
 
166
        else:
 
167
            self._note(gettext('Committed revid %s.'), rev_id)
169
168
 
170
169
    def deleted(self, path):
171
170
        self._note(gettext('deleted %s'), path)
283
282
        :param lossy: When committing to a foreign VCS, ignore any
284
283
            data that can not be natively represented.
285
284
        """
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(
291
 
            message=message,
292
 
            timestamp=timestamp,
293
 
            timezone=timezone,
294
 
            committer=committer,
295
 
            specific_files=specific_files,
296
 
            rev_id=rev_id,
297
 
            allow_pointless=allow_pointless,
298
 
            strict=strict,
299
 
            verbose=verbose,
300
 
            working_tree=working_tree,
301
 
            local=local,
302
 
            reporter=reporter,
303
 
            message_callback=message_callback,
304
 
            recursive=recursive,
305
 
            exclude=exclude,
306
 
            possible_master_transports=possible_master_transports,
307
 
            lossy=lossy)
308
 
 
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')
314
 
 
315
 
        if working_tree is None:
316
 
            raise BzrError("working_tree must be passed into commit().")
317
 
        else:
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())
327
 
 
328
 
                def message_callback(x):
329
 
                    return message
330
 
            else:
331
 
                raise BzrError("The message or message_callback keyword"
332
 
                               " parameter is required for commit().")
333
 
 
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))
339
 
        else:
340
 
            self.exclude = []
341
 
        self.local = local
342
 
        self.master_branch = None
343
 
        self.recursive = recursive
344
 
        self.rev_id = None
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))
350
 
        else:
351
 
            self.specific_files = None
352
 
 
353
 
        self.allow_pointless = allow_pointless
354
 
        self.message_callback = message_callback
355
 
        self.timestamp = timestamp
356
 
        self.timezone = timezone
357
 
        self.committer = committer
358
 
        self.strict = strict
359
 
        self.verbose = verbose
360
 
 
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
373
 
 
374
 
        # Setup the bound branch variables as needed.
375
 
        self._check_bound_branch(operation, possible_master_transports)
376
 
 
377
 
        # Check that the working tree is up to date
378
 
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
379
 
 
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()
387
 
 
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
404
 
 
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)
412
 
 
413
 
        # Collect the changes
414
 
        self._set_progress_stage("Collecting changes", counter=True)
415
 
        self._lossy = lossy
416
 
        self.builder = self.branch.get_commit_builder(
417
 
            self.parents, self.config_stack, timestamp, timezone, committer,
418
 
            self.revprops, rev_id, lossy=lossy)
419
 
 
420
 
        if self.builder.updates_branch and self.bound_branch:
421
 
            self.builder.abort()
422
 
            raise AssertionError(
423
 
                "bound branches not supported for commit builders "
424
 
                "that update the branch")
425
 
 
426
 
        try:
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')
 
290
 
 
291
            if working_tree is None:
 
292
                raise BzrError("working_tree must be passed into commit().")
 
293
            else:
 
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())
 
303
 
 
304
                    def message_callback(x):
 
305
                        return message
 
306
                else:
 
307
                    raise BzrError("The message or message_callback keyword"
 
308
                                   " parameter is required for commit().")
 
309
 
 
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))
 
315
            else:
 
316
                self.exclude = []
 
317
            self.local = local
 
318
            self.master_branch = None
 
319
            self.recursive = recursive
 
320
            self.rev_id = None
 
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))
 
326
            else:
 
327
                self.specific_files = None
 
328
 
 
329
            self.allow_pointless = allow_pointless
 
330
            self.message_callback = message_callback
 
331
            self.timestamp = timestamp
 
332
            self.timezone = timezone
 
333
            self.committer = committer
 
334
            self.strict = strict
 
335
            self.verbose = verbose
 
336
 
 
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
 
347
 
 
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()
 
352
 
 
353
            # Check that the working tree is up to date
 
354
            old_revno, old_revid, new_revno = self._check_out_of_date_tree()
 
355
 
 
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()
 
361
 
 
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
430
 
            else:
431
 
                master_location = self.branch.base
432
 
 
433
 
            # report the start of the commit
434
 
            self.reporter.started(new_revno, self.rev_id, master_location)
435
 
 
436
 
            self._update_builder_with_changes()
437
 
            self._check_pointless()
438
 
 
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()
445
 
 
446
 
            # Prompt the user for a commit message if none provided
447
 
            message = message_callback(self)
448
 
            self.message = message
449
 
 
450
 
            # Add revision data to the local branch
451
 
            self.rev_id = self.builder.commit(self.message)
452
 
 
453
 
        except Exception:
454
 
            mutter("aborting commit write group because of exception:")
455
 
            trace.log_exception_quietly()
456
 
            self.builder.abort()
457
 
            raise
458
 
 
459
 
        self._update_branches(old_revno, old_revid, new_revno)
460
 
 
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)
470
 
        return self.rev_id
 
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
 
378
 
 
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)
 
386
 
 
387
            # Collect the changes
 
388
            self._set_progress_stage("Collecting changes", counter=True)
 
389
            self._lossy = lossy
 
390
            self.builder = self.branch.get_commit_builder(
 
391
                self.parents, self.config_stack, timestamp, timezone, committer,
 
392
                self.revprops, rev_id, lossy=lossy)
 
393
 
 
394
            if self.builder.updates_branch and self.bound_branch:
 
395
                self.builder.abort()
 
396
                raise AssertionError(
 
397
                    "bound branches not supported for commit builders "
 
398
                    "that update the branch")
 
399
 
 
400
            try:
 
401
                # find the location being committed to
 
402
                if self.bound_branch:
 
403
                    master_location = self.master_branch.base
 
404
                else:
 
405
                    master_location = self.branch.base
 
406
 
 
407
                # report the start of the commit
 
408
                self.reporter.started(new_revno, self.rev_id, master_location)
 
409
 
 
410
                self._update_builder_with_changes()
 
411
                self._check_pointless()
 
412
 
 
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()
 
419
 
 
420
                # Prompt the user for a commit message if none provided
 
421
                message = message_callback(self)
 
422
                self.message = message
 
423
 
 
424
                # Add revision data to the local branch
 
425
                self.rev_id = self.builder.commit(self.message)
 
426
 
 
427
            except Exception:
 
428
                mutter("aborting commit write group because of exception:")
 
429
                trace.log_exception_quietly()
 
430
                self.builder.abort()
 
431
                raise
 
432
 
 
433
            self._update_branches(old_revno, old_revid, new_revno)
 
434
 
 
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)
 
444
            return self.rev_id
471
445
 
472
446
    def _update_branches(self, old_revno, old_revid, new_revno):
473
447
        """Update the master and local branch to the new revision.
495
469
                    self.branch.fetch(self.master_branch, self.rev_id)
496
470
 
497
471
            # and now do the commit locally.
 
472
            if new_revno is None:
 
473
                # Keep existing behaviour around ghosts
 
474
                new_revno = 1
498
475
            self.branch.set_last_revision_info(new_revno, self.rev_id)
499
476
        else:
500
477
            try:
531
508
            return
532
509
        raise PointlessCommit()
533
510
 
534
 
    def _check_bound_branch(self, operation, possible_master_transports=None):
 
511
    def _check_bound_branch(self, stack, possible_master_transports=None):
535
512
        """Check to see if the local branch is bound.
536
513
 
537
514
        If it is bound, then most of the commit will actually be
571
548
        # Now things are ready to change the master branch
572
549
        # so grab the lock
573
550
        self.bound_branch = self.branch
574
 
        self.master_branch.lock_write()
575
 
        operation.add_cleanup(self.master_branch.unlock)
 
551
        stack.enter_context(self.master_branch.lock_write())
576
552
 
577
553
    def _check_out_of_date_tree(self):
578
554
        """Check that the working tree is up to date.
588
564
            # - in a checkout scenario the tree may have no
589
565
            # parents but the branch may do.
590
566
            first_tree_parent = breezy.revision.NULL_REVISION
591
 
        try:
592
 
            old_revno, master_last = self.master_branch.last_revision_info()
593
 
        except errors.UnsupportedOperation:
 
567
        if (self.master_branch._format.stores_revno() or
 
568
                self.config_stack.get('calculate_revnos')):
 
569
            try:
 
570
                old_revno, master_last = self.master_branch.last_revision_info()
 
571
            except errors.UnsupportedOperation:
 
572
                master_last = self.master_branch.last_revision()
 
573
                old_revno = self.branch.revision_id_to_revno(master_last)
 
574
        else:
594
575
            master_last = self.master_branch.last_revision()
595
 
            old_revno = self.branch.revision_id_to_revno(master_last)
 
576
            old_revno = None
596
577
        if master_last != first_tree_parent:
597
578
            if master_last != breezy.revision.NULL_REVISION:
598
579
                raise errors.OutOfDateTree(self.work_tree)
599
 
        if self.branch.repository.has_revision(first_tree_parent):
 
580
        if (old_revno is not None and
 
581
                self.branch.repository.has_revision(first_tree_parent)):
600
582
            new_revno = old_revno + 1
601
583
        else:
602
584
            # ghost parents never appear in revision history.
603
 
            new_revno = 1
 
585
            new_revno = None
604
586
        return old_revno, master_last, new_revno
605
587
 
606
588
    def _process_pre_hooks(self, old_revno, new_revno):
681
663
        if self.exclude:
682
664
            iter_changes = filter_excluded(iter_changes, self.exclude)
683
665
        iter_changes = self._filter_iter_changes(iter_changes)
684
 
        for file_id, path, fs_hash in self.builder.record_iter_changes(
 
666
        for path, fs_hash in self.builder.record_iter_changes(
685
667
                self.work_tree, self.basis_revid, iter_changes):
686
668
            self.work_tree._observed_sha1(path, fs_hash)
687
669
 
700
682
        deleted_paths = []
701
683
        for change in iter_changes:
702
684
            if report_changes:
703
 
                old_path = change[1][0]
704
 
                new_path = change[1][1]
705
 
                versioned = change[3][1]
706
 
            kind = change[6][1]
707
 
            versioned = change[3][1]
 
685
                old_path = change.path[0]
 
686
                new_path = change.path[1]
 
687
                versioned = change.versioned[1]
 
688
            kind = change.kind[1]
 
689
            versioned = change.versioned[1]
708
690
            if kind is None and versioned:
709
691
                # 'missing' path
710
692
                if report_changes:
711
693
                    reporter.missing(new_path)
712
 
                deleted_paths.append(change[1][1])
 
694
                if change.kind[0] == 'symlink' and not self.work_tree.supports_symlinks():
 
695
                    trace.warning('Ignoring "%s" as symlinks are not '
 
696
                                  'supported on this filesystem.' % (change.path[0],))
 
697
                    continue
 
698
                deleted_paths.append(change.path[1])
713
699
                # Reset the new path (None) and new versioned flag (False)
714
 
                change = (change[0], (change[1][0], None), change[2],
715
 
                          (change[3][0], False)) + change[4:]
716
 
                new_path = change[1][1]
 
700
                change = change.discard_new()
 
701
                new_path = change.path[1]
717
702
                versioned = False
718
703
            elif kind == 'tree-reference':
719
704
                if self.recursive == 'down':
720
 
                    self._commit_nested_tree(change[1][1])
721
 
            if change[3][0] or change[3][1]:
 
705
                    self._commit_nested_tree(change.path[1])
 
706
            if change.versioned[0] or change.versioned[1]:
722
707
                yield change
723
708
                if report_changes:
724
709
                    if new_path is None: