模块化重构代码
This commit is contained in:
53
README.md
53
README.md
@@ -1,6 +1,6 @@
|
|||||||
# QuickGit - 萌芽一键Git管理工具
|
# QuickGit - 萌芽一键Git管理工具
|
||||||
|
|
||||||
一个简单易用的Git命令行管理工具,让Git操作更加便捷高效。
|
一个简单易用的模块化Git命令行管理工具,让Git操作更加便捷高效。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
@@ -10,12 +10,33 @@
|
|||||||
- **远程仓库管理** - 便捷地添加、删除、查看远程仓库
|
- **远程仓库管理** - 便捷地添加、删除、查看远程仓库
|
||||||
- **状态查看** - 快速查看仓库状态和提交历史
|
- **状态查看** - 快速查看仓库状态和提交历史
|
||||||
- **彩色界面** - 友好的彩色控制台输出
|
- **彩色界面** - 友好的彩色控制台输出
|
||||||
|
- **模块化设计** - 易于维护和扩展
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
QuickGit/
|
||||||
|
├── quickgit/ # 核心模块
|
||||||
|
│ ├── __init__.py # 包初始化
|
||||||
|
│ ├── config.py # 配置模块
|
||||||
|
│ ├── utils.py # 工具类(命令执行、输出格式化、输入验证)
|
||||||
|
│ ├── git_operations.py # Git操作模块
|
||||||
|
│ ├── remote_manager.py # 远程仓库管理模块
|
||||||
|
│ └── ui.py # UI交互模块
|
||||||
|
├── quickgit.py # 主程序入口
|
||||||
|
├── mengya_git_manager.py # 旧版单文件脚本(兼容保留)
|
||||||
|
└── README.md # 项目文档
|
||||||
|
```
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 运行脚本
|
### 运行脚本
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# 使用新版模块化脚本(推荐)
|
||||||
|
python quickgit.py
|
||||||
|
|
||||||
|
# 或使用旧版单文件脚本
|
||||||
python mengya_git_manager.py
|
python mengya_git_manager.py
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -103,12 +124,40 @@ A: 使用 `5. 管理远程仓库` 功能添加或删除远程仓库
|
|||||||
**Q: 支持哪些Git操作?**
|
**Q: 支持哪些Git操作?**
|
||||||
A: 目前支持init、add、commit、push、pull等常用操作
|
A: 目前支持init、add、commit、push、pull等常用操作
|
||||||
|
|
||||||
|
## 模块说明
|
||||||
|
|
||||||
|
### config.py - 配置模块
|
||||||
|
存储所有配置信息,包括Gitea服务器地址、GitHub用户名、.gitignore模板等。
|
||||||
|
|
||||||
|
### utils.py - 工具类模块
|
||||||
|
- `Colors`: 控制台颜色定义
|
||||||
|
- `CommandExecutor`: 命令执行器
|
||||||
|
- `OutputFormatter`: 输出格式化器
|
||||||
|
- `InputValidator`: 输入验证器
|
||||||
|
|
||||||
|
### git_operations.py - Git操作模块
|
||||||
|
提供Git基本操作功能:
|
||||||
|
- 初始化仓库
|
||||||
|
- 检查状态
|
||||||
|
- 添加/提交更改
|
||||||
|
- 推送/拉取代码
|
||||||
|
|
||||||
|
### remote_manager.py - 远程仓库管理模块
|
||||||
|
管理GitHub和Gitea远程仓库:
|
||||||
|
- 添加/删除远程仓库
|
||||||
|
- 查看远程仓库列表
|
||||||
|
- 选择推送/拉取目标
|
||||||
|
|
||||||
|
### ui.py - UI交互模块
|
||||||
|
处理用户界面和交互逻辑,整合所有功能模块。
|
||||||
|
|
||||||
## 开发计划
|
## 开发计划
|
||||||
|
|
||||||
|
- [x] 模块化架构重构
|
||||||
- [ ] 支持分支管理
|
- [ ] 支持分支管理
|
||||||
- [ ] 支持标签管理
|
- [ ] 支持标签管理
|
||||||
- [ ] 支持冲突解决辅助
|
- [ ] 支持冲突解决辅助
|
||||||
- [ ] 支持配置文件
|
- [ ] 支持自定义配置文件
|
||||||
- [ ] 支持批量操作多个仓库
|
- [ ] 支持批量操作多个仓库
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|||||||
@@ -1,561 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
萌芽一键Git管理工具
|
|
||||||
支持快速初始化Git仓库、提交更改、推送到GitHub和Gitea
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import subprocess
|
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
|
|
||||||
class Colors:
|
|
||||||
"""控制台颜色"""
|
|
||||||
HEADER = '\033[95m'
|
|
||||||
BLUE = '\033[94m'
|
|
||||||
CYAN = '\033[96m'
|
|
||||||
GREEN = '\033[92m'
|
|
||||||
WARNING = '\033[93m'
|
|
||||||
FAIL = '\033[91m'
|
|
||||||
ENDC = '\033[0m'
|
|
||||||
BOLD = '\033[1m'
|
|
||||||
|
|
||||||
|
|
||||||
class GitManager:
|
|
||||||
"""Git管理器"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.gitea_host = "repo.shumengya.top"
|
|
||||||
self.gitea_port = "8022"
|
|
||||||
self.github_user = "shumengya"
|
|
||||||
self.current_dir = os.getcwd()
|
|
||||||
|
|
||||||
def print_header(self, text: str):
|
|
||||||
"""打印标题"""
|
|
||||||
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*50}")
|
|
||||||
print(f" {text}")
|
|
||||||
print(f"{'='*50}{Colors.ENDC}\n")
|
|
||||||
|
|
||||||
def print_success(self, text: str):
|
|
||||||
"""打印成功信息"""
|
|
||||||
print(f"{Colors.GREEN}✓ {text}{Colors.ENDC}")
|
|
||||||
|
|
||||||
def print_error(self, text: str):
|
|
||||||
"""打印错误信息"""
|
|
||||||
print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}")
|
|
||||||
|
|
||||||
def print_info(self, text: str):
|
|
||||||
"""打印提示信息"""
|
|
||||||
print(f"{Colors.CYAN}ℹ {text}{Colors.ENDC}")
|
|
||||||
|
|
||||||
def print_warning(self, text: str):
|
|
||||||
"""打印警告信息"""
|
|
||||||
print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}")
|
|
||||||
|
|
||||||
def run_command(self, command: str, show_output: bool = True) -> tuple[bool, str]:
|
|
||||||
"""
|
|
||||||
执行命令
|
|
||||||
返回: (是否成功, 输出内容)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
command,
|
|
||||||
shell=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
encoding='utf-8',
|
|
||||||
errors='ignore'
|
|
||||||
)
|
|
||||||
|
|
||||||
output = result.stdout + result.stderr
|
|
||||||
|
|
||||||
if show_output and output.strip():
|
|
||||||
print(output)
|
|
||||||
|
|
||||||
return result.returncode == 0, output
|
|
||||||
except Exception as e:
|
|
||||||
self.print_error(f"命令执行失败: {str(e)}")
|
|
||||||
return False, str(e)
|
|
||||||
|
|
||||||
def is_git_repo(self) -> bool:
|
|
||||||
"""检查当前目录是否是Git仓库"""
|
|
||||||
return os.path.isdir('.git')
|
|
||||||
|
|
||||||
def get_gitignore_template(self) -> str:
|
|
||||||
"""获取.gitignore模板"""
|
|
||||||
return """# Node/React
|
|
||||||
node_modules/
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
coverage/
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
# Go
|
|
||||||
*.exe
|
|
||||||
*.exe~
|
|
||||||
*.test
|
|
||||||
*.out
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
vendor/
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
*.so
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
*.egg-info/
|
|
||||||
dist/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# 数据文件
|
|
||||||
data/data.json
|
|
||||||
*.db
|
|
||||||
*.sqlite
|
|
||||||
|
|
||||||
# 日志文件
|
|
||||||
*.log
|
|
||||||
logs/
|
|
||||||
|
|
||||||
# 操作系统
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
desktop.ini
|
|
||||||
|
|
||||||
# IDE
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
*~
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.settings/
|
|
||||||
|
|
||||||
# 其他
|
|
||||||
*.bak
|
|
||||||
*.tmp
|
|
||||||
*.temp
|
|
||||||
"""
|
|
||||||
|
|
||||||
def init_git_repo(self):
|
|
||||||
"""初始化Git仓库"""
|
|
||||||
self.print_header("初始化Git仓库")
|
|
||||||
|
|
||||||
if self.is_git_repo():
|
|
||||||
self.print_warning("当前目录已经是Git仓库")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 1. 初始化Git仓库
|
|
||||||
self.print_info("正在初始化Git仓库...")
|
|
||||||
success, _ = self.run_command("git init", show_output=False)
|
|
||||||
if not success:
|
|
||||||
self.print_error("Git初始化失败")
|
|
||||||
return False
|
|
||||||
self.print_success("Git仓库初始化成功")
|
|
||||||
|
|
||||||
# 2. 创建main分支
|
|
||||||
self.print_info("正在创建main分支...")
|
|
||||||
success, _ = self.run_command("git checkout -b main", show_output=False)
|
|
||||||
if success:
|
|
||||||
self.print_success("main分支创建成功")
|
|
||||||
else:
|
|
||||||
self.print_warning("main分支创建失败,将使用默认分支")
|
|
||||||
|
|
||||||
# 3. 创建.gitignore文件
|
|
||||||
self.print_info("正在创建.gitignore文件...")
|
|
||||||
try:
|
|
||||||
with open('.gitignore', 'w', encoding='utf-8') as f:
|
|
||||||
f.write(self.get_gitignore_template())
|
|
||||||
self.print_success(".gitignore文件创建成功")
|
|
||||||
except Exception as e:
|
|
||||||
self.print_error(f".gitignore文件创建失败: {str(e)}")
|
|
||||||
|
|
||||||
# 4. 首次提交
|
|
||||||
self.print_info("正在进行首次提交...")
|
|
||||||
self.run_command("git add .", show_output=False)
|
|
||||||
success, _ = self.run_command('git commit -m "first commit"', show_output=False)
|
|
||||||
if success:
|
|
||||||
self.print_success("首次提交完成")
|
|
||||||
else:
|
|
||||||
self.print_warning("首次提交失败(可能没有文件可提交)")
|
|
||||||
|
|
||||||
# 5. 配置远程仓库
|
|
||||||
self.configure_remotes()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def configure_remotes(self):
|
|
||||||
"""配置远程仓库"""
|
|
||||||
self.print_info("\n配置远程仓库...")
|
|
||||||
|
|
||||||
print("\n请选择要配置的远程仓库:")
|
|
||||||
print("1. GitHub")
|
|
||||||
print("2. Gitea")
|
|
||||||
print("3. 两者都配置")
|
|
||||||
print("4. 跳过")
|
|
||||||
|
|
||||||
choice = input("\n请选择 [1-4]: ").strip()
|
|
||||||
|
|
||||||
if choice == '1':
|
|
||||||
self.add_github_remote()
|
|
||||||
elif choice == '2':
|
|
||||||
self.add_gitea_remote()
|
|
||||||
elif choice == '3':
|
|
||||||
self.add_github_remote()
|
|
||||||
self.add_gitea_remote()
|
|
||||||
else:
|
|
||||||
self.print_info("跳过远程仓库配置")
|
|
||||||
|
|
||||||
def add_github_remote(self):
|
|
||||||
"""添加GitHub远程仓库"""
|
|
||||||
repo_name = input(f"\n请输入GitHub仓库名: ").strip()
|
|
||||||
if not repo_name:
|
|
||||||
self.print_warning("仓库名不能为空,跳过GitHub配置")
|
|
||||||
return
|
|
||||||
|
|
||||||
remote_url = f"git@github.com:{self.github_user}/{repo_name}.git"
|
|
||||||
|
|
||||||
# 检查remote是否已存在
|
|
||||||
success, output = self.run_command("git remote", show_output=False)
|
|
||||||
if "github" in output:
|
|
||||||
self.run_command("git remote remove github", show_output=False)
|
|
||||||
|
|
||||||
success, _ = self.run_command(f'git remote add github {remote_url}', show_output=False)
|
|
||||||
if success:
|
|
||||||
self.print_success(f"GitHub远程仓库已添加: {remote_url}")
|
|
||||||
else:
|
|
||||||
self.print_error("GitHub远程仓库添加失败")
|
|
||||||
|
|
||||||
def add_gitea_remote(self):
|
|
||||||
"""添加Gitea远程仓库"""
|
|
||||||
user = input(f"\n请输入Gitea用户名: ").strip()
|
|
||||||
if not user:
|
|
||||||
self.print_warning("用户名不能为空,跳过Gitea配置")
|
|
||||||
return
|
|
||||||
|
|
||||||
repo_name = input(f"请输入Gitea仓库名: ").strip()
|
|
||||||
if not repo_name:
|
|
||||||
self.print_warning("仓库名不能为空,跳过Gitea配置")
|
|
||||||
return
|
|
||||||
|
|
||||||
remote_url = f"ssh://git@{self.gitea_host}:{self.gitea_port}/{user}/{repo_name}.git"
|
|
||||||
|
|
||||||
# 检查remote是否已存在
|
|
||||||
success, output = self.run_command("git remote", show_output=False)
|
|
||||||
if "gitea" in output:
|
|
||||||
self.run_command("git remote remove gitea", show_output=False)
|
|
||||||
|
|
||||||
success, _ = self.run_command(f'git remote add gitea {remote_url}', show_output=False)
|
|
||||||
if success:
|
|
||||||
self.print_success(f"Gitea远程仓库已添加: {remote_url}")
|
|
||||||
else:
|
|
||||||
self.print_error("Gitea远程仓库添加失败")
|
|
||||||
|
|
||||||
def commit_and_push(self):
|
|
||||||
"""提交并推送更改"""
|
|
||||||
self.print_header("提交并推送更改")
|
|
||||||
|
|
||||||
if not self.is_git_repo():
|
|
||||||
self.print_error("当前目录不是Git仓库,请先初始化")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 1. 检查是否有更改
|
|
||||||
self.print_info("检查文件更改...")
|
|
||||||
success, output = self.run_command("git status --short", show_output=False)
|
|
||||||
|
|
||||||
if not output.strip():
|
|
||||||
self.print_warning("没有文件更改,无需提交")
|
|
||||||
return True
|
|
||||||
|
|
||||||
print("\n当前更改的文件:")
|
|
||||||
print(output)
|
|
||||||
|
|
||||||
# 2. 输入提交信息
|
|
||||||
commit_msg = input("\n请输入提交信息 (直接回车使用默认信息): ").strip()
|
|
||||||
if not commit_msg:
|
|
||||||
from datetime import datetime
|
|
||||||
commit_msg = f"update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
||||||
|
|
||||||
# 3. 添加所有更改
|
|
||||||
self.print_info("正在添加文件...")
|
|
||||||
success, _ = self.run_command("git add .", show_output=False)
|
|
||||||
if not success:
|
|
||||||
self.print_error("添加文件失败")
|
|
||||||
return False
|
|
||||||
self.print_success("文件添加成功")
|
|
||||||
|
|
||||||
# 4. 提交更改
|
|
||||||
self.print_info("正在提交更改...")
|
|
||||||
success, _ = self.run_command(f'git commit -m "{commit_msg}"', show_output=True)
|
|
||||||
if not success:
|
|
||||||
self.print_error("提交失败")
|
|
||||||
return False
|
|
||||||
self.print_success("提交成功")
|
|
||||||
|
|
||||||
# 5. 推送到远程仓库
|
|
||||||
self.push_to_remote()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def push_to_remote(self):
|
|
||||||
"""推送到远程仓库"""
|
|
||||||
# 获取当前分支
|
|
||||||
success, branch = self.run_command("git branch --show-current", show_output=False)
|
|
||||||
if not success or not branch.strip():
|
|
||||||
branch = "main"
|
|
||||||
else:
|
|
||||||
branch = branch.strip()
|
|
||||||
|
|
||||||
# 获取所有远程仓库
|
|
||||||
success, output = self.run_command("git remote", show_output=False)
|
|
||||||
if not success or not output.strip():
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
remotes = [r.strip() for r in output.strip().split('\n') if r.strip()]
|
|
||||||
|
|
||||||
if not remotes:
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("\n可用的远程仓库:")
|
|
||||||
for idx, remote in enumerate(remotes, 1):
|
|
||||||
print(f"{idx}. {remote}")
|
|
||||||
print(f"{len(remotes) + 1}. 全部推送")
|
|
||||||
|
|
||||||
choice = input(f"\n请选择要推送的远程仓库 [1-{len(remotes) + 1}]: ").strip()
|
|
||||||
|
|
||||||
try:
|
|
||||||
choice_idx = int(choice)
|
|
||||||
if choice_idx == len(remotes) + 1:
|
|
||||||
# 推送到所有远程仓库
|
|
||||||
for remote in remotes:
|
|
||||||
self.push_to_specific_remote(remote, branch)
|
|
||||||
elif 1 <= choice_idx <= len(remotes):
|
|
||||||
# 推送到指定远程仓库
|
|
||||||
remote = remotes[choice_idx - 1]
|
|
||||||
self.push_to_specific_remote(remote, branch)
|
|
||||||
else:
|
|
||||||
self.print_error("无效的选择")
|
|
||||||
except ValueError:
|
|
||||||
self.print_error("无效的输入")
|
|
||||||
|
|
||||||
def push_to_specific_remote(self, remote: str, branch: str):
|
|
||||||
"""推送到指定远程仓库"""
|
|
||||||
self.print_info(f"正在推送到 {remote}...")
|
|
||||||
|
|
||||||
# 检查是否需要设置上游分支
|
|
||||||
success, _ = self.run_command(f"git push {remote} {branch}", show_output=True)
|
|
||||||
|
|
||||||
if not success:
|
|
||||||
# 尝试设置上游分支
|
|
||||||
self.print_info(f"尝试设置上游分支...")
|
|
||||||
success, _ = self.run_command(f"git push -u {remote} {branch}", show_output=True)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.print_success(f"成功推送到 {remote}")
|
|
||||||
else:
|
|
||||||
self.print_error(f"推送到 {remote} 失败")
|
|
||||||
|
|
||||||
def pull_from_remote(self):
|
|
||||||
"""从远程仓库拉取"""
|
|
||||||
self.print_header("从远程仓库拉取")
|
|
||||||
|
|
||||||
if not self.is_git_repo():
|
|
||||||
self.print_error("当前目录不是Git仓库")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取所有远程仓库
|
|
||||||
success, output = self.run_command("git remote", show_output=False)
|
|
||||||
if not success or not output.strip():
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return False
|
|
||||||
|
|
||||||
remotes = [r.strip() for r in output.strip().split('\n') if r.strip()]
|
|
||||||
|
|
||||||
if not remotes:
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return False
|
|
||||||
|
|
||||||
print("\n可用的远程仓库:")
|
|
||||||
for idx, remote in enumerate(remotes, 1):
|
|
||||||
print(f"{idx}. {remote}")
|
|
||||||
|
|
||||||
choice = input(f"\n请选择要拉取的远程仓库 [1-{len(remotes)}]: ").strip()
|
|
||||||
|
|
||||||
try:
|
|
||||||
choice_idx = int(choice)
|
|
||||||
if 1 <= choice_idx <= len(remotes):
|
|
||||||
remote = remotes[choice_idx - 1]
|
|
||||||
|
|
||||||
# 获取当前分支
|
|
||||||
success, branch = self.run_command("git branch --show-current", show_output=False)
|
|
||||||
if not success or not branch.strip():
|
|
||||||
branch = "main"
|
|
||||||
else:
|
|
||||||
branch = branch.strip()
|
|
||||||
|
|
||||||
self.print_info(f"正在从 {remote} 拉取 {branch} 分支...")
|
|
||||||
success, _ = self.run_command(f"git pull {remote} {branch}", show_output=True)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.print_success(f"成功从 {remote} 拉取更新")
|
|
||||||
else:
|
|
||||||
self.print_error(f"从 {remote} 拉取失败")
|
|
||||||
else:
|
|
||||||
self.print_error("无效的选择")
|
|
||||||
except ValueError:
|
|
||||||
self.print_error("无效的输入")
|
|
||||||
|
|
||||||
def show_status(self):
|
|
||||||
"""显示仓库状态"""
|
|
||||||
self.print_header("仓库状态")
|
|
||||||
|
|
||||||
if not self.is_git_repo():
|
|
||||||
self.print_error("当前目录不是Git仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"{Colors.CYAN}当前目录:{Colors.ENDC} {self.current_dir}\n")
|
|
||||||
|
|
||||||
# Git状态
|
|
||||||
self.print_info("Git状态:")
|
|
||||||
self.run_command("git status", show_output=True)
|
|
||||||
|
|
||||||
# 远程仓库
|
|
||||||
print(f"\n{Colors.CYAN}远程仓库:{Colors.ENDC}")
|
|
||||||
self.run_command("git remote -v", show_output=True)
|
|
||||||
|
|
||||||
# 最近提交
|
|
||||||
print(f"\n{Colors.CYAN}最近3次提交:{Colors.ENDC}")
|
|
||||||
self.run_command("git log --oneline -3", show_output=True)
|
|
||||||
|
|
||||||
def manage_remotes(self):
|
|
||||||
"""管理远程仓库"""
|
|
||||||
self.print_header("管理远程仓库")
|
|
||||||
|
|
||||||
if not self.is_git_repo():
|
|
||||||
self.print_error("当前目录不是Git仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
while True:
|
|
||||||
print("\n远程仓库管理:")
|
|
||||||
print("1. 查看远程仓库")
|
|
||||||
print("2. 添加GitHub远程仓库")
|
|
||||||
print("3. 添加Gitea远程仓库")
|
|
||||||
print("4. 删除远程仓库")
|
|
||||||
print("5. 返回主菜单")
|
|
||||||
|
|
||||||
choice = input("\n请选择 [1-5]: ").strip()
|
|
||||||
|
|
||||||
if choice == '1':
|
|
||||||
print(f"\n{Colors.CYAN}当前远程仓库:{Colors.ENDC}")
|
|
||||||
self.run_command("git remote -v", show_output=True)
|
|
||||||
elif choice == '2':
|
|
||||||
self.add_github_remote()
|
|
||||||
elif choice == '3':
|
|
||||||
self.add_gitea_remote()
|
|
||||||
elif choice == '4':
|
|
||||||
self.remove_remote()
|
|
||||||
elif choice == '5':
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.print_error("无效的选择")
|
|
||||||
|
|
||||||
def remove_remote(self):
|
|
||||||
"""删除远程仓库"""
|
|
||||||
success, output = self.run_command("git remote", show_output=False)
|
|
||||||
if not success or not output.strip():
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
remotes = [r.strip() for r in output.strip().split('\n') if r.strip()]
|
|
||||||
|
|
||||||
if not remotes:
|
|
||||||
self.print_warning("没有配置远程仓库")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("\n当前远程仓库:")
|
|
||||||
for idx, remote in enumerate(remotes, 1):
|
|
||||||
print(f"{idx}. {remote}")
|
|
||||||
|
|
||||||
choice = input(f"\n请选择要删除的远程仓库 [1-{len(remotes)}]: ").strip()
|
|
||||||
|
|
||||||
try:
|
|
||||||
choice_idx = int(choice)
|
|
||||||
if 1 <= choice_idx <= len(remotes):
|
|
||||||
remote = remotes[choice_idx - 1]
|
|
||||||
confirm = input(f"确认删除远程仓库 '{remote}'? [y/N]: ").strip().lower()
|
|
||||||
if confirm == 'y':
|
|
||||||
success, _ = self.run_command(f"git remote remove {remote}", show_output=False)
|
|
||||||
if success:
|
|
||||||
self.print_success(f"远程仓库 '{remote}' 已删除")
|
|
||||||
else:
|
|
||||||
self.print_error("删除失败")
|
|
||||||
else:
|
|
||||||
self.print_error("无效的选择")
|
|
||||||
except ValueError:
|
|
||||||
self.print_error("无效的输入")
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""运行主程序"""
|
|
||||||
self.print_header("萌芽一键Git管理工具")
|
|
||||||
print(f"{Colors.CYAN}当前目录:{Colors.ENDC} {self.current_dir}")
|
|
||||||
print(f"{Colors.CYAN}Git仓库:{Colors.ENDC} {'是' if self.is_git_repo() else '否'}")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
print(f"\n{Colors.BOLD}请选择操作:{Colors.ENDC}")
|
|
||||||
print("1. 初始化Git仓库")
|
|
||||||
print("2. 提交并推送更改")
|
|
||||||
print("3. 从远程仓库拉取")
|
|
||||||
print("4. 查看仓库状态")
|
|
||||||
print("5. 管理远程仓库")
|
|
||||||
print("6. 退出")
|
|
||||||
|
|
||||||
choice = input(f"\n{Colors.BOLD}请输入选项 [1-6]: {Colors.ENDC}").strip()
|
|
||||||
|
|
||||||
if choice == '1':
|
|
||||||
self.init_git_repo()
|
|
||||||
elif choice == '2':
|
|
||||||
self.commit_and_push()
|
|
||||||
elif choice == '3':
|
|
||||||
self.pull_from_remote()
|
|
||||||
elif choice == '4':
|
|
||||||
self.show_status()
|
|
||||||
elif choice == '5':
|
|
||||||
self.manage_remotes()
|
|
||||||
elif choice == '6':
|
|
||||||
self.print_success("感谢使用萌芽Git管理工具!")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.print_error("无效的选择,请重新输入")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""主函数"""
|
|
||||||
try:
|
|
||||||
manager = GitManager()
|
|
||||||
manager.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print(f"\n\n{Colors.WARNING}程序被用户中断{Colors.ENDC}")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n{Colors.FAIL}发生错误: {str(e)}{Colors.ENDC}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
30
quickgit.py
Normal file
30
quickgit.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
QuickGit - 萌芽一键Git管理工具
|
||||||
|
主程序入口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from quickgit.ui import GitManagerUI
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
try:
|
||||||
|
ui = GitManagerUI()
|
||||||
|
ui.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
from quickgit.utils import Colors
|
||||||
|
print(f"\n\n{Colors.WARNING}程序被用户中断{Colors.ENDC}")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception as e:
|
||||||
|
from quickgit.utils import Colors
|
||||||
|
print(f"\n{Colors.FAIL}发生错误: {str(e)}{Colors.ENDC}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
6
quickgit/__init__.py
Normal file
6
quickgit/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"""
|
||||||
|
QuickGit - 萌芽一键Git管理工具
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "shumengya"
|
||||||
84
quickgit/config.py
Normal file
84
quickgit/config.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
"""
|
||||||
|
配置模块 - 存储项目配置信息
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""配置类"""
|
||||||
|
|
||||||
|
# Gitea服务器配置
|
||||||
|
GITEA_HOST = "repo.shumengya.top"
|
||||||
|
GITEA_PORT = "8022"
|
||||||
|
|
||||||
|
# GitHub配置
|
||||||
|
GITHUB_USER = "shumengya"
|
||||||
|
|
||||||
|
# Git配置
|
||||||
|
DEFAULT_BRANCH = "main"
|
||||||
|
|
||||||
|
# .gitignore模板
|
||||||
|
GITIGNORE_TEMPLATE = """# Node/React
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Go
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.test
|
||||||
|
*.out
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# 数据文件
|
||||||
|
data/data.json
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# 日志文件
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# 操作系统
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
"""
|
||||||
207
quickgit/git_operations.py
Normal file
207
quickgit/git_operations.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
"""
|
||||||
|
Git操作模块 - 提供Git基本操作功能
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from .utils import CommandExecutor, OutputFormatter
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
|
|
||||||
|
class GitOperations:
|
||||||
|
"""Git操作类"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.executor = CommandExecutor()
|
||||||
|
self.current_dir = os.getcwd()
|
||||||
|
|
||||||
|
def is_git_repo(self) -> bool:
|
||||||
|
"""检查当前目录是否是Git仓库"""
|
||||||
|
return os.path.isdir('.git')
|
||||||
|
|
||||||
|
def init_repo(self) -> bool:
|
||||||
|
"""
|
||||||
|
初始化Git仓库
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if self.is_git_repo():
|
||||||
|
OutputFormatter.warning("当前目录已经是Git仓库")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 初始化Git仓库
|
||||||
|
OutputFormatter.info("正在初始化Git仓库...")
|
||||||
|
success, _ = self.executor.run("git init", show_output=False)
|
||||||
|
if not success:
|
||||||
|
OutputFormatter.error("Git初始化失败")
|
||||||
|
return False
|
||||||
|
OutputFormatter.success("Git仓库初始化成功")
|
||||||
|
|
||||||
|
# 创建main分支
|
||||||
|
OutputFormatter.info("正在创建main分支...")
|
||||||
|
success, _ = self.executor.run(f"git checkout -b {Config.DEFAULT_BRANCH}", show_output=False)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success("main分支创建成功")
|
||||||
|
else:
|
||||||
|
OutputFormatter.warning("main分支创建失败,将使用默认分支")
|
||||||
|
|
||||||
|
# 创建.gitignore文件
|
||||||
|
self._create_gitignore()
|
||||||
|
|
||||||
|
# 首次提交
|
||||||
|
OutputFormatter.info("正在进行首次提交...")
|
||||||
|
self.executor.run("git add .", show_output=False)
|
||||||
|
success, _ = self.executor.run('git commit -m "first commit"', show_output=False)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success("首次提交完成")
|
||||||
|
else:
|
||||||
|
OutputFormatter.warning("首次提交失败(可能没有文件可提交)")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _create_gitignore(self):
|
||||||
|
"""创建.gitignore文件"""
|
||||||
|
OutputFormatter.info("正在创建.gitignore文件...")
|
||||||
|
try:
|
||||||
|
with open('.gitignore', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(Config.GITIGNORE_TEMPLATE)
|
||||||
|
OutputFormatter.success(".gitignore文件创建成功")
|
||||||
|
except Exception as e:
|
||||||
|
OutputFormatter.error(f".gitignore文件创建失败: {str(e)}")
|
||||||
|
|
||||||
|
def get_status(self) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
获取Git状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否成功, 状态输出)
|
||||||
|
"""
|
||||||
|
return self.executor.run("git status --short", show_output=False)
|
||||||
|
|
||||||
|
def has_changes(self) -> bool:
|
||||||
|
"""
|
||||||
|
检查是否有更改
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否有更改
|
||||||
|
"""
|
||||||
|
success, output = self.get_status()
|
||||||
|
return success and bool(output.strip())
|
||||||
|
|
||||||
|
def add_all(self) -> bool:
|
||||||
|
"""
|
||||||
|
添加所有更改
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
OutputFormatter.info("正在添加文件...")
|
||||||
|
success, _ = self.executor.run("git add .", show_output=False)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success("文件添加成功")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error("添加文件失败")
|
||||||
|
return success
|
||||||
|
|
||||||
|
def commit(self, message: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
提交更改
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: 提交信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if not message:
|
||||||
|
message = f"update: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||||||
|
|
||||||
|
OutputFormatter.info("正在提交更改...")
|
||||||
|
success, _ = self.executor.run(f'git commit -m "{message}"', show_output=True)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success("提交成功")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error("提交失败")
|
||||||
|
return success
|
||||||
|
|
||||||
|
def get_current_branch(self) -> str:
|
||||||
|
"""
|
||||||
|
获取当前分支
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
分支名
|
||||||
|
"""
|
||||||
|
success, branch = self.executor.run("git branch --show-current", show_output=False)
|
||||||
|
if success and branch.strip():
|
||||||
|
return branch.strip()
|
||||||
|
return Config.DEFAULT_BRANCH
|
||||||
|
|
||||||
|
def push(self, remote: str, branch: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
推送到远程仓库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote: 远程仓库名
|
||||||
|
branch: 分支名(默认为当前分支)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if not branch:
|
||||||
|
branch = self.get_current_branch()
|
||||||
|
|
||||||
|
OutputFormatter.info(f"正在推送到 {remote}...")
|
||||||
|
|
||||||
|
# 先尝试直接推送
|
||||||
|
success, _ = self.executor.run(f"git push {remote} {branch}", show_output=True)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
# 如果失败,尝试设置上游分支
|
||||||
|
OutputFormatter.info("尝试设置上游分支...")
|
||||||
|
success, _ = self.executor.run(f"git push -u {remote} {branch}", show_output=True)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success(f"成功推送到 {remote}")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error(f"推送到 {remote} 失败")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def pull(self, remote: str, branch: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
从远程仓库拉取
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote: 远程仓库名
|
||||||
|
branch: 分支名(默认为当前分支)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if not branch:
|
||||||
|
branch = self.get_current_branch()
|
||||||
|
|
||||||
|
OutputFormatter.info(f"正在从 {remote} 拉取 {branch} 分支...")
|
||||||
|
success, _ = self.executor.run(f"git pull {remote} {branch}", show_output=True)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success(f"成功从 {remote} 拉取更新")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error(f"从 {remote} 拉取失败")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def show_status(self):
|
||||||
|
"""显示仓库状态"""
|
||||||
|
from .utils import Colors
|
||||||
|
|
||||||
|
print(f"{Colors.CYAN}当前目录:{Colors.ENDC} {self.current_dir}\n")
|
||||||
|
|
||||||
|
# Git状态
|
||||||
|
OutputFormatter.info("Git状态:")
|
||||||
|
self.executor.run("git status", show_output=True)
|
||||||
|
|
||||||
|
# 最近提交
|
||||||
|
print(f"\n{Colors.CYAN}最近3次提交:{Colors.ENDC}")
|
||||||
|
self.executor.run("git log --oneline -3", show_output=True)
|
||||||
210
quickgit/remote_manager.py
Normal file
210
quickgit/remote_manager.py
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
"""
|
||||||
|
远程仓库管理模块 - 处理GitHub和Gitea远程仓库的管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .utils import CommandExecutor, OutputFormatter, InputValidator
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteManager:
|
||||||
|
"""远程仓库管理器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.executor = CommandExecutor()
|
||||||
|
|
||||||
|
def get_remotes(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
获取所有远程仓库
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
远程仓库列表
|
||||||
|
"""
|
||||||
|
success, output = self.executor.run("git remote", show_output=False)
|
||||||
|
if success and output.strip():
|
||||||
|
return [r.strip() for r in output.strip().split('\n') if r.strip()]
|
||||||
|
return []
|
||||||
|
|
||||||
|
def show_remotes(self):
|
||||||
|
"""显示所有远程仓库"""
|
||||||
|
from .utils import Colors
|
||||||
|
print(f"\n{Colors.CYAN}远程仓库:{Colors.ENDC}")
|
||||||
|
self.executor.run("git remote -v", show_output=True)
|
||||||
|
|
||||||
|
def add_github_remote(self, repo_name: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
添加GitHub远程仓库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repo_name: 仓库名(如果为None则提示用户输入)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if not repo_name:
|
||||||
|
repo_name = InputValidator.get_input("\n请输入GitHub仓库名: ")
|
||||||
|
|
||||||
|
remote_url = f"git@github.com:{Config.GITHUB_USER}/{repo_name}.git"
|
||||||
|
|
||||||
|
return self._add_remote("github", remote_url)
|
||||||
|
|
||||||
|
def add_gitea_remote(self, user: str = None, repo_name: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
添加Gitea远程仓库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user: Gitea用户名(如果为None则提示用户输入)
|
||||||
|
repo_name: 仓库名(如果为None则提示用户输入)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
if not user:
|
||||||
|
user = InputValidator.get_input("\n请输入Gitea用户名: ")
|
||||||
|
|
||||||
|
if not repo_name:
|
||||||
|
repo_name = InputValidator.get_input("请输入Gitea仓库名: ")
|
||||||
|
|
||||||
|
remote_url = f"ssh://git@{Config.GITEA_HOST}:{Config.GITEA_PORT}/{user}/{repo_name}.git"
|
||||||
|
|
||||||
|
return self._add_remote("gitea", remote_url)
|
||||||
|
|
||||||
|
def _add_remote(self, name: str, url: str) -> bool:
|
||||||
|
"""
|
||||||
|
添加远程仓库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 远程仓库名称
|
||||||
|
url: 远程仓库URL
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
# 检查remote是否已存在
|
||||||
|
remotes = self.get_remotes()
|
||||||
|
if name in remotes:
|
||||||
|
if InputValidator.confirm(f"远程仓库 '{name}' 已存在,是否覆盖?", default=False):
|
||||||
|
self.executor.run(f"git remote remove {name}", show_output=False)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
success, _ = self.executor.run(f'git remote add {name} {url}', show_output=False)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success(f"{name.capitalize()}远程仓库已添加: {url}")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error(f"{name.capitalize()}远程仓库添加失败")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def remove_remote(self, name: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
删除远程仓库
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 远程仓库名(如果为None则提示用户选择)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
是否成功
|
||||||
|
"""
|
||||||
|
remotes = self.get_remotes()
|
||||||
|
|
||||||
|
if not remotes:
|
||||||
|
OutputFormatter.warning("没有配置远程仓库")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
# 让用户选择要删除的远程仓库
|
||||||
|
print("\n当前远程仓库:")
|
||||||
|
for idx, remote in enumerate(remotes, 1):
|
||||||
|
print(f"{idx}. {remote}")
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice(
|
||||||
|
f"\n请选择要删除的远程仓库 [1-{len(remotes)}]: ",
|
||||||
|
range(1, len(remotes) + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
name = remotes[choice - 1]
|
||||||
|
|
||||||
|
if not InputValidator.confirm(f"确认删除远程仓库 '{name}'?", default=False):
|
||||||
|
return False
|
||||||
|
|
||||||
|
success, _ = self.executor.run(f"git remote remove {name}", show_output=False)
|
||||||
|
if success:
|
||||||
|
OutputFormatter.success(f"远程仓库 '{name}' 已删除")
|
||||||
|
else:
|
||||||
|
OutputFormatter.error("删除失败")
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def configure_remotes_interactive(self):
|
||||||
|
"""交互式配置远程仓库"""
|
||||||
|
OutputFormatter.info("\n配置远程仓库...")
|
||||||
|
|
||||||
|
print("\n请选择要配置的远程仓库:")
|
||||||
|
print("1. GitHub")
|
||||||
|
print("2. Gitea")
|
||||||
|
print("3. 两者都配置")
|
||||||
|
print("4. 跳过")
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice("\n请选择 [1-4]: ", range(1, 5))
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
self.add_github_remote()
|
||||||
|
elif choice == 2:
|
||||||
|
self.add_gitea_remote()
|
||||||
|
elif choice == 3:
|
||||||
|
self.add_github_remote()
|
||||||
|
self.add_gitea_remote()
|
||||||
|
else:
|
||||||
|
OutputFormatter.info("跳过远程仓库配置")
|
||||||
|
|
||||||
|
def select_remote_for_push(self) -> list[str]:
|
||||||
|
"""
|
||||||
|
选择要推送的远程仓库
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
选中的远程仓库列表
|
||||||
|
"""
|
||||||
|
remotes = self.get_remotes()
|
||||||
|
|
||||||
|
if not remotes:
|
||||||
|
OutputFormatter.warning("没有配置远程仓库")
|
||||||
|
return []
|
||||||
|
|
||||||
|
print("\n可用的远程仓库:")
|
||||||
|
for idx, remote in enumerate(remotes, 1):
|
||||||
|
print(f"{idx}. {remote}")
|
||||||
|
print(f"{len(remotes) + 1}. 全部推送")
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice(
|
||||||
|
f"\n请选择要推送的远程仓库 [1-{len(remotes) + 1}]: ",
|
||||||
|
range(1, len(remotes) + 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
if choice == len(remotes) + 1:
|
||||||
|
return remotes
|
||||||
|
else:
|
||||||
|
return [remotes[choice - 1]]
|
||||||
|
|
||||||
|
def select_remote_for_pull(self) -> str:
|
||||||
|
"""
|
||||||
|
选择要拉取的远程仓库
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
选中的远程仓库名
|
||||||
|
"""
|
||||||
|
remotes = self.get_remotes()
|
||||||
|
|
||||||
|
if not remotes:
|
||||||
|
OutputFormatter.warning("没有配置远程仓库")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("\n可用的远程仓库:")
|
||||||
|
for idx, remote in enumerate(remotes, 1):
|
||||||
|
print(f"{idx}. {remote}")
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice(
|
||||||
|
f"\n请选择要拉取的远程仓库 [1-{len(remotes)}]: ",
|
||||||
|
range(1, len(remotes) + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
return remotes[choice - 1]
|
||||||
158
quickgit/ui.py
Normal file
158
quickgit/ui.py
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
"""
|
||||||
|
UI交互模块 - 处理用户界面和交互逻辑
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .git_operations import GitOperations
|
||||||
|
from .remote_manager import RemoteManager
|
||||||
|
from .utils import OutputFormatter, InputValidator, Colors
|
||||||
|
|
||||||
|
|
||||||
|
class GitManagerUI:
|
||||||
|
"""Git管理器UI"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.git_ops = GitOperations()
|
||||||
|
self.remote_mgr = RemoteManager()
|
||||||
|
|
||||||
|
def show_welcome(self):
|
||||||
|
"""显示欢迎信息"""
|
||||||
|
OutputFormatter.header("萌芽一键Git管理工具 - QuickGit")
|
||||||
|
print(f"{Colors.CYAN}当前目录:{Colors.ENDC} {self.git_ops.current_dir}")
|
||||||
|
print(f"{Colors.CYAN}Git仓库:{Colors.ENDC} {'是' if self.git_ops.is_git_repo() else '否'}")
|
||||||
|
|
||||||
|
def show_main_menu(self):
|
||||||
|
"""显示主菜单"""
|
||||||
|
print(f"\n{Colors.BOLD}请选择操作:{Colors.ENDC}")
|
||||||
|
print("1. 初始化Git仓库")
|
||||||
|
print("2. 提交并推送更改")
|
||||||
|
print("3. 从远程仓库拉取")
|
||||||
|
print("4. 查看仓库状态")
|
||||||
|
print("5. 管理远程仓库")
|
||||||
|
print("6. 退出")
|
||||||
|
|
||||||
|
def handle_init_repo(self):
|
||||||
|
"""处理初始化仓库"""
|
||||||
|
OutputFormatter.header("初始化Git仓库")
|
||||||
|
|
||||||
|
if self.git_ops.init_repo():
|
||||||
|
# 配置远程仓库
|
||||||
|
self.remote_mgr.configure_remotes_interactive()
|
||||||
|
|
||||||
|
def handle_commit_and_push(self):
|
||||||
|
"""处理提交并推送"""
|
||||||
|
OutputFormatter.header("提交并推送更改")
|
||||||
|
|
||||||
|
if not self.git_ops.is_git_repo():
|
||||||
|
OutputFormatter.error("当前目录不是Git仓库,请先初始化")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查是否有更改
|
||||||
|
OutputFormatter.info("检查文件更改...")
|
||||||
|
|
||||||
|
if not self.git_ops.has_changes():
|
||||||
|
OutputFormatter.warning("没有文件更改,无需提交")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 显示更改
|
||||||
|
print("\n当前更改的文件:")
|
||||||
|
_, status = self.git_ops.get_status()
|
||||||
|
print(status)
|
||||||
|
|
||||||
|
# 输入提交信息
|
||||||
|
commit_msg = InputValidator.get_input(
|
||||||
|
"\n请输入提交信息 (直接回车使用默认信息): ",
|
||||||
|
allow_empty=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加并提交
|
||||||
|
if not self.git_ops.add_all():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.git_ops.commit(commit_msg if commit_msg else None):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 推送到远程仓库
|
||||||
|
selected_remotes = self.remote_mgr.select_remote_for_push()
|
||||||
|
|
||||||
|
for remote in selected_remotes:
|
||||||
|
self.git_ops.push(remote)
|
||||||
|
|
||||||
|
def handle_pull(self):
|
||||||
|
"""处理拉取"""
|
||||||
|
OutputFormatter.header("从远程仓库拉取")
|
||||||
|
|
||||||
|
if not self.git_ops.is_git_repo():
|
||||||
|
OutputFormatter.error("当前目录不是Git仓库")
|
||||||
|
return
|
||||||
|
|
||||||
|
remote = self.remote_mgr.select_remote_for_pull()
|
||||||
|
if remote:
|
||||||
|
self.git_ops.pull(remote)
|
||||||
|
|
||||||
|
def handle_show_status(self):
|
||||||
|
"""处理显示状态"""
|
||||||
|
OutputFormatter.header("仓库状态")
|
||||||
|
|
||||||
|
if not self.git_ops.is_git_repo():
|
||||||
|
OutputFormatter.error("当前目录不是Git仓库")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.git_ops.show_status()
|
||||||
|
|
||||||
|
# 显示远程仓库
|
||||||
|
self.remote_mgr.show_remotes()
|
||||||
|
|
||||||
|
def handle_manage_remotes(self):
|
||||||
|
"""处理远程仓库管理"""
|
||||||
|
OutputFormatter.header("管理远程仓库")
|
||||||
|
|
||||||
|
if not self.git_ops.is_git_repo():
|
||||||
|
OutputFormatter.error("当前目录不是Git仓库")
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print("\n远程仓库管理:")
|
||||||
|
print("1. 查看远程仓库")
|
||||||
|
print("2. 添加GitHub远程仓库")
|
||||||
|
print("3. 添加Gitea远程仓库")
|
||||||
|
print("4. 删除远程仓库")
|
||||||
|
print("5. 返回主菜单")
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice("\n请选择 [1-5]: ", range(1, 6))
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
self.remote_mgr.show_remotes()
|
||||||
|
elif choice == 2:
|
||||||
|
self.remote_mgr.add_github_remote()
|
||||||
|
elif choice == 3:
|
||||||
|
self.remote_mgr.add_gitea_remote()
|
||||||
|
elif choice == 4:
|
||||||
|
self.remote_mgr.remove_remote()
|
||||||
|
elif choice == 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""运行主程序"""
|
||||||
|
self.show_welcome()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
self.show_main_menu()
|
||||||
|
|
||||||
|
choice = InputValidator.get_choice(
|
||||||
|
f"\n{Colors.BOLD}请输入选项 [1-6]: {Colors.ENDC}",
|
||||||
|
range(1, 7)
|
||||||
|
)
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
self.handle_init_repo()
|
||||||
|
elif choice == 2:
|
||||||
|
self.handle_commit_and_push()
|
||||||
|
elif choice == 3:
|
||||||
|
self.handle_pull()
|
||||||
|
elif choice == 4:
|
||||||
|
self.handle_show_status()
|
||||||
|
elif choice == 5:
|
||||||
|
self.handle_manage_remotes()
|
||||||
|
elif choice == 6:
|
||||||
|
OutputFormatter.success("感谢使用萌芽Git管理工具!")
|
||||||
|
break
|
||||||
150
quickgit/utils.py
Normal file
150
quickgit/utils.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
"""
|
||||||
|
工具类模块 - 提供命令执行、输出格式化等工具函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
class Colors:
|
||||||
|
"""控制台颜色"""
|
||||||
|
HEADER = '\033[95m'
|
||||||
|
BLUE = '\033[94m'
|
||||||
|
CYAN = '\033[96m'
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
WARNING = '\033[93m'
|
||||||
|
FAIL = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
|
||||||
|
|
||||||
|
class CommandExecutor:
|
||||||
|
"""命令执行器"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run(command: str, show_output: bool = True) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
执行命令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 要执行的命令
|
||||||
|
show_output: 是否显示输出
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否成功, 输出内容)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
command,
|
||||||
|
shell=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding='utf-8',
|
||||||
|
errors='ignore'
|
||||||
|
)
|
||||||
|
|
||||||
|
output = result.stdout + result.stderr
|
||||||
|
|
||||||
|
if show_output and output.strip():
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
return result.returncode == 0, output
|
||||||
|
except Exception as e:
|
||||||
|
OutputFormatter.error(f"命令执行失败: {str(e)}")
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputFormatter:
|
||||||
|
"""输出格式化器"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def header(text: str):
|
||||||
|
"""打印标题"""
|
||||||
|
print(f"\n{Colors.HEADER}{Colors.BOLD}{'='*50}")
|
||||||
|
print(f" {text}")
|
||||||
|
print(f"{'='*50}{Colors.ENDC}\n")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def success(text: str):
|
||||||
|
"""打印成功信息"""
|
||||||
|
print(f"{Colors.GREEN}✓ {text}{Colors.ENDC}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def error(text: str):
|
||||||
|
"""打印错误信息"""
|
||||||
|
print(f"{Colors.FAIL}✗ {text}{Colors.ENDC}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def info(text: str):
|
||||||
|
"""打印提示信息"""
|
||||||
|
print(f"{Colors.CYAN}ℹ {text}{Colors.ENDC}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def warning(text: str):
|
||||||
|
"""打印警告信息"""
|
||||||
|
print(f"{Colors.WARNING}⚠ {text}{Colors.ENDC}")
|
||||||
|
|
||||||
|
|
||||||
|
class InputValidator:
|
||||||
|
"""输入验证器"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_choice(prompt: str, valid_range: range) -> int:
|
||||||
|
"""
|
||||||
|
获取用户选择(数字)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: 提示信息
|
||||||
|
valid_range: 有效范围
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户选择的数字
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = input(prompt).strip()
|
||||||
|
choice_num = int(choice)
|
||||||
|
if choice_num in valid_range:
|
||||||
|
return choice_num
|
||||||
|
else:
|
||||||
|
OutputFormatter.error(f"请输入 {valid_range.start}-{valid_range.stop-1} 之间的数字")
|
||||||
|
except ValueError:
|
||||||
|
OutputFormatter.error("请输入有效的数字")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_input(prompt: str, allow_empty: bool = False) -> str:
|
||||||
|
"""
|
||||||
|
获取用户输入
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: 提示信息
|
||||||
|
allow_empty: 是否允许空输入
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户输入的字符串
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
user_input = input(prompt).strip()
|
||||||
|
if user_input or allow_empty:
|
||||||
|
return user_input
|
||||||
|
else:
|
||||||
|
OutputFormatter.error("输入不能为空")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def confirm(prompt: str, default: bool = False) -> bool:
|
||||||
|
"""
|
||||||
|
获取用户确认
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prompt: 提示信息
|
||||||
|
default: 默认值
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户确认结果
|
||||||
|
"""
|
||||||
|
suffix = " [Y/n]: " if default else " [y/N]: "
|
||||||
|
user_input = input(prompt + suffix).strip().lower()
|
||||||
|
|
||||||
|
if not user_input:
|
||||||
|
return default
|
||||||
|
|
||||||
|
return user_input in ['y', 'yes']
|
||||||
Reference in New Issue
Block a user