~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: 2010-01-20 17:19:44 UTC
  • mfrom: (4973.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100120171944-8gkub20gotjcye67
(spiv) Implement a per-file merge hook

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
49
49
# TODO: Change the parameter 'rev_id' to 'revision_id' to be consistent with
50
50
# the rest of the code; add a deprecation of the old name.
51
51
 
 
52
import os
 
53
import re
 
54
import sys
 
55
import time
 
56
 
 
57
from cStringIO import StringIO
 
58
 
52
59
from bzrlib import (
53
60
    debug,
54
61
    errors,
 
62
    revision,
55
63
    trace,
56
64
    tree,
57
 
    ui,
 
65
    xml_serializer,
58
66
    )
59
67
from bzrlib.branch import Branch
60
68
from bzrlib.cleanup import OperationWithCleanups
64
72
                           StrictCommitFailed
65
73
                           )
66
74
from bzrlib.osutils import (get_user_encoding,
67
 
                            is_inside_any,
 
75
                            kind_marker, isdir,isfile, is_inside_any,
 
76
                            is_inside_or_parent_of_any,
68
77
                            minimum_path_selection,
 
78
                            quotefn, sha_file, split_lines,
69
79
                            splitpath,
70
80
                            )
71
 
from bzrlib.trace import mutter, note, is_quiet
 
81
from bzrlib.testament import Testament
 
82
from bzrlib.trace import mutter, note, warning, is_quiet
72
83
from bzrlib.inventory import Inventory, InventoryEntry, make_entry
73
84
from bzrlib import symbol_versioning
 
85
from bzrlib.symbol_versioning import (deprecated_passed,
 
86
        deprecated_function,
 
87
        DEPRECATED_PARAMETER)
 
88
from bzrlib.workingtree import WorkingTree
74
89
from bzrlib.urlutils import unescape_for_display
 
90
import bzrlib.ui
75
91
 
76
92
 
77
93
class NullCommitReporter(object):
131
147
 
132
148
    def completed(self, revno, rev_id):
133
149
        self._note('Committed revision %d.', revno)
134
 
        # self._note goes to the console too; so while we want to log the
135
 
        # rev_id, we can't trivially only log it. (See bug 526425). Long
136
 
        # term we should rearrange the reporting structure, but for now
137
 
        # we just mutter seperately. We mutter the revid and revno together
138
 
        # so that concurrent bzr invocations won't lead to confusion.
139
 
        mutter('Committed revid %s as revno %d.', rev_id, revno)
140
150
 
141
151
    def deleted(self, path):
142
152
        self._note('deleted %s', path)
173
183
        self.reporter = reporter
174
184
        self.config = config
175
185
 
176
 
    @staticmethod
177
 
    def update_revprops(revprops, branch, authors=None, author=None,
178
 
                        local=False, possible_master_transports=None):
179
 
        if revprops is None:
180
 
            revprops = {}
181
 
        if possible_master_transports is None:
182
 
            possible_master_transports = []
183
 
        if not 'branch-nick' in revprops:
184
 
            revprops['branch-nick'] = branch._get_nick(
185
 
                local,
186
 
                possible_master_transports)
187
 
        if authors is not None:
188
 
            if author is not None:
189
 
                raise AssertionError('Specifying both author and authors '
190
 
                        'is not allowed. Specify just authors instead')
191
 
            if 'author' in revprops or 'authors' in revprops:
192
 
                # XXX: maybe we should just accept one of them?
193
 
                raise AssertionError('author property given twice')
194
 
            if authors:
195
 
                for individual in authors:
196
 
                    if '\n' in individual:
197
 
                        raise AssertionError('\\n is not a valid character '
198
 
                                'in an author identity')
199
 
                revprops['authors'] = '\n'.join(authors)
200
 
        if author is not None:
201
 
            symbol_versioning.warn('The parameter author was deprecated'
202
 
                   ' in version 1.13. Use authors instead',
203
 
                   DeprecationWarning)
204
 
            if 'author' in revprops or 'authors' in revprops:
205
 
                # XXX: maybe we should just accept one of them?
206
 
                raise AssertionError('author property given twice')
207
 
            if '\n' in author:
208
 
                raise AssertionError('\\n is not a valid character '
209
 
                        'in an author identity')
210
 
            revprops['authors'] = author
211
 
        return revprops
212
 
 
213
186
    def commit(self,
214
187
               message=None,
215
188
               timestamp=None,
228
201
               message_callback=None,
229
202
               recursive='down',
230
203
               exclude=None,
231
 
               possible_master_transports=None,
232
 
               lossy=False):
 
204
               possible_master_transports=None):
233
205
        """Commit working copy as a new revision.
234
206
 
235
207
        :param message: the commit message (it or message_callback is required)
262
234
        :param exclude: None or a list of relative paths to exclude from the
263
235
            commit. Pending changes to excluded files will be ignored by the
264
236
            commit.
265
 
        :param lossy: When committing to a foreign VCS, ignore any
266
 
            data that can not be natively represented.
267
237
        """
268
238
        operation = OperationWithCleanups(self._commit)
269
 
        self.revprops = revprops or {}
270
 
        # XXX: Can be set on __init__ or passed in - this is a bit ugly.
271
 
        self.config = config or self.config
272
239
        return operation.run(
273
240
               message=message,
274
241
               timestamp=timestamp,
279
246
               allow_pointless=allow_pointless,
280
247
               strict=strict,
281
248
               verbose=verbose,
 
249
               revprops=revprops,
282
250
               working_tree=working_tree,
283
251
               local=local,
284
252
               reporter=reporter,
 
253
               config=config,
285
254
               message_callback=message_callback,
286
255
               recursive=recursive,
287
256
               exclude=exclude,
288
 
               possible_master_transports=possible_master_transports,
289
 
               lossy=lossy)
 
