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

  • Committer: Alexander Belchenko
  • Date: 2007-06-05 08:02:04 UTC
  • mto: This revision was merged to the branch mainline in revision 2512.
  • Revision ID: bialix@ukr.net-20070605080204-hvhqw69njlpxcscb
sanitizeĀ developersĀ docs

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
 
27
18
# The newly committed revision is going to have a shape corresponding
28
19
# to that of the working inventory.  Files that are not in the
29
20
# working tree and that were in the predecessor are reported as
55
46
# merges from, then it should still be reported as newly added
56
47
# relative to the basis revision.
57
48
 
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
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
 
50
# the rest of the code; add a deprecation of the old name.
64
51
 
65
52
import os
66
53
import re
69
56
 
70
57
from cStringIO import StringIO
71
58
 
 
59
from bzrlib import (
 
60
    errors,
 
61
    inventory,
 
62
    tree,
 
63
    )
 
64
from bzrlib.branch import Branch
72
65
import bzrlib.config
73
 
import bzrlib.errors as errors
74
66
from bzrlib.errors import (BzrError, PointlessCommit,
75
67
                           ConflictsInTree,
76
68
                           StrictCommitFailed
81
73
from bzrlib.testament import Testament
82
74
from bzrlib.trace import mutter, note, warning
83
75
from bzrlib.xml5 import serializer_v5
84
 
from bzrlib.inventory import Inventory, ROOT_ID, InventoryEntry
 
76
from bzrlib.inventory import Inventory, InventoryEntry
85
77
from bzrlib import symbol_versioning
86
78
from bzrlib.symbol_versioning import (deprecated_passed,
87
79
        deprecated_function,
88
80
        DEPRECATED_PARAMETER)
89
81
from bzrlib.workingtree import WorkingTree
 
82
import bzrlib.ui
90
83
 
91
84
 
92
85
class NullCommitReporter(object):
121
114
    def snapshot_change(self, change, path):
122
115
        if change == 'unchanged':
123
116
            return
 
117
        if change == 'added' and path == '':
 
118
            return
124
119
        note("%s %s", change, path)
125
120
 
126
121
    def completed(self, revno, rev_id):
164
159
            self.config = None
165
160
        
166
161
    def commit(self,
167
 
               branch=DEPRECATED_PARAMETER, message=None,
 
162
               message=None,
168
163
               timestamp=None,
169
164
               timezone=None,
170
165
               committer=None,
177
172
               working_tree=None,
178
173
               local=False,
179
174
               reporter=None,
180
 
               config=None):
 
175
               config=None,
 
176
               message_callback=None,
 
177
               recursive='down'):
181
178
        """Commit working copy as a new revision.
182
179
 
183
 
        branch -- the deprecated branch to commit to. New callers should pass in 
184
 
                  working_tree instead
185
 
 
186
 
        message -- the commit message, a mandatory parameter
 
180
        message -- the commit message (it or message_callback is required)
187
181
 
188
182
        timestamp -- if not None, seconds-since-epoch for a
189
183
             postdated/predated commit.
204
198
 
205
199
        revprops -- Properties for new revision
206
200
        :param local: Perform a local only commit.
 
201
        :param recursive: If set to 'down', commit in any subtrees that have
 
202
            pending changes of any sort during this commit.
207
203
        """
208
204
        mutter('preparing to commit')
209
205
 
210
 
        if deprecated_passed(branch):
211
 
            symbol_versioning.warn("Commit.commit (branch, ...): The branch parameter is "
212
 
                 "deprecated as of bzr 0.8. Please use working_tree= instead.",
213
 
                 DeprecationWarning, stacklevel=2)
214
 
            self.branch = branch
215
 
            self.work_tree = self.branch.bzrdir.open_workingtree()
216
 
        elif working_tree is None:
217
 
            raise BzrError("One of branch and working_tree must be passed into commit().")
 
206
        if working_tree is None:
 
207
            raise BzrError("working_tree must be passed into commit().")
218
208
        else:
219
209
            self.work_tree = working_tree
220
210
            self.branch = self.work_tree.branch
221
 
        if message is None:
222
 
            raise BzrError("The message keyword parameter is required for commit().")
 
211
            if getattr(self.work_tree, 'requires_rich_root', lambda: False)():
 
212
                if not self.branch.repository.supports_rich_root():
 
213
                    raise errors.RootNotRich()
 
214
        if message_callback is None:
 
215
            if message is not None:
 
