[gitea] week 2025-02 cherry pick (gitea/main -> forgejo) (#6471)

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6471
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2025-01-07 06:17:49 +00:00
commit 4261301dfb
34 changed files with 341 additions and 127 deletions

View file

@ -69,6 +69,10 @@ var microcmdUserCreate = &cli.Command{
} }
func runCreateUser(c *cli.Context) error { func runCreateUser(c *cli.Context) error {
// this command highly depends on the many setting options (create org, visibility, etc.), so it must have a full setting load first
// duplicate setting loading should be safe at the moment, but it should be refactored & improved in the future.
setting.LoadSettings()
if err := argsSet(c, "email"); err != nil { if err := argsSet(c, "email"); err != nil {
return err return err
} }

View file

@ -206,6 +206,7 @@ func innerNewMainApp(version, versionExtra string, subCmdsStandaloneArgs, subCmd
app.Commands = append(app.Commands, subCmdWithConfig...) app.Commands = append(app.Commands, subCmdWithConfig...)
app.Commands = append(app.Commands, subCmdStandalone...) app.Commands = append(app.Commands, subCmdStandalone...)
setting.InitGiteaEnvVars()
return app return app
} }

View file

@ -6,7 +6,6 @@ package cmd
import ( import (
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
@ -114,37 +113,17 @@ func TestCliCmd(t *testing.T) {
_, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf)) _, _ = fmt.Fprint(ctx.App.Writer, makePathOutput(setting.AppWorkPath, setting.CustomPath, setting.CustomConf))
return nil return nil
}) })
var envBackup []string
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") && strings.Contains(s, "=") {
envBackup = append(envBackup, s)
}
}
clearGiteaEnv := func() {
for _, s := range os.Environ() {
if strings.HasPrefix(s, "GITEA_") {
_ = os.Unsetenv(s)
}
}
}
defer func() {
clearGiteaEnv()
for _, s := range envBackup {
k, v, _ := strings.Cut(s, "=")
_ = os.Setenv(k, v)
}
}()
for _, c := range cases { for _, c := range cases {
clearGiteaEnv() t.Run(c.cmd, func(t *testing.T) {
for k, v := range c.env { for k, v := range c.env {
_ = os.Setenv(k, v) t.Setenv(k, v)
} }
args := strings.Split(c.cmd, " ") // for test only, "split" is good enough args := strings.Split(c.cmd, " ") // for test only, "split" is good enough
r, err := runTestApp(app, args...) r, err := runTestApp(app, args...)
require.NoError(t, err, c.cmd) require.NoError(t, err, c.cmd)
assert.NotEmpty(t, c.exp, c.cmd) assert.NotEmpty(t, c.exp, c.cmd)
assert.Contains(t, r.Stdout, c.exp, c.cmd) assert.Contains(t, r.Stdout, c.exp, c.cmd)
})
} }
} }

View file