257
               possible_master_transports=possible_master_transports)
290
258
 
291
259
    def _commit(self, operation, message, timestamp, timezone, committer,
292
 
            specific_files, rev_id, allow_pointless, strict, verbose,
293
 
            working_tree, local, reporter, message_callback, recursive,
294
 
            exclude, possible_master_transports, lossy):
 
260
            specific_files, rev_id, allow_pointless, strict, verbose, revprops,
 
261
            working_tree, local, reporter, config, message_callback, recursive,
 
262
            exclude, possible_master_transports):
295
263
        mutter('preparing to commit')
296
264
 
297
265
        if working_tree is None:
329
297
                minimum_path_selection(specific_files))
330
298
        else:
331
299
            self.specific_files = None
332
 
 
 
300
            
333
301
        self.allow_pointless = allow_pointless
 
302
        self.revprops = revprops
334
303
        self.message_callback = message_callback
335
304
        self.timestamp = timestamp
336
305
        self.timezone = timezone
349
318
            not self.branch.repository._format.supports_tree_reference and
350
319
            (self.branch.repository._format.fast_deltas or
351
320
             len(self.parents) < 2))
352
 
        self.pb = ui.ui_factory.nested_progress_bar()
 
321
        self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
353
322
        operation.add_cleanup(self.pb.finished)
354
323
        self.basis_revid = self.work_tree.last_revision()
355
324
        self.basis_tree = self.work_tree.basis_tree()
383
352
        self.pb_stage_count = 0
384
353
        self.pb_stage_total = 5
385
354
        if self.bound_branch:
386
 
            # 2 extra stages: "Uploading data to master branch" and "Merging
387
 
            # tags to master branch"
388
 
            self.pb_stage_total += 2
 
355
            self.pb_stage_total += 1
389
356
        self.pb.show_pct = False
390
357
        self.pb.show_spinner = False
391
358
        self.pb.show_eta = False
403
370
 
404
371
        # Collect the changes
405
372
        self._set_progress_stage("Collecting changes", counter=True)
406
 
        self._lossy = lossy
407
373
        self.builder = self.branch.get_commit_builder(self.parents,
408
 
            self.config, timestamp, timezone, committer, self.revprops,
409
 
            rev_id, lossy=lossy)
410
 
        if not self.builder.supports_record_entry_contents and self.exclude:
411
 
            self.builder.abort()
412
 
            raise errors.ExcludesUnsupported(self.branch.repository)
 
374
            self.config, timestamp, timezone, committer, revprops, rev_id)
413
375
 
414
376
        try:
415
377
            self.builder.will_record_deletes()
442
404
        except Exception, e:
443
405
            mutter("aborting commit write group because of exception:")
444
406
            trace.log_exception_quietly()
 
407
            note("aborting commit write group: %r" % (e,))
445
408
            self.builder.abort()
446
409
            raise
447
410
 
453
416
            self._set_progress_stage("Uploading data to master branch")
454
417
            # 'commit' to the master first so a timeout here causes the
455
418
            # local branch to be out of date
456
 
            (new_revno, self.rev_id) = self.master_branch.import_last_revision_info_and_tags(
457
 
                self.branch, new_revno, self.rev_id, lossy=lossy)
458
 
            if lossy:
459
 
                self.branch.fetch(self.master_branch, self.rev_id)
 
419
            self.master_branch.import_last_revision_info(
 
420
                self.branch.repository, new_revno, self.rev_id)
460
421
 
461
422
        # and now do the commit locally.
462
423
        self.branch.set_last_revision_info(new_revno, self.rev_id)
463
424
 
464
 
        # Merge local tags to remote
465
 
        if self.bound_branch:
466
 
            self._set_progress_stage("Merging tags to master branch")
467
 
            tag_conflicts = self.branch.tags.merge_to(self.master_branch.tags)
468
 
            if tag_conflicts:
469
 
                warning_lines = ['    ' + name for name, _, _ in tag_conflicts]
470
 
                note("Conflicting tags in bound branch:\n" +
471
 
                    "\n".join(warning_lines))
472
 
 
473
425
        # Make the working tree be up to date with the branch. This
474
426
        # includes automatic changes scheduled to be made to the tree, such
475
427
        # as updating its basis and unversioning paths that were missing.
493
445
        # A merge with no effect on files
494
446
        if len(self.parents) > 1:
495
447
            return
 
448
        # TODO: we could simplify this by using self.builder.basis_delta.
 
449
 
 
450
        # The initial commit adds a root directory, but this in itself is not
 
451
        # a worthwhile commit.
 
452
        if (self.basis_revid == revision.NULL_REVISION and
 
453
            ((self.builder.new_inventory is not None and
 
454
             len(self.builder.new_inventory) == 1) or
 
455
            len(self.builder._basis_delta) == 1)):
 
456
            raise PointlessCommit()
496
457
        if self.builder.any_changes():
497
458
            return
498
459
        raise PointlessCommit()
723
684
        if self.specific_files or self.exclude:
724
685
            specific_files = self.specific_files or []
725
686
            for path, old_ie in self.basis_inv.iter_entries():
726
 
                if self.builder.new_inventory.has_id(old_ie.file_id):
 
687
                if old_ie.file_id in self.builder.new_inventory:
727
688
                    # already added - skip.
728
689
                    continue
729
690
                if (is_inside_any(specific_files, path)