216
                if isinstance(message, str):
 
217
                    message = message.decode(bzrlib.user_encoding)
 
218
                message_callback = lambda x: message
 
219
            else:
 
220
                raise BzrError("The message or message_callback keyword"
 
221
                               " parameter is required for commit().")
223
222
 
224
223
        self.bound_branch = None
225
224
        self.local = local
228
227
        self.rev_id = None
229
228
        self.specific_files = specific_files
230
229
        self.allow_pointless = allow_pointless
 
230
        self.recursive = recursive
 
231
        self.revprops = revprops
 
232
        self.message_callback = message_callback
 
233
        self.timestamp = timestamp
 
234
        self.timezone = timezone
 
235
        self.committer = committer
 
236
        self.strict = strict
 
237
        self.verbose = verbose
 
238
        self.local = local
231
239
 
232
240
        if reporter is None and self.reporter is None:
233
241
            self.reporter = NullCommitReporter()
236
244
 
237
245
        self.work_tree.lock_write()
238
246
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
247
        self.basis_tree = self.work_tree.basis_tree()
 
248
        self.basis_tree.lock_read()
239
249
        try:
240
250
            # Cannot commit with conflicts present.
241
251
            if len(self.work_tree.conflicts())>0:
252
262
                # this is so that we still consier the master branch
253
263
                # - in a checkout scenario the tree may have no
254
264
                # parents but the branch may do.
255
 
                first_tree_parent = None
256
 
            master_last = self.master_branch.last_revision()
257
 
            if (master_last is not None and
258
 
                master_last != first_tree_parent):
259
 
                raise errors.OutOfDateTree(self.work_tree)
260
 
    
 
265
                first_tree_parent = bzrlib.revision.NULL_REVISION
 
266
            old_revno, master_last = self.master_branch.last_revision_info()
 
267
            if master_last != first_tree_parent:
 
268
                if master_last != bzrlib.revision.NULL_REVISION:
 
269
                    raise errors.OutOfDateTree(self.work_tree)
 
270
            if self.branch.repository.has_revision(first_tree_parent):
 
271
                new_revno = old_revno + 1
 
272
            else:
 
273
                # ghost parents never appear in revision history.
 
274
                new_revno = 1
261
275
            if strict:
262
276
                # raise an exception as soon as we find a single unknown.
263
277
                for unknown in self.work_tree.unknowns():
265
279
                   
266
280
            if self.config is None:
267
281
                self.config = self.branch.get_config()
268
 
      
269
 
            if isinstance(message, str):
270
 
                message = message.decode(bzrlib.user_encoding)
271
 
            assert isinstance(message, unicode), type(message)
272
 
            self.message = message
273
 
            self._escape_commit_message()
274
282
 
275
283
            self.work_inv = self.work_tree.inventory
276
 
            self.basis_tree = self.work_tree.basis_tree()
277
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
                # XXX: Dont we have filter_unversioned to do this more
 
289
                # cheaply?
 
290
                tree.find_ids_across_trees(specific_files,
 
291
                                           [self.basis_tree, self.work_tree])
278
292
            # one to finish, one for rev and inventory, and one for each
279
293
            # inventory entry, and the same for the new inventory.
280
294
            # note that this estimate is too long when we do a partial tree
285
299
 
286
300
            self._gather_parents()
287
301
            if len(self.parents) > 1 and self.specific_files:
288
 
                raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
289
 
                        self.specific_files)
 
302
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
290
303
            
