git_validate_branch.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. import argparse
  9. import os
  10. import sys
  11. from typing import Dict, List
  12. import difflib
  13. from commit_validation.commit_validation import Commit, validate_commit
  14. import git
  15. class GitChange(Commit):
  16. """An implementation of the :class:`Commit` interface for accessing details about a git change"""
  17. def __init__(self, source: str = None, target: str = 'origin/main') -> None:
  18. """Creates a new instance of :class:`GitChange`
  19. :param source: source of the change, e.g. commit hash or branch
  20. :param target: target of the change, e.g. main branch
  21. """
  22. root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
  23. self.repo = git.Repo()
  24. self.git_root_path = self.repo.git.rev_parse("--show-toplevel")
  25. if source:
  26. git.cmd.Git().fetch('origin', source) # So we can compare it
  27. self.source_commit = self.repo.commit(target)
  28. else:
  29. self.source_commit = self.repo.commit()
  30. if 'origin' in target:
  31. origin_target = target.replace('origin/','')
  32. git.cmd.Git().fetch('origin', origin_target) # So we can compare it
  33. self.target_commit = self.repo.commit(target)
  34. # We only want to run the verification on the changes introduced by the
  35. # branch being merged in. Find the merge base (the common ancestor) of
  36. # the two commits, and then get the diff from merge_base..target_commit
  37. self.merge_base = self.repo.merge_base(self.source_commit, self.target_commit)
  38. if not len(self.merge_base) == 1:
  39. raise RuntimeError(f"Cannot find the merge base of {self.source_commit} and {self.target_commit}")
  40. self.merge_base = self.merge_base[0]
  41. self.diff_index = self.merge_base.diff(self.source_commit)
  42. print(f"Running validation from '{source}' ({self.source_commit}) to '{target}' ({self.target_commit}) using baseline {self.merge_base}")
  43. # Cache the file lists since they are requested by each validator
  44. self.files_list: List[str] = []
  45. self.removed_files_list: List[str] = []
  46. for diff_item in self.diff_index:
  47. # 'A' for added paths
  48. # 'C' for changed paths
  49. # 'R' for renamed paths
  50. # 'M' for paths with modified data
  51. # 'T' for changed in the type paths
  52. # 'D' for deleted paths
  53. # 'R' for renamed paths
  54. if diff_item.change_type in ('A', 'C', 'R', 'M', 'T'):
  55. self.files_list.append(os.path.abspath(os.path.join(self.git_root_path, diff_item.b_path)))
  56. if diff_item.change_type in ('D', 'R'):
  57. self.removed_files_list.append(os.path.abspath(os.path.join(self.git_root_path, diff_item.a_path)))
  58. def get_files(self) -> List[str]:
  59. """Returns a list of local files added/modified by the commit"""
  60. return self.files_list
  61. def get_removed_files(self) -> List[str]:
  62. """Returns a list of local files removed by the commit"""
  63. return self.removed_files_list
  64. def get_file_diff(self, str) -> str:
  65. """
  66. Given a file name, returns a string in unified diff format
  67. that represents the changes made to that file for this commit.
  68. Most validators will only pay attention to added lines (with + in front)
  69. """
  70. diff = self.repo.git.diff(self.merge_base, self.source_commit, str)
  71. return diff
  72. def get_description(self) -> str:
  73. """Returns the description of the commit"""
  74. return self.target_commit.message
  75. def get_author(self) -> str:
  76. """Returns the author of the commit"""
  77. return self.target_commit.author
  78. def init_parser():
  79. """Prepares the command line parser"""
  80. parser = argparse.ArgumentParser()
  81. parser.add_argument('--source', default=None, help='Change source (e.g. commit hash or branch), defaults to active branch')
  82. parser.add_argument('--target', default='origin/main', help='Change target, defaults to "origin/main"')
  83. return parser
  84. def main():
  85. parser = init_parser()
  86. args = parser.parse_args()
  87. change = GitChange(source=args.source, target=args.target)
  88. if not validate_commit(commit=change, ignore_validators=["NewlineValidator", "WhitespaceValidator"]):
  89. sys.exit(1)
  90. sys.exit(0)
  91. if __name__ == '__main__':
  92. main()