~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-09-25 18:42:17 UTC
  • mfrom: (2039.1.2 progress-cleanup)
  • Revision ID: pqm@pqm.ubuntu.com-20060925184217-fd144de117df49c3
cleanup progress properly when interrupted during fetch (#54000)

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
# XXX: Can we do any better about making interrupted commits change
 
19
# nothing?  
 
20
 
 
21
# TODO: Separate 'prepare' phase where we find a list of potentially
 
22
# committed files.  We then can then pause the commit to prompt for a
 
23
# commit message, knowing the summary will be the same as what's
 
24
# actually used for the commit.  (But perhaps simpler to simply get
 
25
# the tree status, then use that for a selective commit?)
 
26
 
18
27
# The newly committed revision is going to have a shape corresponding
19
28
# to that of the working inventory.  Files that are not in the
20
29
# working tree and that were in the predecessor are reported as
46
55
# merges from, then it should still be reported as newly added
47
56
# relative to the basis revision.
48
57
 
 
58
# TODO: Do checks that the tree can be committed *before* running the 
 
59
# editor; this should include checks for a pointless commit and for 
 
60
# unknown or missing files.
 
61
 
 
62
# TODO: If commit fails, leave the message in a file somewhere.
 
63
 
49
64
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
65
# the rest of the code; add a deprecation of the old name.
51
66
 
58
73
 
59
74
from bzrlib import (
60
75
    errors,
61
 
    inventory,
62
76
    tree,
63
77
    )
64
 
from bzrlib.branch import Branch
65
78
import bzrlib.config
66
79
from bzrlib.errors import (BzrError, PointlessCommit,
67
80
                           ConflictsInTree,
73
86
from bzrlib.testament import Testament
74
87
from bzrlib.trace import mutter, note, warning
75
88
from bzrlib.xml5 import serializer_v5
76
 
from bzrlib.inventory import Inventory, InventoryEntry
 
89
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
77
90
from bzrlib import symbol_versioning
78
91
from bzrlib.symbol_versioning import (deprecated_passed,
79
92
        deprecated_function,
80
93
        DEPRECATED_PARAMETER)
81
94
from bzrlib.workingtree import WorkingTree
82
 
import bzrlib.ui
83
95
 
84
96
 
85
97
class NullCommitReporter(object):
114
126
    def snapshot_change(self, change, path):
115
127
        if change == 'unchanged':
116
128
            return
117
 
        if change == 'added' and path == '':
118
 
            return
119
129
        note("%s %s", change, path)
120
130
 
121
131
    def completed(self, revno, rev_id):
172
182
               working_tree=None,
173
183
               local=False,
174
184
               reporter=None,
175
 
               config=None,
176
 
               message_callback=None):
 
185
               config=None):
