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.
o3de/scripts/commit_validation/git_validate_branch.py

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()