#!/usr/bin/python3 import fcntl import logging import os import operator import sys from bin import django_setup, add_logging_arguments django_setup() # must be called to get sys.path and django settings in place from patchwork.models import Project, State import gitrepo import patch_matcher from importlib import import_module log = logging.getLogger('update_commited_patches') _here = os.path.abspath(os.path.dirname(__file__)) def _assert_repo_dir(path): if not os.path.exists(path): os.mkdir(path) def _update_commit(project, repo, commit, dryrun): patches = patch_matcher.get_patches_matching_commit(project, repo, commit) accepted = State.objects.get(name='Accepted') superseded = State.objects.get(name='Superseded') # sort the patches newest to oldest. the most recent gets marked as # accepted and everything else is superseded. patches = sorted(patches, key=operator.attrgetter('date'), reverse=True) for i, patch in enumerate(patches): if i == 0: patch.state = accepted patch.commit_ref = commit.id.decode() else: patch.state = superseded log.info('Updating patch %s, commit: %s, state: %s', patch, commit.id, patch.state.name) if not dryrun: patch.save() def _update_project(cb, repo_dir, project, commits, dryrun): log.info('Checking for updates to %s', project.linkname) repo = gitrepo.Repo(repo_dir, project.linkname, project.scm_url) repo.update() if commits: commits = [repo[x] for x in commits] else: save_state = dryrun is False commits = repo.process_unchecked_commits(save_state) for commit in commits: log.debug('check commit: %s', commit.id) try: _update_commit(project, repo, commit, dryrun) if cb: cb(project, repo, commit, dryrun) except MemoryError as e: log.error('Unable to process commit(%s) because of size: %s', commit.id, e) except Exception as e: log.error('Unable to process commit(%s): %s', commit.id, e) def get_commit_callback_constructor(): p = getattr(settings, 'UPDATE_COMMIT_CALLBACK', None) if p: module, func = p.rsplit(':', 1) module = import_module(module) return getattr(module, func) if __name__ == '__main__': import argparse from django.conf import settings parser = argparse.ArgumentParser( description='Find patches that have been committed upstream') parser.add_argument('--dryrun', action='store_true', help='Run through without changing anything in the DB') add_logging_arguments(parser) parser.add_argument('project', nargs='?', help='only check on specific project') parser.add_argument('commit_id', nargs='*', help='only check on given commit(s) for a project') args = parser.parse_args() _assert_repo_dir(settings.REPO_DIR) # Ensure no other copy of this script is running f = open(os.path.join(settings.REPO_DIR, '.lock'), 'w+') try: fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: sys.exit('Script is already running') if args.project: projects = [Project.objects.get(linkname=args.project)] else: projects = Project.objects.filter( scm_url__isnull=False).exclude(scm_url='') cb_constructor = get_commit_callback_constructor() if cb_constructor: with cb_constructor() as cb: for p in projects: try: _update_project( cb, settings.REPO_DIR, p, args.commit_id, args.dryrun ) except Exception: log.exception('Error updating commits for: %s', p) if not cb_constructor: for p in projects: try: _update_project( None, settings.REPO_DIR, p, args.commit_id, args.dryrun ) except Exception: log.exception('Error updating commits for: %s', p)