@ -12,6 +12,7 @@ import (
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"time"
_ "net/http/pprof" // Used for debugging if enabled and a web server is running _ "net/http/pprof" // Used for debugging if enabled and a web server is running
@ -115,6 +116,16 @@ func showWebStartupMessage(msg string) {
log.Info("* CustomPath: %s", setting.CustomPath) log.Info("* CustomPath: %s", setting.CustomPath)
log.Info("* ConfigFile: %s", setting.CustomConf) log.Info("* ConfigFile: %s", setting.CustomConf)
log.Info("%s", msg) // show startup message log.Info("%s", msg) // show startup message
if setting.CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
if setting.DefaultUILocation != time.Local {
log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
}
if setting.MailService != nil {
log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
}
} }
func serveInstall(ctx *cli.Context) error { func serveInstall(ctx *cli.Context) error {

View file

@ -194,6 +194,20 @@ func (t CommentType) HasMailReplySupport() bool {
return false return false
} }
func (t CommentType) CountedAsConversation() bool {
for _, ct := range ConversationCountedCommentType() {
if t == ct {
return true
}
}
return false
}
// ConversationCountedCommentType returns the comment types that are counted as a conversation
func ConversationCountedCommentType() []CommentType {
return []CommentType{CommentTypeComment, CommentTypeReview}
}
// RoleInRepo presents the user's participation in the repo // RoleInRepo presents the user's participation in the repo
type RoleInRepo string type RoleInRepo string
@ -887,7 +901,7 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
} }
fallthrough fallthrough
case CommentTypeComment: case CommentTypeComment:
if _, err = db.Exec(ctx, "UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil { if err := UpdateIssueNumComments(ctx, opts.Issue.ID); err != nil {
return err return err
} }
fallthrough fallthrough
@ -1182,8 +1196,8 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
return err return err
} }
if comment.Type == CommentTypeComment { if comment.Type.CountedAsConversation() {
if _, err := e.ID(comment.IssueID).Decr("num_comments").Update(new(Issue)); err != nil { if err := UpdateIssueNumComments(ctx, comment.IssueID); err != nil {
return err return err
} }
} }
@ -1300,6 +1314,21 @@ func (c *Comment) HasOriginalAuthor() bool {
return c.OriginalAuthor != "" && c.OriginalAuthorID != 0 return c.OriginalAuthor != "" && c.OriginalAuthorID != 0
} }
func UpdateIssueNumCommentsBuilder(issueID int64) *builder.Builder {
subQuery := builder.Select("COUNT(*)").From("`comment`").Where(
builder.Eq{"issue_id": issueID}.And(
builder.In("`type`", ConversationCountedCommentType()),
))
return builder.Update(builder.Eq{"num_comments": subQuery}).
From("`issue`").Where(builder.Eq{"id": issueID})
}
func UpdateIssueNumComments(ctx context.Context, issueID int64) error {
_, err := db.GetEngine(ctx).Exec(UpdateIssueNumCommentsBuilder(issueID))
return err
}
// InsertIssueComments inserts many comments of issues. // InsertIssueComments inserts many comments of issues.
func InsertIssueComments(ctx context.Context, comments []*Comment) error { func InsertIssueComments(ctx context.Context, comments []*Comment) error {
if len(comments) == 0 { if len(comments) == 0 {
@ -1332,8 +1361,7 @@ func InsertIssueComments(ctx context.Context, comments []*Comment) error {
} }
for _, issueID := range issueIDs { for _, issueID := range issueIDs {
if _, err := db.Exec(ctx, "UPDATE issue set num_comments = (SELECT count(*) FROM comment WHERE issue_id = ? AND `type`=?) WHERE id = ?", if err := UpdateIssueNumComments(ctx, issueID); err != nil {
issueID, CommentTypeComment, issueID); err != nil {
return err return err
} }
} }

View file

@ -125,3 +125,12 @@ func TestUpdateCommentsMigrationsByType(t *testing.T) {
assert.Empty(t, comment.OriginalAuthorID) assert.Empty(t, comment.OriginalAuthorID)
assert.EqualValues(t, 513, comment.PosterID) assert.EqualValues(t, 513, comment.PosterID)
} }
func Test_UpdateIssueNumComments(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
require.NoError(t, issues_model.UpdateIssueNumComments(db.DefaultContext, issue2.ID))
issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.EqualValues(t, 1, issue2.NumComments)
}

View file

@ -614,6 +614,10 @@ func InsertReviews(ctx context.Context, reviews []*Review) error {
return err return err
} }
} }
if err := UpdateIssueNumComments(ctx, review.IssueID); err != nil {
return err
}
} }
return committer.Commit() return committer.Commit()

View file

@ -126,6 +126,14 @@ func (p *Project) LoadRepo(ctx context.Context) (err error) {
return err return err
} }
func ProjectLinkForOrg(org *user_model.User, projectID int64) string { //nolint
return fmt.Sprintf("%s/-/projects/%d", org.HomeLink(), projectID)
}
func ProjectLinkForRepo(repo *repo_model.Repository, projectID int64) string { //nolint
return fmt.Sprintf("%s/projects/%d", repo.Link(), projectID)
}
// Link returns the project's relative URL. // Link returns the project's relative URL.
func (p *Project) Link(ctx context.Context) string { func (p *Project) Link(ctx context.Context) string {
if p.OwnerID > 0 { if p.OwnerID > 0 {
@ -134,7 +142,7 @@ func (p *Project) Link(ctx context.Context) string {
log.Error("LoadOwner: %v", err) log.Error("LoadOwner: %v", err)
return "" return ""
} }
return fmt.Sprintf("%s/-/projects/%d", p.Owner.HomeLink(), p.ID) return ProjectLinkForOrg(p.Owner, p.ID)
} }
if p.RepoID > 0 { if p.RepoID > 0 {
err := p.LoadRepo(ctx) err := p.LoadRepo(ctx)
@ -142,7 +150,7 @@ func (p *Project) Link(ctx context.Context) string {
log.Error("LoadRepo: %v", err) log.Error("LoadRepo: %v", err)
return "" return ""
} }
return fmt.Sprintf("%s/projects/%d", p.Repo.Link(), p.ID) return ProjectLinkForRepo(p.Repo, p.ID)
} }
return "" return ""
} }

View file

