NIP-34: Git 相關事件
在 Nostr 上進行 Git 程式碼協作、議題討論和補丁提交
概述
NIP-34 定義了一系列用於 Git 協作的 Nostr 事件類型。開發者可以在 Nostr 上 建立程式碼倉庫、提交補丁、開設議題,實現去中心化的程式碼協作, 無需依賴 GitHub 等中心化平台。
事件類型
| Kind | 說明 | 類型 |
|---|---|---|
30617 | 倉庫公告 (Repository) | 可定址 |
1617 | 補丁 (Patches) | 一般 |
1621 | 議題 (Issues) | 一般 |
1622 | 回覆 (Reply) | 一般 |
1630-1633 | 狀態 (Status) | 一般 |
倉庫公告 (Kind 30617)
倉庫公告事件宣布一個 Git 倉庫的存在,包含基本資訊和可選的克隆 URL。
標籤
| 標籤 | 說明 | 範例 |
|---|---|---|
d | 倉庫識別符 | ["d", "my-project"] |
name | 倉庫名稱 | ["name", "My Project"] |
description | 倉庫描述 | ["description", "一個很棒的專案"] |
clone | 克隆 URL | ["clone", "https://git.example.com/repo.git"] |
web | 網頁瀏覽 URL | ["web", "https://git.example.com/repo"] |
relays | 相關中繼器 | ["relays", "wss://relay1.com", "wss://relay2.com"] |
maintainers | 維護者公鑰 | ["maintainers", "pubkey1", "pubkey2"] |
範例
{
"kind": 30617,
"content": "",
"tags": [
["d", "nostr-client"],
["name", "Nostr 客戶端"],
["description", "一個開源的 Nostr 客戶端應用"],
["clone", "https://github.com/example/nostr-client.git"],
["web", "https://github.com/example/nostr-client"],
["relays", "wss://relay.damus.io", "wss://nos.lol"],
["maintainers", "npub1xxx...", "npub1yyy..."],
["t", "nostr"],
["t", "typescript"]
]
} 補丁 (Kind 1617)
補丁事件包含要合併到倉庫的程式碼變更,類似 Pull Request。
標籤
| 標籤 | 說明 |
|---|---|
a | 目標倉庫的事件座標 |
p | 倉庫維護者公鑰 |
t | 主題標籤 |
commit | 提交資訊 |
parent-commit | 基於的父提交 |
Content 格式
Content 包含標準的 Git patch 格式:
From abc123 Mon Sep 17 00:00:00 2001
From: Author <[email protected]>
Date: Mon, 1 Jan 2024 12:00:00 +0800
Subject: [PATCH] 修復登入問題
修復了用戶無法登入的問題
---
src/auth.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/auth.ts b/src/auth.ts
index abc123..def456 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -10,7 +10,7 @@
- const token = getToken();
+ const token = await getToken();
議題 (Kind 1621)
議題事件用於報告 bug、提出功能請求或進行討論。
標籤
| 標籤 | 說明 |
|---|---|
a | 目標倉庫的事件座標 |
p | 倉庫維護者公鑰 |
subject | 議題標題 |
t | 標籤(bug、feature 等) |
範例
{
"kind": 1621,
"content": "## 問題描述\n\n在 iOS 上無法正常顯示圖片\n\n## 重現步驟\n\n1. 打開應用\n2. 瀏覽到圖片貼文\n3. 圖片無法載入\n\n## 預期行為\n\n圖片應該正常顯示",
"tags": [
["a", "30617:pubkey:nostr-client", "wss://relay.example.com"],
["p", "maintainer-pubkey"],
["subject", "iOS 圖片無法顯示"],
["t", "bug"],
["t", "ios"]
]
} 狀態事件 (Kind 1630-1633)
用於標記補丁或議題的狀態:
| Kind | 狀態 | 說明 |
|---|---|---|
1630 | Open | 開啟/待處理 |
1631 | Applied/Closed | 已合併/已關閉 |
1632 | Closed | 已關閉(未合併) |
1633 | Draft | 草稿 |
TypeScript 實作
建立倉庫公告
import { finalizeEvent } from 'nostr-tools';
interface Repository {
id: string;
name: string;
description?: string;
cloneUrls?: string[];
webUrl?: string;
relays?: string[];
maintainers?: string[];
tags?: string[];
}
function createRepositoryEvent(
repo: Repository,
secretKey: Uint8Array
) {
const tags: string[][] = [
['d', repo.id],
['name', repo.name],
];
if (repo.description) {
tags.push(['description', repo.description]);
}
repo.cloneUrls?.forEach((url) => {
tags.push(['clone', url]);
});
if (repo.webUrl) {
tags.push(['web', repo.webUrl]);
}
if (repo.relays && repo.relays.length > 0) {
tags.push(['relays', ...repo.relays]);
}
if (repo.maintainers && repo.maintainers.length > 0) {
tags.push(['maintainers', ...repo.maintainers]);
}
repo.tags?.forEach((tag) => {
tags.push(['t', tag]);
});
const event = {
kind: 30617,
content: '',
tags,
created_at: Math.floor(Date.now() / 1000),
};
return finalizeEvent(event, secretKey);
}
// 使用範例
const repoEvent = createRepositoryEvent(
{
id: 'my-nostr-app',
name: 'My Nostr App',
description: '一個去中心化的社交應用',
cloneUrls: ['https://github.com/user/my-nostr-app.git'],
webUrl: 'https://github.com/user/my-nostr-app',
relays: ['wss://relay.damus.io'],
maintainers: ['npub1...'],
tags: ['nostr', 'typescript', 'react'],
},
secretKey
); 建立議題
interface Issue {
repoCoordinate: string; // "30617:pubkey:repo-id"
repoRelay?: string;
maintainerPubkey: string;
subject: string;
content: string;
labels?: string[];
}
function createIssueEvent(
issue: Issue,
secretKey: Uint8Array
) {
const tags: string[][] = [
['a', issue.repoCoordinate, issue.repoRelay || ''],
['p', issue.maintainerPubkey],
['subject', issue.subject],
];
issue.labels?.forEach((label) => {
tags.push(['t', label]);
});
const event = {
kind: 1621,
content: issue.content,
tags,
created_at: Math.floor(Date.now() / 1000),
};
return finalizeEvent(event, secretKey);
}
// 使用範例
const issueEvent = createIssueEvent(
{
repoCoordinate: '30617:abc123:my-nostr-app',
repoRelay: 'wss://relay.damus.io',
maintainerPubkey: 'maintainer-pubkey',
subject: '新增深色模式支援',
content: '## 功能請求\n\n希望能新增深色模式,保護眼睛。',
labels: ['feature', 'ui'],
},
secretKey
); 建立補丁
interface Patch {
repoCoordinate: string;
repoRelay?: string;
maintainerPubkey: string;
patchContent: string; // Git patch 格式
commit?: string;
parentCommit?: string;
subject?: string;
}
function createPatchEvent(
patch: Patch,
secretKey: Uint8Array
) {
const tags: string[][] = [
['a', patch.repoCoordinate, patch.repoRelay || ''],
['p', patch.maintainerPubkey],
];
if (patch.commit) {
tags.push(['commit', patch.commit]);
}
if (patch.parentCommit) {
tags.push(['parent-commit', patch.parentCommit]);
}
if (patch.subject) {
tags.push(['subject', patch.subject]);
}
const event = {
kind: 1617,
content: patch.patchContent,
tags,
created_at: Math.floor(Date.now() / 1000),
};
return finalizeEvent(event, secretKey);
} 更新狀態
type StatusKind = 1630 | 1631 | 1632 | 1633;
interface StatusUpdate {
targetEventId: string;
kind: StatusKind;
reason?: string;
}
function createStatusEvent(
status: StatusUpdate,
secretKey: Uint8Array
) {
const tags: string[][] = [
['e', status.targetEventId],
];
const event = {
kind: status.kind,
content: status.reason || '',
tags,
created_at: Math.floor(Date.now() / 1000),
};
return finalizeEvent(event, secretKey);
}
// 使用範例:關閉議題
const closeStatus = createStatusEvent(
{
targetEventId: 'issue-event-id',
kind: 1631,
reason: '已在 commit abc123 中修復',
},
secretKey
); 查詢倉庫相關事件
import { SimplePool } from 'nostr-tools';
async function getRepositoryActivity(
pool: SimplePool,
relays: string[],
repoCoordinate: string
) {
// 查詢議題
const issues = await pool.querySync(relays, {
kinds: [1621],
'#a': [repoCoordinate],
});
// 查詢補丁
const patches = await pool.querySync(relays, {
kinds: [1617],
'#a': [repoCoordinate],
});
// 查詢狀態更新
const issueIds = issues.map((i) => i.id);
const patchIds = patches.map((p) => p.id);
const allIds = [...issueIds, ...patchIds];
const statuses = await pool.querySync(relays, {
kinds: [1630, 1631, 1632, 1633],
'#e': allIds,
});
return {
issues,
patches,
statuses,
};
} 使用場景
去中心化程式碼協作
- 在 Nostr 上宣布開源專案
- 接收社群貢獻的補丁
- 討論功能和 bug
抗審查的程式碼托管
- 不依賴 GitHub 等中心化平台
- 程式碼相關討論不會被刪除
- 全球開發者協作
整合 Git 工作流程
- 透過 Nostr 發送和接收補丁
- 程式碼審查和討論
- 追蹤議題狀態
最佳實踐
- 完整的倉庫資訊:提供克隆 URL 和網頁連結
- 標準補丁格式:使用 git format-patch 產生的格式
- 清晰的議題描述:包含重現步驟和預期行為
- 及時更新狀態:合併或關閉時更新狀態事件
- 標籤分類:使用標籤幫助分類和搜尋
相關 NIPs
參考資源
- NIP-34 規範
- Git Workshop - Nostr Git 客戶端
- nostr-tools
已複製連結