mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
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:
16 files changed
+211
-37
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -79,3 +79,42 @@ func TestRepositoryAssigneesTagsAndMissingPermissions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, RepositoryPermissions{}, permissions)
|
||||
}
|
||||
|
||||
func TestRepositoryCreateReportsMissingOrganization(t *testing.T) {
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message":"GetOrgByName"}`))
|
||||
}))
|
||||
_, err := (Repositories{Client: client}).Create(context.Background(), "acme-typo", RepositoryOptions{Name: "tool"})
|
||||
require.EqualError(t, err, `organization "acme-typo" not found`)
|
||||
}
|
||||
|
||||
func TestRepositoryReadme(t *testing.T) {
|
||||
dirEntry := func(name string) string { return `{"name":"` + name + `","path":"` + name + `","type":"dir"}` }
|
||||
fileEntry := func(dir, name string) string {
|
||||
return `{"name":"` + name + `","path":"` + dir + name + `","type":"file"}`
|
||||
}
|
||||
listings := map[string]string{
|
||||
"/api/v1/repos/alice/project/contents/": "[" + fileEntry("", "README.rst") + "," + fileEntry("", "readme.txt") + "," + dirEntry(".github") + "]",
|
||||
"/api/v1/repos/alice/project/contents/.github": "[" + fileEntry(".github/", "README.md") + "]",
|
||||
}
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Query().Get("ref") == "develp" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if body, ok := listings[r.URL.Path]; ok {
|
||||
_, _ = w.Write([]byte(body))
|
||||
return
|
||||
}
|
||||
assert.Equal(t, "/api/v1/repos/alice/project/raw/.github/README.md", r.URL.Path)
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
}))
|
||||
path, content, err := (Repositories{Client: client}).Readme(context.Background(), "alice/project", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, ".github/README.md", path)
|
||||
assert.Equal(t, "hello", content)
|
||||
|
||||
_, _, err = (Repositories{Client: client}).Readme(context.Background(), "alice/project", "develp")
|
||||
require.EqualError(t, err, `branch "develp" not found in alice/project`)
|
||||
}
|
||||
@@ -121,7 +121,10 @@ func (s Repositories) Search(ctx context.Context, opts RepositorySearchOptions)
|
||||
if err != nil {
|
||||
return Page[Repository]{}, err
|
||||
}
|
||||
options.PrioritizedByOwnerID = account.ID
|
||||
// Gitea groups results by owner ahead of the requested sort, so an explicit sort cannot keep it.
|
||||
if opts.Sort == "" {
|
||||
options.PrioritizedByOwnerID = account.ID
|
||||
}
|
||||
window, err := callPage(ctx, s.Client, opts.Limit, opts.Page, func(ctx context.Context, c *gitea.Client, list gitea.ListOptions) ([]*gitea.Repository, *gitea.Response, error) {
|
||||
options.ListOptions = list
|
||||
return c.Repositories.SearchRepos(ctx, options)
|
||||
|
||||
@@ -118,6 +118,22 @@ func TestRepositorySearchSendsExplicitFalseFilters(t *testing.T) {
|
||||
require.Empty(t, page.Items)
|
||||
}
|
||||
|
||||
func TestRepositorySearchSortDropsOwnerPriority(t *testing.T) {
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v1/user" {
|
||||
_, _ = w.Write([]byte(`{"id":7}`))
|
||||
return
|
||||
}
|
||||
query := r.URL.Query()
|
||||
assert.False(t, query.Has("priority_owner_id"))
|
||||
assert.Equal(t, "updated", query.Get("sort"))
|
||||
assert.Equal(t, "desc", query.Get("order"))
|
||||
_, _ = w.Write([]byte(`{"data":[]}`))
|
||||
}))
|
||||
_, err := (Repositories{Client: client}).Search(context.Background(), RepositorySearchOptions{Query: "go", Sort: "updated", Order: "desc", Limit: 30, Page: 1})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRepositorySearchScopesOwner(t *testing.T) {
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
|
||||
Reference in new issue
Block a user