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