前言
为了方便在线上直接覆盖更新,而不用单个文件覆盖,从而减少更新错误率,提升更新效率。
每次发版更新前,都要整理最近修改的文件,并按照文件原有目录结构放置。
当提交次数多了后,操作起来就非常的麻烦;为此,就想使用脚本一次性处理该需求。
从而诞生了这个脚本。
功能说明
导出 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"