1
0
Fork 0

Merge branch 'master' of ssh://github.com/google/llvm-premerge-checks

This commit is contained in:
Christian Kühnel 2020-02-04 15:46:28 +00:00
commit dfce0a7e0f
2 changed files with 42 additions and 34 deletions

View file

@ -20,9 +20,10 @@ import sys
from typing import List, Optional from typing import List, Optional
from phabricator import Phabricator from phabricator import Phabricator
class ApplyPatch: class ApplyPatch:
def __init__(self, comment_file_path: str): def __init__(self, comment_file_path: str, git_hash: str):
# TODO: turn os.environ parameter into command line arguments # TODO: turn os.environ parameter into command line arguments
# this would be much clearer and easier for testing # this would be much clearer and easier for testing
self.comment_file_path = comment_file_path self.comment_file_path = comment_file_path
@ -34,7 +35,7 @@ class ApplyPatch:
if not self.host.endswith('/api/'): if not self.host.endswith('/api/'):
self.host += '/api/' self.host += '/api/'
self.phab = Phabricator(token=self.conduit_token, host=self.host) self.phab = Phabricator(token=self.conduit_token, host=self.host)
self.git_hash = None # type: Optional[str] self.git_hash = git_hash # type: Optional[str]
self.msg = [] # type: List[str] self.msg = [] # type: List[str]
def _load_arcrc(self): def _load_arcrc(self):
@ -56,17 +57,20 @@ class ApplyPatch:
self.phab.update_interfaces() self.phab.update_interfaces()
try: try:
self._get_parent_hash() if self.git_hash is None:
self._get_parent_hash()
else:
print('Use provided commit {}'.format(self.git_hash))
self._git_checkout() self._git_checkout()
self._apply_patch() self._apply_patch()
finally: finally:
self._write_error_message() self._write_error_message()
def _get_parent_hash(self) -> str: def _get_parent_hash(self):
diff = self.phab.differential.getdiff(diff_id=self.diff_id) diff = self.phab.differential.getdiff(diff_id=self.diff_id)
# Keep a copy of the Phabricator answer for later usage in a json file # Keep a copy of the Phabricator answer for later usage in a json file
try: try:
with open(self.diff_json_path,'w') as json_file: with open(self.diff_json_path, 'w') as json_file:
json.dump(diff.response, json_file, sort_keys=True, indent=4) json.dump(diff.response, json_file, sort_keys=True, indent=4)
print('Wrote diff details to "{}".'.format(self.diff_json_path)) print('Wrote diff details to "{}".'.format(self.diff_json_path))
except Exception: except Exception:
@ -122,7 +126,8 @@ class ApplyPatch:
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Apply Phabricator patch to working directory.') parser = argparse.ArgumentParser(description='Apply Phabricator patch to working directory.')
parser.add_argument('--comment-file', type=str, dest='comment_file_path', default=None) parser.add_argument('--comment-file', type=str, dest='comment_file_path', default=None)
parser.add_argument('--commit', type=str, dest='commit', default=None, help='use explicitly specified base commit')
args = parser.parse_args() args = parser.parse_args()
patcher = ApplyPatch(args.comment_file_path) patcher = ApplyPatch(args.comment_file_path, args.commit)
patcher.run() patcher.run()

View file

