~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/commit.py

  • Committer: Robert Collins
  • Date: 2005-10-07 01:01:07 UTC
  • mfrom: (1185.13.2)
  • Revision ID: robertc@robertcollins.net-20051007010107-fe48434051a15b92
mergeĀ fromĀ upstream

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
 
 
52
47
# TODO: Rather than mashing together the ancestry and storing it back,
53
48
# perhaps the weave should have single method which does it all in one
54
49
# go, avoiding a lot of redundant work.
79
74
from bzrlib.branch import gen_file_id
80
75
from bzrlib.errors import (BzrError, PointlessCommit,
81
76
                           HistoryMissing,
 
77
                           ConflictsInTree
82
78
                           )
83
79
from bzrlib.revision import Revision
84
80
from bzrlib.trace import mutter, note, warning
85
81
from bzrlib.xml5 import serializer_v5
86
 
from bzrlib.inventory import Inventory
 
82
from bzrlib.inventory import Inventory, ROOT_ID
87
83
from bzrlib.weave import Weave
88
84
from bzrlib.weavefile import read_weave, write_weave_v5
89
85
from bzrlib.atomicfile import AtomicFile
102
98
 
103
99
class NullCommitReporter(object):
104
100
    """I report on progress of a commit."""
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
 
 
 
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
114
116
 
115
117
class ReportCommitToLog(NullCommitReporter):
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
 
 
 
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)
125
133
 
126
134
class Commit(object):
127
135
    """Task of committing a new revision.
223
231
                    or self.new_inv != self.basis_inv):
224
232
                raise PointlessCommit()
225
233
 
 
234
            if len(list(self.work_tree.iter_conflicts()))>0:
 
235
                raise ConflictsInTree
 
236
 
226
237
            self._record_inventory()
227
 
            self._record_ancestry()
228
238
            self._make_revision()
229
 
            note('committed r%d {%s}', (self.branch.revno() + 1),
230
 
                 self.rev_id)
 
239
            self.reporter.completed(self.branch.revno()+1, self.rev_id)
231
240
            self.branch.append_revision(self.rev_id)
232
241
            self.branch.set_pending_merges([])
233
242
        finally:
258
267
            lambda match: match.group(0).encode('unicode_escape'),
259
268
            self.message)
260
269
        if escape_count:
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
 
270
            self.reporter.escaped(escape_count, self.message)
282
271
 
283
272
    def _gather_parents(self):
284
273
        """Record the parents of a merge for merge detection."""
285
274
        pending_merges = self.branch.pending_merges()
286
275
        self.parents = []
287
 
        self.parent_trees = []
 
276
        self.parent_invs = []
288
277
        self.present_parents = []
289
278
        precursor_id = self.branch.last_revision()
290
279
        if precursor_id:
292
281
        self.parents += pending_merges
293
282
        for revision in self.parents:
294
283
            if self.branch.has_revision(revision):
295
 
                self.parent_trees.append(self.branch.revision_tree(revision))
 
284
                self.parent_invs.append(self.branch.get_inventory(revision))
296
285
                self.present_parents.append(revision)
297
286
 
298
287
    def _check_parents_present(self):
300
289
            mutter('commit parent revision {%s}', parent_id)
301
290
            if not self.branch.has_revision(parent_id):
302
291
                if parent_id == self.branch.last_revision():
303
 
                    warning("parent is pissing %r", parent_id)
 
292
                    warning("parent is missing %r", parent_id)
304
293
                    raise HistoryMissing(self.branch, 'revision', parent_id)
305
294
                else:
306
295
                    mutter("commit will ghost revision %r", parent_id)
320
309
        self.branch.revision_store.add(rev_tmp, self.rev_id)
321
310
        mutter('new revision_id is {%s}', self.rev_id)
322
311
 
323
 
 
324
312
    def _remove_deleted(self):
325
313
        """Remove deleted files from the working inventories.
326
314
 
338
326
            if specific and not is_inside_any(specific, path):
339
327
                continue
340
328
            if not self.work_tree.has_filename(path):
341
 
                note('missing %s', path)
 
329
                self.reporter.missing(path)
342
330
                deleted_ids.append((path, ie.file_id))
343
331
        if deleted_ids:
344
332
            deleted_ids.sort(reverse=True)
346
334
                del self.work_inv[file_id]
347
335
            self.branch._write_inventory(self.work_inv)
348
336
 
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
 
 
369
337
    def _store_snapshot(self):
370
338
        """Pass over inventory and record a snapshot.
371
339
 
372
340
        Entries get a new revision when they are modified in 
373
341
        any way, which includes a merge with a new set of
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.
 
342
        parents that have the same entry. 
385
343
        """
386
344
        # XXX: Need to think more here about when the user has
387
345
        # made a specific decision on a particular value -- c.f.
388
346
        # mark-merge.  
389
347
        for path, ie in self.new_inv.iter_entries():
390
 
            previous_entries = self._find_entry_parents(ie. file_id)
 
348
            previous_entries = ie.find_previous_heads(
 
349
                self.parent_invs, 
 
350
                self.weave_store.get_weave_or_empty(ie.file_id))
391
351
            if ie.revision is None:
392
352
                change = ie.snapshot(self.rev_id, path, previous_entries,
393
353
                                     self.work_tree, self.weave_store)
394
354
            else:
395
355
                change = "unchanged"
396
 
            note("%s %s", change, path)
 
356
            self.reporter.snapshot_change(change, path)
397
357
 
398
358
    def _populate_new_inv(self):
399
359
        """Build revision inventory.
413
373
            if self.specific_files:
414
374
                if not is_inside_any(self.specific_files, path):
415
375
                    mutter('%s not selected for commit', path)
416
 
                    self._carry_file(file_id)
 
376
                    self._carry_entry(file_id)
417
377
                    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
418
393
            mutter('%s selected for commit', path)
419
 
            ie = new_ie.copy()
420
 
            ie.revision = None
421
 
            self.new_inv.add(ie)
422
 
 
423
 
    def _carry_file(self, file_id):
 
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):
424
404
        """Carry the file unchanged from the basis revision."""
425
405
        if self.basis_inv.has_id(file_id):
426
406
            self.new_inv.add(self.basis_inv[file_id].copy())
428
408
    def _report_deletes(self):
429
409
        for file_id in self.basis_inv:
430
410
            if file_id not in self.new_inv:
431
 
                note('deleted %s', self.basis_inv.id2path(file_id))
432
 
 
433
 
 
 
411
                self.reporter.deleted(self.basis_inv.id2path(file_id))
434
412
 
435
413
def _gen_revision_id(branch, when):
436
414
    """Return new revision-id."""
437
415
    s = '%s-%s-' % (user_email(branch), compact_date(when))
438
416
    s += hexlify(rand_bytes(8))
439
417
    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