~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: John Arbash Meinel
  • Date: 2007-06-07 22:31:44 UTC
  • mfrom: (2517 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2518.
  • Revision ID: john@arbash-meinel.com-20070607223144-u4oljlajcvq6by2n
[merge] bzr.dev 2517

Show diffs side-by-side

added added

removed removed

Lines of Context:
106
106
 
107
107
class ReportCommitToLog(NullCommitReporter):
108
108
 
109
 
    # this may be more useful if 'note' was replaced by an overridable
110
 
    # method on self, which would allow more trivial subclassing.
111
 
    # alternative, a callable could be passed in, allowing really trivial
112
 
    # reuse for some uis. RBC 20060511
 
109
    def _note(self, format, *args):
 
110
        """Output a message.
 
111
 
 
112
        Messages are output by writing directly to stderr instead of
 
113
        using bzrlib.trace.note(). The latter constantly updates the
 
114
        log file as we go causing an unnecessary performance hit.
 
115
 
 
116
        Subclasses may choose to override this method but need to be aware
 
117
        of its potential impact on performance.
 
118
        """
 
119
        bzrlib.ui.ui_factory.clear_term()
 
120
        sys.stderr.write((format + "\n") % args)
113
121
 
114
122
    def snapshot_change(self, change, path):
115
123
        if change == 'unchanged':
116
124
            return
117
125
        if change == 'added' and path == '':
118
126
            return
119
 
        note("%s %s", change, path)
 
127
        self._note("%s %s", change, path)
120
128
 
121
129
    def completed(self, revno, rev_id):
122
 
        note('Committed revision %d.', revno)
 
130
        self._note('Committed revision %d.', revno)
123
131
    
124
132
    def deleted(self, file_id):
125
 
        note('deleted %s', file_id)
 
133
        self._note('deleted %s', file_id)
126
134
 
127
135
    def escaped(self, escape_count, message):
128
 
        note("replaced %d control characters in message", escape_count)
 
136
        self._note("replaced %d control characters in message", escape_count)
129
137
 
130
138
    def missing(self, path):
131
 
        note('missing %s', path)
 
139
        self._note('missing %s', path)
132
140
 
133
141
    def renamed(self, change, old_path, new_path):
134
 
        note('%s %s => %s', change, old_path, new_path)
 
142
        self._note('%s %s => %s', change, old_path, new_path)
135
143
 
136
144
 
137
145
class Commit(object):
153
161
            self.reporter = reporter
154
162
        else:
155
163
            self.reporter = NullCommitReporter()
156
 
        if config is not None:
157
 
            self.config = config
158
 
        else:
159
 
            self.config = None
 
164
        self.config = config
160
165
        
161
166
    def commit(self,
162
167
               message=None,
177
182
               recursive='down'):
178
183
        """Commit working copy as a new revision.
179
184
 
180
 
        message -- the commit message (it or message_callback is required)
181
 
 
182
 
        timestamp -- if not None, seconds-since-epoch for a
183
 
             postdated/predated commit.
184
 
 
185
 
        specific_files -- If true, commit only those files.
186
 
 
187
 
        rev_id -- If set, use this as the new revision id.
 
185
        :param message: the commit message (it or message_callback is required)
 
186
 
 
187
        :param timestamp: if not None, seconds-since-epoch for a
 
188
            postdated/predated commit.
 
189
 
 
190
        :param specific_files: If true, commit only those files.
 
191
 
 
192
        :param rev_id: If set, use this as the new revision id.
188
193
            Useful for test or import commands that need to tightly
189
194
            control what revisions are assigned.  If you duplicate
190
195
            a revision id that exists elsewhere it is your own fault.
191
196
            If null (default), a time/random revision id is generated.
192
197
 
193
 
        allow_pointless -- If true (default), commit even if nothing
 
198
        :param allow_pointless: If true (default), commit even if nothing
194
199
            has changed and no merges are recorded.
195
200
 
196
 
        strict -- If true, don't allow a commit if the working tree
 
201
        :param strict: If true, don't allow a commit if the working tree
197
202
            contains unknown files.
198
203
 
199
 
        revprops -- Properties for new revision
 
204
        :param revprops: Properties for new revision
200
205
        :param local: Perform a local only commit.
201
206
        :param recursive: If set to 'down', commit in any subtrees that have
202
207
            pending changes of any sort during this commit.
235
240
        self.committer = committer
236
241
        self.strict = strict
237
242
        self.verbose = verbose
238
 
        self.local = local
239
243
 
240
244
        if reporter is None and self.reporter is None:
241
245
            self.reporter = NullCommitReporter()
248
252
        self.basis_tree.lock_read()
249
253
        try:
250
254
            # Cannot commit with conflicts present.
251
 
            if len(self.work_tree.conflicts())>0:
 
255
            if len(self.work_tree.conflicts()) > 0:
252
256
                raise ConflictsInTree
253
257
 
254
 
            # setup the bound branch variables as needed.
 
258
            # Setup the bound branch variables as needed.
255
259
            self._check_bound_branch()
256
260
 
257
 
            # check for out of date working trees
258
 
            try:
259
 
                first_tree_parent = self.work_tree.get_parent_ids()[0]
260
 
            except IndexError:
261
 
                # if there are no parents, treat our parent as 'None'
262
 
                # this is so that we still consier the master branch
263
 
                # - in a checkout scenario the tree may have no
264
 
                # parents but the branch may do.
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
            # Check that the working tree is up to date
 
262
            old_revno,new_revno = self._check_out_of_date_tree()
 
263
 
275
264
            if strict:
276
265
                # raise an exception as soon as we find a single unknown.
277
266
                for unknown in self.work_tree.unknowns():
289
278
                # cheaply?
290
279
                tree.find_ids_across_trees(specific_files,
291
280
                                           [self.basis_tree, self.work_tree])
 
281
 
 
282
            # Setup the progress bar ...
292
283
            # one to finish, one for rev and inventory, and one for each
293
284
            # inventory entry, and the same for the new inventory.
294
285
            # note that this estimate is too long when we do a partial tree
299
290
 
300
291
            self._gather_parents()
301
292
            if len(self.parents) > 1 and self.specific_files:
302
 
                raise NotImplementedError('selected-file commit of merges is not supported yet: files %r',
303
 
                        self.specific_files)
 
293
                raise errors.CannotCommitSelectedFileMerge(self.specific_files)
304
294
            
 
295
            # Build the new inventory
305
296
            self.builder = self.branch.get_commit_builder(self.parents,
306
297
                self.config, timestamp, timezone, committer, revprops, rev_id)
307
 
            
308
298
            self._remove_deleted()
309
299
            self._populate_new_inv()
310
300
            self._report_deletes()
311
 
 
312
301
            self._check_pointless()
 
302
            self._emit_progress_update()
313
303
 
314
 
            self._emit_progress_update()
315
304
            # TODO: Now the new inventory is known, check for conflicts and
316
305
            # prompt the user for a commit message.
317
306
            # ADHB 2006-08-08: If this is done, populate_new_inv should not add
324
313
            self.message = message
325
314
            self._escape_commit_message()
326
315
 
 
316
            # Add revision data to the local branch
327
317
            self.rev_id = self.builder.commit(self.message)
328
318
            self._emit_progress_update()
329
 
            # revision data is in the local branch now.
330
319
            
331
320
            # upload revision data to the master.
332
321
            # this will propagate merged revisions too if needed.
334
323
                self.master_branch.repository.fetch(self.branch.repository,
335
324
                                                    revision_id=self.rev_id)
336
325
                # now the master has the revision data
337
 
                # 'commit' to the master first so a timeout here causes the local
338
 
                # branch to be out of date
 
326
                # 'commit' to the master first so a timeout here causes the
 
327
                # local branch to be out of date
339
328
                self.master_branch.set_last_revision_info(new_revno,
340
329
                                                          self.rev_id)
341
330
 
342
331
            # and now do the commit locally.
343
332
            self.branch.set_last_revision_info(new_revno, self.rev_id)
344
333
 
 
334
            # Make the working tree up to date with the branch
345
335
            rev_tree = self.builder.revision_tree()
346
336
            self.work_tree.set_parent_trees([(self.rev_id, rev_tree)])
347
 
            # now the work tree is up to date with the branch
348
 
            
349
337
            self.reporter.completed(new_revno, self.rev_id)
350
 
            # old style commit hooks - should be deprecated ? (obsoleted in
351
 
            # 0.15)
352
 
            if self.config.post_commit() is not None:
353
 
                hooks = self.config.post_commit().split(' ')
354
 
                # this would be nicer with twisted.python.reflect.namedAny
355
 
                for hook in hooks:
356
 
                    result = eval(hook + '(branch, rev_id)',
357
 
                                  {'branch':self.branch,
358
 
                                   'bzrlib':bzrlib,
359
 
                                   'rev_id':self.rev_id})
360
 
            # new style commit hooks:
361
 
            if not self.bound_branch:
362
 
                hook_master = self.branch
363
 
                hook_local = None
364
 
            else:
365
 
                hook_master = self.master_branch
366
 
                hook_local = self.branch
367
 
            # With bound branches, when the master is behind the local branch,
368
 
            # the 'old_revno' and old_revid values here are incorrect.
369
 
            # XXX: FIXME ^. RBC 20060206
370
 
            if self.parents:
371
 
                old_revid = self.parents[0]
372
 
            else:
373
 
                old_revid = bzrlib.revision.NULL_REVISION
374
 
            for hook in Branch.hooks['post_commit']:
375
 
                hook(hook_local, hook_master, old_revno, old_revid, new_revno,
376
 
                    self.rev_id)
 
338
 
 
339
            # Process the post commit hooks, if any
 
340
            self._process_hooks(old_revno, new_revno)
377
341
            self._emit_progress_update()
378
342
        finally:
379
343
            self._cleanup()
476
440
        self.master_branch.lock_write()
477
441
        self.master_locked = True
478
442
 
 
443
    def _check_out_of_date_tree(self):
 
444
        """Check that the working tree is up to date.
 
445
 
 
446
        :return: old_revision_number,new_revision_number tuple
 
447
        """
 
448
        try:
 
449
            first_tree_parent = self.work_tree.get_parent_ids()[0]
 
450
        except IndexError:
 
451
            # if there are no parents, treat our parent as 'None'
 
452
            # this is so that we still consider the master branch
 
453
            # - in a checkout scenario the tree may have no
 
454
            # parents but the branch may do.
 
455
            first_tree_parent = bzrlib.revision.NULL_REVISION
 
456
        old_revno, master_last = self.master_branch.last_revision_info()
 
457
        if master_last != first_tree_parent:
 
458
            if master_last != bzrlib.revision.NULL_REVISION:
 
459
                raise errors.OutOfDateTree(self.work_tree)
 
460
        if self.branch.repository.has_revision(first_tree_parent):
 
461
            new_revno = old_revno + 1
 
462
        else:
 
463
            # ghost parents never appear in revision history.
 
464
            new_revno = 1
 
465
        return old_revno,new_revno
 
466
 
 
467
    def _process_hooks(self, old_revno, new_revno):
 
468
        """Process any registered commit hooks."""
 
469
        # old style commit hooks - should be deprecated ? (obsoleted in
 
470
        # 0.15)
 
471
        if self.config.post_commit() is not None:
 
472
            hooks = self.config.post_commit().split(' ')
 
473
            # this would be nicer with twisted.python.reflect.namedAny
 
474
            for hook in hooks:
 
475
                result = eval(hook + '(branch, rev_id)',
 
476
                              {'branch':self.branch,
 
477
                               'bzrlib':bzrlib,
 
478
                               'rev_id':self.rev_id})
 
479
        # new style commit hooks:
 
480
        if not self.bound_branch:
 
481
            hook_master = self.branch
 
482
            hook_local = None
 
483
        else:
 
484
            hook_master = self.master_branch
 
485
            hook_local = self.branch
 
486
        # With bound branches, when the master is behind the local branch,
 
487
        # the 'old_revno' and old_revid values here are incorrect.
 
488
        # XXX: FIXME ^. RBC 20060206
 
489
        if self.parents:
 
490
            old_revid = self.parents[0]
 
491
        else:
 
492
            old_revid = bzrlib.revision.NULL_REVISION
 
493
        for hook in Branch.hooks['post_commit']:
 
494
            hook(hook_local, hook_master, old_revno, old_revid, new_revno,
 
495
                self.rev_id)
 
496
 
479
497
    def _cleanup(self):
480
498
        """Cleanup any open locks, progress bars etc."""
481
499
        cleanups = [self._cleanup_bound_branch,