Initial commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
56
scripts/archive.sh
Normal file
56
scripts/archive.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: archive.sh <repo> [--unarchive] [-y|--yes]
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || usage
|
||||
|
||||
REPO=""
|
||||
ACTION="archive"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--unarchive) ACTION="unarchive"; shift ;;
|
||||
-y|--yes) GITEA_YES=1; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) log_err "未知參數:$1"; usage ;;
|
||||
*)
|
||||
if [[ -z "$REPO" ]]; then REPO="$1"; else log_err "多餘參數:$1"; usage; fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$REPO" ]] || usage
|
||||
|
||||
load_config
|
||||
|
||||
if [[ "$ACTION" == "archive" ]]; then
|
||||
confirm_destructive "確定要 archive ${GITEA_USER}/${REPO}?"
|
||||
BODY='{"archived":true}'
|
||||
MSG="archived"
|
||||
else
|
||||
confirm_destructive "確定要 unarchive ${GITEA_USER}/${REPO}?"
|
||||
BODY='{"archived":false}'
|
||||
MSG="unarchived"
|
||||
fi
|
||||
|
||||
log_info "${ACTION} ${GITEA_USER}/${REPO}"
|
||||
gitea_api PATCH "/repos/${GITEA_USER}/${REPO}" "$BODY"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
200|201) log_ok "$MSG" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${REPO}"; exit 1 ;;
|
||||
*) log_err "${ACTION} 失敗 (HTTP ${HTTP_CODE})"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
log_done "完成"
|
||||
echo "${GITEA_URL}/${GITEA_USER}/${REPO}"
|
||||
48
scripts/clone.sh
Normal file
48
scripts/clone.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: clone.sh <repo> [target-dir]
|
||||
|
||||
<repo> GITEA_USER 名下的 repo 名稱
|
||||
[target-dir] clone 目的地(預設 ./<repo>)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 && $# -le 2 ]] || usage
|
||||
case "$1" in
|
||||
-h|--help) usage ;;
|
||||
esac
|
||||
|
||||
REPO="$1"
|
||||
TARGET="${2:-./${REPO}}"
|
||||
|
||||
if [[ -e "$TARGET" ]]; then
|
||||
# 空目錄 OK;其他情況拒絕
|
||||
if [[ -d "$TARGET" ]]; then
|
||||
if [[ -n "$(ls -A "$TARGET")" ]]; then
|
||||
log_err "目標目錄已存在且非空:${TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_err "目標路徑已存在且不是目錄:${TARGET}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
load_config
|
||||
|
||||
CLEAN_URL="${GITEA_URL}/${GITEA_USER}/${REPO}.git"
|
||||
|
||||
log_info "clone ${GITEA_USER}/${REPO} → ${TARGET}"
|
||||
git_clone_with_token "$CLEAN_URL" "$TARGET"
|
||||
|
||||
ABS="$(cd "$TARGET" && pwd)"
|
||||
log_done "完成"
|
||||
echo "$ABS"
|
||||
48
scripts/delete.sh
Normal file
48
scripts/delete.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: delete.sh <repo> [-y|--yes]
|
||||
|
||||
互動模式:會要求重新輸入完整 repo 名稱才執行。
|
||||
-y / --yes 或 GITEA_YES=1:直接執行,不需輸入。
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || usage
|
||||
|
||||
REPO=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-y|--yes) GITEA_YES=1; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) log_err "未知參數:$1"; usage ;;
|
||||
*)
|
||||
if [[ -z "$REPO" ]]; then REPO="$1"; else log_err "多餘參數:$1"; usage; fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$REPO" ]] || usage
|
||||
|
||||
load_config
|
||||
|
||||
log_warn "即將刪除 ${GITEA_USER}/${REPO}(此操作不可復原)"
|
||||
confirm_exact_match "$REPO"
|
||||
|
||||
log_info "刪除 ${GITEA_USER}/${REPO}"
|
||||
gitea_api DELETE "/repos/${GITEA_USER}/${REPO}"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
204) log_ok "已刪除" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${REPO}"; exit 1 ;;
|
||||
*) log_err "刪除失敗 (HTTP ${HTTP_CODE})"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
log_done "完成"
|
||||
193
scripts/lib/common.sh
Normal file
193
scripts/lib/common.sh
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
# gitea-cli 共用 lib。
|
||||
# 使用方式(呼叫端):
|
||||
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# source "${SCRIPT_DIR}/lib/common.sh"
|
||||
# load_config
|
||||
#
|
||||
# 重要:所有 API 呼叫走 gitea_api;所有 push/clone 走 git_push_with_token / git_clone_with_token。
|
||||
# token 絕不允許出現在 .git/config、remote URL、log 中。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
_SKILL_DIR="$(cd "${_LIB_DIR}/../.." && pwd)"
|
||||
CONFIG_FILE="${_SKILL_DIR}/config.env"
|
||||
|
||||
GITEA_API_RESP_FILE=""
|
||||
_cleanup_common() {
|
||||
if [[ -n "${GITEA_API_RESP_FILE:-}" && -f "${GITEA_API_RESP_FILE}" ]]; then
|
||||
rm -f "${GITEA_API_RESP_FILE}"
|
||||
fi
|
||||
}
|
||||
trap _cleanup_common EXIT
|
||||
|
||||
# === logging(全部寫 stderr;stdout 留給結構化輸出)===
|
||||
log_info() { printf '▶ %s\n' "$*" >&2; }
|
||||
log_ok() { printf '✓ %s\n' "$*" >&2; }
|
||||
log_warn() { printf '⚠ %s\n' "$*" >&2; }
|
||||
log_err() { printf '❌ %s\n' "$*" >&2; }
|
||||
log_done() { printf '✅ %s\n' "$*" >&2; }
|
||||
|
||||
# === 必要指令檢查 ===
|
||||
require_cmd() {
|
||||
local missing=()
|
||||
for c in "$@"; do
|
||||
command -v "$c" >/dev/null 2>&1 || missing+=("$c")
|
||||
done
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
log_err "缺少指令:${missing[*]}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# === config 載入 ===
|
||||
load_config() {
|
||||
require_cmd curl git
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
log_err "找不到設定檔:${CONFIG_FILE}"
|
||||
{
|
||||
echo ""
|
||||
echo "請先建立:"
|
||||
echo " cp ${_SKILL_DIR}/config.example.env ${CONFIG_FILE}"
|
||||
echo ""
|
||||
echo "然後編輯 ${CONFIG_FILE} 填入 GITEA_URL / GITEA_USER / GITEA_TOKEN"
|
||||
} >&2
|
||||
exit 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source "$CONFIG_FILE"
|
||||
: "${GITEA_URL:?GITEA_URL 未設定(請編輯 ${CONFIG_FILE})}"
|
||||
: "${GITEA_USER:?GITEA_USER 未設定(請編輯 ${CONFIG_FILE})}"
|
||||
: "${GITEA_TOKEN:?GITEA_TOKEN 未設定(請編輯 ${CONFIG_FILE})}"
|
||||
if [[ "$GITEA_TOKEN" == "請貼上你的_access_token" ]]; then
|
||||
log_err "GITEA_TOKEN 仍是預設值,請編輯 ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$GITEA_URL" == "http://your-gitea.example.com:3000" ]]; then
|
||||
log_err "GITEA_URL 仍是預設值,請編輯 ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$GITEA_USER" == "your-username" ]]; then
|
||||
log_err "GITEA_USER 仍是預設值,請編輯 ${CONFIG_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
GITEA_URL="${GITEA_URL%/}"
|
||||
export GITEA_URL GITEA_USER GITEA_TOKEN
|
||||
}
|
||||
|
||||
# === Gitea API 呼叫 ===
|
||||
# gitea_api <METHOD> <path-from-/api/v1> [json-body]
|
||||
# 回傳後讀:$HTTP_CODE(狀態碼字串)、$GITEA_API_RESP_FILE(body 檔路徑)
|
||||
gitea_api() {
|
||||
# 清掉上一次的 tmpfile(防止多次呼叫時洩漏)
|
||||
if [[ -n "${GITEA_API_RESP_FILE:-}" && -f "${GITEA_API_RESP_FILE}" ]]; then
|
||||
rm -f "${GITEA_API_RESP_FILE}"
|
||||
fi
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local body="${3:-}"
|
||||
local url="${GITEA_URL}/api/v1${path}"
|
||||
|
||||
GITEA_API_RESP_FILE="$(mktemp)"
|
||||
|
||||
local -a curl_args=(
|
||||
-sS
|
||||
-o "$GITEA_API_RESP_FILE"
|
||||
-w '%{http_code}'
|
||||
-X "$method"
|
||||
-H "Authorization: token ${GITEA_TOKEN}"
|
||||
)
|
||||
if [[ -n "$body" ]]; then
|
||||
curl_args+=(-H "Content-Type: application/json" -d "$body")
|
||||
fi
|
||||
HTTP_CODE="$(curl "${curl_args[@]}" "$url")"
|
||||
export HTTP_CODE GITEA_API_RESP_FILE
|
||||
}
|
||||
|
||||
# 一次性帶 token 的 git push / clone
|
||||
# 注意:token 透過 -c 參數傳入,在 git 子程序存活期間短暫出現於 process 列表
|
||||
# (/proc/PID/cmdline)。這是此模式固有取捨,不會持久化到 .git/config / remote URL / log。
|
||||
_git_with_token() {
|
||||
git \
|
||||
-c 'credential.helper=' \
|
||||
-c "credential.helper=!f() { echo username=${GITEA_USER}; echo password=${GITEA_TOKEN}; }; f" \
|
||||
"$@"
|
||||
}
|
||||
|
||||
git_push_with_token() {
|
||||
local branch="$1"
|
||||
_git_with_token push -u origin "$branch"
|
||||
}
|
||||
|
||||
git_push_tag_with_token() {
|
||||
local tag="$1"
|
||||
_git_with_token push origin "$tag"
|
||||
}
|
||||
|
||||
git_clone_with_token() {
|
||||
local clean_url="$1"
|
||||
local target_dir="$2"
|
||||
_git_with_token clone "$clean_url" "$target_dir"
|
||||
# 再確保 origin 是乾淨 URL(clone 本來就是用 clean_url,這裡是防呆)
|
||||
git -C "$target_dir" remote set-url origin "$clean_url"
|
||||
}
|
||||
|
||||
# 從 cwd origin 推斷 repo 名稱
|
||||
# 成功時 echo "owner/repo"(例:timmy/my-project)並回傳 0;失敗回傳非 0
|
||||
# 呼叫端若只需要 repo 名稱:repo_name="${result##*/}"
|
||||
# 只支援 http(s):// 形式 URL;ssh (git@) 不支援(刻意 YAGNI)
|
||||
infer_repo_from_cwd() {
|
||||
local url
|
||||
if ! url="$(git remote get-url origin 2>/dev/null)"; then
|
||||
return 1
|
||||
fi
|
||||
# 支援 http(s)://host[:port]/owner/repo[.git]
|
||||
local path="${url#*://}"
|
||||
path="${path#*/}"
|
||||
path="${path%.git}"
|
||||
[[ -z "$path" ]] && return 1
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
# === 確認提示 ===
|
||||
# confirm_destructive <prompt>
|
||||
# 若 GITEA_YES=1 則直接通過;否則 y/N 互動
|
||||
confirm_destructive() {
|
||||
local prompt="$1"
|
||||
if [[ "${GITEA_YES:-0}" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -r /dev/tty ]]; then
|
||||
log_err "需要確認但無 TTY;請加 -y / --yes 或設定 GITEA_YES=1"
|
||||
exit 1
|
||||
fi
|
||||
local ans
|
||||
printf '%s [y/N] ' "$prompt" >&2
|
||||
read -r ans < /dev/tty
|
||||
case "$ans" in
|
||||
y|Y|yes|YES) return 0 ;;
|
||||
*) log_err "取消"; exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# confirm_exact_match <expected>
|
||||
# 要求使用者重新輸入完整字串才通過;GITEA_YES=1 可略過
|
||||
confirm_exact_match() {
|
||||
local expected="$1"
|
||||
if [[ "${GITEA_YES:-0}" == "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -r /dev/tty ]]; then
|
||||
log_err "需要確認但無 TTY;請加 -y / --yes 或設定 GITEA_YES=1"
|
||||
exit 1
|
||||
fi
|
||||
local ans
|
||||
printf '請完整輸入 "%s" 以確認:' "$expected" >&2
|
||||
read -r ans < /dev/tty
|
||||
if [[ "$ans" == "$expected" ]]; then
|
||||
return 0
|
||||
fi
|
||||
log_err "輸入不符合,取消"
|
||||
exit 1
|
||||
}
|
||||
82
scripts/list.sh
Executable file
82
scripts/list.sh
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
FILTER=""
|
||||
FORMAT="table"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: list.sh [--private|--public] [--format table|tsv|name]
|
||||
|
||||
--private 只列出 private repo
|
||||
--public 只列出 public repo
|
||||
--format FORMAT table (default) | tsv | name
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--private) FILTER="private"; shift ;;
|
||||
--public) FILTER="public"; shift ;;
|
||||
--format)
|
||||
[[ $# -ge 2 ]] || { log_err "--format 需要一個值"; exit 1; }
|
||||
FORMAT="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
*) log_err "未知參數:$1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$FORMAT" in
|
||||
table|tsv|name) ;;
|
||||
*) log_err "--format 只能是 table / tsv / name"; exit 1 ;;
|
||||
esac
|
||||
|
||||
load_config
|
||||
require_cmd jq column
|
||||
|
||||
PAGE=1
|
||||
LIMIT=50
|
||||
ALL_ITEMS="$(mktemp)"
|
||||
trap 'rm -f "$ALL_ITEMS"; _cleanup_common' EXIT
|
||||
|
||||
while :; do
|
||||
gitea_api GET "/user/repos?limit=${LIMIT}&page=${PAGE}"
|
||||
if [[ "$HTTP_CODE" != "200" ]]; then
|
||||
log_err "GET /user/repos 失敗 (HTTP ${HTTP_CODE})"
|
||||
cat "$GITEA_API_RESP_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
jq -c '.[]' < "$GITEA_API_RESP_FILE" >> "$ALL_ITEMS"
|
||||
COUNT="$(jq 'length' < "$GITEA_API_RESP_FILE")"
|
||||
[[ "$COUNT" -lt "$LIMIT" ]] && break
|
||||
PAGE=$((PAGE + 1))
|
||||
done
|
||||
|
||||
filter_jq="."
|
||||
case "$FILTER" in
|
||||
private) filter_jq='select(.private == true)' ;;
|
||||
public) filter_jq='select(.private == false)' ;;
|
||||
esac
|
||||
|
||||
TOTAL="$(jq -s "map($filter_jq) | length" < "$ALL_ITEMS")"
|
||||
log_info "抓到 ${TOTAL} 個 repo"
|
||||
|
||||
case "$FORMAT" in
|
||||
name)
|
||||
jq -r "$filter_jq | .name" < "$ALL_ITEMS"
|
||||
;;
|
||||
tsv)
|
||||
jq -r "$filter_jq | [.name, (if .private then \"private\" else \"public\" end), (.description // \"\")] | @tsv" < "$ALL_ITEMS"
|
||||
;;
|
||||
table)
|
||||
{
|
||||
printf 'NAME\tVISIBILITY\tDESCRIPTION\n'
|
||||
jq -r "$filter_jq | [.name, (if .private then \"private\" else \"public\" end), (.description // \"\")] | @tsv" < "$ALL_ITEMS"
|
||||
} | column -t -s $'\t'
|
||||
;;
|
||||
esac
|
||||
72
scripts/meta.sh
Normal file
72
scripts/meta.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: meta.sh <repo> [--desc "description"] [--topics "a,b,c"]
|
||||
|
||||
--desc 設定 repo description
|
||||
--topics 覆蓋式設定 topic list(逗號分隔)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || usage
|
||||
|
||||
REPO=""
|
||||
DESC=""
|
||||
HAS_DESC=0
|
||||
TOPICS_CSV=""
|
||||
HAS_TOPICS=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--desc)
|
||||
[[ $# -ge 2 ]] || { log_err "--desc 需要一個值"; exit 1; }
|
||||
DESC="$2"; HAS_DESC=1; shift 2 ;;
|
||||
--topics)
|
||||
[[ $# -ge 2 ]] || { log_err "--topics 需要一個值"; exit 1; }
|
||||
TOPICS_CSV="$2"; HAS_TOPICS=1; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) log_err "未知參數:$1"; usage ;;
|
||||
*)
|
||||
if [[ -z "$REPO" ]]; then REPO="$1"; else log_err "多餘參數:$1"; usage; fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$REPO" ]] || usage
|
||||
[[ $HAS_DESC -eq 1 || $HAS_TOPICS -eq 1 ]] || { log_err "必須至少提供 --desc 或 --topics"; usage; }
|
||||
|
||||
load_config
|
||||
require_cmd jq
|
||||
|
||||
if [[ $HAS_DESC -eq 1 ]]; then
|
||||
ESCAPED_DESC="$(printf '%s' "$DESC" | jq -Rs .)"
|
||||
log_info "更新 description"
|
||||
gitea_api PATCH "/repos/${GITEA_USER}/${REPO}" "{\"description\":${ESCAPED_DESC}}"
|
||||
case "$HTTP_CODE" in
|
||||
200|201) log_ok "description 已更新" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${REPO}"; exit 1 ;;
|
||||
*) log_err "更新 description 失敗 (HTTP ${HTTP_CODE})"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ $HAS_TOPICS -eq 1 ]]; then
|
||||
TOPICS_JSON="$(printf '%s' "$TOPICS_CSV" | jq -R 'split(",") | map(select(length > 0))')"
|
||||
log_info "覆蓋 topics:${TOPICS_JSON}"
|
||||
gitea_api PUT "/repos/${GITEA_USER}/${REPO}/topics" "{\"topics\":${TOPICS_JSON}}"
|
||||
case "$HTTP_CODE" in
|
||||
200|204) log_ok "topics 已更新" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${REPO}"; exit 1 ;;
|
||||
422) log_err "topics 格式錯誤"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
*) log_err "更新 topics 失敗 (HTTP ${HTTP_CODE})"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
log_done "完成"
|
||||
echo "${GITEA_URL}/${GITEA_USER}/${REPO}"
|
||||
123
scripts/publish.sh
Executable file
123
scripts/publish.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
REPO_NAME=""
|
||||
VISIBILITY="public"
|
||||
COMMIT_MSG=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-m)
|
||||
[[ $# -ge 2 ]] || { log_err "-m 需要一個訊息參數"; exit 1; }
|
||||
COMMIT_MSG="$2"
|
||||
shift 2
|
||||
;;
|
||||
public|private)
|
||||
VISIBILITY="$1"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
cat >&2 <<'EOF'
|
||||
Usage: publish.sh [repo] [public|private] [-m "commit message"]
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
log_err "未知參數:$1"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$REPO_NAME" ]]; then
|
||||
REPO_NAME="$1"
|
||||
elif [[ "$VISIBILITY" == "public" ]]; then
|
||||
VISIBILITY="$1"
|
||||
else
|
||||
log_err "多餘參數:$1"
|
||||
exit 1
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
REPO_NAME="${REPO_NAME:-$(basename "$PWD")}"
|
||||
|
||||
case "$VISIBILITY" in
|
||||
public) PRIVATE=false ;;
|
||||
private) PRIVATE=true ;;
|
||||
*)
|
||||
log_err "可見性只能是 public 或 private,收到:${VISIBILITY}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
load_config
|
||||
|
||||
CLEAN_URL="${GITEA_URL}/${GITEA_USER}/${REPO_NAME}.git"
|
||||
WEB_URL="${GITEA_URL}/${GITEA_USER}/${REPO_NAME}"
|
||||
|
||||
log_info "建立 Gitea repo:${GITEA_USER}/${REPO_NAME} (${VISIBILITY})"
|
||||
|
||||
gitea_api POST "/user/repos" \
|
||||
"{\"name\":\"${REPO_NAME}\",\"private\":${PRIVATE},\"auto_init\":false,\"default_branch\":\"main\"}"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
201) log_ok "repo 已建立" ;;
|
||||
409) log_warn "repo 已存在,略過建立步驟" ;;
|
||||
401|403)
|
||||
log_err "Gitea 授權失敗 (HTTP ${HTTP_CODE})"
|
||||
{
|
||||
echo "請確認 ${CONFIG_FILE} 的 GITEA_TOKEN 是否正確、權限是否包含 write:repository"
|
||||
cat "$GITEA_API_RESP_FILE"
|
||||
echo ""
|
||||
} >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
log_err "建立 repo 失敗 (HTTP ${HTTP_CODE})"
|
||||
{ cat "$GITEA_API_RESP_FILE"; echo ""; } >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ ! -d .git ]]; then
|
||||
log_info "git init"
|
||||
git init -b main >/dev/null
|
||||
fi
|
||||
|
||||
if ! git rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
log_info "工作目錄沒有檔案,建立空的 initial commit"
|
||||
git commit --allow-empty -m "${COMMIT_MSG:-Initial commit}" >/dev/null
|
||||
else
|
||||
log_info "建立 initial commit"
|
||||
git commit -m "${COMMIT_MSG:-Initial commit}" >/dev/null
|
||||
fi
|
||||
else
|
||||
git add -A
|
||||
if ! git diff --cached --quiet; then
|
||||
log_info "提交未提交的變更"
|
||||
git commit -m "${COMMIT_MSG:-Update}" >/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
if git remote get-url origin >/dev/null 2>&1; then
|
||||
git remote set-url origin "$CLEAN_URL"
|
||||
else
|
||||
git remote add origin "$CLEAN_URL"
|
||||
fi
|
||||
|
||||
log_info "推送到 origin (${CLEAN_URL}) branch=${BRANCH}"
|
||||
git_push_with_token "$BRANCH"
|
||||
|
||||
log_done "完成"
|
||||
echo " Branch: ${BRANCH}" >&2
|
||||
# stdout:最終結構化輸出
|
||||
echo "$WEB_URL"
|
||||
140
scripts/release.sh
Executable file
140
scripts/release.sh
Executable file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: release.sh <tag> [--name NAME] [--notes TEXT] [--target BRANCH]
|
||||
[--draft] [--prerelease] [--asset PATH]... [--repo NAME]
|
||||
|
||||
<tag> tag_name;若 tag 不存在 Gitea 會在 target 自動建
|
||||
--name release 標題(預設同 tag)
|
||||
--notes release 說明
|
||||
--target target_commitish;預設 HEAD 所在分支
|
||||
--draft 建成 draft
|
||||
--prerelease 標記 prerelease
|
||||
--asset PATH 要上傳的附件(可重複)
|
||||
--repo NAME 指定 repo;省略則從 cwd origin 推斷
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || usage
|
||||
|
||||
TAG=""
|
||||
NAME=""
|
||||
NOTES=""
|
||||
TARGET=""
|
||||
DRAFT=false
|
||||
PRERELEASE=false
|
||||
ASSETS=()
|
||||
REPO_OVERRIDE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name) [[ $# -ge 2 ]] || { log_err "--name 需要值"; exit 1; }; NAME="$2"; shift 2 ;;
|
||||
--notes) [[ $# -ge 2 ]] || { log_err "--notes 需要值"; exit 1; }; NOTES="$2"; shift 2 ;;
|
||||
--target) [[ $# -ge 2 ]] || { log_err "--target 需要值"; exit 1; }; TARGET="$2"; shift 2 ;;
|
||||
--draft) DRAFT=true; shift ;;
|
||||
--prerelease) PRERELEASE=true; shift ;;
|
||||
--asset) [[ $# -ge 2 ]] || { log_err "--asset 需要值"; exit 1; }; ASSETS+=("$2"); shift 2 ;;
|
||||
--repo) [[ $# -ge 2 ]] || { log_err "--repo 需要值"; exit 1; }; REPO_OVERRIDE="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) log_err "未知參數:$1"; usage ;;
|
||||
*)
|
||||
if [[ -z "$TAG" ]]; then TAG="$1"; else log_err "多餘參數:$1"; usage; fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$TAG" ]] || usage
|
||||
NAME="${NAME:-$TAG}"
|
||||
|
||||
load_config
|
||||
require_cmd jq
|
||||
|
||||
if [[ -n "$REPO_OVERRIDE" ]]; then
|
||||
OWNER_REPO="${GITEA_USER}/${REPO_OVERRIDE}"
|
||||
else
|
||||
if ! OWNER_REPO="$(infer_repo_from_cwd)"; then
|
||||
log_err "無法從 cwd 推斷 repo;請加 --repo"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$TARGET" ]]; then
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
TARGET="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 驗證 asset 路徑
|
||||
for a in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
if [[ ! -f "$a" ]]; then
|
||||
log_err "找不到 asset 檔案:$a"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# 組 release body
|
||||
BODY="$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg name "$NAME" \
|
||||
--arg body "$NOTES" \
|
||||
--arg target "$TARGET" \
|
||||
--argjson draft "$DRAFT" \
|
||||
--argjson prerelease "$PRERELEASE" \
|
||||
'{tag_name:$tag, name:$name, body:$body, draft:$draft, prerelease:$prerelease}
|
||||
+ (if $target == "" then {} else {target_commitish:$target} end)')"
|
||||
|
||||
log_info "建立 release ${OWNER_REPO} / ${TAG}"
|
||||
gitea_api POST "/repos/${OWNER_REPO}/releases" "$BODY"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
201) log_ok "release 已建立" ;;
|
||||
404) log_err "找不到 repo:${OWNER_REPO}"; exit 1 ;;
|
||||
*) log_err "建立 release 失敗 (HTTP ${HTTP_CODE})"; { cat "$GITEA_API_RESP_FILE"; echo ""; } >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
RELEASE_ID="$(jq -r '.id' < "$GITEA_API_RESP_FILE")"
|
||||
RELEASE_URL="$(jq -r '.html_url' < "$GITEA_API_RESP_FILE")"
|
||||
|
||||
# stdout 先印 release URL
|
||||
echo "$RELEASE_URL"
|
||||
|
||||
# 上傳 asset
|
||||
FAILED=()
|
||||
for a in "${ASSETS[@]+"${ASSETS[@]}"}"; do
|
||||
local_name="$(basename "$a")"
|
||||
log_info "上傳 asset:${local_name}"
|
||||
UP_RESP="$(mktemp)"
|
||||
UP_CODE="$(curl -sS -o "$UP_RESP" -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@${a};filename=${local_name}" \
|
||||
"${GITEA_URL}/api/v1/repos/${OWNER_REPO}/releases/${RELEASE_ID}/assets?name=${local_name}")"
|
||||
case "$UP_CODE" in
|
||||
201)
|
||||
ASSET_URL="$(jq -r '.browser_download_url' < "$UP_RESP")"
|
||||
log_ok "$local_name"
|
||||
echo "$ASSET_URL"
|
||||
;;
|
||||
*)
|
||||
log_err "上傳 ${local_name} 失敗 (HTTP ${UP_CODE})"
|
||||
cat "$UP_RESP" >&2
|
||||
echo "" >&2
|
||||
FAILED+=("$local_name")
|
||||
;;
|
||||
esac
|
||||
rm -f "$UP_RESP"
|
||||
done
|
||||
|
||||
if [[ ${#FAILED[@]} -gt 0 ]]; then
|
||||
log_err "部分 asset 上傳失敗:${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_done "完成"
|
||||
47
scripts/rename.sh
Normal file
47
scripts/rename.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: rename.sh <old-name> <new-name>
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -eq 2 ]] || usage
|
||||
|
||||
OLD="$1"
|
||||
NEW="$2"
|
||||
|
||||
load_config
|
||||
|
||||
log_info "改名 ${GITEA_USER}/${OLD} → ${GITEA_USER}/${NEW}"
|
||||
gitea_api PATCH "/repos/${GITEA_USER}/${OLD}" "{\"name\":\"${NEW}\"}"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
200|201) log_ok "改名完成" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${OLD}"; exit 1 ;;
|
||||
422) log_err "新名稱可能已被使用或無效"; cat "$GITEA_API_RESP_FILE" >&2; echo "" >&2; exit 1 ;;
|
||||
*)
|
||||
log_err "改名失敗 (HTTP ${HTTP_CODE})"
|
||||
{ cat "$GITEA_API_RESP_FILE"; echo ""; } >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# 若 cwd 的 origin 指到舊 repo,自動更新
|
||||
if CURR="$(infer_repo_from_cwd 2>/dev/null)"; then
|
||||
if [[ "$CURR" == "${GITEA_USER}/${OLD}" ]]; then
|
||||
NEW_CLEAN_URL="${GITEA_URL}/${GITEA_USER}/${NEW}.git"
|
||||
git remote set-url origin "$NEW_CLEAN_URL"
|
||||
log_ok "cwd 的 origin 已更新為 ${NEW_CLEAN_URL}"
|
||||
fi
|
||||
fi
|
||||
|
||||
NEW_WEB_URL="${GITEA_URL}/${GITEA_USER}/${NEW}"
|
||||
log_done "完成"
|
||||
echo "$NEW_WEB_URL"
|
||||
56
scripts/tag.sh
Normal file
56
scripts/tag.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: tag.sh <tag> [-m "message"]
|
||||
|
||||
建立 annotated tag 並推到 origin。
|
||||
需要在 cwd 已有 git repo 且 origin 已指到 Gitea。
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -ge 1 ]] || usage
|
||||
|
||||
TAG=""
|
||||
MSG=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-m) [[ $# -ge 2 ]] || { log_err "-m 需要一個值"; exit 1; }; MSG="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) log_err "未知參數:$1"; usage ;;
|
||||
*)
|
||||
if [[ -z "$TAG" ]]; then TAG="$1"; else log_err "多餘參數:$1"; usage; fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$TAG" ]] || usage
|
||||
MSG="${MSG:-Tag $TAG}"
|
||||
|
||||
load_config
|
||||
|
||||
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
log_err "cwd 不是 git repo"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
log_err "tag 已存在:${TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "建立 tag ${TAG}"
|
||||
git tag -a "$TAG" -m "$MSG"
|
||||
|
||||
log_info "推送 tag 到 origin"
|
||||
git_push_tag_with_token "$TAG"
|
||||
|
||||
log_done "完成"
|
||||
echo "$TAG"
|
||||
42
scripts/visibility.sh
Normal file
42
scripts/visibility.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=lib/common.sh
|
||||
source "${SCRIPT_DIR}/lib/common.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
Usage: visibility.sh <repo> <public|private>
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
[[ $# -eq 2 ]] || usage
|
||||
|
||||
REPO="$1"
|
||||
VIS="$2"
|
||||
|
||||
case "$VIS" in
|
||||
public) PRIVATE=false ;;
|
||||
private) PRIVATE=true ;;
|
||||
*) log_err "可見性只能是 public 或 private"; exit 1 ;;
|
||||
esac
|
||||
|
||||
load_config
|
||||
|
||||
log_info "切換 ${GITEA_USER}/${REPO} → ${VIS}"
|
||||
gitea_api PATCH "/repos/${GITEA_USER}/${REPO}" "{\"private\":${PRIVATE}}"
|
||||
|
||||
case "$HTTP_CODE" in
|
||||
200|201) log_ok "已更新" ;;
|
||||
404) log_err "找不到 repo:${GITEA_USER}/${REPO}"; exit 1 ;;
|
||||
*)
|
||||
log_err "更新失敗 (HTTP ${HTTP_CODE})"
|
||||
{ cat "$GITEA_API_RESP_FILE"; echo ""; } >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
log_done "完成"
|
||||
echo "${GITEA_URL}/${GITEA_USER}/${REPO}"
|
||||
Reference in New Issue
Block a user