~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Robert Collins
  • Date: 2005-10-06 00:52:53 UTC
  • Revision ID: robertc@robertcollins.net-20051006005253-415c38ad22094f13
define some expected behaviour for inventory_entry.snapshot

Show diffs side-by-side

added added

removed removed

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