Compare commits

...

195 Commits

Author SHA1 Message Date
yp05327
0113e6c55b
Merge 47e7c5869e into 55d5a74bb3 2024-09-20 08:48:30 +08:00
GiteaBot
55d5a74bb3 [skip ci] Updated translations via Crowdin 2024-09-20 00:29:58 +00:00
yp05327
47e7c5869e
Merge branch 'main' into add-repo-license-display 2024-09-19 09:16:34 +09:00
techknowlogick
f1ddccf4aa
Merge branch 'main' into add-repo-license-display 2024-09-18 13:33:45 -04:00
techknowlogick
8991c244b5
Merge branch 'main' into add-repo-license-display 2024-09-17 12:29:00 -04:00
yp05327
4e18872f69 fix test 2024-09-17 02:41:28 +00:00
yp05327
875daf82e4 fix swagger 2024-09-17 00:48:24 +00:00
yp05327
da313ff9f9 fix lint 2024-09-17 00:33:01 +00:00
yp05327
1043d39be0 fix lint 2024-09-17 00:00:30 +00:00
yp05327
051fdffb50 fix lint 2024-09-12 05:20:42 +00:00
yp05327
0717cb6faf make generate-swagger 2024-09-12 05:13:36 +00:00
yp05327
cfe8cb5192 fix repository size 2024-09-12 05:12:52 +00:00
yp05327
fa0119b552 go mod tidy 2024-09-12 04:56:54 +00:00
yp05327
0f008950d4 fix migration 2024-09-12 04:56:40 +00:00
yp05327
a93879586f
Merge branch 'main' into add-repo-license-display 2024-09-12 13:50:29 +09:00
yp05327
d5ca326968 renqme v289 to v305 2024-09-12 04:44:31 +00:00
yp05327
60aba0cae0 fix typo 2024-03-06 00:08:36 +00:00
yp05327
2910c9c1d9 fix year 2024-03-05 08:20:45 +00:00
yp05327
f87f8578cf move to services 2024-03-05 08:19:27 +00:00
yp05327
2f1af68517 fix lint 2024-03-05 07:54:38 +00:00
yp05327
be5052990f improve 2024-03-05 06:16:58 +00:00
yp05327
8dc1c3be40 fix test 2024-03-05 06:07:24 +00:00
yp05327
0e3d5122b3 import gitrepo 2024-03-05 05:41:56 +00:00
yp05327
0eb85451b8
Merge branch 'main' into add-repo-license-display 2024-03-05 14:13:43 +09:00
yp05327
75cf5a2b3b convert v288 into v289 2024-03-05 05:12:53 +00:00
yp05327
5059a0fbfc
Update modules/repository/license.go
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-03-05 14:10:51 +09:00
yp05327
ccde2013f7 fix lint 2024-03-05 05:06:11 +00:00
yp05327
00b182c17f
Update models/repo/license.go
Co-authored-by: a1012112796 <1012112796@qq.com>
2024-03-05 14:04:10 +09:00
yp05327
c218be05ff remove commit 2024-03-05 04:45:21 +00:00
yp05327
f43425142e do not use time.Now(9 in test 2024-03-05 04:41:15 +00:00
6543
46e1246c00
Apply suggestions from code review
Co-authored-by: delvh <dev.lh@web.de>
2024-03-04 02:16:41 +01:00
6543
49ab83b41d
Update tests/integration/api_repo_license_test.go
Co-authored-by: delvh <dev.lh@web.de>
2024-03-04 02:12:32 +01:00
6543
60bfdea091
Merge branch 'main' into add-repo-license-display 2024-03-04 02:05:49 +01:00
delvh
7582a662c7
Merge branch 'main' into add-repo-license-display 2024-03-04 00:36:25 +01:00
6543
51a5bce065
Merge branch 'main' into add-repo-license-display 2024-03-01 13:34:08 +01:00
yp05327
ce750617f6 convert code.gitea.io/gitea/modules/context into code.gitea.io/gitea/services/context 2024-03-01 08:54:26 +00:00
yp05327
2ec2083c3c Merge remote-tracking branch 'upstream/main' into add-repo-license-display 2024-03-01 08:52:01 +00:00
yp05327
11b5e09e28 convert v287 to v288 2024-03-01 08:41:30 +00:00
yp05327
a0a7e9a9b3 add api and test 2024-03-01 07:36:08 +00:00
yp05327
882767c2ba fix ci error 2024-02-27 01:09:52 +00:00
yp05327
fe77ff5429 remove unnecessary enter 2024-02-26 08:29:46 +00:00
yp05327
c79fbaa6e5 fix not remove old records when license file not detected after branch change 2024-02-26 08:28:27 +00:00
yp05327
7d92e475e1 use queue 2024-02-26 08:11:57 +00:00
6543
d92bbabfa2
Merge branch 'main' into add-repo-license-display 2024-02-25 23:18:19 +01:00
yp05327
baa29d4df6 update license 2024-02-22 06:28:54 +00:00
yp05327
d46b98eec8
Merge branch 'main' into add-repo-license-display 2024-02-22 15:26:12 +09:00
yp05327
841453d787 convert v285 to v287 2024-02-22 06:25:06 +00:00
yp05327
86f8bec027
Update build/generate-licenses.go
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-02-22 15:22:49 +09:00
yp05327
a0e8a5db75
Update build/generate-licenses.go
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
2024-02-22 15:22:44 +09:00
yp05327
aabb200ea5 fix 2024-01-18 00:42:57 +00:00
yp05327
cc3cf074b9 fix comment 2024-01-18 00:40:39 +00:00
yp05327
80934c369f
Merge branch 'main' into add-repo-license-display 2024-01-18 09:35:05 +09:00
yp05327
3ef4aa814d improve 2024-01-18 00:33:54 +00:00
yp05327
5e72897db8 fix changes from 28691 2024-01-17 04:47:56 +00:00
yp05327
91c54540e5
Merge branch 'main' into add-repo-license-display 2024-01-17 13:42:24 +09:00
yp05327
030c0dda2f rename to v285 2024-01-17 04:26:13 +00:00
yp05327
db2e9ebcef remove add update repo license notify todo 2024-01-17 04:22:47 +00:00
yp05327
8e0929eebd fix test 2023-12-08 03:00:32 +00:00
yp05327
d501891dac fix test 2023-12-06 01:55:58 +00:00
yp05327
7621d98006 fix lint 2023-12-06 01:21:13 +00:00
yp05327
3e45a9a3d6
Update models/repo/license.go
Co-authored-by: delvh <dev.lh@web.de>
2023-12-04 13:15:59 +09:00
yp05327
da365540d4 use ctx.Locale.Tr 2023-12-04 02:56:58 +00:00
yp05327
727d5ccdc8 add cron task 2023-12-04 02:52:14 +00:00
yp05327
bb46e0176c
Merge branch 'main' into add-repo-license-display 2023-12-04 11:14:50 +09:00
yp05327
c97caf8b08 improve 2023-12-01 01:46:07 +00:00
yp05327
121f2b2c29 fix 2023-12-01 01:06:08 +00:00
yp05327
c5296f449c improve initClassifier 2023-12-01 01:03:31 +00:00
yp05327
13867800c5 remove generate licenses in migrations 2023-12-01 01:03:12 +00:00
yp05327
093e71d4be
Update modules/repository/license.go
Co-authored-by: delvh <dev.lh@web.de>
2023-12-01 08:54:41 +09:00
yp05327
9178d90a5f convert sameLicenses to license aliases 2023-11-30 23:51:23 +00:00
yp05327
a5d9763795 fix bug 2023-11-30 07:06:38 +00:00
yp05327
5ec4b50d75 add data-tooltip-placement 2023-11-24 07:43:21 +00:00
yp05327
cd06857fd0 fix test 2023-11-24 07:17:35 +00:00
yp05327
21b1a7a14c fix 2023-11-24 07:14:34 +00:00
yp05327
dac32cede8 improve detect license 2023-11-24 06:57:18 +00:00
yp05327
b45f907347 improve variable name 2023-11-24 06:22:19 +00:00
yp05327
648d0a0953 improve the logic of copy license info 2023-11-24 06:11:41 +00:00
yp05327
44cf8e4eee add return 2023-11-24 05:55:13 +00:00
yp05327
c57d542245 do not init classifier in init() 2023-11-24 05:52:20 +00:00
yp05327
cd0b44c640 fix typo 2023-11-24 05:41:22 +00:00
yp05327
7781e7d536 sync funcs in migration from origin 2023-11-06 01:13:57 +00:00
yp05327
8a1c7673bb fix 2023-11-06 01:02:59 +00:00
yp05327
d6ba4af183 use slices.Contains 2023-11-06 00:59:37 +00:00
yp05327
640a5e455b
Update models/migrations/v1_22/v283.go
Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-11-06 09:55:30 +09:00
yp05327
7a6c3b307a
Merge branch 'main' into add-repo-license-display 2023-11-01 11:20:26 +09:00
yp05327
3abe77e690 rename to v283 2023-11-01 02:19:40 +00:00
yp05327
df3e16872b
Update models/migrations/migrations.go
Co-authored-by: Denys Konovalov <kontakt@denyskon.de>
2023-11-01 11:18:58 +09:00
Giteabot
a335732621
Merge branch 'main' into add-repo-license-display 2023-10-27 19:54:38 +08:00
yp05327
462f7f5108 fix package name of migration 2023-10-23 01:35:20 +00:00
yp05327
fe1f08751c
Merge branch 'main' into add-repo-license-display 2023-10-23 10:32:02 +09:00
yp05327
91dda747ab rename to v282 2023-10-23 01:27:36 +00:00
Lunny Xiao
71ad15e6d8
Merge branch 'main' into add-repo-license-display 2023-09-27 11:40:34 +08:00
yp05327
ba6019507a fix 2023-09-27 02:08:38 +00:00
yp05327
bae33d5633 recover changes in templates/repo/sub_menu.tmpl 2023-09-27 01:59:37 +00:00
yp05327
83945edb04 recover changes in SetDefaultBranchPost 2023-09-27 01:59:27 +00:00
yp05327
498ea785c8 recover changes in CreateRepository 2023-09-27 01:47:02 +00:00
yp05327
e70bb25882 recover changes in DeleteRepository 2023-09-27 01:42:28 +00:00
yp05327
f65eeec206
Merge branch 'main' into add-repo-license-display 2023-09-27 10:34:22 +09:00
yp05327
cbe15154b6 rename to v278 2023-09-27 01:19:05 +00:00
yp05327
24b1c131bb
Merge branch 'main' into add-repo-license-display 2023-08-29 16:37:47 +09:00
yp05327
87ae3a626c rename to v274 2023-08-29 07:36:24 +00:00
yp05327
05c844df47
Merge branch 'main' into add-repo-license-display 2023-08-22 13:40:53 +09:00
yp05327
f546aaefe3 rename to v273 2023-08-22 04:37:46 +00:00
yp05327
3c554a54c7 fix lint 2023-08-07 05:02:51 +00:00
yp05327
8369e939d2
Merge branch 'main' into add-repo-license-display 2023-08-03 08:59:39 +09:00
yp05327
d6f700636a rename to v271 2023-08-02 23:58:55 +00:00
yp05327
d57592f693 fix lint 2023-07-27 09:00:58 +00:00
yp05327
d6d2524e3e
Merge branch 'main' into add-repo-license-display 2023-07-27 18:00:18 +09:00
yp05327
fc93550898 rename to v270 2023-07-27 08:59:30 +00:00
yp05327
16822cd864 make tidy 2023-07-20 04:13:21 +00:00
yp05327
7e2589caf6
Merge branch 'main' into add-repo-license-display 2023-07-20 11:43:16 +09:00
yp05327
7a9d60538a add fixtures 2023-07-07 05:56:14 +00:00
yp05327
7640268926 remove licenses when remove repo 2023-07-06 05:12:03 +00:00
yp05327
b168d1bc9f improve 2023-07-05 01:35:25 +00:00
yp05327
2b56a5a307 fix 2023-07-05 00:57:53 +00:00
yp05327
7c7bdea1f7
Merge branch 'main' into add-repo-license-display 2023-07-05 09:29:24 +09:00
yp05327
e38161b487 fix conflicts 2023-07-05 00:23:57 +00:00
yp05327
2904f345e0 fix conflicts 2023-07-05 00:21:37 +00:00
yp05327
3a99e010eb
Merge branch 'main' into add-repo-license-display 2023-06-26 10:40:52 +09:00
yp05327
f7da797dbc fix conflicts 2023-06-26 01:40:02 +00:00
yp05327
9cbde7294e avoid return same licenses 2023-06-26 01:37:58 +00:00
yp05327
50150b80ab
Merge branch 'main' into add-repo-license-display 2023-06-20 14:15:44 +09:00
yp05327
4ec4a0ffaf finish todo 2023-06-19 07:47:02 +00:00
yp05327
376b91ac31 remove todo 2023-06-19 07:23:05 +00:00
yp05327
44a8eceeda fix lint 2023-06-19 07:22:40 +00:00
yp05327
6f2529435d convert license col into table 2023-06-19 06:11:52 +00:00
yp05327
e67a77154c improve test 2023-06-19 02:21:57 +00:00
yp05327
d193276616 fix lint 2023-06-15 01:15:37 +00:00
yp05327
ea2c0bede2 convert name
remove todo: add threshold to app.ini
2023-06-15 01:13:44 +00:00
yp05327
c6cc8b4c8f fix lint 2023-06-15 00:52:57 +00:00
yp05327
a79cb78857 fix 2023-06-15 00:01:16 +00:00
yp05327
ae27d02aa8 remove GetBlobAll 2023-06-14 08:51:41 +00:00
yp05327
c831ee31bf improve 2023-06-14 08:40:27 +00:00
yp05327
e46fd139c2 fix 2023-06-14 08:20:25 +00:00
yp05327
27cb436faf
Merge branch 'main' into add-repo-license-display 2023-06-14 17:16:33 +09:00
yp05327
866de8ddec make generate-swagger 2023-06-14 08:15:42 +00:00
yp05327
4a838adc5e allow not existed git repo 2023-06-14 04:22:17 +00:00
yp05327
59198d3c31 fix lint 2023-06-14 04:02:55 +00:00
yp05327
25966eb078 fix lint 2023-06-14 02:53:57 +00:00
yp05327
1b0783c464 fix conflict 2023-06-14 02:42:14 +00:00
yp05327
d0e5676402
Merge branch 'main' into add-repo-license-display 2023-06-14 11:33:32 +09:00
yp05327
3da5a80a9c generate licenses 2023-06-14 02:02:38 +00:00
yp05327
84c7b98aaa generate same file list 2023-06-14 02:02:08 +00:00
yp05327
f45d59263c
Merge branch 'main' into add-repo-license-display 2023-06-13 14:02:51 +09:00
yp05327
4cf79bef02 fix 2023-06-09 07:52:59 +00:00
yp05327
7f2b5d0c8e improve 2023-06-09 07:46:27 +00:00
yp05327
388bc2600f add tooltips 2023-06-08 02:21:08 +00:00
yp05327
f2933cccba fix migration 2023-06-06 07:57:21 +00:00
yp05327
c58162ecd2 fix test 2023-06-06 06:52:24 +00:00
yp05327
58b4cc2168 remove unnecessary code
and add comment
2023-06-06 06:02:19 +00:00
yp05327
1cb20208f0 init licenseclassifier when service start 2023-06-06 05:59:38 +00:00
yp05327
7a01d3b889 remove unnecessary code 2023-06-06 05:54:15 +00:00
yp05327
fa8e34790c fix 2023-06-05 08:27:56 +00:00
yp05327
a000c38ba7
Merge branch 'main' into add-repo-license-display 2023-06-05 17:18:59 +09:00
yp05327
48da7a639a convert use licenseclassifier 2023-06-05 08:14:34 +00:00
yp05327
b9c2329e0e update licenses info when update default branch 2023-05-31 06:21:38 +00:00
yp05327
63a6da8867 add licenses to api.Repository 2023-05-31 02:54:36 +00:00
yp05327
ef340f6628 only use defaultbranch to display license file 2023-05-31 02:41:00 +00:00
yp05327
0dc4c3e5f1
Merge branch 'main' into add-repo-license-display 2023-05-31 10:45:06 +09:00
yp05327
ac8442202c fix 2023-05-31 01:26:42 +00:00
yp05327
92d8df7592 move the position of license 2023-05-31 01:09:16 +00:00
silverwind
ea2f23c4d2
Merge branch 'main' into add-repo-license-display 2023-05-30 22:20:26 +02:00
yp05327
93d2c7f055 fix lint 2023-05-30 05:33:32 +00:00
yp05327
744a03226f add licensecheck test 2023-05-29 06:13:49 +00:00
yp05327
07e91bf049 fix migration 2023-05-29 05:24:09 +00:00
yp05327
f481624e4c add db migration 2023-05-29 04:40:36 +00:00
yp05327
763f104e74 improve 2023-05-29 04:40:05 +00:00
yp05327
0a9e4e8f14
Merge branch 'main' into add-repo-license-display 2023-05-26 17:37:01 +09:00
yp05327
3518a63899 fix lint 2023-05-26 02:54:57 +00:00
yp05327
2f870f7015
Merge branch 'main' into add-repo-license-display 2023-05-26 11:53:24 +09:00
yp05327
bc005ddc8f fix no commitID 2023-05-26 02:48:05 +00:00
yp05327
30c25ba3ad remove check iswiki 2023-05-26 01:00:22 +00:00
yp05327
af1b031c05 fix fork repo 2023-05-26 00:53:45 +00:00
yp05327
facd6982e2 improve 2023-05-26 00:44:00 +00:00
yp05327
0637b87923 fix 2023-05-25 10:19:20 +00:00
yp05327
8ba16c0c54 fix 2023-05-25 09:47:41 +00:00
yp05327
7eb45846fa fix test 2023-05-25 08:29:26 +00:00
yp05327
f2857204c9 fix test 2023-05-25 08:19:24 +00:00
yp05327
10b2a90220 imporve debug info 2023-05-25 07:34:49 +00:00
yp05327
cf028887b8 support search license file 2023-05-25 07:24:57 +00:00
yp05327
0421e98982 update license when sync mirror repo 2023-05-25 07:16:33 +00:00
yp05327
84801d5ef1 move findFileInEntries to repo modules 2023-05-25 07:07:27 +00:00
yp05327
aa91765ddc add license to mirror repo 2023-05-25 06:40:14 +00:00
yp05327
c30bca1f30 fix lint 2023-05-25 06:06:04 +00:00
yp05327
0278775051 add license to fork repo 2023-05-25 05:58:20 +00:00
yp05327
c4c6da53c7 add todo 2023-05-25 05:53:41 +00:00
yp05327
a2ef8f68a9 update license when create a repo 2023-05-25 05:51:10 +00:00
yp05327
a876b2dc03 remove debug code 2023-05-25 00:33:57 +00:00
yp05327
973f9950d1 check license change in HookPostReceive 2023-05-25 00:32:35 +00:00
yp05327
5a75aab5e0 use statistics in db 2023-05-24 07:05:11 +00:00
yp05327
a4e0deb444 use make tidy to add licensecheck 2023-05-24 07:03:08 +00:00
yp05327
8acb46e527 remove debug code 2023-05-23 07:47:20 +00:00
yp05327
7d3f1d5da2 fix test 2023-05-23 07:24:04 +00:00
yp05327
33ec600163 fix incorrect location of {{end}} 2023-05-23 06:52:27 +00:00
yp05327
43129a1901 add repo license check 2023-05-23 06:47:35 +00:00
49 changed files with 1055 additions and 160 deletions

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,8 @@ package main
import (
"archive/tar"
"compress/gzip"
"crypto/md5"
"encoding/hex"
"flag"
"fmt"
"io"
@ -15,19 +17,22 @@ import (
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
)
func main() {
var (
prefix = "gitea-licenses"
url = "https://api.github.com/repos/spdx/license-list-data/tarball"
githubApiToken = ""
githubUsername = ""
destination = ""
prefix = "gitea-licenses"
url = "https://api.github.com/repos/spdx/license-list-data/tarball"
githubApiToken = ""
githubUsername = ""
destination = ""
licenseAliasesDestination = ""
)
flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses")
flag.StringVar(&licenseAliasesDestination, "aliasesdest", "options/license-aliases.json", "destination for same license json")
flag.StringVar(&githubUsername, "username", "", "github username")
flag.StringVar(&githubApiToken, "token", "", "github api token")
flag.Parse()
@ -77,7 +82,7 @@ func main() {
}
tr := tar.NewReader(gz)
aliasesFiles := make(map[string][]string)
for {
hdr, err := tr.Next()
@ -97,26 +102,97 @@ func main() {
continue
}
if strings.HasPrefix(filepath.Base(hdr.Name), "README") {
fileBaseName := filepath.Base(hdr.Name)
licenseName := strings.TrimSuffix(fileBaseName, ".txt")
if strings.HasPrefix(fileBaseName, "README") {
continue
}
if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") {
if strings.HasPrefix(fileBaseName, "deprecated_") {
continue
}
out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt")))
out, err := os.Create(path.Join(destination, licenseName))
if err != nil {
log.Fatalf("Failed to create new file. %s", err)
}
defer out.Close()
if _, err := io.Copy(out, tr); err != nil {
// some license files have same content, so we need to detect these files and create a convert map into a json file
// Later we use this convert map to avoid adding same license content with different license name
h := md5.New()
// calculate md5 and write file in the same time
r := io.TeeReader(tr, h)
if _, err := io.Copy(out, r); err != nil {
log.Fatalf("Failed to write new file. %s", err)
} else {
fmt.Printf("Written %s\n", out.Name())
md5 := hex.EncodeToString(h.Sum(nil))
aliasesFiles[md5] = append(aliasesFiles[md5], licenseName)
}
}
// generate convert license name map
licenseAliases := make(map[string]string)
for _, fileNames := range aliasesFiles {
if len(fileNames) > 1 {
licenseName := getLicenseNameFromAliases(fileNames)
for _, fileName := range fileNames {
licenseAliases[fileName] = licenseName
}
}
}
// save convert license name map to file
b, err := json.Marshal(licenseAliases)
if err != nil {
log.Fatalf("Failed to create json bytes. %s", err)
return
}
f, err := os.Create(licenseAliasesDestination)
if err != nil {
log.Fatalf("Failed to create license aliases json file. %s", err)
}
defer f.Close()
if _, err = f.Write(b); err != nil {
log.Fatalf("Failed to write license aliases json file. %s", err)
}
fmt.Println("Done")
}
func getLicenseNameFromAliases(fnl []string) string {
if len(fnl) == 0 {
return ""
}
shortestItem := func(list []string) string {
s := list[0]
for _, l := range list[1:] {
if len(l) < len(s) {
s = l
}
}
return s
}
allHasPrefix := func(list []string, s string) bool {
for _, l := range list {
if !strings.HasPrefix(l, s) {
return false
}
}
return true
}
sl := shortestItem(fnl)
slv := strings.Split(sl, "-")
var result string
for i := len(slv); i >= 0; i-- {
result = strings.Join(slv[:i], "-")
if allHasPrefix(fnl, result) {
return result
}
}
return ""
}

1
go.mod
View File

@ -67,6 +67,7 @@ require (
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/go-github/v61 v61.0.0
github.com/google/licenseclassifier/v2 v2.0.0
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0

3
go.sum
View File

@ -435,6 +435,8 @@ github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/licenseclassifier/v2 v2.0.0 h1:1Y57HHILNf4m0ABuMVb6xk4vAJYEUO0gDxNpog0pyeA=
github.com/google/licenseclassifier/v2 v2.0.0/go.mod h1:cOjbdH0kyC9R22sdQbYsFkto4NGCAc+ZSwbeThazEtM=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8 h1:ASJ/LAqdCHOyMYI+dwNxn7Rd8FscNkMyTr1KZU1JI/M=
github.com/google/pprof v0.0.0-20240618054019-d3b898a103f8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
@ -729,6 +731,7 @@ github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jN
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

View File

@ -0,0 +1 @@
[] # empty

View File

@ -26,7 +26,7 @@
fork_id: 0
is_template: false
template_id: 0
size: 7597
size: 8478
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false

View File

@ -500,7 +500,7 @@ var migrations = []Migration{
// v259 -> v260
NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens),
// Gitea 1.20.0 ends at 260
// Gitea 1.20.0 ends at v260
// v260 -> v261
NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
@ -601,6 +601,8 @@ var migrations = []Migration{
NewMigration("Add metadata column for comment table", v1_23.AddCommentMetaDataColumn),
// v304 -> v305
NewMigration("Add index for release sha1", v1_23.AddIndexForReleaseSha1),
// v305 -> v306
NewMigration("Add Repository Licenses", v1_23.AddRepositoryLicenses),
}
// GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,23 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_23 //nolint
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddRepositoryLicenses(x *xorm.Engine) error {
type RepoLicense struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
CommitID string
License string `xorm:"VARCHAR(50) UNIQUE(s) INDEX NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
}
return x.Sync(new(RepoLicense))
}

114
models/repo/license.go Normal file
View File

@ -0,0 +1,114 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
)
func init() {
db.RegisterModel(new(RepoLicense))
}
type RepoLicense struct { //revive:disable-line:exported
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
CommitID string
// TODO: `INDEX` will be used to find repositories by license
License string `xorm:"VARCHAR(50) UNIQUE(s) INDEX NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX UPDATED"`
}
// RepoLicenseList defines a list of repo licenses
type RepoLicenseList []*RepoLicense //revive:disable-line:exported
func (rll RepoLicenseList) StringList() []string {
var licenses []string
for _, rl := range rll {
licenses = append(licenses, rl.License)
}
return licenses
}
// GetRepoLicenses returns the license statistics for a repository
func GetRepoLicenses(ctx context.Context, repo *Repository) (RepoLicenseList, error) {
licenses := make(RepoLicenseList, 0)
if err := db.GetEngine(ctx).Where("`repo_id` = ?", repo.ID).Asc("`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 {
oldLicenses, err := GetRepoLicenses(ctx, repo)
if err != nil {
return err
}
for _, license := range licenses {
upd := false
for _, o := range oldLicenses {
// Update already existing license
if o.License == license {
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
return err
}
upd = true
break
}
}
// Insert new license
if !upd {
if err := db.Insert(ctx, &RepoLicense{
RepoID: repo.ID,
CommitID: commitID,
License: license,
}); err != nil {
return err
}
}
}
// Delete old licenses
licenseToDelete := make([]int64, 0, len(oldLicenses))
for _, o := range oldLicenses {
if o.CommitID != commitID {
licenseToDelete = append(licenseToDelete, o.ID)
}
}
if len(licenseToDelete) > 0 {
if _, err := db.GetEngine(ctx).In("`id`", licenseToDelete).Delete(&RepoLicense{}); err != nil {
return err
}
}
return nil
}
// CopyLicense Copy originalRepo license information to destRepo (use for forked repo)
func CopyLicense(ctx context.Context, originalRepo, destRepo *Repository) error {
repoLicenses, err := GetRepoLicenses(ctx, originalRepo)
if err != nil {
return err
}
if len(repoLicenses) > 0 {
newRepoLicenses := make(RepoLicenseList, 0, len(repoLicenses))
for _, rl := range repoLicenses {
newRepoLicense := &RepoLicense{
RepoID: destRepo.ID,
CommitID: rl.CommitID,
License: rl.License,
}
newRepoLicenses = append(newRepoLicenses, newRepoLicense)
}
if err := db.Insert(ctx, &newRepoLicenses); err != nil {
return err
}
}
return nil
}

View File

@ -23,7 +23,7 @@ type LicenseValues struct {
func GetLicense(name string, values *LicenseValues) ([]byte, error) {
data, err := options.License(name)
if err != nil {
return nil, fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
return nil, fmt.Errorf("GetLicense[%s]: %w", name, err)
}
return fillLicensePlaceholder(name, values, data), nil
}

View File

@ -114,6 +114,7 @@ type Repository struct {
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
RepoTransfer *RepoTransfer `json:"repo_transfer"`
Topics []string `json:"topics"`
Licenses []string `json:"licenses"`
}
// CreateRepoOption options when creating repository

View File

@ -283,38 +283,47 @@ func CommonSkip(name string) bool {
return false
}
// IsReadmeFileName reports whether name looks like a README file
type FileType string
const (
FileTypeReadme FileType = "readme"
FileTypeLicense FileType = "license"
)
// IsFileName reports whether name looks like a target name file
// based on its name.
func IsReadmeFileName(name string) bool {
func IsFileName(name string, fileType FileType) bool {
name = strings.ToLower(name)
if len(name) < 6 {
lenFileType := len(fileType)
if len(name) < lenFileType {
return false
} else if len(name) == 6 {
return name == "readme"
} else if len(name) == lenFileType {
return name == string(fileType)
}
return name[:7] == "readme."
return name[:lenFileType+1] == string(fileType)+"."
}
// IsReadmeFileExtension reports whether name looks like a README file
// IsFileExtension reports whether name looks like a target name file
// based on its name. It will look through the provided extensions and check if the file matches
// one of the extensions and provide the index in the extension list.
// If the filename is `readme.` with an unmatched extension it will match with the index equaling
// the length of the provided extension list.
// Note that the '.' should be provided in ext, e.g ".md"
func IsReadmeFileExtension(name string, ext ...string) (int, bool) {
func IsFileExtension(name string, fileType FileType, ext ...string) (int, bool) {
name = strings.ToLower(name)
if len(name) < 6 || name[:6] != "readme" {
lenFileType := len(fileType)
if len(name) < lenFileType || name[:lenFileType] != string(fileType) {
return 0, false
}
for i, extension := range ext {
extension = strings.ToLower(extension)
if name[6:] == extension {
if name[lenFileType:] == extension {
return i, true
}
}
if name[6] == '.' {
if name[lenFileType] == '.' {
return len(ext), true
}

View File

@ -76,10 +76,10 @@ func TestMisc_IsReadmeFileName(t *testing.T) {
}
for _, testCase := range trueTestCases {
assert.True(t, IsReadmeFileName(testCase))
assert.True(t, IsFileName(testCase, FileTypeReadme))
}
for _, testCase := range falseTestCases {
assert.False(t, IsReadmeFileName(testCase))
assert.False(t, IsFileName(testCase, FileTypeReadme))
}
type extensionTestcase struct {
@ -131,7 +131,7 @@ func TestMisc_IsReadmeFileName(t *testing.T) {
}
for _, testCase := range testCasesExtensions {
idx, ok := IsReadmeFileExtension(testCase.name, exts...)
idx, ok := IsFileExtension(testCase.name, FileTypeReadme, exts...)
assert.Equal(t, testCase.expected, ok)
assert.Equal(t, testCase.idx, idx)
}

View File

@ -0,0 +1 @@
{"AGPL-1.0-only":"AGPL-1.0","AGPL-1.0-or-later":"AGPL-1.0","AGPL-3.0-only":"AGPL-3.0","AGPL-3.0-or-later":"AGPL-3.0","CAL-1.0":"CAL-1.0","CAL-1.0-Combined-Work-Exception":"CAL-1.0","GFDL-1.1-invariants-only":"GFDL-1.1","GFDL-1.1-invariants-or-later":"GFDL-1.1","GFDL-1.1-no-invariants-only":"GFDL-1.1","GFDL-1.1-no-invariants-or-later":"GFDL-1.1","GFDL-1.1-only":"GFDL-1.1","GFDL-1.1-or-later":"GFDL-1.1","GFDL-1.2-invariants-only":"GFDL-1.2","GFDL-1.2-invariants-or-later":"GFDL-1.2","GFDL-1.2-no-invariants-only":"GFDL-1.2","GFDL-1.2-no-invariants-or-later":"GFDL-1.2","GFDL-1.2-only":"GFDL-1.2","GFDL-1.2-or-later":"GFDL-1.2","GFDL-1.3-invariants-only":"GFDL-1.3","GFDL-1.3-invariants-or-later":"GFDL-1.3","GFDL-1.3-no-invariants-only":"GFDL-1.3","GFDL-1.3-no-invariants-or-later":"GFDL-1.3","GFDL-1.3-only":"GFDL-1.3","GFDL-1.3-or-later":"GFDL-1.3","GPL-1.0-only":"GPL-1.0","GPL-1.0-or-later":"GPL-1.0","GPL-2.0-only":"GPL-2.0","GPL-2.0-or-later":"GPL-2.0","GPL-3.0-only":"GPL-3.0","GPL-3.0-or-later":"GPL-3.0","LGPL-2.0-only":"LGPL-2.0","LGPL-2.0-or-later":"LGPL-2.0","LGPL-2.1-only":"LGPL-2.1","LGPL-2.1-or-later":"LGPL-2.1","LGPL-3.0-only":"LGPL-3.0","LGPL-3.0-or-later":"LGPL-3.0","MPL-2.0":"MPL-2.0","MPL-2.0-no-copyleft-exception":"MPL-2.0","OFL-1.0":"OFL-1.0","OFL-1.0-RFN":"OFL-1.0","OFL-1.0-no-RFN":"OFL-1.0","OFL-1.1":"OFL-1.1","OFL-1.1-RFN":"OFL-1.1","OFL-1.1-no-RFN":"OFL-1.1"}

View File

@ -1040,6 +1040,7 @@ issue_labels_helper = Select an issue label set.
license = License
license_helper = Select a license file.
license_helper_desc = A license governs what others can and can't do with your code. Not sure which one is right for your project? See <a target="_blank" rel="noopener noreferrer" href="%s">Choose a license.</a>
multiple_licenses = Multiple Licenses
object_format = Object Format
object_format_helper = Object format of the repository. Cannot be changed later. SHA1 is most compatible.
readme = README
@ -2941,6 +2942,7 @@ dashboard.start_schedule_tasks = Start actions schedule tasks
dashboard.sync_branch.started = Branches Sync started
dashboard.sync_tag.started = Tags Sync started
dashboard.rebuild_issue_indexer = Rebuild issue indexer
dashboard.sync_repo_licenses = Sync repo licenses
users.user_manage_panel = User Account Management
users.new_account = Create User Account

View File

@ -1731,6 +1731,7 @@ issues.dependency.add_error_dep_not_same_repo=Ambas as questões têm que estar
issues.review.self.approval=Não pode aprovar o seu próprio pedido de integração.
issues.review.self.rejection=Não pode solicitar modificações sobre o seu próprio pedido de integração.
issues.review.approve=aprovou estas modificações %s
issues.review.comment=reviu %s
issues.review.dismissed=descartou a revisão de %s %s
issues.review.dismissed_label=Descartada
issues.review.left_comment=deixou um comentário

View File

@ -1325,6 +1325,7 @@ func Routes() *web.Router {
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
m.Get("/licenses", reqRepoReader(unit.TypeCode), repo.GetLicenses)
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
m.Group("/avatar", func() {

View File

@ -0,0 +1,51 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"net/http"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/context"
)
// GetLicenses returns licenses
func GetLicenses(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/licenses repository repoGetLicenses
// ---
// summary: Get repo licenses
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// responses:
// "404":
// "$ref": "#/responses/notFound"
// "200":
// "$ref": "#/responses/LicensesList"
licenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
if err != nil {
log.Error("GetRepoLicenses failed: %v", err)
ctx.InternalServerError(err)
return
}
resp := make([]string, len(licenses))
for i := range licenses {
resp[i] = licenses[i].License
}
ctx.JSON(http.StatusOK, resp)
}

View File

@ -731,6 +731,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
}
// Default branch only updated if changed and exist or the repository is empty
updateRepoLicense := false
if opts.DefaultBranch != nil && repo.DefaultBranch != *opts.DefaultBranch && (repo.IsEmpty || ctx.Repo.GitRepo.IsBranchExist(*opts.DefaultBranch)) {
if !repo.IsEmpty {
if err := gitrepo.SetDefaultBranch(ctx, ctx.Repo.Repository, *opts.DefaultBranch); err != nil {
@ -739,6 +740,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
return err
}
}
updateRepoLicense = true
}
repo.DefaultBranch = *opts.DefaultBranch
}
@ -748,6 +750,15 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
return err
}
if updateRepoLicense {
if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
RepoID: ctx.Repo.Repository.ID,
}); err != nil {
ctx.Error(http.StatusInternalServerError, "AddRepoToLicenseUpdaterQueue", err)
return err
}
}
log.Trace("Repository basic settings updated: %s/%s", owner.Name, repo.Name)
return nil
}

View File

@ -359,6 +359,13 @@ type swaggerLanguageStatistics struct {
Body map[string]int64 `json:"body"`
}
// LicensesList
// swagger:response LicensesList
type swaggerLicensesList struct {
// in: body
Body []string `json:"body"`
}
// CombinedStatus
// swagger:response CombinedStatus
type swaggerCombinedStatus struct {

View File

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/private"
gitea_context "code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
// SetDefaultBranch updates the default branch
@ -36,5 +37,15 @@ func SetDefaultBranch(ctx *gitea_context.PrivateContext) {
})
return
}
if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
RepoID: ctx.Repo.Repository.ID,
}); err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to set default branch on repository: %s/%s Error: %v", ownerName, repoName, err),
})
return
}
ctx.PlainText(http.StatusOK, "success")
}

View File

@ -278,10 +278,19 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
branch := refFullName.BranchName()
// If our branch is the default branch of an unforked repo - there's no PR to create or refer to
if !repo.IsFork && branch == baseRepo.DefaultBranch {
results = append(results, private.HookPostReceiveBranchResult{})
continue
if branch == baseRepo.DefaultBranch {
if err := repo_service.AddRepoToLicenseUpdaterQueue(&repo_service.LicenseUpdaterOptions{
RepoID: repo.ID,
}); err != nil {
ctx.JSON(http.StatusInternalServerError, private.Response{Err: err.Error()})
return
}
// If our branch is the default branch of an unforked repo - there's no PR to create or refer to
if !repo.IsFork {
results = append(results, private.HookPostReceiveBranchResult{})
continue
}
}
pr, err := issues_model.GetUnmergedPullRequest(ctx, repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch, issues_model.PullRequestFlowGithub)

View File

@ -90,6 +90,11 @@ func Branches(ctx *context.Context) {
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
ctx.Data["DetectedLicenseFileName"], err = repo_service.GetDetectedLicenseFileName(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
if err != nil {
ctx.ServerError("GetDetectedLicenseFileName", err)
return
}
ctx.HTML(http.StatusOK, tplBranch)
}

View File

@ -29,7 +29,7 @@ import (
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/gitdiff"
git_service "code.gitea.io/gitea/services/repository"
repo_service "code.gitea.io/gitea/services/repository"
)
const (
@ -101,7 +101,11 @@ func Commits(ctx *context.Context) {
pager := context.NewPagination(int(commitsCount), pageSize, page, 5)
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
ctx.Data["DetectedLicenseFileName"], err = repo_service.GetDetectedLicenseFileName(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
if err != nil {
ctx.ServerError("GetDetectedLicenseFileName", err)
return
}
ctx.HTML(http.StatusOK, tplCommits)
}
@ -218,6 +222,12 @@ func SearchCommits(ctx *context.Context) {
}
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["RefName"] = ctx.Repo.RefName
ctx.Data["DetectedLicenseFileName"], err = repo_service.GetDetectedLicenseFileName(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
if err != nil {
ctx.ServerError("GetDetectedLicenseFileName", err)
return
}
ctx.HTML(http.StatusOK, tplCommits)
}
@ -264,11 +274,16 @@ func FileHistory(ctx *context.Context) {
pager.SetDefaultParams(ctx)
ctx.Data["Page"] = pager
ctx.Data["DetectedLicenseFileName"], err = repo_service.GetDetectedLicenseFileName(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
if err != nil {
ctx.ServerError("GetDetectedLicenseFileName", err)
return
}
ctx.HTML(http.StatusOK, tplCommits)
}
func LoadBranchesAndTags(ctx *context.Context) {
response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha"))
response, err := repo_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha"))
if err == nil {
ctx.JSON(http.StatusOK, response)
return

View File

@ -289,7 +289,6 @@ func releasesOrTagsFeed(ctx *context.Context, isReleasesOnly bool, formatType st
// SingleRelease renders a single release's page
func SingleRelease(ctx *context.Context) {
ctx.Data["PageIsReleaseList"] = true
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
writeAccess := ctx.Repo.CanWrite(unit.TypeReleases)
ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived

View File

@ -51,6 +51,7 @@ import (
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/services/context"
issue_service "code.gitea.io/gitea/services/issue"
repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/nektos/act/pkg/model"
@ -68,99 +69,6 @@ const (
tplMigrating base.TplName = "repo/migrate/migrating"
)
// locate a README for a tree in one of the supported paths.
//
// entries is passed to reduce calls to ListEntries(), so
// this has precondition:
//
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
//
// FIXME: There has to be a more efficient way of doing this
func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
// Create a list of extensions in priority order
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
// 2. Txt files - e.g. README.txt
// 3. No extension - e.g. README
exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
extCount := len(exts)
readmeFiles := make([]*git.TreeEntry, extCount+1)
docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
for _, entry := range entries {
if tryWellKnownDirs && entry.IsDir() {
// as a special case for the top-level repo introduction README,
// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
// (note that docsEntries is ignored unless we are at the root)
lowerName := strings.ToLower(entry.Name())
switch lowerName {
case "docs":
if entry.Name() == "docs" || docsEntries[0] == nil {
docsEntries[0] = entry
}
case ".gitea":
if entry.Name() == ".gitea" || docsEntries[1] == nil {
docsEntries[1] = entry
}
case ".github":
if entry.Name() == ".github" || docsEntries[2] == nil {
docsEntries[2] = entry
}
}
continue
}
if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
log.Debug("Potential readme file: %s", entry.Name())
if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
if entry.IsLink() {
target, err := entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
return "", nil, err
} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
readmeFiles[i] = entry
}
} else {
readmeFiles[i] = entry
}
}
}
}
var readmeFile *git.TreeEntry
for _, f := range readmeFiles {
if f != nil {
readmeFile = f
break
}
}
if ctx.Repo.TreePath == "" && readmeFile == nil {
for _, subTreeEntry := range docsEntries {
if subTreeEntry == nil {
continue
}
subTree := subTreeEntry.Tree()
if subTree == nil {
// this should be impossible; if subTreeEntry exists so should this.
continue
}
var err error
childEntries, err := subTree.ListEntries()
if err != nil {
return "", nil, err
}
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
if err != nil && !git.IsErrNotExist(err) {
return "", nil, err
}
if readmeFile != nil {
return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil
}
}
}
return "", readmeFile, nil
}
func renderDirectory(ctx *context.Context) {
entries := renderDirectoryFiles(ctx, 1*time.Second)
if ctx.Written() {
@ -172,37 +80,15 @@ func renderDirectory(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
}
subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
subfolder, readmeFile, err := repo_service.FindFileInEntries(util.FileTypeReadme, entries, ctx.Repo.TreePath, ctx.Locale.Language(), true)
if err != nil {
ctx.ServerError("findReadmeFileInEntries", err)
ctx.ServerError("findFileInEntries", err)
return
}
renderReadmeFile(ctx, subfolder, readmeFile)
}
// localizedExtensions prepends the provided language code with and without a
// regional identifier to the provided extension.
// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
// Note: ext should be prefixed with a `.`
func localizedExtensions(ext, languageCode string) (localizedExts []string) {
if len(languageCode) < 1 {
return []string{ext}
}
lowerLangCode := "." + strings.ToLower(languageCode)
if strings.Contains(lowerLangCode, "-") {
underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
indexOfDash := strings.Index(lowerLangCode, "-")
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
}
// e.g. [.en.md, .md]
return []string{lowerLangCode + ext, ext}
}
type fileInfo struct {
isTextFile bool
isLFSFile bool
@ -498,7 +384,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
shouldRenderSource := ctx.FormString("display") == "source"
readmeExist := util.IsReadmeFileName(blob.Name())
readmeExist := util.IsFileName(blob.Name(), util.FileTypeReadme)
ctx.Data["ReadmeExist"] = readmeExist
markupType := markup.DetectMarkupTypeByFileName(blob.Name())
@ -1077,6 +963,11 @@ func renderHomeCode(ctx *context.Context) {
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink
ctx.Data["DetectedLicenseFileName"], err = repo_service.GetDetectedLicenseFileName(ctx, ctx.Repo.Repository, ctx.Repo.Commit)
if err != nil {
ctx.ServerError("GetDetectedLicenseFileName", err)
return
}
ctx.HTML(http.StatusOK, tplRepoHome)
}

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
@ -544,6 +551,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
ctx.Data["Title"] = owner.Name + "/" + repo.Name
ctx.Data["Repository"] = repo
ctx.Data["Owner"] = ctx.Repo.Repository.Owner
ctx.Data["DefaultBranch"] = ctx.Repo.Repository.DefaultBranch
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
ctx.Data["RepoOwnerIsOrganization"] = repo.Owner.IsOrganization()

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(),
}
}

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()
}

View File

@ -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(),

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

View File

@ -611,6 +611,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

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 {

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},

129
services/repository/file.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repository
import (
"path"
"strings"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
// locate a README/LICENSE for a tree in one of the supported paths.
//
// entries is passed to reduce calls to ListEntries(), so
// this has precondition:
//
// entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
//
// FIXME: There has to be a more efficient way of doing this
func FindFileInEntries(fileType util.FileType, entries []*git.TreeEntry, treePath, language string, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
// Create a list of extensions in priority order
// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
// 2. Txt files - e.g. README.txt
// 3. No extension - e.g. README
exts := append(localizedExtensions(".md", language), ".txt", "") // sorted by priority
extCount := len(exts)
targetFiles := make([]*git.TreeEntry, extCount+1)
docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
for _, entry := range entries {
if tryWellKnownDirs && entry.IsDir() {
// as a special case for the top-level repo introduction README,
// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
// (note that docsEntries is ignored unless we are at the root)
lowerName := strings.ToLower(entry.Name())
switch lowerName {
case "docs":
if entry.Name() == "docs" || docsEntries[0] == nil {
docsEntries[0] = entry
}
case ".gitea":
if entry.Name() == ".gitea" || docsEntries[1] == nil {
docsEntries[1] = entry
}
case ".github":
if entry.Name() == ".github" || docsEntries[2] == nil {
docsEntries[2] = entry
}
}
continue
}
if i, ok := util.IsFileExtension(entry.Name(), fileType, exts...); ok {
log.Debug("Potential %s file: %s", fileType, entry.Name())
if targetFiles[i] == nil || base.NaturalSortLess(targetFiles[i].Name(), entry.Blob().Name()) {
if entry.IsLink() {
target, err := entry.FollowLinks()
if err != nil && !git.IsErrBadLink(err) {
return "", nil, err
} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
targetFiles[i] = entry
}
} else {
targetFiles[i] = entry
}
}
}
}
var targetFile *git.TreeEntry
for _, f := range targetFiles {
if f != nil {
targetFile = f
break
}
}
if treePath == "" && targetFile == nil {
for _, subTreeEntry := range docsEntries {
if subTreeEntry == nil {
continue
}
subTree := subTreeEntry.Tree()
if subTree == nil {
// this should be impossible; if subTreeEntry exists so should this.
continue
}
var err error
childEntries, err := subTree.ListEntries()
if err != nil {
return "", nil, err
}
subfolder, targetFile, err := FindFileInEntries(fileType, childEntries, subTreeEntry.Name(), language, false)
if err != nil && !git.IsErrNotExist(err) {
return "", nil, err
}
if targetFile != nil {
return path.Join(subTreeEntry.Name(), subfolder), targetFile, nil
}
}
}
return "", targetFile, nil
}
// localizedExtensions prepends the provided language code with and without a
// regional identifier to the provided extension.
// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
// Note: ext should be prefixed with a `.`
func localizedExtensions(ext, languageCode string) (localizedExts []string) {
if len(languageCode) < 1 {
return []string{ext}
}
lowerLangCode := "." + strings.ToLower(languageCode)
if strings.Contains(lowerLangCode, "-") {
underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
indexOfDash := strings.Index(lowerLangCode, "-")
// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
}
// e.g. [.en.md, .md]
return []string{lowerLangCode + ext, ext}
}

View File

@ -1,8 +1,7 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
package repository
import (
"reflect"

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 {

View File

@ -0,0 +1,236 @@
// 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"
"code.gitea.io/gitea/modules/util"
licenseclassifier "github.com/google/licenseclassifier/v2"
)
var (
classifier *licenseclassifier.Classifier
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-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 initClassifier() error {
if classifier != nil {
return nil
}
// 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
}
licenseFile, err := findLicenseFile(commit)
if err != nil {
return fmt.Errorf("findLicenseFile: %w", err)
}
licenses := make([]string, 0)
if licenseFile != nil {
r, err := licenseFile.Blob().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)
}
// GetDetectedLicenseFileName returns license file name in the repository if it exists
func GetDetectedLicenseFileName(ctx context.Context, repo *repo_model.Repository, commit *git.Commit) (string, error) {
if commit == nil {
return "", nil
}
licenseFile, err := findLicenseFile(commit)
if err != nil {
return "", fmt.Errorf("findLicenseFile: %w", err)
}
if licenseFile != nil {
return licenseFile.Name(), nil
}
return "", nil
}
// findLicenseFile returns the entry of license file in the repository if it exists
func findLicenseFile(commit *git.Commit) (*git.TreeEntry, error) {
if commit == nil {
return nil, nil
}
entries, err := commit.ListEntries()
if err != nil {
return nil, fmt.Errorf("ListEntries: %w", err)
}
_, f, err := FindFileInEntries(util.FileTypeLicense, entries, "", "", false)
return f, err
}
// detectLicense returns the licenses detected by the given content buff
func detectLicense(r io.Reader) ([]string, error) {
if r == nil {
return nil, nil
}
if err := initClassifier(); err != nil {
return nil, err
}
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
}

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 = initClassifier()
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])
})
}

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)

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
}

View File

@ -13,7 +13,12 @@
{{svg "octicon-tag"}} <b>{{ctx.Locale.PrettyNumber .NumTags}}</b> {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}
</a>
{{end}}
<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
{{if .DetectedRepoLicenses}}
<a class="item muted" href="{{.RepoLink}}/src/{{.DefaultBranch}}/{{PathEscapeSegments .DetectedLicenseFileName}}" data-tooltip-placement="top" data-tooltip-content="{{StringUtils.Join .DetectedRepoLicenses ", "}}">
{{svg "octicon-law"}} <b>{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}</b>
</a>
{{end}}
<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-placement="top" data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}}
{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}}
{{svg "octicon-database"}} <b>{{ctx.Locale.PrettyNumber (index $fileSizeFields 0)}}</b> {{index $fileSizeFields 1}}

