~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 22:15:52 UTC
  • mfrom: (1185.13.2)
  • mto: This revision was merged to the branch mainline in revision 1420.
  • Revision ID: robertc@robertcollins.net-20051006221552-9b15c96fa504e0ad
mergeĀ fromĀ upstream

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
77
78
                           )
78
79
from bzrlib.revision import Revision
79
80
from bzrlib.trace import mutter, note, warning
80
81
from bzrlib.xml5 import serializer_v5
81
 
from bzrlib.inventory import Inventory
 
82
from bzrlib.inventory import Inventory, ROOT_ID
82
83
from bzrlib.weave import Weave
83
84
from bzrlib.weavefile import read_weave, write_weave_v5
84
85
from bzrlib.atomicfile import AtomicFile
97
98
 
98
99
class NullCommitReporter(object):
99
100
    """I report on progress of a commit."""
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
 
 
 
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
109
116
 
110
117
class ReportCommitToLog(NullCommitReporter):
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
 
 
 
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)
120
133
 
121
134
class Commit(object):
122
135
    """Task of committing a new revision.
218
231
                    or self.new_inv != self.basis_inv):
219
232
                raise PointlessCommit()
220
233
 
 
234
            if len(list(self.work_tree.iter_conflicts()))>0:
 
235
                raise ConflictsInTree
 
236
 
221
237
            self._record_inventory()
222
 
            self._record_ancestry()
223
238
            self._make_revision()
224
 
            note('committed r%d {%s}', (self.branch.revno() + 1),
225
 
                 self.rev_id)
 
239
            self.reporter.completed(self.branch.revno()+1, self.rev_id)
226
240
            self.branch.append_revision(self.rev_id)
227
241
            self.branch.set_pending_merges([])
228
242
        finally:
253
267
            lambda match: match.group(0).encode('unicode_escape'),
254
268
            self.message)
255
269
        if escape_count:
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
 
270
            self.reporter.escaped(escape_count, self.message)
277
271
 
278
272
    def _gather_parents(self):
279
273
        """Record the parents of a merge for merge detection."""
280
274
        pending_merges = self.branch.pending_merges()
281
275
        self.parents = []
282
 
        self.parent_trees = []
 
276
        self.parent_invs = []
283
277
        self.present_parents = []
284
278
        precursor_id = self.branch.last_revision()
285
279
        if precursor_id:
287
281
        self.parents += pending_merges
288
282
        for revision in self.parents:
289
283
            if self.branch.has_revision(revision):
290
 
                self.parent_trees.append(self.branch.revision_tree(revision))
 
284
                self.parent_invs.append(self.branch.get_inventory(revision))
291
285
                self.present_parents.append(revision)
292
286
 
293
287
    def _check_parents_present(self):
295
289
            mutter('commit parent revision {%s}', parent_id)
296
290
            if not self.branch.has_revision(parent_id):
297
291
                if parent_id == self.branch.last_revision():
298
 
                    warning("parent is pissing %r", parent_id)
 
292
                    warning("parent is missing %r", parent_id)
299
293
                    raise HistoryMissing(self.branch, 'revision', parent_id)
300
294
                else:
301
295
                    mutter("commit will ghost revision %r", parent_id)
315
309
        self.branch.revision_store.add(rev_tmp, self.rev_id)
316
310
        mutter('new revision_id is {%s}', self.rev_id)
317
311
 
318
 
 
319
312
    def _remove_deleted(self):
320
313
        """Remove deleted files from the working inventories.
321
314
 
333
326
            if specific and not is_inside_any(specific, path):
334
327
                continue
335
328
            if not self.work_tree.has_filename(path):
336
 
                note('missing %s', path)
337
 
                deleted_ids.append(ie.file_id)
 
329
                self.reporter.missing(path)
 
330
                deleted_ids.append((path, ie.file_id))
338
331
        if deleted_ids:
339
 
            for file_id in deleted_ids:
340
 
                if file_id in self.work_inv:
341
 
                    del self.work_inv[file_id]
 
332
            deleted_ids.sort(reverse=True)
 
333
            for path, file_id in deleted_ids:
 
334
                del self.work_inv[file_id]
342
335
            self.branch._write_inventory(self.work_inv)
343
336
 
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
 
 
364
337
    def _store_snapshot(self):
365
338
        """Pass over inventory and record a snapshot.
366
339
 
367
340
        Entries get a new revision when they are modified in 
368
341
        any way, which includes a merge with a new set of
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.
 
342
        parents that have the same entry. 
380
343
        """
381
344
        # XXX: Need to think more here about when the user has
382
345
        # made a specific decision on a particular value -- c.f.
383
346
        # mark-merge.  
384
347
        for path, ie in self.new_inv.iter_entries():
385
 
            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))
386
351
            if ie.revision is None:
387
352
                change = ie.snapshot(self.rev_id, path, previous_entries,
388
353
                                     self.work_tree, self.weave_store)
389
354
            else:
390
355
                change = "unchanged"
391
 
            note("%s %s", change, path)
 
356
            self.reporter.snapshot_change(change, path)
392
357
 
393
358
    def _populate_new_inv(self):
394
359
        """Build revision inventory.
408
373
            if self.specific_files:
409
374
                if not is_inside_any(self.specific_files, path):
410
375
                    mutter('%s not selected for commit', path)
411
 
                    self._carry_file(file_id)
 
376
                    self._carry_entry(file_id)
412
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
413
393
            mutter('%s selected for commit', path)
414
 
            ie = new_ie.copy()
415
 
            ie.revision = None
416
 
            self.new_inv.add(ie)
417
 
 
418
 
    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):
419
404
        """Carry the file unchanged from the basis revision."""
420
405
        if self.basis_inv.has_id(file_id):
421
406
            self.new_inv.add(self.basis_inv[file_id].copy())
423
408
    def _report_deletes(self):
424
409
        for file_id in self.basis_inv:
425
410
            if file_id not in self.new_inv:
426
 
                note('deleted %s', self.basis_inv.id2path(file_id))
427
 
 
428
 
 
 
411
                self.reporter.deleted(self.basis_inv.id2path(file_id))
429
412
 
430
413
def _gen_revision_id(branch, when):
431
414
    """Return new revision-id."""
432
415
    s = '%s-%s-' % (user_email(branch), compact_date(when))
433
416
    s += hexlify(rand_bytes(8))
434
417
    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