229 lines
14 KiB
Markdown
229 lines
14 KiB
Markdown
---
|
||
name: code-sync
|
||
description: 透過 `GITEA_TOKEN` 取得使用者有讀取權限的所有 Gitea 專案,依擁有者(owner)分組後列出讓使用者多選,再把選定擁有者底下有讀取權限的專案,於目標目錄(預設家目錄)依擁有者建立資料夾並逐一 clone;若專案已存在,則切換到 develop(再退而 master)並更新到最新。當使用者說同步 Gitea 專案、批次 clone、把某些擁有者的 repo 全抓下來、依擁有者分類 clone、批次更新本機專案到最新時觸發。不適用於:建立/刪除遠端 repo、單一專案的 clone(直接用 git 即可)、非 Gitea 的來源,或需要 push/改動遠端的操作。
|
||
argument-hint: "[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>] [--host <gitea 主機>] [--include-forks] [--ssh] [--yes]"
|
||
---
|
||
|
||
# code-sync — 依擁有者批次 clone/更新 Gitea 專案
|
||
|
||
五階段 skill:先做**前置設定**(解析 token、gitea 主機、目標根目錄),再**取得使用者有讀取權限的所有專案並依擁有者分組**,接著**列出所有擁有者讓使用者多選**,然後**列出選定擁有者底下有讀取權限的專案**,最後在目標根目錄**依擁有者建立資料夾並逐一 clone**;專案已存在時改為**切換到 develop(再退而 master)並更新到最新**。
|
||
|
||
| 階段 | 動作 |
|
||
| --- | --- |
|
||
| A. 前置設定 | 確認 `GITEA_TOKEN` → 決定 gitea 主機(`--host`/`$GITEA_HOST`/目前 repo 的 origin/詢問)→ 決定目標根目錄(`--target-dir`,預設家目錄) |
|
||
| B. 取得可讀專案 | 以 token 呼叫 Gitea API 分頁取回使用者有讀取(`pull`)權限的所有專案 → 依 `owner.login` 分組 |
|
||
| C. 選擇擁有者 | 列出所有擁有者(含各自專案數)→ 讓使用者**多選**(`--owner` 已指定則沿用,不再詢問) |
|
||
| D. 列出選定專案 | 列出選定擁有者底下所有有讀取權限的專案(含是否已存在於本機) |
|
||
| E. Clone/更新 | 依擁有者建立資料夾 → 不存在則 clone;已存在則 `fetch` → 切到 develop(再退而 master)→ `pull` 更新到最新 |
|
||
|
||
---
|
||
|
||
## 輸出規範(務必遵守)
|
||
|
||
- **語言**:所有面向使用者的輸出(擁有者清單、專案清單、進度、總結、反問)一律使用**繁體中文(台灣用語)**;僅識別字、檔名、git/curl 指令、API 路徑等技術標識保留原文,**不可**使用簡體字。
|
||
- **編碼無亂碼**:凡輸出含繁體中文、全形標點、emoji,一律 **UTF-8(不含 BOM)**,不得出現問號方框或錯碼。
|
||
- **Token 機密保護(極重要)**:gitea token 一律**從環境變數讀取**(如 `$GITEA_TOKEN`),**絕不**寫死在 skill、log 或任何輸出;**不可** echo 含 token 的指令或 URL。所有顯示給使用者的指令/錯誤訊息都要**遮蔽 token**(如以 `***` 取代)。clone 時帶 token 的 URL 用變數帶入、**不可印出**,且 clone 完成後要把 origin 還原成不含 token 的乾淨 URL(見 E3),避免 token 落地在 `.git/config`。
|
||
- **自動執行原則**:除非使用者明確要求先確認,或遇到不可忽略的必要決策(例如缺 token、無法決定 gitea 主機、目標目錄不可寫、單一專案更新時工作區有未提交變更需使用者裁示),否則各階段只需輸出簡短計畫/進度後直接執行到完成。**唯一一定會中斷的點是階段 C 的擁有者多選**(除非已帶 `--owner`)。
|
||
- **不破壞既有工作**:更新既有專案時若工作區有未提交變更而導致切換/pull 失敗,**停止該專案的更新並回報**,請使用者自行處理;**絕不**強制丟棄(不可 `reset --hard`/`checkout -f`/`clean`)。
|
||
- **唯讀本意**:本 skill 只做 clone 與本機分支更新,**不 push、不改遠端、不刪本機未追蹤檔**。
|
||
|
||
---
|
||
|
||
## 參數
|
||
|
||
格式:`[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>] [--host <gitea 主機>] [--include-forks] [--ssh] [--yes]`
|
||
|
||
- `--target-dir <根目錄>`:clone 的根目錄。**省略時預設家目錄**(`$HOME`,Windows 為 `%USERPROFILE%`)。實際每個專案會 clone 到 `<根目錄>/<擁有者>/<專案名>`。
|
||
- `--owner <擁有者,以逗號分隔>`:預先指定要同步的擁有者(可多個,以逗號分隔,例如 `plugins,jeffery`)。**帶此參數時跳過階段 C 的多選詢問**,直接同步這些擁有者;若指定的擁有者不在可讀清單內,回報並略過該擁有者。
|
||
- `--host <gitea 主機>`:gitea 主機(如 `gitea.jsc.idv.tw`)。省略時依 A2 規則決定。
|
||
- `--include-forks`:一併納入 fork 來的專案;**省略時預設排除 fork**(`fork=true` 的 repo 不列入)。
|
||
- `--ssh`:改用 SSH(`ssh_url`)clone,不帶 token;**省略時預設用 HTTPS+token**。
|
||
- `--yes`:全自動。即使未帶此參數,也依「自動執行原則」盡量不中斷;但階段 C 的擁有者多選仍會詢問(除非已帶 `--owner`)。
|
||
|
||
---
|
||
|
||
## 階段 A:前置設定
|
||
|
||
### A1. 確認 token
|
||
|
||
讀取環境變數 `GITEA_TOKEN`(或助理慣用的等價變數名)。
|
||
|
||
```bash
|
||
[ -n "${GITEA_TOKEN}" ] && echo "GITEA_TOKEN 已設定" || echo "GITEA_TOKEN 未設定"
|
||
```
|
||
|
||
- **未設定** → 回報「缺少 `GITEA_TOKEN` 環境變數,無法存取 Gitea API」並停止;提示使用者於環境變數提供 token(不要請使用者把 token 貼進對話)。
|
||
- **不可** echo token 本身,只確認是否存在。
|
||
|
||
### A2. 決定 gitea 主機
|
||
|
||
依序決定主機(取第一個成功者):
|
||
|
||
1. 帶 `--host <主機>` → 直接採用。
|
||
2. 否則讀環境變數 `$GITEA_HOST`(若有)。
|
||
3. 否則若**目前工作目錄是 git repo** 且 `git remote get-url origin` 指向某 gitea 主機 → 取該 host。
|
||
4. 以上皆無 → **詢問使用者** gitea 主機,**不臆測**。
|
||
|
||
主機僅取 host 部分(如 `gitea.jsc.idv.tw`),組 API base 為 `https://<host>/api/v1`。
|
||
|
||
### A3. 決定目標根目錄
|
||
|
||
- 帶 `--target-dir <根目錄>` → 採用(可含 `~`,需展開為實際家目錄)。
|
||
- 省略 → 預設家目錄(`$HOME`)。
|
||
- 確認根目錄存在且可寫;不存在時先建立(`mkdir -p`)。若無法建立或不可寫,回報並停止。
|
||
|
||
輸出本次設定摘要(主機、根目錄、是否含 fork、HTTPS/SSH),再進入階段 B。
|
||
|
||
---
|
||
|
||
## 階段 B:取得使用者有讀取權限的所有專案並依擁有者分組
|
||
|
||
### B1. 分頁取回可讀專案
|
||
|
||
以 token 呼叫 `GET /api/v1/user/repos`(回傳 token 對應使用者**有權限存取**的 repo:自有、組織成員、協作者),**逐頁抓到取回筆數不足一頁為止**(每頁上限以 API 為準,常見 `limit=50`):
|
||
|
||
```bash
|
||
# 不可印出含 token 的指令;token 以 Header 帶入,輸出時遮蔽
|
||
page=1
|
||
curl -sS \
|
||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||
"https://<host>/api/v1/user/repos?page=${page}&limit=50"
|
||
```
|
||
|
||
- 持續累加 `page` 直到某頁回傳筆數 `< limit`(或回空陣列)為止,確保**取得全部**而非只第一頁。
|
||
- API 失敗(401/403/網路錯誤)→ 回報錯誤(**遮蔽 token**)並停止;401/403 多半是 token 失效或權限不足。
|
||
|
||
### B2. 篩選與正規化
|
||
|
||
每筆 repo 取下列欄位:`owner.login`、`name`、`full_name`、`clone_url`、`ssh_url`、`permissions.pull`、`fork`、`default_branch`。
|
||
|
||
- **只保留 `permissions.pull == true`** 的專案(確有讀取權限)。
|
||
- **預設排除 `fork == true`**;帶 `--include-forks` 才納入。
|
||
- 去除重複(以 `full_name` 為鍵)。
|
||
|
||
### B3. 依擁有者分組
|
||
|
||
以 `owner.login` 分組,統計每個擁有者底下的可讀專案數,並依擁有者名稱排序(專案數多者可優先列出)。**無任何可讀專案** → 回報並結束。
|
||
|
||
---
|
||
|
||
## 階段 C:列出所有擁有者讓使用者多選
|
||
|
||
輸出一張「擁有者清單」表,含編號、擁有者、可讀專案數:
|
||
|
||
| # | 擁有者 | 可讀專案數 |
|
||
| --- | --- | --- |
|
||
|
||
選擇方式:
|
||
|
||
- **帶 `--owner` 參數** → 直接採用其中的擁有者,**跳過詢問**;清單中沒有的擁有者回報並略過。
|
||
- **未帶 `--owner`** → 這是**不可忽略的必要決策**,必須讓使用者**多選**:
|
||
- 擁有者數量 ≤ 4 時,可用助理的多選提問元件(如 Claude Code 的多選問題)。
|
||
- 擁有者較多時,請使用者直接以**編號或擁有者名稱**回覆(支援多個,以逗號或空白分隔),並支援回覆 `all`/`全部` 代表全選。
|
||
- 解析使用者回覆為擁有者集合;無法對應的輸入請回報並請使用者重選,**不臆測**。
|
||
|
||
選定後輸出「將同步的擁有者:…」再進入階段 D。
|
||
|
||
---
|
||
|
||
## 階段 D:列出選定擁有者底下有讀取權限的專案
|
||
|
||
對每個選定擁有者,列出其底下所有可讀專案,並標示本機是否已存在(`<根目錄>/<擁有者>/<專案名>/.git` 是否存在):
|
||
|
||
| 擁有者 | 專案 | 狀態 |
|
||
| --- | --- | --- |
|
||
| | | 🆕 將 clone/♻️ 已存在將更新 |
|
||
|
||
輸出總計(將 clone N 個、將更新 M 個),除非使用者要求先確認,否則直接進入階段 E 執行。
|
||
|
||
---
|
||
|
||
## 階段 E:依擁有者建立資料夾並逐一 clone/更新
|
||
|
||
對選定擁有者底下的每個專案,依序處理(一次一個,互不干擾):
|
||
|
||
### E1. 建立擁有者資料夾
|
||
|
||
```bash
|
||
mkdir -p "<根目錄>/<擁有者>"
|
||
```
|
||
|
||
專案目標路徑為 `<根目錄>/<擁有者>/<專案名>`。
|
||
|
||
### E2. 專案不存在 → clone
|
||
|
||
- **HTTPS(預設)**:以 token 組 clone URL,**用變數帶入、不可印出**;clone 完成後**還原乾淨 origin**:
|
||
|
||
```bash
|
||
dest="<根目錄>/<擁有者>/<專案名>"
|
||
# GITEA_TOKEN 來自環境變數;clone_url 形如 https://<host>/<owner>/<repo>.git
|
||
git clone "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" "${dest}"
|
||
# 還原成不含 token 的乾淨 URL,避免 token 落地在 .git/config
|
||
git -C "${dest}" remote set-url origin "https://<host>/<owner>/<repo>.git"
|
||
```
|
||
|
||
- **SSH(`--ssh`)**:直接用 `ssh_url`,不帶 token:
|
||
|
||
```bash
|
||
git clone "<ssh_url>" "<根目錄>/<擁有者>/<專案名>"
|
||
```
|
||
|
||
- clone 後若遠端有 `develop`,將工作分支切到 `develop`(沒有則維持 `default_branch`/`master`);clone 失敗回報原因(**遮蔽 token**)並繼續下一個專案,不中斷整批。
|
||
|
||
### E3. 專案已存在 → 切到 develop(再退而 master)並更新到最新
|
||
|
||
先盤點該專案狀態,**有未提交變更則不強切**:
|
||
|
||
```bash
|
||
dest="<根目錄>/<擁有者>/<專案名>"
|
||
git -C "${dest}" rev-parse --is-inside-work-tree # 確認是 git repo
|
||
git -C "${dest}" status --porcelain # 是否乾淨
|
||
git -C "${dest}" fetch --all --prune
|
||
```
|
||
|
||
- **目錄存在但不是 git repo**(無 `.git`)→ 回報「目標已存在且非 git repo,略過」並繼續下一個;**不刪除、不覆蓋**。
|
||
- **工作區有未提交變更**(`status --porcelain` 非空)→ 回報「有未提交變更,略過更新以免覆蓋」並繼續下一個;**絕不**強制丟棄。
|
||
- **工作區乾淨** → 依序選擇要更新的分支並切換、拉取:
|
||
|
||
```bash
|
||
if git -C "${dest}" rev-parse --verify --quiet origin/develop; then
|
||
git -C "${dest}" switch develop 2>/dev/null || git -C "${dest}" switch -c develop --track origin/develop
|
||
elif git -C "${dest}" rev-parse --verify --quiet origin/master; then
|
||
git -C "${dest}" switch master 2>/dev/null || git -C "${dest}" switch -c master --track origin/master
|
||
fi
|
||
git -C "${dest}" pull --ff-only
|
||
```
|
||
|
||
- `origin/develop` 存在 → 切到 `develop`;否則 `origin/master` 存在 → 切到 `master`;兩者都不存在 → 回報「找不到 develop/master」並維持原分支、略過該專案。
|
||
- `pull --ff-only` 無法快進(本地與遠端分歧)→ 回報該專案需人工處理(**不**自動 merge/rebase/reset),繼續下一個。
|
||
- 已在目標分支時直接 `pull --ff-only` 更新即可。
|
||
|
||
### E4. 逐一處理、彙整結果
|
||
|
||
- 每個專案處理完記錄結果:🆕 已 clone/♻️ 已更新到最新/⏭️ 已略過(原因)/❌ 失敗(原因,遮蔽 token)。
|
||
- 單一專案失敗或略過**不應中斷整批**;全部處理完再輸出總結。
|
||
|
||
---
|
||
|
||
## 總結
|
||
|
||
各階段執行後輸出:
|
||
|
||
- **階段 A**:使用的 gitea 主機、目標根目錄、是否含 fork、HTTPS/SSH。
|
||
- **階段 B/C**:可讀專案總數、擁有者總數、使用者選定的擁有者。
|
||
- **階段 D/E**:本批處理結果統計 —— 新 clone N 個、更新 M 個、略過 K 個(列原因)、失敗 J 個(列原因,遮蔽 token),並列出各專案的本機路徑。
|
||
- 若過程曾在指令中帶入 token,提醒確認輸出與 log 無明文 token。
|
||
|
||
---
|
||
|
||
## 呼叫方式
|
||
|
||
格式:`[--target-dir <根目錄>] [--owner <擁有者,以逗號分隔>] [--host <gitea 主機>] [--include-forks] [--ssh] [--yes]` —
|
||
全部可省略(根目錄預設家目錄;未帶 `--owner` 時於階段 C 詢問多選)。token 一律由環境變數(如 `GITEA_TOKEN`)提供,不寫死、不印出。
|
||
|
||
| 助理 | 呼叫 |
|
||
| --- | --- |
|
||
| Claude Code / Antigravity | `/jsc:code-sync`,或 `/jsc:code-sync --owner plugins --target-dir ~/work`、`/jsc:code-sync --include-forks --ssh` |
|
||
| Codex | `$code-sync`,或 `$code-sync --owner plugins --target-dir ~/work`,或用 `/skills` 選單 |
|
||
| OpenCode | 描述需求(如「用 GITEA_TOKEN 取得我有讀取權限的所有 Gitea 專案,依擁有者分組讓我多選,把選定擁有者的專案 clone 到家目錄;已存在的切到 develop/master 更新到最新」)自動觸發 |
|