feat(repo): validate create templates, publish local projects, verify edits

repo create --license NOPE returned Gitea's HTTP 500 internals. License,
.gitignore and label templates are now checked against the server's
lists, case-insensitively, and complete with Tab. --source DIR adds the
new repository as a remote of a local Git repository and --push pushes
its current branch; --public is the explicit opposite of --private, and
-d/-g/-l and the new alias follow gh. --trust-model now offers the names
Gitea accepts. A prompted name is confirmed with the visibility, owner
and host, and creation reports "✓ Created repository ..." and the URL.

repo edit --default-branch nope exited 0 without changing anything; the
branch must now exist. Edit gains --visibility, --enable-issues/-wiki/
-pull-requests/-projects, --allow-merge-commit/-squash/-rebase,
--delete-branch-on-merge, --default-merge-style and --add-topic/
--remove-topic, and reports which settings it changed.

repo migrate names the repository after the source URL, validates
--service before prompting, and reports the import with the URL.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 23:51:49 +02:00
1 parent 368de47c40
commit 682c9f66a2
9 files changed
+742 -308

No files matched your search

+67
View File
@@ -62,3 +62,70 @@ func TestRepositoryCreateClonesNewRepository(t *testing.T) {
require.Equal(t, []string{call}, runner.calls)
require.JSONEq(t, `{"id":7,"full_name":"alice/project"}`, out.String())
}
func TestRepositoryCreateValidatesTemplatesBeforeCreating(t *testing.T) {
var requests []string
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests = append(requests, r.Method+" "+r.URL.Path)
switch r.URL.Path {
case "/api/v1/licenses":
_, _ = w.Write([]byte(`[{"key":"MIT","name":"MIT"},{"key":"Apache-2.0","name":"Apache-2.0"}]`))
case "/api/v1/gitignore/templates":
_, _ = w.Write([]byte(`["Go","Node"]`))
case "/api/v1/user/repos":
requirePayload(t, `{"name":"project","auto_init":true,"license":"MIT","gitignores":"Go,Node"}`, r.Body)
_, _ = w.Write([]byte(`{"full_name":"alice/project","html_url":"https://gitea.test/alice/project"}`))
}
}))
defer server.Close()
deps, out, diagnostics := repoTTYDeps(t, server.URL)
err := Execute(context.Background(), deps, []string{"repo", "create", "project", "--auto-init", "--license", "NOPE"})
require.Equal(t, clierr.Usage, clierr.ExitCode(err), "%v", err)
require.ErrorContains(t, err, `unknown license "NOPE"`)
require.NotContains(t, requests, "POST /api/v1/user/repos")
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "new", "project", "--auto-init", "-l", "mit", "-g", "go,Node"}))
require.Equal(t, "https://gitea.test/alice/project\n", out.String())
require.Equal(t, "✓ Created repository alice/project\n", diagnostics.String())
}
func TestRepositoryCreatePublishesALocalRepository(t *testing.T) {
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requirePayload(t, `{"name":"project"}`, r.Body)
_, _ = fmt.Fprintf(w, `{"full_name":"alice/project","html_url":"https://gitea.test/alice/project","clone_url":"http://%s/alice/project.git"}`, r.Host)
}))
defer server.Close()
deps, _, diagnostics := repoTTYDeps(t, server.URL)
dir := t.TempDir()
remote := server.URL + "/alice/project.git"
push := credentialCall(t, deps, server.URL, "alice", "push", "--quiet", "--set-upstream", "origin", "HEAD")
runner := &fakeGit{results: map[string]string{"rev-parse --is-inside-work-tree": "true", "remote add -- origin " + remote: "", push: ""}}
deps.Git = runner
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "create", "project", "--public", "--source", dir, "--push"}))
require.Equal(t, []string{"rev-parse --is-inside-work-tree", "remote get-url origin", "remote add -- origin " + remote, push}, runner.calls)
require.Equal(t, []string{dir, dir, dir, dir}, runner.dirs)
require.Equal(t, "✓ Created repository alice/project\n✓ Added Git remote origin for alice/project\n✓ Pushed the current branch to alice/project\n", diagnostics.String())
err := Execute(context.Background(), deps, []string{"repo", "create", "project", "--source", dir, "--auto-init"})
require.ErrorContains(t, err, "--source publishes a local repository")
}
func TestRepositoryMigrateNamesTheRepositoryAfterItsSource(t *testing.T) {
for _, test := range []struct{ source, want string }{
{"https://github.com/go-gitea/tea.git", "tea"},
{"https://example.com/group/project/", "project"},
{"/srv/git/legacy.git", "legacy"},
{"https://example.com/", ""},
} {
require.Equal(t, test.want, migrationName(test.source), test.source)
}
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requirePayload(t, `{"repo_name":"tea","clone_addr":"https://github.com/go-gitea/tea.git","service":"git","mirror":true,"mirror_interval":"8h"}`, r.Body)
_, _ = w.Write([]byte(`{"full_name":"alice/tea","mirror":true,"html_url":"https://gitea.test/alice/tea"}`))
}))
defer server.Close()
deps, out, diagnostics := repoTTYDeps(t, server.URL)
require.NoError(t, Execute(context.Background(), deps, []string{"repo", "migrate", "https://github.com/go-gitea/tea.git", "--mirror", "--mirror-interval", "8h"}))
require.Equal(t, "https://gitea.test/alice/tea\n", out.String())
require.Equal(t, "✓ Imported https://github.com/go-gitea/tea.git into alice/tea (pull mirror, every 8h)\n", diagnostics.String())
err := Execute(context.Background(), deps, []string{"repo", "migrate", "https://github.com/go-gitea/tea.git", "--service", "GitHub", "--issues"})
require.ErrorContains(t, err, "importing from github needs --auth-token or --auth-token-file")
}