mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-05-08 15:12:44 +00:00
Compare commits
30 commits
8d2c576a62
...
f9628f883d
Author | SHA1 | Date | |
---|---|---|---|
f9628f883d | |||
d6c36ec406 | |||
20350846fc | |||
c31ae1a651 | |||
0819ed2053 | |||
51a610d46c | |||
2bc226eb57 | |||
801554f708 | |||
c864448dc9 | |||
127eff49ee | |||
5247fd50db | |||
3dfa5ba43a | |||
90c56a9d66 | |||
aa2af10f67 | |||
a641ebf221 | |||
40d0f50838 | |||
2385f3c9db | |||
b51c608d3f | |||
a3be70f0a5 | |||
7f187f9857 | |||
4036448c02 | |||
94d7523f83 | |||
f7786e207e | |||
9e212c515e | |||
ad9872d884 | |||
44a1f90308 | |||
ea9051624d | |||
0d37f3a79b | |||
f23fd221e4 | |||
dc39043cd6 |
|
@ -4,6 +4,7 @@ reportUnusedDisableDirectives: true
|
|||
ignorePatterns:
|
||||
- /web_src/js/vendor
|
||||
- /web_src/fomantic
|
||||
- /public/assets/js
|
||||
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
|
||||
runs-on: docker
|
||||
container:
|
||||
image: ghcr.io/visualon/renovate:37.316.2
|
||||
image: ghcr.io/visualon/renovate:37.323.3
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
||||
|
|
|
@ -4,21 +4,4 @@ The Forgejo project is run by a community of people who are expected to follow t
|
|||
|
||||
Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
|
||||
|
||||
## For everyone involved
|
||||
|
||||
- [Documentation](https://forgejo.org/docs/next/)
|
||||
- [Code of Conduct](https://forgejo.org/docs/latest/developer/coc/)
|
||||
- [Bugs, features, security and others discussions](https://forgejo.org/docs/latest/developer/discussions/)
|
||||
- [Governance](https://forgejo.org/docs/latest/developer/governance/)
|
||||
- [Sustainability and funding](https://codeberg.org/forgejo/sustainability/src/branch/main/README.md)
|
||||
|
||||
## For contributors
|
||||
|
||||
- [Developer Certificate of Origin (DCO)](https://forgejo.org/docs/latest/developer/dco/)
|
||||
- [Development workflow](https://forgejo.org/docs/latest/developer/workflow/)
|
||||
- [Compiling from source](https://forgejo.org/docs/latest/developer/from-source/)
|
||||
|
||||
## For maintainers
|
||||
|
||||
- [Release management](https://forgejo.org/docs/latest/developer/release/)
|
||||
- [Secrets](https://forgejo.org/docs/latest/developer/secrets/)
|
||||
You can find links to the different aspects of Developer documentation on this page: [Forgejo developer guide](https://forgejo.org/docs/next/developer/).
|
||||
|
|
|
@ -4,30 +4,9 @@ A minor or major Forgejo release is published every [three months](https://forge
|
|||
|
||||
A [patch or minor release](https://semver.org/spec/v2.0.0.html) (e.g. upgrading from v7.0.0 to v7.0.1 or v7.1.0) does not require manual intervention. But [major releases](https://semver.org/spec/v2.0.0.html#spec-item-8) where the first version number changes (e.g. upgrading from v1.21 to v7.0) contain breaking changes and the release notes explain how to deal with them.
|
||||
|
||||
## 8.0.0
|
||||
## Upcoming releases (not available yet)
|
||||
|
||||
This is a major release. It contains breaking changes that may require manual intervention. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/).
|
||||
|
||||
* **Breaking changes:**
|
||||
|
||||
In addition to the following notable bug fixes, you can browse the [full list of commits](https://codeberg.org/forgejo/forgejo/compare/v8.0.0...v7.0.0) included in this release.
|
||||
|
||||
If you have any feedback or suggestions for Forgejo do not hold back, it is also your project.
|
||||
Open an issue in [the issue tracker](https://codeberg.org/forgejo/forgejo/issues)
|
||||
for feature requests or bug reports, reach out [on the Fediverse](https://floss.social/@forgejo),
|
||||
or drop into [the Matrix space](https://matrix.to/#/#forgejo:matrix.org)
|
||||
([main chat room](https://matrix.to/#/#forgejo-chat:matrix.org)) and say hi!
|
||||
|
||||
## 7.0.1
|
||||
|
||||
This is a bug fix release. See the documentation for more information on the [upgrade procedure](https://forgejo.org/docs/v7.0/admin/upgrade/).
|
||||
|
||||
In addition to the following notable bug fixes, you can browse the [full list of commits](https://codeberg.org/forgejo/forgejo/compare/v7.0.0...v7.0.1) included in this release.
|
||||
|
||||
* **Bug fixes:**
|
||||
* The regression in the [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [is fixed](https://codeberg.org/forgejo/forgejo/issues/3399) and it is backward compatible.
|
||||
* Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use. ([#3430](https://codeberg.org/forgejo/forgejo/pulls/3430))
|
||||
* Fixed an error 500 when visiting [the LFS settings]() at `/{owner}/{repo}/settings/lfs/find?oid=...`.
|
||||
- [8.0.0](/release-notes/8.0.0/)
|
||||
|
||||
## 7.0.0
|
||||
|
||||
|
@ -38,7 +17,8 @@ $ git clone https://codeberg.org/forgejo/forgejo/
|
|||
$ git -C forgejo log --oneline --no-merges origin/v1.21/forgejo..origin/v7.0/forgejo
|
||||
```
|
||||
|
||||
* **Regression and workaround:**
|
||||
* **Regressions and workarounds:**
|
||||
* Running the [`forgejo doctor check --fix`](https://forgejo.org/docs/v7.0/admin/command-line/#doctor-check) CLI command or setting [`[cron.gc_lfs].ENABLED=true`](https://forgejo.org/docs/v7.0/admin/config-cheat-sheet/#cron---garbage-collect-lfs-pointers-in-repositories-crongc_lfs) (the default is `false`) will corrupt the LFS storage. The workaround is to not run the doctor CLI command and disable the `cron.gc_lfs`. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3438).
|
||||
* The [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [requires a password](https://codeberg.org/forgejo/forgejo/commit/b122c6ef8b9254120432aed373cbe075331132ac) change by default when creating the first user and the `--admin` flag is not specified. The `--must-change-password=false` argument must be given to not require a password change. This regression will be [fixed in 7.0.1](https://codeberg.org/forgejo/forgejo/issues/3399).
|
||||
* **Breaking changes requiring manual intervention:**
|
||||
* [MySQL 8.0 or PostgreSQL 12](https://codeberg.org/forgejo/forgejo/commit/e94f9fcafdcf284561e7fb33f60156a69c4ad6a5) are the minimum supported versions. The database must be migrated before upgrading. The requirements regarding SQLite did not change.
|
||||
|
|
|
@ -407,8 +407,8 @@ USER = root
|
|||
;; Database connection max life time, default is 0 or 3s mysql (See #6804 & #7071 for reasoning)
|
||||
;CONN_MAX_LIFETIME = 3s
|
||||
;;
|
||||
;; Database maximum number of open connections, default is 0 meaning no maximum
|
||||
;MAX_OPEN_CONNS = 0
|
||||
;; Database maximum number of open connections, default is 100 which is the lowest default from Postgres (MariaDB + MySQL default to 151). Ensure you only increase the value if you configured your database server accordingly.
|
||||
;MAX_OPEN_CONNS = 100
|
||||
;;
|
||||
;; Whether execute database models migrations automatically
|
||||
;AUTO_MIGRATION = true
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
-
|
||||
|
||||
id: 2
|
||||
id: 2 # this is an LFS orphan object
|
||||
oid: 2eccdb43825d2a49d99d542daa20075cff1d97d9d2349a8977efe9c03661737c
|
||||
size: 107
|
||||
repository_id: 54
|
||||
|
|
|
@ -64,6 +64,8 @@ var migrations = []*Migration{
|
|||
NewMigration("Add repo_archive_download_count table", forgejo_v1_22.AddRepoArchiveDownloadCount),
|
||||
// v13 -> v14
|
||||
NewMigration("Add `hide_archive_links` column to `release` table", AddHideArchiveLinksToRelease),
|
||||
// v14 -> v15
|
||||
NewMigration("Remove Gitea-specific columns from the repository and badge tables", RemoveGiteaSpecificColumnsFromRepositoryAndBadge),
|
||||
}
|
||||
|
||||
// GetCurrentDBVersion returns the current Forgejo database version.
|
||||
|
|
43
models/forgejo_migrations/v14.go
Normal file
43
models/forgejo_migrations/v14.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package forgejo_migrations //nolint:revive
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/migrations/base"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func RemoveGiteaSpecificColumnsFromRepositoryAndBadge(x *xorm.Engine) error {
|
||||
// Make sure the columns exist before dropping them
|
||||
type Repository struct {
|
||||
ID int64
|
||||
DefaultWikiBranch string
|
||||
}
|
||||
if err := x.Sync(&Repository{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type Badge struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Slug string
|
||||
}
|
||||
err := x.Sync(new(Badge))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "repository", "default_wiki_branch"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := base.DropTableColumns(sess, "badge", "slug"); err != nil {
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
|
@ -6,6 +6,7 @@ package git
|
|||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
@ -33,6 +34,15 @@ type ObjectFormat interface {
|
|||
ComputeHash(t ObjectType, content []byte) ObjectID
|
||||
}
|
||||
|
||||
func computeHash(dst []byte, hasher hash.Hash, t ObjectType, content []byte) []byte {
|
||||
_, _ = hasher.Write(t.Bytes())
|
||||
_, _ = hasher.Write([]byte(" "))
|
||||
_, _ = hasher.Write([]byte(strconv.Itoa(len(content))))
|
||||
_, _ = hasher.Write([]byte{0})
|
||||
_, _ = hasher.Write(content)
|
||||
return hasher.Sum(dst)
|
||||
}
|
||||
|
||||
/* SHA1 Type */
|
||||
type Sha1ObjectFormatImpl struct{}
|
||||
|
||||
|
@ -65,16 +75,9 @@ func (Sha1ObjectFormatImpl) MustID(b []byte) ObjectID {
|
|||
|
||||
// ComputeHash compute the hash for a given ObjectType and content
|
||||
func (h Sha1ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
||||
hasher := sha1.New()
|
||||
_, _ = hasher.Write(t.Bytes())
|
||||
_, _ = hasher.Write([]byte(" "))
|
||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
||||
_, _ = hasher.Write([]byte{0})
|
||||
|
||||
// HashSum generates a SHA1 for the provided hash
|
||||
var sha1 Sha1Hash
|
||||
copy(sha1[:], hasher.Sum(nil))
|
||||
return &sha1
|
||||
var obj Sha1Hash
|
||||
computeHash(obj[:0], sha1.New(), t, content)
|
||||
return &obj
|
||||
}
|
||||
|
||||
/* SHA256 Type */
|
||||
|
@ -111,16 +114,9 @@ func (Sha256ObjectFormatImpl) MustID(b []byte) ObjectID {
|
|||
|
||||
// ComputeHash compute the hash for a given ObjectType and content
|
||||
func (h Sha256ObjectFormatImpl) ComputeHash(t ObjectType, content []byte) ObjectID {
|
||||
hasher := sha256.New()
|
||||
_, _ = hasher.Write(t.Bytes())
|
||||
_, _ = hasher.Write([]byte(" "))
|
||||
_, _ = hasher.Write([]byte(strconv.FormatInt(int64(len(content)), 10)))
|
||||
_, _ = hasher.Write([]byte{0})
|
||||
|
||||
// HashSum generates a SHA256 for the provided hash
|
||||
var sha256 Sha1Hash
|
||||
copy(sha256[:], hasher.Sum(nil))
|
||||
return &sha256
|
||||
var obj Sha256Hash
|
||||
computeHash(obj[:0], sha256.New(), t, content)
|
||||
return &obj
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
@ -18,4 +18,8 @@ func TestIsValidSHAPattern(t *testing.T) {
|
|||
assert.False(t, h.IsValid("abc"))
|
||||
assert.False(t, h.IsValid("123g"))
|
||||
assert.False(t, h.IsValid("some random text"))
|
||||
|
||||
assert.Equal(t, "79ee38a6416c1ede423ec7ee0a8639ceea4aad22", ComputeBlobHash(Sha1ObjectFormat, []byte("some random blob")).String())
|
||||
assert.Equal(t, "d5c6407415d85df49592672aa421aed39b9db5e3", ComputeBlobHash(Sha1ObjectFormat, []byte("same length blob")).String())
|
||||
assert.Equal(t, "df0b5174ed06ae65aea40d43316bcbc21d82c9e3158ce2661df2ad28d7931dd6", ComputeBlobHash(Sha256ObjectFormat, []byte("some random blob")).String())
|
||||
}
|
||||
|
|
32
modules/git/pipeline/lfs_common.go
Normal file
32
modules/git/pipeline/lfs_common.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
// LFSResult represents commits found using a provided pointer file hash
|
||||
type LFSResult struct {
|
||||
Name string
|
||||
SHA string
|
||||
Summary string
|
||||
When time.Time
|
||||
ParentHashes []git.ObjectID
|
||||
BranchName string
|
||||
FullCommitName string
|
||||
}
|
||||
|
||||
type lfsResultSlice []*LFSResult
|
||||
|
||||
func (a lfsResultSlice) Len() int { return len(a) }
|
||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||
|
||||
func lfsError(msg string, err error) error {
|
||||
return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err)
|
||||
}
|
|
@ -7,12 +7,10 @@ package pipeline
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
|
@ -21,23 +19,6 @@ import (
|
|||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
)
|
||||
|
||||
// LFSResult represents commits found using a provided pointer file hash
|
||||
type LFSResult struct {
|
||||
Name string
|
||||
SHA string
|
||||
Summary string
|
||||
When time.Time
|
||||
ParentHashes []git.ObjectID
|
||||
BranchName string
|
||||
FullCommitName string
|
||||
}
|
||||
|
||||
type lfsResultSlice []*LFSResult
|
||||
|
||||
func (a lfsResultSlice) Len() int { return len(a) }
|
||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||
|
||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||
resultsMap := map[string]*LFSResult{}
|
||||
|
@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err)
|
||||
return nil, lfsError("failed to get GoGit CommitsIter", err)
|
||||
}
|
||||
|
||||
err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
|
||||
|
@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
return nil
|
||||
})
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err)
|
||||
return nil, lfsError("failure in CommitIter.ForEach", err)
|
||||
}
|
||||
|
||||
for _, result := range resultsMap {
|
||||
|
@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
select {
|
||||
case err, has := <-errChan:
|
||||
if has {
|
||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
||||
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||
}
|
||||
default:
|
||||
}
|
|
@ -8,33 +8,14 @@ package pipeline
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
)
|
||||
|
||||
// LFSResult represents commits found using a provided pointer file hash
|
||||
type LFSResult struct {
|
||||
Name string
|
||||
SHA string
|
||||
Summary string
|
||||
When time.Time
|
||||
ParentIDs []git.ObjectID
|
||||
BranchName string
|
||||
FullCommitName string
|
||||
}
|
||||
|
||||
type lfsResultSlice []*LFSResult
|
||||
|
||||
func (a lfsResultSlice) Len() int { return len(a) }
|
||||
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
|
||||
|
||||
// FindLFSFile finds commits that contain a provided pointer file hash
|
||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
|
||||
resultsMap := map[string]*LFSResult{}
|
||||
|
@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
n += int64(count)
|
||||
if bytes.Equal(binObjectID, objectID.RawValue()) {
|
||||
result := LFSResult{
|
||||
Name: curPath + string(fname),
|
||||
SHA: curCommit.ID.String(),
|
||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||
When: curCommit.Author.When,
|
||||
ParentIDs: curCommit.Parents,
|
||||
Name: curPath + string(fname),
|
||||
SHA: curCommit.ID.String(),
|
||||
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
|
||||
When: curCommit.Author.When,
|
||||
ParentHashes: curCommit.Parents,
|
||||
}
|
||||
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
|
||||
} else if string(mode) == git.EntryModeTree.String() {
|
||||
|
@ -183,7 +164,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
|
||||
for _, result := range resultsMap {
|
||||
hasParent := false
|
||||
for _, parentID := range result.ParentIDs {
|
||||
for _, parentID := range result.ParentHashes {
|
||||
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
|
||||
break
|
||||
}
|
||||
|
@ -241,7 +222,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
|
|||
select {
|
||||
case err, has := <-errChan:
|
||||
if has {
|
||||
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err)
|
||||
return nil, lfsError("unable to obtain name for LFS files", err)
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
|
|
@ -41,6 +41,8 @@ const (
|
|||
maxBatchSize = 16
|
||||
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
||||
fuzzyDenominator = 4
|
||||
// see https://github.com/blevesearch/bleve/issues/1563#issuecomment-786822311
|
||||
maxFuzziness = 2
|
||||
)
|
||||
|
||||
func addUnicodeNormalizeTokenFilter(m *mapping.IndexMappingImpl) error {
|
||||
|
@ -246,7 +248,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int
|
|||
phraseQuery.Analyzer = repoIndexerAnalyzer
|
||||
keywordQuery = phraseQuery
|
||||
if opts.IsKeywordFuzzy {
|
||||
phraseQuery.Fuzziness = len(opts.Keyword) / fuzzyDenominator
|
||||
phraseQuery.Fuzziness = min(maxFuzziness, len(opts.Keyword)/fuzzyDenominator)
|
||||
}
|
||||
|
||||
if len(opts.RepoIDs) > 0 {
|
||||
|
|
|
@ -49,6 +49,12 @@ func testIndexer(name string, t *testing.T, indexer internal.Indexer) {
|
|||
IDs: []int64{},
|
||||
Langs: 0,
|
||||
},
|
||||
{
|
||||
RepoIDs: nil,
|
||||
Keyword: "Description for",
|
||||
IDs: []int64{repoID},
|
||||
Langs: 1,
|
||||
},
|
||||
{
|
||||
RepoIDs: nil,
|
||||
Keyword: "repo1",
|
||||
|
|
|
@ -39,6 +39,8 @@ const (
|
|||
maxBatchSize = 16
|
||||
// fuzzyDenominator determines the levenshtein distance per each character of a keyword
|
||||
fuzzyDenominator = 4
|
||||
// see https://github.com/blevesearch/bleve/issues/1563#issuecomment-786822311
|
||||
maxFuzziness = 2
|
||||
)
|
||||
|
||||
// IndexerData an update to the issue indexer
|
||||
|
@ -162,7 +164,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) (
|
|||
if options.Keyword != "" {
|
||||
fuzziness := 0
|
||||
if options.IsFuzzyKeyword {
|
||||
fuzziness = len(options.Keyword) / fuzzyDenominator
|
||||
fuzziness = min(maxFuzziness, len(options.Keyword)/fuzzyDenominator)
|
||||
}
|
||||
|
||||
queries = append(queries, bleve.NewDisjunctionQuery([]query.Query{
|
||||
|
|
|
@ -130,6 +130,20 @@ var cases = []*testIndexerCase{
|
|||
ExpectedIDs: []int64{1002, 1001, 1000},
|
||||
ExpectedTotal: 3,
|
||||
},
|
||||
{
|
||||
Name: "Keyword Fuzzy",
|
||||
ExtraData: []*internal.IndexerData{
|
||||
{ID: 1000, Title: "hi hello world"},
|
||||
{ID: 1001, Content: "hi hello world"},
|
||||
{ID: 1002, Comments: []string{"hi", "hello world"}},
|
||||
},
|
||||
SearchOptions: &internal.SearchOptions{
|
||||
Keyword: "hello wrold",
|
||||
IsFuzzyKeyword: true,
|
||||
},
|
||||
ExpectedIDs: []int64{1002, 1001, 1000},
|
||||
ExpectedTotal: 3,
|
||||
},
|
||||
{
|
||||
Name: "RepoIDs",
|
||||
ExtraData: []*internal.IndexerData{
|
||||
|
|
|
@ -83,7 +83,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||
Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0)
|
||||
}
|
||||
Database.ConnMaxIdleTime = sec.Key("CONN_MAX_IDLETIME").MustDuration(0)
|
||||
Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)
|
||||
Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(100)
|
||||
|
||||
Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
|
||||
Database.LogSQL = sec.Key("LOG_SQL").MustBool(false)
|
||||
|
|
|
@ -162,7 +162,7 @@ var (
|
|||
PreferredLicenses: []string{"Apache-2.0", "MIT"},
|
||||
DisableHTTPGit: false,
|
||||
AccessControlAllowOrigin: "",
|
||||
UseCompatSSHURI: false,
|
||||
UseCompatSSHURI: true,
|
||||
DefaultCloseIssuesViaCommitsInAnyBranch: false,
|
||||
EnablePushCreateUser: false,
|
||||
EnablePushCreateOrg: false,
|
||||
|
|
74
package-lock.json
generated
74
package-lock.json
generated
|
@ -44,7 +44,7 @@
|
|||
"postcss-nesting": "12.1.2",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.17.1",
|
||||
"swagger-ui-dist": "5.17.2",
|
||||
"tailwindcss": "3.4.3",
|
||||
"temporal-polyfill": "0.2.4",
|
||||
"throttle-debounce": "5.0.0",
|
||||
|
@ -96,7 +96,7 @@
|
|||
"svgo": "3.2.0",
|
||||
"updates": "16.0.1",
|
||||
"vite-string-plugin": "1.2.0",
|
||||
"vitest": "1.5.1"
|
||||
"vitest": "1.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
|
@ -2545,13 +2545,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.1.tgz",
|
||||
"integrity": "sha512-w3Bn+VUMqku+oWmxvPhTE86uMTbfmBl35aGaIPlwVW7Q89ZREC/icfo2HBsEZ3AAW6YR9lObfZKPEzstw9tJOQ==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.2.tgz",
|
||||
"integrity": "sha512-rf7MTD1WCoDlN3FfYJ9Llfp0PbdtOMZ3FIF0AVkDnKbp3oiMW1c8AmvRZBcqbAhDUAvF52e9zx4WQM1r3oraVA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "1.5.1",
|
||||
"@vitest/utils": "1.5.1",
|
||||
"@vitest/spy": "1.5.2",
|
||||
"@vitest/utils": "1.5.2",
|
||||
"chai": "^4.3.10"
|
||||
},
|
||||
"funding": {
|
||||
|
@ -2559,12 +2559,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.1.tgz",
|
||||
"integrity": "sha512-mt372zsz0vFR7L1xF/ert4t+teD66oSuXoTyaZbl0eJgilvyzCKP1tJ21gVa8cDklkBOM3DLnkE1ljj/BskyEw==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.2.tgz",
|
||||
"integrity": "sha512-7IJ7sJhMZrqx7HIEpv3WrMYcq8ZNz9L6alo81Y6f8hV5mIE6yVZsFoivLZmr0D777klm1ReqonE9LyChdcmw6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "1.5.1",
|
||||
"@vitest/utils": "1.5.2",
|
||||
"p-limit": "^5.0.0",
|
||||
"pathe": "^1.1.1"
|
||||
},
|
||||
|
@ -2600,9 +2600,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.1.tgz",
|
||||
"integrity": "sha512-h/1SGaZYXmjn6hULRBOlqam2z4oTlEe6WwARRzLErAPBqljAs6eX7tfdyN0K+MpipIwSZ5sZsubDWkCPAiVXZQ==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.2.tgz",
|
||||
"integrity": "sha512-CTEp/lTYos8fuCc9+Z55Ga5NVPKUgExritjF5VY7heRFUfheoAqBneUlvXSUJHUZPjnPmyZA96yLRJDP1QATFQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"magic-string": "^0.30.5",
|
||||
|
@ -2626,9 +2626,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.1.tgz",
|
||||
"integrity": "sha512-vsqczk6uPJjmPLy6AEtqfbFqgLYcGBe9BTY+XL8L6y8vrGOhyE23CJN9P/hPimKXnScbqiZ/r/UtUSOQ2jIDGg==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.2.tgz",
|
||||
"integrity": "sha512-xCcPvI8JpCtgikT9nLpHPL1/81AYqZy1GCy4+MCHBE7xi8jgsYkULpW5hrx5PGLgOQjUpb6fd15lqcriJ40tfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^2.2.0"
|
||||
|
@ -2638,9 +2638,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.1.tgz",
|
||||
"integrity": "sha512-92pE17bBXUxA0Y7goPcvnATMCuq4NQLOmqsG0e2BtzRi7KLwZB5jpiELi/8ybY8IQNWemKjSD5rMoO7xTdv8ug==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.2.tgz",
|
||||
"integrity": "sha512-sWOmyofuXLJ85VvXNsroZur7mOJGiQeM0JN3/0D1uU8U9bGFM69X1iqHaRXl6R8BwaLY6yPCogP257zxTzkUdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"diff-sequences": "^29.6.3",
|
||||
|
@ -11495,9 +11495,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.1.tgz",
|
||||
"integrity": "sha512-6MNu1MYNALLFvcPpo2MJVJFIxz2rFkH+XoX+J72LBLdj4JLjVaP4lHmNHtJ/tXZUXHdsb2Iw9JhPlqspjkomQg=="
|
||||
"version": "5.17.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz",
|
||||
"integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw=="
|
||||
},
|
||||
"node_modules/sync-fetch": {
|
||||
"version": "0.4.5",
|
||||
|
@ -12257,9 +12257,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.1.tgz",
|
||||
"integrity": "sha512-HNpfV7BrAsjkYVNWIcPleJwvJmydJqqJRrRbpoQ/U7QDwJKyEzNa4g5aYg8MjXJyKsk29IUCcMLFRcsEvqUIsA==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.2.tgz",
|
||||
"integrity": "sha512-Y8p91kz9zU+bWtF7HGt6DVw2JbhyuB2RlZix3FPYAYmUyZ3n7iTp8eSyLyY6sxtPegvxQtmlTMhfPhUfCUF93A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cac": "^6.7.14",
|
||||
|
@ -12339,16 +12339,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.1.tgz",
|
||||
"integrity": "sha512-3GvBMpoRnUNbZRX1L3mJCv3Ou3NAobb4dM48y8k9ZGwDofePpclTOyO+lqJFKSQpubH1V8tEcAEw/Y3mJKGJQQ==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.2.tgz",
|
||||
"integrity": "sha512-l9gwIkq16ug3xY7BxHwcBQovLZG75zZL0PlsiYQbf76Rz6QGs54416UWMtC0jXeihvHvcHrf2ROEjkQRVpoZYw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "1.5.1",
|
||||
"@vitest/runner": "1.5.1",
|
||||
"@vitest/snapshot": "1.5.1",
|
||||
"@vitest/spy": "1.5.1",
|
||||
"@vitest/utils": "1.5.1",
|
||||
"@vitest/expect": "1.5.2",
|
||||
"@vitest/runner": "1.5.2",
|
||||
"@vitest/snapshot": "1.5.2",
|
||||
"@vitest/spy": "1.5.2",
|
||||
"@vitest/utils": "1.5.2",
|
||||
"acorn-walk": "^8.3.2",
|
||||
"chai": "^4.3.10",
|
||||
"debug": "^4.3.4",
|
||||
|
@ -12362,7 +12362,7 @@
|
|||
"tinybench": "^2.5.1",
|
||||
"tinypool": "^0.8.3",
|
||||
"vite": "^5.0.0",
|
||||
"vite-node": "1.5.1",
|
||||
"vite-node": "1.5.2",
|
||||
"why-is-node-running": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -12377,8 +12377,8 @@
|
|||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"@vitest/browser": "1.5.1",
|
||||
"@vitest/ui": "1.5.1",
|
||||
"@vitest/browser": "1.5.2",
|
||||
"@vitest/ui": "1.5.2",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"postcss-nesting": "12.1.2",
|
||||
"pretty-ms": "9.0.0",
|
||||
"sortablejs": "1.15.2",
|
||||
"swagger-ui-dist": "5.17.1",
|
||||
"swagger-ui-dist": "5.17.2",
|
||||
"tailwindcss": "3.4.3",
|
||||
"temporal-polyfill": "0.2.4",
|
||||
"throttle-debounce": "5.0.0",
|
||||
|
@ -95,7 +95,7 @@
|
|||
"svgo": "3.2.0",
|
||||
"updates": "16.0.1",
|
||||
"vite-string-plugin": "1.2.0",
|
||||
"vitest": "1.5.1"
|
||||
"vitest": "1.5.2"
|
||||
},
|
||||
"browserslist": ["defaults"]
|
||||
}
|
||||
|
|
1
release-notes/8.0.0/fix/3399.md
Normal file
1
release-notes/8.0.0/fix/3399.md
Normal file
|
@ -0,0 +1 @@
|
|||
The regression in the [`fogejo admin user create`](https://forgejo.org/docs/v7.0/admin/command-line/#admin-user-create) CLI command [is fixed](https://codeberg.org/forgejo/forgejo/issues/3399) and it is backward compatible.
|
1
release-notes/8.0.0/fix/3430.md
Normal file
1
release-notes/8.0.0/fix/3430.md
Normal file
|
@ -0,0 +1 @@
|
|||
Fixed a bug where the `/api/v1/repos/{owner}/{repo}/wiki` API endpoints were using a hardcoded "master" branch for the wiki, rather than the branch they really use.
|
1
release-notes/8.0.0/fix/3442.md
Normal file
1
release-notes/8.0.0/fix/3442.md
Normal file
|
@ -0,0 +1 @@
|
|||
Save updated empty comments instead of skipping the update silently, [which prevented the removal of attachments of such comments](https://codeberg.org/forgejo/forgejo/issues/3424).
|
1
release-notes/8.0.0/fix/3444.md
Normal file
1
release-notes/8.0.0/fix/3444.md
Normal file
|
@ -0,0 +1 @@
|
|||
Fixed bleve indexer failing when [fuzziness exceeds the maximum 2](https://codeberg.org/forgejo/forgejo/pulls/3444)
|
1
release-notes/8.0.0/fix/3451.md
Normal file
1
release-notes/8.0.0/fix/3451.md
Normal file
|
@ -0,0 +1 @@
|
|||
Fixed an error 500 when visiting [the LFS settings](https://codeberg.org/forgejo/forgejo/pulls/3451) at `/{owner}/{repo}/settings/lfs/find?oid=...`.
|
|
@ -3160,12 +3160,6 @@ func UpdateCommentContent(ctx *context.Context) {
|
|||
|
||||
oldContent := comment.Content
|
||||
comment.Content = ctx.FormString("content")
|
||||
if len(comment.Content) == 0 {
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"content": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
|
||||
ctx.ServerError("UpdateComment", err)
|
||||
return
|
||||
|
|
|
@ -237,6 +237,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
|
|||
MirrorInterval: mirrorInterval,
|
||||
MirrorUpdated: mirrorUpdated,
|
||||
RepoTransfer: transfer,
|
||||
ObjectFormatName: repo.ObjectFormatName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,13 @@ func TestGarbageCollectLFSMetaObjects(t *testing.T) {
|
|||
err := storage.Init()
|
||||
assert.NoError(t, err)
|
||||
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1")
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs")
|
||||
assert.NoError(t, err)
|
||||
|
||||
validLFSObjects, err := db.GetEngine(db.DefaultContext).Count(git_model.LFSMetaObject{RepositoryID: repo.ID})
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, validLFSObjects, int64(1))
|
||||
|
||||
// add lfs object
|
||||
lfsContent := []byte("gitea1")
|
||||
lfsOid := storeObjectInRepo(t, repo.ID, &lfsContent)
|
||||
|
@ -39,13 +43,18 @@ func TestGarbageCollectLFSMetaObjects(t *testing.T) {
|
|||
err = repo_service.GarbageCollectLFSMetaObjects(context.Background(), repo_service.GarbageCollectLFSMetaObjectsOptions{
|
||||
AutoFix: true,
|
||||
OlderThan: time.Now().Add(7 * 24 * time.Hour).Add(5 * 24 * time.Hour),
|
||||
UpdatedLessRecentlyThan: time.Now().Add(7 * 24 * time.Hour).Add(3 * 24 * time.Hour),
|
||||
UpdatedLessRecentlyThan: time.Time{}, // ensure that the models/fixtures/lfs_meta_object.yml objects are considered as well
|
||||
LogDetail: t.Logf,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// lfs meta has been deleted
|
||||
_, err = git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, lfsOid)
|
||||
assert.ErrorIs(t, err, git_model.ErrLFSObjectNotExist)
|
||||
|
||||
remainingLFSObjects, err := db.GetEngine(db.DefaultContext).Count(git_model.LFSMetaObject{RepositoryID: repo.ID})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validLFSObjects-1, remainingLFSObjects)
|
||||
}
|
||||
|
||||
func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string {
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
</a>
|
||||
{{end}}
|
||||
{{if .IsOrganizationOwner}}
|
||||
<a class="{{if .PageIsOrgSettings}}active {{end}}item" href="{{.OrgLink}}/settings">
|
||||
<a id="settings-btn" class="{{if .PageIsOrgSettings}}active {{end}}right item" href="{{.OrgLink}}/settings">
|
||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||
</a>
|
||||
{{end}}
|
||||
|
|
|
@ -177,18 +177,18 @@
|
|||
{{$highlightSettings := true}}
|
||||
{{if and .SignedUser.EnableRepoUnitHints (not (.Repository.AllUnitsEnabled ctx))}}
|
||||
{{$highlightSettings = false}}
|
||||
<a class="{{if .PageIsRepoSettingsUnits}}active {{end}}item" href="{{.RepoLink}}/settings/units">
|
||||
<a id="settings-btn" class="{{if .PageIsRepoSettingsUnits}}active {{end}}right item" href="{{.RepoLink}}/settings/units">
|
||||
{{svg "octicon-diff-added"}} {{ctx.Locale.Tr "repo.settings.units.add_more"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if and .PageIsRepoSettings (or $highlightSettings (not .PageIsRepoSettingsUnits))}}active {{end}} item" href="{{.RepoLink}}/settings">
|
||||
<a id="settings-btn" class="{{if and .PageIsRepoSettings (or $highlightSettings (not .PageIsRepoSettingsUnits))}}active {{end}}right item" href="{{.RepoLink}}/settings">
|
||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if .Permission.IsAdmin}}
|
||||
<div class="overflow-menu-items">
|
||||
<a class="{{if .PageIsRepoSettings}}active {{end}} item" href="{{.RepoLink}}/settings">
|
||||
<a id="settings-btn" class="{{if .PageIsRepoSettings}}active {{end}}right item" href="{{.RepoLink}}/settings">
|
||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
<span class="text grey">{{svg "octicon-git-branch"}}{{.BranchName}}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{if .ParentIDs}}
|
||||
{{if .ParentHashes}}
|
||||
{{ctx.Locale.Tr "repo.diff.parent"}}
|
||||
{{range .ParentIDs}}
|
||||
{{range .ParentHashes}}
|
||||
<a class="ui primary sha label" href="{{$.RepoLink}}/commit/{{.String}}">{{ShortSha .String}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
114
tests/e2e/right-settings-button.test.e2e.js
Normal file
114
tests/e2e/right-settings-button.test.e2e.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
// @ts-check
|
||||
import {test, expect} from '@playwright/test';
|
||||
import {login_user, load_logged_in_context} from './utils_e2e.js';
|
||||
|
||||
test.beforeAll(async ({browser}, workerInfo) => {
|
||||
await login_user(browser, workerInfo, 'user2');
|
||||
});
|
||||
|
||||
test.describe('desktop viewport', () => {
|
||||
test.use({viewport: {width: 1920, height: 300}});
|
||||
|
||||
test('Settings button on right of repo header', async ({browser}, workerInfo) => {
|
||||
const context = await load_logged_in_context(browser, workerInfo, 'user2');
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto('/user2/repo1');
|
||||
|
||||
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
|
||||
await expect(settingsBtn).toBeVisible();
|
||||
await expect(settingsBtn).toHaveClass(/right/);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('Settings button on right of org header', async ({browser}, workerInfo) => {
|
||||
const context = await load_logged_in_context(browser, workerInfo, 'user2');
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto('/org3');
|
||||
|
||||
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
|
||||
await expect(settingsBtn).toBeVisible();
|
||||
await expect(settingsBtn).toHaveClass(/right/);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('User overview overflow menu should not be influenced', async ({page}) => {
|
||||
await page.goto('/user2');
|
||||
|
||||
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('small viewport', () => {
|
||||
test.use({viewport: {width: 800, height: 300}});
|
||||
|
||||
test('Settings button in overflow menu of repo header', async ({browser}, workerInfo) => {
|
||||
const context = await load_logged_in_context(browser, workerInfo, 'user2');
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto('/user2/repo1');
|
||||
|
||||
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toBeVisible();
|
||||
|
||||
await page.click('.overflow-menu-button');
|
||||
await expect(page.locator('.tippy-target>#settings-btn')).toBeVisible();
|
||||
|
||||
// Verify that we have no duplicated items
|
||||
const shownItems = await page.locator('.overflow-menu-items>a').all();
|
||||
expect(shownItems).not.toHaveLength(0);
|
||||
const overflowItems = await page.locator('.tippy-target>a').all();
|
||||
expect(overflowItems).not.toHaveLength(0);
|
||||
|
||||
const items = shownItems.concat(overflowItems);
|
||||
expect(Array.from(new Set(items))).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
test('Settings button in overflow menu of org header', async ({browser}, workerInfo) => {
|
||||
const context = await load_logged_in_context(browser, workerInfo, 'user2');
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto('/org3');
|
||||
|
||||
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toBeVisible();
|
||||
|
||||
await page.click('.overflow-menu-button');
|
||||
await expect(page.locator('.tippy-target>#settings-btn')).toBeVisible();
|
||||
|
||||
// Verify that we have no duplicated items
|
||||
const shownItems = await page.locator('.overflow-menu-items>a').all();
|
||||
expect(shownItems).not.toHaveLength(0);
|
||||
const overflowItems = await page.locator('.tippy-target>a').all();
|
||||
expect(overflowItems).not.toHaveLength(0);
|
||||
|
||||
const items = shownItems.concat(overflowItems);
|
||||
expect(Array.from(new Set(items))).toHaveLength(items.length);
|
||||
});
|
||||
|
||||
test('User overview overflow menu should not be influenced', async ({page}) => {
|
||||
await page.goto('/user2');
|
||||
|
||||
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toBeVisible();
|
||||
await page.click('.overflow-menu-button');
|
||||
await expect(page.locator('.tippy-target>#settings-btn')).toHaveCount(0);
|
||||
|
||||
// Verify that we have no duplicated items
|
||||
const shownItems = await page.locator('.overflow-menu-items>a').all();
|
||||
expect(shownItems).not.toHaveLength(0);
|
||||
const overflowItems = await page.locator('.tippy-target>a').all();
|
||||
expect(overflowItems).not.toHaveLength(0);
|
||||
|
||||
const items = shownItems.concat(overflowItems);
|
||||
expect(Array.from(new Set(items))).toHaveLength(items.length);
|
||||
});
|
||||
});
|
|
@ -701,3 +701,14 @@ func TestAPIRepoGetAssignees(t *testing.T) {
|
|||
DecodeJSON(t, resp, &assignees)
|
||||
assert.Len(t, assignees, 1)
|
||||
}
|
||||
|
||||
func TestAPIViewRepoObjectFormat(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
var repo api.Repository
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repo)
|
||||
assert.EqualValues(t, "sha1", repo.ObjectFormatName)
|
||||
}
|
||||
|
|
|
@ -307,6 +307,16 @@ func TestIssueCommentUpdate(t *testing.T) {
|
|||
|
||||
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
||||
assert.Equal(t, modifiedContent, comment.Content)
|
||||
|
||||
// make the comment empty
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
|
||||
"_csrf": GetCSRF(t, session, issueURL),
|
||||
"content": "",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
||||
assert.Equal(t, "", comment.Content)
|
||||
}
|
||||
|
||||
func TestIssueReaction(t *testing.T) {
|
||||
|
|
|
@ -100,6 +100,6 @@ func TestLFSRender(t *testing.T) {
|
|||
req = NewRequest(t, "GET", lfsFindPath)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
doc := NewHTMLParser(t, resp.Body).doc
|
||||
assert.Contains(t, doc.Text(), "README.md")
|
||||
assert.Equal(t, 1, doc.Find(`.sha.label[href="/user2/lfs/commit/73cf03db6ece34e12bf91e8853dc58f678f2f82d"]`).Length(), "could not find link to commit")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -69,13 +69,35 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
|
|||
this.tippyItems = [];
|
||||
const menuRight = this.offsetLeft + this.offsetWidth;
|
||||
const menuItems = this.menuItemsEl.querySelectorAll('.item');
|
||||
const settingItem = this.menuItemsEl.querySelector('#settings-btn');
|
||||
for (const item of menuItems) {
|
||||
const itemRight = item.offsetLeft + item.offsetWidth;
|
||||
if (menuRight - itemRight < 38) { // roughly the width of .overflow-menu-button
|
||||
// Width of the settings button plus a small value to get the next item to the left if there is directly one
|
||||
// If no setting button is in the menu the default threshold is 38 - roughly the width of .overflow-menu-button
|
||||
const overflowBtnThreshold = 38;
|
||||
const threshold = settingItem?.offsetWidth ?? overflowBtnThreshold;
|
||||
// If we have a settings item on the right-hand side, we must also check if the first,
|
||||
// possibly overflowing item would still fit on the left-hand side of the overflow menu
|
||||
// If not, it must be added to the array (twice). The duplicate is removed with the shift.
|
||||
if (settingItem && !this.tippyItems?.length && item !== settingItem && menuRight - itemRight < overflowBtnThreshold) {
|
||||
this.tippyItems.push(settingItem);
|
||||
}
|
||||
if (menuRight - itemRight < threshold) {
|
||||
this.tippyItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for settings button on right. Only done if a setting item is present
|
||||
if (settingItem) {
|
||||
// If less than 2 items overflow, remove all items (only settings "overflowed" - because it's on the right side)
|
||||
if (this.tippyItems?.length < 2) {
|
||||
this.tippyItems = [];
|
||||
} else {
|
||||
// Remove the first item of the list, because we have always one item more in the array due to the big threshold above
|
||||
this.tippyItems.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no overflown items, remove any previously created button
|
||||
if (!this.tippyItems?.length) {
|
||||
const btn = this.querySelector('.overflow-menu-button');
|
||||
|
|
Loading…
Reference in a new issue