diff --git a/gittools.py b/gittools.py deleted file mode 100644 index 20239c3..0000000 --- a/gittools.py +++ /dev/null @@ -1,590 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Git 自动同步工具 -用法: python gitrun.py [命令] - -命令说明: - push - 强制推送本地到远程(直接覆盖,保留历史记录) - pull - 强制拉取远程到本地(直接覆盖,保留历史记录) - sync - 智能同步(先拉取合并,再推送) - log - 查看提交历史并支持快速回退版本 - history - 查看完整提交历史 - config - 配置远程仓库地址 - - - - -重要说明: - • push/pull 都会保留完整的 Git 历史记录,可以随时回退 - • push 是纯粹的本地覆盖远程,不会先拉取远程内容 - • pull 是纯粹的远程覆盖本地,不会保留本地未推送的修改 - • sync 才会进行智能合并 - • log 命令可以查看历史并直接回退版本(输入数字选择,0或回车跳过) - • 所有历史提交都保存在 .git 目录中,永不丢失 -""" - -import os -import sys -import subprocess -import json -from pathlib import Path -from datetime import datetime - -CONFIG_FILE = Path.home() / '.gitrun_config.json' - - -class GitSyncTool: - def __init__(self): - self.config = self.load_config() - self.repo_path = os.getcwd() - - def load_config(self): - """加载配置文件""" - if CONFIG_FILE.exists(): - with open(CONFIG_FILE, 'r', encoding='utf-8') as f: - return json.load(f) - return {} - - def save_config(self): - """保存配置文件""" - with open(CONFIG_FILE, 'w', encoding='utf-8') as f: - json.dump(self.config, f, indent=2, ensure_ascii=False) - - def run_command(self, cmd, check=True, silent=False): - """执行命令并返回结果""" - try: - # Windows 上使用 GBK 编码,其他系统使用 UTF-8 - encoding = 'gbk' if sys.platform == 'win32' else 'utf-8' - - result = subprocess.run( - cmd, - shell=True, - cwd=self.repo_path, - capture_output=True, - text=True, - encoding=encoding, - errors='ignore' # 忽略无法解码的字符 - ) - if check and result.returncode != 0: - if not silent: - print(f"❌ 命令执行失败: {cmd}") - if result.stderr: - print(f"错误信息: {result.stderr}") - return None - return result - except Exception as e: - if not silent: - print(f"❌ 执行命令时出错: {e}") - return None - - def is_git_repo(self): - """检查当前目录是否是 Git 仓库""" - result = self.run_command("git rev-parse --is-inside-work-tree", check=False, silent=True) - return result and result.returncode == 0 - - def get_current_branch(self): - """获取当前分支名""" - result = self.run_command("git branch --show-current", silent=True) - return result.stdout.strip() if result else None - - def has_changes(self): - """检查是否有未提交的更改""" - result = self.run_command("git status --porcelain", silent=True) - return bool(result and result.stdout.strip()) - - def get_remote_url(self): - """获取远程仓库地址""" - result = self.run_command("git remote get-url origin", check=False, silent=True) - return result.stdout.strip() if result and result.returncode == 0 else None - - def config_remote(self): - """配置远程仓库地址""" - print("🔧 配置远程仓库地址") - - # 检查是否是 Git 仓库,如果不是则初始化 - if not self.is_git_repo(): - print("⚠️ 当前目录不是 Git 仓库") - init = input("是否初始化为 Git 仓库? (y/n): ").strip().lower() - if init == 'y': - result = self.run_command("git init") - if result: - print("✅ Git 仓库初始化成功") - else: - print("❌ Git 仓库初始化失败") - return - else: - print("❌ 操作已取消") - return - - current_url = self.get_remote_url() - if current_url: - print(f"当前远程仓库: {current_url}") - change = input("是否修改? (y/n): ").strip().lower() - if change != 'y': - return - - remote_url = input("请输入远程仓库地址 (如: https://github.com/username/repo.git): ").strip() - - if not remote_url: - print("❌ 远程仓库地址不能为空") - return - - # 检查是否已有 origin - if current_url: - result = self.run_command(f"git remote set-url origin {remote_url}") - else: - result = self.run_command(f"git remote add origin {remote_url}") - - if result: - print(f"✅ 远程仓库配置成功: {remote_url}") - - # 保存到配置文件 - repo_name = os.path.basename(self.repo_path) - if repo_name not in self.config: - self.config[repo_name] = {} - self.config[repo_name]['remote_url'] = remote_url - self.save_config() - - # 询问是否设置默认分支 - print("\n💡 提示: 首次推送前需要设置默认分支") - set_branch = input("是否设置默认分支为 main? (y/n): ").strip().lower() - if set_branch == 'y': - # 检查当前分支 - current_branch = self.get_current_branch() - if not current_branch: - # 创建初始提交 - print("\n📝 创建初始提交...") - self.run_command("git add .") - self.run_command('git commit -m "Initial commit" --allow-empty') - self.run_command("git branch -M main") - print("✅ 默认分支已设置为 main") - elif current_branch != 'main': - self.run_command("git branch -M main") - print("✅ 分支已重命名为 main") - else: - print("✅ 当前分支已是 main") - - def commit_changes(self, message=None): - """提交本地更改""" - if not self.has_changes(): - print("ℹ️ 没有需要提交的更改") - return True - - print("\n📝 检测到以下更改:") - self.run_command("git status --short") - - if message is None: - message = input("\n请输入提交信息: ").strip() - - if not message: - print("❌ 提交信息不能为空") - return False - - # 添加所有更改 - print("\n📦 正在暂存所有更改...") - result = self.run_command("git add -A") - if not result: - return False - - # 提交更改 - print(f"💾 正在提交: {message}") - result = self.run_command(f'git commit -m "{message}"') - if result: - print(f"✅ 提交成功") - return True - return False - - def force_push(self): - """纯粹的本地覆盖远程(不拉取,直接强制推送)""" - print("🚀 强制推送本地到远程") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - branch = self.get_current_branch() - if not branch: - print("❌ 无法获取当前分支") - return - - print(f"📍 当前分支: {branch}") - - # 检查远程仓库 - remote_url = self.get_remote_url() - if not remote_url: - print("⚠️ 未配置远程仓库") - self.config_remote() - remote_url = self.get_remote_url() - if not remote_url: - return - - print(f"🌐 远程仓库: {remote_url}") - - # 提交本地更改 - print() - if not self.commit_changes(): # 改为交互式提交,不自动 - print("❌ 提交失败,推送中止") - return - - # 强制推送(不拉取远程) - print(f"\n⬆️ 正在强制推送到远程 ({branch})...") - result = self.run_command(f"git push -f origin {branch}") - - if result: - print(f"\n✅ 推送成功!") - else: - print("\n❌ 推送失败") - - def force_pull(self): - """纯粹的远程覆盖本地(不保留本地未推送的修改)""" - print("⬇️ 强制拉取远程到本地") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - branch = self.get_current_branch() - if not branch: - print("❌ 无法获取当前分支") - return - - print(f"📍 当前分支: {branch}") - - # 检查远程仓库 - remote_url = self.get_remote_url() - if not remote_url: - print("⚠️ 未配置远程仓库") - self.config_remote() - return - - print(f"🌐 远程仓库: {remote_url}") - - # 检查本地更改 - if self.has_changes(): - print("\n⚠️ 检测到未提交的本地更改,这些更改将会被丢弃") - self.run_command("git status --short") - - # 先获取远程最新信息 - print(f"\n📡 正在获取远程仓库信息...") - self.run_command("git fetch origin") - - # 强制重置到远程版本 - print(f"⬇️ 正在强制拉取远程 ({branch})...") - self.run_command(f"git reset --hard origin/{branch}") - self.run_command("git clean -fd") - - print(f"\n✅ 拉取成功!") - - def smart_sync(self): - """智能同步(先拉取合并,再推送)""" - print("🔄 智能同步") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - branch = self.get_current_branch() - if not branch: - print("❌ 无法获取当前分支") - return - - print(f"📍 当前分支: {branch}") - - # 检查远程仓库 - remote_url = self.get_remote_url() - if not remote_url: - print("⚠️ 未配置远程仓库") - self.config_remote() - return - - print(f"🌐 远程仓库: {remote_url}") - - # 提交本地更改 - print() - if self.has_changes(): - if not self.commit_changes(): # 改为交互式提交 - print("❌ 提交失败") - return - - # 拉取远程更改 - print(f"\n⬇️ 正在拉取远程更新 ({branch})...") - result = self.run_command(f"git pull origin {branch}", check=False) - - if result and result.returncode != 0: - # 如果有冲突 - if "conflict" in result.stderr.lower() or "conflict" in result.stdout.lower(): - print("\n⚠️ 检测到合并冲突!") - print("\n冲突文件:") - self.run_command("git diff --name-only --diff-filter=U") - - choice = input("\n选择操作:\n1. 保留本地版本\n2. 保留远程版本\n3. 手动解决后继续\n请选择 (1/2/3): ").strip() - - if choice == '1': - self.run_command("git checkout --ours .") - self.run_command("git add -A") - self.run_command('git commit --no-edit') - print("✅ 已保留本地版本") - elif choice == '2': - self.run_command("git checkout --theirs .") - self.run_command("git add -A") - self.run_command('git commit --no-edit') - print("✅ 已保留远程版本") - else: - print("ℹ️ 请手动解决冲突后执行: git add . && git commit") - return - else: - print("❌ 拉取失败") - return - else: - print("✅ 拉取成功") - - # 推送到远程 - print(f"\n⬆️ 正在推送到远程 ({branch})...") - push_result = self.run_command(f"git push origin {branch}") - - if push_result: - print(f"\n✅ 同步成功!") - else: - print("⚠️ 推送失败或无新内容需要推送") - - def show_log(self, num=10): - """查看提交历史并支持直接回退""" - print(f"📋 最近 {num} 次提交历史") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - branch = self.get_current_branch() - print(f"📍 当前分支: {branch}\n") - - # 使用 git log 的简单格式,然后解析 - result = self.run_command(f'git log -{num} --format="COMMIT_START%nHASH:%H%nSUBJECT:%s%nAUTHOR:%an%nDATE:%ar%nCOMMIT_END"') - - if not result: - return - - commits = result.stdout.strip().split('COMMIT_START') - commit_hashes = [] - idx = 1 - - for commit in commits: - if not commit.strip(): - continue - - lines = commit.strip().split('\n') - commit_info = {} - for line in lines: - if ':' in line: - key, value = line.split(':', 1) - commit_info[key] = value.strip() - - if 'HASH' in commit_info: - hash_full = commit_info['HASH'] - hash_short = hash_full[:7] - subject = commit_info.get('SUBJECT', '') - date = commit_info.get('DATE', '') - - commit_hashes.append(hash_full) - print(f"{idx}. [{hash_short}] {subject} -- {date}") - idx += 1 - - # 询问是否回退 - print() - choice = input("是否回退版本? (输入数字1-10回退,直接回车或输入0跳过): ").strip() - - if not choice or choice == '0': - print("✅ 未进行回退") - return - - # 判断是数字还是哈希值 - if choice.isdigit(): - idx = int(choice) - 1 - if 0 <= idx < len(commit_hashes): - commit_hash = commit_hashes[idx][:7] - else: - print(f"❌ 无效的数字,请输入 1-{len(commit_hashes)}") - return - else: - commit_hash = choice - - # 验证提交是否存在 - verify = self.run_command(f"git cat-file -t {commit_hash}", check=False, silent=True) - if not verify or verify.returncode != 0: - print(f"❌ 提交 {commit_hash} 不存在") - return - - # 显示目标提交信息 - print(f"\n📍 即将回退到: {commit_hash}") - - # 选择回退方式 - print("\n回退方式:") - print("1. 软回退 (保留工作区和暂存区的更改)") - print("2. 混合回退 (保留工作区更改,清空暂存区) [推荐]") - print("3. 硬回退 (完全回退,丢弃所有更改)") - - mode_choice = input("\n请选择 (1/2/3,直接回车默认选2): ").strip() - - if not mode_choice: - mode_choice = '2' - - mode_map = { - '1': ('--soft', '软回退'), - '2': ('--mixed', '混合回退'), - '3': ('--hard', '硬回退') - } - - if mode_choice not in mode_map: - print("❌ 无效选择") - return - - mode, mode_name = mode_map[mode_choice] - - # 执行回退 - print(f"\n⏮️ 正在执行{mode_name}...") - result = self.run_command(f"git reset {mode} {commit_hash}") - - if result: - print(f"\n✅ {mode_name}成功!") - print(f"📍 已回退到: {commit_hash}\n") - self.run_command("git status --short") - else: - print(f"\n❌ 回退失败") - - def show_full_history(self): - """查看完整提交历史""" - print("📚 完整提交历史") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - branch = self.get_current_branch() - print(f"📍 当前分支: {branch}\n") - - # 显示完整历史(简化格式以兼容 Windows) - self.run_command("git log --graph --oneline --decorate --all") - - def rollback(self): - """回退到指定版本""" - print("⏮️ 版本回退") - - if not self.is_git_repo(): - print("❌ 当前目录不是 Git 仓库") - return - - # 显示最近10次提交 - print("📋 最近10次提交:\n") - result = self.run_command("git log -10 --pretty=format:%H|%s|%an|%ar") - - if not result: - return - - lines = result.stdout.strip().split('\n') - commits = [] - - for i, line in enumerate(lines, 1): - if '|' in line: - hash_full, subject, author, date = line.split('|', 3) - hash_short = hash_full[:7] - commits.append(hash_full) - print(f"{i}. [{hash_short}] {subject}") - print(f" 作者: {author} | 时间: {date}\n") - - print() - - # 获取用户输入 - choice = input("请输入要回退到的版本 (输入数字1-10,或输入提交哈希值,q 取消): ").strip() - - if not choice or choice.lower() == 'q': - print("❌ 操作已取消") - return - - # 判断是数字还是哈希值 - if choice.isdigit(): - idx = int(choice) - 1 - if 0 <= idx < len(commits): - commit_hash = commits[idx][:7] - else: - print(f"❌ 无效的数字,请输入 1-{len(commits)}") - return - else: - commit_hash = choice - - # 验证提交是否存在 - verify = self.run_command(f"git cat-file -t {commit_hash}", check=False, silent=True) - if not verify or verify.returncode != 0: - print(f"❌ 提交 {commit_hash} 不存在") - return - - # 显示目标提交信息 - print(f"\n📍 即将回退到:") - self.run_command(f"git log -1 {commit_hash} --pretty=format:[%h] %s%n作者: %an | 时间: %ar") - - # 选择回退方式 - print("\n\n回退方式:") - print("1. 软回退 (保留工作区和暂存区的更改)") - print("2. 混合回退 (保留工作区更改,清空暂存区) [推荐]") - print("3. 硬回退 (完全回退,丢弃所有更改)") - - mode_choice = input("\n请选择 (1/2/3,输入 q 取消): ").strip() - - if mode_choice.lower() == 'q': - print("❌ 操作已取消") - return - - mode_map = { - '1': ('--soft', '软回退'), - '2': ('--mixed', '混合回退'), - '3': ('--hard', '硬回退') - } - - if mode_choice not in mode_map: - print("❌ 无效选择") - return - - mode, mode_name = mode_map[mode_choice] - - # 执行回退 - print(f"\n⏮️ 正在执行{mode_name}...") - result = self.run_command(f"git reset {mode} {commit_hash}") - - if result: - print(f"\n✅ {mode_name}成功!") - print(f"📍 已回退到: {commit_hash}\n") - self.run_command("git status --short") - else: - print(f"\n❌ 回退失败") - - -def main(): - tool = GitSyncTool() - - if len(sys.argv) < 2: - print(__doc__) - return - - command = sys.argv[1].lower() - - if command == 'push': - tool.force_push() - elif command == 'pull': - tool.force_pull() - elif command == 'sync': - tool.smart_sync() - elif command == 'log': - tool.show_log(5) - elif command == 'history': - tool.show_full_history() - elif command == 'rollback': - tool.rollback() - elif command == 'config': - tool.config_remote() - else: - print(f"❌ 未知命令: {command}") - print(__doc__) - - -if __name__ == '__main__': - main() \ No newline at end of file