feat(gitea): G7 一键接线——新建仓库后本地 init/origin/首推一键完成

绑定完成但本地未连通时(无 .git 或无 origin),Gitea 绑定区显示接线引导:
- 无 git → init + 首次提交 + 关联 origin + 推送
- 有 git 无 origin → 关联 origin + 推送当前分支
复用 publish.rs 存量命令(git_init_and_commit/git_remote_set/git_push),
补齐 GitHub 退役时遗落的"本地接线"半程(接线/放风筝边界裁决的落地)。

Rules-Applied: R01
This commit is contained in:
lanrtop 2026-07-03 10:04:31 +09:00
parent 09410cc35f
commit 4cd941c975
3 changed files with 65 additions and 4 deletions

View File

@ -124,8 +124,8 @@ Gitea push → POST /webhook/gitea/:project_id
- complexity: M
- acceptance: TBD —— remote 切换 + 推送,安全增量,不丢历史
### 📋 G7. 新建仓库后一键接线(本地 init + 首次 push
- status: todo
### G7. 新建仓库后一键接线(本地 init + 首次 push
- status: done
- complexity: M
- depends: G2
- files: src/components/dashboard/GiteaPanel.tsx, src/lib/commands.ts

View File

@ -1372,7 +1372,7 @@ function GovernancePanel({
<AgentInfraPanel projectPath={projectPath} projectId={projectId} />
{/* ── Gitea 集成(统一 git 后端)─────────────────────────── */}
{projectId && <GiteaPanel projectId={projectId} projectName={projectName} />}
{projectId && <GiteaPanel projectId={projectId} projectName={projectName} projectPath={projectPath} />}
{/* 项目信息 */}
<div className="pt-2 border-t border-gray-100">

View File

@ -18,6 +18,10 @@ import {
giteaSetupWebhook,
giteaUnlinkRepo,
giteaPollNow,
scanLocalGit,
gitInitAndCommit,
gitRemoteSet,
gitPush,
type GiteaInstance,
} from "../../lib/commands";
import { useUIStore } from "../../store/ui";
@ -186,9 +190,10 @@ export function GiteaInstancesSection() {
interface Props {
projectId: string;
projectName: string;
projectPath?: string;
}
export function GiteaPanel({ projectId, projectName }: Props) {
export function GiteaPanel({ projectId, projectName, projectPath }: Props) {
const qc = useQueryClient();
const showToast = useUIStore((s) => s.showToast);
@ -207,6 +212,36 @@ export function GiteaPanel({ projectId, projectName }: Props) {
queryFn: giteaListLinkedRepos,
});
// G7 一键接线:绑定后检测本地 git 状态(无 git / 无 origin / 已接线)
const { data: localGit, refetch: refetchLocalGit } = useQuery({
queryKey: ["gitea-local-git", projectId],
queryFn: () => scanLocalGit(projectPath!),
enabled: !!link && !!projectPath,
staleTime: 30000,
retry: false,
});
const hasOrigin = localGit?.remotes.some((r) => r.name === "origin") ?? false;
const needsWiring = !!link && !!projectPath && localGit != null && (!localGit.has_git || !hasOrigin);
const [wiring, setWiring] = useState(false);
const handleWireUp = async () => {
if (!projectPath || !link?.clone_url) return;
setWiring(true);
try {
if (!localGit?.has_git || !localGit?.has_commits) {
await gitInitAndCommit(projectPath, "chore: 项目初始化(炼境接线)");
}
await gitRemoteSet(projectPath, link.clone_url);
const msg = await gitPush(projectPath);
showToast(`✓ 接线完成:${msg}`);
refetchLocalGit();
} catch (e) {
showToast(`❌ 接线失败: ${e}`);
} finally {
setWiring(false);
}
};
const refresh = () => {
qc.invalidateQueries({ queryKey: ["gitea-link", projectId] });
qc.invalidateQueries({ queryKey: ["gitea-linked-repos"] });
@ -326,6 +361,32 @@ export function GiteaPanel({ projectId, projectName }: Props) {
</button>
</div>
{/* G7 本地接线:仓库已建但本地未连通时的一次性引导 */}
{needsWiring && (
<div className="bg-indigo-50 border border-indigo-100 rounded p-2 space-y-1.5">
<p className="text-[11px] text-indigo-700 font-medium">
{!localGit?.has_git ? "本地目录还不是 git 仓库" : "本地仓库尚未关联远端 origin"}
</p>
<div className="flex items-center gap-1.5">
<p className="flex-1 text-[10px] text-indigo-400 leading-relaxed">
{!localGit?.has_git
? "将执行git init + 首次提交 + 关联 origin + 推送"
: "将执行:关联 origin + 推送当前分支"}
</p>
<button
onClick={handleWireUp}
disabled={wiring}
className="px-2.5 py-1 rounded text-[11px] bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-50 shrink-0"
>
{wiring ? "接线中…" : "🔌 一键接线"}
</button>
</div>
<p className="text-[10px] text-indigo-300 leading-relaxed">
线 commit / push AI
</p>
</div>
)}
{/* webhook 状态与注册 */}
<div className="bg-gray-50 rounded p-2 space-y-1.5">
<div className="flex items-center gap-1.5 text-[11px]">