feat(licenses): support REUSE specification in licenses (#38720)

extends the current license detection to support two modes:
- legacy which is using classification and was expanded to handle more
paths (extensions, different spelling or GNU copying file)
- REUSE which avoids classification by relying on the spec dictating
that license must be named as SPDX-ID.extension. Newly created
repositories will default to REUSE based paths

Use of styles at the same time is not allowed by design.

Extends the UI to show all the detected licenses and paths to them,
deduplicating them per SPDX-ID in database as is in github

closes: https://github.com/go-gitea/gitea/issues/28672


---------

Assisted-By: omp:glm5.2
Assisted-By: omp:mimo-v2.5-pro
Assisted-By: omp:mimimax-m3
Assisted-By: omp:kimi-k3
Assisted-By: omp:deepseek-v4-flash
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
TheFox0x7
2026-08-03 09:34:53 +02:00
committed by GitHub
parent 09f7c71e3d
commit e23fe79e5e
10 changed files with 480 additions and 71 deletions
+33 -11
View File
@@ -16,9 +16,10 @@ func init() {
type RepoLicense struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) NOT NULL"`
RepoID int64 `xorm:"UNIQUE(path) NOT NULL"`
CommitID string
License string `xorm:"VARCHAR(255) UNIQUE(s) NOT NULL"`
License string `xorm:"VARCHAR(255) UNIQUE(path) NOT NULL"`
LicensePath string `xorm:"VARCHAR(255) UNIQUE(path) NOT NULL DEFAULT 'LICENSE'"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
}
@@ -34,6 +35,13 @@ func (rll RepoLicenseList) StringList() []string {
return licenses
}
type DetectedLicense struct {
// SPDXID of the license
SPDXID string
// LicensePath is in repo path to the license
LicensePath string
}
// GetRepoLicenses returns the license statistics for a repository
func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) {
licenses := make(RepoLicenseList, 0)
@@ -43,8 +51,19 @@ func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, er
return licenses, nil
}
func GetUniqueRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) {
licenses := make(RepoLicenseList, 0)
if err := db.GetEngine(ctx).Select("MAX(`id`) AS `id`, `license`, MAX(`license_path`) AS `license_path`, MAX(`commit_id`) AS `commit_id`").
Where("`repo_id` = ?", repo.ID).
GroupBy("`license`").
Find(&licenses); err != nil {
return nil, err
}
return licenses, nil
}
// UpdateRepoLicenses updates the license statistics for repository
func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string, licenses []string) error {
func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string, licenses []DetectedLicense) error {
oldLicenses, err := GetRepoLicenses(ctx, repo)
if err != nil {
return err
@@ -53,9 +72,10 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
upd := false
for _, o := range oldLicenses {
// Update already existing license
if o.License == license {
if o.License == license.SPDXID {
o.CommitID = commitID
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
o.LicensePath = license.LicensePath
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`", "`license_path`").Update(o); err != nil {
return err
}
upd = true
@@ -65,9 +85,10 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
// Insert new license
if !upd {
if err := db.Insert(ctx, &RepoLicense{
RepoID: repo.ID,
CommitID: commitID,
License: license,
RepoID: repo.ID,
CommitID: commitID,
License: license.SPDXID,
LicensePath: license.LicensePath,
}); err != nil {
return err
}
@@ -100,9 +121,10 @@ func CopyLicense(ctx context.Context, originalRepo, destRepo *Repository) error
for _, rl := range repoLicenses {
newRepoLicense := &RepoLicense{
RepoID: destRepo.ID,
CommitID: rl.CommitID,
License: rl.License,
RepoID: destRepo.ID,
CommitID: rl.CommitID,
License: rl.License,
LicensePath: rl.LicensePath,
}
newRepoLicenses = append(newRepoLicenses, newRepoLicense)
}