1017 lines
35 KiB
Bash
Executable File
1017 lines
35 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# ====================================================
|
||
# 项目名称: Git Master 终端可视化管理脚本 (完整重构版 v2)
|
||
# 运行环境: 适配 Android / Termux / MT 管理器终端环境
|
||
# 核心原则: 独立执行单步操作,采用现代 Git 命令,详细注释
|
||
# 新增模块: 初始化向导 / 暂存管理 / 撤销操作 / 身份配置 / 克隆仓库
|
||
# ====================================================
|
||
|
||
# ================= 配置区 =================
|
||
# 专属 Github 仓库地址与日志绝对路径 (请勿随意修改)
|
||
MY_REPO_URL="https://github.com/cluntop/tvbox.git"
|
||
LOG_FILE="/storage/emulated/0/box/.github/git.log"
|
||
LOG_DIR=$(dirname "$LOG_FILE") # FIX: 全局缓存,避免每次调用 dirname
|
||
|
||
# ================= 颜色与样式 =================
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
PURPLE='\033[0;35m'
|
||
BOLD='\033[1m'
|
||
NC='\033[0m' # 恢复默认配色
|
||
|
||
# ================= 基础核心函数 =================
|
||
|
||
# 初始化日志目录 (单独执行,避免与其他命令合并)
|
||
init_log_dir() {
|
||
if [ ! -d "$LOG_DIR" ]; then
|
||
mkdir -p "$LOG_DIR" 2>/dev/null
|
||
fi
|
||
}
|
||
init_log_dir
|
||
|
||
# 统一日志记录器
|
||
log() {
|
||
if [ -w "$LOG_DIR" ]; then
|
||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||
fi
|
||
}
|
||
|
||
# FIX: 消息函数现在实际使用颜色变量(原版定义了颜色但从未使用)
|
||
success_msg() { echo -e "${GREEN}✔ $1${NC}"; log "成功: $1"; }
|
||
error_msg() { echo -e "${RED}✘ $1${NC}"; log "错误: $1"; }
|
||
warn_msg() { echo -e "${YELLOW}⚠ $1${NC}"; log "警告: $1"; }
|
||
info_msg() { echo -e "${CYAN}ℹ $1${NC}"; }
|
||
title_msg() { echo -e "\n${BOLD}${PURPLE}>>> $1 <<<${NC}\n"; }
|
||
|
||
# 依赖检查:验证 Git 是否已安装
|
||
check_git() {
|
||
# command -v 是检测命令是否存在的标准 POSIX 写法
|
||
if ! command -v git > /dev/null 2>&1; then
|
||
error_msg "致命错误: 未检测到 Git 环境,请先安装 Git。"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 环境检查:验证当前是否处于 Git 仓库工作区内 (支持子目录识别)
|
||
check_git_repo() {
|
||
# 现代标准写法:无论在仓库的哪个子目录,只要受 git 管理都会返回 true
|
||
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||
return 0
|
||
else
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 安全的分页输出:优先使用 less,降级为直接打印
|
||
paged_output() {
|
||
if command -v less > /dev/null 2>&1; then
|
||
less -R
|
||
else
|
||
cat
|
||
fi
|
||
}
|
||
|
||
# ================= 业务功能模块 =================
|
||
|
||
# 1. 暂存变动 (Git Add)
|
||
do_add() {
|
||
title_msg "📝 步骤 1/3: 暂存文件 (Git Add)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库,请先初始化"
|
||
return 1
|
||
fi
|
||
|
||
# 获取工作区变动情况
|
||
local changes
|
||
changes=$(git status --porcelain)
|
||
if [ -z "$changes" ]; then
|
||
warn_msg "工作区纯净,没有需要暂存的修改文件。"
|
||
return 0
|
||
fi
|
||
|
||
echo -e "待暂存的变更文件:"
|
||
git status --short
|
||
echo ""
|
||
|
||
info_msg "正在执行文件追踪 (git add .) ..."
|
||
if git add .; then
|
||
success_msg "所有变更已成功加入暂存区!"
|
||
return 0
|
||
else
|
||
error_msg "暂存失败,请检查文件权限。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 2. 创建快照 (Git Commit)
|
||
do_commit() {
|
||
title_msg "📦 步骤 2/3: 提交快照 (Git Commit)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库,请先初始化"
|
||
return 1
|
||
fi
|
||
|
||
# 检查暂存区是否有待提交的内容
|
||
local staged_changes
|
||
staged_changes=$(git diff --cached --name-only)
|
||
if [ -z "$staged_changes" ]; then
|
||
warn_msg "暂存区为空。请先执行 [1] 暂存文件 (Add) 再进行提交。"
|
||
return 0
|
||
fi
|
||
|
||
# 捕获用户自定义提交信息
|
||
read -p "请输入提交信息 (直接回车默认: Update Up): " msg
|
||
if [ -z "$msg" ]; then
|
||
msg="Update Up"
|
||
fi
|
||
|
||
info_msg "正在生成本地提交快照..."
|
||
if git commit -m "$msg"; then
|
||
success_msg "版本快照生成完毕!"
|
||
return 0
|
||
else
|
||
error_msg "提交失败,请检查配置或终端输出。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 3. 单独执行:推送云端 (Git Push)
|
||
do_push() {
|
||
title_msg "🚀 步骤 3/3: 推送至云端 (Git Push)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
# 获取当前所在分支 (现代命令 --show-current)
|
||
local curr
|
||
curr=$(git branch --show-current)
|
||
if [ -z "$curr" ]; then curr="main"; fi
|
||
|
||
info_msg "正在推送数据包至 origin/$curr ..."
|
||
if git push origin "$curr"; then
|
||
success_msg "代码已成功同步至云端!"
|
||
return 0
|
||
else
|
||
warn_msg "常规推送被拒绝。远程仓库可能包含您本地没有的更改。"
|
||
read -p "⚠ 是否执行安全强制推送 (--force-with-lease)? (y/n): " force_push
|
||
if [[ "$force_push" =~ ^[Yy]$ ]]; then
|
||
info_msg "启动安全覆盖协议 (git push --force-with-lease) ..."
|
||
if git push --force-with-lease --set-upstream origin "$curr"; then
|
||
success_msg "安全强推成功!远程状态已被本地更新覆盖。"
|
||
return 0
|
||
else
|
||
error_msg "强推失败,可能存在更严重的冲突或网络问题。"
|
||
return 1
|
||
fi
|
||
else
|
||
info_msg "操作已取消。建议先执行拉取操作。"
|
||
return 1
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# 4. 拉取更新 (Git Pull)
|
||
# FIX: stash 后自动执行 pop,不再只打印提示让用户手动处理
|
||
do_pull() {
|
||
title_msg "📥 拉取最新更新 (Git Pull)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
local curr
|
||
curr=$(git branch --show-current)
|
||
if [ -z "$curr" ]; then curr="main"; fi
|
||
|
||
info_msg "1/2 探测远程状态 (git fetch)..."
|
||
git fetch origin 2>/dev/null
|
||
|
||
# 冲突阻断机制
|
||
local local_changes
|
||
local_changes=$(git status --porcelain)
|
||
local did_stash=false
|
||
|
||
if [ -n "$local_changes" ]; then
|
||
warn_msg "检测到本地有未提交的更改,直接拉取可能导致冲突!"
|
||
read -p "是否先暂存 (stash) 本地更改,安全拉取后自动恢复? (y/n): " stash_choice
|
||
if [[ "$stash_choice" =~ ^[Yy]$ ]]; then
|
||
git stash push -m "auto-stash before pull $(date '+%H:%M:%S')"
|
||
did_stash=true
|
||
info_msg "本地更改已存入 stash。"
|
||
fi
|
||
fi
|
||
|
||
info_msg "2/2 下载与合并 (git pull origin $curr)..."
|
||
if git pull origin "$curr"; then
|
||
success_msg "拉取成功,本地已是最新版本。"
|
||
# FIX: 自动恢复 stash,不再只打印提示
|
||
if [ "$did_stash" = true ]; then
|
||
info_msg "正在自动恢复您之前暂存的代码 (git stash pop)..."
|
||
if git stash pop; then
|
||
success_msg "本地代码已自动恢复!"
|
||
else
|
||
warn_msg "自动恢复失败,可能存在合并冲突,请手动执行 git stash pop 并解决冲突。"
|
||
fi
|
||
fi
|
||
return 0
|
||
else
|
||
error_msg "拉取过程产生冲突或网络连接失败。"
|
||
if [ "$did_stash" = true ]; then
|
||
warn_msg "注意:您有代码存于 stash,解决冲突后请手动执行 git stash pop。"
|
||
fi
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 5. 分支管理 (新增:删除分支)
|
||
manage_branches() {
|
||
title_msg "🌿 分支管理 (Branch)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
echo -e "当前分支列表:"
|
||
git branch -a
|
||
echo ""
|
||
echo "1) 创建并切换至新分支"
|
||
echo "2) 切换到已存在的分支"
|
||
echo "3) 删除本地分支"
|
||
echo "4) 返回主菜单"
|
||
read -p "请选择分支指令编号: " b_choice
|
||
|
||
case $b_choice in
|
||
1)
|
||
read -p "请输入新分支名称 (无空格): " b_name
|
||
if [ -n "$b_name" ]; then
|
||
if git switch -c "$b_name"; then
|
||
success_msg "已创建并切换至新分支: $b_name"
|
||
return 0
|
||
else
|
||
error_msg "分支创建失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# FIX: 原来空名称时静默无提示,现在明确报错
|
||
error_msg "分支名称不能为空"
|
||
return 1
|
||
fi
|
||
;;
|
||
2)
|
||
read -p "请输入目标分支名称: " b_name
|
||
if [ -n "$b_name" ]; then
|
||
if git switch "$b_name"; then
|
||
success_msg "成功切换至分支: $b_name"
|
||
return 0
|
||
else
|
||
error_msg "分支切换失败"
|
||
return 1
|
||
fi
|
||
else
|
||
# FIX: 原来空名称时静默无提示,现在明确报错
|
||
error_msg "分支名称不能为空"
|
||
return 1
|
||
fi
|
||
;;
|
||
3)
|
||
read -p "请输入要删除的本地分支名称: " b_name
|
||
if [ -n "$b_name" ]; then
|
||
local curr
|
||
curr=$(git branch --show-current)
|
||
if [ "$b_name" = "$curr" ]; then
|
||
error_msg "无法删除当前所在分支,请先切换到其他分支。"
|
||
return 1
|
||
fi
|
||
read -p "⚠ 确认删除本地分支 '$b_name'? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
if git branch -d "$b_name"; then
|
||
success_msg "分支 '$b_name' 已删除。"
|
||
return 0
|
||
else
|
||
warn_msg "该分支存在未合并的提交,是否强制删除?"
|
||
read -p "强制删除? (y/n): " force_del
|
||
if [[ "$force_del" =~ ^[Yy]$ ]]; then
|
||
if git branch -D "$b_name"; then
|
||
success_msg "已强制删除分支 '$b_name'。"
|
||
return 0
|
||
fi
|
||
fi
|
||
fi
|
||
fi
|
||
else
|
||
error_msg "分支名称不能为空"
|
||
return 1
|
||
fi
|
||
;;
|
||
4)
|
||
info_msg "操作取消"
|
||
return 0
|
||
;;
|
||
*)
|
||
error_msg "无效选项"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 6. 状态明细
|
||
# FIX: diff 使用 less -R 分页,避免改动多时刷屏
|
||
view_status() {
|
||
title_msg "📊 库区状态剖析"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
echo -e "${BOLD}【当前文件级状态概览 (git status -s)】${NC}"
|
||
git status -s
|
||
echo ""
|
||
|
||
echo -e "${BOLD}【工作区尚未暂存的代码变动 (git diff)】${NC}"
|
||
git --no-pager diff | paged_output
|
||
echo ""
|
||
|
||
echo -e "${BOLD}【已放入暂存区待提交的代码变动 (git diff --cached)】${NC}"
|
||
git --no-pager diff --cached | paged_output
|
||
echo ""
|
||
return 0
|
||
}
|
||
|
||
# 7. 历史查询
|
||
view_logs() {
|
||
title_msg "📜 提交历史溯源"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
git --no-pager log --graph \
|
||
--pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' \
|
||
--abbrev-commit -n 15
|
||
echo -e "\n"
|
||
return 0
|
||
}
|
||
|
||
# 8. 绑定远程地址
|
||
bind_remote() {
|
||
title_msg "🔗 绑定远程仓库地址"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
local current_url
|
||
current_url=$(git remote get-url origin 2>/dev/null || echo "未绑定")
|
||
|
||
echo -e "当前设备识别到的源地址: ${CYAN}$current_url${NC}"
|
||
echo -e "脚本预设的目标源地址: ${CYAN}$MY_REPO_URL${NC}"
|
||
|
||
read -p "确认将本地仓库指向预设目标地址吗? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
git remote remove origin 2>/dev/null
|
||
if git remote add origin "$MY_REPO_URL"; then
|
||
success_msg "远程源绑定成功!"
|
||
return 0
|
||
else
|
||
error_msg "绑定失败,请检查权限。"
|
||
return 1
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# 9. NEW: 初始化向导 (init + config + bind remote 三合一)
|
||
# FIX: 兼容旧版 Git (< 2.28),不依赖 --initial-branch 参数
|
||
init_wizard() {
|
||
title_msg "🧙 初始化向导 (Init Wizard)"
|
||
if check_git_repo; then
|
||
error_msg "阻止操作:当前已经是受控 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
# Step 1: git init
|
||
info_msg "[1/3] 初始化 Git 仓库..."
|
||
if git init; then
|
||
# 兼容旧版 Git:通过 symbolic-ref 设置默认分支为 main
|
||
git symbolic-ref HEAD refs/heads/main 2>/dev/null || true
|
||
success_msg "仓库初始化完毕!默认分支已设为 main。"
|
||
else
|
||
error_msg "初始化失败"
|
||
return 1
|
||
fi
|
||
|
||
# Step 2: 用户身份配置
|
||
info_msg "[2/3] 配置 Git 身份信息..."
|
||
local cur_name cur_email
|
||
cur_name=$(git config --global user.name 2>/dev/null)
|
||
cur_email=$(git config --global user.email 2>/dev/null)
|
||
echo -e "当前 user.name : ${CYAN}${cur_name:-未设置}${NC}"
|
||
echo -e "当前 user.email : ${CYAN}${cur_email:-未设置}${NC}"
|
||
|
||
read -p "请输入 user.name (回车跳过): " new_name
|
||
if [ -n "$new_name" ]; then
|
||
git config --global user.name "$new_name"
|
||
success_msg "user.name 已设为: $new_name"
|
||
fi
|
||
|
||
read -p "请输入 user.email (回车跳过): " new_email
|
||
if [ -n "$new_email" ]; then
|
||
git config --global user.email "$new_email"
|
||
success_msg "user.email 已设为: $new_email"
|
||
fi
|
||
|
||
# Step 3: 绑定远程
|
||
info_msg "[3/3] 绑定远程仓库..."
|
||
echo -e "预设远程地址: ${CYAN}$MY_REPO_URL${NC}"
|
||
read -p "是否绑定到此地址? (y/n): " bind_confirm
|
||
if [[ "$bind_confirm" =~ ^[Yy]$ ]]; then
|
||
git remote remove origin 2>/dev/null
|
||
if git remote add origin "$MY_REPO_URL"; then
|
||
success_msg "远程地址绑定成功!"
|
||
else
|
||
error_msg "远程地址绑定失败。"
|
||
fi
|
||
fi
|
||
|
||
echo -e "【当前文件级状态概览 (git status -s)】"
|
||
git status -s
|
||
echo ""
|
||
success_msg "🎉 初始化向导完成!可以开始使用 Add → Commit → Push 工作流了。"
|
||
return 0
|
||
}
|
||
|
||
# 10. 切换目录
|
||
# FIX: 移除永远为真的 if 判断,cd 成功即确认
|
||
change_dir() {
|
||
title_msg "📁 切换物理工作目录"
|
||
echo -e "当前系统位置: $(pwd)"
|
||
read -p "请输入新路径 (绝对/相对均可): " new_path
|
||
if [ -n "$new_path" ]; then
|
||
if [ ! -d "$new_path" ]; then
|
||
mkdir -p "$new_path" 2>/dev/null
|
||
fi
|
||
if cd "$new_path"; then
|
||
success_msg "系统位置已成功转移至: $(pwd)"
|
||
return 0
|
||
else
|
||
error_msg "无法进入指定路径"
|
||
return 1
|
||
fi
|
||
fi
|
||
return 0
|
||
}
|
||
|
||
# 11. 深度清理
|
||
# FIX: 同时检查两条命令的执行结果
|
||
deep_clean() {
|
||
title_msg "🧹 垃圾回收与深度清理"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
info_msg "清理历史动作残留并压缩数据库..."
|
||
if git reflog expire --expire=now --all 2>/dev/null && \
|
||
git gc --prune=now --aggressive 2>/dev/null; then
|
||
success_msg "清理成功!当前 .git 体积为: $(du -sh .git 2>/dev/null | cut -f1)"
|
||
return 0
|
||
else
|
||
error_msg "清理任务中断或失败。"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
# 12. NEW: 统一暂存管理 (Stash 子菜单,整合 Save / Pop / Drop)
|
||
manage_stash() {
|
||
title_msg "📦 暂存管理 (Stash)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
echo -e "${BOLD}【当前暂存记录列表】${NC}"
|
||
local stash_count
|
||
stash_count=$(git stash list | wc -l)
|
||
|
||
if [ "$stash_count" -eq 0 ]; then
|
||
echo -e " (暂无记录)"
|
||
else
|
||
git --no-pager stash list
|
||
fi
|
||
echo ""
|
||
echo "1) 🗃 存入暂存 (Stash Save) — 将工作区改动压入 stash"
|
||
echo "2) 📤 恢复最新 (Stash Pop) — 弹出最新一条并合并到工作区"
|
||
echo "3) 🗑 删除记录 (Stash Drop) — 删除指定的 stash 条目"
|
||
echo "4) 🔙 返回主菜单"
|
||
read -p "请选择操作: " s_choice
|
||
|
||
case $s_choice in
|
||
1)
|
||
local local_changes
|
||
local_changes=$(git status --porcelain)
|
||
if [ -z "$local_changes" ]; then
|
||
warn_msg "工作区纯净,没有需要暂存的改动。"
|
||
return 0
|
||
fi
|
||
read -p "请输入暂存备注 (回车使用默认): " stash_msg
|
||
if [ -z "$stash_msg" ]; then
|
||
stash_msg="manual-stash $(date '+%m-%d %H:%M')"
|
||
fi
|
||
if git stash push -m "$stash_msg"; then
|
||
success_msg "当前改动已安全存入 stash: $stash_msg"
|
||
return 0
|
||
else
|
||
error_msg "存入暂存失败。"
|
||
return 1
|
||
fi
|
||
;;
|
||
2)
|
||
if [ "$stash_count" -eq 0 ]; then
|
||
warn_msg "当前没有任何暂存记录。"
|
||
return 0
|
||
fi
|
||
local local_changes
|
||
local_changes=$(git status --porcelain)
|
||
if [ -n "$local_changes" ]; then
|
||
warn_msg "高危拦截:工作区存在未提交的修改,此时释放 stash 可能产生冲突!"
|
||
read -p "是否仍要尝试恢复? (y/n): " force_pop
|
||
if [[ ! "$force_pop" =~ ^[Yy]$ ]]; then
|
||
info_msg "操作已安全取消。"
|
||
return 0
|
||
fi
|
||
fi
|
||
info_msg "正在释放 stash (git stash pop)..."
|
||
local pop_output pop_status
|
||
pop_output=$(git stash pop 2>&1)
|
||
pop_status=$?
|
||
echo -e "$pop_output"
|
||
if [ $pop_status -eq 0 ]; then
|
||
success_msg "恢复成功!暂存代码已回到工作区。"
|
||
return 0
|
||
elif echo "$pop_output" | grep -q "Aborting"; then
|
||
error_msg "恢复被 Git 中止!工作区存在冲突文件,请先提交或丢弃后再试。"
|
||
return 1
|
||
else
|
||
error_msg "恢复产生合并冲突!请解决文件内的冲突标记 (<<<<<<<) 后手动提交。"
|
||
return 1
|
||
fi
|
||
;;
|
||
3)
|
||
if [ "$stash_count" -eq 0 ]; then
|
||
warn_msg "当前没有任何暂存记录。"
|
||
return 0
|
||
fi
|
||
echo ""
|
||
git --no-pager stash list
|
||
echo ""
|
||
read -p "请输入要删除的编号 (如输入 0 代表 stash@{0}): " stash_idx
|
||
if [[ "$stash_idx" =~ ^[0-9]+$ ]]; then
|
||
read -p "⚠ 确认删除 stash@{$stash_idx}? 此操作不可恢复! (y/n): " del_confirm
|
||
if [[ "$del_confirm" =~ ^[Yy]$ ]]; then
|
||
if git stash drop "stash@{$stash_idx}"; then
|
||
success_msg "已成功删除 stash@{$stash_idx}。"
|
||
return 0
|
||
else
|
||
error_msg "删除失败,请确认编号是否正确。"
|
||
return 1
|
||
fi
|
||
fi
|
||
else
|
||
error_msg "请输入有效的数字编号。"
|
||
return 1
|
||
fi
|
||
;;
|
||
4)
|
||
info_msg "返回主菜单"
|
||
return 0
|
||
;;
|
||
*)
|
||
error_msg "无效选项"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 13. 一键同步 (完整修复版)
|
||
# FIX1: 修正执行顺序 → Stash(保护) → Pull → Pop → Add → Commit → Push
|
||
# FIX2: 提交信息附带时间戳,与原注释一致
|
||
do_one_click() {
|
||
title_msg "⚡ 执行一键同步工作流"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
local curr
|
||
curr=$(git branch --show-current 2>/dev/null)
|
||
if [ -z "$curr" ]; then curr="main"; fi
|
||
|
||
local did_stash=false
|
||
|
||
# Step 0: 检测本地改动,提前 stash 保护,再拉取
|
||
local local_changes
|
||
local_changes=$(git status --porcelain)
|
||
if [ -n "$local_changes" ]; then
|
||
info_msg "[0/5] 检测到本地改动,自动存入 stash 保护..."
|
||
if git stash push -m "auto-stash $(date '+%m-%d %H:%M:%S')"; then
|
||
did_stash=true
|
||
success_msg "本地改动已安全暂存。"
|
||
else
|
||
error_msg "自动暂存失败,终止同步以保护您的代码。"
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
# Step 1: 拉取
|
||
info_msg "[1/5] 正在拉取远程更新 (Git Pull)..."
|
||
local pull_out
|
||
# 捕获原生输出和错误
|
||
pull_out=$(git pull origin "$curr" 2>&1)
|
||
if [ $? -ne 0 ]; then
|
||
error_msg "拉取失败!Git 报错:"
|
||
echo -e "$pull_out"
|
||
[ "$did_stash" = true ] && warn_msg "您的代码已安全保存在 stash 中,请手动 git stash pop 恢复。"
|
||
return 1
|
||
fi
|
||
|
||
# Step 2: 恢复 stash
|
||
if [ "$did_stash" = true ]; then
|
||
info_msg "[2/5] 正在恢复暂存代码 (Git Stash Pop)..."
|
||
local pop_out
|
||
pop_out=$(git stash pop 2>&1)
|
||
if [ $? -ne 0 ]; then
|
||
error_msg "恢复暂存失败!可能存在合并冲突,请手动解决。"
|
||
echo -e "$pop_out"
|
||
return 1
|
||
fi
|
||
success_msg "暂存代码已恢复。"
|
||
else
|
||
info_msg "[2/5] 无暂存记录,自动跳过。"
|
||
fi
|
||
|
||
# Step 3: Add
|
||
info_msg "[3/5] 正在追踪变动文件 (Git Add)..."
|
||
if ! git add . 2>&1; then
|
||
error_msg "暂存文件失败!"
|
||
return 1
|
||
fi
|
||
|
||
# 4. 提交快照 (Commit)
|
||
info_msg "[4/5] 正在生成快照 (Git Commit)..."
|
||
# 如果有可提交的改动才执行 commit
|
||
if ! git diff --cached --quiet; then
|
||
local commit_msg="Update Up"
|
||
if ! git commit -m "$commit_msg"; then
|
||
error_msg "提交代码失败!"
|
||
return 1
|
||
fi
|
||
else
|
||
warn_msg "工作区没有新变动,跳过提交。"
|
||
fi
|
||
|
||
# Step 5: Push
|
||
info_msg "[5/5] 正在推送至云端 (Git Push)..."
|
||
local push_out
|
||
push_out=$(git push origin "$curr" 2>&1)
|
||
if [ $? -ne 0 ]; then
|
||
error_msg "推送失败!Git 报错:"
|
||
echo -e "$push_out"
|
||
return 1
|
||
fi
|
||
|
||
success_msg "🎉 一键同步工作流全部执行成功!"
|
||
return 0
|
||
}
|
||
|
||
# 14. NEW: 撤销操作 (Undo/Reset/Restore)
|
||
undo_changes() {
|
||
title_msg "↩️ 撤销操作 (Undo)"
|
||
if ! check_git_repo; then
|
||
error_msg "当前目录非 Git 仓库"
|
||
return 1
|
||
fi
|
||
|
||
echo "1) 撤销最近一次 commit,改动退回暂存区 — git reset HEAD~1 --soft"
|
||
echo "2) 撤销最近一次 commit,改动退回工作区 — git reset HEAD~1 --mixed"
|
||
echo "3) 丢弃工作区全部未暂存改动 — git restore ."
|
||
echo "4) 丢弃指定文件的未暂存改动 — git restore <file>"
|
||
echo "5) 将指定文件移出暂存区 (取消 git add) — git restore --staged <file>"
|
||
echo "6) 返回主菜单"
|
||
echo ""
|
||
read -p "请选择操作: " u_choice
|
||
|
||
case $u_choice in
|
||
1)
|
||
warn_msg "将撤销最近一次 commit,改动退回暂存区 (staged)。"
|
||
read -p "确认执行? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
if git reset HEAD~1 --soft; then
|
||
success_msg "已撤销最近一次 commit,改动已退回暂存区。"
|
||
else
|
||
error_msg "撤销失败。"
|
||
fi
|
||
fi
|
||
;;
|
||
2)
|
||
warn_msg "将撤销最近一次 commit,改动退回工作区 (unstaged)。"
|
||
read -p "确认执行? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
if git reset HEAD~1 --mixed; then
|
||
success_msg "已撤销最近一次 commit,改动已退回工作区。"
|
||
else
|
||
error_msg "撤销失败。"
|
||
fi
|
||
fi
|
||
;;
|
||
3)
|
||
warn_msg "⚠ 高危:将丢弃工作区全部未暂存的改动,且不可恢复!"
|
||
read -p "确认丢弃? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
if git restore .; then
|
||
success_msg "工作区已恢复到最近一次提交的状态。"
|
||
else
|
||
error_msg "操作失败。"
|
||
fi
|
||
fi
|
||
;;
|
||
4)
|
||
read -p "请输入要丢弃改动的文件路径: " file_path
|
||
if [ -n "$file_path" ]; then
|
||
warn_msg "⚠ 将丢弃 '$file_path' 的未暂存改动,且不可恢复!"
|
||
read -p "确认? (y/n): " confirm
|
||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||
if git restore "$file_path"; then
|
||
success_msg "已丢弃 $file_path 的改动。"
|
||
else
|
||
error_msg "操作失败,请检查文件路径是否正确。"
|
||
fi
|
||
fi
|
||
else
|
||
error_msg "文件路径不能为空。"
|
||
return 1
|
||
fi
|
||
;;
|
||
5)
|
||
read -p "请输入要取消暂存的文件路径: " file_path
|
||
if [ -n "$file_path" ]; then
|
||
if git restore --staged "$file_path"; then
|
||
success_msg "已将 $file_path 从暂存区移回工作区。"
|
||
else
|
||
error_msg "操作失败,请检查文件路径是否正确。"
|
||
fi
|
||
else
|
||
error_msg "文件路径不能为空。"
|
||
return 1
|
||
fi
|
||
;;
|
||
6)
|
||
return 0
|
||
;;
|
||
*)
|
||
error_msg "无效选项"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 15. NEW: Git 身份配置 (Config)
|
||
git_config() {
|
||
title_msg "🔧 Git 身份配置 (Config)"
|
||
|
||
local cur_name cur_email
|
||
cur_name=$(git config --global user.name 2>/dev/null)
|
||
cur_email=$(git config --global user.email 2>/dev/null)
|
||
|
||
echo -e "当前全局 user.name : ${CYAN}${cur_name:-未设置}${NC}"
|
||
echo -e "当前全局 user.email : ${CYAN}${cur_email:-未设置}${NC}"
|
||
echo ""
|
||
echo "1) 修改 user.name"
|
||
echo "2) 修改 user.email"
|
||
echo "3) 查看完整的全局 Git 配置"
|
||
echo "4) 返回主菜单"
|
||
read -p "请选择操作: " c_choice
|
||
|
||
case $c_choice in
|
||
1)
|
||
read -p "请输入新的 user.name: " new_name
|
||
if [ -n "$new_name" ]; then
|
||
git config --global user.name "$new_name"
|
||
success_msg "user.name 已更新为: $new_name"
|
||
else
|
||
error_msg "名称不能为空"
|
||
fi
|
||
;;
|
||
2)
|
||
read -p "请输入新的 user.email: " new_email
|
||
if [ -n "$new_email" ]; then
|
||
git config --global user.email "$new_email"
|
||
success_msg "user.email 已更新为: $new_email"
|
||
else
|
||
error_msg "邮箱不能为空"
|
||
fi
|
||
;;
|
||
3)
|
||
echo ""
|
||
git config --global --list
|
||
;;
|
||
4)
|
||
return 0
|
||
;;
|
||
*)
|
||
error_msg "无效选项"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# 16. NEW: 克隆仓库 (Clone)
|
||
clone_repo() {
|
||
title_msg "📥 克隆仓库 (Clone)"
|
||
|
||
echo -e "预设仓库地址: ${CYAN}$MY_REPO_URL${NC}"
|
||
echo ""
|
||
echo "1) 克隆到当前目录下 (自动创建子文件夹)"
|
||
echo "2) 克隆到指定路径"
|
||
echo "3) 返回主菜单"
|
||
read -p "请选择操作: " cl_choice
|
||
|
||
case $cl_choice in
|
||
1)
|
||
info_msg "正在克隆 $MY_REPO_URL ..."
|
||
if git clone "$MY_REPO_URL"; then
|
||
success_msg "克隆成功!"
|
||
return 0
|
||
else
|
||
error_msg "克隆失败,请检查网络或仓库地址。"
|
||
return 1
|
||
fi
|
||
;;
|
||
2)
|
||
read -p "请输入目标路径: " target_path
|
||
if [ -n "$target_path" ]; then
|
||
info_msg "正在克隆到 $target_path ..."
|
||
if git clone "$MY_REPO_URL" "$target_path"; then
|
||
success_msg "克隆成功!目录: $target_path"
|
||
return 0
|
||
else
|
||
error_msg "克隆失败,请检查网络或路径。"
|
||
return 1
|
||
fi
|
||
else
|
||
error_msg "路径不能为空"
|
||
return 1
|
||
fi
|
||
;;
|
||
3)
|
||
return 0
|
||
;;
|
||
*)
|
||
error_msg "无效选项"
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# ================= 终端前端 GUI / 菜单仪表盘 =================
|
||
show_dashboard() {
|
||
clear 2>/dev/null || printf '\033[2J\033[H'
|
||
echo -e "${BOLD}══════════════════════════════════════════════${NC}"
|
||
echo -e "${BOLD}${PURPLE} 🛠️ Git Master 控制台 ${NC}"
|
||
echo -e "${BOLD}══════════════════════════════════════════════${NC}"
|
||
|
||
echo -e " 📍 物理坐标: ${CYAN}$(pwd)${NC}"
|
||
|
||
if check_git_repo; then
|
||
local b_name changes remote
|
||
b_name=$(git branch --show-current 2>/dev/null)
|
||
# local changes
|
||
changes=$(git status --porcelain 2>/dev/null | wc -l)
|
||
# local remote
|
||
remote=$(git remote get-url origin 2>/dev/null || echo "未绑定远程")
|
||
|
||
echo -e " 🌿 当前分支: ${GREEN}$b_name${NC}"
|
||
echo -e " 🔗 远程目标: ${CYAN}$remote${NC}"
|
||
|
||
if [ "$changes" -gt 0 ]; then
|
||
echo -e " 📝 变动预警: ${YELLOW}工作区有 $changes 个变更未处理${NC}"
|
||
else
|
||
echo -e " 📝 变动预警: ${GREEN}工作区完全纯净${NC}"
|
||
fi
|
||
else
|
||
echo -e " ${YELLOW}⚠️ 存储核心: 未检测到 Git 数据库${NC}"
|
||
fi
|
||
|
||
echo -e "${BOLD}──────────────────────────────────────────────${NC}"
|
||
echo -e " [1] 📝 暂存变动 (Git Add)"
|
||
echo -e " [2] 📦 创建快照 (Git Commit)"
|
||
echo -e " [3] 🚀 推送云端 (Git Push)"
|
||
echo -e " [4] 📥 拉取更新 (Git Pull)"
|
||
echo -e " [5] 🌿 分支管理 (Branch)"
|
||
echo -e " [6] 📊 状态明细 (Status & Diff)"
|
||
echo -e " [7] 📜 历史查询 (Log Graph)"
|
||
echo -e " [8] 🔗 绑定源址 (Bind Remote)"
|
||
echo -e " [9] 🧙 初始化向导 (Init + Config + Bind)"
|
||
echo -e " [10] 📁 切换目录 (Change Dir)"
|
||
echo -e " [11] 🧹 深度清理 (Git GC)"
|
||
echo -e " [12] 📦 暂存管理 (Stash Save/Pop/Drop)"
|
||
echo -e " [13] ⚡ 一键同步 (Stash→Pull→Pop→Add→Commit→Push)"
|
||
echo -e " [14] ↩️ 撤销操作 (Undo/Reset/Restore)"
|
||
echo -e " [15] 🔧 身份配置 (Git Config)"
|
||
echo -e " [16] 📥 克隆仓库 (Clone)"
|
||
echo -e " [0] ❌ 退出终端 (Exit)"
|
||
echo -e "${BOLD}══════════════════════════════════════════════${NC}"
|
||
}
|
||
|
||
# ================= 权限前置防线 =================
|
||
if [ "$(id -u)" -ne 0 ]; then
|
||
warn_msg "环境提示:未检测到 Root 权限,针对根目录等高权区域可能会读写受阻。"
|
||
fi
|
||
|
||
check_git
|
||
|
||
# ================= 命令行外置参数解析路由器 =================
|
||
if [ $# -gt 0 ]; then
|
||
case "$1" in
|
||
add) do_add ;;
|
||
commit) do_commit ;;
|
||
push) do_push ;;
|
||
pull) do_pull ;;
|
||
branch) manage_branches ;;
|
||
status) view_status ;;
|
||
log) view_logs ;;
|
||
bind) bind_remote ;;
|
||
init) init_wizard ;;
|
||
cd) change_dir ;;
|
||
clean) deep_clean ;;
|
||
stash) manage_stash ;;
|
||
sync) do_one_click ;;
|
||
undo) undo_changes ;;
|
||
config) git_config ;;
|
||
clone) clone_repo ;;
|
||
help|-h|--help)
|
||
echo -e "Git Master CLI 独立模式使用指南:"
|
||
echo -e " add : 暂存当前所有改动"
|
||
echo -e " commit : 为暂存的内容创建快照"
|
||
echo -e " push : 将本地提交推送到远程仓库"
|
||
echo -e " pull : 拉取远程最新版本"
|
||
echo -e " branch : 分支管理"
|
||
echo -e " status : 查看仓库状态"
|
||
echo -e " log : 查看提交历史"
|
||
echo -e " bind : 绑定远程地址"
|
||
echo -e " init : 初始化向导 (Init + Config + Bind)"
|
||
echo -e " clean : 深度清理"
|
||
echo -e " stash : 暂存管理 (Save/Pop/Drop)"
|
||
echo -e " sync : 一键同步"
|
||
echo -e " undo : 撤销操作 (Reset/Restore)"
|
||
echo -e " config : Git 身份配置"
|
||
echo -e " clone : 克隆仓库"
|
||
;;
|
||
*) error_msg "未识别的参数: $1" ;;
|
||
esac
|
||
exit 0
|
||
fi
|
||
|
||
# ================= 交互式生命周期循环 =================
|
||
while true; do
|
||
show_dashboard
|
||
read -p "👉 键入数字并回车: " choice
|
||
|
||
# FIX: 移除原版 case 里的 [ $? -eq 0 ] 死代码
|
||
# 逻辑:子菜单/查看类操作始终暂停;写操作成功后自动刷新
|
||
require_pause=true
|
||
|
||
case $choice in
|
||
1) do_add ;;
|
||
2) do_commit ;;
|
||
3) do_push ;;
|
||
4) do_pull ;;
|
||
5) manage_branches ;;
|
||
6) view_status ;;
|
||
7) view_logs ;;
|
||
8) bind_remote && require_pause=false ;;
|
||
9) init_wizard && require_pause=false ;;
|
||
10) change_dir && require_pause=false ;;
|
||
11) deep_clean && require_pause=false ;;
|
||
12) manage_stash ;;
|
||
13) do_one_click ;;
|
||
14) undo_changes ;;
|
||
15) git_config ;;
|
||
16) clone_repo ;;
|
||
0) echo "控制台已下线。"; exit 0 ;;
|
||
*) error_msg "非法的选项指令,请确认您输入的数字有效" ;;
|
||
esac
|
||
|
||
echo ""
|
||
if [ "$require_pause" = true ]; then
|
||
# 失败、或者执行查看类操作时,要求按回车以便查阅终端内容
|
||
read -p "按 [Enter] 键继续..."
|
||
else
|
||
# 成功完成操作时,停留一小会儿让用户看清成功提示,然后自动刷新面板
|
||
# FIX: sleep 1.5 不符合 POSIX,部分系统不支持小数,改为 sleep 2
|
||
info_msg "操作成功完成,即将自动返回主菜单..."
|
||
sleep 2
|
||
fi
|
||
done
|