291
 
            self.builder = self.branch.get_commit_builder(self.parents, 
 
304
            self.builder = self.branch.get_commit_builder(self.parents,
292
305
                self.config, timestamp, timezone, committer, revprops, rev_id)
293
306
            
294
307
            self._remove_deleted()
305
318
            # that commit will succeed.
306
319
            self.builder.finish_inventory()
307
320
            self._emit_progress_update()
 
321
            message = message_callback(self)
 
322
            assert isinstance(message, unicode), type(message)
 
323
            self.message = message
 
324
            self._escape_commit_message()
 
325
 
308
326
            self.rev_id = self.builder.commit(self.message)
309
327
            self._emit_progress_update()
310
328
            # revision data is in the local branch now.
317
335
                # now the master has the revision data
318
336
                # 'commit' to the master first so a timeout here causes the local
319
337
                # branch to be out of date
320
 
                self.master_branch.append_revision(self.rev_id)
 
338
                self.master_branch.set_last_revision_info(new_revno,
 
339
                                                          self.rev_id)
321
340
 
322
341
            # and now do the commit locally.
323
 
            self.branch.append_revision(self.rev_id)
 
342
            self.branch.set_last_revision_info(new_revno, self.rev_id)
324
343
 
325
 
            # if the builder gave us the revisiontree it created back, we
326
 
            # could use it straight away here.
327
 
            # TODO: implement this.
328
 
            self.work_tree.set_parent_trees([(self.rev_id,
329
 
                self.branch.repository.revision_tree(self.rev_id))])
 
344
            rev_tree = self.builder.revision_tree()
 
345
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
330
346
            # now the work tree is up to date with the branch
331
347
            
332
 
            self.reporter.completed(self.branch.revno(), self.rev_id)
 
348
            self.reporter.completed(new_revno, self.rev_id)
 
349
            # old style commit hooks - should be deprecated ? (obsoleted in
 
350
            # 0.15)
333
351
            if self.config.post_commit() is not None:
334
352
                hooks = self.config.post_commit().split(' ')
335
353
                # this would be nicer with twisted.python.reflect.namedAny
338
356
                                  {'branch':self.branch,
339
357
                                   'bzrlib':bzrlib,
340
358
                                   'rev_id':self.rev_id})
 
359
            # new style commit hooks:
 
360
            if not self.bound_branch:
 
361
                hook_master = self.branch
 
362
                hook_local = None
 
363
            else:
 
364
                hook_master = self.master_branch
 
365
                hook_local = self.branch
 
366
            # With bound branches, when the master is behind the local branch,
 
367
            # the 'old_revno' and old_revid values here are incorrect.
 
368
            # XXX: FIXME ^. RBC 20060206
 
369
            if self.parents:
 
370
                old_revid = self.parents[0]
 
371
            else:
 
372
                old_revid = bzrlib.revision.NULL_REVISION
 
373
            for hook in Branch.hooks['post_commit']:
 
374
                hook(hook_local, hook_master, old_revno, old_revid, new_revno,
 
375
                    self.rev_id)
341
376
            self._emit_progress_update()
342
377
        finally:
343
378
            self._cleanup()
344
379
        return self.rev_id
345
380
 
 
381
    def _any_real_changes(self):
 
382
        """Are there real changes between new_inventory and basis?
 
383
 
 
384
        For trees without rich roots, inv.root.revision changes every commit.
 
385
        But if that is the only change, we want to treat it as though there
 
386
        are *no* changes.
 
387
        """
 
388
        new_entries = self.builder.new_inventory.iter_entries()
 
389
        basis_entries = self.basis_inv.iter_entries()
 
390
        new_path, new_root_ie = new_entries.next()
 
391
        basis_path, basis_root_ie = basis_entries.next()
 
392
 
 
393
        # This is a copy of InventoryEntry.__eq__ only leaving out .revision
 
394
        def ie_equal_no_revision(this, other):
 
395
            return ((this.file_id == other.file_id)
 
396
                    and (this.name == other.name)
 
397
                    and (this.symlink_target == other.symlink_target)
 
398
                    and (this.text_sha1 == other.text_sha1)
 
399
                    and (this.text_size == other.text_size)
 
400
                    and (this.text_id == other.text_id)
 
401
                    and (this.parent_id == other.parent_id)
 
402
                    and (this.kind == other.kind)
 
403
                    and (this.executable == other.executable)
 
404
                    and (this.reference_revision == other.reference_revision)
 
405
                    )
 
406
        if not ie_equal_no_revision(new_root_ie, basis_root_ie):
 
407
            return True
 
408
 
 
409
        for new_ie, basis_ie in zip(new_entries, basis_entries):
 
410
            if new_ie != basis_ie:
 
411
                return True
 
412
 
 
413
        # No actual changes present
 
414
        return False
 
415
 
346
416
    def _check_pointless(self):
347
417
        if self.allow_pointless:
348
418
            return
351
421
            return
352
422
        # work around the fact that a newly-initted tree does differ from its
353
423
        # basis
 
424
        if len(self.basis_inv) == 0 and len(self.builder.new_inventory) == 1:
 
425
            raise PointlessCommit()
 
426
        # Shortcut, if the number of entries changes, then we obviously have
 
427
        # a change
354
428
        if len(self.builder.new_inventory) != len(self.basis_inv):
355
429
            return
356
 
        if (len(self.builder.new_inventory) != 1 and
357
 
            self.builder.new_inventory != self.basis_inv):
 
