Files
log/tools/worklog-pending.sh
T
jiantw83 416fb916cb feat(worklog-pending): 暫存改三段式的 merge、commit、abort
What:`tools/worklog-pending.sh` 新增 `merge {hash} {本次條目檔}`、`commit {hash} {併入清單}`、`abort {hash} {併入清單}` 三個子命令。`merge` 合成「暫存內容在前、本次條目在後」的檔案並印出 `MERGED=`、`CLAIM=`、`PENDING=`;`commit` 只刪併入清單上那幾個檔;`abort` 暫存一個都不刪,只丟掉合併檔與清單。原本的 `clear` 保留,改為清掉全部暫存。

Why:暫存只能在 wiki 寫入成功之後才清,但原本只有一個 `clear`,呼叫端要嘛先清再寫(寫失敗就兩邊都沒有),要嘛寫完清全部(把 `merge` 之後才存進來的內容一起刪掉)。兩種都會弄丟工作紀錄。

How:`merge` 只讀不刪,刪檔一律等 `commit`,也就是等 wiki 回報成功。只刪清單上的檔案,是為了保住 `merge` 之後、`commit` 之前另一個工作階段新存進來的內容。`commit` 只認 `merge` 產出的清單,而且清單上的路徑必須落在該 hash 的暫存目錄底下,不然當用法錯誤擋下來——這道護欄擋的是拿別處的清單來刪檔。`merge` 在沒有暫存時仍然回 `0`,合併檔至少有本次條目,呼叫端不必分兩條路走。

Who:`jsc-log:worklog` 寫日誌那一步,以及用 `stage-report.sh --pending-file` 暫存過內容的 SDLC 階段。
2026-08-27 11:20:28 +08:00

