我完全是在手机上面弄的。所有各种东西都很难弄。所以就搞了一些脚本(实际上是大肥鱼做)
mdtohtml
#!/data/data/com.termux/files/usr/bin/bash
# ========== 按你的实际路径修改这三行 ==========
MD_DIR="/storage/emulated/0/我的文件/开发/博客md"
TPL="/storage/emulated/0/我的文件/开发/脚本/mdtohtml_/post.html"
OUT_DIR="/storage/emulated/0/我的文件/开发/博客/posts"
# ==============================================
# ---------- 可选参数 ----------
# --no-index 只转换,不刷新首页列表
# --force 即使目标 html 比 md 新(疑似手改过)也覆盖
no_index=0
force=0
_args=()
for _a in "$@"; do
case "$_a" in
--no-index) no_index=1 ;;
--force) force=1 ;;
*) _args+=("$_a") ;;
esac
done
set -- "${_args[@]}"
if [ -z "$1" ]; then
echo "用法: mdtohtml [--no-index] [--force] <文章名> [输出目录](不带 .md 后缀)"
echo "示例: mdtohtml 未命名"
echo " mdtohtml --no-index 未命名 # 只转换,不刷新首页列表"
echo " mdtohtml --force 未命名 # 覆盖比 md 新的 html(谨慎)"
echo " mdtohtml 未命名 /storage/emulated/0/我的文件/开发/博客/posts/draft"
exit 1
fi
# ---------- 清洗参数:去掉可能带上的各种后缀 ----------
name="$1"
name="${name%.md}"
name="${name%.html}"
name="${name%.md}" # 防 .md.html 这种夹心
if [ -z "$name" ]; then
echo "❌ 文章名为空(清洗后),请检查参数: $1"
exit 1
fi
# ---------- 输出目录:第二个参数,留空则用默认 ----------
if [ -n "$2" ]; then
out_dir="$2"
else
out_dir="$OUT_DIR"
fi
src="$MD_DIR/$name.md"
dst="$out_dir/$name.html"
# ---------- 检查源文件和模板 ----------
if [ ! -f "$src" ]; then
echo "❌ 找不到源文件: $src"
exit 1
fi
if [ ! -f "$TPL" ]; then
echo "❌ 找不到模板: $TPL"
exit 1
fi
mkdir -p "$out_dir"
# ---------- 转换 ----------
# 守卫:目标 html 比源 md 新,说明生成后又被手改过,默认不覆盖
if [ "$force" -eq 0 ] && [ -f "$dst" ] && [ "$dst" -nt "$src" ]; then
echo "⚠️ $dst"
echo " 比源文件更新(疑似手改过),已跳过转换,保留你的修改。"
echo " 确实要从 md 重新生成,请加 --force:mdtohtml --force $name"
else
if ! pandoc --standalone \
--template="$TPL" \
--output="$dst" \
"$src"; then
echo "❌ pandoc 转换失败,未生成: $dst"
exit 1
fi
echo "✅ 已生成: $dst"
fi
# ---------- 自动刷新首页文章列表 ----------
# 脚本放在 /sdcard 上,FUSE 会忽略可执行位,所以统一用 bash 调用。
GEN_INDEX="/storage/emulated/0/我的文件/开发/脚本/gen-index"
if [ "$no_index" -eq 1 ]; then
echo "ℹ️ 已跳过首页列表刷新(--no-index)"
elif [ -f "$GEN_INDEX" ]; then
echo
bash "$GEN_INDEX" || echo "⚠️ 首页列表刷新失败,可手动重跑: bash $GEN_INDEX"
else
echo "⚠️ 找不到 $GEN_INDEX,跳过首页列表刷新"
fi
gen-index
#!/data/data/com.termux/files/usr/bin/bash
# ==========================================================================
# gen-index —— 根据 posts/*.html 自动重建首页文章列表
#
# 用法:
# gen-index # 用默认站点目录
# gen-index <站点目录> # 指定站点目录
#
# 做的事:
# 1. 扫描 <站点>/posts/*.html
# 2. 从每篇提取:
# <h1 class="article-title">标题</h1>
# <time datetime="YYYY-MM-DD">
# <meta name="description" content="摘要">
# 3. 按日期倒序,重写 index.html 中
# <!-- POSTS:START --> ... <!-- POSTS:END -->
# 之间的 <ul class="post-list"> 区块
#
# 设计要点:
# - 全量重建、幂等,重复运行结果一致
# - 缺元数据的文章自动回退(标题=文件名,日期=文件 mtime),并给出警告
# - 改 index.html 前先备份为 index.html.bak,再原子替换
# ==========================================================================
set -u
DEFAULT_SITE="/storage/emulated/0/我的文件/开发/博客"
SITE_DIR="${1:-$DEFAULT_SITE}"
POSTS_DIR="$SITE_DIR/posts"
INDEX="$SITE_DIR/index.html"
START_MARK="POSTS:START"
END_MARK="POSTS:END"
info() { printf '%s\n' "$*"; }
warn() { printf '⚠️ %s\n' "$*" >&2; }
err() { printf '❌ %s\n' "$*" >&2; exit 1; }
# ---------- 前置检查 ----------
[ -d "$POSTS_DIR" ] || err "找不到文章目录: $POSTS_DIR"
[ -f "$INDEX" ] || err "找不到首页: $INDEX"
grep -q -- "$START_MARK" "$INDEX" || err "index.html 缺少 $START_MARK 标记,请先手工加上"
grep -q -- "$END_MARK" "$INDEX" || err "index.html 缺少 $END_MARK 标记,请先手工加上"
# ---------- 工具函数 ----------
# 仅用于 href 属性里的文件名:只需处理 & 和 "
attr_escape() {
printf '%s' "$1" | sed 's/&/\&/g; s/"/\"/g'
}
trim() {
printf '%s' "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}
# 把整篇 HTML 压成一行,跨行属性、缩进都不再影响提取
flatten() {
tr '\n\r\t' ' ' < "$1" | tr -s ' '
}
# 剥掉标签,只留文本(& 这类实体原样保留,正好可以再放进 HTML)
strip_tags() {
printf '%s' "$1" | sed 's|<[^>]*>||g'
}
# 标题:优先 <h1 class="article-title">(允许内嵌 <code>/<em> 等标签),
# 没有就用 <title> 去掉站点后缀
extract_title() {
local flat t
flat="$(flatten "$1")"
t="$(printf '%s' "$flat" | sed -n 's|.*<h1[^>]*article-title[^>]*>\(.*\)</h1>.*|\1|p' | head -n1)"
if [ -z "$t" ]; then
t="$(printf '%s' "$flat" | sed -n 's|.*<title>\(.*\)</title>.*|\1|p' | head -n1)"
t="${t%% · *}"
fi
trim "$(strip_tags "$t")"
}
# 日期:优先 <time datetime="...">,其次 <time> 文本;只返回 YYYY-MM-DD
extract_date() {
local flat d
flat="$(flatten "$1")"
d="$(printf '%s' "$flat" | sed -n 's|.*<time[^>]*datetime="\([^"]*\)".*|\1|p' | head -n1)"
if [ -z "$d" ]; then
d="$(printf '%s' "$flat" | sed -n 's|.*<time[^>]*>\([^<]*\)</time>.*|\1|p' | head -n1)"
d="$(strip_tags "$d" | tr -d '"')"
fi
d="$(trim "$d")"
if printf '%s' "$d" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
printf '%s' "$d"
else
printf ''
fi
}
# 摘要:<meta name="description" content="...">,兼容属性换序、跨行书写
extract_desc() {
local flat d
flat="$(flatten "$1")"
if printf '%s' "$flat" | grep -q 'name="description"[^>]*content="'; then
d="$(printf '%s' "$flat" | sed -e 's|.*name="description"[^>]*content="||' -e 's|".*||')"
elif printf '%s' "$flat" | grep -q 'content="[^"]*"[^>]*name="description"'; then
d="$(printf '%s' "$flat" | sed -e 's|.*<meta[^>]*content="||' -e 's|".*||')"
else
d=""
fi
trim "$d"
}
# 文件修改日期,作为日期的兜底
mtime_date() {
local f="$1" d
d="$(stat -c %y "$f" 2>/dev/null | cut -d' ' -f1)"
if ! printf '%s' "$d" | grep -Eq '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
d="$(date -r "$f" +%Y-%m-%d 2>/dev/null)"
fi
printf '%s' "$d"
}
# ---------- 收集元数据 ----------
rows=""
count=0
warned=0
for f in "$POSTS_DIR"/*.html; do
[ -e "$f" ] || continue
base="$(basename "$f" .html)"
title="$(extract_title "$f")"
date="$(extract_date "$f")"
desc="$(extract_desc "$f")"
if [ -z "$title" ]; then
title="$base"
warn "「$base」缺少 article-title,标题回退为文件名"
warned=$((warned + 1))
fi
if [ -z "$date" ]; then
date="$(mtime_date "$f")"
warn "「$base」缺少有效日期,改用文件修改时间 $date"
warned=$((warned + 1))
fi
rows="${rows}${date}"$'\t'"${base}"$'\t'"${title}"$'\t'"${desc}"$'\n'
count=$((count + 1))
done
if [ "$count" -eq 0 ]; then
warn "posts/ 下没有 .html 文件,将生成空列表"
fi
# ---------- 排序:日期倒序,同日期按文件名倒序 ----------
sorted="$(printf '%s' "$rows" | grep -v '^[[:space:]]*$' | LC_ALL=C sort -t $'\t' -k1,1r -k2,2r)"
# ---------- 生成 <ul> 区块 ----------
block="$(mktemp "${INDEX}.block.XXXXXX")" || err "无法创建临时文件"
new_index="$(mktemp "${INDEX}.new.XXXXXX")" || { rm -f "$block"; err "无法创建临时文件"; }
trap 'rm -f "$block" "$new_index"' EXIT
{
printf '\t\t<ul class="post-list">\n'
while IFS=$'\t' read -r d b t ds; do
[ -n "$d" ] || continue
printf '\t\t <li>\n'
printf '\t\t\t<a class="post-card" href="posts/%s.html">\n' "$(attr_escape "$b")"
printf '\t\t\t <h2>%s</h2>\n' "$t"
if [ -n "$ds" ]; then
printf '\t\t\t <p>%s</p>\n' "$ds"
fi
printf '\t\t\t <time datetime="%s">%s</time>\n' "$d" "$d"
printf '\t\t\t</a>\n'
printf '\t\t </li>\n'
done <<< "$sorted"
printf '\t\t</ul>\n'
} > "$block"
# ---------- 注入 index.html ----------
awk -v blockfile="$block" '
index($0, "POSTS:START") {
print
while ((getline line < blockfile) > 0) print line
close(blockfile)
skip = 1
next
}
index($0, "POSTS:END") { skip = 0 }
skip { next }
{ print }
' "$INDEX" > "$new_index"
# ---------- 校验后再替换 ----------
[ -s "$new_index" ] || err "生成的 index.html 为空,已放弃写入"
grep -q -- "$END_MARK" "$new_index" || err "生成结果缺少 $END_MARK 标记,已放弃写入"
if [ "$count" -gt 0 ] && ! grep -q 'post-card' "$new_index"; then
err "生成结果里没有文章卡片,已放弃写入"
fi
cp -p "$INDEX" "$INDEX.bak" || err "备份 index.html 失败"
mv -f "$new_index" "$INDEX" || err "替换 index.html 失败"
# ---------- 报告 ----------
info "✅ 已更新首页列表:$count 篇文章"
while IFS=$'\t' read -r d b t ds; do
[ -n "$d" ] || continue
info " $d $t"
done <<< "$sorted"
if [ "$warned" -gt 0 ]; then
info "(有 $warned 项元数据缺失,见上方警告)"
fi
info " 首页: $INDEX"
info " 备份: $INDEX.bak"
exit 0
push-blog.sh
#!/data/data/com.termux/files/usr/bin/bash
# ============================================================
# push-blog.sh v1.0
# 把博客源目录镜像到 git 仓库,自动生成提交信息并推送。
# 适用场景:Cloudflare Pages 直连 GitHub 仓库,push 即部署。
#
# 退出码:0 成功 / 1 环境问题 / 2 远端或推送失败 / 3 无变更
# ============================================================
set -euo pipefail
# 凭据失效时立刻报错,而不是卡在交互式的用户名/密码提示上
export GIT_TERMINAL_PROMPT=0
export GIT_ASKPASS=/bin/true
PROG="push-blog"
VERSION="1.0"
# ---------- 1. 默认值(配置文件可覆盖,命令行再覆盖) ----------
BLOG_SRC="/sdcard/我的文件/开发/博客"
BLOG_REPO="$HOME/My-blog"
BLOG_BRANCH="main"
BLOG_REMOTE="origin"
TITLE_SUFFIX=" · 我道呀博客"
LOG_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/push-blog.log"
CONFIG_FILE="${PUSH_BLOG_CONFIG:-${XDG_CONFIG_HOME:-$HOME/.config}/push-blog/config}"
DRY_RUN=0
NO_PUSH=0
VERBOSE=0
QUIET=0
CUSTOM_MSG=""
EXTRA_EXCLUDES=()
# 仓库独有文件:源目录里没有,但绝不能因为 --delete 被删掉
PROTECTED_EXCLUDES=(
".git"
"_headers" "_redirects" ".github"
"CNAME" ".nojekyll" "wrangler.toml"
".gitignore" ".gitattributes" "LICENSE"
)
# 临时垃圾
JUNK_EXCLUDES=(
".DS_Store" "Thumbs.db" ".directory" ".obsidian" ".trash"
"*.swp" "*.swo" "*~" "*.tmp" "*.bak"
)
# ---------- 2. 配置与参数 ----------
if [ -f "$CONFIG_FILE" ]; then
. "$CONFIG_FILE"
fi
usage() {
cat <<'EOF'
用法: push-blog [选项]
裸跑即:把 BLOG_SRC 同步到 BLOG_REPO,生成提交信息并推送到远端分支。
选项:
-s, --src <dir> 源目录 (默认取配置 BLOG_SRC)
-r, --repo <dir> 仓库目录 (默认取配置 BLOG_REPO)
-b, --branch <name> 推送分支 (默认 main)
-m, --message <text> 手写提交信息,跳过自动生成
-e, --exclude <pat> 追加 rsync 排除规则,可重复
-n, --dry-run 预演:只看会改/删什么,不提交不推送
--no-push 只提交到本地,不推送
-v, --verbose 详细输出
-q, --quiet 安静模式
-h, --help 显示帮助
-V, --version 显示版本
退出码: 0 成功 / 1 环境问题 / 2 推送失败 / 3 无变更
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
-s|--src) BLOG_SRC="${2:?--src 需要目录参数}"; shift 2 ;;
-r|--repo) BLOG_REPO="${2:?--repo 需要目录参数}"; shift 2 ;;
-b|--branch) BLOG_BRANCH="${2:?--branch 需要分支名}"; shift 2 ;;
-m|--message) CUSTOM_MSG="${2:?--message 需要文本}"; shift 2 ;;
-e|--exclude) EXTRA_EXCLUDES+=("${2:?--exclude 需要模式}"); shift 2 ;;
-n|--dry-run) DRY_RUN=1; shift ;;
--no-push) NO_PUSH=1; shift ;;
-v|--verbose) VERBOSE=1; shift ;;
-q|--quiet) QUIET=1; shift ;;
-h|--help) usage; exit 0 ;;
-V|--version) printf '%s %s\n' "$PROG" "$VERSION"; exit 0 ;;
*) printf '未知参数: %s\n\n' "$1" >&2; usage >&2; exit 1 ;;
esac
done
BLOG_SRC="${BLOG_SRC%/}"
BLOG_REPO="${BLOG_REPO%/}"
# ---------- 3. 输出与日志 ----------
ts() { date '+%Y-%m-%d %H:%M:%S'; }
log() { if [ "$QUIET" -eq 1 ]; then return 0; fi; printf '%s\n' "$*"; }
vlog() { if [ "$VERBOSE" -eq 0 ]; then return 0; fi; printf '[%s] %s\n' "$(ts)" "$*"; }
err() { printf '%s\n' "$*" >&2; }
write_log() {
[ -n "${LOG_FILE:-}" ] || return 0
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
printf '%s %s\n' "$(ts)" "$*" >>"$LOG_FILE" 2>/dev/null || true
}
notify() {
local title="$1" content="$2"
if command -v termux-notification >/dev/null 2>&1; then
termux-notification --title "$title" --content "$content" >/dev/null 2>&1 || true
fi
}
die() {
local code="$1"; shift
err "✗ $*"
write_log "FAIL($code): $*"
notify "博客推送失败" "$*"
exit "$code"
}
# ---------- 4. 环境检查 ----------
[ -d "$BLOG_SRC" ] || die 1 "源目录不存在:$BLOG_SRC"
[ -n "$(ls -A "$BLOG_SRC" 2>/dev/null || true)" ] || die 1 "源目录为空,中止:$BLOG_SRC"
[ -d "$BLOG_REPO/.git" ] || die 1 "不是 git 仓库:$BLOG_REPO"
command -v rsync >/dev/null 2>&1 || die 1 "未安装 rsync(pkg install rsync)"
command -v git >/dev/null 2>&1 || die 1 "未安装 git"
# 日志瘦身
if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then
if [ "$(wc -l <"$LOG_FILE" 2>/dev/null || echo 0)" -gt 300 ]; then
tail -n 150 "$LOG_FILE" >"$LOG_FILE.tmp" 2>/dev/null && mv "$LOG_FILE.tmp" "$LOG_FILE" || true
fi
fi
write_log "START src=$BLOG_SRC repo=$BLOG_REPO branch=$BLOG_BRANCH dry_run=$DRY_RUN"
# ---------- 5. 远端预检(在覆写本地之前) ----------
log "→ 检查远端 $BLOG_REMOTE/$BLOG_BRANCH"
cd "$BLOG_REPO"
if git ls-remote --exit-code --heads "$BLOG_REMOTE" "$BLOG_BRANCH" >/dev/null 2>&1; then
git fetch --quiet "$BLOG_REMOTE" "$BLOG_BRANCH" 2>/dev/null \
|| die 2 "git fetch 失败,检查网络或凭据"
remote_ref="$BLOG_REMOTE/$BLOG_BRANCH"
if git rev-parse --verify --quiet HEAD >/dev/null 2>&1; then
if ! git merge-base --is-ancestor "$remote_ref" HEAD 2>/dev/null; then
die 2 "远端有本地没有的提交(可能你在 GitHub 网页上改过)。请先手动 git pull --rebase 后再运行。"
fi
fi
vlog "远端无领先提交"
else
if git ls-remote "$BLOG_REMOTE" >/dev/null 2>&1; then
vlog "远端分支 $BLOG_BRANCH 尚不存在,将首次推送"
else
die 2 "无法连接远端仓库 $BLOG_REMOTE,检查网络或凭据"
fi
fi
# ---------- 6. 同步 ----------
rsync_args=(-rlt --delete)
for e in "${PROTECTED_EXCLUDES[@]}" "${JUNK_EXCLUDES[@]}"; do
rsync_args+=("--exclude=$e")
done
for e in "${EXTRA_EXCLUDES[@]}"; do
rsync_args+=("--exclude=$e")
done
if [ "$DRY_RUN" -eq 1 ]; then
rsync_args+=(--dry-run --itemize-changes)
log "→ 预演同步(不会改动任何文件)"
rsync "${rsync_args[@]}" "$BLOG_SRC/" "$BLOG_REPO/" || die 1 "rsync 预演失败"
log ""
log "→ 当前仓库未提交的变更:"
if [ -n "$(git status --porcelain)" ]; then
git -c core.quotepath=false status --short
else
log " (无)"
fi
log ""
log "预演结束:未修改仓库、未提交、未推送。"
write_log "DRY-RUN 完成"
exit 0
fi
log "→ 同步 $BLOG_SRC → $BLOG_REPO"
rsync "${rsync_args[@]}" "$BLOG_SRC/" "$BLOG_REPO/" || die 1 "rsync 同步失败"
git add -A
if [ -z "$(git status --porcelain)" ]; then
log "没有变更"
write_log "NO-CHANGE"
notify "博客推送" "没有变更"
exit 3
fi
# ---------- 7. 生成提交信息 ----------
trim() {
local s="${1-}"
s="${s#"${s%%[![:space:]]*}"}"
s="${s%"${s##*[![:space:]]}"}"
printf '%s' "$s"
}
html_unescape() {
local s="${1-}" q="'"
s=${s//&/&}
s=${s//</<}
s=${s//>/>}
s=${s//"/\"}
s=${s//'/$q}
printf '%s' "$s"
}
# 从仓库内的 html 提取标题:<title> 优先,其次第一个 <h1>,最后退回文件名
post_title() {
local path="$1" t="" f="$BLOG_REPO/$1"
if [ -f "$f" ]; then
t=$(sed -n 's|.*<title[^>]*>\([^<]*\)</title>.*|\1|p' "$f" 2>/dev/null | head -n 1) || t=""
if [ -z "$(trim "$t")" ]; then
t=$(sed -n 's|.*<h1[^>]*>\([^<]*\)</h1>.*|\1|p' "$f" 2>/dev/null | head -n 1) || t=""
fi
t=$(html_unescape "$t")
t=$(trim "$t")
if [ -n "$TITLE_SUFFIX" ] && [ "${#t}" -ge "${#TITLE_SUFFIX}" ] && [ "${t%"$TITLE_SUFFIX"}" != "$t" ]; then
t="${t%"$TITLE_SUFFIX"}"
t=$(trim "$t")
fi
fi
[ -n "$t" ] || t="$(basename "$path" .html)"
printf '%s' "$t"
}
# 拼接名称:超过 max 个就折叠成「A、B等N个」
join_names() {
local max="$1" unit="${2:-个}"; shift 2
local out="" n=0 total=$#
for x in "$@"; do
n=$((n + 1))
if [ "$n" -gt "$max" ]; then
out="${out}…等${total}${unit}"
break
fi
if [ -n "$out" ]; then out="${out}、"; fi
out="${out}${x}"
done
printf '%s' "$out"
}
build_message() {
local st path
local -a ps_a=() ps_m=() ps_d=() pg_a=() pg_m=() pg_d=()
local -a st_a=() st_m=() st_d=() as_a=() as_m=() as_d=()
local -a ot_a=() ot_m=() ot_d=()
local -a body=()
while IFS=$'\t' read -r st path; do
[ -n "${path:-}" ] || continue
case "$path" in
posts/*)
case "$st" in
A|C) ps_a+=("$(post_title "$path")") ;;
D) ps_d+=("$(post_title "$path")") ;;
*) ps_m+=("$(post_title "$path")") ;;
esac ;;
assets/*)
case "$st" in
A|C) as_a+=("$(basename "$path")") ;;
D) as_d+=("$(basename "$path")") ;;
*) as_m+=("$(basename "$path")") ;;
esac ;;
css/*|js/*)
case "$st" in
A|C) st_a+=("$path") ;;
D) st_d+=("$path") ;;
*) st_m+=("$path") ;;
esac ;;
*.html)
case "$st" in
A|C) pg_a+=("$path") ;;
D) pg_d+=("$path") ;;
*) pg_m+=("$path") ;;
esac ;;
*)
case "$st" in
A|C) ot_a+=("$path") ;;
D) ot_d+=("$path") ;;
*) ot_m+=("$path") ;;
esac ;;
esac
done < <(git -c core.quotepath=false diff --cached --name-status --no-renames)
local total=$(( ${#ps_a[@]} + ${#ps_m[@]} + ${#ps_d[@]}
+ ${#pg_a[@]} + ${#pg_m[@]} + ${#pg_d[@]}
+ ${#st_a[@]} + ${#st_m[@]} + ${#st_d[@]}
+ ${#as_a[@]} + ${#as_m[@]} + ${#as_d[@]}
+ ${#ot_a[@]} + ${#ot_m[@]} + ${#ot_d[@]} ))
[ "$total" -gt 0 ] || { printf '更新文件\n'; return 0; }
local subject="" counted=0
if [ "${#ps_a[@]}" -gt 0 ]; then
if [ "${#ps_a[@]}" -eq 1 ]; then
subject="新增《${ps_a[0]}》"
else
subject="新增 ${#ps_a[@]} 篇文章:$(join_names 3 篇 "${ps_a[@]}")"; counted=1
fi
elif [ "${#ps_d[@]}" -gt 0 ] && [ "$total" -eq "${#ps_d[@]}" ]; then
if [ "${#ps_d[@]}" -eq 1 ]; then
subject="删除《${ps_d[0]}》"
else
subject="删除 ${#ps_d[@]} 篇文章:$(join_names 3 篇 "${ps_d[@]}")"; counted=1
fi
elif [ "${#ps_m[@]}" -gt 0 ]; then
if [ "${#ps_m[@]}" -eq 1 ]; then
subject="更新《${ps_m[0]}》"
else
subject="更新 ${#ps_m[@]} 篇文章:$(join_names 3 篇 "${ps_m[@]}")"; counted=1
fi
elif [ "${#st_a[@]}" -eq 0 ] && [ "${#st_m[@]}" -eq 0 ] && [ "${#st_d[@]}" -eq 0 ] \
&& [ "${#pg_a[@]}" -eq 0 ] && [ "${#pg_m[@]}" -eq 0 ] && [ "${#pg_d[@]}" -eq 0 ] \
&& [ "${#as_a[@]}" -eq 0 ] && [ "${#as_m[@]}" -eq 0 ] && [ "${#as_d[@]}" -eq 0 ] \
&& [ "${#ot_a[@]}" -eq 0 ] && [ "${#ot_m[@]}" -eq 0 ] && [ "${#ot_d[@]}" -eq 0 ]; then
subject="更新文件"
elif [ "${#pg_a[@]}" -eq 0 ] && [ "${#pg_m[@]}" -eq 0 ] && [ "${#pg_d[@]}" -eq 0 ] \
&& [ "${#as_a[@]}" -eq 0 ] && [ "${#as_m[@]}" -eq 0 ] && [ "${#as_d[@]}" -eq 0 ] \
&& [ "${#ot_a[@]}" -eq 0 ] && [ "${#ot_m[@]}" -eq 0 ] && [ "${#ot_d[@]}" -eq 0 ]; then
subject="调整样式与脚本"
elif [ "${#pg_a[@]}" -eq 0 ] && [ "${#pg_m[@]}" -eq 0 ] && [ "${#pg_d[@]}" -eq 0 ] \
&& [ "${#ot_a[@]}" -eq 0 ] && [ "${#ot_m[@]}" -eq 0 ] && [ "${#ot_d[@]}" -eq 0 ] \
&& [ "${#ps_a[@]}" -eq 0 ] && [ "${#ps_m[@]}" -eq 0 ] && [ "${#ps_d[@]}" -eq 0 ]; then
if [ "${#as_m[@]}" -eq 0 ] && [ "${#as_d[@]}" -eq 0 ]; then
subject="新增图片资源 ${total} 个"
elif [ "${#as_a[@]}" -eq 0 ] && [ "${#as_m[@]}" -eq 0 ]; then
subject="删除图片资源 ${total} 个"
else
subject="更新图片资源 ${total} 个"
fi
counted=1
elif [ "$total" -eq 1 ] && [ "${#pg_m[@]}" -eq 1 ]; then
subject="更新页面 ${pg_m[0]}"
else
subject="更新 ${total} 个文件"; counted=1
fi
if [ "$total" -gt 1 ] && [ "$counted" -eq 0 ]; then
subject="${subject},共 ${total} 个文件"
fi
# 正文:按分类列明细
if [ "$total" -gt 1 ]; then
if [ "${#ps_a[@]}" -gt 0 ]; then body+=("文章:新增 ${#ps_a[@]} 篇 · $(join_names 4 篇 "${ps_a[@]}")"); fi
if [ "${#ps_m[@]}" -gt 0 ]; then body+=("文章:修改 ${#ps_m[@]} 篇 · $(join_names 4 篇 "${ps_m[@]}")"); fi
if [ "${#ps_d[@]}" -gt 0 ]; then body+=("文章:删除 ${#ps_d[@]} 篇 · $(join_names 4 篇 "${ps_d[@]}")"); fi
if [ "${#pg_a[@]}" -gt 0 ]; then body+=("页面:新增 $(join_names 4 个 "${pg_a[@]}")"); fi
if [ "${#pg_m[@]}" -gt 0 ]; then body+=("页面:修改 $(join_names 4 个 "${pg_m[@]}")"); fi
if [ "${#pg_d[@]}" -gt 0 ]; then body+=("页面:删除 $(join_names 4 个 "${pg_d[@]}")"); fi
if [ "${#st_a[@]}" -gt 0 ]; then body+=("样式脚本:新增 $(join_names 4 个 "${st_a[@]}")"); fi
if [ "${#st_m[@]}" -gt 0 ]; then body+=("样式脚本:修改 $(join_names 4 个 "${st_m[@]}")"); fi
if [ "${#st_d[@]}" -gt 0 ]; then body+=("样式脚本:删除 $(join_names 4 个 "${st_d[@]}")"); fi
if [ "${#as_a[@]}" -gt 0 ]; then body+=("资源:新增 ${#as_a[@]} 个 · $(join_names 4 个 "${as_a[@]}")"); fi
if [ "${#as_m[@]}" -gt 0 ]; then body+=("资源:修改 ${#as_m[@]} 个 · $(join_names 4 个 "${as_m[@]}")"); fi
if [ "${#as_d[@]}" -gt 0 ]; then body+=("资源:删除 ${#as_d[@]} 个 · $(join_names 4 个 "${as_d[@]}")"); fi
if [ "${#ot_a[@]}" -gt 0 ]; then body+=("其他:新增 $(join_names 4 个 "${ot_a[@]}")"); fi
if [ "${#ot_m[@]}" -gt 0 ]; then body+=("其他:修改 $(join_names 4 个 "${ot_m[@]}")"); fi
if [ "${#ot_d[@]}" -gt 0 ]; then body+=("其他:删除 $(join_names 4 个 "${ot_d[@]}")"); fi
if [ "${#body[@]}" -gt 6 ]; then
local keep=("${body[@]:0:6}")
body=("${keep[@]}" "…等 $(( ${#body[@]} - 6 )) 项")
fi
fi
printf '%s\n' "$subject"
if [ "${#body[@]}" -gt 0 ]; then
printf '\n'
local line
for line in "${body[@]}"; do printf '%s\n' "$line"; done
fi
}
if [ -n "$CUSTOM_MSG" ]; then
msg="$CUSTOM_MSG"
else
msg="$(build_message)"
fi
subject="$(printf '%s\n' "$msg" | head -n 1)"
body="$(printf '%s\n' "$msg" | tail -n +2)"
# ---------- 8. 提交与推送 ----------
log ""
log "提交信息:"
printf '%s\n' "$msg" | sed 's/^/ /'
log ""
if [ -n "$(printf '%s' "$body" | tr -d '[:space:]')" ]; then
git commit -m "$subject" -m "$body" >/dev/null || die 1 "git commit 失败"
else
git commit -m "$subject" >/dev/null || die 1 "git commit 失败"
fi
local_sha="$(git rev-parse --short HEAD)"
if [ "$NO_PUSH" -eq 1 ]; then
log "✓ 已提交 $local_sha(--no-push,未推送)"
write_log "COMMIT $local_sha subject=$subject (no-push)"
notify "博客推送" "已本地提交(未推送)"
exit 0
fi
log "→ 推送到 $BLOG_REMOTE/$BLOG_BRANCH"
if ! git push "$BLOG_REMOTE" "HEAD:$BLOG_BRANCH" 2>&1 | sed 's/^/ /'; then
write_log "PUSH-FAIL $local_sha"
die 2 "推送失败,提交 $local_sha 已保存在本地"
fi
log "✓ 已推送 $local_sha,Cloudflare Pages 将开始构建"
write_log "OK $local_sha subject=$subject"
notify "博客已推送" "$subject"
exit 0