@ -19,6 +19,8 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"xorm.io/builder"
) )
// Init initialize model // Init initialize model
@ -27,7 +29,7 @@ func Init(ctx context.Context) error {
} }
type repoChecker struct { type repoChecker struct {
querySQL func(ctx context.Context) ([]map[string][]byte, error) querySQL func(ctx context.Context) ([]int64, error)
correctSQL func(ctx context.Context, id int64) error correctSQL func(ctx context.Context, id int64) error
desc string desc string
} }
@ -38,8 +40,7 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
log.Error("Select %s: %v", checker.desc, err) log.Error("Select %s: %v", checker.desc, err)
return return
} }
for _, result := range results { for _, id := range results {
id, _ := strconv.ParseInt(string(result["id"]), 10, 64)
select { select {
case <-ctx.Done(): case <-ctx.Done():
log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id) log.Warn("CheckRepoStats: Cancelled before checking %s for with id=%d", checker.desc, id)
@ -54,21 +55,23 @@ func repoStatsCheck(ctx context.Context, checker *repoChecker) {
} }
} }
func StatsCorrectSQL(ctx context.Context, sql string, id int64) error { func StatsCorrectSQL(ctx context.Context, sql any, ids ...any) error {
_, err := db.GetEngine(ctx).Exec(sql, id, id) args := []any{sql}
args = append(args, ids...)
_, err := db.GetEngine(ctx).Exec(args...)
return err return err
} }
func repoStatsCorrectNumWatches(ctx context.Context, id int64) error { func repoStatsCorrectNumWatches(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?", id, id)
} }
func repoStatsCorrectNumStars(ctx context.Context, id int64) error { func repoStatsCorrectNumStars(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `repository` SET num_stars=(SELECT COUNT(*) FROM `star` WHERE repo_id=?) WHERE id=?", id, id)
} }
func labelStatsCorrectNumIssues(ctx context.Context, id int64) error { func labelStatsCorrectNumIssues(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `label` SET num_issues=(SELECT COUNT(*) FROM `issue_label` WHERE label_id=?) WHERE id=?", id, id)
} }
func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error { func labelStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
@ -105,11 +108,11 @@ func milestoneStatsCorrectNumIssuesRepo(ctx context.Context, id int64) error {
} }
func userStatsCorrectNumRepos(ctx context.Context, id int64) error { func userStatsCorrectNumRepos(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id) return StatsCorrectSQL(ctx, "UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?", id, id)
} }
func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error { func repoStatsCorrectIssueNumComments(ctx context.Context, id int64) error {
return StatsCorrectSQL(ctx, "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?", id) return StatsCorrectSQL(ctx, issues_model.UpdateIssueNumCommentsBuilder(id))
} }
func repoStatsCorrectNumIssues(ctx context.Context, id int64) error { func repoStatsCorrectNumIssues(ctx context.Context, id int64) error {
@ -128,9 +131,12 @@ func repoStatsCorrectNumClosedPulls(ctx context.Context, id int64) error {
return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true) return repo_model.UpdateRepoIssueNumbers(ctx, id, true, true)
} }
func statsQuery(args ...any) func(context.Context) ([]map[string][]byte, error) { // statsQuery returns a function that queries the database for a list of IDs
return func(ctx context.Context) ([]map[string][]byte, error) { // sql could be a string or a *builder.Builder
return db.GetEngine(ctx).Query(args...) func statsQuery(sql any, args ...any) func(context.Context) ([]int64, error) {
return func(ctx context.Context) ([]int64, error) {
var ids []int64
return ids, db.GetEngine(ctx).SQL(sql, args...).Find(&ids)
} }
} }
@ -201,7 +207,16 @@ func CheckRepoStats(ctx context.Context) error {
}, },
// Issue.NumComments // Issue.NumComments
{ {
statsQuery("SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)"), statsQuery(builder.Select("`issue`.id").From("`issue`").Where(
builder.Neq{
"`issue`.num_comments": builder.Select("COUNT(*)").From("`comment`").Where(
builder.Expr("issue_id = `issue`.id").And(
builder.In("type", issues_model.ConversationCountedCommentType()),
),
),
},
),
),
repoStatsCorrectIssueNumComments, repoStatsCorrectIssueNumComments,
"issue count 'num_comments'", "issue count 'num_comments'",
}, },

View file

@ -7,8 +7,10 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -22,3 +24,16 @@ func TestDoctorUserStarNum(t *testing.T) {
require.NoError(t, DoctorUserStarNum(db.DefaultContext)) require.NoError(t, DoctorUserStarNum(db.DefaultContext))
} }
func Test_repoStatsCorrectIssueNumComments(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.NotNil(t, issue2)
assert.EqualValues(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here
require.NoError(t, repoStatsCorrectIssueNumComments(db.DefaultContext, 2))
// reload the issue
issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
assert.EqualValues(t, 1, issue2.NumComments)
}

View file

@ -59,6 +59,13 @@ func InitSettings() {
_ = hash.Register("dummy", hash.NewDummyHasher) _ = hash.Register("dummy", hash.NewDummyHasher)
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
setting.InitGiteaEnvVars()
// Avoid loading the git's system config.
// On macOS, system config sets the osxkeychain credential helper, which will cause tests to freeze with a dialog.
// But we do not set it in production at the moment, because it might be a "breaking" change,
// more details are in "modules/git.commonBaseEnvs".
_ = os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
} }
// TestOptions represents test options // TestOptions represents test options

View file

@ -176,3 +176,14 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
return filelist, err return filelist, err
} }
// GetTreePathLatestCommitID returns the latest commit of a tree path
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
AddDynamicArguments(refName).AddDashesAndList(treePath).
RunStdString(&RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
return repo.GetCommit(strings.TrimSpace(stdout))
}

View file

