Merge pull request '[gitea] week 2024-24-v7.0 cherry pick (release/v1.22 -> v7.0/forgejo)' (#4084) from earl-warren/wcp/2024-24-v7.0 into v7.0/forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4084 Reviewed-by: twenty-panda <twenty-panda@noreply.codeberg.org>
This commit is contained in:
commit
f132e98d12
10 changed files with 125 additions and 44 deletions
|
@ -96,20 +96,34 @@ func FeedCapabilityResource(ctx *context.Context) {
|
||||||
xmlResponse(ctx, http.StatusOK, Metadata)
|
xmlResponse(ctx, http.StatusOK, Metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
|
var (
|
||||||
|
searchTermExtract = regexp.MustCompile(`'([^']+)'`)
|
||||||
|
searchTermExact = regexp.MustCompile(`\s+eq\s+'`)
|
||||||
|
)
|
||||||
|
|
||||||
func getSearchTerm(ctx *context.Context) string {
|
func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
|
||||||
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
|
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
|
||||||
if searchTerm == "" {
|
if searchTerm != "" {
|
||||||
// $filter contains a query like:
|
return packages_model.SearchValue{
|
||||||
// (((Id ne null) and substringof('microsoft',tolower(Id)))
|
Value: searchTerm,
|
||||||
// We don't support these queries, just extract the search term.
|
ExactMatch: false,
|
||||||
match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter"))
|
|
||||||
if len(match) == 2 {
|
|
||||||
searchTerm = strings.TrimSpace(match[1])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return searchTerm
|
|
||||||
|
// $filter contains a query like:
|
||||||
|
// (((Id ne null) and substringof('microsoft',tolower(Id)))
|
||||||
|
// https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5
|
||||||
|
// We don't support these queries, just extract the search term.
|
||||||
|
filter := ctx.FormTrim("$filter")
|
||||||
|
match := searchTermExtract.FindStringSubmatch(filter)
|
||||||
|
if len(match) == 2 {
|
||||||
|
return packages_model.SearchValue{
|
||||||
|
Value: strings.TrimSpace(match[1]),
|
||||||
|
ExactMatch: searchTermExact.MatchString(filter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages_model.SearchValue{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
|
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
|
||||||
|
@ -118,11 +132,9 @@ func SearchServiceV2(ctx *context.Context) {
|
||||||
paginator := db.NewAbsoluteListOptions(skip, take)
|
paginator := db.NewAbsoluteListOptions(skip, take)
|
||||||
|
|
||||||
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
Type: packages_model.TypeNuGet,
|
Type: packages_model.TypeNuGet,
|
||||||
Name: packages_model.SearchValue{
|
Name: getSearchTerm(ctx),
|
||||||
Value: getSearchTerm(ctx),
|
|
||||||
},
|
|
||||||
IsInternal: optional.Some(false),
|
IsInternal: optional.Some(false),
|
||||||
Paginator: paginator,
|
Paginator: paginator,
|
||||||
})
|
})
|
||||||
|
@ -169,10 +181,8 @@ func SearchServiceV2(ctx *context.Context) {
|
||||||
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
|
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
|
||||||
func SearchServiceV2Count(ctx *context.Context) {
|
func SearchServiceV2Count(ctx *context.Context) {
|
||||||
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
|
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
Name: packages_model.SearchValue{
|
Name: getSearchTerm(ctx),
|
||||||
Value: getSearchTerm(ctx),
|
|
||||||
},
|
|
||||||
IsInternal: optional.Some(false),
|
IsInternal: optional.Some(false),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}},
|
{{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}},
|
||||||
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}})
|
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}})
|
||||||
<div class="ui right">
|
<div class="ui right">
|
||||||
<form method="post" action="/admin/packages/cleanup">
|
<form method="post" action="{{AppSubUrl}}/admin/packages/cleanup">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button>
|
<button class="ui primary tiny button">{{ctx.Locale.Tr "admin.packages.cleanup"}}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<div><label><input name="check" type="checkbox"> check</label></div>
|
<div><label><input name="check" type="checkbox"> check</label></div>
|
||||||
<div><button name="btn">submit post</button></div>
|
<div><button name="btn">submit post</button></div>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="/no-such-uri" class="form-fetch-action">
|
<form method="post" action="no-such-uri" class="form-fetch-action">
|
||||||
<div class="tw-py-8">bad action url</div>
|
<div class="tw-py-8">bad action url</div>
|
||||||
<div><button name="btn">submit test</button></div>
|
<div><button name="btn">submit test</button></div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
<div id="project-board">
|
<div id="project-board">
|
||||||
<div class="board {{if .CanWriteProjects}}sortable{{end}}"{{if .CanWriteProjects}} data-url="{{$.Link}}/move"{{end}}>
|
<div class="board {{if .CanWriteProjects}}sortable{{end}}"{{if .CanWriteProjects}} data-url="{{$.Link}}/move"{{end}}>
|
||||||
{{range .Columns}}
|
{{range .Columns}}
|
||||||
<div class="ui segment project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
|
<div class="project-column"{{if .Color}} style="background: {{.Color}} !important; color: {{ContrastColor .Color}} !important"{{end}} data-id="{{.ID}}" data-sorting="{{.Sorting}}" data-url="{{$.Link}}/{{.ID}}">
|
||||||
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
|
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
|
||||||
<div class="ui large label project-column-title tw-py-1">
|
<div class="ui large label project-column-title tw-py-1">
|
||||||
<div class="ui small circular grey label project-column-issue-count">
|
<div class="ui small circular grey label project-column-issue-count">
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{{range .RecentlyPushedNewBranches}}
|
{{range .RecentlyPushedNewBranches}}
|
||||||
<div class="ui positive message tw-flex tw-items-center">
|
<div class="ui positive message tw-flex tw-items-center tw-gap-2">
|
||||||
<div class="tw-flex-1">
|
<div class="tw-flex-1 tw-break-anywhere">
|
||||||
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}}
|
{{$timeSince := TimeSince .CommitTime.AsTime ctx.Locale}}
|
||||||
{{$repo := .GetRepo $.Context}}
|
{{$repo := .GetRepo $.Context}}
|
||||||
{{$name := .Name}}
|
{{$name := .Name}}
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
<td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td>
|
<td><p data-tooltip-content="{{.Description}}">{{.Name}}</p></td>
|
||||||
<td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td>
|
<td>{{if .Version}}{{.Version}}{{else}}{{ctx.Locale.Tr "unknown"}}{{end}}</td>
|
||||||
<td><span data-tooltip-content="{{.BelongsToOwnerName}}">{{.BelongsToOwnerType.LocaleString ctx.Locale}}</span></td>
|
<td><span data-tooltip-content="{{.BelongsToOwnerName}}">{{.BelongsToOwnerType.LocaleString ctx.Locale}}</span></td>
|
||||||
<td class="runner-tags">
|
<td class="tw-flex tw-flex-wrap tw-gap-2 runner-tags">
|
||||||
{{range .AgentLabels}}<span class="ui label">{{.}}</span>{{end}}
|
{{range .AgentLabels}}<span class="ui label">{{.}}</span>{{end}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</td>
|
<td>{{if .LastOnline}}{{TimeSinceUnix .LastOnline ctx.Locale}}{{else}}{{ctx.Locale.Tr "never"}}{{end}}</td>
|
||||||
|
|
|
@ -44,14 +44,14 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<a class="notifications-link tw-flex tw-flex-1 tw-flex-col silenced" href="{{.Link ctx}}">
|
<a class="notifications-link tw-flex tw-flex-1 tw-flex-col silenced" href="{{.Link ctx}}">
|
||||||
<div class="notifications-top-row tw-text-13">
|
<div class="notifications-top-row tw-text-13 tw-break-anywhere">
|
||||||
{{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}}
|
{{.Repository.FullName}} {{if .Issue}}<span class="text light-3">#{{.Issue.Index}}</span>{{end}}
|
||||||
{{if eq .Status 3}}
|
{{if eq .Status 3}}
|
||||||
{{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}}
|
{{svg "octicon-pin" 13 "text blue tw-mt-0.5 tw-ml-1"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
|
<div class="notifications-bottom-row tw-text-16 tw-py-0.5">
|
||||||
<span class="issue-title">
|
<span class="issue-title tw-break-anywhere">
|
||||||
{{if .Issue}}
|
{{if .Issue}}
|
||||||
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
|
{{.Issue.Title | RenderEmoji $.Context | RenderCodeBlock}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
{{ctx.Locale.Tr "settings.select_permissions"}}
|
{{ctx.Locale.Tr "settings.select_permissions"}}
|
||||||
</summary>
|
</summary>
|
||||||
<p class="activity meta">
|
<p class="activity meta">
|
||||||
<i>{{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</i>
|
<i>{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://forgejo.org/docs/latest/user/token-scope/" target="_blank"`|SafeHTML)}}</i>
|
||||||
</p>
|
</p>
|
||||||
<div class="scoped-access-token-mount">
|
<div class="scoped-access-token-mount">
|
||||||
<scoped-access-token-selector
|
<scoped-access-token-selector
|
||||||
|
|
|
@ -431,22 +431,33 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||||
|
|
||||||
t.Run("SearchService", func(t *testing.T) {
|
t.Run("SearchService", func(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Query string
|
Query string
|
||||||
Skip int
|
Skip int
|
||||||
Take int
|
Take int
|
||||||
ExpectedTotal int64
|
ExpectedTotal int64
|
||||||
ExpectedResults int
|
ExpectedResults int
|
||||||
|
ExpectedExactMatch bool
|
||||||
}{
|
}{
|
||||||
{"", 0, 0, 1, 1},
|
{"", 0, 0, 4, 4, false},
|
||||||
{"", 0, 10, 1, 1},
|
{"", 0, 10, 4, 4, false},
|
||||||
{"gitea", 0, 10, 0, 0},
|
{"gitea", 0, 10, 0, 0, false},
|
||||||
{"test", 0, 10, 1, 1},
|
{"test", 0, 10, 1, 1, false},
|
||||||
{"test", 1, 10, 1, 0},
|
{"test", 1, 10, 1, 0, false},
|
||||||
|
{"almost.similar", 0, 0, 3, 3, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "PUT", url, createPackage(packageName, "1.0.99")).
|
fakePackages := []string{
|
||||||
AddBasicAuth(user.Name)
|
packageName,
|
||||||
MakeRequest(t, req, http.StatusCreated)
|
"almost.similar.dependency",
|
||||||
|
"almost.similar",
|
||||||
|
"almost.similar.dependant",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fakePackageName := range fakePackages {
|
||||||
|
req := NewRequestWithBody(t, "PUT", url, createPackage(fakePackageName, "1.0.99")).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("v2", func(t *testing.T) {
|
t.Run("v2", func(t *testing.T) {
|
||||||
t.Run("Search()", func(t *testing.T) {
|
t.Run("Search()", func(t *testing.T) {
|
||||||
|
@ -493,6 +504,63 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Packages()", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
t.Run("substringof", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var result FeedResponse
|
||||||
|
decodeXML(t, resp, &result)
|
||||||
|
|
||||||
|
assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
|
||||||
|
assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("IdEq", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
if c.Query == "" {
|
||||||
|
// Ignore the `tolower(Id) eq ''` as it's unlikely to happen
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages()?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var result FeedResponse
|
||||||
|
decodeXML(t, resp, &result)
|
||||||
|
|
||||||
|
expectedCount := 0
|
||||||
|
if c.ExpectedExactMatch {
|
||||||
|
expectedCount = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, int64(expectedCount), result.Count, "case %d: unexpected total hits", i)
|
||||||
|
assert.Len(t, result.Entries, expectedCount, "case %d: unexpected result count", i)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/Packages()/$count?$filter=(tolower(Id) eq '%s')&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
assert.Equal(t, strconv.FormatInt(int64(expectedCount), 10), resp.Body.String(), "case %d: unexpected total hits", i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Next", func(t *testing.T) {
|
t.Run("Next", func(t *testing.T) {
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
|
@ -550,9 +618,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
req = NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, packageName, "1.0.99")).
|
for _, fakePackageName := range fakePackages {
|
||||||
AddBasicAuth(user.Name)
|
req := NewRequest(t, "DELETE", fmt.Sprintf("%s/%s/%s", url, fakePackageName, "1.0.99")).
|
||||||
MakeRequest(t, req, http.StatusNoContent)
|
AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("RegistrationService", func(t *testing.T) {
|
t.Run("RegistrationService", func(t *testing.T) {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
.project-column {
|
.project-column {
|
||||||
background-color: var(--color-project-board-bg) !important;
|
background-color: var(--color-project-board-bg) !important;
|
||||||
border: 1px solid var(--color-secondary) !important;
|
border: 1px solid var(--color-secondary) !important;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
margin: 0 0.5rem !important;
|
margin: 0 0.5rem !important;
|
||||||
padding: 0.5rem !important;
|
padding: 0.5rem !important;
|
||||||
width: 320px;
|
width: 320px;
|
||||||
|
|
Loading…
Reference in a new issue