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
+ print (cmd )
114
+ output = subprocess .check_output (cmd .split (), stderr = subprocess .STDOUT )
115
+ return output .decode (self .encoding ).strip ()
116
+ except subprocess .CalledProcessError as e :
117
+ print (f"Error executing git merge-base: { e .output .decode (self .encoding )} " )
118
+ return None
119
+
120
+ def get_file_size (self , path : str , ref : str ) -> int :
121
+ """获取指定分支上文件的大小"""
122
+ try :
123
+ # 使用 git cat-file 来获取文件内容,然后计算其大小
124
+ cmd = f"git cat-file blob { ref } :{ path } "
125
+ output = subprocess .check_output (cmd .split (), stderr = subprocess .STDOUT )
126
+ return len (output )
127
+ except subprocess .CalledProcessError :
128
+ # 如果文件不存在或无法获取,返回0
129
+ return 0
130
+
131
+ def format_size (size : int ) -> str :
132
+ """将字节大小转换为人类可读的格式"""
133
+ if size < 1024 :
134
+ return f"{ size } bytes"
135
+ elif size < 1024 * 1024 :
136
+ return f"{ size / 1024 :.1f} KB"
137
+ elif size < 1024 * 1024 * 1024 :
138
+ return f"{ size / (1024 * 1024 ):.1f} MB"
139
+ else :
140
+ return f"{ size / (1024 * 1024 * 1024 ):.1f} GB"
141
+
142
+ def main ():
143
+ parser = argparse .ArgumentParser (description = 'Compare current branch with target branch and show file differences.' )
144
+ parser .add_argument ('target_branch' , help = 'Target branch to compare against (e.g., master)' )
145
+ args = parser .parse_args ()
146
+
147
+ analyzer = GitDiffAnalyzer (args .target_branch )
148
+ file_diffs = analyzer .get_diff_files ()
149
+
150
+ # 生成报告
151
+ generate_report (file_diffs , args .target_branch )
152
+
153
+
154
+
155
+ def generate_report (file_diffs : List [FileDiff ], target_branch : str ):
156
+ """生成差异报告"""
157
+ print (f"\n === Comparison between { target_branch } and current branch ===\n " )
158
+
159
+ # 分类统计
160
+ added_files = [f for f in file_diffs if f .status == 'A' ]
161
+ removed_files = [f for f in file_diffs if f .status == 'D' ]
162
+ modified_files = [f for f in file_diffs if f .status == 'M' ]
163
+ renamed_files = [f for f in file_diffs if f .status == 'R' ]
164
+
165
+ # 计算总变化量
166
+ total_added = sum (f .size_change for f in added_files )
167
+ total_removed = sum (f .old_size for f in removed_files )
168
+ total_modified = sum (abs (f .size_change ) for f in modified_files )
169
+ # 计算总的大小变化
170
+ total_size_change = sum (f .size_change for f in file_diffs )
171
+
172
+ print (f"Total changes: { len (file_diffs )} files" )
173
+ print (f"Added: { len (added_files )} files ({ format_size (total_added )} )" )
174
+ print (f"Removed: { len (removed_files )} files ({ format_size (total_removed )} )" )
175
+ print (f"Modified: { len (modified_files )} files ({ format_size (total_modified )} )" )
176
+ print (f"Renamed: { len (renamed_files )} files" )
177
+ print (f"\n Total size change: { format_size (total_size_change )} " )
178
+ print ("\n " + "=" * 60 + "\n " )
179
+
180
+ # 显示详细差异
181
+ for diff in file_diffs :
182
+ print (diff )
183
+
184
+ # 详细展示修改和新增的文件大小
185
+ if diff .status == 'A' :
186
+ print (f" Size: { format_size (diff .new_size )} " )
187
+ elif diff .status == 'M' :
188
+ print (f" Original size: { format_size (diff .old_size )} " )
189
+ print (f" New size: { format_size (diff .new_size )} " )
190
+ print (f" Size change: { format_size (abs (diff .size_change ))} " )
191
+ elif diff .status == 'R' :
192
+ print (f" Original size: { format_size (diff .old_size )} " )
193
+ print (f" New size: { format_size (diff .new_size )} " )
194
+ print (f" Size change: { format_size (abs (diff .size_change ))} " )
195
+ elif diff .status == 'D' :
196
+ print (f" Original size: { format_size (diff .old_size )} " )
197
+
198
+ print ("-" * 50 )
199
+
200
+ if __name__ == "__main__" :
201
+ main ()
0 commit comments