183 lines
8.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# worklog-pending.sh — 待寫入工作日誌的暫存區(供 jsc-sdlc 階段回報與 jsc-log:worklog 使用)。
#
# 為什麼要有這支腳本:SDLC 每個階段結束都要回報工作日誌連結。階段跑完卻沒寫日誌時,
# 內容如果只留在對話裡,換一個工作階段就消失了,下次寫日誌也補不回來。所以暫存搬到檔案層:
# 階段回報發現沒有日誌,就把該階段的內容存進本區並警告使用者;下次 jsc-log:worklog 寫入時
# 先把本區的內容一併寫進去,寫完才清掉。
#
# 一次寫入的三段式(merge、commit、abort)解決同一件事:暫存只能在 wiki 寫入成功之後才清。
# merge 把暫存內容與本次條目合成一份要寫進 wiki 的檔案,同時記下這次併走了哪些暫存檔(併入清單)。
# commit wiki 寫入成功後呼叫,只刪掉併入清單上那幾個檔。
# abort wiki 寫入失敗後呼叫,暫存原封不動,只丟掉合併檔與併入清單。
# 只刪清單上的檔案,是為了保住 merge 之後、commit 之前另一個工作階段新存進來的內容。
#
# 用法:
# worklog-pending.sh add <hash> <file> 把一段待寫入的日誌內容存起來,印出存放路徑
# worklog-pending.sh list <hash> 列出該 hash 的暫存檔路徑(一行一個,依時間排序)
# worklog-pending.sh cat <hash> 依時間順序印出全部暫存內容
# worklog-pending.sh clear <hash> 清掉該 hash 的全部暫存(寫入日誌成功後才做)
# worklog-pending.sh merge <hash> <file> 合成「暫存內容+本次條目」,印出 MERGED= 與 CLAIM=
# worklog-pending.sh commit <hash> <claim> 寫入成功後清掉併入清單上的暫存檔
# worklog-pending.sh abort <hash> <claim> 寫入失敗後保留暫存,只丟掉合併檔與併入清單
#
# <hash> 為 jsc-gitea/tools/hash-id 算出的 8 碼工作日誌 hash,也就是 LOG_{HASH} 的 HASH。
# 存放位置: $JSC_HOME/worklog-pending/{hash}/{UTC 時間}-{pid}.md(JSC_HOME 預設 ~/.jsc)
# 合併檔: $JSC_HOME/worklog-pending/.merge/{hash}-{UTC 時間}-{pid}.md
# 併入清單: 同名換副檔名 .claim,一行一個被併走的暫存檔路徑
#
# 結束碼: 0=成功 1=讀寫失敗 2=用法錯誤 3=該 hash 沒有暫存內容
# merge 沒有暫存內容時仍然 exit 0:合併檔至少有本次條目,呼叫端不必分兩條路走。
#
# 陷阱:
# - clear 與 commit 都只在日誌確實寫進 wiki 之後才呼叫。先清再寫,寫失敗就兩邊都沒有。
# - commit 只認 merge 產出的併入清單,且清單上的路徑必須落在該 hash 的暫存目錄底下,
# 不然就當成用法錯誤擋下來——這道護欄擋的是拿別人的清單來刪檔。
# - 檔名帶 UTC 時間與 pid,同一秒內兩個工作階段各存各的,不會互相覆蓋。
set -eu
JSC_HOME="${JSC_HOME:-$HOME/.jsc}"
ROOT="$JSC_HOME/worklog-pending"
MERGE_DIR="$ROOT/.merge"
usage() {
cat >&2 <<'EOF'
用法:
worklog-pending.sh add <hash> <file> 存一段待寫入的日誌內容
worklog-pending.sh list <hash> 列出暫存檔路徑
worklog-pending.sh cat <hash> 印出全部暫存內容
worklog-pending.sh clear <hash> 清掉全部暫存(寫入日誌成功後才做)
worklog-pending.sh merge <hash> <file> 合成「暫存內容+本次條目」,印出 MERGED= 與 CLAIM=
worklog-pending.sh commit <hash> <claim> 寫入成功後清掉併入清單上的暫存檔
worklog-pending.sh abort <hash> <claim> 寫入失敗後保留暫存,只丟掉合併檔與併入清單
結束碼: 0=成功 1=讀寫失敗 2=用法錯誤 3=沒有暫存內容
EOF
exit 2
}
valid_hash() { # 只收 8 碼大寫英數,擋掉路徑穿越
case "$1" in
[0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z][0-9A-Z]) return 0 ;;
*) echo "[jsc][工作日誌暫存][ERR]:hash 須為 8 碼大寫英數,收到「${1:-空值}」。" >&2; exit 2 ;;
esac
}
valid_claim() { # 併入清單只認 merge 產出的那一份,擋掉拿別處的清單來刪檔
case "$1" in
"$MERGE_DIR"/*.claim) ;;
*) echo "[jsc][工作日誌暫存][ERR]:併入清單須是 merge 產出的「$MERGE_DIR/*.claim」,收到「$1」。" >&2; exit 2 ;;
esac
[ -f "$1" ] || { echo "[jsc][工作日誌暫存][ERR]:找不到併入清單「$1」。" >&2; exit 1; }
}
cmd="${1:-}"; [ -n "$cmd" ] || usage
shift || true
hash="${1:-}"; [ -n "$hash" ] || usage
valid_hash "$hash"
dir="$ROOT/$hash"
case "$cmd" in
add)
src="${2:-}"
[ -n "$src" ] || usage
[ -f "$src" ] || { echo "[jsc][工作日誌暫存][ERR]:找不到內容檔「$src」。" >&2; exit 1; }
mkdir -p "$dir" || { echo "[jsc][工作日誌暫存][ERR]:建不出暫存目錄「$dir」。" >&2; exit 1; }
stamp=$(date -u +%Y%m%dT%H%M%SZ)
target="$dir/$stamp-$$.md"
cp "$src" "$target" || { echo "[jsc][工作日誌暫存][ERR]:寫不進「$target」。" >&2; exit 1; }
printf '%s\n' "$target"
;;
list)
[ -d "$dir" ] || exit 3
found=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
printf '%s\n' "$f"
found=1
done
[ "$found" -eq 1 ] || exit 3
;;
cat)
[ -d "$dir" ] || exit 3
found=0
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
cat "$f"
printf '\n'
found=1
done
[ "$found" -eq 1 ] || exit 3
;;
clear)
[ -d "$dir" ] || exit 3
rm -rf "$dir" || { echo "[jsc][工作日誌暫存][ERR]:清不掉「$dir」。" >&2; exit 1; }
printf '已清除暫存:%s\n' "$dir"
;;
merge)
# 合成這一次要寫進 wiki 的內容:暫存的在前(依時間),本次條目在後。
# 這裡只讀不刪——刪檔一律等 commit,也就是等 wiki 寫入回報成功。
src="${2:-}"
[ -n "$src" ] || usage
[ -f "$src" ] || { echo "[jsc][工作日誌暫存][ERR]:找不到本次條目檔「$src」。" >&2; exit 1; }
mkdir -p "$MERGE_DIR" || { echo "[jsc][工作日誌暫存][ERR]:建不出合併目錄「$MERGE_DIR」。" >&2; exit 1; }
stamp=$(date -u +%Y%m%dT%H%M%SZ)
merged="$MERGE_DIR/$hash-$stamp-$$.md"
claim="$MERGE_DIR/$hash-$stamp-$$.claim"
: > "$merged" || { echo "[jsc][工作日誌暫存][ERR]:寫不進「$merged」。" >&2; exit 1; }
: > "$claim" || { echo "[jsc][工作日誌暫存][ERR]:寫不進「$claim」。" >&2; exit 1; }
taken=0
if [ -d "$dir" ]; then
for f in "$dir"/*.md; do
[ -f "$f" ] || continue
cat "$f" >> "$merged" || { echo "[jsc][工作日誌暫存][ERR]:讀不到「$f」。" >&2; exit 1; }
printf '\n' >> "$merged"
printf '%s\n' "$f" >> "$claim"
taken=$((taken + 1))
done
fi
cat "$src" >> "$merged" || { echo "[jsc][工作日誌暫存][ERR]:併不進本次條目「$src」。" >&2; exit 1; }
printf '\n' >> "$merged"
printf 'MERGED=%s\n' "$merged"
printf 'CLAIM=%s\n' "$claim"
printf 'PENDING=%s\n' "$taken"
;;
commit)
# 護欄:只有 wiki 寫入成功才輪到這一段。清單以外的暫存檔一律不動,
# 因為那是 merge 之後才存進來的內容,還沒被寫進任何一頁。
claim="${2:-}"
[ -n "$claim" ] || usage
valid_claim "$claim"
cleared=0
while IFS= read -r f; do
[ -n "$f" ] || continue
case "$f" in
"$dir"/*) ;;
*) echo "[jsc][工作日誌暫存][ERR]:併入清單裡的「$f」不在「$dir」底下,不清。" >&2; exit 2 ;;
esac
[ -e "$f" ] || continue
rm -f "$f" || { echo "[jsc][工作日誌暫存][ERR]:清不掉「$f」。" >&2; exit 1; }
cleared=$((cleared + 1))
done < "$claim"
rmdir "$dir" 2>/dev/null || true
rm -f "$claim" "${claim%.claim}.md"
printf '已清除暫存 %s 個檔案(wiki 寫入成功後才做)。\n' "$cleared"
;;
abort)
# wiki 寫入失敗時走這裡:暫存一個都不刪,下一次 merge 會再把它們併進去。
claim="${2:-}"
[ -n "$claim" ] || usage
valid_claim "$claim"
kept=0
while IFS= read -r f; do
[ -n "$f" ] || continue
if [ -e "$f" ]; then
kept=$((kept + 1))
fi
done < "$claim"
rm -f "$claim" "${claim%.claim}.md"
printf '暫存保留 %s 個檔案,合併檔與併入清單已丟掉。\n' "$kept"
;;
*)
usage ;;
esac