repo.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # ex:ts=4:sw=4:sts=4:et
  2. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  3. #
  4. # patchtestrepo: PatchTestRepo class used mainly to control a git repo from patchtest
  5. #
  6. # Copyright (C) 2016 Intel Corporation
  7. #
  8. # SPDX-License-Identifier: GPL-2.0-only
  9. #
  10. import os
  11. import utils
  12. import logging
  13. import json
  14. from patch import PatchTestPatch
  15. logger = logging.getLogger('patchtest')
  16. info=logger.info
  17. class PatchTestRepo(object):
  18. # prefixes used for temporal branches/stashes
  19. prefix = 'patchtest'
  20. def __init__(self, patch, repodir, commit=None, branch=None):
  21. self._repodir = repodir
  22. self._patch = PatchTestPatch(patch)
  23. self._current_branch = self._get_current_branch()
  24. # targeted branch defined on the patch may be invalid, so make sure there
  25. # is a corresponding remote branch
  26. valid_patch_branch = None
  27. if self._patch.branch in self.upstream_branches():
  28. valid_patch_branch = self._patch.branch
  29. # Target Branch
  30. # Priority (top has highest priority):
  31. # 1. branch given at cmd line
  32. # 2. branch given at the patch
  33. # 3. current branch
  34. self._branch = branch or valid_patch_branch or self._current_branch
  35. # Target Commit
  36. # Priority (top has highest priority):
  37. # 1. commit given at cmd line
  38. # 2. branch given at cmd line
  39. # 3. branch given at the patch
  40. # 3. current HEAD
  41. self._commit = self._get_commitid(commit) or \
  42. self._get_commitid(branch) or \
  43. self._get_commitid(valid_patch_branch) or \
  44. self._get_commitid('HEAD')
  45. self._workingbranch = "%s_%s" % (PatchTestRepo.prefix, os.getpid())
  46. # create working branch
  47. self._exec({'cmd': ['git', 'checkout', '-b', self._workingbranch, self._commit]})
  48. self._patchmerged = False
  49. # Check if patch can be merged using git-am
  50. self._patchcanbemerged = True
  51. try:
  52. self._exec({'cmd': ['git', 'am', '--keep-cr'], 'input': self._patch.contents})
  53. except utils.CmdException as ce:
  54. self._exec({'cmd': ['git', 'am', '--abort']})
  55. self._patchcanbemerged = False
  56. finally:
  57. # if patch was applied, remove it
  58. if self._patchcanbemerged:
  59. self._exec({'cmd':['git', 'reset', '--hard', self._commit]})
  60. # for debugging purposes, print all repo parameters
  61. logger.debug("Parameters")
  62. logger.debug("\tRepository : %s" % self._repodir)
  63. logger.debug("\tTarget Commit : %s" % self._commit)
  64. logger.debug("\tTarget Branch : %s" % self._branch)
  65. logger.debug("\tWorking branch : %s" % self._workingbranch)
  66. logger.debug("\tPatch : %s" % self._patch)
  67. @property
  68. def patch(self):
  69. return self._patch.path
  70. @property
  71. def branch(self):
  72. return self._branch
  73. @property
  74. def commit(self):
  75. return self._commit
  76. @property
  77. def ismerged(self):
  78. return self._patchmerged
  79. @property
  80. def canbemerged(self):
  81. return self._patchcanbemerged
  82. def _exec(self, cmds):
  83. _cmds = []
  84. if isinstance(cmds, dict):
  85. _cmds.append(cmds)
  86. elif isinstance(cmds, list):
  87. _cmds = cmds
  88. else:
  89. raise utils.CmdException({'cmd':str(cmds)})
  90. results = []
  91. cmdfailure = False
  92. try:
  93. results = utils.exec_cmds(_cmds, self._repodir)
  94. except utils.CmdException as ce:
  95. cmdfailure = True
  96. raise ce
  97. finally:
  98. if cmdfailure:
  99. for cmd in _cmds:
  100. logger.debug("CMD: %s" % ' '.join(cmd['cmd']))
  101. else:
  102. for result in results:
  103. cmd, rc, stdout, stderr = ' '.join(result['cmd']), result['returncode'], result['stdout'], result['stderr']
  104. logger.debug("CMD: %s RCODE: %s STDOUT: %s STDERR: %s" % (cmd, rc, stdout, stderr))
  105. return results
  106. def _get_current_branch(self, commit='HEAD'):
  107. cmd = {'cmd':['git', 'rev-parse', '--abbrev-ref', commit]}
  108. cb = self._exec(cmd)[0]['stdout']
  109. if cb == commit:
  110. logger.warning('You may be detached so patchtest will checkout to master after execution')
  111. cb = 'master'
  112. return cb
  113. def _get_commitid(self, commit):
  114. if not commit:
  115. return None
  116. try:
  117. cmd = {'cmd':['git', 'rev-parse', '--short', commit]}
  118. return self._exec(cmd)[0]['stdout']
  119. except utils.CmdException as ce:
  120. # try getting the commit under any remotes
  121. cmd = {'cmd':['git', 'remote']}
  122. remotes = self._exec(cmd)[0]['stdout']
  123. for remote in remotes.splitlines():
  124. cmd = {'cmd':['git', 'rev-parse', '--short', '%s/%s' % (remote, commit)]}
  125. try:
  126. return self._exec(cmd)[0]['stdout']
  127. except utils.CmdException:
  128. pass
  129. return None
  130. def upstream_branches(self):
  131. cmd = {'cmd':['git', 'branch', '--remotes']}
  132. remote_branches = self._exec(cmd)[0]['stdout']
  133. # just get the names, without the remote name
  134. branches = set(branch.split('/')[-1] for branch in remote_branches.splitlines())
  135. return branches
  136. def merge(self):
  137. if self._patchcanbemerged:
  138. self._exec({'cmd': ['git', 'am', '--keep-cr'],
  139. 'input': self._patch.contents,
  140. 'updateenv': {'PTRESOURCE':self._patch.path}})
  141. self._patchmerged = True
  142. def clean(self):
  143. self._exec({'cmd':['git', 'checkout', '%s' % self._current_branch]})
  144. self._exec({'cmd':['git', 'branch', '-D', self._workingbranch]})
  145. self._patchmerged = False