导出 git 提交记录的文件

0 阅读3分钟

前言

为了方便在线上直接覆盖更新,而不用单个文件覆盖,从而减少更新错误率,提升更新效率。

每次发版更新前,都要整理最近修改的文件,并按照文件原有目录结构放置。

当提交次数多了后,操作起来就非常的麻烦;为此,就想使用脚本一次性处理该需求。

从而诞生了这个脚本。

功能说明

导出 git 提交记录文件,并按照文件原有目录结构放置

如何使用

  • 修改项目目录公共前缀 PROJECT_PATH_PREFIX

  • 修改导出目录公共前缀 EXPORT_BASE

  • 修改项目配置信息,可多项目共存

    具体信息请查看脚本文件中的注释说明

    project "
        name='项目A'
        path='projectA'
        commit_ids='fa5b485e0508ee0c054496d4c5822777ead86503 9232dcbaec77abbec5a0806692a50e36a162b57e'
    #    start_time='2023-10-01 09:00'
    #    end_time='2023-10-15 18:00'
    "
    
    project "
        name='项目B'
        path='projectB'
        commit_ids=''
        start_time='2026-02-01 09:00'
        end_time=''
    "
    
    # 示例:绝对路径且只用 start_time
    # project "
    #     name='外部项目'
    #     path='/e/work/other-repo'
    #     start_time='yesterday'
    # "
    
  • 保存,添加文件执行权限

  • 执行文件

脚本内容

可直接使用

#!/usr/bin/env bash
set -e

##################################################
# 一、全局配置
##################################################
# 默认仓库根目录(Windows 环境示例)
PROJECT_PATH_PREFIX="/d/www"
# 导出目标根目录
EXPORT_BASE="/d/www/export-git-commit"
# 本次导出的时间戳文件夹
EXPORT_TIME=$(date +"%Y%m%d_%H%M%S")

##################################################
# 二、核心逻辑引擎
##################################################

project() {
    # 在子 Shell 中运行,确保变量隔离
    (
        # 初始化局部变量,防止读取到上一个项目的配置
        local name=""
        local path=""
        local commit_ids=""
        local start_time=""
        local end_time=""

        # 【核心修正】解析传入的配置字符串
        eval "$1"

        # 1. 基础校验
        if [[ -z "$name" || -z "$path" ]]; then
            echo -e "\033[31m❌ 配置错误: name 或 path 不能为空\033[0m"
            return
        fi

        # 2. 路径拼接逻辑
        local REPO_PATH
        if [[ "$path" == /* ]]; then
            REPO_PATH="$path"
        else
            REPO_PATH="${PROJECT_PATH_PREFIX%/}/$path"
        fi

        echo -e "\n\033[32m========================================\033[0m"
        echo "📦 项目: $name"
        echo "📁 仓库: $REPO_PATH"

        if [ ! -d "$REPO_PATH/.git" ]; then
            echo "❌ 仓库路径不存在,已跳过"
            return
        fi
        cd "$REPO_PATH"

        # 3. 确定 Commit 范围 (优先级: 时间 > Commit IDs)
        local FINAL_COMMITS=""
        if [[ -n "$start_time" ]]; then
            echo "🕒 模式: 时间区间 [$start_time] -> [${end_time:-至今}]"
            local LOG_CMD="git log --reverse --format=%H --since=\"$start_time\""
            [[ -n "$end_time" ]] && LOG_CMD="$LOG_CMD --until=\"$end_time\""
            FINAL_COMMITS=$(eval "$LOG_CMD")
        elif [[ -n "$commit_ids" ]]; then
            echo "🔖 模式: 指定 Commit IDs"
            # 自动按提交时间排序
            FINAL_COMMITS=$(for C in $commit_ids; do
                local TS=$(git show -s --format=%ct "$C" 2>/dev/null || true)
                [[ -n "$TS" ]] && echo "$TS $C"
            done | sort -n | awk '{print $2}')
        fi

        if [[ -z "$FINAL_COMMITS" ]]; then
            echo "⚠️ 未找到符合条件的提交,跳过"
            return
        fi

        # 4. 追踪文件最终状态 (使用关联数组,Bash 4.0+)
        declare -A FILE_STATE
        for COMMIT in $FINAL_COMMITS; do
            while read -r STATUS FILE; do
                if [[ "$STATUS" == "D" ]]; then
                    unset FILE_STATE["$FILE"]
                else
                    FILE_STATE["$FILE"]="$COMMIT"
                fi
            done < <(git diff-tree --no-commit-id --name-status -r "$COMMIT")
        done

        # 5. 执行导出
        local FILE_COUNT=${#FILE_STATE[@]}
        if [[ $FILE_COUNT -eq 0 ]]; then
            echo "📭 无文件需要导出"
            return
        fi

        local OUT_ROOT="$EXPORT_BASE/$name/$EXPORT_TIME"
        mkdir -p "$OUT_ROOT"

        # 记录导出清单
        local SUMMARY="$OUT_ROOT/export-summary.txt"
        {
            echo "项目: $name"
            echo "导出时间: $EXPORT_TIME"
            echo "提交范围: $(echo $FINAL_COMMITS | tr '\n' ' ')"
            echo "--------------------------------"
        } > "$SUMMARY"

        for F in "${!FILE_STATE[@]}"; do
            local TARGET="$OUT_ROOT/$F"
            mkdir -p "$(dirname "$TARGET")"
            # 从对应的 commit 中提取文件
            git show "${FILE_STATE[$F]}:$F" > "$TARGET"
            echo "✔ $F (来自 ${FILE_STATE[$F]:0:7})" >> "$SUMMARY"
        done

        echo "✅ 成功导出 $FILE_COUNT 个文件"
        echo "📂 导出至: $OUT_ROOT"
    )
}

##################################################
# 三-1、项目配置区(Key-Value 模式)
##################################################
# 注意:配置必须包裹在双引号 ""

project "
    name='项目A'
    path='projectA'
    commit_ids='fa5b485e0508ee0c054496d4c5822777ead86503 9232dcbaec77abbec5a0806692a50e36a162b57e'
#    start_time='2023-10-01 09:00'
#    end_time='2023-10-15 18:00'
"

project "
    name='项目B'
    path='projectB'
    commit_ids=''
    start_time='2026-02-01 09:00'
    end_time=''
"

# 示例:绝对路径且只用 start_time
# project "
#     name='外部项目'
#     path='/e/work/other-repo'
#     start_time='yesterday'
# "

echo -e "\n\033[36m✨ 所有导出任务处理完毕!\033[0m"