#!/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/.git # ssh: git@huajishe.fun:e2hang/.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