Merge branch 'main' into lunny/issue_dev

This commit is contained in:
Lunny Xiao
2024-10-01 18:12:49 -07:00
69 changed files with 1936 additions and 930 deletions
+3 -3
View File
@@ -17,7 +17,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/oauth2_provider"
)
// Ensure the struct implements the interface.
@@ -31,7 +31,7 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 {
if !strings.Contains(accessToken, ".") {
return 0
}
token, err := oauth2.ParseToken(accessToken, oauth2.DefaultSigningKey)
token, err := oauth2_provider.ParseToken(accessToken, oauth2_provider.DefaultSigningKey)
if err != nil {
log.Trace("oauth2.ParseToken: %v", err)
return 0
@@ -40,7 +40,7 @@ func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 {
if grant, err = auth_model.GetOAuth2GrantByID(ctx, token.GrantID); err != nil || grant == nil {
return 0
}
if token.Type != oauth2.TypeAccessToken {
if token.Kind != oauth2_provider.KindAccessToken {
return 0
}
if token.ExpiresAt.Before(time.Now()) || token.IssuedAt.After(time.Now()) {
-4
View File
@@ -30,10 +30,6 @@ const ProviderHeaderKey = "gitea-oauth2-provider"
// Init initializes the oauth source
func Init(ctx context.Context) error {
if err := InitSigningKey(); err != nil {
return err
}
// Lock our mutex
gothRWMutex.Lock()
+7
View File
@@ -404,6 +404,13 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
ctx.Data["PushMirrors"] = pushMirrors
ctx.Data["RepoName"] = ctx.Repo.Repository.Name
ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetRepoLicenses", err)
return
}
ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
}
// RepoAssignment returns a middleware to handle repository assignment
+6
View File
@@ -175,6 +175,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
language = repo.PrimaryLanguage.Language
}
repoLicenses, err := repo_model.GetRepoLicenses(ctx, repo)
if err != nil {
return nil
}
repoAPIURL := repo.APIURL()
return &api.Repository{
@@ -238,6 +243,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
RepoTransfer: transfer,
Topics: repo.Topics,
ObjectFormatName: repo.ObjectFormatName,
Licenses: repoLicenses.StringList(),
}
}
+11
View File
@@ -156,6 +156,16 @@ func registerCleanupPackages() {
})
}
func registerSyncRepoLicenses() {
RegisterTaskFatal("sync_repo_licenses", &BaseConfig{
Enabled: false,
RunAtStart: false,
Schedule: "@annually",
}, func(ctx context.Context, _ *user_model.User, config Config) error {
return repo_service.SyncRepoLicenses(ctx)
})
}
func initBasicTasks() {
if setting.Mirror.Enabled {
registerUpdateMirrorTask()
@@ -172,4 +182,5 @@ func initBasicTasks() {
if setting.Packages.Enabled {
registerCleanupPackages()
}
registerSyncRepoLicenses()
}
@@ -26,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
repo_service "code.gitea.io/gitea/services/repository"
"github.com/stretchr/testify/assert"
)
@@ -302,6 +303,8 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) {
toRepoName := "migrated"
uploader := NewGiteaLocalUploader(context.Background(), fromRepoOwner, fromRepoOwner.Name, toRepoName)
uploader.gitServiceType = structs.GiteaService
assert.NoError(t, repo_service.Init(context.Background()))
assert.NoError(t, uploader.CreateRepo(&base.Repository{
Description: "description",
OriginalURL: fromRepo.RepoPath(),
+9
View File
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
repo_service "code.gitea.io/gitea/services/repository"
)
// gitShortEmptySha Git short empty SHA
@@ -559,6 +560,14 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
}
}
// Update License
if err = repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
RepoID: m.Repo.ID,
}); err != nil {
log.Error("SyncMirrors [repo: %-v]: unable to add repo to license updater queue: %v", m.Repo, err)
return false
}
log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo)
return true
+214
View File
@@ -0,0 +1,214 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2_provider //nolint
import (
"context"
"fmt"
auth "code.gitea.io/gitea/models/auth"
org_model "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"github.com/golang-jwt/jwt/v5"
)
// AccessTokenErrorCode represents an error code specified in RFC 6749
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
type AccessTokenErrorCode string
const (
// AccessTokenErrorCodeInvalidRequest represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidRequest AccessTokenErrorCode = "invalid_request"
// AccessTokenErrorCodeInvalidClient represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidClient = "invalid_client"
// AccessTokenErrorCodeInvalidGrant represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidGrant = "invalid_grant"
// AccessTokenErrorCodeUnauthorizedClient represents an error code specified in RFC 6749
AccessTokenErrorCodeUnauthorizedClient = "unauthorized_client"
// AccessTokenErrorCodeUnsupportedGrantType represents an error code specified in RFC 6749
AccessTokenErrorCodeUnsupportedGrantType = "unsupported_grant_type"
// AccessTokenErrorCodeInvalidScope represents an error code specified in RFC 6749
AccessTokenErrorCodeInvalidScope = "invalid_scope"
)
// AccessTokenError represents an error response specified in RFC 6749
// https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
type AccessTokenError struct {
ErrorCode AccessTokenErrorCode `json:"error" form:"error"`
ErrorDescription string `json:"error_description"`
}
// Error returns the error message
func (err AccessTokenError) Error() string {
return fmt.Sprintf("%s: %s", err.ErrorCode, err.ErrorDescription)
}
// TokenType specifies the kind of token
type TokenType string
const (
// TokenTypeBearer represents a token type specified in RFC 6749
TokenTypeBearer TokenType = "bearer"
// TokenTypeMAC represents a token type specified in RFC 6749
TokenTypeMAC = "mac"
)
// AccessTokenResponse represents a successful access token response
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
type AccessTokenResponse struct {
AccessToken string `json:"access_token"`
TokenType TokenType `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
IDToken string `json:"id_token,omitempty"`
}
func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
if setting.OAuth2.InvalidateRefreshTokens {
if err := grant.IncreaseCounter(ctx); err != nil {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidGrant,
ErrorDescription: "cannot increase the grant counter",
}
}
}
// generate access token to access the API
expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
accessToken := &Token{
GrantID: grant.ID,
Kind: KindAccessToken,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()),
},
}
signedAccessToken, err := accessToken.SignToken(serverKey)
if err != nil {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot sign token",
}
}
// generate refresh token to request an access token after it expired later
refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime()
refreshToken := &Token{
GrantID: grant.ID,
Counter: grant.Counter,
Kind: KindRefreshToken,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(refreshExpirationDate),
},
}
signedRefreshToken, err := refreshToken.SignToken(serverKey)
if err != nil {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot sign token",
}
}
// generate OpenID Connect id_token
signedIDToken := ""
if grant.ScopeContains("openid") {
app, err := auth.GetOAuth2ApplicationByID(ctx, grant.ApplicationID)
if err != nil {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot find application",
}
}
user, err := user_model.GetUserByID(ctx, grant.UserID)
if err != nil {
if user_model.IsErrUserNotExist(err) {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot find user",
}
}
log.Error("Error loading user: %v", err)
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "server error",
}
}
idToken := &OIDCToken{
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()),
Issuer: setting.AppURL,
Audience: []string{app.ClientID},
Subject: fmt.Sprint(grant.UserID),
},
Nonce: grant.Nonce,
}
if grant.ScopeContains("profile") {
idToken.Name = user.GetDisplayName()
idToken.PreferredUsername = user.Name
idToken.Profile = user.HTMLURL()
idToken.Picture = user.AvatarLink(ctx)
idToken.Website = user.Website
idToken.Locale = user.Language
idToken.UpdatedAt = user.UpdatedUnix
}
if grant.ScopeContains("email") {
idToken.Email = user.Email
idToken.EmailVerified = user.IsActive
}
if grant.ScopeContains("groups") {
groups, err := GetOAuthGroupsForUser(ctx, user)
if err != nil {
log.Error("Error getting groups: %v", err)
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "server error",
}
}
idToken.Groups = groups
}
signedIDToken, err = idToken.SignToken(clientKey)
if err != nil {
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "cannot sign token",
}
}
}
return &AccessTokenResponse{
AccessToken: signedAccessToken,
TokenType: TokenTypeBearer,
ExpiresIn: setting.OAuth2.AccessTokenExpirationTime,
RefreshToken: signedRefreshToken,
IDToken: signedIDToken,
}, nil
}
// returns a list of "org" and "org:team" strings,
// that the given user is a part of.
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User) ([]string, error) {
orgs, err := org_model.GetUserOrgsList(ctx, user)
if err != nil {
return nil, fmt.Errorf("GetUserOrgList: %w", err)
}
var groups []string
for _, org := range orgs {
groups = append(groups, org.Name)
teams, err := org.LoadTeams(ctx)
if err != nil {
return nil, fmt.Errorf("LoadTeams: %w", err)
}
for _, team := range teams {
if team.IsMember(ctx, user.ID) {
groups = append(groups, org.Name+":"+team.LowerName)
}
}
}
return groups, nil
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2_provider //nolint
import (
"context"
"code.gitea.io/gitea/modules/setting"
)
// Init initializes the oauth source
func Init(ctx context.Context) error {
if !setting.OAuth2.Enabled {
return nil
}
return InitSigningKey()
}
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
package oauth2_provider //nolint
import (
"crypto/ecdsa"
@@ -1,7 +1,7 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
package oauth2_provider //nolint
import (
"fmt"
@@ -12,29 +12,22 @@ import (
"github.com/golang-jwt/jwt/v5"
)
// ___________ __
// \__ ___/___ | | __ ____ ____
// | | / _ \| |/ // __ \ / \
// | |( <_> ) <\ ___/| | \
// |____| \____/|__|_ \\___ >___| /
// \/ \/ \/
// Token represents an Oauth grant
// TokenType represents the type of token for an oauth application
type TokenType int
// TokenKind represents the type of token for an oauth application
type TokenKind int
const (
// TypeAccessToken is a token with short lifetime to access the api
TypeAccessToken TokenType = 0
// TypeRefreshToken is token with long lifetime to refresh access tokens obtained by the client
TypeRefreshToken = iota
// KindAccessToken is a token with short lifetime to access the api
KindAccessToken TokenKind = 0
// KindRefreshToken is token with long lifetime to refresh access tokens obtained by the client
KindRefreshToken = iota
)
// Token represents a JWT token used to authenticate a client
type Token struct {
GrantID int64 `json:"gnt"`
Type TokenType `json:"tt"`
Kind TokenKind `json:"tt"`
Counter int64 `json:"cnt,omitempty"`
jwt.RegisteredClaims
}
+7 -1
View File
@@ -1013,6 +1013,8 @@ type CommitInfo struct {
}
// GetPullCommits returns all commits on given pull request and the last review commit sha
// Attention: The last review commit sha must be from the latest review whose commit id is not empty.
// So the type of the latest review cannot be "ReviewTypeRequest".
func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]CommitInfo, string, error) {
pull := issue.PullRequest
@@ -1058,7 +1060,11 @@ func GetPullCommits(ctx *gitea_context.Context, issue *issues_model.Issue) ([]Co
lastreview, err := issues_model.FindLatestReviews(ctx, issues_model.FindReviewOptions{
IssueID: issue.ID,
ReviewerID: ctx.Doer.ID,
Type: issues_model.ReviewTypeUnknown,
Types: []issues_model.ReviewType{
issues_model.ReviewTypeApprove,
issues_model.ReviewTypeComment,
issues_model.ReviewTypeReject,
},
})
if err != nil && !issues_model.IsErrReviewNotExist(err) {
+1 -1
View File
@@ -348,7 +348,7 @@ func DismissApprovalReviews(ctx context.Context, doer *user_model.User, pull *is
reviews, err := issues_model.FindReviews(ctx, issues_model.FindReviewOptions{
ListOptions: db.ListOptionsAll,
IssueID: pull.IssueID,
Type: issues_model.ReviewTypeApprove,
Types: []issues_model.ReviewType{issues_model.ReviewTypeApprove},
Dismissed: optional.Some(false),
})
if err != nil {
+8
View File
@@ -616,6 +616,14 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitR
return err
}
if !repo.IsEmpty {
if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{
RepoID: repo.ID,
}); err != nil {
log.Error("AddRepoToLicenseUpdaterQueue: %v", err)
}
}
notify_service.ChangeDefaultBranch(ctx, repo)
return nil
+19
View File
@@ -303,6 +303,25 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("CreateRepository(git update-server-info): %w", err)
}
// update licenses
var licenses []string
if len(opts.License) > 0 {
licenses = append(licenses, ConvertLicenseName(opts.License))
stdout, _, err := git.NewCommand(ctx, "rev-parse", "HEAD").
SetDescription(fmt.Sprintf("CreateRepository(git rev-parse HEAD): %s", repoPath)).
RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil {
log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err)
rollbackRepo = repo
rollbackRepo.OwnerID = u.ID
return fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err)
}
if err := repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil {
return err
}
}
return nil
}); err != nil {
if rollbackRepo != nil {
+1
View File
@@ -140,6 +140,7 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
&git_model.Branch{RepoID: repoID},
&git_model.LFSLock{RepoID: repoID},
&repo_model.LanguageStat{RepoID: repoID},
&repo_model.RepoLicense{RepoID: repoID},
&issues_model.Milestone{RepoID: repoID},
&repo_model.Mirror{RepoID: repoID},
&activities_model.Notification{RepoID: repoID},
+3
View File
@@ -198,6 +198,9 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork
if err := repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil {
log.Error("Copy language stat from oldRepo failed: %v", err)
}
if err := repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil {
return nil, err
}
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
+205
View File
@@ -0,0 +1,205 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"context"
"fmt"
"io"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/queue"
licenseclassifier "github.com/google/licenseclassifier/v2"
)
var (
classifier *licenseclassifier.Classifier
LicenseFileName = "LICENSE"
licenseAliases map[string]string
// licenseUpdaterQueue represents a queue to handle update repo licenses
licenseUpdaterQueue *queue.WorkerPoolQueue[*LicenseUpdaterOptions]
)
func AddRepoToLicenseUpdaterQueue(opts *LicenseUpdaterOptions) error {
if opts == nil {
return nil
}
return licenseUpdaterQueue.Push(opts)
}
func loadLicenseAliases() error {
if licenseAliases != nil {
return nil
}
data, err := options.AssetFS().ReadFile("license", "etc", "license-aliases.json")
if err != nil {
return err
}
err = json.Unmarshal(data, &licenseAliases)
if err != nil {
return err
}
return nil
}
func ConvertLicenseName(name string) string {
if err := loadLicenseAliases(); err != nil {
return name
}
v, ok := licenseAliases[name]
if ok {
return v
}
return name
}
func InitLicenseClassifier() error {
// threshold should be 0.84~0.86 or the test will be failed
classifier = licenseclassifier.NewClassifier(.85)
licenseFiles, err := options.AssetFS().ListFiles("license", true)
if err != nil {
return err
}
existLicense := make(container.Set[string])
if len(licenseFiles) > 0 {
for _, licenseFile := range licenseFiles {
licenseName := ConvertLicenseName(licenseFile)
if existLicense.Contains(licenseName) {
continue
}
existLicense.Add(licenseName)
data, err := options.License(licenseFile)
if err != nil {
return err
}
classifier.AddContent("License", licenseFile, licenseName, data)
}
}
return nil
}
type LicenseUpdaterOptions struct {
RepoID int64
}
func repoLicenseUpdater(items ...*LicenseUpdaterOptions) []*LicenseUpdaterOptions {
ctx := graceful.GetManager().ShutdownContext()
for _, opts := range items {
repo, err := repo_model.GetRepositoryByID(ctx, opts.RepoID)
if err != nil {
log.Error("repoLicenseUpdater [%d] failed: GetRepositoryByID: %v", opts.RepoID, err)
continue
}
if repo.IsEmpty {
continue
}
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
if err != nil {
log.Error("repoLicenseUpdater [%d] failed: OpenRepository: %v", opts.RepoID, err)
continue
}
defer gitRepo.Close()
commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch)
if err != nil {
log.Error("repoLicenseUpdater [%d] failed: GetBranchCommit: %v", opts.RepoID, err)
continue
}
if err = UpdateRepoLicenses(ctx, repo, commit); err != nil {
log.Error("repoLicenseUpdater [%d] failed: updateRepoLicenses: %v", opts.RepoID, err)
}
}
return nil
}
func SyncRepoLicenses(ctx context.Context) error {
log.Trace("Doing: SyncRepoLicenses")
if err := db.Iterate(
ctx,
nil,
func(ctx context.Context, repo *repo_model.Repository) error {
select {
case <-ctx.Done():
return db.ErrCancelledf("before sync repo licenses for %s", repo.FullName())
default:
}
return AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID})
},
); err != nil {
log.Trace("Error: SyncRepoLicenses: %v", err)
return err
}
log.Trace("Finished: SyncReposLicenses")
return nil
}
// UpdateRepoLicenses will update repository licenses col if license file exists
func UpdateRepoLicenses(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) error {
if commit == nil {
return nil
}
b, err := commit.GetBlobByPath(LicenseFileName)
if err != nil && !git.IsErrNotExist(err) {
return fmt.Errorf("GetBlobByPath: %w", err)
}
if git.IsErrNotExist(err) {
return repo_model.CleanRepoLicenses(ctx, repo)
}
licenses := make([]string, 0)
if b != nil {
r, err := b.DataAsync()
if err != nil {
return err
}
defer r.Close()
licenses, err = detectLicense(r)
if err != nil {
return fmt.Errorf("detectLicense: %w", err)
}
}
return repo_model.UpdateRepoLicenses(ctx, repo, commit.ID.String(), licenses)
}
// detectLicense returns the licenses detected by the given content buff
func detectLicense(r io.Reader) ([]string, error) {
if r == nil {
return nil, nil
}
matches, err := classifier.MatchFrom(r)
if err != nil {
return nil, err
}
if len(matches.Matches) > 0 {
results := make(container.Set[string], len(matches.Matches))
for _, r := range matches.Matches {
if r.MatchType == "License" && !results.Contains(r.Variant) {
results.Add(r.Variant)
}
}
return results.Values(), nil
}
return nil, nil
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"fmt"
"strings"
"testing"
repo_module "code.gitea.io/gitea/modules/repository"
"github.com/stretchr/testify/assert"
)
func Test_detectLicense(t *testing.T) {
type DetectLicenseTest struct {
name string
arg string
want []string
}
tests := []DetectLicenseTest{
{
name: "empty",
arg: "",
want: nil,
},
{
name: "no detected license",
arg: "Copyright (c) 2023 Gitea",
want: nil,
},
}
repo_module.LoadRepoConfig()
err := loadLicenseAliases()
assert.NoError(t, err)
for _, licenseName := range repo_module.Licenses {
license, err := repo_module.GetLicense(licenseName, &repo_module.LicenseValues{
Owner: "Gitea",
Email: "teabot@gitea.io",
Repo: "gitea",
Year: "2024",
})
assert.NoError(t, err)
tests = append(tests, DetectLicenseTest{
name: fmt.Sprintf("single license test: %s", licenseName),
arg: string(license),
want: []string{ConvertLicenseName(licenseName)},
})
}
err = InitLicenseClassifier()
assert.NoError(t, err)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
license, err := detectLicense(strings.NewReader(tt.arg))
assert.NoError(t, err)
assert.Equal(t, tt.want, license)
})
}
result, err := detectLicense(strings.NewReader(tests[2].arg + tests[3].arg + tests[4].arg))
assert.NoError(t, err)
t.Run("multiple licenses test", func(t *testing.T) {
assert.Equal(t, 3, len(result))
assert.Contains(t, result, tests[2].want[0])
assert.Contains(t, result, tests[3].want[0])
assert.Contains(t, result, tests[4].want[0])
})
}
+5
View File
@@ -172,6 +172,11 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
return repo, fmt.Errorf("StoreMissingLfsObjectsInRepository: %w", err)
}
}
// Update repo license
if err := AddRepoToLicenseUpdaterQueue(&LicenseUpdaterOptions{RepoID: repo.ID}); err != nil {
log.Error("Failed to add repo to license updater queue: %v", err)
}
}
ctx, committer, err := db.TxContext(ctx)
+7
View File
@@ -18,6 +18,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
@@ -96,6 +97,12 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN
// Init start repository service
func Init(ctx context.Context) error {
licenseUpdaterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "repo_license_updater", repoLicenseUpdater)
if licenseUpdaterQueue == nil {
return fmt.Errorf("unable to create repo_license_updater queue")
}
go graceful.GetManager().RunWithCancel(licenseUpdaterQueue)
if err := repo_module.LoadRepoConfig(); err != nil {
return err
}