View File

@ -10640,6 +10640,42 @@
}
}
},
"/repos/{owner}/{repo}/licenses": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get repo licenses",
"operationId": "repoGetLicenses",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/LicensesList"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/media/{filepath}": {
"get": {
"produces": [
@ -24142,6 +24178,13 @@
"type": "string",
"x-go-name": "LanguagesURL"
},
"licenses": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "Licenses"
},
"link": {
"type": "string",
"x-go-name": "Link"
@ -25717,6 +25760,15 @@
}
}
},
"LicensesList": {
"description": "LicensesList",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
"MarkdownRender": {
"description": "MarkdownRender is a rendered markdown document",
"schema": {

View File

@ -0,0 +1,2 @@
x•ŽM
Â0F]çÙ 2“d&ñ*ù™`Á¶¶Æ…··Wð[>Þƒ¯®ó< ëÐ<C3AB>Æ®j!Æ&=Õ *Êž1*¢@””TS*X3WÞu©cé.êK<C3AA>Ð90E¦Äž”$h+µ„fòg<ÖÝ~_@ÞE{=,! €÷m»Ôu¾YŒ Ž„B´g8fz|ú_erkö9U]Þj~2]<¼

View File

@ -1 +1 @@
65f1bf27bc3bf70f64657658635e66094edbcb4d
90c1019714259b24fb81711d4416ac0f18667dfa

View File

@ -304,11 +304,11 @@ func TestAPICron(t *testing.T) {
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
assert.Equal(t, "28", resp.Header().Get("X-Total-Count"))
assert.Equal(t, "29", resp.Header().Get("X-Total-Count"))
var crons []api.Cron
DecodeJSON(t, resp, &crons)
assert.Len(t, crons, 28)
assert.Len(t, crons, 29)
})
t.Run("Execute", func(t *testing.T) {

View File

@ -0,0 +1,80 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"testing"
"time"
auth_model "code.gitea.io/gitea/models/auth"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
var testLicenseContent = `
Copyright (c) 2024 Gitea
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
`
func TestAPIRepoLicense(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user2")
// Request editor page
req := NewRequest(t, "GET", "/user2/repo1/_new/master/")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
lastCommit := doc.GetInputValueByName("last_commit")
assert.NotEmpty(t, lastCommit)
// Save new file to master branch
req = NewRequestWithValues(t, "POST", "/user2/repo1/_new/master/", map[string]string{
"_csrf": doc.GetCSRF(),
"last_commit": lastCommit,
"tree_path": "LICENSE",
"content": testLicenseContent,
"commit_choice": "direct",
})
session.MakeRequest(t, req, http.StatusSeeOther)
// let gitea update repo license
time.Sleep(time.Second)
checkRepoLicense(t, "user2", "repo1", []string{"BSD-2-Clause"})
// Change default branch
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
branchName := "DefaultBranch"
req = NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1", api.EditRepoOption{
DefaultBranch: &branchName,
}).AddTokenAuth(token)
session.MakeRequest(t, req, http.StatusOK)
// let gitea update repo license
time.Sleep(time.Second)
checkRepoLicense(t, "user2", "repo1", []string{"MIT"})
})
}
func checkRepoLicense(t *testing.T, owner, repo string, expected []string) {
reqURL := fmt.Sprintf("/api/v1/repos/%s/%s/licenses", owner, repo)
req := NewRequest(t, "GET", reqURL)
resp := MakeRequest(t, req, http.StatusOK)
var licenses []string
DecodeJSON(t, resp, &licenses)
assert.ElementsMatch(t, expected, licenses, 0)
}