@ -26,3 +26,18 @@ func TestSubTree_Issue29101(t *testing.T) {
assert.True(t, IsErrNotExist(err)) assert.True(t, IsErrNotExist(err))
} }
} }
func Test_GetTreePathLatestCommit(t *testing.T) {
repo, err := openRepositoryWithDefaultContext(filepath.Join(testReposDir, "repo6_blame"))
require.NoError(t, err)
defer repo.Close()
commitID, err := repo.GetBranchCommitID("master")
require.NoError(t, err)
assert.EqualValues(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID)
commit, err := repo.GetTreePathLatestCommit("master", "blame.txt")
require.NoError(t, err)
assert.NotNil(t, commit)
assert.EqualValues(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String())
}

View file

@ -168,3 +168,22 @@ func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
} }
return changed return changed
} }
// InitGiteaEnvVars initializes the environment variables for gitea
func InitGiteaEnvVars() {
// Ideally Gitea should only accept the environment variables which it clearly knows instead of unsetting the ones it doesn't want,
// but the ideal behavior would be a breaking change, and it seems not bringing enough benefits to end users,
// so at the moment we could still keep "unsetting the unnecessary environments"
// HOME is managed by Gitea, Gitea's git should use "HOME/.gitconfig".
// But git would try "XDG_CONFIG_HOME/git/config" first if "HOME/.gitconfig" does not exist,
// then our git.InitFull would still write to "XDG_CONFIG_HOME/git/config" if XDG_CONFIG_HOME is set.
_ = os.Unsetenv("XDG_CONFIG_HOME")
_ = os.Unsetenv("GIT_AUTHOR_NAME")
_ = os.Unsetenv("GIT_AUTHOR_EMAIL")
_ = os.Unsetenv("GIT_AUTHOR_DATE")
_ = os.Unsetenv("GIT_COMMITTER_NAME")
_ = os.Unsetenv("GIT_COMMITTER_EMAIL")
_ = os.Unsetenv("GIT_COMMITTER_DATE")
}

View file

