~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Robert Collins
  • Date: 2005-10-04 04:49:21 UTC
  • Revision ID: robertc@robertcollins.net-20051004044921-45fd2dc3f70abe59
remove debug print statement

Show diffs side-by-side

added added

removed removed

Lines of Context:
44
44
# TODO: Update hashcache before and after - or does the WorkingTree
45
45
# look after that?
46
46
 
 
47
# This code requires all merge parents to be present in the branch.
 
48
# We could relax this but for the sake of simplicity the constraint is
 
49
# here for now.  It's not totally clear to me how we'd know which file
 
50
# need new text versions if some parents are absent.  -- mbp 20050915
 
51
 
47
52
# TODO: Rather than mashing together the ancestry and storing it back,
48
53
# perhaps the weave should have single method which does it all in one
49
54
# go, avoiding a lot of redundant work.
74
79
from bzrlib.branch import gen_file_id
75
80
from bzrlib.errors import (BzrError, PointlessCommit,
76
81
                           HistoryMissing,
77
 
                           ConflictsInTree
78
82
                           )
79
83
from bzrlib.revision import Revision
80
84
from bzrlib.trace import mutter, note, warning
81
85
from bzrlib.xml5 import serializer_v5
82
 
from bzrlib.inventory import Inventory, ROOT_ID
 
86
from bzrlib.inventory import Inventory
83
87
from bzrlib.weave import Weave
84
88
from bzrlib.weavefile import read_weave, write_weave_v5
85
89
from bzrlib.atomicfile import AtomicFile
98
102
 
99
103
class NullCommitReporter(object):
100
104
    """I report on progress of a commit."""
101
 
 
102
 
    def snapshot_change(self, change, path):
103
 
        pass
104
 
 
105
 
    def completed(self, revno, rev_id):
106
 
        pass
107
 
 
108
 
    def deleted(self, file_id):
109
 
        pass
110
 
 
111
 
    def escaped(self, escape_count, message):
112
 
        pass
113
 
 
114
 
    def missing(self, path):
115
 
        pass
 
105
    def added(self, path):
 
106
        pass
 
107
 
 
108
    def removed(self, path):
 
109
        pass
 
110
 
 
111
    def renamed(self, old_path, new_path):
 
112
        pass
 
113
 
116
114
 
117
115
class ReportCommitToLog(NullCommitReporter):
118
 
 
119
 
    def snapshot_change(self, change, path):
120
 
        note("%s %s", change, path)
121
 
 
122
 
    def completed(self, revno, rev_id):
123
 
        note('committed r%d {%s}', revno, rev_id)
124
 
    
125
 
    def deleted(self, file_id):
126
 
        note('deleted %s', file_id)
127
 
 
128
 
    def escaped(self, escape_count, message):
129
 
        note("replaced %d control characters in message", escape_count)
130
 
 
131
 
    def missing(self, path):
132
 
        note('missing %s', path)
 
116
    def added(self, path):
 
117
        note('added %s', path)
 
118
 
 
119
    def removed(self, path):
 
120
        note('removed %s', path)
 
121
 
 
122
    def renamed(self, old_path, new_path):
 
123
        note('renamed %s => %s', old_path, new_path)
 
124
 
133
125
 
134
126
class Commit(object):
135
127
    """Task of committing a new revision.
231
223
                    or self.new_inv != self.basis_inv):
232
224
                raise PointlessCommit()
233
225
 
234
 
            if len(list(self.work_tree.iter_conflicts()))>0:
235
 
                raise ConflictsInTree
236
 
 
237
226
            self._record_inventory()
 
227
            self._record_ancestry()
238
228
            self._make_revision()
239
 
            self.reporter.completed(self.branch.revno()+1, self.rev_id)
 
229
            note('committed r%d {%s}', (self.branch.revno() + 1),
 
230
                 self.rev_id)
240
231
            self.branch.append_revision(self.rev_id)
241
232
            self.branch.set_pending_merges([])
242
233
        finally:
267
258
            lambda match: match.group(0).encode('unicode_escape'),
268
259
            self.message)
269
260
        if escape_count:
270
 
            self.reporter.escaped(escape_count, self.message)
 
261
            note("replaced %d control characters in message", escape_count)
 
262
 
 
263
    def _record_ancestry(self):
 
264
        """Append merged revision ancestry to the ancestry file.
 
265
 
 
266
        This should be the merged ancestry of all parents, plus the
 
