247 lines
7.2 KiB
Bash
247 lines
7.2 KiB
Bash
#!/bin/bash
|
||
|
||
# ============================================
|
||
# Git 批量克隆/更新脚本 - Bash 版本
|
||
# 适用于 Linux / macOS / Git Bash
|
||
# ============================================
|
||
|
||
# -------------------- 配置区 --------------------
|
||
# 父目录路径(存放所有仓库的目录)
|
||
PARENT_DIR=".."
|
||
|
||
# Git 远程仓库用户名
|
||
GIT_USERNAME="e2hang"
|
||
|
||
# Git 远程仓库基础 URL
|
||
GIT_BASE_URL="https://huajishe.fun/git"
|
||
|
||
# 连接方式:https 或 ssh
|
||
# https: https://huajishe.fun/git/e2hang/<repo>.git
|
||
# ssh: git@huajishe.fun:e2hang/<repo>.git
|
||
CONNECTION_TYPE="https" # 可选: "https" 或 "ssh"
|
||
|
||
# SSH 主机(仅当 CONNECTION_TYPE="ssh" 时使用)
|
||
SSH_HOST="huajishe.fun"
|
||
|
||
# -------------------- 仓库列表模式选择 --------------------
|
||
# 模式 1: "folders" - 基于父目录下已有的文件夹
|
||
# 模式 2: "array" - 基于下面定义的 REPO_LIST 数组
|
||
LIST_MODE="folders" # 可选: "folders" 或 "array"
|
||
|
||
# 仓库列表(仅当 LIST_MODE="array" 时使用)
|
||
# 在这里定义你想要同步的仓库名
|
||
REPO_LIST=(
|
||
"project-one"
|
||
"project-two"
|
||
"my awesome project"
|
||
"another-repo"
|
||
)
|
||
|
||
# -------------------- 脚本主体 --------------------
|
||
|
||
# 颜色定义
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
RED='\033[0;31m'
|
||
BLUE='\033[0;34m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# 检查父目录是否存在,不存在则创建
|
||
if [ ! -d "$PARENT_DIR" ]; then
|
||
echo -e "${YELLOW}警告: 父目录 '$PARENT_DIR' 不存在,正在创建...${NC}"
|
||
mkdir -p "$PARENT_DIR" || {
|
||
echo -e "${RED}错误: 无法创建父目录${NC}"
|
||
exit 1
|
||
}
|
||
fi
|
||
|
||
# 转换为绝对路径
|
||
PARENT_DIR=$(cd "$PARENT_DIR" && pwd)
|
||
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e "${BLUE}Git 批量同步工具${NC}"
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e "目标目录: ${CYAN}$PARENT_DIR${NC}"
|
||
echo -e "连接方式: ${CYAN}$CONNECTION_TYPE${NC}"
|
||
echo -e "列表模式: ${CYAN}$LIST_MODE${NC}"
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo ""
|
||
|
||
# 统计变量
|
||
total=0
|
||
cloned=0
|
||
updated=0
|
||
skipped=0
|
||
failed=0
|
||
uptodate=0
|
||
|
||
# 构建远程仓库 URL
|
||
# 参数: $1 = 仓库名
|
||
build_remote_url() {
|
||
local repo_name="$1"
|
||
if [ "$CONNECTION_TYPE" = "ssh" ]; then
|
||
echo "git@${SSH_HOST}:${GIT_USERNAME}/${repo_name}.git"
|
||
else
|
||
echo "${GIT_BASE_URL}/${GIT_USERNAME}/${repo_name}.git"
|
||
fi
|
||
}
|
||
|
||
# 处理单个仓库
|
||
# 参数: $1 = 仓库名
|
||
process_repo() {
|
||
local repo_name="$1"
|
||
local repo_path="$PARENT_DIR/$repo_name"
|
||
local remote_url=$(build_remote_url "$repo_name")
|
||
|
||
((total++))
|
||
echo -e "${BLUE}[$total] 处理仓库: ${CYAN}$repo_name${NC}"
|
||
|
||
# 情况 1: 目录不存在 - 执行 clone
|
||
if [ ! -d "$repo_path" ]; then
|
||
echo -e "${YELLOW} → 本地目录不存在,开始克隆...${NC}"
|
||
|
||
# 进入父目录
|
||
cd "$PARENT_DIR" || {
|
||
echo -e "${RED} ✗ [ERROR] 无法进入父目录${NC}"
|
||
((failed++))
|
||
echo ""
|
||
return
|
||
}
|
||
|
||
# 执行 clone
|
||
if git clone "$remote_url" "$repo_name" 2>&1 | grep -q "Cloning into\|done"; then
|
||
echo -e "${GREEN} ✓ [CLONED] 克隆成功${NC}"
|
||
((cloned++))
|
||
else
|
||
echo -e "${RED} ✗ [ERROR] 克隆失败 - 请检查仓库是否存在或网络连接${NC}"
|
||
((failed++))
|
||
fi
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
# 情况 2: 目录存在但不是 Git 仓库 - 跳过并警告
|
||
if [ ! -d "$repo_path/.git" ]; then
|
||
echo -e "${YELLOW} ⚠ [SKIPPED] 目录存在但不是 Git 仓库${NC}"
|
||
((skipped++))
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
# 情况 3: 是 Git 仓库 - 执行 pull
|
||
echo -e "${YELLOW} → Git 仓库已存在,开始更新...${NC}"
|
||
|
||
# 进入仓库目录
|
||
cd "$repo_path" || {
|
||
echo -e "${RED} ✗ [ERROR] 无法进入仓库目录${NC}"
|
||
((failed++))
|
||
echo ""
|
||
return
|
||
}
|
||
|
||
# 获取当前分支
|
||
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||
|
||
if [ -z "$current_branch" ]; then
|
||
echo -e "${RED} ✗ [ERROR] 无法确定当前分支${NC}"
|
||
((failed++))
|
||
echo ""
|
||
return
|
||
fi
|
||
|
||
# 保存当前提交哈希(用于判断是否有更新)
|
||
before_hash=$(git rev-parse HEAD 2>/dev/null)
|
||
|
||
# 执行 pull
|
||
pull_output=$(git pull origin "$current_branch" 2>&1)
|
||
pull_exit_code=$?
|
||
|
||
if [ $pull_exit_code -eq 0 ]; then
|
||
# 获取更新后的提交哈希
|
||
after_hash=$(git rev-parse HEAD 2>/dev/null)
|
||
|
||
if echo "$pull_output" | grep -q "Already up to date\|Already up-to-date"; then
|
||
echo -e "${CYAN} ○ [UP-TO-DATE] 已是最新版本${NC}"
|
||
((uptodate++))
|
||
elif [ "$before_hash" != "$after_hash" ]; then
|
||
echo -e "${GREEN} ✓ [UPDATED] 更新成功${NC}"
|
||
((updated++))
|
||
else
|
||
echo -e "${CYAN} ○ [UP-TO-DATE] 已是最新版本${NC}"
|
||
((uptodate++))
|
||
fi
|
||
else
|
||
echo -e "${RED} ✗ [ERROR] 更新失败${NC}"
|
||
echo -e "${RED} 错误信息: $(echo "$pull_output" | head -n 2)${NC}"
|
||
((failed++))
|
||
fi
|
||
|
||
echo ""
|
||
}
|
||
|
||
# -------------------- 主循环 --------------------
|
||
|
||
# 根据模式获取仓库列表
|
||
if [ "$LIST_MODE" = "array" ]; then
|
||
echo -e "${CYAN}使用预定义仓库列表 (共 ${#REPO_LIST[@]} 个)${NC}"
|
||
echo ""
|
||
|
||
# 遍历预定义的仓库列表
|
||
for repo in "${REPO_LIST[@]}"; do
|
||
process_repo "$repo"
|
||
done
|
||
|
||
elif [ "$LIST_MODE" = "folders" ]; then
|
||
echo -e "${CYAN}基于现有文件夹列表同步${NC}"
|
||
echo ""
|
||
|
||
# 检查父目录下是否有子文件夹
|
||
if [ -z "$(ls -A "$PARENT_DIR" 2>/dev/null)" ]; then
|
||
echo -e "${YELLOW}警告: 父目录为空,没有需要处理的仓库${NC}"
|
||
echo -e "${YELLOW}提示: 可以将 LIST_MODE 改为 'array' 并定义 REPO_LIST${NC}"
|
||
exit 0
|
||
fi
|
||
|
||
# 遍历父目录下的所有一级子目录
|
||
for repo_path in "$PARENT_DIR"/*/; do
|
||
# 去除末尾的斜杠
|
||
repo_path="${repo_path%/}"
|
||
|
||
# 跳过不是目录的项
|
||
if [ ! -d "$repo_path" ]; then
|
||
continue
|
||
fi
|
||
|
||
# 获取文件夹名
|
||
repo_name=$(basename "$repo_path")
|
||
|
||
# 处理仓库(这里是 pull 操作)
|
||
process_repo "$repo_name"
|
||
done
|
||
else
|
||
echo -e "${RED}错误: 未知的 LIST_MODE '$LIST_MODE'${NC}"
|
||
echo -e "${YELLOW}请设置 LIST_MODE 为 'folders' 或 'array'${NC}"
|
||
exit 1
|
||
fi
|
||
|
||
# -------------------- 输出统计信息 --------------------
|
||
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e "${BLUE}同步完成!${NC}"
|
||
echo -e "${BLUE}========================================${NC}"
|
||
echo -e "总计处理: ${CYAN}$total${NC} 个仓库"
|
||
echo ""
|
||
echo -e "${GREEN}✓ 克隆成功: $cloned${NC}"
|
||
echo -e "${GREEN}✓ 更新成功: $updated${NC}"
|
||
echo -e "${CYAN}○ 已是最新: $uptodate${NC}"
|
||
echo -e "${YELLOW}⚠ 跳过: $skipped${NC}"
|
||
echo -e "${RED}✗ 失败: $failed${NC}"
|
||
echo ""
|
||
|
||
# 如果有失败的,返回非零退出码
|
||
if [ $failed -gt 0 ]; then
|
||
exit 1
|
||
fi
|
||
|
||
exit 0 |