@ -5,8 +5,6 @@ package setting
import ( import (
"time" "time"
"code.gitea.io/gitea/modules/log"
) )
// CORSConfig defines CORS settings // CORSConfig defines CORS settings
@ -28,7 +26,4 @@ var CORSConfig = struct {
func loadCorsFrom(rootCfg ConfigProvider) { func loadCorsFrom(rootCfg ConfigProvider) {
mustMapSetting(rootCfg, "cors", &CORSConfig) mustMapSetting(rootCfg, "cors", &CORSConfig)
if CORSConfig.Enabled {
log.Info("CORS Service Enabled")
}
} }

View file

@ -109,7 +109,7 @@ func IndexerGlobFromString(globstr string) []Glob {
expr = strings.TrimSpace(expr) expr = strings.TrimSpace(expr)
if expr != "" { if expr != "" {
if g, err := glob.Compile(expr, '.', '/'); err != nil { if g, err := glob.Compile(expr, '.', '/'); err != nil {
log.Info("Invalid glob expression '%s' (skipped): %v", expr, err) log.Warn("Invalid glob expression '%s' (skipped): %v", expr, err)
} else { } else {
extarr = append(extarr, Glob{glob: g, pattern: expr}) extarr = append(extarr, Glob{glob: g, pattern: expr})
} }

View file

@ -263,8 +263,6 @@ func loadMailerFrom(rootCfg ConfigProvider) {
MailService.OverrideEnvelopeFrom = true MailService.OverrideEnvelopeFrom = true
MailService.EnvelopeFrom = parsed.Address MailService.EnvelopeFrom = parsed.Address
} }
log.Info("Mail Service Enabled")
} }
func loadRegisterMailFrom(rootCfg ConfigProvider) { func loadRegisterMailFrom(rootCfg ConfigProvider) {
@ -275,7 +273,6 @@ func loadRegisterMailFrom(rootCfg ConfigProvider) {
return return
} }
Service.RegisterEmailConfirm = true Service.RegisterEmailConfirm = true
log.Info("Register Mail Service Enabled")
} }
func loadNotifyMailFrom(rootCfg ConfigProvider) { func loadNotifyMailFrom(rootCfg ConfigProvider) {
@ -286,7 +283,6 @@ func loadNotifyMailFrom(rootCfg ConfigProvider) {
return return
} }
Service.EnableNotifyMail = true Service.EnableNotifyMail = true
log.Info("Notify Mail Service Enabled")
} }
func tryResolveAddr(addr string) []net.IPAddr { func tryResolveAddr(addr string) []net.IPAddr {

View file

@ -73,6 +73,4 @@ func loadSessionFrom(rootCfg ConfigProvider) {
SessionConfig.ProviderConfig = string(shadowConfig) SessionConfig.ProviderConfig = string(shadowConfig)
SessionConfig.OriginalProvider = SessionConfig.Provider SessionConfig.OriginalProvider = SessionConfig.Provider
SessionConfig.Provider = "VirtualSession" SessionConfig.Provider = "VirtualSession"
log.Info("Session Service Enabled")
} }

View file

@ -20,7 +20,6 @@ func loadTimeFrom(rootCfg ConfigProvider) {
if err != nil { if err != nil {
log.Fatal("Load time zone failed: %v", err) log.Fatal("Load time zone failed: %v", err)
} }
log.Info("Default UI Location is %v", zone)
} }
if DefaultUILocation == nil { if DefaultUILocation == nil {
DefaultUILocation = time.Local DefaultUILocation = time.Local

View file

@ -3643,6 +3643,7 @@ versions = Versions
versions.view_all = View all versions.view_all = View all
dependency.id = ID dependency.id = ID
dependency.version = Version dependency.version = Version
search_in_external_registry = Search in %s
alpine.registry = Setup this registry by adding the url in your <code>/etc/apk/repositories</code> file: alpine.registry = Setup this registry by adding the url in your <code>/etc/apk/repositories</code> file:
alpine.registry.key = Download the registry public RSA key into the <code>/etc/apk/keys/</code> folder to verify the index signature: alpine.registry.key = Download the registry public RSA key into the <code>/etc/apk/keys/</code> folder to verify the index signature:
alpine.registry.info = Choose $branch and $repository from the list below. alpine.registry.info = Choose $branch and $repository from the list below.

1
release-notes/6471.md Normal file
View file

@ -0,0 +1 @@
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/76a85d26c8576fc410dc6494f2907ffc2b353c39) Use `Project-URL` metadata field to get a PyPI package's homepage URL

View file

@ -10,6 +10,7 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"unicode"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -139,9 +140,30 @@ func UploadPackageFile(ctx *context.Context) {
return return
} }
projectURL := ctx.Req.FormValue("home_page") // Ensure ctx.Req.Form exists.
if !validation.IsValidURL(projectURL) { _ = ctx.Req.ParseForm()
projectURL = ""
var homepageURL string
projectURLs := ctx.Req.Form["project_urls"]
for _, purl := range projectURLs {
label, url, found := strings.Cut(purl, ",")
if !found {
continue
}
if normalizeLabel(label) != "homepage" {
continue
}
homepageURL = strings.TrimSpace(url)
break
}
if len(homepageURL) == 0 {
// TODO: Home-page is a deprecated metadata field. Remove this branch once it's no longer apart of the spec.
homepageURL = ctx.Req.FormValue("home_page")
}
if !validation.IsValidURL(homepageURL) {
homepageURL = ""
} }
_, _, err = packages_service.CreatePackageOrAddFileToExisting( _, _, err = packages_service.CreatePackageOrAddFileToExisting(
@ -160,7 +182,7 @@ func UploadPackageFile(ctx *context.Context) {
Description: ctx.Req.FormValue("description"), Description: ctx.Req.FormValue("description"),
LongDescription: ctx.Req.FormValue("long_description"), LongDescription: ctx.Req.FormValue("long_description"),
Summary: ctx.Req.FormValue("summary"), Summary: ctx.Req.FormValue("summary"),
ProjectURL: projectURL, ProjectURL: homepageURL,
License: ctx.Req.FormValue("license"), License: ctx.Req.FormValue("license"),
RequiresPython: ctx.Req.FormValue("requires_python"), RequiresPython: ctx.Req.FormValue("requires_python"),
}, },
@ -189,6 +211,23 @@ func UploadPackageFile(ctx *context.Context) {
ctx.Status(http.StatusCreated) ctx.Status(http.StatusCreated)
} }
// Normalizes a Project-URL label.
// See https://packaging.python.org/en/latest/specifications/well-known-project-urls/#label-normalization.
func normalizeLabel(label string) string {
var builder strings.Builder
// "A label is normalized by deleting all ASCII punctuation and whitespace, and then converting the result
// to lowercase."
for _, r := range label {
if unicode.IsPunct(r) || unicode.IsSpace(r) {
continue
}
builder.WriteRune(unicode.ToLower(r))
}
return builder.String()
}
func isValidNameAndVersion(packageName, packageVersion string) bool { func isValidNameAndVersion(packageName, packageVersion string) bool {
return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion) return nameMatcher.MatchString(packageName) && versionMatcher.MatchString(packageVersion)
} }

View file

@ -36,3 +36,13 @@ func TestIsValidNameAndVersion(t *testing.T) {
assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa")) assert.False(t, isValidNameAndVersion("test-name", "1.0.1aa"))
assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta")) assert.False(t, isValidNameAndVersion("test-name", "1.0.0-alpha.beta"))
} }
func TestNormalizeLabel(t *testing.T) {
// Cases fetched from https://packaging.python.org/en/latest/specifications/well-known-project-urls/#label-normalization.
assert.Equal(t, "homepage", normalizeLabel("Homepage"))
assert.Equal(t, "homepage", normalizeLabel("Home-page"))
assert.Equal(t, "homepage", normalizeLabel("Home page"))
assert.Equal(t, "changelog", normalizeLabel("Change_Log"))
assert.Equal(t, "whatsnew", normalizeLabel("What's New?"))
assert.Equal(t, "github", normalizeLabel("github"))
}

View file

