git-importer/lib/tree_builder.py
2022-10-27 13:34:04 +02:00

128 lines
4.3 KiB
Python

from lib.db_revision import DBRevision
from lib.request import Request
class TreeNode:
"""
Nodes in this "tree" have either no parent (root), one parent (in a chain)
or two parents (in this case the merged revision wins in conflicts).
"""
def __init__(self, rev):
self.parent = None
self.merged = None
self.revision = rev
self.merged_into = None
def print(self):
node = self
while node:
print(node.revision, node.revision.files_hash)
if node.merged:
source_node = node.merged
while source_node:
print(" ", source_node.revision, source_node.revision.files_hash)
source_node = source_node.parent
if source_node and source_node.merged_into:
break
node = node.parent
def as_list(self):
"""Return a list for test cases"""
node = self
ret = []
while node:
repr = {"commit": node.revision.short_string()}
if node.merged:
source_node = node.merged
repr["merged"] = []
while source_node:
repr["merged"].append(source_node.revision.short_string())
source_node = source_node.parent
if source_node and source_node.merged_into:
break
node = node.parent
ret.append(repr)
return ret
class TreeBuilder:
def __init__(self, db):
self.db = db
def revisions_chain(self, project, package):
revisions = DBRevision.all_revisions(self.db, project, package)
revisions.sort()
prev = None
tree = None
for rev in revisions:
if rev.broken:
continue
if prev and prev.files_hash == rev.files_hash:
continue
prev = rev
new_tree = TreeNode(rev)
if tree:
new_tree.parent = tree
tree = new_tree
return tree
def find_merge(self, revision, source_chain):
node = source_chain
while node:
# exclude reverts happening after the merge
if (
node.revision.commit_time <= revision.commit_time
and node.revision.files_hash == revision.files_hash
):
return node
node = node.parent
def add_merge_points(self, factory_revisions):
source_revisions = dict()
factory_node = factory_revisions
while factory_node:
if factory_node.revision.request_id:
req = Request.find(self.db, factory_node.revision.request_id)
key = f"{req.source_project}/{req.source_package}"
if key not in source_revisions:
source_revisions[key] = self.revisions_chain(
req.source_project, req.source_package
)
factory_node.merged = self.find_merge(
factory_node.revision, source_revisions[key]
)
# add a reverse lookup
if factory_node.merged:
factory_node.merged.merged_into = factory_node
factory_node = factory_node.parent
def prune_loose_end(self, factory_node):
"""Look for source revisions that end in a new root and prune them"""
last_merge = None
while factory_node:
if factory_node.merged:
source_node = factory_node.merged
ended_without_merge = False
while source_node:
source_node = source_node.parent
if source_node and source_node.merged_into:
ended_without_merge = True
break
if not ended_without_merge:
factory_node.merged = None
if last_merge:
last_merge.parent = None
else:
last_merge = factory_node.merged
factory_node = factory_node.parent
def build(self, package):
factory_revisions = self.revisions_chain("openSUSE:Factory", package)
self.add_merge_points(factory_revisions)
self.prune_loose_end(factory_revisions)
return factory_revisions