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