@ -11,7 +11,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"path"
"strings" "strings"
"time" "time"
@ -247,19 +246,14 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn
return nil, nil, nil return nil, nil, nil
} }
info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:]) latestCommit, err := ctx.Repo.GitRepo.GetTreePathLatestCommit(ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetCommitsInfo", err) ctx.Error(http.StatusInternalServerError, "GetTreePathLatestCommit", err)
return nil, nil, nil return nil, nil, nil
} }
when := &latestCommit.Committer.When
if len(info) == 1 { return entry.Blob(), entry, when
// Not Modified
lastModified = &info[0].Commit.Committer.When
}
blob = entry.Blob()
return blob, entry, lastModified
} }
// GetArchive get archive of a repository // GetArchive get archive of a repository

View file

@ -209,7 +209,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return return
} }
ctx.JSONRedirect(fmt.Sprintf("%s/-/projects/%d", ctx.ContextUser.HomeLink(), id)) ctx.JSONRedirect(project_model.ProjectLinkForOrg(ctx.ContextUser, id))
} }
// DeleteProject delete a project // DeleteProject delete a project
@ -259,7 +259,7 @@ func RenderEditProject(ctx *context.Context) {
ctx.Data["redirect"] = ctx.FormString("redirect") ctx.Data["redirect"] = ctx.FormString("redirect")
ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink()
ctx.Data["card_type"] = p.CardType ctx.Data["card_type"] = p.CardType
ctx.Data["CancelLink"] = fmt.Sprintf("%s/-/projects/%d", ctx.ContextUser.HomeLink(), p.ID) ctx.Data["CancelLink"] = project_model.ProjectLinkForOrg(ctx.ContextUser, p.ID)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
} }
@ -273,7 +273,7 @@ func EditProjectPost(ctx *context.Context) {
ctx.Data["PageIsViewProjects"] = true ctx.Data["PageIsViewProjects"] = true
ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["CanWriteProjects"] = canWriteProjects(ctx)
ctx.Data["CardTypes"] = project_model.GetCardConfig() ctx.Data["CardTypes"] = project_model.GetCardConfig()
ctx.Data["CancelLink"] = fmt.Sprintf("%s/-/projects/%d", ctx.ContextUser.HomeLink(), projectID) ctx.Data["CancelLink"] = project_model.ProjectLinkForOrg(ctx.ContextUser, projectID)
shared_user.RenderUserHeader(ctx) shared_user.RenderUserHeader(ctx)

View file

@ -5,7 +5,6 @@
package repo package repo
import ( import (
"path"
"time" "time"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -82,7 +81,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim
return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified)
} }
func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified *time.Time) { func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) {
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
if err != nil { if err != nil {
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {
@ -98,19 +97,14 @@ func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified *time.T
return nil, nil return nil, nil
} }
info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:]) latestCommit, err := ctx.Repo.GitRepo.GetTreePathLatestCommit(ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath)
if err != nil { if err != nil {
ctx.ServerError("GetCommitsInfo", err) ctx.ServerError("GetTreePathLatestCommit", err)
return nil, nil return nil, nil
} }
lastModified := &latestCommit.Committer.When
if len(info) == 1 { return entry.Blob(), lastModified
// Not Modified
lastModified = &info[0].Commit.Committer.When
}
blob = entry.Blob()
return blob, lastModified
} }
// SingleDownload download a file by repos path // SingleDownload download a file by repos path

View file

@ -1291,10 +1291,17 @@ func NewIssuePost(ctx *context.Context) {
log.Trace("Issue created: %d/%d", repo.ID, issue.ID) log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
if ctx.FormString("redirect_after_creation") == "project" && projectID > 0 { if ctx.FormString("redirect_after_creation") == "project" && projectID > 0 {
ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects/" + strconv.FormatInt(projectID, 10)) project, err := project_model.GetProjectByID(ctx, projectID)
} else { if err == nil {
ctx.JSONRedirect(issue.Link()) if project.Type == project_model.TypeOrganization {
ctx.JSONRedirect(project_model.ProjectLinkForOrg(ctx.Repo.Owner, project.ID))
} else {
ctx.JSONRedirect(project_model.ProjectLinkForRepo(repo, project.ID))
}
return
}
} }
ctx.JSONRedirect(issue.Link())
} }
// roleDescriptor returns the role descriptor for a comment in/with the given repo, poster and issue // roleDescriptor returns the role descriptor for a comment in/with the given repo, poster and issue

View file

@ -183,7 +183,7 @@ func ChangeProjectStatus(ctx *context.Context) {
ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err) ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
return return
} }
ctx.JSONRedirect(fmt.Sprintf("%s/projects/%d", ctx.Repo.RepoLink, id)) ctx.JSONRedirect(project_model.ProjectLinkForRepo(ctx.Repo.Repository, id))
} }
// DeleteProject delete a project // DeleteProject delete a project
@ -237,7 +237,7 @@ func RenderEditProject(ctx *context.Context) {
ctx.Data["content"] = p.Description ctx.Data["content"] = p.Description
ctx.Data["card_type"] = p.CardType ctx.Data["card_type"] = p.CardType
ctx.Data["redirect"] = ctx.FormString("redirect") ctx.Data["redirect"] = ctx.FormString("redirect")
ctx.Data["CancelLink"] = fmt.Sprintf("%s/projects/%d", ctx.Repo.Repository.Link(), p.ID) ctx.Data["CancelLink"] = project_model.ProjectLinkForRepo(ctx.Repo.Repository, p.ID)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
} }
@ -251,7 +251,7 @@ func EditProjectPost(ctx *context.Context) {
ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["CardTypes"] = project_model.GetCardConfig() ctx.Data["CardTypes"] = project_model.GetCardConfig()
ctx.Data["CancelLink"] = fmt.Sprintf("%s/projects/%d", ctx.Repo.Repository.Link(), projectID) ctx.Data["CancelLink"] = project_model.ProjectLinkForRepo(ctx.Repo.Repository, projectID)
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)

