271
268
s = self.branch.control_weaves
272
269
w = s.get_weave_or_empty('ancestry')
273
270
lines = self._make_ancestry(w)
274
w.add(self.rev_id, self.parents, lines)
271
w.add(self.rev_id, self.present_parents, lines)
275
272
s.put_weave('ancestry', w)
278
274
def _make_ancestry(self, ancestry_weave):
279
275
"""Return merged ancestry lines.
281
277
The lines are revision-ids followed by newlines."""
282
parent_ancestries = [ancestry_weave.get(p) for p in self.parents]
278
parent_ancestries = [ancestry_weave.get(p) for p in self.present_parents]
283
279
new_lines = merge_ancestry_lines(self.rev_id, parent_ancestries)
284
280
mutter('merged ancestry of {%s}:\n%s', self.rev_id, ''.join(new_lines))
288
283
def _gather_parents(self):
284
"""Record the parents of a merge for merge detection."""
289
285
pending_merges = self.branch.pending_merges()
290
286
self.parents = []
291
287
self.parent_trees = []
288
self.present_parents = []
292
289
precursor_id = self.branch.last_revision()
294
291
self.parents.append(precursor_id)
295
self.parent_trees.append(self.basis_tree)
296
292
self.parents += pending_merges
297
self.parent_trees.extend(map(self.branch.revision_tree, pending_merges))
293
for revision in self.parents:
294
if self.branch.has_revision(revision):
295
self.parent_trees.append(self.branch.revision_tree(revision))
296
self.present_parents.append(revision)
300
298
def _check_parents_present(self):
301
299
for parent_id in self.parents:
302
300
mutter('commit parent revision {%s}', parent_id)
303
301
if not self.branch.has_revision(parent_id):
304
warning("can't commit a merge from an absent parent")
305
raise HistoryMissing(self.branch, 'revision', parent_id)
302
if parent_id == self.branch.last_revision():
303
warning("parent is pissing %r", parent_id)
304
raise HistoryMissing(self.branch, 'revision', parent_id)
306
mutter("commit will ghost revision %r", parent_id)
308
308
def _make_revision(self):
309
309
"""Record a new revision object for this commit."""
359
359
for tree in self.parent_trees:
360
360
if file_id in tree.inventory:
361
361
ie = tree.inventory[file_id]
362
assert ie.kind == 'file'
363
362
assert ie.file_id == file_id
364
if ie.text_version in r:
365
assert r[ie.text_version] == ie
364
assert r[ie.revision] == ie
367
r[ie.text_version] = ie
371
def _set_name_versions(self):
372
"""Pass over inventory and mark new entry version as needed.
374
Files get a new name version when they are new, have a
375
different parent, or a different name from in the
376
basis inventory, or if the file is in a different place
377
to any of the parents."""
369
def _store_snapshot(self):
370
"""Pass over inventory and record a snapshot.
372
Entries get a new revision when they are modified in
373
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,
378
386
# XXX: Need to think more here about when the user has
379
387
# made a specific decision on a particular value -- c.f.
381
389
for path, ie in self.new_inv.iter_entries():
384
for parent_tree in self.parent_trees:
385
parent_inv = parent_tree.inventory
386
if file_id not in parent_inv:
388
parent_ie = parent_inv[file_id]
389
if parent_ie.parent_id != ie.parent_id:
392
elif parent_ie.name != ie.name:
395
elif old_version is None:
396
old_version = parent_ie.name_version
397
elif old_version != parent_ie.name_version:
401
pass # so far so good
402
if old_version is None:
403
mutter('new name_version for {%s}', file_id)
404
ie.name_version = self.rev_id
390
previous_entries = self._find_entry_parents(ie. file_id)
391
if ie.revision is None:
392
change = ie.snapshot(self.rev_id, path, previous_entries,
393
self.work_tree, self.weave_store)
406
mutter('name_version for {%s} inherited as {%s}',
407
file_id, old_version)
408
ie.name_version = old_version
411
def _store_entries(self):
412
"""Build revision inventory and store modified files.
414
This is called with new_inv a new empty inventory. Depending on
415
which files are selected for commit, and which ones have
416
been modified or merged, new inventory entries are built
417
based on the working and parent inventories.
419
As a side-effect this stores new text versions for committed
420
files with text changes or merges.
422
Each entry can have one of several things happen:
424
carry_file -- carried from the previous version (if not
427
commit_nonfile -- no text to worry about
429
commit_old_text -- same text, may have moved
431
commit_file -- new text version
396
note("%s %s", change, path)
398
def _populate_new_inv(self):
399
"""Build revision inventory.
401
This creates a new empty inventory. Depending on
402
which files are selected for commit, and what is present in the
403
current tree, the new inventory is populated. inventory entries
404
which are candidates for modification have their revision set to
405
None; inventory entries that are carried over untouched have their
406
revision set to their prior value.
408
mutter("Selecting files for commit with filter %s", self.specific_files)
409
self.new_inv = Inventory()
433
410
for path, new_ie in self.work_inv.iter_entries():
434
411
file_id = new_ie.file_id
435
412
mutter('check %s {%s}', path, new_ie.file_id)
438
415
mutter('%s not selected for commit', path)
439
416
self._carry_file(file_id)
441
if new_ie.kind != 'file':
442
self._commit_nonfile(file_id)
445
file_parents = self._find_file_parents(file_id)
446
mutter('parents of %s are %r', path, file_parents)
447
if len(file_parents) == 1:
448
parent_ie = file_parents.values()[0]
449
wc_sha1 = self.work_tree.get_file_sha1(file_id)
450
if parent_ie.text_sha1 == wc_sha1:
451
# text not changed or merged
452
self._commit_old_text(file_id, parent_ie)
454
# file is either new, or a file merge; need to record
456
if len(file_parents) > 1:
457
note('merged %s', path)
458
elif len(file_parents) == 0:
459
note('added %s', path)
461
note('modified %s', path)
462
self._commit_file(new_ie, file_id, file_parents)
465
def _commit_nonfile(self, file_id):
466
self.new_inv.add(self.work_inv[file_id].copy())
418
mutter('%s selected for commit', path)
469
423
def _carry_file(self, file_id):
470
424
"""Carry the file unchanged from the basis revision."""
471
425
if self.basis_inv.has_id(file_id):
472
426
self.new_inv.add(self.basis_inv[file_id].copy())
475
def _commit_old_text(self, file_id, parent_ie):
476
"""Keep the same text as last time, but possibly a different name."""
477
ie = self.work_inv[file_id].copy()
478
ie.text_version = parent_ie.text_version
479
ie.text_size = parent_ie.text_size
480
ie.text_sha1 = parent_ie.text_sha1
484
428
def _report_deletes(self):
485
429
for file_id in self.basis_inv:
486
430
if file_id not in self.new_inv:
487
431
note('deleted %s', self.basis_inv.id2path(file_id))
490
def _commit_file(self, new_ie, file_id, file_parents):
491
mutter('store new text for {%s} in revision {%s}',
492
file_id, self.rev_id)
493
new_lines = self.work_tree.get_file(file_id).readlines()
494
self._add_text_to_weave(file_id, new_lines, file_parents)
495
new_ie.text_version = self.rev_id
496
new_ie.text_sha1 = sha_strings(new_lines)
497
new_ie.text_size = sum(map(len, new_lines))
498
self.new_inv.add(new_ie)
501
def _add_text_to_weave(self, file_id, new_lines, parents):
502
self.weave_store.add_text(file_id, self.rev_id, new_lines, parents)
505
435
def _gen_revision_id(branch, when):
506
436
"""Return new revision-id."""