~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: wang
  • Date: 2006-10-29 13:41:32 UTC
  • mto: (2104.4.1 wang_65714)
  • mto: This revision was merged to the branch mainline in revision 2109.
  • Revision ID: wang@ubuntu-20061029134132-3d7f4216f20c4aef
Replace python's difflib by patiencediff because the worst case 
performance is cubic for difflib and people commiting large data 
files are often hurt by this. The worst case performance of patience is 
quadratic. Fix bug 65714.

Show diffs side-by-side

added added

removed removed

Lines of Context:
61
61
 
62
62
# TODO: If commit fails, leave the message in a file somewhere.
63
63
 
 
64
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
 
65
# the rest of the code; add a deprecation of the old name.
64
66
 
65
67
import os
66
68
import re
67
69
import sys
68
70
import time
69
 
import warnings
70
71
 
71
72
from cStringIO import StringIO
72
73
 
 
74
from bzrlib import (
 
75
    errors,
 
76
    tree,
 
77
    )
73
78
import bzrlib.config
74
 
import bzrlib.errors as errors
75
79
from bzrlib.errors import (BzrError, PointlessCommit,
76
80
                           ConflictsInTree,
77
81
                           StrictCommitFailed
82
86
from bzrlib.testament import Testament
83
87
from bzrlib.trace import mutter, note, warning
84
88
from bzrlib.xml5 import serializer_v5
85
 
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
 
89
from bzrlib.inventory import Inventory, InventoryEntry
 
90
from bzrlib import symbol_versioning
86
91
from bzrlib.symbol_versioning import (deprecated_passed,
87
92
        deprecated_function,
88
 
        zero_seven,
89
93
        DEPRECATED_PARAMETER)
90
94
from bzrlib.workingtree import WorkingTree
91
95
 
92
96
 
93
 
@deprecated_function(zero_seven)
94
 
def commit(*args, **kwargs):
95
 
    """Commit a new revision to a branch.
96
 
 
97
 
    Function-style interface for convenience of old callers.
98
 
 
99
 
    New code should use the Commit class instead.
100
 
    """
101
 
    ## XXX: Remove this in favor of WorkingTree.commit?
102
 
    Commit().commit(*args, **kwargs)
103
 
 
104
 
 
105
97
class NullCommitReporter(object):
106
98
    """I report on progress of a commit."""
107
99
 
134
126
    def snapshot_change(self, change, path):
135
127
        if change == 'unchanged':
136
128
            return
 
129
        if change == 'added' and path == '':
 
130
            return
137
131
        note("%s %s", change, path)
138
132
 
139
133
    def completed(self, revno, rev_id):
221
215
        mutter('preparing to commit')
222
216
 
223
217
        if deprecated_passed(branch):
224
 
            warnings.warn("Commit.commit (branch, ...): The branch parameter is "
 
218
            symbol_versioning.warn("Commit.commit (branch, ...): The branch parameter is "
225
219
                 "deprecated as of bzr 0.8. Please use working_tree= instead.",
226
220
                 DeprecationWarning, stacklevel=2)
227
221
            self.branch = branch
258
252
            self._check_bound_branch()
259
253
 
260
254
            # check for out of date working trees
261
 
            # if we are bound, then self.branch is the master branch and this
262
 
            # test is thus all we need.
263
 
            if self.work_tree.last_revision() != self.master_branch.last_revision():
 
255
            try:
 
256
                first_tree_parent = self.work_tree.get_parent_ids()[0]
 
257
            except IndexError:
 
258
                # if there are no parents, treat our parent as 'None'
 
259
                # this is so that we still consier the master branch
 
260
                # - in a checkout scenario the tree may have no
 
261
                # parents but the branch may do.
 
262
                first_tree_parent = None
 
263
            master_last = self.master_branch.last_revision()
 
264
            if (master_last is not None and
 
265
                master_last != first_tree_parent):
264
266
                raise errors.OutOfDateTree(self.work_tree)
265
267
    
266
268
            if strict:
280
282
            self.work_inv = self.work_tree.inventory
281
283
            self.basis_tree = self.work_tree.basis_tree()
282
284
            self.basis_inv = self.basis_tree.inventory
 
285
            if specific_files is not None:
 
286
                # Ensure specified files are versioned
 
287
                # (We don't actually need the ids here)
 
288
                tree.find_ids_across_trees(specific_files, 
 
289
                                           [self.basis_tree, self.work_tree])
283
290
            # one to finish, one for rev and inventory, and one for each
284
291
            # inventory entry, and the same for the new inventory.
285
292
            # note that this estimate is too long when we do a partial tree
292
299
            if len(self.parents) > 1 and self.specific_files:
293
300
                raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
294
301
                        self.specific_files)
295
 
            self._check_parents_present()
 
302
            
296
303
            self.builder = self.branch.get_commit_builder(self.parents, 
297
304
                self.config, timestamp, timezone, committer, revprops, rev_id)
298
305
            
300
307
            self._populate_new_inv()
301
308
            self._report_deletes()
302
309
 
303
 
            if not (self.allow_pointless
304
 
                    or len(self.parents) > 1
305
 
                    or self.builder.new_inventory != self.basis_inv):
306
 
                raise PointlessCommit()
 
310
            self._check_pointless()
307
311
 
308
312
            self._emit_progress_update()
309
 
            # TODO: Now the new inventory is known, check for conflicts and prompt the 
310
 
            # user for a commit message.
 
313
            # TODO: Now the new inventory is known, check for conflicts and
 
314
            # prompt the user for a commit message.
 
315
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
 
316
            # weave lines, because nothing should be recorded until it is known
 
317
            # that commit will succeed.
311
318
            self.builder.finish_inventory()
312
319
            self._emit_progress_update()
313
320
            self.rev_id = self.builder.commit(self.message)
327
334
            # and now do the commit locally.
328
335
            self.branch.append_revision(self.rev_id)
329
336
 
330
 
            self.work_tree.set_pending_merges([])
331
 
            self.work_tree.set_last_revision(self.rev_id)
 
337
            rev_tree = self.builder.revision_tree()
 
338
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
332
339
            # now the work tree is up to date with the branch
333
340
            
334
341
            self.reporter.completed(self.branch.revno(), self.rev_id)
345
352
            self._cleanup()
346
353
        return self.rev_id
347
354
 
 
355
    def _any_real_changes(self):
 
356
        """Are there real changes between new_inventory and basis?
 
357
 
 
358
        For trees without rich roots, inv.root.revision changes every commit.
 
359
        But if that is the only change, we want to treat it as though there
 
360
        are *no* changes.
 
361
        """
 
362
        new_entries = self.builder.new_inventory.iter_entries()
 
363
        basis_entries = self.basis_inv.iter_entries()
 
364
        new_path, new_root_ie = new_entries.next()
 
365
        basis_path, basis_root_ie = basis_entries.next()
 
366
 
 
367
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
 
368
        def ie_equal_no_revision(this, other):
 
369
            return ((this.file_id == other.file_id)
 
370
                    and (this.name == other.name)
 
371
                    and (this.symlink_target == other.symlink_target)
 
372
                    and (this.text_sha1 == other.text_sha1)
 
373
                    and (this.text_size == other.text_size)
 
374
                    and (this.text_id == other.text_id)
 
375
                    and (this.parent_id == other.parent_id)
 
376
                    and (this.kind == other.kind)
 
377
                    and (this.executable == other.executable)
 
378
                    )
 
379
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
 
380
            return True
 
381
 
 
382
        for new_ie, basis_ie in zip(new_entries, basis_entries):
 
383
            if new_ie != basis_ie:
 
384
                return True
 
385
 
 
386
        # No actual changes present
 
387
        return False
 
388
 
 
389
    def _check_pointless(self):
 
390
        if self.allow_pointless:
 
391
            return
 
392
        # A merge with no effect on files
 
393
        if len(self.parents) > 1:
 
394
            return
 
395
        # work around the fact that a newly-initted tree does differ from its
 
396
        # basis
 
397
        if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
 
398
            raise PointlessCommit()
 
399
        # Shortcut, if the number of entries changes, then we obviously have
 
400
        # a change
 
401
        if len(self.builder.new_inventory) != len(self.basis_inv):
 
402
            return
 
403
        # If length == 1, then we only have the root entry. Which means
 
404
        # that there is no real difference (only the root could be different)
 
405
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
 
406
            return
 
407
        raise PointlessCommit()
 
408
 
348
409
    def _check_bound_branch(self):
349
410
        """Check to see if the local branch is bound.
350
411
 
448
509
        self.parent_invs = []
449
510
        for revision in self.parents:
450
511
            if self.branch.repository.has_revision(revision):
 
512
                mutter('commit parent revision {%s}', revision)
451
513
                inventory = self.branch.repository.get_inventory(revision)
452
514
                self.parent_invs.append(inventory)
 
515
            else:
 
516
                mutter('commit parent ghost revision {%s}', revision)
453
517
 
454
 
    def _check_parents_present(self):
455
 
        for parent_id in self.parents:
456
 
            mutter('commit parent revision {%s}', parent_id)
457
 
            if not self.branch.repository.has_revision(parent_id):
458
 
                if parent_id == self.branch.last_revision():
459
 
                    warning("parent is missing %r", parent_id)
460
 
                    raise BzrCheckError("branch %s is missing revision {%s}"
461
 
                            % (self.branch, parent_id))
462
 
            
463
518
    def _remove_deleted(self):
464
519
        """Remove deleted files from the working inventories.
465
520
 
473
528
        """
474
529
        specific = self.specific_files
475
530
        deleted_ids = []
 
531
        deleted_paths = set()
476
532
        for path, ie in self.work_inv.iter_entries():
 
533
            if is_inside_any(deleted_paths, path):
 
534
                # The tree will delete the required ids recursively.
 
535
                continue
477
536
            if specific and not is_inside_any(specific, path):
478
537
                continue
479
538
            if not self.work_tree.has_filename(path):
 
539
                deleted_paths.add(path)
480
540
                self.reporter.missing(path)
481
 
                deleted_ids.append((path, ie.file_id))
482
 
        if deleted_ids:
483
 
            deleted_ids.sort(reverse=True)
484
 
            for path, file_id in deleted_ids:
485
 
                del self.work_inv[file_id]
486
 
            self.work_tree._write_inventory(self.work_inv)
 
541
                deleted_ids.append(ie.file_id)
 
542
        self.work_tree.unversion(deleted_ids)
487
543
 
488
544
    def _populate_new_inv(self):
489
545
        """Build revision inventory.
500
556
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
501
557
        # ADHB 11-07-2006
502
558
        mutter("Selecting files for commit with filter %s", self.specific_files)
503
 
        # at this point we dont copy the root entry:
 
559
        assert self.work_inv.root is not None
504
560
        entries = self.work_inv.iter_entries()
505
 
        entries.next()
506
 
        self._emit_progress_update()
 
561
        if not self.builder.record_root_entry:
 
562
            symbol_versioning.warn('CommitBuilders should support recording'
 
563
                ' the root entry as of bzr 0.10.', DeprecationWarning, 
 
564
                stacklevel=1)
 
565
            self.builder.new_inventory.add(self.basis_inv.root.copy())
 
566
            entries.next()
 
567
            self._emit_progress_update()
507
568
        for path, new_ie in entries:
508
569
            self._emit_progress_update()
509
570
            file_id = new_ie.file_id
520
581
                else:
521
582
                    # this entry is new and not being committed
522
583
                    continue
523
 
 
524
584
            self.builder.record_entry_contents(ie, self.parent_invs, 
525
585
                path, self.work_tree)
526
586
            # describe the nature of the change that has occurred relative to