Files
tvbox-1/git.sh
T
2026-05-29 02:37:07 +08:00

555 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ====================================================
# 项目名称: Git Master 终端可视化管理脚本 (重构优化版)
# 运行环境: 适配 Android / Termux / MT 管理器终端环境
# 核心原则: 独立执行单步操作,采用现代 Git 命令,详细注释
# ====================================================
# ================= 配置区 =================
# 专属 Github 仓库地址与日志绝对路径 (请勿随意修改)
MY_REPO_URL="https://github.com/cluntop/tvbox.git"
LOG_FILE="/data/data/bin.mt.plus/home/tvbox/.github/git.log"
# ================= 颜色与样式 =================
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() {
local log_dir
log_dir=$(dirname "$LOG_FILE")
if [ ! -d "$log_dir" ]; then
mkdir -p "$log_dir" 2>/dev/null
fi
}
init_log_dir
# 统一日志记录器
log() {
local log_dir
log_dir=$(dirname "$LOG_FILE")
# 检查日志目录是否具有可写权限
if [ -w "$log_dir" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
fi
}
# 格式化消息输出函数
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
}
# ================= 业务功能模块 (原子化分离) =================
# 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 "${YELLOW}待暂存的变更文件:${NC}"
git status --short
echo ""
info_msg "正在执行文件追踪 (git add .) ..."
git add .
# 单独校验执行结果
if [ $? -eq 0 ]; then
success_msg "所有变更已成功加入暂存区!"
else
error_msg "暂存失败,请检查文件权限。"
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 "正在生成本地提交快照..."
git commit -m "$msg"
if [ $? -eq 0 ]; then
success_msg "版本快照生成完毕!"
else
error_msg "提交失败,请检查配置或终端输出。"
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 ..."
git push origin "$curr"
# 根据状态码判断推送是否成功
if [ $? -eq 0 ]; then
success_msg "代码已成功同步至云端!"
else
warn_msg "常规推送被拒绝。远程仓库可能包含您本地没有的更改。"
read -p "⚠ 是否执行安全强制推送 (使用 --force-with-lease 避免误覆盖他人代码)? (y/n): " force_push
if [[ "$force_push" =~ ^[Yy]$ ]]; then
info_msg "启动安全覆盖协议 (git push --force-with-lease) ..."
# 使用更现代、更安全的 force-with-lease 替代危险的 -f
git push --force-with-lease --set-upstream origin "$curr"
if [ $? -eq 0 ]; then
success_msg "安全强推成功!远程状态已被本地更新覆盖。"
else
error_msg "强推失败,可能存在更严重的冲突或网络问题。"
fi
else
info_msg "操作已取消。建议先执行拉取操作。"
fi
fi
}
# 4. 单独执行:拉取更新 (Git Pull)
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 stash_choice="n"
if [ -n "$local_changes" ]; then
warn_msg "检测到本地有未提交的更改。直接拉取可能导致冲突!"
read -p "是否先暂存(stash)本地更改,安全拉取后再恢复? (y/n): " stash_choice
if [[ "$stash_choice" =~ ^[Yy]$ ]]; then
git stash
info_msg "本地更改已存入 stash。"
fi
fi
info_msg "2/2 下载与合并 (git pull origin $curr)..."
git pull origin "$curr"
if [ $? -eq 0 ]; then
success_msg "拉取成功,本地已是最新版本。"
else
error_msg "拉取过程产生冲突或网络连接失败。"
fi
# 如果刚才暂存了代码,提示恢复
if [[ "$stash_choice" =~ ^[Yy]$ ]]; then
warn_msg "请记得手动执行 'git stash pop' 来恢复您刚才暂存的本地代码。"
fi
}
# 5. 现代版:分支管理 (使用 git switch)
manage_branches() {
title_msg "🌿 分支管理 (Branch)"
if ! check_git_repo; then
error_msg "当前目录非 Git 仓库"
return 1
fi
echo -e "${CYAN}当前分支列表:${NC}"
git branch -a
echo ""
echo "1) 基于当前状态创建并切换至新分支"
echo "2) 切换到已存在的其他分支"
echo "3) 返回主菜单"
read -p "请选择分支指令编号: " b_choice
case $b_choice in
1)
read -p "请输入新分支名称 (无空格): " b_name
if [ -n "$b_name" ]; then
# 现代命令:使用 switch -c 替代 checkout -b
git switch -c "$b_name"
if [ $? -eq 0 ]; then
success_msg "已创建并切换至新分支: $b_name"
else
error_msg "分支创建失败"
fi
fi
;;
2)
read -p "请输入目标分支名称: " b_name
if [ -n "$b_name" ]; then
# 现代命令:使用 switch 替代 checkout
git switch "$b_name"
if [ $? -eq 0 ]; then
success_msg "成功切换至分支: $b_name"
else
error_msg "分支切换失败"
fi
fi
;;
3)
info_msg "操作取消"
;;
*)
error_msg "无效选项"
;;
esac
}
# 6. 单独执行:绑定远程地址
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 -e "当前设备识别到的源地址: ${YELLOW}${current_url:-"[本地暂无配置远程源]"}${NC}"
echo -e "脚本预设的目标源地址: ${GREEN}$MY_REPO_URL${NC}"
read -p "确认将本地仓库指向预设目标地址吗? (y/n): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
# 分离执行删除和添加,避免合并逻辑隐患
git remote remove origin 2>/dev/null
git remote add origin "$MY_REPO_URL"
if [ $? -eq 0 ]; then
success_msg "远程源绑定成功!"
else
error_msg "绑定失败,请检查权限。"
fi
fi
}
# 7. 现代版:初始化仓库
init_repo() {
title_msg "📦 初始化 Git 仓库"
if check_git_repo; then
error_msg "阻止操作:当前已经是受控 Git 仓库"
return 1
fi
# 现代命令:直接在初始化时指定默认主分支为 main (Git 2.28+)
git init --initial-branch=main
if [ $? -eq 0 ]; then
success_msg "仓库初始化完毕!默认主分支已设为: main"
else
error_msg "初始化失败"
fi
}
# 8. 单独执行:历史查询
view_logs() {
title_msg "📜 提交历史溯源"
if ! check_git_repo; then
error_msg "当前目录非 Git 仓库"
return 1
fi
# 限制显示 15 条
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"
}
# 9. 状态剖析与明细
view_status() {
title_msg "📊 库区状态剖析"
if ! check_git_repo; then
error_msg "当前目录非 Git 仓库"
return 1
fi
echo -e "${CYAN}【当前文件级状态概览 (git status -s)】${NC}"
git status -s
echo ""
echo -e "${CYAN}【工作区尚未暂存的代码变动 (git diff)】${NC}"
git --no-pager diff
echo ""
echo -e "${CYAN}【已放入暂存区待提交的代码变动 (git diff --cached)】${NC}"
git --no-pager diff --cached
echo ""
}
# 10. 系统操作:切换目录
change_dir() {
title_msg "📁 切换物理工作目录"
echo -e "当前系统位置: ${YELLOW}$(pwd)${NC}"
read -p "请输入新路径 (绝对/相对均可): " new_path
if [ -n "$new_path" ]; then
if [ ! -d "$new_path" ]; then
mkdir -p "$new_path" 2>/dev/null
fi
cd "$new_path" || error_msg "无法进入指定路径"
if [ "$(pwd)" = "$new_path" ] || [ "$(pwd)" = "$(realpath "$new_path" 2>/dev/null)" ]; then
success_msg "系统位置已成功转移至: $(pwd)"
fi
fi
}
# 11. 系统操作:深度清理
deep_clean() {
title_msg "🧹 垃圾回收与深度清理"
if ! check_git_repo; then
error_msg "当前目录非 Git 仓库"
return 1
fi
info_msg "清理历史动作残留并压缩数据库..."
git reflog expire --expire=now --all 2>/dev/null
git gc --prune=now --aggressive 2>/dev/null
if [ $? -eq 0 ]; then
success_msg "清理成功!当前 .git 体积为: $(du -sh .git 2>/dev/null | cut -f1)"
else
error_msg "清理任务中断或失败。"
fi
}
# 12. 单独执行:恢复暂存代码 (Stash Pop)
restore_stash() {
title_msg "📦 恢复暂存代码 (Git Stash Pop)"
if ! check_git_repo; then
error_msg "当前目录非 Git 仓库"
return 1
fi
# 检查是否有 stash 记录
local stash_count
stash_count=$(git stash list | wc -l)
if [ "$stash_count" -eq 0 ]; then
warn_msg "当前没有发现任何被暂存 (stash) 的代码记录。"
return 0
fi
echo -e "${CYAN}【当前的暂存记录列表 (git stash list)】${NC}"
git --no-pager stash list
echo ""
# === 新增安全防线:检测工作区是否干净 ===
local local_changes
local_changes=$(git status --porcelain)
if [ -n "$local_changes" ]; then
warn_msg "高危拦截:您的工作区目前存在未提交的修改!"
error_msg "此时强制释放暂存极大概率会导致覆盖报错 (Aborting)。"
info_msg "建议方案:请先按 [1] 暂存并 [2] 提交当前代码,然后再执行此操作。"
read -p "⚠ 是否仍要无视警告强行尝试释放?(y/n): " force_pop
if [[ ! "$force_pop" =~ ^[Yy]$ ]]; then
info_msg "操作已安全取消。"
return 0
fi
fi
# =======================================
read -p "检测到有 $stash_count 条暂存记录,是否立即恢复最新的一条并合并回工作区? (y/n): " pop_choice
if [[ "$pop_choice" =~ ^[Yy]$ ]]; then
info_msg "正在释放暂存区代码 (git stash pop)..."
# 捕获恢复操作的结果
local pop_output
pop_output=$(git stash pop 2>&1)
local pop_status=$?
# 直接打印完整日志以便排错
echo -e "${CYAN}$pop_output${NC}"
if [ $pop_status -eq 0 ]; then
success_msg "恢复成功!您暂存的代码已安全回到工作区。"
elif echo "$pop_output" | grep -q "Aborting"; then
error_msg "恢复被 Git 中止!原因:工作区存在冲突的未保存文件。请先提交或丢弃当前更改。"
else
error_msg "恢复时产生合并冲突!请打开编辑器解决文件内的冲突标记 (<<<<<<<) 后再提交。"
fi
else
info_msg "已取消操作。您的代码依然安全地保留在 stash 中。"
fi
}
# ================= 终端前端 GUI / 菜单仪表盘 =================
show_dashboard() {
clear 2>/dev/null || printf '\033[2J\033[H'
echo -e "${BOLD}${BLUE}══════════════════════════════════════════════${NC}"
echo -e "${BOLD}${CYAN} 🛠️ Git Master 控制台 (原子重构版) ${NC}"
echo -e "${BOLD}${BLUE}══════════════════════════════════════════════${NC}"
echo -e " 📍 ${BOLD}物理坐标:${NC} ${YELLOW}$(pwd)${NC}"
if check_git_repo; then
local b_name
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 " 🌿 ${BOLD}当前分支:${NC} ${GREEN}${b_name:-"游离状态/未命名"}${NC}"
echo -e " 🔗 ${BOLD}远程目标:${NC} ${CYAN}${remote}${NC}"
if [ "$changes" -gt 0 ]; then
echo -e " 📝 ${BOLD}变动预警:${NC} ${RED}工作区有 $changes 个变更未处理${NC}"
else
echo -e " 📝 ${BOLD}变动预警:${NC} ${GREEN}工作区完全纯净${NC}"
fi
else
echo -e " ⚠️ ${BOLD}存储核心:${NC} ${RED}未检测到 Git 数据库${NC}"
fi
echo -e "${BOLD}${BLUE}──────────────────────────────────────────────${NC}"
# 菜单打散为独立功能
echo -e " ${YELLOW}[1] 📝 暂存变动 (Git Add)${NC}"
echo -e " ${GREEN}[2] 📦 创建快照 (Git Commit)${NC}"
echo -e " ${CYAN}[3] 🚀 推送云端 (Git Push)${NC}"
echo -e " ${PURPLE}[4] 📥 拉取更新 (Git Pull)${NC}"
echo -e " ${BLUE}[5] 🌿 分支管理 (Branch)${NC}"
echo -e " ${YELLOW}[6] 📊 状态明细 (Status & Diff)${NC}"
echo -e " ${CYAN}[7] 📜 历史查询 (Log Graph)${NC}"
echo -e " ${PURPLE}[8] 🔗 绑定源址 (Bind Remote)${NC}"
echo -e " ${GREEN}[9] 📦 建立仓库 (Init Repo)${NC}"
echo -e " ${YELLOW}[10] 📁 切换目录 (Change Dir)${NC}"
echo -e " ${RED}[11] 🧹 深度清理 (Git GC)${NC}"
echo -e " ${PURPLE}[12] 📦 恢复暂存 (Stash Pop)${NC}"
echo -e " ${BOLD}[0] ❌ 退出终端 (Exit)${NC}"
echo -e "${BOLD}${BLUE}══════════════════════════════════════════════${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_repo ;;
cd) change_dir ;;
clean) deep_clean ;;
stash) restore_stash ;;
help|-h|--help)
echo -e "${CYAN}Git Master CLI 独立模式使用指南:${NC}"
echo -e " add : 暂存当前所有改动"
echo -e " commit : 为暂存的内容创建快照"
echo -e " push : 将本地提交推送到远程仓库"
echo -e " pull : 拉取并合并最新代码"
echo -e " branch : 分支操作"
echo -e " status : 查看仓库状态"
echo -e " ...其他命令见菜单"
;;
*) error_msg "未识别的参数: $1" ;;
esac
exit 0
fi
# ================= 交互式生命周期循环 =================
while true; do
show_dashboard
read -p "👉 键入数字并回车: " choice
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 ;;
9) init_repo ;;
10) change_dir ;;
11) deep_clean ;;
12) restore_stash ;;
0) echo "控制台已下线。"; exit 0 ;;
*) error_msg "非法的选项指令,请确认您输入的数字有效" ;;
esac
echo ""
read -p "按 [Enter] 键继续..."
done