~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Martin Pool
  • Date: 2006-06-20 07:55:43 UTC
  • mfrom: (1798 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1799.
  • Revision ID: mbp@sourcefrog.net-20060620075543-b10f6575d4a4fa32
[merge] bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
67
67
import re
68
68
import sys
69
69
import time
70
 
import pdb
 
70
import warnings
71
71
 
72
 
from binascii import hexlify
73
72
from cStringIO import StringIO
74
73
 
75
74
from bzrlib.atomicfile import AtomicFile
76
 
from bzrlib.osutils import (local_time_offset,
77
 
                            rand_bytes, compact_date,
78
 
                            kind_marker, is_inside_any, quotefn,
79
 
                            sha_file, isdir, isfile,
80
 
                            split_lines)
81
75
import bzrlib.config
82
76
import bzrlib.errors as errors
83
77
from bzrlib.errors import (BzrError, PointlessCommit,
84
 
                           HistoryMissing,
85
78
                           ConflictsInTree,
86
79
                           StrictCommitFailed
87
80
                           )
88
 
from bzrlib.revision import Revision
 
81
from bzrlib.osutils import (kind_marker, isdir,isfile, is_inside_any, 
 
82
                            is_inside_or_parent_of_any,
 
83
                            quotefn, sha_file, split_lines)
89
84
from bzrlib.testament import Testament
90
85
from bzrlib.trace import mutter, note, warning
91
86
from bzrlib.xml5 import serializer_v5
92
87
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
93
 
from bzrlib.symbol_versioning import *
 
88
from bzrlib.symbol_versioning import (deprecated_passed,
 
89
        deprecated_function,
 
90
        zero_seven,
 
91
        DEPRECATED_PARAMETER)
94
92
from bzrlib.workingtree import WorkingTree
95
93
 
96
94
 
102
100
 
103
101
    New code should use the Commit class instead.
104
102
    """
105
 
    ## XXX: Remove this in favor of Branch.commit?
 
103
    ## XXX: Remove this in favor of WorkingTree.commit?
106
104
    Commit().commit(*args, **kwargs)
107
105
 
108
106
 
225
223
        mutter('preparing to commit')
226
224
 
227
225
        if deprecated_passed(branch):
228
 
            warn("Commit.commit (branch, ...): The branch parameter is "
 
226
            warnings.warn("Commit.commit (branch, ...): The branch parameter is "
229
227
                 "deprecated as of bzr 0.8. Please use working_tree= instead.",
230
228
                 DeprecationWarning, stacklevel=2)
231
229
            self.branch = branch
238
236
        if message is None:
239
237
            raise BzrError("The message keyword parameter is required for commit().")
240
238
 
241
 
        self.weave_store = self.branch.repository.weave_store
242
239
        self.bound_branch = None
243
240
        self.local = local
244
241
        self.master_branch = None
245
242
        self.master_locked = False
246
 
        self.rev_id = rev_id
 
243
        self.rev_id = None
247
244
        self.specific_files = specific_files
248
245
        self.allow_pointless = allow_pointless
249
 
        self.revprops = {}
250
 
        if revprops is not None:
251
 
            self.revprops.update(revprops)
252
246
 
253
247
        if reporter is None and self.reporter is None:
254
248
            self.reporter = NullCommitReporter()
275
269
                # raise an exception as soon as we find a single unknown.
276
270
                for unknown in self.work_tree.unknowns():
277
271
                    raise StrictCommitFailed()
278
 
    
279
 
            if timestamp is None:
280
 
                self.timestamp = time.time()
281
 
            else:
282
 
                self.timestamp = long(timestamp)
283
 
                
 
272
                   
284
273
            if self.config is None:
285
274
                self.config = bzrlib.config.BranchConfig(self.branch)
286
 
    
287
 
            if rev_id is None:
288
 
                self.rev_id = _gen_revision_id(self.config, self.timestamp)
289
 
            else:
290
 
                self.rev_id = rev_id
291
 
    
292
 
            if committer is None:
293
 
                self.committer = self.config.username()
294
 
            else:
295
 
                assert isinstance(committer, basestring), type(committer)
296
 
                self.committer = committer
297
 
    
298
 
            if timezone is None:
299
 
                self.timezone = local_time_offset()
300
 
            else:
301
 
                self.timezone = int(timezone)
302
 
    
 
275
      
303
276
            if isinstance(message, str):
304
277
                message = message.decode(bzrlib.user_encoding)
305
278
            assert isinstance(message, unicode), type(message)
314
287
            # note that this estimate is too long when we do a partial tree
315
288
            # commit which excludes some new files from being considered.
316
289
            # The estimate is corrected when we populate the new inv.
317
 
            self.pb_total = len(self.basis_inv) + len(self.work_inv) + 3 - 1
 
290
            self.pb_total = len(self.work_inv) + 5
318
291
            self.pb_count = 0
319
292
 
320
293
            self._gather_parents()
322
295
                raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
323
296
                        self.specific_files)
324
297
            self._check_parents_present()
 
298
            self.builder = self.branch.get_commit_builder(self.parents, 
 
299
                self.config, timestamp, timezone, committer, revprops, rev_id)
325
300
            
326
301
            self._remove_deleted()
327
302
            self._populate_new_inv()
328
 
            self._store_snapshot()
329
303
            self._report_deletes()
330
304
 
331
305
            if not (self.allow_pointless
332
306
                    or len(self.parents) > 1
333
 
                    or self.new_inv != self.basis_inv):
 
307
                    or self.builder.new_inventory != self.basis_inv):
334
308
                raise PointlessCommit()
335
309
 
336
310
            self._emit_progress_update()
337
 
            self.inv_sha1 = self.branch.repository.add_inventory(
338
 
                self.rev_id,
339
 
                self.new_inv,
340
 
                self.present_parents
341
 
                )
342
 
            self._emit_progress_update()
343
 
            self._make_revision()
 
311
            # TODO: Now the new inventory is known, check for conflicts and prompt the 
 
312
            # user for a commit message.
 
313
            self.builder.finish_inventory()
 
314
            self._emit_progress_update()
 
315
            self.rev_id = self.builder.commit(self.message)
 
316
            self._emit_progress_update()
344
317
            # revision data is in the local branch now.
345
318
            
346
319
            # upload revision data to the master.
347
 
            # this will propogate merged revisions too if needed.
 
320
            # this will propagate merged revisions too if needed.
348
321
            if self.bound_branch:
349
322
                self.master_branch.repository.fetch(self.branch.repository,
350
323
                                                    revision_id=self.rev_id)
372
345
            self._emit_progress_update()
373
346
        finally:
374
347
            self._cleanup()
 
348
        return self.rev_id
375
349
 
376
350
    def _check_bound_branch(self):
377
351
        """Check to see if the local branch is bound.
414
388
        self.bound_branch = self.branch
415
389
        self.master_branch.lock_write()
416
390
        self.master_locked = True
417
 
####        
418
 
####        # Check to see if we have any pending merges. If we do
419
 
####        # those need to be pushed into the master branch
420
 
####        pending_merges = self.work_tree.pending_merges()
421
 
####        if pending_merges:
422
 
####            for revision_id in pending_merges:
423
 
####                self.master_branch.repository.fetch(self.bound_branch.repository,
424
 
####                                                    revision_id=revision_id)
425
391
 
426
392
    def _cleanup(self):
427
393
        """Cleanup any open locks, progress bars etc."""
439
405
            except Exception, e:
440
406
                found_exception = e
441
407
        if found_exception is not None: 
442
 
            # dont do a plan raise, because the last exception may have been
 
408
            # don't do a plan raise, because the last exception may have been
443
409
            # trashed, e is our sure-to-work exception even though it loses the
444
410
            # full traceback. XXX: RBC 20060421 perhaps we could check the
445
411
            # exc_info and if its the same one do a plain raise otherwise 
478
444
 
479
445
    def _gather_parents(self):
480
446
        """Record the parents of a merge for merge detection."""
481
 
        pending_merges = self.work_tree.pending_merges()
482
 
        self.parents = []
 
447
        # TODO: Make sure that this list doesn't contain duplicate 
 
448
        # entries and the order is preserved when doing this.
 
449
        self.parents = self.work_tree.get_parent_ids()
483
450
        self.parent_invs = []
484
 
        self.present_parents = []
485
 
        precursor_id = self.branch.last_revision()
486
 
        if precursor_id:
487
 
            self.parents.append(precursor_id)
488
 
        self.parents += pending_merges
489
451
        for revision in self.parents:
490
452
            if self.branch.repository.has_revision(revision):
491
453
                inventory = self.branch.repository.get_inventory(revision)
492
454
                self.parent_invs.append(inventory)
493
 
                self.present_parents.append(revision)
494
455
 
495
456
    def _check_parents_present(self):
496
457
        for parent_id in self.parents:
498
459
            if not self.branch.repository.has_revision(parent_id):
499
460
                if parent_id == self.branch.last_revision():
500
461
                    warning("parent is missing %r", parent_id)
501
 
                    raise HistoryMissing(self.branch, 'revision', parent_id)
502
 
                else:
503
 
                    mutter("commit will ghost revision %r", parent_id)
 
462
                    raise BzrCheckError("branch %s is missing revision {%s}"
 
463
                            % (self.branch, parent_id))
504
464
            
505
 
    def _make_revision(self):
506
 
        """Record a new revision object for this commit."""
507
 
        rev = Revision(timestamp=self.timestamp,
508
 
                       timezone=self.timezone,
509
 
                       committer=self.committer,
510
 
                       message=self.message,
511
 
                       inventory_sha1=self.inv_sha1,
512
 
                       revision_id=self.rev_id,
513
 
                       properties=self.revprops)
514
 
        rev.parent_ids = self.parents
515
 
        self.branch.repository.add_revision(self.rev_id, rev, self.new_inv, self.config)
516
 
 
517
465
    def _remove_deleted(self):
518
466
        """Remove deleted files from the working inventories.
519
467
 
539
487
                del self.work_inv[file_id]
540
488
            self.work_tree._write_inventory(self.work_inv)
541
489
 
542
 
    def _store_snapshot(self):
543
 
        """Pass over inventory and record a snapshot.
544
 
 
545
 
        Entries get a new revision when they are modified in 
546
 
        any way, which includes a merge with a new set of
547
 
        parents that have the same entry. 
548
 
        """
549
 
        # XXX: Need to think more here about when the user has
550
 
        # made a specific decision on a particular value -- c.f.
551
 
        # mark-merge.  
552
 
 
553
 
        # iter_entries does not visit the ROOT_ID node so we need to call
554
 
        # self._emit_progress_update once by hand.
555
 
        self._emit_progress_update()
556
 
        for path, ie in self.new_inv.iter_entries():
557
 
            self._emit_progress_update()
558
 
            previous_entries = ie.find_previous_heads(
559
 
                self.parent_invs,
560
 
                self.weave_store,
561
 
                self.branch.repository.get_transaction())
562
 
            if ie.revision is None:
563
 
                # we are creating a new revision for ie in the history store
564
 
                # and inventory.
565
 
                ie.snapshot(self.rev_id, path, previous_entries,
566
 
                    self.work_tree, self.weave_store,
567
 
                    self.branch.repository.get_transaction())
568
 
            # describe the nature of the change that has occured relative to
569
 
            # the basis inventory.
570
 
            if (self.basis_inv.has_id(ie.file_id)):
571
 
                basis_ie = self.basis_inv[ie.file_id]
572
 
            else:
573
 
                basis_ie = None
574
 
            change = ie.describe_change(basis_ie, ie)
575
 
            if change in (InventoryEntry.RENAMED, 
576
 
                InventoryEntry.MODIFIED_AND_RENAMED):
577
 
                old_path = self.basis_inv.id2path(ie.file_id)
578
 
                self.reporter.renamed(change, old_path, path)
579
 
            else:
580
 
                self.reporter.snapshot_change(change, path)
581
 
 
582
490
    def _populate_new_inv(self):
583
491
        """Build revision inventory.
584
492
 
590
498
        revision set to their prior value.
591
499
        """
592
500
        mutter("Selecting files for commit with filter %s", self.specific_files)
593
 
        self.new_inv = Inventory(revision_id=self.rev_id)
594
501
        # iter_entries does not visit the ROOT_ID node so we need to call
595
502
        # self._emit_progress_update once by hand.
596
503
        self._emit_progress_update()
597
504
        for path, new_ie in self.work_inv.iter_entries():
598
505
            self._emit_progress_update()
599
506
            file_id = new_ie.file_id
600
 
            mutter('check %s {%s}', path, new_ie.file_id)
601
 
            if self.specific_files:
602
 
                if not is_inside_any(self.specific_files, path):
603
 
                    mutter('%s not selected for commit', path)
604
 
                    self._carry_entry(file_id)
 
507
            mutter('check %s {%s}', path, file_id)
 
508
            if (not self.specific_files or 
 
509
                is_inside_or_parent_of_any(self.specific_files, path)):
 
510
                    mutter('%s selected for commit', path)
 
511
                    ie = new_ie.copy()
 
512
                    ie.revision = None
 
513
            else:
 
514
                mutter('%s not selected for commit', path)
 
515
                if self.basis_inv.has_id(file_id):
 
516
                    ie = self.basis_inv[file_id].copy()
 
517
                else:
 
518
                    # this entry is new and not being committed
605
519
                    continue
606
 
                else:
607
 
                    # this is selected, ensure its parents are too.
608
 
                    parent_id = new_ie.parent_id
609
 
                    while parent_id != ROOT_ID:
610
 
                        if not self.new_inv.has_id(parent_id):
611
 
                            ie = self._select_entry(self.work_inv[parent_id])
612
 
                            mutter('%s selected for commit because of %s',
613
 
                                   self.new_inv.id2path(parent_id), path)
614
520
 
615
 
                        ie = self.new_inv[parent_id]
616
 
                        if ie.revision is not None:
617
 
                            ie.revision = None
618
 
                            mutter('%s selected for commit because of %s',
619
 
                                   self.new_inv.id2path(parent_id), path)
620
 
                        parent_id = ie.parent_id
621
 
            mutter('%s selected for commit', path)
622
 
            self._select_entry(new_ie)
 
521
            self.builder.record_entry_contents(ie, self.parent_invs, 
 
522
                path, self.work_tree)
 
523
            # describe the nature of the change that has occurred relative to
 
524
            # the basis inventory.
 
525
            if (self.basis_inv.has_id(ie.file_id)):
 
526
                basis_ie = self.basis_inv[ie.file_id]
 
527
            else:
 
528
                basis_ie = None
 
529
            change = ie.describe_change(basis_ie, ie)
 
530
            if change in (InventoryEntry.RENAMED, 
 
531
                InventoryEntry.MODIFIED_AND_RENAMED):
 
532
                old_path = self.basis_inv.id2path(ie.file_id)
 
533
                self.reporter.renamed(change, old_path, path)
 
534
            else:
 
535
                self.reporter.snapshot_change(change, path)
623
536
 
624
537
    def _emit_progress_update(self):
625
538
        """Emit an update to the progress bar."""
626
539
        self.pb.update("Committing", self.pb_count, self.pb_total)
627
540
        self.pb_count += 1
628
541
 
629
 
    def _select_entry(self, new_ie):
630
 
        """Make new_ie be considered for committing."""
631
 
        ie = new_ie.copy()
632
 
        ie.revision = None
633
 
        self.new_inv.add(ie)
634
 
        return ie
635
 
 
636
 
    def _carry_entry(self, file_id):
637
 
        """Carry the file unchanged from the basis revision."""
638
 
        if self.basis_inv.has_id(file_id):
639
 
            self.new_inv.add(self.basis_inv[file_id].copy())
640
 
        else:
641
 
            # this entry is new and not being committed
642
 
            self.pb_total -= 1
643
 
 
644
542
    def _report_deletes(self):
645
543
        for path, ie in self.basis_inv.iter_entries():
646
 
            if ie.file_id not in self.new_inv:
 
544
            if ie.file_id not in self.builder.new_inventory:
647
545
                self.reporter.deleted(path)
648
546
 
649
 
def _gen_revision_id(config, when):
650
 
    """Return new revision-id."""
651
 
    s = '%s-%s-' % (config.user_email(), compact_date(when))
652
 
    s += hexlify(rand_bytes(8))
653
 
    return s
 
547