#! /usr/bin/env python3.3
"""Mapping of fast-export marks to commit SHA1s for all references.
The Marks class exists to solve one basic problem: with the advent of
background push, the preflight of all pushed references is performed before
any copying is done. As a result, the depot branch info values are
populated with mark numbers, and during the copy phase, a parent change may
have been processed in an earlier branch but the "change" is still listed
as a mark number in the branch info (because the marks are different for
the same commit across branches).
"""
import collections
import logging
LOG = logging.getLogger(__name__)
class Marks:
"""Collection of export marks for all branches."""
def __init__(self):
"""Construct an instance of Marks."""
# reference to consider first when searching for commits/marks
self._preferred_head = None
# mapping of mark to commit for preferred head
self._preferred_marks = None
# mapping of commit to mark for preferred head
self._preferred_commits = None
# mapping of ref to mapping of mark to commit
self._mark_to_commit = collections.OrderedDict()
# mapping of ref to mapping of commit to mark
self._commit_to_mark = collections.OrderedDict()
def set_head(self, ref):
"""Set the preferred branch to search before consulting others.
:type ref: str
:param ref: name of branch reference (e.g. 'refs/heads/master').
"""
LOG.debug2('switching from {} to {}'.format(self._preferred_head, ref))
self._preferred_head = ref
self._preferred_marks = self._mark_to_commit.get(ref)
self._preferred_commits = self._commit_to_mark.get(ref)
def add(self, ref, marks):
"""Add the given marks associated with the named branch.
The order in which the marks are added is remembered, so that the
find functions will scan the refs in the order in which they were
added. This is useful if the marks are added in the pushed order.
:type ref: str
:param ref: name of branch reference (e.g. 'refs/heads/master').
:type marks: dict
:param marks: mapping of fast-export marks to commit SHA1s.
"""
LOG.debug2('adding {} marks for {}'.format(len(marks), ref))
self._mark_to_commit[ref] = marks
self._commit_to_mark[ref] = {v: k for k, v in marks.items()}
def get_commit(self, mark):
"""Retreive the commit associated with the mark on the preferred branch.
If the preferred branch has not been set, will scan the first added
branch only.
:type mark: str
:param mark: mark value for which to find commit.
:rtype: str or None
:return: commit SHA1, or None if not found.
"""
if self._preferred_marks is None:
results = self.find_commits(mark)
return results[0] if results else None
elif mark in self._preferred_marks:
sha1 = self._preferred_marks[mark]
LOG.debug2('found preferred ({2}) commit {0} for mark {1}'.format(
sha1, mark, self._preferred_head))
return sha1
return None
def find_commits(self, mark):
"""Search for all marks associated with the given SHA1.
Scans across all known branches in the order in which they were
added to this colleciton, returning the list of all matching marks.
:type mark: str
:param mark: mark value for which to find commit.
:rtype: list of str
:return: list of commits.
"""
results = []
for ref, marks in self._mark_to_commit.items():
if mark in marks:
sha1 = marks[mark]
LOG.debug2('found commit {0} for mark {1} from ref {2}'.format(sha1, mark, ref))
results.append(sha1)
return results
def get_mark(self, sha1):
"""Retreive the mark associated with the commit on the preferred branch.
If the preferred branch has not been set, will scan the first added
branch only.
:type sha1: str
:param sha1: commit for which to find mark.
:rtype: str or None
:return: mark value, or None if not found.
"""
if self._preferred_commits is None:
results = self.find_marks(sha1)
return results[0] if results else None
elif sha1 in self._preferred_commits:
mark = self._preferred_commits[sha1]
LOG.debug2('found preferred ({2}) mark {1} for commit {0}'.format(
sha1, mark, self._preferred_head))
return mark
return None
def find_marks(self, sha1):
"""Search for all marks associated with the given SHA1.
Scans across all known branches in the order in which they were
added to this colleciton, returning the list of all matching commits.
:type sha1: str
:param sha1: commit for which to find mark.
:rtype: list of str
:return: list of marks.
"""
results = []
for ref, commits in self._commit_to_mark.items():
if sha1 in commits:
mark = commits[sha1]
LOG.debug2('found mark {1} for commit {0} from ref {2}'.format(sha1, mark, ref))
results.append(mark)
return results