/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: Jelmer Vernooij
  • Date: 2020-03-22 01:35:14 UTC
  • mfrom: (7490.7.6 work)
  • mto: This revision was merged to the branch mainline in revision 7499.
  • Revision ID: jelmer@jelmer.uk-20200322013514-7vw1ntwho04rcuj3
merge lp:brz/3.1.

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
54
52
    debug,
55
53
    errors,
56
54
    trace,
57
 
    tree,
58
55
    ui,
59
56
    )
60
57
from .branch import Branch
61
 
from .cleanup import OperationWithCleanups
 
58
from contextlib import ExitStack
62
59
import breezy.config
63
60
from .errors import (BzrError,
64
61
                     ConflictsInTree,
65
62
                     StrictCommitFailed
66
63
                     )
67
64
from .osutils import (get_user_encoding,
 
65
                      has_symlinks,
68
66
                      is_inside_any,
69
67
                      minimum_path_selection,
70
 
                      splitpath,
71
68
                      )
72
69
from .trace import mutter, note, is_quiet
73
 
from .bzr.inventory import Inventory, InventoryEntry, make_entry
 
70
from .tree import TreeChange
74
71
from .urlutils import unescape_for_display
75
72
from .i18n import gettext
76
73
 
98
95
    :return: iter_changes function
99
96
    """
100
97
    for change in iter_changes:
101
 
        old_path = change[1][0]
102
 
        new_path = change[1][1]
103
 
 
104
 
        new_excluded = (new_path is not None and
105
 
            is_inside_any(exclude, new_path))
106
 
 
107
 
        old_excluded = (old_path is not None and
108
 
            is_inside_any(exclude, old_path))
 
98
        new_excluded = (change.path[1] is not None and
 
99
                        is_inside_any(exclude, change.path[1]))
 
100
 
 
101
        old_excluded = (change.path[0] is not None and
 
102
                        is_inside_any(exclude, change.path[0]))
109
103
 
110
104
        if old_excluded and new_excluded:
111
105
            continue
162
156
            unescape_for_display(location, 'utf-8'))
163
157
 
164
158
    def completed(self, revno, rev_id):
165
 
        self._note(gettext('Committed revision %d.'), revno)
166
 
        # self._note goes to the console too; so while we want to log the
167
 
        # rev_id, we can't trivially only log it. (See bug 526425). Long
168
 
        # term we should rearrange the reporting structure, but for now
169
 
        # we just mutter seperately. We mutter the revid and revno together
170
 
        # so that concurrent bzr invocations won't lead to confusion.
171
 
        mutter('Committed revid %s as revno %d.', rev_id, revno)
 
159
        if revno is not None:
 
160
            self._note(gettext('Committed revision %d.'), revno)
 
161
            # self._note goes to the console too; so while we want to log the
 
162
            # rev_id, we can't trivially only log it. (See bug 526425). Long
 
163
            # term we should rearrange the reporting structure, but for now
 
164
            # we just mutter seperately. We mutter the revid and revno together
 
165
            # so that concurrent bzr invocations won't lead to confusion.
 
166
            mutter('Committed revid %s as revno %d.', rev_id, revno)
 
167
        else:
 
168
            self._note(gettext('Committed revid %s.'), rev_id)
172
169
 
173
170
    def deleted(self, path):
174
171
        self._note(gettext('deleted %s'), path)
195
192
            the working directory; these should be removed from the
196
193
            working inventory.
197
194
    """
 
195
 
198
196
    def __init__(self,
199
197
                 reporter=None,
200
198
                 config_stack=None):
212
210
            revprops = {}
213
211
        if possible_master_transports is None:
214
212
            possible_master_transports = []
