44
44
# TODO: Update hashcache before and after - or does the WorkingTree
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
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,
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
103
99
class NullCommitReporter(object):
104
100
"""I report on progress of a commit."""
105
def added(self, path):
108
def removed(self, path):
111
def renamed(self, old_path, new_path):
102
def snapshot_change(self, change, path):
105
def completed(self, revno, rev_id):
108
def deleted(self, file_id):
111
def escaped(self, escape_count, message):
114
def missing(self, path):
115
117
class ReportCommitToLog(NullCommitReporter):
116
def added(self, path):
117
note('added %s', path)
119
def removed(self, path):
120
note('removed %s', path)
122
def renamed(self, old_path, new_path):
123
note('renamed %s => %s', old_path, new_path)
119
def snapshot_change(self, change, path):
120
note("%s %s", change, path)
122
def completed(self, revno, rev_id):
123
note('committed r%d {%s}', revno, rev_id)
125
def deleted(self, file_id):
126
note('deleted %s', file_id)
128
def escaped(self, escape_count, message):
129
note("replaced %d control characters in message", escape_count)
131
def missing(self, path):
132
note('missing %s', path)
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()
234
if len(list(self.work_tree.iter_conflicts()))>0:
235
raise ConflictsInTree
226
237
self._record_inventory()
227
self._record_ancestry()
228
238
self._make_revision()
229
note('committed r%d {%s}', (self.branch.revno() + 1),
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([])
258
267
lambda match: match.group(0).encode('unicode_escape'),
261
note("replaced %d control characters in message", escape_count)
263
def _record_ancestry(self):
264
"""Append merged revision ancestry to the ancestry file.
266
This should be the merged ancestry of all parents, plus the
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)
274
def _make_ancestry(self, ancestry_weave):
275
"""Return merged ancestry lines.
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))
270
self.reporter.escaped(escape_count, self.message)
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()
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)
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)
306
295
mutter("commit will ghost revision %r", parent_id)
346
334
del self.work_inv[file_id]
347
335
self.branch._write_inventory(self.work_inv)
350
def _find_entry_parents(self, file_id):
351
"""Return the text versions and hashes for all file parents.
353
Returned as a map from text version to inventory entry.
355
This is a set containing the file versions in all parents
356
revisions containing the file. If the file is new, the set
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
364
assert r[ie.revision] == ie
369
337
def _store_snapshot(self):
370
338
"""Pass over inventory and record a snapshot.
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,
342
parents that have the same entry.
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.
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(
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)
395
355
change = "unchanged"
396
note("%s %s", change, path)
356
self.reporter.snapshot_change(change, path)
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)
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)
387
ie = self.new_inv[parent_id]
388
if ie.revision is not 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)
423
def _carry_file(self, file_id):
394
self._select_entry(new_ie)
396
def _select_entry(self, new_ie):
397
"""Make new_ie be considered for committing."""
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))
411
self.reporter.deleted(self.basis_inv.id2path(file_id))
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))
444
def merge_ancestry_lines(rev_id, ancestries):
445
"""Return merged ancestry lines.
447
rev_id -- id of the new revision
449
ancestries -- a sequence of ancestries for parent revisions,
450
as newline-terminated line lists.
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'