fix(repo): README lookup like Gitea, clearer branch/org errors, clone-after-create report, mirror interval 0 and search sort (follow gh)

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 20:03:55 +02:00
1 parent 8305a89531
commit aa181a2b5c
16 files changed
+211 -37

No files matched your search

+62 -17
View File
@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
"slices"
"strconv"
"strings"
gitea "gitea.dev/sdk"
@@ -99,35 +100,75 @@ func (s Repositories) get(ctx context.Context, repo string) (*gitea.Repository,
return item, Classify(err, "repository", repo, "")
}
// readmeNames are the README file names that Gitea shows, in order of preference.
var readmeNames = []string{"readme.md", "readme.markdown", "readme", "readme.txt"}
// readmeDirs are the directories Gitea searches for a README, in order; "" is the repository root.
var readmeDirs = []string{".gitea", ".github", "", "docs"}
// Readme returns the README in the top directory of the repository at ref: its file name and its
// content, or "" when there is none.
func (s Repositories) Readme(ctx context.Context, repo, ref string) (name, content string, err error) {
entries, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]*gitea.ContentsResponse, *gitea.Response, error) {
return c.Repositories.ListContents(ctx, owner, name, ref, "")
})
// readmeRank orders README file names as Gitea does: Markdown first, then text, then any other
// README.* such as README.rst. It returns -1 for names that are not a README.
func readmeRank(name string) int {
switch lower := strings.ToLower(name); {
case lower == "readme.md":
return 0
case lower == "readme.markdown":
return 1
case lower == "readme.txt":
return 2
case lower == "readme":
return 3
case strings.HasPrefix(lower, "readme."):
return 4
}
return -1
}
// Readme returns the README of the repository at ref as Gitea's web view finds it, searching .gitea,
// .github, the root and docs: its path and its content, or "" when there is none. An explicit ref
// that does not exist is an error.
func (s Repositories) Readme(ctx context.Context, repo, ref string) (path, content string, err error) {
list := func(dir string) ([]*gitea.ContentsResponse, error) {
return repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]*gitea.ContentsResponse, *gitea.Response, error) {
return c.Repositories.ListContents(ctx, owner, name, ref, dir)
})
}
root, err := list("")
if err != nil {
if IsNotFound(err) && ref != "" {
return "", "", NotFound("branch", strconv.Quote(ref), repo)
}
// A reader without code access sees the repository but not its files.
if IsNotFound(err) || IsForbidden(err) {
return "", "", nil
}
return "", "", err
}
rank := len(readmeNames)
for _, entry := range entries {
if i := slices.Index(readmeNames, strings.ToLower(entry.Name)); entry.Type == "file" && i >= 0 && i < rank {
name, rank = entry.Name, i
for _, dir := range readmeDirs {
entries := root
if dir != "" {
i := slices.IndexFunc(root, func(e *gitea.ContentsResponse) bool { return e.Type == "dir" && e.Name == dir })
if i < 0 {
continue
}
if entries, err = list(dir); err != nil {
return "", "", err
}
}
rank := -1
for _, entry := range entries {
if r := readmeRank(entry.Name); entry.Type == "file" && r >= 0 && (rank < 0 || r < rank) {
path, rank = strings.TrimPrefix(dir+"/"+entry.Name, "/"), r
}
}
if path != "" {
break
}
}
if name == "" {
if path == "" {
return "", "", nil
}
data, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, repo string) ([]byte, *gitea.Response, error) {
return c.Repositories.GetFile(ctx, owner, repo, ref, name)
data, err := repoCall(ctx, s.Client, repo, func(ctx context.Context, c *gitea.Client, owner, name string) ([]byte, *gitea.Response, error) {
return c.Repositories.GetFile(ctx, owner, name, ref, path)
})
return name, string(data), err
return path, string(data), err
}
// RepositoryPermissions holds server-side access flags used only for local decisions.
@@ -204,12 +245,16 @@ func (s Repositories) Create(ctx context.Context, org string, opts RepositoryOpt
return c.Repositories.CreateRepo(ctx, options)
})
if err != nil {
if org != "" && IsNotFound(err) {
// The repository cannot be missing before it exists: the organization is.
return Repository{}, &NotFoundError{Kind: "organization", Key: strconv.Quote(org), Err: err}
}
return Repository{}, Classify(err, "repository", "", "")
}
return repositoryFrom(item), nil
}
// Edit updates only explicitly supplied resource fields.
// Editupdates only explicitly supplied resource fields.
func (s Repositories) Edit(ctx context.Context, repo string, opts RepositoryEdit) (Repository, error) {
var mergeStyle *gitea.MergeStyle
if opts.MergeStyle != nil {