f233212ed6
为生产构建和导入导出流水线的 GitSCM checkout 增加 Jenkins 凭据。 保留 genarrative-station HTTP 主源,并增加本地 Gitea SSH 凭据备用源。 让 jenkins-checkout-source.sh 可复用已鉴权的浅克隆结果。 同步生产运维文档和项目共享记忆。
200 lines
7.3 KiB
Bash
200 lines
7.3 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
SOURCE_BRANCH="${SOURCE_BRANCH:-master}"
|
|
COMMIT_HASH="${COMMIT_HASH:-}"
|
|
GIT_REMOTE_URL="${GIT_REMOTE_URL:-}"
|
|
GIT_REMOTE_FALLBACK_URL="${GIT_REMOTE_FALLBACK_URL:-}"
|
|
SOURCE_COMMIT_FILE="${SOURCE_COMMIT_FILE:-.jenkins-source-commit}"
|
|
GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT:-false}"
|
|
|
|
# Windows PowerShell 5.1 的 UTF-8 输出可能带 BOM;下游参数校验前先剥离不可见字节。
|
|
SOURCE_BRANCH="$(printf "%s" "${SOURCE_BRANCH}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')"
|
|
COMMIT_HASH="$(printf "%s" "${COMMIT_HASH}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')"
|
|
GIT_REMOTE_URL="$(printf "%s" "${GIT_REMOTE_URL}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')"
|
|
GIT_REMOTE_FALLBACK_URL="$(printf "%s" "${GIT_REMOTE_FALLBACK_URL}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n')"
|
|
GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT="$(printf "%s" "${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT}" | sed $'s/^\xef\xbb\xbf//' | tr -d '\r\n' | tr '[:upper:]' '[:lower:]')"
|
|
|
|
if [[ ! "${SOURCE_BRANCH}" =~ ^[0-9A-Za-z._/-]+$ ]]; then
|
|
echo "[jenkins-checkout-source] SOURCE_BRANCH 只能包含数字、字母、点、下划线、短横线和斜杠: ${SOURCE_BRANCH}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${SOURCE_BRANCH}" == /* || "${SOURCE_BRANCH}" == */ || "${SOURCE_BRANCH}" == *..* ]]; then
|
|
echo "[jenkins-checkout-source] SOURCE_BRANCH 不能以斜杠开头/结尾,也不能包含连续点号: ${SOURCE_BRANCH}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${COMMIT_HASH}" && ! "${COMMIT_HASH}" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
|
|
echo "[jenkins-checkout-source] COMMIT_HASH 只能填写 7 到 40 位十六进制 Git commit hash: ${COMMIT_HASH}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
GIT_REMOTE_CANDIDATES=()
|
|
add_git_remote_candidate() {
|
|
local candidate="$1"
|
|
local existing
|
|
if [[ -z "${candidate}" ]]; then
|
|
return
|
|
fi
|
|
for existing in "${GIT_REMOTE_CANDIDATES[@]}"; do
|
|
if [[ "${existing}" == "${candidate}" ]]; then
|
|
return
|
|
fi
|
|
done
|
|
GIT_REMOTE_CANDIDATES+=("${candidate}")
|
|
}
|
|
|
|
fetch_source_branch() {
|
|
local remote_url="$1"
|
|
if [[ -n "${remote_url}" ]]; then
|
|
git remote set-url origin "${remote_url}"
|
|
fi
|
|
|
|
echo "[jenkins-checkout-source] 尝试 Git 远端: ${remote_url:-origin}"
|
|
git fetch --no-tags --prune --depth=1 origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
|
|
}
|
|
|
|
is_shallow_repository() {
|
|
[[ "$(git rev-parse --is-shallow-repository 2>/dev/null || echo false)" == "true" ]]
|
|
}
|
|
|
|
resolve_requested_commit_if_on_branch() {
|
|
local requested_commit="$1"
|
|
local resolved_commit
|
|
|
|
if ! git cat-file -e "${requested_commit}^{commit}" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
resolved_commit="$(git rev-parse "${requested_commit}^{commit}")"
|
|
if ! git merge-base --is-ancestor "${resolved_commit}" "refs/remotes/origin/${SOURCE_BRANCH}" 2>/dev/null; then
|
|
return 1
|
|
fi
|
|
|
|
printf "%s\n" "${resolved_commit}"
|
|
}
|
|
|
|
resolve_requested_commit_with_deepen() {
|
|
local requested_commit="$1"
|
|
local deepen_steps_raw="${GENARRATIVE_JENKINS_CHECKOUT_DEEPEN_STEPS:-50 200 1000 5000}"
|
|
local deepen_steps=()
|
|
local deepen_depth
|
|
local resolved_commit
|
|
|
|
# 中文注释:上游构建 commit 通常就是分支 HEAD,先吃浅克隆;确实不是浅历史内提交时再逐步加深。
|
|
if resolved_commit="$(resolve_requested_commit_if_on_branch "${requested_commit}")"; then
|
|
printf "%s\n" "${resolved_commit}"
|
|
return 0
|
|
fi
|
|
|
|
read -r -a deepen_steps <<<"${deepen_steps_raw}"
|
|
for deepen_depth in "${deepen_steps[@]}"; do
|
|
if [[ ! "${deepen_depth}" =~ ^[0-9]+$ || "${deepen_depth}" -le 1 ]]; then
|
|
echo "[jenkins-checkout-source] 忽略无效加深深度: ${deepen_depth}" >&2
|
|
continue
|
|
fi
|
|
|
|
echo "[jenkins-checkout-source] 浅历史未命中 commit=${requested_commit},加深到 depth=${deepen_depth}" >&2
|
|
if is_shallow_repository; then
|
|
git fetch --no-tags --prune --depth="${deepen_depth}" origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
|
|
else
|
|
git fetch --no-tags --prune origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
|
|
fi
|
|
|
|
if resolved_commit="$(resolve_requested_commit_if_on_branch "${requested_commit}")"; then
|
|
printf "%s\n" "${resolved_commit}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
if is_shallow_repository; then
|
|
echo "[jenkins-checkout-source] 逐步加深仍未命中 commit=${requested_commit},最后尝试展开完整历史" >&2
|
|
git fetch --unshallow --no-tags origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}" || \
|
|
git fetch --no-tags --prune origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
|
|
else
|
|
git fetch --no-tags --prune origin "+refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/${SOURCE_BRANCH}"
|
|
fi
|
|
|
|
resolve_requested_commit_if_on_branch "${requested_commit}"
|
|
}
|
|
|
|
# 中文注释:Jenkins GitSCM 已经用凭据完成浅克隆时,优先复用当前 checkout,避免 shell 再次无凭据 fetch 私有仓库。
|
|
resolve_existing_checkout_if_requested() {
|
|
local current_commit
|
|
local branch_commit
|
|
local requested_commit
|
|
|
|
if [[ "${GENARRATIVE_JENKINS_REUSE_EXISTING_CHECKOUT}" != "true" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
current_commit="$(git rev-parse --verify HEAD^{commit} 2>/dev/null)" || return 1
|
|
|
|
if [[ -n "${COMMIT_HASH}" ]]; then
|
|
requested_commit="$(resolve_requested_commit_if_on_branch "${COMMIT_HASH}")" || return 1
|
|
if [[ "${current_commit}" != "${requested_commit}" ]]; then
|
|
return 1
|
|
fi
|
|
printf "%s\n" "${requested_commit}"
|
|
return 0
|
|
fi
|
|
|
|
branch_commit="$(git rev-parse --verify "refs/remotes/origin/${SOURCE_BRANCH}^{commit}" 2>/dev/null)" || return 1
|
|
if [[ "${current_commit}" != "${branch_commit}" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
printf "%s\n" "${current_commit}"
|
|
}
|
|
|
|
add_git_remote_candidate "${GIT_REMOTE_URL}"
|
|
add_git_remote_candidate "${GIT_REMOTE_FALLBACK_URL}"
|
|
|
|
git reset --hard HEAD
|
|
if RESOLVED_COMMIT="$(resolve_existing_checkout_if_requested)"; then
|
|
git checkout --detach "${RESOLVED_COMMIT}"
|
|
git reset --hard HEAD
|
|
git clean -fd
|
|
printf "%s\n" "${RESOLVED_COMMIT}" >"${SOURCE_COMMIT_FILE}"
|
|
echo "[jenkins-checkout-source] 复用 Jenkins GitSCM checkout: branch=${SOURCE_BRANCH} commit=${RESOLVED_COMMIT}"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "${#GIT_REMOTE_CANDIDATES[@]}" -eq 0 ]]; then
|
|
fetch_source_branch ""
|
|
else
|
|
fetch_ok=0
|
|
for git_remote_candidate in "${GIT_REMOTE_CANDIDATES[@]}"; do
|
|
if fetch_source_branch "${git_remote_candidate}"; then
|
|
GIT_REMOTE_URL="${git_remote_candidate}"
|
|
fetch_ok=1
|
|
break
|
|
fi
|
|
echo "[jenkins-checkout-source] Git 远端拉取失败: ${git_remote_candidate}" >&2
|
|
done
|
|
if [[ "${fetch_ok}" -ne 1 ]]; then
|
|
echo "[jenkins-checkout-source] 所有 Git 远端均拉取失败。" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
git cat-file -e "refs/remotes/origin/${SOURCE_BRANCH}^{commit}"
|
|
|
|
if [[ -n "${COMMIT_HASH}" ]]; then
|
|
if ! RESOLVED_COMMIT="$(resolve_requested_commit_with_deepen "${COMMIT_HASH}")"; then
|
|
echo "[jenkins-checkout-source] 指定 commit 不属于 origin/${SOURCE_BRANCH}: ${COMMIT_HASH}" >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
RESOLVED_COMMIT="$(git rev-parse "refs/remotes/origin/${SOURCE_BRANCH}^{commit}")"
|
|
fi
|
|
|
|
git checkout --detach "${RESOLVED_COMMIT}"
|
|
git reset --hard HEAD
|
|
git clean -fd
|
|
|
|
printf "%s\n" "${RESOLVED_COMMIT}" >"${SOURCE_COMMIT_FILE}"
|
|
echo "[jenkins-checkout-source] 使用源码: branch=${SOURCE_BRANCH} commit=${RESOLVED_COMMIT} remote=${GIT_REMOTE_URL:-origin}"
|