215
 
        if (not 'branch-nick' in revprops and
 
213
        if (u'branch-nick' not in revprops and
216
214
                branch.repository._format.supports_storing_branch_nick):
217
 
            revprops['branch-nick'] = branch._get_nick(
 
215
            revprops[u'branch-nick'] = branch._get_nick(
218
216
                local,
219
217
                possible_master_transports)
220
218
        if authors is not None:
221
 
            if 'author' in revprops or 'authors' in revprops:
 
219
            if u'author' in revprops or u'authors' in revprops:
222
220
                # XXX: maybe we should just accept one of them?
223
221
                raise AssertionError('author property given twice')
224
222
            if authors:
225
223
                for individual in authors:
226
224
                    if '\n' in individual:
227
225
                        raise AssertionError('\\n is not a valid character '
228
 
                                'in an author identity')
229
 
                revprops['authors'] = '\n'.join(authors)
 
226
                                             'in an author identity')
 
227
                revprops[u'authors'] = '\n'.join(authors)
230
228
        return revprops
231
229
 
232
230
    def commit(self,
252
250
        """Commit working copy as a new revision.
253
251
 
254
252
        :param message: the commit message (it or message_callback is required)
255
 
        :param message_callback: A callback: message = message_callback(cmt_obj)
 
253
        :param message_callback: A callback: message =
 
254
            message_callback(cmt_obj)
256
255
 
257
256
        :param timestamp: if not None, seconds-since-epoch for a
258
257
            postdated/predated commit.
284
283
        :param lossy: When committing to a foreign VCS, ignore any
285
284
            data that can not be natively represented.
286
285
        """
287
 
        operation = OperationWithCleanups(self._commit)
288
 
        self.revprops = revprops or {}
289
 
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
290
 
        self.config_stack = config or self.config_stack
291
 
        return operation.run(
292
 
               message=message,
293
 
               timestamp=timestamp,
294
 
               timezone=timezone,
295
 
               committer=committer,
296
 
               specific_files=specific_files,
297
 
               rev_id=rev_id,
298
 
               allow_pointless=allow_pointless,
299
 
               strict=strict,
300
 
               verbose=verbose,
301
 
               working_tree=working_tree,
302
 
               local=local,
303
 
               reporter=reporter,
304
 
               message_callback=message_callback,
305
 
               recursive=recursive,
306
 
               exclude=exclude,
307
 
               possible_master_transports=possible_master_transports,
308
 
               lossy=lossy)
309
 
 
310
 
    def _commit(self, operation, message, timestamp, timezone, committer,
311
 
            specific_files, rev_id, allow_pointless, strict, verbose,
312
 
            working_tree, local, reporter, message_callback, recursive,
313
 
            exclude, possible_master_transports, lossy):
314
 
        mutter('preparing to commit')
315
 
 
316
 
        if working_tree is None:
317
 
            raise BzrError("working_tree must be passed into commit().")
318
 
        else:
319
 
            self.work_tree = working_tree
320
 
            self.branch = self.work_tree.branch
321
 
            if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
322
 
                if not self.branch.repository.supports_rich_root():
323
 
                    raise errors.RootNotRich()
324
 
        if message_callback is None:
325
 
            if message is not None:
326
 
                if isinstance(message, bytes):
327
 
                    message = message.decode(get_user_encoding())
328
 
                message_callback = lambda x: message
329
 
            else:
330
 
                raise BzrError("The message or message_callback keyword"
331
 
                               " parameter is required for commit().")
332
 
 
333
 
        self.bound_branch = None
334
 
        self.any_entries_deleted = False
335
 
        if exclude is not None:
336
 
            self.exclude = sorted(
337
 
                minimum_path_selection(exclude))
338
 
        else:
339
 
            self.exclude = []
340
 
        self.local = local
341
 
        self.master_branch = None
342
 
        self.recursive = recursive
343
 
        self.rev_id = None
344
 
        # self.specific_files is None to indicate no filter, or any iterable to
345
 
        # indicate a filter - [] means no files at all, as per iter_changes.
346
 
        if specific_files is not None:
347
 
            self.specific_files = sorted(
348
 
                minimum_path_selection(specific_files))
349
 
        else:
350
 
            self.specific_files = None
351
 
 
352
 
        self.allow_pointless = allow_pointless
353
 
        self.message_callback = message_callback
354
 
        self.timestamp = timestamp
355
 
        self.timezone = timezone
356
 
        self.committer = committer
357
 
        self.strict = strict
358
 
        self.verbose = verbose
359
 
 
360
 
        self.work_tree.lock_write()
361
 
        operation.add_cleanup(self.work_tree.unlock)
362
 
        self.parents = self.work_tree.get_parent_ids()
363
 
        self.pb = ui.ui_factory.nested_progress_bar()
364
 
        operation.add_cleanup(self.pb.finished)
365
 
        self.basis_revid = self.work_tree.last_revision()
366
 
        self.basis_tree = self.work_tree.basis_tree()
367
 
        self.basis_tree.lock_read()
368
 
        operation.add_cleanup(self.basis_tree.unlock)
369
 
        # Cannot commit with conflicts present.
370
 
        if len(self.work_tree.conflicts()) > 0:
371
 
            raise ConflictsInTree
372
 
 
373
 
        # Setup the bound branch variables as needed.
374
 
        self._check_bound_branch(operation, possible_master_transports)
375
 
 
376
 
        # Check that the working tree is up to date
377
 
        old_revno, old_revid, new_revno = self._check_out_of_date_tree()
378
 
 
379
 
        # Complete configuration setup
380
 
        if reporter is not None:
381
 
            self.reporter = reporter
382
 
        elif self.reporter is None:
383
 
            self.reporter = self._select_reporter()
384
 
        if self.config_stack is None:
385
 
            self.config_stack = self.work_tree.get_config_stack()
386
 
 
387
 
        # Setup the progress bar. As the number of files that need to be
388
 
        # committed in unknown, progress is reported as stages.
389
 
        # We keep track of entries separately though and include that
390
 
        # information in the progress bar during the relevant stages.
391
 
        self.pb_stage_name = ""
392
 
        self.pb_stage_count = 0
393
 
        self.pb_stage_total = 5
394
 
        if self.bound_branch:
395
 
            # 2 extra stages: "Uploading data to master branch" and "Merging
396
 
            # tags to master branch"
397
 
            self.pb_stage_total += 2
398
 
        self.pb.show_pct = False
399
 
        self.pb.show_spinner = False
400
 
        self.pb.show_eta = False
401
 
        self.pb.show_count = True
402
 
        self.pb.show_bar = True
403
 
 
404
 
        # After a merge, a selected file commit is not supported.
405
 
        # See 'bzr help merge' for an explanation as to why.
406
 
        if len(self.parents) > 1 and self.specific_files is not None:
407
 
            raise CannotCommitSelectedFileMerge(self.specific_files)
408
 
        # Excludes are a form of selected file commit.
409
 
        if len(self.parents) > 1 and self.exclude:
410
 
            raise CannotCommitSelectedFileMerge(self.exclude)
411
 
 
412
 
        # Collect the changes
413
 
        self._set_progress_stage("Collecting changes", counter=True)
414
 
        self._lossy = lossy
415
 
        self.builder = self.branch.get_commit_builder(self.parents,
416
 
            self.config_stack, timestamp, timezone, committer, self.revprops,
417
 
            rev_id, lossy=lossy)
418
 
 
419
 
        if self.builder.updates_branch and self.bound_branch:
420
 
            self.builder.abort()
421
 
            raise AssertionError(
422
 
                "bound branches not supported for commit builders "
423
 
                "that update the branch")
424
 
 
425
 
        try:
426
 
            # find the location being committed to
 
286
        with ExitStack() as stack:
 
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
            mutter('preparing to commit')
 
291
 
 
292
            if working_tree is None:
 
293
                raise BzrError("working_tree must be passed into commit().")
 
294
            else:
 
295
                self.work_tree = working_tree
 
296
                self.branch = self.work_tree.branch
 
297
                if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
 
298
                    if not self.branch.repository.supports_rich_root():
 
299
                        raise errors.RootNotRich()
 
300
            if message_callback is None:
 
301
                if message is not None:
 
302
                    if isinstance(message, bytes):
 
303
                        message = message.decode(get_user_encoding())
 
304
 
 
305
                    def message_callback(x):
 
306
                        return message
 
307
                else:
 
308
                    raise BzrError("The message or message_callback keyword"
 
309
                                   " parameter is required for commit().")
 
310
 
 
311
            self.bound_branch = None
 
312
            self.any_entries_deleted = False
 
313
            if exclude is not None:
 
314
                self.exclude = sorted(
 
315
                    minimum_path_selection(exclude))
 
316
            else:
 
317
                self.exclude = []
 
318
            self.local = local
 
319
            self.master_branch = None
 
320
            self.recursive = recursive
 
321
            self.rev_id = None
 
322
            # self.specific_files is None to indicate no filter, or any iterable to
 
323
            # indicate a filter - [] means no files at all, as per iter_changes.
 
324
            if specific_files is not None:
 
325
                self.specific_files = sorted(
 
326
                    minimum_path_selection(specific_files))
 
327
            else:
 
328
                self.specific_files = None
 
329
 
 
330
            self.allow_pointless = allow_pointless
 
331
            self.message_callback = message_callback
 
332
            self.timestamp = timestamp
 
333
            self.timezone = timezone
 
334
            self.committer = committer
 
335
            self.strict = strict
 
336
            self.verbose = verbose
 
337
 
 
338
            stack.enter_context(self.work_tree.lock_write())
 
339
            self.parents = self.work_tree.get_parent_ids()
 
340
            self.pb = ui.ui_factory.nested_progress_bar()
 
341
            stack.callback(self.pb.finished)
 
342
            self.basis_revid = self.work_tree.last_revision()
 
343
            self.basis_tree = self.work_tree.basis_tree()
 
344
            stack.enter_context(self.basis_tree.lock_read())
 
345
            # Cannot commit with conflicts present.
 
346
            if len(self.work_tree.conflicts()) > 0:
 
347
                raise ConflictsInTree
 
348
 
 
349
            # Setup the bound branch variables as needed.
 
350
            self._check_bound_branch(stack, possible_master_transports)
 
351
            if self.config_stack is None:
 
352
                self.config_stack = self.work_tree.get_config_stack()
 
353
 
 
354
            # Check that the working tree is up to date
 
355
            old_revno, old_revid, new_revno = self._check_out_of_date_tree()
 
356
 
 
357
            # Complete configuration setup
 
358
            if reporter is not None:
 
359
                self.reporter = reporter
 
360
            elif self.reporter is None:
 
361
                self.reporter = self._select_reporter()
 
362
 
 
363
            # Setup the progress bar. As the number of files that need to be
 
364
            # committed in unknown, progress is reported as stages.
 
365
            # We keep track of entries separately though and include that
 
366
            # information in the progress bar during the relevant stages.
 
367
            self.pb_stage_name = ""
 
368
            self.pb_stage_count = 0
 
369
            self.pb_stage_total = 5
427
370
            if self.bound_branch:
428
 
                master_location = self.master_branch.base
429
 
            else:
430
 
                master_location = self.branch.base
431
 
 
432
 
            # report the start of the commit
433
 
            self.reporter.started(new_revno, self.rev_id, master_location)
434
 
 
435
 
            self._update_builder_with_changes()
436
 
            self._check_pointless()
437
 
 
438
 
            # TODO: Now the new inventory is known, check for conflicts.
439
 
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
440
 
            # weave lines, because nothing should be recorded until it is known
441
 
            # that commit will succeed.
442
 
            self._set_progress_stage("Saving data locally")
443
 
            self.builder.finish_inventory()
444
 
 
445
 
            # Prompt the user for a commit message if none provided
446
 
            message = message_callback(self)
447
 
            self.message = message
448
 
 
449
 
            # Add revision data to the local branch
450
 
            self.rev_id = self.builder.commit(self.message)
451
 
 
452
 
        except Exception as e:
453
 
            mutter("aborting commit write group because of exception:")
454
 
            trace.log_exception_quietly()
455
 
            self.builder.abort()
456
 
            raise
457
 
 
458
 
        self._update_branches(old_revno, old_revid, new_revno)
459
 
 
460
 
        # Make the working tree be up to date with the branch. This
461
 
        # includes automatic changes scheduled to be made to the tree, such
462
 
        # as updating its basis and unversioning paths that were missing.
463
 
        self.work_tree.unversion(
464
 
                {self.work_tree.id2path(file_id) for file_id in self.deleted_ids},
465
 
                self.deleted_ids)
466
 
        self._set_progress_stage("Updating the working tree")
467
 
        self.work_tree.update_basis_by_delta(self.rev_id,
468
 
             self.builder.get_basis_delta())
469
 
        self.reporter.completed(new_revno, self.rev_id)
470
 
        self._process_post_hooks(old_revno, new_revno)
471
 
        return self.rev_id
 
371
                # 2 extra stages: "Uploading data to master branch" and "Merging
 
372
                # tags to master branch"
 
373
                self.pb_stage_total += 2
 
374
            self.pb.show_pct = False
 
375
            self.pb.show_spinner = False
 
376
            self.pb.show_eta = False
 
377
            self.pb.show_count = True
 
378
            self.pb.show_bar = True
 
379
 
 
380
            # After a merge, a selected file commit is not supported.
 
381
            # See 'bzr help merge' for an explanation as to why.
 
382
            if len(self.parents) > 1 and self.specific_files is not None:
 
383
                raise CannotCommitSelectedFileMerge(self.specific_files)
 
384
            # Excludes are a form of selected file commit.
 
385
            if len(self.parents) > 1 and self.exclude:
 
386
                raise CannotCommitSelectedFileMerge(self.exclude)
 
387
 
 
388
            # Collect the changes
 
389
            self._set_progress_stage("Collecting changes", counter=True)
 
390
            self._lossy = lossy
 
391
            self.builder = self.branch.get_commit_builder(
 
392
                self.parents, self.config_stack, timestamp, timezone, committer,
 
393
                self.revprops, rev_id, lossy=lossy)
 
394
 
 
395
            if self.builder.updates_branch and self.bound_branch:
 
396
                self.builder.abort()
 
397
                raise AssertionError(
 
398
                    "bound branches not supported for commit builders "
 
399
                    "that update the branch")
 
400
 
 
401
            try:
 
402
                # find the location being committed to
 
403
                if self.bound_branch:
 
404
                    master_location = self.master_branch.base
 
405
                else:
 
406
                    master_location = self.branch.base
 
407
 
 
408
                # report the start of the commit
 
409
                self.reporter.started(new_revno, self.rev_id, master_location)
 
410
 
 
411
                self._update_builder_with_changes()
 
412
                self._check_pointless()
 
413
 
 
414
                # TODO: Now the new inventory is known, check for conflicts.
 
415
                # ADHB 2006-08-08: If this is done, populate_new_inv should not add
 
416
                # weave lines, because nothing should be recorded until it is known
 
417
                # that commit will succeed.
 
418
                self._set_progress_stage("Saving data locally")
 
419
                self.builder.finish_inventory()
 
420
 
 
421
                # Prompt the user for a commit message if none provided
 
422
                message = message_callback(self)
 
423
                self.message = message
 
424
 
 
425
                # Add revision data to the local branch
 
426
                self.rev_id = self.builder.commit(self.message)
 
427
 
 
428
            except Exception:
 
429
                mutter("aborting commit write group because of exception:")
 
430
                trace.log_exception_quietly()
 
431
                self.builder.abort()
 
432
                raise
 
433
 
 
434
            self._update_branches(old_revno, old_revid, new_revno)
 
435
 
 
436
            # Make the working tree be up to date with the branch. This
 
437
            # includes automatic changes scheduled to be made to the tree, such
 
438
            # as updating its basis and unversioning paths that were missing.
 
439
            self.work_tree.unversion(self.deleted_paths)
 
440
            self._set_progress_stage("Updating the working tree")
 
441
            self.work_tree.update_basis_by_delta(self.rev_id,
 
442
                                                 self.builder.get_basis_delta())
 
443
            self.reporter.completed(new_revno, self.rev_id)
 
444
            self._process_post_hooks(old_revno, new_revno)
 
445
            return self.rev_id
472
446
 
473
447
    def _update_branches(self, old_revno, old_revid, new_revno):
474
448
        """Update the master and local branch to the new revision.
496
470
                    self.branch.fetch(self.master_branch, self.rev_id)
497
471
 
498
472
            # and now do the commit locally.
 
473
            if new_revno is None:
 
474
                # Keep existing behaviour around ghosts
 
475
                new_revno = 1
499
476
            self.branch.set_last_revision_info(new_revno, self.rev_id)
500
477
        else:
501
478
            try:
502
479
                self._process_pre_hooks(old_revno, new_revno)
503
 
            except:
 
480
            except BaseException:
504
481
                # The commit builder will already have updated the branch,
505
482
                # revert it.
506
483
                self.branch.set_last_revision_info(old_revno, old_revid)
513
490
                self.master_branch.tags)
514
491
            if tag_conflicts:
515
492
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
516
 
                note( gettext("Conflicting tags in bound branch:\n{0}".format(
517
 
                    "\n".join(warning_lines))) )
 
493
                note(gettext("Conflicting tags in bound branch:\n{0}".format(
 
494
                    "\n".join(warning_lines))))
518
495
 
519
496
    def _select_reporter(self):
520
497
        """Select the CommitReporter to use."""
532
509
            return
533
510
        raise PointlessCommit()
534
511
 
535
 
    def _check_bound_branch(self, operation, possible_master_transports=None):
 
512
    def _check_bound_branch(self, stack, possible_master_transports=None):
536
513
        """Check to see if the local branch is bound.
537
514
 
538
515
        If it is bound, then most of the commit will actually be
554
531
        # If the master branch is bound, we must fail
555
532
        master_bound_location = self.master_branch.get_bound_location()
556
533
        if master_bound_location:
557
 
            raise errors.CommitToDoubleBoundBranch(self.branch,
558
 
                    self.master_branch, master_bound_location)
 
534
            raise errors.CommitToDoubleBoundBranch(
 
535
                self.branch, self.master_branch, master_bound_location)
559
536
 
560
537
        # TODO: jam 20051230 We could automatically push local
561
538
        #       commits to the remote branch if they would fit.
563
540
        #       to local.
564
541
 
565
542
        # Make sure the local branch is identical to the master
566
 
        master_info = self.master_branch.last_revision_info()
567
 
        local_info = self.branch.last_revision_info()
568
 
        if local_info != master_info:
 
543
        master_revid = self.master_branch.last_revision()
 
544
        local_revid = self.branch.last_revision()
 
545
        if local_revid != master_revid:
569
546
            raise errors.BoundBranchOutOfDate(self.branch,
570
 
                    self.master_branch)
 
547
                                              self.master_branch)
571
548
 
572
549
        # Now things are ready to change the master branch
573
550
        # so grab the lock
574
551
        self.bound_branch = self.branch
575
 
        self.master_branch.lock_write()
576
 
        operation.add_cleanup(self.master_branch.unlock)
 
552
        stack.enter_context(self.master_branch.lock_write())
577
553
 
578
554
    def _check_out_of_date_tree(self):
579
555
        """Check that the working tree is up to date.
589
565
            # - in a checkout scenario the tree may have no
590
566
            # parents but the branch may do.
591
567
            first_tree_parent = breezy.revision.NULL_REVISION
592
 
        old_revno, master_last = self.master_branch.last_revision_info()
 
568
        if (self.master_branch._format.stores_revno() or
 
569
                self.config_stack.get('calculate_revnos')):
 
570
            try:
 
571
                old_revno, master_last = self.master_branch.last_revision_info()
 
572
            except errors.UnsupportedOperation:
 
573
                master_last = self.master_branch.last_revision()
 
574
                old_revno = self.branch.revision_id_to_revno(master_last)
 
575
        else:
 
576
            master_last = self.master_branch.last_revision()
 
577
            old_revno = None
593
578
        if master_last != first_tree_parent:
594
579
            if master_last != breezy.revision.NULL_REVISION:
595
580
                raise errors.OutOfDateTree(self.work_tree)
596
 
        if self.branch.repository.has_revision(first_tree_parent):
 
581
        if (old_revno is not None and
 
582
                self.branch.repository.has_revision(first_tree_parent)):
597
583
            new_revno = old_revno + 1
598
584
        else:
599
585
            # ghost parents never appear in revision history.
600
 
            new_revno = 1
 
586
            new_revno = None
601
587
        return old_revno, master_last, new_revno
602
588
 
603
589
    def _process_pre_hooks(self, old_revno, new_revno):
617
603
            # this would be nicer with twisted.python.reflect.namedAny
618
604
            for hook in hooks:
619
605
                result = eval(hook + '(branch, rev_id)',
620
 
                              {'branch':self.branch,
621
 
                               'breezy':breezy,
622
 
                               'rev_id':self.rev_id})
 
606
                              {'branch': self.branch,
 
607
                               'breezy': breezy,
 
608
                               'rev_id': self.rev_id})
623
609
        # process new style post commit hooks
624
610
        self._process_hooks("post_commit", old_revno, new_revno)
625
611
 
645
631
        if hook_name == "pre_commit":
646
632
            future_tree = self.builder.revision_tree()
647
633
            tree_delta = future_tree.changes_from(self.basis_tree,
648
 
                                             include_root=True)
 
634
                                                  include_root=True)
649
635
 
650
636
        for hook in Branch.hooks[hook_name]:
651
637
            # show the running hook in the progress bar. As hooks may
673
659
        mutter("Selecting files for commit with filter %r", specific_files)
674
660
 
675
661
        self._check_strict()
676
 
        iter_changes = self.work_tree.iter_changes(self.basis_tree,
677
 
            specific_files=specific_files)
 
662
        iter_changes = self.work_tree.iter_changes(
 
663
            self.basis_tree, specific_files=specific_files)
678
664
        if self.exclude:
679
665
            iter_changes = filter_excluded(iter_changes, self.exclude)
680
666
        iter_changes = self._filter_iter_changes(iter_changes)
681
 
        for file_id, path, fs_hash in self.builder.record_iter_changes(
682
 
            self.work_tree, self.basis_revid, iter_changes):
683
 
            self.work_tree._observed_sha1(file_id, path, fs_hash)
 
667
        for path, fs_hash in self.builder.record_iter_changes(
 
668
                self.work_tree, self.basis_revid, iter_changes):
 
669
            self.work_tree._observed_sha1(path, fs_hash)
684
670
 
685
671
    def _filter_iter_changes(self, iter_changes):
686
672
        """Process iter_changes.
687
673
 
688
 
        This method reports on the changes in iter_changes to the user, and 
 
674
        This method reports on the changes in iter_changes to the user, and
689
675
        converts 'missing' entries in the iter_changes iterator to 'deleted'
690
676
        entries. 'missing' entries have their
691
677
 
694
680
        """
695
681
        reporter = self.reporter
696
682
        report_changes = reporter.is_verbose()
697
 
        deleted_ids = []
 
683
        deleted_paths = []
698
684
        for change in iter_changes:
699
685
            if report_changes:
700
 
                old_path = change[1][0]
701
 
                new_path = change[1][1]
702
 
                versioned = change[3][1]
703
 
            kind = change[6][1]
704
 
            versioned = change[3][1]
 
686
                old_path = change.path[0]
 
687
                new_path = change.path[1]
 
688
                versioned = change.versioned[1]
 
689
            kind = change.kind[1]
 
690
            versioned = change.versioned[1]
705
691
            if kind is None and versioned:
706
692
                # 'missing' path
707
693
                if report_changes:
708
694
                    reporter.missing(new_path)
709
 
                deleted_ids.append(change[0])
 
695
                if change.kind[0] == 'symlink' and not self.work_tree.supports_symlinks():
 
696
                    trace.warning('Ignoring "%s" as symlinks are not '
 
697
                                  'supported on this filesystem.' % (change.path[0],))
 
698
                    continue
 
699
                deleted_paths.append(change.path[1])
710
700
                # Reset the new path (None) and new versioned flag (False)
711
 
                change = (change[0], (change[1][0], None), change[2],
712
 
                    (change[3][0], False)) + change[4:]
713
 
                new_path = change[1][1]
 
701
                change = change.discard_new()
 
702
                new_path = change.path[1]
714
703
                versioned = False
715
704
            elif kind == 'tree-reference':
716
705
                if self.recursive == 'down':
717
 
                    self._commit_nested_tree(change[0], change[1][1])
718
 
            if change[3][0] or change[3][1]:
 
706
                    self._commit_nested_tree(change.path[1])
 
707
            if change.versioned[0] or change.versioned[1]:
719
708
                yield change
720
709
                if report_changes:
721
710
                    if new_path is None:
723
712
                    elif old_path is None:
724
713
                        reporter.snapshot_change(gettext('added'), new_path)
725
714
                    elif old_path != new_path:
726
 
                        reporter.renamed(gettext('renamed'), old_path, new_path)
 
715
                        reporter.renamed(gettext('renamed'),
 
716
                                         old_path, new_path)
727
717
                    else:
728
 
                        if (new_path or 
729
 
                            self.work_tree.branch.repository._format.rich_root_data):
 
718
                        if (new_path
 
719
                                or self.work_tree.branch.repository._format.rich_root_data):
730
720
                            # Don't report on changes to '' in non rich root
731
721
                            # repositories.
732
 
                            reporter.snapshot_change(gettext('modified'), new_path)
 
722
                            reporter.snapshot_change(
 
723
                                gettext('modified'), new_path)
733
724
            self._next_progress_entry()
734
 
        # Unversion IDs that were found to be deleted
735
 
        self.deleted_ids = deleted_ids
 
725
        # Unversion files that were found to be deleted
 
726
        self.deleted_paths = deleted_paths
736
727
 
737
728
    def _check_strict(self):
738
729
        # XXX: when we use iter_changes this would likely be faster if
743
734
            for unknown in self.work_tree.unknowns():
744
735
                raise StrictCommitFailed()
745
736
 
746
 
    def _commit_nested_tree(self, file_id, path):
 
737
    def _commit_nested_tree(self, path):
747
738
        "Commit a nested tree."
748
 
        sub_tree = self.work_tree.get_nested_tree(path, file_id)
 
739
        sub_tree = self.work_tree.get_nested_tree(path)
749
740
        # FIXME: be more comprehensive here:
750
741
        # this works when both trees are in --trees repository,
751
742
        # but when both are bound to a different repository,
753
744
        # finally implement the explicit-caches approach design
754
745
        # a while back - RBC 20070306.
755
746
        if sub_tree.branch.repository.has_same_location(
756
 
            self.work_tree.branch.repository):
 
747
                self.work_tree.branch.repository):
757
748
            sub_tree.branch.repository = \
758
749
                self.work_tree.branch.repository
759
750
        try:
760
751
            return sub_tree.commit(message=None, revprops=self.revprops,
761
 
                recursive=self.recursive,
762
 
                message_callback=self.message_callback,
763
 
                timestamp=self.timestamp, timezone=self.timezone,
764
 
                committer=self.committer,
765
 
                allow_pointless=self.allow_pointless,
766
 
                strict=self.strict, verbose=self.verbose,
767
 
                local=self.local, reporter=self.reporter)
 
752
                                   recursive=self.recursive,
 
753
                                   message_callback=self.message_callback,
 
754
                                   timestamp=self.timestamp,
 
755
                                   timezone=self.timezone,
 
756
                                   committer=self.committer,
 
757
                                   allow_pointless=self.allow_pointless,
 
758
                                   strict=self.strict, verbose=self.verbose,
 
759
                                   local=self.local, reporter=self.reporter)
768
760
        except PointlessCommit:
769
 
            return self.work_tree.get_reference_revision(path, file_id)
 
761
            return self.work_tree.get_reference_revision(path)
770
762
 
771
763
    def _set_progress_stage(self, name, counter=False):
772
764
        """Set the progress stage and emit an update to the progress bar."""
786
778
    def _emit_progress(self):
787
779
        if self.pb_entries_count is not None:
788
780
            text = gettext("{0} [{1}] - Stage").format(self.pb_stage_name,
789
 
                self.pb_entries_count)
 
781
                                                       self.pb_entries_count)
790
782
        else:
791
783
            text = gettext("%s - Stage") % (self.pb_stage_name, )
792
784
        self.pb.update(text, self.pb_stage_count, self.pb_stage_total)