| # ex:ts=4:sw=4:sts=4:et |
| # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| # |
| # patchtestrepo: PatchTestRepo class used mainly to control a git repo from patchtest |
| # |
| # Copyright (C) 2016 Intel Corporation |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| import os |
| import utils |
| import logging |
| from patch import PatchTestPatch |
| |
| logger = logging.getLogger('patchtest') |
| info=logger.info |
| |
| class PatchTestRepo(object): |
| |
| # prefixes used for temporal branches/stashes |
| prefix = 'patchtest' |
| |
| def __init__(self, patch, repodir, commit=None, branch=None): |
| self._repodir = repodir |
| self._patch = PatchTestPatch(patch) |
| self._current_branch = self._get_current_branch() |
| |
| # targeted branch defined on the patch may be invalid, so make sure there |
| # is a corresponding remote branch |
| valid_patch_branch = None |
| if self._patch.branch in self.upstream_branches(): |
| valid_patch_branch = self._patch.branch |
| |
| # Target Branch |
| # Priority (top has highest priority): |
| # 1. branch given at cmd line |
| # 2. branch given at the patch |
| # 3. current branch |
| self._branch = branch or valid_patch_branch or self._current_branch |
| |
| # Target Commit |
| # Priority (top has highest priority): |
| # 1. commit given at cmd line |
| # 2. branch given at cmd line |
| # 3. branch given at the patch |
| # 3. current HEAD |
| self._commit = self._get_commitid(commit) or \ |
| self._get_commitid(branch) or \ |
| self._get_commitid(valid_patch_branch) or \ |
| self._get_commitid('HEAD') |
| |
| self._workingbranch = "%s_%s" % (PatchTestRepo.prefix, os.getpid()) |
| |
| # create working branch |
| self._exec({'cmd': ['git', 'checkout', '-b', self._workingbranch, self._commit]}) |
| |
| self._patchmerged = False |
| |
| # Check if patch can be merged using git-am |
| self._patchcanbemerged = True |
| try: |
| self._exec({'cmd': ['git', 'am', '--keep-cr'], 'input': self._patch.contents}) |
| except utils.CmdException as ce: |
| self._exec({'cmd': ['git', 'am', '--abort']}) |
| self._patchcanbemerged = False |
| finally: |
| # if patch was applied, remove it |
| if self._patchcanbemerged: |
| self._exec({'cmd':['git', 'reset', '--hard', self._commit]}) |
| |
| # for debugging purposes, print all repo parameters |
| logger.debug("Parameters") |
| logger.debug("\tRepository : %s" % self._repodir) |
| logger.debug("\tTarget Commit : %s" % self._commit) |
| logger.debug("\tTarget Branch : %s" % self._branch) |
| logger.debug("\tWorking branch : %s" % self._workingbranch) |
| logger.debug("\tPatch : %s" % self._patch) |
| |
| @property |
| def patch(self): |
| return self._patch.path |
| |
| @property |
| def branch(self): |
| return self._branch |
| |
| @property |
| def commit(self): |
| return self._commit |
| |
| @property |
| def ismerged(self): |
| return self._patchmerged |
| |
| @property |
| def canbemerged(self): |
| return self._patchcanbemerged |
| |
| def _exec(self, cmds): |
| _cmds = [] |
| if isinstance(cmds, dict): |
| _cmds.append(cmds) |
| elif isinstance(cmds, list): |
| _cmds = cmds |
| else: |
| raise utils.CmdException({'cmd':str(cmds)}) |
| |
| results = [] |
| cmdfailure = False |
| try: |
| results = utils.exec_cmds(_cmds, self._repodir) |
| except utils.CmdException as ce: |
| cmdfailure = True |
| raise ce |
| finally: |
| if cmdfailure: |
| for cmd in _cmds: |
| logger.debug("CMD: %s" % ' '.join(cmd['cmd'])) |
| else: |
| for result in results: |
| cmd, rc, stdout, stderr = ' '.join(result['cmd']), result['returncode'], result['stdout'], result['stderr'] |
| logger.debug("CMD: %s RCODE: %s STDOUT: %s STDERR: %s" % (cmd, rc, stdout, stderr)) |
| |
| return results |
| |
| def _get_current_branch(self, commit='HEAD'): |
| cmd = {'cmd':['git', 'rev-parse', '--abbrev-ref', commit]} |
| cb = self._exec(cmd)[0]['stdout'] |
| if cb == commit: |
| logger.warning('You may be detached so patchtest will checkout to master after execution') |
| cb = 'master' |
| return cb |
| |
| def _get_commitid(self, commit): |
| |
| if not commit: |
| return None |
| |
| try: |
| cmd = {'cmd':['git', 'rev-parse', '--short', commit]} |
| return self._exec(cmd)[0]['stdout'] |
| except utils.CmdException as ce: |
| # try getting the commit under any remotes |
| cmd = {'cmd':['git', 'remote']} |
| remotes = self._exec(cmd)[0]['stdout'] |
| for remote in remotes.splitlines(): |
| cmd = {'cmd':['git', 'rev-parse', '--short', '%s/%s' % (remote, commit)]} |
| try: |
| return self._exec(cmd)[0]['stdout'] |
| except utils.CmdException: |
| pass |
| |
| return None |
| |
| def upstream_branches(self): |
| cmd = {'cmd':['git', 'branch', '--remotes']} |
| remote_branches = self._exec(cmd)[0]['stdout'] |
| |
| # just get the names, without the remote name |
| branches = set(branch.split('/')[-1] for branch in remote_branches.splitlines()) |
| return branches |
| |
| def merge(self): |
| if self._patchcanbemerged: |
| self._exec({'cmd': ['git', 'am', '--keep-cr'], |
| 'input': self._patch.contents, |
| 'updateenv': {'PTRESOURCE':self._patch.path}}) |
| self._patchmerged = True |
| |
| def clean(self): |
| self._exec({'cmd':['git', 'checkout', '%s' % self._current_branch]}) |
| self._exec({'cmd':['git', 'branch', '-D', self._workingbranch]}) |
| self._patchmerged = False |