177
186
        """Commit working copy as a new revision.
178
187
 
179
188
        branch -- the deprecated branch to commit to. New callers should pass in 
180
189
                  working_tree instead
181
190
 
182
 
        message -- the commit message (it or message_callback is required)
 
191
        message -- the commit message, a mandatory parameter
183
192
 
184
193
        timestamp -- if not None, seconds-since-epoch for a
185
194
             postdated/predated commit.
214
223
        else:
215
224
            self.work_tree = working_tree
216
225
            self.branch = self.work_tree.branch
217
 
        if message_callback is None:
218
 
            if message is not None:
219
 
                if isinstance(message, str):
220
 
                    message = message.decode(bzrlib.user_encoding)
221
 
                message_callback = lambda x: message
222
 
            else:
223
 
                raise BzrError("The message or message_callback keyword"
224
 
                               " parameter is required for commit().")
 
226
        if message is None:
 
227
            raise BzrError("The message keyword parameter is required for commit().")
225
228
 
226
229
        self.bound_branch = None
227
230
        self.local = local
238
241
 
239
242
        self.work_tree.lock_write()
240
243
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
241
 
        self.basis_tree = self.work_tree.basis_tree()
242
 
        self.basis_tree.lock_read()
243
244
        try:
244
245
            # Cannot commit with conflicts present.
245
246
            if len(self.work_tree.conflicts())>0:
256
257
                # this is so that we still consier the master branch
257
258
                # - in a checkout scenario the tree may have no
258
259
                # parents but the branch may do.
259
 
                first_tree_parent = bzrlib.revision.NULL_REVISION
260
 
            old_revno, master_last = self.master_branch.last_revision_info()
261
 
            if master_last != first_tree_parent:
262
 
                if master_last != bzrlib.revision.NULL_REVISION:
263
 
                    raise errors.OutOfDateTree(self.work_tree)
264
 
            if self.branch.repository.has_revision(first_tree_parent):
265
 
                new_revno = old_revno + 1
266
 
            else:
267
 
                # ghost parents never appear in revision history.
268
 
                new_revno = 1
 
260
                first_tree_parent = None
 
261
            master_last = self.master_branch.last_revision()
 
262
            if (master_last is not None and
 
263
                master_last != first_tree_parent):
 
264
                raise errors.OutOfDateTree(self.work_tree)
 
265
    
269
266
            if strict:
270
267
                # raise an exception as soon as we find a single unknown.
271
268
                for unknown in self.work_tree.unknowns():
273
270
                   
274
271
            if self.config is None:
275
272
                self.config = self.branch.get_config()
 
273
      
 
274
            if isinstance(message, str):
 
275
                message = message.decode(bzrlib.user_encoding)
 
276
            assert isinstance(message, unicode), type(message)
 
277
            self.message = message
 
278
            self._escape_commit_message()
276
279
 
277
280
            self.work_inv = self.work_tree.inventory
 
281
            self.basis_tree = self.work_tree.basis_tree()
278
282
            self.basis_inv = self.basis_tree.inventory
279
283
            if specific_files is not None:
280
284
                # Ensure specified files are versioned
311
315
            # that commit will succeed.
312
316
            self.builder.finish_inventory()
313
317
            self._emit_progress_update()
314
 
            message = message_callback(self)
315
 
            assert isinstance(message, unicode), type(message)
316
 
            self.message = message
317
 
            self._escape_commit_message()
318
 
 
319
318
            self.rev_id = self.builder.commit(self.message)
320
319
            self._emit_progress_update()
321
320
            # revision data is in the local branch now.
328
327
                # now the master has the revision data
329
328
                # 'commit' to the master first so a timeout here causes the local
330
329
                # branch to be out of date
331
 
                self.master_branch.set_last_revision_info(new_revno,
332
 
                                                          self.rev_id)
 
330
                self.master_branch.append_revision(self.rev_id)
333
331
 
334
332
            # and now do the commit locally.
335
 
            self.branch.set_last_revision_info(new_revno, self.rev_id)
 
333
            self.branch.append_revision(self.rev_id)
336
334
 
337
 
            rev_tree = self.builder.revision_tree()
338
 
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
 
335
            # if the builder gave us the revisiontree it created back, we
 
336
            # could use it straight away here.
 
337
            # TODO: implement this.
 
338
            self.work_tree.set_parent_trees([(self.rev_id,
 
339
                self.branch.repository.revision_tree(self.rev_id))])
339
340
            # now the work tree is up to date with the branch
340
341
            
341
 
            self.reporter.completed(new_revno, self.rev_id)
342
 
            # old style commit hooks - should be deprecated ? (obsoleted in
343
 
            # 0.15)
 
342
            self.reporter.completed(self.branch.revno(), self.rev_id)
344
343
            if self.config.post_commit() is not None:
345
344
                hooks = self.config.post_commit().split(' ')
346
345
                # this would be nicer with twisted.python.reflect.namedAny
349
348
                                  {'branch':self.branch,
350
349
                                   'bzrlib':bzrlib,
351
350
                                   'rev_id':self.rev_id})
352
 
            # new style commit hooks:
353
 
            if not self.bound_branch:
354
 
                hook_master = self.branch
355
 
                hook_local = None
356
 
            else:
357
 
                hook_master = self.master_branch
358
 
                hook_local = self.branch
359
 
            # With bound branches, when the master is behind the local branch,
360
 
            # the 'old_revno' and old_revid values here are incorrect.
361
 
            # XXX: FIXME ^. RBC 20060206
362
 
            if self.parents:
363
 
                old_revid = self.parents[0]
364
 
            else:
365
 
                old_revid = bzrlib.revision.NULL_REVISION
366
 
            for hook in Branch.hooks['post_commit']:
367
 
                hook(hook_local, hook_master, old_revno, old_revid, new_revno,
368
 
                    self.rev_id)
369
351
            self._emit_progress_update()
370
352
        finally:
371
353
            self._cleanup()
372
354
        return self.rev_id
373
355
 
374
 
    def _any_real_changes(self):
375
 
        """Are there real changes between new_inventory and basis?
376
 
 
377
 
        For trees without rich roots, inv.root.revision changes every commit.
378
 
        But if that is the only change, we want to treat it as though there
379
 
        are *no* changes.
