🐛 fix(spx): GITEA_TOKEN 只发给 tea 登录过的 host,origin 不在 tea 里时直接报错

This commit is contained in:
cruldra committed 2026-10-19 08:50:20 +11:00
1 parent e6d086d821
commit 9c7c9d8636
6 files changed
+43 -25

No files matched your search

+8 -7
View File
@@ -51,8 +51,9 @@ func ghAuthToken(host string) (string, error) {
// Open connects to the repo at `o` on the forge its host names, picking credentials the way that forge expects.
//
// GitHub takes the token from `gh auth token`. Gitea takes the token of the tea login for the same host, and its
// URL as the API root; with no such login, or one without a token, it falls back to `GITEA_TOKEN` and the origin's web root.
// GitHub takes the token from `gh auth token`. Gitea needs a [tea.TeaLogin] for the same host and uses its URL as the API
// root and its token; `GITEA_TOKEN` only fills in when that login has no token, so the variable never reaches a host the
// user has not logged in to with tea.
func Open(o repo.Origin, env Env) (Forge, error) {
if KindForHost(o.Host) == KindGitHub {
token, err := env.GitHubToken(o.Host)
@@ -69,15 +70,15 @@ func Open(o repo.Origin, env Env) (Forge, error) {
if err != nil {
return nil, err
}
baseURL, token := o.WebURL, ""
if login != nil {
baseURL, token = login.URL, login.Token
if login == nil {
return nil, fmt.Errorf("tea config 里没有 %s 的 login,spx 不会把 Gitea token 发给 tea 没登录过的 host;请先运行 `tea login add` 登录这个 host", o.Host)
}
token := login.Token
if token == "" {
token = env.Getenv("GITEA_TOKEN")
}
if token == "" {
return nil, fmt.Errorf("没有 %s 的 Gitea token:tea config 里没有该 host 的 login 或其 token 为空,GITEA_TOKEN 也没设置;请运行 `tea login add` 或设置 GITEA_TOKEN", o.Host)
return nil, fmt.Errorf("tea config 里 %s 的 login %q 没有 token,GITEA_TOKEN 也没设置;请重新运行 `tea login add` 或设置 GITEA_TOKEN", o.Host, login.Name)
}
return NewGitea(baseURL, token, o.Owner, o.Repo), nil
return NewGitea(login.URL, token, o.Owner, o.Repo), nil
}
+28 -12
View File
@@ -17,6 +17,7 @@ type fakeEnv struct {
env map[string]string
ghAsked []string
teaAsked []string
envAsked []string
}
func (f *fakeEnv) Env() Env {
@@ -29,7 +30,10 @@ func (f *fakeEnv) Env() Env {
f.teaAsked = append(f.teaAsked, host)
return f.logins[host], nil
},
Getenv: func(key string) string { return f.env[key] },
Getenv: func(key string) string {
f.envAsked = append(f.envAsked, key)
return f.env[key]
},
}
}
@@ -133,22 +137,34 @@ func TestOpenGiteaFallsBackToEnvTokenWhenLoginHasNone(t *testing.T) {
}
}
func TestOpenGiteaWithoutLoginUsesOriginWebURLAndEnvToken(t *testing.T) {
fe := &fakeEnv{env: map[string]string{"GITEA_TOKEN": "env-tok"}}
f, err := Open(giteaOrigin(), fe.Env())
if err != nil {
t.Fatalf("Open 出错: %v", err)
func TestOpenGiteaWithoutLoginForOriginHostNeverReadsEnvToken(t *testing.T) {
fe := &fakeEnv{
logins: map[string]*tea.TeaLogin{
"gitea.example.com": {Name: "work", URL: "https://gitea.example.com", Token: "tea-tok"},
},
env: map[string]string{"GITEA_TOKEN": "env-tok"},
}
rf := f.(*restForge)
if rf.apiBase != "https://gitea.example.com/api/v1" || rf.header.Get("Authorization") != "token env-tok" {
t.Fatalf("没有 tea login 时应用 origin 地址 + GITEA_TOKEN: base=%s auth=%q", rf.apiBase, rf.header.Get("Authorization"))
o := repo.Origin{Host: "gitlab.com", WebURL: "https://gitlab.com", Owner: "acme", Repo: "widgets"}
f, err := Open(o, fe.Env())
if err == nil || f != nil {
t.Fatalf("tea 里没有 origin host 的 login 时应拒绝连接,得到 forge=%v err=%v", f, err)
}
if !strings.Contains(err.Error(), "tea login add") || !strings.Contains(err.Error(), "gitlab.com") {
t.Fatalf("错误应点名 host 并提示先 tea login add,得到 %v", err)
}
if len(fe.envAsked) != 0 {
t.Fatalf("没有同 host 的 login 时不能读 GITEA_TOKEN,免得把它发给别的网站: %v", fe.envAsked)
}
}
func TestOpenGiteaWithoutAnyTokenFails(t *testing.T) {
fe := &fakeEnv{}
func TestOpenGiteaFailsWhenLoginAndEnvHaveNoToken(t *testing.T) {
fe := &fakeEnv{
logins: map[string]*tea.TeaLogin{
"gitea.example.com": {Name: "work", URL: "https://gitea.example.com", Token: ""},
},
}
_, err := Open(giteaOrigin(), fe.Env())
if err == nil || !strings.Contains(err.Error(), "GITEA_TOKEN") || !strings.Contains(err.Error(), "gitea.example.com") {
t.Fatalf("没有 token 时应报错并点名 host 与 GITEA_TOKEN,得到 %v", err)
t.Fatalf("login 和 GITEA_TOKEN 都没有 token 时应报错并点名 host 与 GITEA_TOKEN,得到 %v", err)
}
}
+2 -2
View File
@@ -59,8 +59,8 @@ func load(path string) (*TeaConfig, error) {
//
// Hosts are compared by hostname only, ignoring case, scheme, port and trailing slashes, so the host read from a git
// SSH remote still finds the login saved with the forge's web URL. When several logins point at `host`, the one marked
// `default` wins, because that is the identity the user picked; otherwise the first. A missing config file is not an
// error either, because the caller can still fall back to `GITEA_TOKEN`.
// `default` wins, because that is the identity the user picked; otherwise the first. A missing config file just means
// there is no login, so it returns `nil` rather than an error.
func LoginForHost(path, host string) (*TeaLogin, error) {
cfg, err := load(path)
if errors.Is(err, fs.ErrNotExist) {