@ -12,6 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import argparse import argparse
import json import json
import os import os
@ -23,6 +24,7 @@ from typing import List, Optional, Tuple
from phabricator import Phabricator from phabricator import Phabricator
from git import Repo, GitCommandError from git import Repo, GitCommandError
class ApplyPatch: class ApplyPatch:
"""Apply a diff from Phabricator on local working copy. """Apply a diff from Phabricator on local working copy.
@ -30,29 +32,25 @@ class ApplyPatch:
that have already landed, but could not be identified by `arc patch`. that have already landed, but could not be identified by `arc patch`.
For a given diff_id, this class will get the dependencies listed on Phabricator. For a given diff_id, this class will get the dependencies listed on Phabricator.
For each dependency D it wil check, if D has already landed by looking For each dependency D it will check the diff history:
for the id of D in the gif history. If D has not landed, it will download - if D has already landed, skip it.
the patch for D and try to apply it locally. Once this class has applied all - If D has not landed, it will download the patch for D and try to apply it locally.
dependencies, it will apply the diff itself. Once this class has applied all dependencies, it will apply the diff itself.
This script must be called from the root folder of a local checkout of This script must be called from the root folder of a local checkout of
https://github.com/llvm/llvm-project https://github.com/llvm/llvm-project
""" """
def __init__(self, diff_id:str, comment_file_path: str, token: str, url: str, store_json_diff: str): def __init__(self, diff_id: int, comment_file_path: str, token: str, url: str, git_hash: str):
# TODO: turn os.environ parameter into command line arguments
# this would be much clearer and easier for testing
self.comment_file_path = comment_file_path self.comment_file_path = comment_file_path
self.conduit_token = token # type: Optional[str] self.conduit_token = token # type: Optional[str]
self.host = url # type: Optional[str] self.host = url # type: Optional[str]
self._load_arcrc() self._load_arcrc()
self.diff_id = diff_id # type: str self.diff_id = diff_id # type: int
self.diff_json_path = store_json_diff # type: str
if not self.host.endswith('/api/'): if not self.host.endswith('/api/'):
self.host += '/api/' self.host += '/api/'
self.phab = self._create_phab() self.phab = self._create_phab()
self.git_hash = None # type: Optional[str] self.git_hash = git_hash # type: Optional[str]
self.msg = [] # type: List[str] self.msg = [] # type: List[str]
self.repo = Repo(os.getcwd()) # type: Repo self.repo = Repo(os.getcwd()) # type: Repo
@ -75,13 +73,16 @@ class ApplyPatch:
try: try:
revision_id, dependencies, base_revision = self._get_dependencies() revision_id, dependencies, base_revision = self._get_dependencies()
if self.git_hash is not None:
print('Using base revision provided by command line')
base_revision = self.git_hash
print('Checking out {}...'.format(base_revision)) print('Checking out {}...'.format(base_revision))
try: try:
self.repo.git.checkout(base_revision) self.repo.git.checkout(base_revision)
except GitCommandError: except GitCommandError:
print('ERROR checking out revision {}. It`s not in the ' print('ERROR checking out revision {}. It`s not in the '
'repository. Using master instead.'.format(base_revision)) 'repository. Using master instead.'.format(base_revision))
self.repo.git.checkout('master') self.repo.git.checkout('master')
print('Revision is {}'.format(self.repo.head.commit.hexsha)) print('Revision is {}'.format(self.repo.head.commit.hexsha))
print('git reset, git cleanup...') print('git reset, git cleanup...')
self.repo.git.reset('--hard') self.repo.git.reset('--hard')
@ -94,6 +95,7 @@ class ApplyPatch:
print(' These are missing on master: {}'.format(diff_list_to_str(missing))) print(' These are missing on master: {}'.format(diff_list_to_str(missing)))
for revision in missing: for revision in missing:
self._apply_revision(revision) self._apply_revision(revision)
print('All depended diffs are applied')
self._apply_diff(self.diff_id, revision_id) self._apply_diff(self.diff_id, revision_id)
print('done.') print('done.')
finally: finally:
@ -104,7 +106,7 @@ class ApplyPatch:
phab.update_interfaces() phab.update_interfaces()
return phab return phab
def _get_diff(self, diff_id: str): def _get_diff(self, diff_id: int):
"""Get a diff from Phabricator based on it's diff id.""" """Get a diff from Phabricator based on it's diff id."""
return self.phab.differential.getdiff(diff_id=diff_id) return self.phab.differential.getdiff(diff_id=diff_id)
@ -112,18 +114,17 @@ class ApplyPatch:
"""Get a revision from Phabricator based on its revision id.""" """Get a revision from Phabricator based on its revision id."""
return self.phab.differential.query(ids=[revision_id])[0] return self.phab.differential.query(ids=[revision_id])[0]
def _get_revisions(self, *, phids: str = None): def _get_revisions(self, *, phids: List[str] = None):
"""Get a list of revisions from Phabricator based on their PH-IDs.""" """Get a list of revisions from Phabricator based on their PH-IDs."""
if phids is None: if phids is None:
raise InputError('no arguments given') raise Exception('_get_revisions phids is None')
if phids == []: if not phids:
# Handle an empty query locally. Otherwise the connection # Handle an empty query locally. Otherwise the connection
# will time out. # will time out.
return [] return []
return self.phab.differential.query(phids=phids) return self.phab.differential.query(phids=phids)
def _get_dependencies(self) -> Tuple[int, List[int], str]:
def _get_dependencies(self) -> Tuple[int,List[int],str]:
"""Get all dependencies for the diff.""" """Get all dependencies for the diff."""
diff = self._get_diff(self.diff_id) diff = self._get_diff(self.diff_id)
revision_id = int(diff.revisionID) revision_id = int(diff.revisionID)
@ -143,9 +144,10 @@ class ApplyPatch:
def _apply_diff(self, diff_id: int, revision_id: int): def _apply_diff(self, diff_id: int, revision_id: int):
"""Download and apply a diff to the local working copy.""" """Download and apply a diff to the local working copy."""
print('Applying diff {} for revision {}...'.format(diff_id, diff_to_str(revision_id))) print('Applying diff {} for revision {}...'.format(diff_id, diff_to_str(revision_id)))
diff = self.phab.differential.getrawdiff(diffID=diff_id).response # TODO: print diff or URL to it
diff = self.phab.differential.getrawdiff(diffID=str(diff_id)).response
proc = subprocess.run('patch -p1', input=diff, shell=True, text=True, proc = subprocess.run('patch -p1', input=diff, shell=True, text=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if proc.returncode != 0: if proc.returncode != 0:
raise Exception('Applying patch failed:\n{}'.format(proc.stdout + proc.stderr)) raise Exception('Applying patch failed:\n{}'.format(proc.stdout + proc.stderr))
@ -181,7 +183,7 @@ class ApplyPatch:
"""Check which of the dependencies have already landed on the current branch.""" """Check which of the dependencies have already landed on the current branch."""
landed_deps = [] landed_deps = []
missing_deps = [] missing_deps = []
for dependency in dependencies: for dependency in dependencies:
if diff_to_str(dependency) in self._get_landed_revisions(): if diff_to_str(dependency) in self._get_landed_revisions():
landed_deps.append(dependency) landed_deps.append(dependency)
else: else:
@ -201,12 +203,13 @@ def diff_list_to_str(diffs: List[int]) -> str:
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Apply Phabricator patch to working directory.') parser = argparse.ArgumentParser(description='Apply Phabricator patch to working directory.')
parser.add_argument('diff_id', type=str) parser.add_argument('diff_id', type=int)
# TODO: instead of --comment-file use stdout / stderr.
parser.add_argument('--comment-file', type=str, dest='comment_file_path', default=None) parser.add_argument('--comment-file', type=str, dest='comment_file_path', default=None)
parser.add_argument('--token', type=str, default=None) parser.add_argument('--token', type=str, default=None, help='Conduit API token')
parser.add_argument('--url', type=str, default=None) parser.add_argument('--url', type=str, default=None, help='Phabricator URL')
parser.add_argument('--store-json-diff', dest='store_json_diff', type=str, default=None) parser.add_argument('--commit', dest='commit', type=str, default=None,
help='Use this commit as a base. By default tool tries to pick the base commit itself')
args = parser.parse_args() args = parser.parse_args()
patcher = ApplyPatch(args.diff_id, args.comment_file_path, args.token, args.url, args.store_json_diff) patcher = ApplyPatch(args.diff_id, args.comment_file_path, args.token, args.url, args.commit)
patcher.run() patcher.run()