380
 
        """
381
 
        new_entries = self.builder.new_inventory.iter_entries()
382
 
        basis_entries = self.basis_inv.iter_entries()
383
 
        new_path, new_root_ie = new_entries.next()
384
 
        basis_path, basis_root_ie = basis_entries.next()
385
 
 
386
 
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
387
 
        def ie_equal_no_revision(this, other):
388
 
            return ((this.file_id == other.file_id)
389
 
                    and (this.name == other.name)
390
 
                    and (this.symlink_target == other.symlink_target)
391
 
                    and (this.text_sha1 == other.text_sha1)
392
 
                    and (this.text_size == other.text_size)
393
 
                    and (this.text_id == other.text_id)
394
 
                    and (this.parent_id == other.parent_id)
395
 
                    and (this.kind == other.kind)
396
 
                    and (this.executable == other.executable)
397
 
                    )
398
 
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
399
 
            return True
400
 
 
401
 
        for new_ie, basis_ie in zip(new_entries, basis_entries):
402
 
            if new_ie != basis_ie:
403
 
                return True
404
 
 
405
 
        # No actual changes present
406
 
        return False
407
 
 
408
356
    def _check_pointless(self):
409
357
        if self.allow_pointless:
410
358
            return
413
361
            return
414
362
        # work around the fact that a newly-initted tree does differ from its
415
363
        # basis
416
 
        if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
417
 
            raise PointlessCommit()
418
 
        # Shortcut, if the number of entries changes, then we obviously have
419
 
        # a change
420
364
        if len(self.builder.new_inventory) != len(self.basis_inv):
421
365
            return
422
 
        # If length == 1, then we only have the root entry. Which means
423
 
        # that there is no real difference (only the root could be different)
424
 
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
 
366
        if (len(self.builder.new_inventory) != 1 and
 
367
            self.builder.new_inventory != self.basis_inv):
425
368
            return
426
369
        raise PointlessCommit()
427
370
 
455
398
        #       to local.
456
399
        
457
400
        # Make sure the local branch is identical to the master
458
 
        master_info = self.master_branch.last_revision_info()
459
 
        local_info = self.branch.last_revision_info()
460
 
        if local_info != master_info:
 
401
        master_rh = self.master_branch.revision_history()
 
402
        local_rh = self.branch.revision_history()
 
403
        if local_rh != master_rh:
461
404
            raise errors.BoundBranchOutOfDate(self.branch,
462
405
                    self.master_branch)
463
406
 
470
413
    def _cleanup(self):
471
414
        """Cleanup any open locks, progress bars etc."""
472
415
        cleanups = [self._cleanup_bound_branch,
473
 
                    self.basis_tree.unlock,
474
416
                    self.work_tree.unlock,
475
417
                    self.pb.finished]
476
418
        found_exception = None
526
468
        # TODO: Make sure that this list doesn't contain duplicate 
527
469
        # entries and the order is preserved when doing this.
528
470
        self.parents = self.work_tree.get_parent_ids()
529
 
        self.parent_invs = [self.basis_inv]
530
 
        for revision in self.parents[1:]:
 
471
        self.parent_invs = []
 
472
        for revision in self.parents:
531
473
            if self.branch.repository.has_revision(revision):
532
474
                mutter('commit parent revision {%s}', revision)
533
475
                inventory = self.branch.repository.get_inventory(revision)
576
518
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
577
519
        # ADHB 11-07-2006
578
520
        mutter("Selecting files for commit with filter %s", self.specific_files)
579
 
        assert self.work_inv.root is not None
580
521
        entries = self.work_inv.iter_entries()
581
522
        if not self.builder.record_root_entry:
582
523
            symbol_versioning.warn('CommitBuilders should support recording'
588
529
        for path, new_ie in entries:
589
530
            self._emit_progress_update()
590
531
            file_id = new_ie.file_id
591
 
            try:
592
 
                kind = self.work_tree.kind(file_id)
593
 
                if kind != new_ie.kind:
594
 
                    new_ie = inventory.make_entry(kind, new_ie.name,
595
 
                                                  new_ie.parent_id, file_id)
596
 
            except errors.NoSuchFile:
597
 
                pass
598
532
            # mutter('check %s {%s}', path, file_id)
599
533
            if (not self.specific_files or 
600
534
                is_inside_or_parent_of_any(self.specific_files, path)):
608
542
                else:
609
543
                    # this entry is new and not being committed
610
544
                    continue
 
545
 
611
546
            self.builder.record_entry_contents(ie, self.parent_invs, 
612
547
                path, self.work_tree)
613
548
            # describe the nature of the change that has occurred relative to