diff --git a/.gitea/workflows/prepare.yml b/.gitea/workflows/prepare.yml index 47c4c44..250661f 100644 --- a/.gitea/workflows/prepare.yml +++ b/.gitea/workflows/prepare.yml @@ -17,7 +17,7 @@ jobs: prepare: runs-on: standard-ubuntu-22 steps: - - name: 准备共享仓库和持久 Worktree + - name: 准备共享 bare 仓库 run: | set -euo pipefail @@ -25,7 +25,6 @@ jobs: REPO_URL="${{ github.server_url }}/${{ github.repository }}.git" HEAD_SHA="${{ github.sha }}" REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git" - WORKTREE_ROOT="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees" case "$HEAD_SHA" in ''|*[!0-9a-fA-F]*) @@ -36,11 +35,10 @@ jobs: echo "===== [$(date '+%F %T')] 开始准备共享仓库 =====" echo "共享仓库: $REPOSITORY_DIR" - echo "Worktree 根目录: $WORKTREE_ROOT" echo "仓库地址: $REPO_URL" echo "触发提交: $HEAD_SHA" - mkdir -p "$(dirname "$REPOSITORY_DIR")" "$WORKTREE_ROOT" + mkdir -p "$(dirname "$REPOSITORY_DIR")" if [ ! -d "$REPOSITORY_DIR" ]; then echo "首次使用,初始化 bare 仓库..." @@ -66,36 +64,8 @@ jobs: exit 1 fi - ensure_worktree() { - role=$1 - worktree_dir="${WORKTREE_ROOT}/${role}" - - if [ -e "$worktree_dir" ]; then - if [ ! -f "$worktree_dir/.git" ]; then - echo "Worktree 路径已存在但不是 linked worktree: $worktree_dir" >&2 - return 1 - fi - - common_dir=$(git -C "$worktree_dir" rev-parse --path-format=absolute --git-common-dir) - if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then - echo "Worktree 不属于共享仓库: $worktree_dir" >&2 - return 1 - fi - - echo "复用持久 Worktree: $worktree_dir" - return 0 - fi - - echo "首次创建持久 Worktree: $worktree_dir" - git --git-dir="$REPOSITORY_DIR" worktree add --detach "$worktree_dir" "$HEAD_SHA" - } - - ensure_worktree "print1" - ensure_worktree "print2" - echo "----- 当前共享仓库状态 -----" git --git-dir="$REPOSITORY_DIR" log -1 --oneline "$HEAD_SHA" - git --git-dir="$REPOSITORY_DIR" worktree list echo "===== Prepare 完成 =====" - name: 模拟准备耗时 diff --git a/.gitea/workflows/print1.yml b/.gitea/workflows/print1.yml index 7c15614..75ff91c 100644 --- a/.gitea/workflows/print1.yml +++ b/.gitea/workflows/print1.yml @@ -24,7 +24,9 @@ jobs: REPO_NAME="${{ github.event.repository.name }}" HEAD_SHA="${{ github.event.workflow_run.head_sha }}" + REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git" WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/print1" + WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock" case "$HEAD_SHA" in ''|*[!0-9a-fA-F]*) @@ -32,12 +34,43 @@ jobs: exit 1 ;; esac - if [ ! -f "$WORKTREE_DIR/.git" ]; then - echo "Prepare 未创建 Print One Worktree: $WORKTREE_DIR" >&2 + + if [ ! -d "$REPOSITORY_DIR" ] || \ + [ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository)" != "true" ]; then + echo "Prepare 未创建共享 bare 仓库: $REPOSITORY_DIR" >&2 exit 1 fi - if ! git -C "$WORKTREE_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then - echo "Worktree 共享仓库中不存在上游提交: $HEAD_SHA" >&2 + if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then + echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2 + exit 1 + fi + + if [ ! -f "$WORKTREE_DIR/.git" ]; then + if ! command -v flock >/dev/null 2>&1; then + echo "首次创建 Worktree 需要 flock" >&2 + exit 1 + fi + + exec 9>"$WORKTREE_LOCK" + flock 9 + + if [ ! -f "$WORKTREE_DIR/.git" ]; then + if [ -e "$WORKTREE_DIR" ]; then + echo "Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2 + exit 1 + fi + + mkdir -p "$(dirname "$WORKTREE_DIR")" + echo "首次创建 Print One Worktree: $WORKTREE_DIR" + git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA" + fi + + flock -u 9 + fi + + COMMON_DIR=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir) + if [ "$COMMON_DIR" != "$(realpath "$REPOSITORY_DIR")" ]; then + echo "Print One Worktree 不属于共享仓库: $WORKTREE_DIR" >&2 exit 1 fi diff --git a/.gitea/workflows/print2.yml b/.gitea/workflows/print2.yml index 69be98e..3fc7502 100644 --- a/.gitea/workflows/print2.yml +++ b/.gitea/workflows/print2.yml @@ -24,7 +24,9 @@ jobs: REPO_NAME="${{ github.event.repository.name }}" HEAD_SHA="${{ github.event.workflow_run.head_sha }}" + REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git" WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/print2" + WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock" case "$HEAD_SHA" in ''|*[!0-9a-fA-F]*) @@ -32,12 +34,43 @@ jobs: exit 1 ;; esac - if [ ! -f "$WORKTREE_DIR/.git" ]; then - echo "Prepare 未创建 Print Two Worktree: $WORKTREE_DIR" >&2 + + if [ ! -d "$REPOSITORY_DIR" ] || \ + [ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository)" != "true" ]; then + echo "Prepare 未创建共享 bare 仓库: $REPOSITORY_DIR" >&2 exit 1 fi - if ! git -C "$WORKTREE_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then - echo "Worktree 共享仓库中不存在上游提交: $HEAD_SHA" >&2 + if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then + echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2 + exit 1 + fi + + if [ ! -f "$WORKTREE_DIR/.git" ]; then + if ! command -v flock >/dev/null 2>&1; then + echo "首次创建 Worktree 需要 flock" >&2 + exit 1 + fi + + exec 9>"$WORKTREE_LOCK" + flock 9 + + if [ ! -f "$WORKTREE_DIR/.git" ]; then + if [ -e "$WORKTREE_DIR" ]; then + echo "Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2 + exit 1 + fi + + mkdir -p "$(dirname "$WORKTREE_DIR")" + echo "首次创建 Print Two Worktree: $WORKTREE_DIR" + git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA" + fi + + flock -u 9 + fi + + COMMON_DIR=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir) + if [ "$COMMON_DIR" != "$(realpath "$REPOSITORY_DIR")" ]; then + echo "Print Two Worktree 不属于共享仓库: $WORKTREE_DIR" >&2 exit 1 fi diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 643d9d5..7065f3b 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -21,12 +21,11 @@ class WorkflowPipelineTest(unittest.TestCase): self.assertNotIn("/tmp/reused-repo", workflow) self.assertNotIn("git clone", workflow) - def test_prepare_creates_persistent_print_worktrees(self) -> None: + def test_prepare_leaves_worktree_ownership_to_consumers(self) -> None: workflow = self.read_workflow("prepare.yml") - self.assertIn('ensure_worktree "print1"', workflow) - self.assertIn('ensure_worktree "print2"', workflow) - self.assertIn('worktree add --detach "$worktree_dir" "$HEAD_SHA"', workflow) + self.assertNotIn("ensure_worktree", workflow) + self.assertNotIn("worktree add", workflow) self.assertNotIn("worktree remove", workflow) def assert_print_workflow(self, filename: str, role: str) -> None: @@ -37,7 +36,16 @@ class WorkflowPipelineTest(unittest.TestCase): self.assertIn(f'group: {role}-${{{{ github.repository }}}}', workflow) self.assertIn('cancel-in-progress: false', workflow) self.assertIn('HEAD_SHA="${{ github.event.workflow_run.head_sha }}"', workflow) + self.assertIn('REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"', workflow) self.assertIn(f'WORKTREE_DIR="${{WORKSPACE_ROOT}}/${{REPO_NAME}}/worktrees/{role}"', workflow) + self.assertIn('WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"', workflow) + self.assertGreaterEqual(workflow.count('if [ ! -f "$WORKTREE_DIR/.git" ]; then'), 2) + self.assertIn('exec 9>"$WORKTREE_LOCK"', workflow) + self.assertIn("flock 9", workflow) + self.assertIn( + 'git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"', + workflow, + ) self.assertIn('git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"', workflow) self.assertIn('git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"', workflow) self.assertIn('git -C "$WORKTREE_DIR" clean -ffdx', workflow)