--- /dev/null
+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