You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
4.5 KiB
Python
115 lines
4.5 KiB
Python
#
|
|
# Copyright (c) Contributors to the Open 3D Engine Project.
|
|
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
#
|
|
#
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from typing import Dict, List
|
|
import difflib
|
|
|
|
from commit_validation.commit_validation import Commit, validate_commit
|
|
import git
|
|
|
|
|
|
class GitChange(Commit):
|
|
"""An implementation of the :class:`Commit` interface for accessing details about a git change"""
|
|
|
|
def __init__(self, source: str = None, target: str = 'origin/main') -> None:
|
|
"""Creates a new instance of :class:`GitChange`
|
|
|
|
:param source: source of the change, e.g. commit hash or branch
|
|
:param target: target of the change, e.g. main branch
|
|
"""
|
|
root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
|
self.repo = git.Repo()
|
|
self.git_root_path = self.repo.git.rev_parse("--show-toplevel")
|
|
if source:
|
|
git.cmd.Git().fetch('origin', source) # So we can compare it
|
|
self.source_commit = self.repo.commit(target)
|
|
else:
|
|
self.source_commit = self.repo.commit()
|
|
|
|
if 'origin' in target:
|
|
origin_target = target.replace('origin/','')
|
|
git.cmd.Git().fetch('origin', origin_target) # So we can compare it
|
|
self.target_commit = self.repo.commit(target)
|
|
|
|
# We only want to run the verification on the changes introduced by the
|
|
# branch being merged in. Find the merge base (the common ancestor) of
|
|
# the two commits, and then get the diff from merge_base..target_commit
|
|
self.merge_base = self.repo.merge_base(self.source_commit, self.target_commit)
|
|
if not len(self.merge_base) == 1:
|
|
raise RuntimeError(f"Cannot find the merge base of {self.source_commit} and {self.target_commit}")
|
|
self.merge_base = self.merge_base[0]
|
|
self.diff_index = self.merge_base.diff(self.source_commit)
|
|
|
|
print(f"Running validation from '{source}' ({self.source_commit}) to '{target}' ({self.target_commit}) using baseline {self.merge_base}")
|
|
|
|
# Cache the file lists since they are requested by each validator
|
|
self.files_list: List[str] = []
|
|
self.removed_files_list: List[str] = []
|
|
|
|
for diff_item in self.diff_index:
|
|
# 'A' for added paths
|
|
# 'C' for changed paths
|
|
# 'R' for renamed paths
|
|
# 'M' for paths with modified data
|
|
# 'T' for changed in the type paths
|
|
# 'D' for deleted paths
|
|
# 'R' for renamed paths
|
|
if diff_item.change_type in ('A', 'C', 'R', 'M', 'T'):
|
|
self.files_list.append(os.path.abspath(os.path.join(self.git_root_path, diff_item.b_path)))
|
|
if diff_item.change_type in ('D', 'R'):
|
|
self.removed_files_list.append(os.path.abspath(os.path.join(self.git_root_path, diff_item.a_path)))
|
|
|
|
def get_files(self) -> List[str]:
|
|
"""Returns a list of local files added/modified by the commit"""
|
|
return self.files_list
|
|
|
|
def get_removed_files(self) -> List[str]:
|
|
"""Returns a list of local files removed by the commit"""
|
|
return self.removed_files_list
|
|
|
|
def get_file_diff(self, str) -> str:
|
|
"""
|
|
Given a file name, returns a string in unified diff format
|
|
that represents the changes made to that file for this commit.
|
|
Most validators will only pay attention to added lines (with + in front)
|
|
"""
|
|
diff = self.repo.git.diff(self.merge_base, self.source_commit, str)
|
|
return diff
|
|
|
|
def get_description(self) -> str:
|
|
"""Returns the description of the commit"""
|
|
return self.target_commit.message
|
|
|
|
def get_author(self) -> str:
|
|
"""Returns the author of the commit"""
|
|
return self.target_commit.author
|
|
|
|
def init_parser():
|
|
"""Prepares the command line parser"""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--source', default=None, help='Change source (e.g. commit hash or branch), defaults to active branch')
|
|
parser.add_argument('--target', default='origin/main', help='Change target, defaults to "origin/main"')
|
|
return parser
|
|
|
|
def main():
|
|
parser = init_parser()
|
|
args = parser.parse_args()
|
|
|
|
change = GitChange(source=args.source, target=args.target)
|
|
|
|
if not validate_commit(commit=change, ignore_validators=["NewlineValidator", "WhitespaceValidator"]):
|
|
sys.exit(1)
|
|
|
|
sys.exit(0)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|