git_diff_status_show.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #
  2. # Copyright (c) 2025, RT-Thread Development Team
  3. #
  4. # SPDX-License-Identifier: Apache-2.0
  5. #
  6. # Change Logs:
  7. # Date Author Notes
  8. # 2025-05-15 supperthomas add PR status show
  9. #
  10. #!/usr/bin/env python3
  11. import subprocess
  12. import sys
  13. import os
  14. import re
  15. import argparse
  16. import locale
  17. from typing import List, Dict
  18. class FileDiff:
  19. def __init__(self, path: str, status: str, size_change: int = 0, old_size: int = 0, new_size: int = 0):
  20. self.path = path
  21. self.status = status # A (added), M (modified), D (deleted), R (renamed)
  22. self.size_change = size_change
  23. self.old_size = old_size
  24. self.new_size = new_size
  25. def __str__(self):
  26. if self.status == 'A':
  27. return f"Added {self.path}: {self.size_change} bytes"
  28. elif self.status == 'D':
  29. return f"Deleted {self.path}: was {self.old_size} bytes"
  30. elif self.status == 'M' or self.status == 'R':
  31. return f"Modified {self.path}: {self.size_change} bytes change"
  32. else:
  33. return f"{self.status} {self.path}"
  34. class GitDiffAnalyzer:
  35. def __init__(self, target_branch: str):
  36. self.target_branch = target_branch
  37. self.encoding = locale.getpreferredencoding()
  38. def get_diff_files(self) -> List[FileDiff]:
  39. """获取当前分支与目标分支之间的差异文件"""
  40. # 找到当前分支和目标分支的最近共同祖先
  41. merge_base = self.get_merge_base()
  42. if not merge_base:
  43. print("No common ancestor found between current branch and target branch")
  44. sys.exit(1)
  45. # 获取差异文件列表
  46. diff_cmd = f"git diff --name-status {merge_base} "
  47. print(diff_cmd)
  48. try:
  49. output = subprocess.check_output(diff_cmd.split(), stderr=subprocess.STDOUT)
  50. output = output.decode(self.encoding).strip()
  51. print(output)
  52. except subprocess.CalledProcessError as e:
  53. print(f"Error executing git diff: {e.output.decode(self.encoding)}")
  54. sys.exit(1)
  55. if not output:
  56. print("No differences between current branch and target branch")
  57. sys.exit(0)
  58. # 处理可能的换行符问题
  59. output = output.replace('\r\n', '\n')
  60. lines = output.split('\n')
  61. file_diffs = []
  62. for line in lines:
  63. line = line.strip()
  64. if not line:
  65. continue
  66. parts = line.split('\t')
  67. if len(parts) < 2:
  68. # 可能是重命名文件,格式为 "RXXX\told_path\tnew_path"
  69. match = re.match(r'R(\d+)\t(.+)\t(.+)', line)
  70. if match:
  71. status = 'R'
  72. old_path = match.group(2)
  73. new_path = match.group(3)
  74. # 计算重命名文件的修改大小
  75. old_size = self.get_file_size(old_path, self.target_branch)
  76. new_size = self.get_file_size(new_path, 'HEAD')
  77. size_change = new_size - old_size if old_size > 0 else new_size
  78. file_diffs.append(FileDiff(new_path, status, size_change, old_size, new_size))
  79. else:
  80. status = parts[0][0] # 取状态码的第一个字符
  81. path = parts[1]
  82. if status == 'A':
  83. # 新增文件,计算大小
  84. size = self.get_file_size(path, 'HEAD')
  85. file_diffs.append(FileDiff(path, status, size, 0, size))
  86. elif status == 'D':
  87. # 删除文件,计算原大小
  88. size = self.get_file_size(path, self.target_branch)
  89. file_diffs.append(FileDiff(path, status, 0, size, 0))
  90. elif status == 'M':
  91. # 修改文件,计算大小变化
  92. old_size = self.get_file_size(path, self.target_branch)
  93. new_size = self.get_file_size(path, 'HEAD')
  94. size_change = new_size - old_size
  95. file_diffs.append(FileDiff(path, status, size_change, old_size, new_size))
  96. return file_diffs
  97. def get_merge_base(self) -> str:
  98. """获取当前分支和目标分支的最近共同祖先"""
  99. try:
  100. cmd = f"git merge-base {self.target_branch} HEAD"
  101. print(cmd)
  102. output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
  103. return output.decode(self.encoding).strip()
  104. except subprocess.CalledProcessError as e:
  105. print(f"Error executing git merge-base: {e.output.decode(self.encoding)}")
  106. return None
  107. def get_file_size(self, path: str, ref: str) -> int:
  108. """获取指定分支上文件的大小"""
  109. try:
  110. # 使用 git cat-file 来获取文件内容,然后计算其大小
  111. cmd = f"git cat-file blob {ref}:{path}"
  112. output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
  113. return len(output)
  114. except subprocess.CalledProcessError:
  115. # 如果文件不存在或无法获取,返回0
  116. return 0
  117. def format_size(size: int) -> str:
  118. """将字节大小转换为人类可读的格式"""
  119. if size < 1024:
  120. return f"{size} bytes"
  121. elif size < 1024 * 1024:
  122. return f"{size / 1024:.1f} KB"
  123. elif size < 1024 * 1024 * 1024:
  124. return f"{size / (1024 * 1024):.1f} MB"
  125. else:
  126. return f"{size / (1024 * 1024 * 1024):.1f} GB"
  127. def main():
  128. parser = argparse.ArgumentParser(description='Compare current branch with target branch and show file differences.')
  129. parser.add_argument('target_branch', help='Target branch to compare against (e.g., master)')
  130. args = parser.parse_args()
  131. analyzer = GitDiffAnalyzer(args.target_branch)
  132. file_diffs = analyzer.get_diff_files()
  133. # 生成报告
  134. generate_report(file_diffs, args.target_branch)
  135. def generate_report(file_diffs: List[FileDiff], target_branch: str):
  136. """生成差异报告"""
  137. print(f"\n=== Comparison between {target_branch} and current branch ===\n")
  138. # 分类统计
  139. added_files = [f for f in file_diffs if f.status == 'A']
  140. removed_files = [f for f in file_diffs if f.status == 'D']
  141. modified_files = [f for f in file_diffs if f.status == 'M']
  142. renamed_files = [f for f in file_diffs if f.status == 'R']
  143. # 计算总变化量
  144. total_added = sum(f.size_change for f in added_files)
  145. total_removed = sum(f.old_size for f in removed_files)
  146. total_modified = sum(abs(f.size_change) for f in modified_files)
  147. # 计算总的大小变化
  148. total_size_change = sum(f.size_change for f in file_diffs)
  149. print(f"Total changes: {len(file_diffs)} files")
  150. print(f"Added: {len(added_files)} files ({format_size(total_added)})")
  151. print(f"Removed: {len(removed_files)} files ({format_size(total_removed)})")
  152. print(f"Modified: {len(modified_files)} files ({format_size(total_modified)})")
  153. print(f"Renamed: {len(renamed_files)} files")
  154. print(f"\nTotal size change: {format_size(total_size_change)}")
  155. print("\n" + "="*60 + "\n")
  156. # 显示详细差异
  157. for diff in file_diffs:
  158. print(diff)
  159. # 详细展示修改和新增的文件大小
  160. if diff.status == 'A':
  161. print(f" Size: {format_size(diff.new_size)}")
  162. elif diff.status == 'M':
  163. print(f" Original size: {format_size(diff.old_size)}")
  164. print(f" New size: {format_size(diff.new_size)}")
  165. print(f" Size change: {format_size(abs(diff.size_change))}")
  166. elif diff.status == 'R':
  167. print(f" Original size: {format_size(diff.old_size)}")
  168. print(f" New size: {format_size(diff.new_size)}")
  169. print(f" Size change: {format_size(abs(diff.size_change))}")
  170. elif diff.status == 'D':
  171. print(f" Original size: {format_size(diff.old_size)}")
  172. print("-" * 50)
  173. if __name__ == "__main__":
  174. main()