git_utils.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  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 subprocess
  9. import git
  10. import pathlib
  11. # Basic representation of a git repository
  12. class Repo:
  13. def __init__(self, repo_path: str):
  14. self._repo = git.Repo(repo_path)
  15. self._remote_url = self._repo.remotes[0].config_reader.get("url")
  16. # Returns the current branch
  17. @property
  18. def current_branch(self):
  19. branch = self._repo.active_branch
  20. return branch.name
  21. # Returns the remote URL
  22. @property
  23. def remote_url(self):
  24. return self._remote_url
  25. def create_diff_file(self, src_commit_hash: str, dst_commit_hash: str, output_path: pathlib.Path, multi_branch: bool):
  26. """
  27. Attempts to create a diff from the src and dst commits and write to the specified output file.
  28. @param src_commit_hash: The hash for the source commit.
  29. @param dst_commit_hash: The hash for the destination commit.
  30. @param multi_branch: The two commits are on different branches so view the changes on the
  31. branch containing and up to dst_commit, starting at a common ancestor of both.
  32. @param output_path: The path to the file to write the diff to.
  33. """
  34. try:
  35. # Remove the existing file (if any) and create the parent directory
  36. # output_path.unlink(missing_ok=True) # missing_ok is only available in Python 3.8+
  37. if output_path.is_file():
  38. output_path.unlink()
  39. output_path.parent.mkdir(parents=True, exist_ok=True)
  40. except EnvironmentError as e:
  41. raise RuntimeError(f"Could not create path for output file '{output_path}'")
  42. args = ["git", "diff", "--name-status", f"--output={output_path}"]
  43. if multi_branch:
  44. args.append(f"{src_commit_hash}...{dst_commit_hash}")
  45. else:
  46. args.append(src_commit_hash)
  47. args.append(dst_commit_hash)
  48. # git diff will only write to the output file if both commit hashes are valid
  49. subprocess.run(args)
  50. if not output_path.is_file():
  51. raise RuntimeError(f"Source commit '{src_commit_hash}' and/or destination commit '{dst_commit_hash}' are invalid")
  52. def is_descendent(self, src_commit_hash: str, dst_commit_hash: str):
  53. """
  54. Determines whether or not dst_commit is a descendent of src_commit.
  55. @param src_commit_hash: The hash for the source commit.
  56. @param dst_commit_hash: The hash for the destination commit.
  57. @return: True if the dst commit descends from the src commit, otherwise False.
  58. """
  59. if not src_commit_hash and not dst_commit_hash:
  60. return False
  61. result = subprocess.run(["git", "merge-base", "--is-ancestor", src_commit_hash, dst_commit_hash])
  62. return result.returncode == 0
  63. # Returns the distance between two commits
  64. def commit_distance(self, src_commit_hash: str, dst_commit_hash: str):
  65. """
  66. Determines the number of commits between src_commit and dst_commit.
  67. @param src_commit_hash: The hash for the source commit.
  68. @param dst_commit_hash: The hash for the destination commit.
  69. @return: The distance between src_commit and dst_commit (if both are valid commits), otherwise None.
  70. """
  71. if not src_commit_hash and not dst_commit_hash:
  72. return None
  73. commits = self._repo.iter_commits(src_commit_hash + '..' + dst_commit_hash)
  74. return len(list(commits))