View file

@ -5,6 +5,7 @@ package pull
import ( import (
"fmt" "fmt"
"strings"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -66,7 +67,10 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error {
if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() {
// add trailer // add trailer
message += fmt.Sprintf("\nCo-authored-by: %s\nCo-committed-by: %s\n", sig.String(), sig.String()) if !strings.Contains(message, fmt.Sprintf("Co-authored-by: %s", sig.String())) {
message += fmt.Sprintf("\nCo-authored-by: %s", sig.String())
}
message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String())
} }
cmdCommit := git.NewCommand(ctx, "commit"). cmdCommit := git.NewCommand(ctx, "commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email).

View file

@ -10,9 +10,12 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
webhook_model "code.gitea.io/gitea/models/webhook" webhook_model "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook" webhook_module "code.gitea.io/gitea/modules/webhook"
"code.gitea.io/gitea/services/convert"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -98,3 +101,11 @@ func TestPrepareWebhooksBranchFilterNoMatch(t *testing.T) {
}) })
} }
} }
func TestWebhookUserMail(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
setting.Service.NoReplyAddress = "no-reply.com"
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
assert.Equal(t, user.GetPlaceholderEmail(), convert.ToUser(db.DefaultContext, user, nil).Email)
assert.Equal(t, user.Email, convert.ToUser(db.DefaultContext, user, user).Email)
}

View file

@ -35,11 +35,12 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{$tooltipSearchInNuget := ctx.Locale.Tr "packages.search_in_external_registry" "nuget.org"}}
{{range $framework, $dependencies := .PackageDescriptor.Metadata.Dependencies}} {{range $framework, $dependencies := .PackageDescriptor.Metadata.Dependencies}}
{{range $dependencies}} {{range $dependencies}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}} <a target="_blank" rel="noreferrer" href="https://www.nuget.org/packages/{{.ID}}" data-tooltip-content="{{$tooltipSearchInNuget}}">{{svg "octicon-link-external"}}</a></td>
<td>{{.Version}}</td> <td>{{.Version}} <a target="_blank" rel="noreferrer" href="https://www.nuget.org/packages/{{.ID}}/{{.Version}}" data-tooltip-content="{{$tooltipSearchInNuget}}">{{svg "octicon-link-external"}}</a></td>
<td>{{$framework}}</td> <td>{{$framework}}</td>
</tr> </tr>
{{end}} {{end}}

View file