267
        new revision id."""
 
268
        s = self.branch.control_weaves
 
269
        w = s.get_weave_or_empty('ancestry')
 
270
        lines = self._make_ancestry(w)
 
271
        w.add(self.rev_id, self.present_parents, lines)
 
272
        s.put_weave('ancestry', w)
 
273
 
 
274
    def _make_ancestry(self, ancestry_weave):
 
275
        """Return merged ancestry lines.
 
276
 
 
277
        The lines are revision-ids followed by newlines."""
 
278
        parent_ancestries = [ancestry_weave.get(p) for p in self.present_parents]
 
279
        new_lines = merge_ancestry_lines(self.rev_id, parent_ancestries)
 
280
        mutter('merged ancestry of {%s}:\n%s', self.rev_id, ''.join(new_lines))
 
281
        return new_lines
271
282
 
272
283
    def _gather_parents(self):
273
284
        """Record the parents of a merge for merge detection."""
274
285
        pending_merges = self.branch.pending_merges()
275
286
        self.parents = []
276
 
        self.parent_invs = []
 
287
        self.parent_trees = []
277
288
        self.present_parents = []
278
289
        precursor_id = self.branch.last_revision()
279
290
        if precursor_id:
281
292
        self.parents += pending_merges
282
293
        for revision in self.parents:
283
294
            if self.branch.has_revision(revision):
284
 
                self.parent_invs.append(self.branch.get_inventory(revision))
 
295
                self.parent_trees.append(self.branch.revision_tree(revision))
285
296
                self.present_parents.append(revision)
286
297
 
287
298
    def _check_parents_present(self):
289
300
            mutter('commit parent revision {%s}', parent_id)
290
301
            if not self.branch.has_revision(parent_id):
291
302
                if parent_id == self.branch.last_revision():
292
 
                    warning("parent is missing %r", parent_id)
 
303
                    warning("parent is pissing %r", parent_id)
293
304
                    raise HistoryMissing(self.branch, 'revision', parent_id)
294
305
                else:
295
306
                    mutter("commit will ghost revision %r", parent_id)
309
320
        self.branch.revision_store.add(rev_tmp, self.rev_id)
310
321
        mutter('new revision_id is {%s}', self.rev_id)
311
322
 
 
323
 
312
324
    def _remove_deleted(self):
313
325
        """Remove deleted files from the working inventories.
314
326
 
326
338
            if specific and not is_inside_any(specific, path):
327
339
                continue
328
340
            if not self.work_tree.has_filename(path):
329
 
                self.reporter.missing(path)
330
 
                deleted_ids.append((path, ie.file_id))
 
341
                note('missing %s', path)
 
342
                deleted_ids.append(ie.file_id)
331
343
        if deleted_ids:
332
 
            deleted_ids.sort(reverse=True)
333
 
            for path, file_id in deleted_ids:
334
 
                del self.work_inv[file_id]
 
344
            for file_id in deleted_ids:
 
345
                if file_id in self.work_inv:
 
346
                    del self.work_inv[file_id]
335
347
            self.branch._write_inventory(self.work_inv)
336
348
 
 
349
 
 
350
    def _find_entry_parents(self, file_id):
 
351
        """Return the text versions and hashes for all file parents.
 
352
 
 
353
        Returned as a map from text version to inventory entry.
 
354
 
 
355
        This is a set containing the file versions in all parents
 
356
        revisions containing the file.  If the file is new, the set
 
357
        will be empty."""
 
358
        r = {}
 
359
        for tree in self.parent_trees:
 
360
            if file_id in tree.inventory:
 
361
                ie = tree.inventory[file_id]
 
362
                assert ie.file_id == file_id
 
363
                if ie.revision in r:
 
364
                    assert r[ie.revision] == ie
 
365
                else:
 
366
                    r[ie.revision] = ie
 
367
        return r
 
368
 
337
369
    def _store_snapshot(self):
338
370
        """Pass over inventory and record a snapshot.
339
371
 
340
372
        Entries get a new revision when they are modified in 
341
373
        any way, which includes a merge with a new set of
342
 
        parents that have the same entry. 
 
374
        parents that have the same entry. Currently we do not
 
375
        check for that set being ancestors of each other - and
 
376
        we should - only parallel children should count for this
 
377
        test see find_entry_parents to correct this. FIXME <---
 
378
        I.e. if we are merging in revision FOO, and our
 
379
        copy of file id BAR is identical to FOO.BAR, we should
 
380
        generate a new revision of BAR IF and only IF FOO is
 
381
        neither a child of our current tip, nor an ancestor of
 
382
        our tip. The presence of FOO in our store should not 
 
383
        affect this logic UNLESS we are doing a merge of FOO,
 
384
        or a child of FOO.
343
385
        """
