import difflib import email.utils import logging import re from patchwork.models import Patch, Person, State log = logging.getLogger('patch-matcher') def _is_revert(name1, name2): name1 = re.sub(r'\[[^\]]*\]', '', name1).strip() name2 = re.sub(r'\[[^\]]*\]', '', name2).strip() if name1.startswith('Revert') and not name2.startswith('Revert'): return True return False def _patches_similar(name1, diff1, name2, diff2): # Be conservative and use 0.9 as the similarity ratio between the # commit's title and content to prevent false-positives. name_ratio = difflib.SequenceMatcher(None, name1, name2).ratio() if name_ratio < 0.7: return False diff_ratio = difflib.SequenceMatcher(None, diff1, diff2).ratio() log.debug( 'name_ratio(%f) diff_ratio(%f) for: %s', name_ratio, diff_ratio, name1) if diff_ratio > 0.9 and not _is_revert(name1, name2): return True if name_ratio > 0.9: if diff_ratio > 0.9 or (name_ratio > .99 and diff_ratio > 0.6): if not _is_revert(name1, name2): return True return False def _get_patchwork_author_committer(commit): _, auth_email = email.utils.parseaddr(commit.author) _, comm_email = email.utils.parseaddr(commit.committer) try: auth = Person.objects.get(email=auth_email) except Person.DoesNotExist: auth = None try: comm = Person.objects.get(email=comm_email) except Person.DoesNotExist: comm = None return auth, comm def get_patches_matching(project, submitters, name, content): accepted = State.objects.get(name='Accepted') superseded = State.objects.get(name='Superseded') pending = Patch.objects.filter( project=project, submitter__in=submitters, ).exclude( state__in=(accepted, superseded), ) for p in pending: # Ignore pull requests (p.pull_url) for the purpose of this function if not p.pull_url and \ _patches_similar(p.name, p.diff, name, content): yield p def get_patches_matching_commit(project, repo, commit): """Return the Patch objects that are likely matches to the given commit. Patches that are either Accepted or Superseded are never included in the results. """ persons = [x for x in _get_patchwork_author_committer(commit) if x] patch = repo.get_patch(commit) name = commit.message.split('\n')[0] return get_patches_matching(project, persons, name, patch)