430
        # If length == 1, then we only have the root entry. Which means
 
431
        # that there is no real difference (only the root could be different)
 
432
        if (len(self.builder.new_inventory) != 1 and self._any_real_changes()):
358
433
            return
359
434
        raise PointlessCommit()
360
435
 
388
463
        #       to local.
389
464
        
390
465
        # Make sure the local branch is identical to the master
391
 
        master_rh = self.master_branch.revision_history()
392
 
        local_rh = self.branch.revision_history()
393
 
        if local_rh != master_rh:
 
466
        master_info = self.master_branch.last_revision_info()
 
467
        local_info = self.branch.last_revision_info()
 
468
        if local_info != master_info:
394
469
            raise errors.BoundBranchOutOfDate(self.branch,
395
470
                    self.master_branch)
396
471
 
403
478
    def _cleanup(self):
404
479
        """Cleanup any open locks, progress bars etc."""
405
480
        cleanups = [self._cleanup_bound_branch,
 
481
                    self.basis_tree.unlock,
406
482
                    self.work_tree.unlock,
407
483
                    self.pb.finished]
408
484
        found_exception = None
458
534
        # TODO: Make sure that this list doesn't contain duplicate 
459
535
        # entries and the order is preserved when doing this.
460
536
        self.parents = self.work_tree.get_parent_ids()
461
 
        self.parent_invs = []
462
 
        for revision in self.parents:
 
537
        self.parent_invs = [self.basis_inv]
 
538
        for revision in self.parents[1:]:
463
539
            if self.branch.repository.has_revision(revision):
464
540
                mutter('commit parent revision {%s}', revision)
465
541
                inventory = self.branch.repository.get_inventory(revision)
508
584
        # in bugs like #46635.  Any reason not to use/enhance Tree.changes_from?
509
585
        # ADHB 11-07-2006
510
586
        mutter("Selecting files for commit with filter %s", self.specific_files)
 
587
        assert self.work_inv.root is not None
511
588
        entries = self.work_inv.iter_entries()
512
589
        if not self.builder.record_root_entry:
513
590
            symbol_versioning.warn('CommitBuilders should support recording'
519
596
        for path, new_ie in entries:
520
597
            self._emit_progress_update()
521
598
            file_id = new_ie.file_id
 
599
            try:
 
600
                kind = self.work_tree.kind(file_id)
 
601
                if kind == 'tree-reference' and self.recursive == 'down':
 
602
                    # nested tree: commit in it
 
603
                    sub_tree = WorkingTree.open(self.work_tree.abspath(path))
 
604
                    # FIXME: be more comprehensive here:
 
605
                    # this works when both trees are in --trees repository,
 
606
                    # but when both are bound to a different repository,
 
607
                    # it fails; a better way of approaching this is to 
 
608
                    # finally implement the explicit-caches approach design
 
609
                    # a while back - RBC 20070306.
 
610
                    if (sub_tree.branch.repository.bzrdir.root_transport.base
 
611
                        ==
 
612
                        self.work_tree.branch.repository.bzrdir.root_transport.base):
 
613
                        sub_tree.branch.repository = \
 
614
                            self.work_tree.branch.repository
 
615
                    try:
 
616
                        sub_tree.commit(message=None, revprops=self.revprops,
 
617
                            recursive=self.recursive,
 
618
                            message_callback=self.message_callback,
 
619
                            timestamp=self.timestamp, timezone=self.timezone,
 
620
                            committer=self.committer,
 
621
                            allow_pointless=self.allow_pointless,
 
622
                            strict=self.strict, verbose=self.verbose,
 
623
                            local=self.local, reporter=self.reporter)
 
624
                    except errors.PointlessCommit:
 
625
                        pass
 
626
                if kind != new_ie.kind:
 
627
                    new_ie = inventory.make_entry(kind, new_ie.name,
 
628
                                                  new_ie.parent_id, file_id)
 
629
            except errors.NoSuchFile:
 
630
                pass
522
631
            # mutter('check %s {%s}', path, file_id)
523
632
            if (not self.specific_files or 
524
633
                is_inside_or_parent_of_any(self.specific_files, path)):
532
641
                else:
533
642
                    # this entry is new and not being committed
534
643
                    continue
535
 
 
536
644
            self.builder.record_entry_contents(ie, self.parent_invs, 
537
645
                path, self.work_tree)
538
646
            # describe the nature of the change that has occurred relative to