From fa545202c3d5e863cbfcdbe8c50e470bef29192f Mon Sep 17 00:00:00 2001 From: =?utf8?q?=E5=AD=99=E6=98=A5=E9=98=B3?= Date: Mon, 4 Aug 2025 11:48:51 +0800 Subject: [PATCH] =?utf8?q?=E6=B7=BB=E5=8A=A0gitdiff.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- gitdiff.py | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 gitdiff.py diff --git a/gitdiff.py b/gitdiff.py new file mode 100644 index 000000000..48529eae5 --- /dev/null +++ b/gitdiff.py @@ -0,0 +1,212 @@ +import os +import subprocess +import datetime +import sys +from pathlib import Path + +def run_git_command(command): + try: + result = subprocess.run( + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding='utf-8', + errors='replace', + check=True + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Error executing git command: {e.stderr}") + sys.exit(1) + except UnicodeDecodeError as e: + print(f"Encoding error: {e}") + sys.exit(1) + +def main(): + if len(sys.argv) > 3: + print("Usage: python git_changes_extractor.py [commit1 [commit2]]") + print(" If no arguments are provided, uncommitted changes will be extracted") + print(" If only commit1 is provided, it defaults to comparing with HEAD") + sys.exit(1) + + # 处理不同参数情况 + if len(sys.argv) == 1: + commit1 = None + commit2 = None + mode = "uncommitted" + elif len(sys.argv) == 2: + commit1 = sys.argv[1] + commit2 = "HEAD" + mode = "commits" + else: + commit1 = sys.argv[1] + commit2 = sys.argv[2] + mode = "commits" + + # 获取当前仓库路径和名称 + repo_path = Path(os.getcwd()) + repo_name = repo_path.name + + # 创建输出目录 (格式: 仓库名_时间戳) + timestamp = datetime.datetime.now().strftime("%y%m%d%H%M%S") + output_dir_name = f"{repo_name}_{timestamp}" + patches_dir = repo_path.parent / "PATCHES" + output_dir = patches_dir / output_dir_name + output_dir.mkdir(parents=True, exist_ok=True) + + # 创建diff子目录 + diff_dir = output_dir / "diff" + diff_dir.mkdir(exist_ok=True) + + # 获取变更文件列表 + if mode == "uncommitted": + # 获取暂存区变更 + staged_changes = run_git_command( + ["git", "diff", "--name-status", "--cached", "--diff-filter=ACDMRTUXB"] + ).splitlines() + + # 获取工作区变更 + unstaged_changes = run_git_command( + ["git", "diff", "--name-status", "--diff-filter=ACDMRTUXB"] + ).splitlines() + + # 获取未跟踪文件 (新增但未git add的文件) + untracked_files = run_git_command( + ["git", "ls-files", "--others", "--exclude-standard"] + ).splitlines() + untracked_changes = [f"A\t{file}" for file in untracked_files] + + # 合并变更 (暂存区优先,然后工作区,最后未跟踪) + change_list = staged_changes.copy() + + # 添加工作区变更中不在暂存区的文件 + staged_files = {line.split('\t', 1)[1] for line in staged_changes} + for line in unstaged_changes: + file_path = line.split('\t', 1)[1] + if file_path not in staged_files: + change_list.append(line) + + # 添加未跟踪文件 + all_existing_files = {line.split('\t', 1)[1] for line in change_list} + for line in untracked_changes: + file_path = line.split('\t', 1)[1] + if file_path not in all_existing_files: + change_list.append(line) + + # 生成完整的未提交变更diff + full_diff = run_git_command( + ["git", "diff", "--patch", "--cached"] + ) + with open(diff_dir / "uncommitted_changes.diff", "w", encoding='utf-8') as f: + f.write(full_diff) + + # 生成工作区与暂存区差异 + unstaged_diff = run_git_command( + ["git", "diff", "--patch"] + ) + with open(diff_dir / "unstaged_changes.diff", "w", encoding='utf-8') as f: + f.write(unstaged_diff) + else: + # 获取两个提交之间的变更 + change_list = run_git_command( + ["git", "diff", "--name-status", commit1, commit2] + ).splitlines() + + # 生成每个commit的详细diff + commit_hashes = run_git_command( + ["git", "rev-list", "--reverse", f"{commit1}..{commit2}"] + ).splitlines() + + if not commit_hashes: + commit_diff = run_git_command( + ["git", "diff", "--patch", commit1, commit2] + ) + diff_file = diff_dir / f"{commit1[:8]}_{commit2[:8]}.diff" + with open(diff_file, "w", encoding='utf-8') as f: + f.write(commit_diff) + else: + all_commits = [commit1] + commit_hashes + for i in range(len(all_commits) - 1): + parent = all_commits[i] + child = all_commits[i + 1] + + commit_diff = run_git_command( + ["git", "diff", "--patch", parent, child] + ) + diff_file = diff_dir / f"{parent[:8]}_{child[:8]}.diff" + with open(diff_file, "w", encoding='utf-8') as f: + f.write(commit_diff) + + # 分类变更文件 + added_files = [] + modified_files = [] + deleted_files = [] + + for line in change_list: + parts = line.split('\t') + if len(parts) < 2: + continue + status, file_path = parts[0], '\t'.join(parts[1:]) + + if status == 'A': + added_files.append(file_path) + elif status == 'M': + modified_files.append(file_path) + elif status == 'D': + deleted_files.append(file_path) + + # 写入变更列表文件 + with open(output_dir / "gitchangelist.txt", "w", encoding='utf-8') as f: + f.write("Added Files:\n") + for file in added_files: + f.write(f" {file}\n") + + f.write("\nModified Files:\n") + for file in modified_files: + f.write(f" {file}\n") + + f.write("\nDeleted Files:\n") + for file in deleted_files: + f.write(f" {file}\n") + + # 写入git log(如果有提交范围) + if mode == "commits": + git_log = run_git_command( + ["git", "log", "--name-status", f"{commit1}..{commit2}"] + ) + with open(output_dir / "gitlog.txt", "w", encoding='utf-8') as f: + f.write(git_log) + else: + # 未提交变更的简短信息 + with open(output_dir / "gitlog.txt", "w", encoding='utf-8') as f: + f.write("Summary of uncommitted changes\n") + f.write("================================\n\n") + f.write("Changes are relative to HEAD:\n") + f.write(run_git_command(["git", "status", "--short"])) + + # 复制变更文件(不包括已删除的) + for file_path in added_files + modified_files: + source_file = repo_path / file_path + target_file = output_dir / file_path + + target_file.parent.mkdir(parents=True, exist_ok=True) + + try: + # 直接从工作区复制,因为未跟踪文件在工作区中存在 + if os.path.exists(source_file): + with open(source_file, "rb") as src, open(target_file, "wb") as dst: + dst.write(src.read()) + else: + # 从暂存区提取文件内容 + blob_content = run_git_command( + ["git", "show", f":{file_path}"] + ) + with open(target_file, "w", encoding='utf-8') as dst: + dst.write(blob_content) + except Exception as e: + print(f"Error copying file {file_path}: {e}") + + print(f"Changes saved to: {output_dir}") + +if __name__ == "__main__": + main() \ No newline at end of file -- 2.17.1