@ -40,13 +40,6 @@ func TestMain(m *testing.M) {
initChangedFiles() initChangedFiles()
testE2eWebRoutes = routers.NormalRoutes() testE2eWebRoutes = routers.NormalRoutes()
os.Unsetenv("GIT_AUTHOR_NAME")
os.Unsetenv("GIT_AUTHOR_EMAIL")
os.Unsetenv("GIT_AUTHOR_DATE")
os.Unsetenv("GIT_COMMITTER_NAME")
os.Unsetenv("GIT_COMMITTER_EMAIL")
os.Unsetenv("GIT_COMMITTER_DATE")
err := unittest.InitFixtures( err := unittest.InitFixtures(
unittest.FixturesOptions{ unittest.FixturesOptions{
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"), Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),

View file

@ -33,15 +33,16 @@ func TestPackagePyPI(t *testing.T) {
packageVersion := "1!1.0.1+r1234" packageVersion := "1!1.0.1+r1234"
packageAuthor := "KN4CK3R" packageAuthor := "KN4CK3R"
packageDescription := "Test Description" packageDescription := "Test Description"
projectURL := "https://example.com"
content := "test" content := "test"
hashSHA256 := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" hashSHA256 := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
root := fmt.Sprintf("/api/packages/%s/pypi", user.Name) root := fmt.Sprintf("/api/packages/%s/pypi", user.Name)
uploadFile := func(t *testing.T, filename, content string, expectedStatus int) { createBasicMultipartFile := func(filename, packageName, content string) (body *bytes.Buffer, writer *multipart.Writer, closer func() error) {
body := &bytes.Buffer{} body = &bytes.Buffer{}
writer := multipart.NewWriter(body) writer = multipart.NewWriter(body)
part, _ := writer.CreateFormFile("content", filename) part, _ := writer.CreateFormFile("content", filename)
_, _ = io.Copy(part, strings.NewReader(content)) _, _ = io.Copy(part, strings.NewReader(content))
@ -53,14 +54,27 @@ func TestPackagePyPI(t *testing.T) {
writer.WriteField("sha256_digest", hashSHA256) writer.WriteField("sha256_digest", hashSHA256)
writer.WriteField("requires_python", "3.6") writer.WriteField("requires_python", "3.6")
_ = writer.Close() return body, writer, writer.Close
}
uploadHelper := func(t *testing.T, body *bytes.Buffer, contentType string, expectedStatus int) {
req := NewRequestWithBody(t, "POST", root, body). req := NewRequestWithBody(t, "POST", root, body).
SetHeader("Content-Type", writer.FormDataContentType()). SetHeader("Content-Type", contentType).
AddBasicAuth(user.Name) AddBasicAuth(user.Name)
MakeRequest(t, req, expectedStatus) MakeRequest(t, req, expectedStatus)
} }
uploadFile := func(t *testing.T, filename, content string, expectedStatus int) {
body, writer, closeFunc := createBasicMultipartFile(filename, packageName, content)
writer.WriteField("project_urls", "DOCUMENTATION , https://readthedocs.org")
writer.WriteField("project_urls", fmt.Sprintf("Home-page, %s", projectURL))
_ = closeFunc()
uploadHelper(t, body, writer.FormDataContentType(), expectedStatus)
}
t.Run("Upload", func(t *testing.T) { t.Run("Upload", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -75,6 +89,7 @@ func TestPackagePyPI(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Nil(t, pd.SemVer) assert.Nil(t, pd.SemVer)
assert.IsType(t, &pypi.Metadata{}, pd.Metadata) assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
assert.Equal(t, projectURL, pd.Metadata.(*pypi.Metadata).ProjectURL)
assert.Equal(t, packageName, pd.Package.Name) assert.Equal(t, packageName, pd.Package.Name)
assert.Equal(t, packageVersion, pd.Version.Version) assert.Equal(t, packageVersion, pd.Version.Version)
@ -134,6 +149,48 @@ func TestPackagePyPI(t *testing.T) {
uploadFile(t, "test.tar.gz", content, http.StatusConflict) uploadFile(t, "test.tar.gz", content, http.StatusConflict)
}) })
t.Run("UploadUsingDeprecatedHomepageMetadata", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pkgName := "homepage-package"
body, writer, closeFunc := createBasicMultipartFile("test.whl", pkgName, content)
writer.WriteField("home_page", projectURL)
_ = closeFunc()
uploadHelper(t, body, writer.FormDataContentType(), http.StatusCreated)
pvs, err := packages.GetVersionsByPackageName(db.DefaultContext, user.ID, packages.TypePyPI, pkgName)
require.NoError(t, err)
assert.Len(t, pvs, 1)
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
require.NoError(t, err)
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
assert.Equal(t, projectURL, pd.Metadata.(*pypi.Metadata).ProjectURL)
})
t.Run("UploadWithoutAnyHomepageURLMetadata", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
pkgName := "no-project-url-or-homepage-package"
body, writer, closeFunc := createBasicMultipartFile("test.whl", pkgName, content)
_ = closeFunc()
uploadHelper(t, body, writer.FormDataContentType(), http.StatusCreated)
pvs, err := packages.GetVersionsByPackageName(db.DefaultContext, user.ID, packages.TypePyPI, pkgName)
require.NoError(t, err)
assert.Len(t, pvs, 1)
pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0])
require.NoError(t, err)
assert.IsType(t, &pypi.Metadata{}, pd.Metadata)
assert.Empty(t, pd.Metadata.(*pypi.Metadata).ProjectURL)
})
t.Run("Download", func(t *testing.T) { t.Run("Download", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -148,7 +205,7 @@ func TestPackagePyPI(t *testing.T) {
downloadFile("test.whl") downloadFile("test.whl")
downloadFile("test.tar.gz") downloadFile("test.tar.gz")
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypePyPI) pvs, err := packages.GetVersionsByPackageName(db.DefaultContext, user.ID, packages.TypePyPI, packageName)
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, pvs, 1) assert.Len(t, pvs, 1)
assert.Equal(t, int64(2), pvs[0].DownloadCount) assert.Equal(t, int64(2), pvs[0].DownloadCount)

View file

@ -159,18 +159,6 @@ func TestMain(m *testing.M) {
} }
} }
os.Unsetenv("GIT_AUTHOR_NAME")
os.Unsetenv("GIT_AUTHOR_EMAIL")
os.Unsetenv("GIT_AUTHOR_DATE")
os.Unsetenv("GIT_COMMITTER_NAME")
os.Unsetenv("GIT_COMMITTER_EMAIL")
os.Unsetenv("GIT_COMMITTER_DATE")
// Avoid loading the default system config. On MacOS, this config
// sets the osxkeychain credential helper, which will cause tests
// to freeze with a dialog.
os.Setenv("GIT_CONFIG_NOSYSTEM", "true")
err := unittest.InitFixtures( err := unittest.InitFixtures(
unittest.FixturesOptions{ unittest.FixturesOptions{
Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"), Dir: filepath.Join(filepath.Dir(setting.AppPath), "models/fixtures/"),