344
386
        # XXX: Need to think more here about when the user has
345
387
        # made a specific decision on a particular value -- c.f.
346
388
        # mark-merge.  
347
389
        for path, ie in self.new_inv.iter_entries():
348
 
            previous_entries = ie.find_previous_heads(
349
 
                self.parent_invs, 
350
 
                self.weave_store.get_weave_or_empty(ie.file_id))
 
390
            previous_entries = self._find_entry_parents(ie. file_id)
351
391
            if ie.revision is None:
352
392
                change = ie.snapshot(self.rev_id, path, previous_entries,
353
393
                                     self.work_tree, self.weave_store)
354
394
            else:
355
395
                change = "unchanged"
356
 
            self.reporter.snapshot_change(change, path)
 
396
            note("%s %s", change, path)
357
397
 
358
398
    def _populate_new_inv(self):
359
399
        """Build revision inventory.
373
413
            if self.specific_files:
374
414
                if not is_inside_any(self.specific_files, path):
375
415
                    mutter('%s not selected for commit', path)
376
 
                    self._carry_entry(file_id)
 
416
                    self._carry_file(file_id)
377
417
                    continue
378
 
                else:
379
 
                    # this is selected, ensure its parents are too.
380
 
                    parent_id = new_ie.parent_id
381
 
                    while parent_id != ROOT_ID:
382
 
                        if not self.new_inv.has_id(parent_id):
383
 
                            ie = self._select_entry(self.work_inv[parent_id])
384
 
                            mutter('%s selected for commit because of %s',
385
 
                                   self.new_inv.id2path(parent_id), path)
386
 
 
387
 
                        ie = self.new_inv[parent_id]
388
 
                        if ie.revision is not None:
389
 
                            ie.revision = None
390
 
                            mutter('%s selected for commit because of %s',
391
 
                                   self.new_inv.id2path(parent_id), path)
392
 
                        parent_id = ie.parent_id
393
418
            mutter('%s selected for commit', path)
394
 
            self._select_entry(new_ie)
395
 
 
396
 
    def _select_entry(self, new_ie):
397
 
        """Make new_ie be considered for committing."""
398
 
        ie = new_ie.copy()
399
 
        ie.revision = None
400
 
        self.new_inv.add(ie)
401
 
        return ie
402
 
 
403
 
    def _carry_entry(self, file_id):
 
419
            ie = new_ie.copy()
 
420
            ie.revision = None
 
421
            self.new_inv.add(ie)
 
422
 
 
423
    def _carry_file(self, file_id):
404
424
        """Carry the file unchanged from the basis revision."""
405
425
        if self.basis_inv.has_id(file_id):
406
426
            self.new_inv.add(self.basis_inv[file_id].copy())
408
428
    def _report_deletes(self):
409
429
        for file_id in self.basis_inv:
410
430
            if file_id not in self.new_inv:
411
 
                self.reporter.deleted(self.basis_inv.id2path(file_id))
 
431
                note('deleted %s', self.basis_inv.id2path(file_id))
 
432
 
 
433
 
412
434
 
413
435
def _gen_revision_id(branch, when):
414
436
    """Return new revision-id."""
415
437
    s = '%s-%s-' % (user_email(branch), compact_date(when))
416
438
    s += hexlify(rand_bytes(8))
417
439
    return s
 
440
 
 
441
 
 
442
 
 
443
    
 
444
def merge_ancestry_lines(rev_id, ancestries):
 
445
    """Return merged ancestry lines.
 
446
 
 
447
    rev_id -- id of the new revision
 
448
    
 
449
    ancestries -- a sequence of ancestries for parent revisions,
 
450
        as newline-terminated line lists.
 
451
    """
 
452
    if len(ancestries) == 0:
 
453
        return [rev_id + '\n']
 
454
    seen = set(ancestries[0])
 
455
    ancs = ancestries[0][:]    
 
456
    for parent_ancestry in ancestries[1:]:
 
457
        for line in parent_ancestry:
 
458
            assert line[-1] == '\n'
 
459
            if line not in seen:
 
460
                ancs.append(line)
 
461
                seen.add(line)
 
462
    r = rev_id + '\n'
 
463
    assert r not in seen
 
464
    ancs.append(r)
 
465
    return ancs