blob: 064b922ccefd17a55f74d0cc5fed1cc5064a05f9 [file] [log] [blame]
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.decode())
_, comm_email = email.utils.parseaddr(commit.committer.decode())
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.decode().split('\n')[0]
return get_patches_matching(project, persons, name, patch)