mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			48 Commits
		
	
	
		
			v1.24.0-de
			...
			v1.19.0-rc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1edb57eda9 | ||
| 
						 | 
					a2a9b0f977 | ||
| 
						 | 
					ff96f804b6 | ||
| 
						 | 
					a926994bfe | ||
| 
						 | 
					83903535e3 | ||
| 
						 | 
					8142408d3a | ||
| 
						 | 
					a4158d1904 | ||
| 
						 | 
					781019216c | ||
| 
						 | 
					1322cd7a58 | ||
| 
						 | 
					464bbd747e | ||
| 
						 | 
					574182e1eb | ||
| 
						 | 
					ef8209a953 | ||
| 
						 | 
					9309098eab | ||
| 
						 | 
					790a79b04c | ||
| 
						 | 
					f8a40dafb9 | ||
| 
						 | 
					9843a0b741 | ||
| 
						 | 
					085a4debd5 | ||
| 
						 | 
					4c1e24864f | ||
| 
						 | 
					5d5f907e7f | ||
| 
						 | 
					39178b5756 | ||
| 
						 | 
					3d8412dd51 | ||
| 
						 | 
					ff7057a46d | ||
| 
						 | 
					bb8ef28913 | ||
| 
						 | 
					13918ad344 | ||
| 
						 | 
					7528ce60e7 | ||
| 
						 | 
					6c6a7e7d97 | ||
| 
						 | 
					111c509287 | ||
| 
						 | 
					9d7ef0ad63 | ||
| 
						 | 
					9aae54c81f | ||
| 
						 | 
					1bc4ffc337 | ||
| 
						 | 
					27879bc45e | ||
| 
						 | 
					a3694b6989 | ||
| 
						 | 
					28625fba5b | ||
| 
						 | 
					7c3196ceac | ||
| 
						 | 
					80c1264f4b | ||
| 
						 | 
					f0340c28f1 | ||
| 
						 | 
					5beb29ad35 | ||
| 
						 | 
					27e307142b | ||
| 
						 | 
					e02e752f68 | ||
| 
						 | 
					5ddf67a9c2 | ||
| 
						 | 
					4d3e2b23b8 | ||
| 
						 | 
					ddf61373f6 | ||
| 
						 | 
					b4ed3f07e4 | ||
| 
						 | 
					ced94f2e0d | ||
| 
						 | 
					aff432b197 | ||
| 
						 | 
					0ac3be1482 | ||
| 
						 | 
					75eaf99076 | ||
| 
						 | 
					e67d60d336 | 
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -859,6 +859,8 @@ fomantic:
 | 
			
		||||
	cp -f $(FOMANTIC_WORK_DIR)/theme.config.less $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/theme.config
 | 
			
		||||
	cp -rf $(FOMANTIC_WORK_DIR)/_site $(FOMANTIC_WORK_DIR)/node_modules/fomantic-ui/src/
 | 
			
		||||
	cd $(FOMANTIC_WORK_DIR) && npx gulp -f node_modules/fomantic-ui/gulpfile.js build
 | 
			
		||||
	# fomantic uses "touchstart" as click event for some browsers, it's not ideal, so we force fomantic to always use "click" as click event
 | 
			
		||||
	$(SED_INPLACE) -e 's/clickEvent[ \t]*=/clickEvent = "click", unstableClickEvent =/g' $(FOMANTIC_WORK_DIR)/build/semantic.js
 | 
			
		||||
	$(SED_INPLACE) -e 's/\r//g' $(FOMANTIC_WORK_DIR)/build/semantic.css $(FOMANTIC_WORK_DIR)/build/semantic.js
 | 
			
		||||
	rm -f $(FOMANTIC_WORK_DIR)/build/*.min.*
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -272,6 +272,7 @@ func runDump(ctx *cli.Context) error {
 | 
			
		||||
		fatal("Failed to create tmp file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = dbDump.Close()
 | 
			
		||||
		if err := util.Remove(dbDump.Name()); err != nil {
 | 
			
		||||
			log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								cmd/serv.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								cmd/serv.go
									
									
									
									
									
								
							@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -290,17 +291,21 @@ func runServ(c *cli.Context) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Special handle for Windows.
 | 
			
		||||
	if setting.IsWindows {
 | 
			
		||||
		verb = strings.Replace(verb, "-", " ", 1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var gitcmd *exec.Cmd
 | 
			
		||||
	verbs := strings.Split(verb, " ")
 | 
			
		||||
	if len(verbs) == 2 {
 | 
			
		||||
		gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath)
 | 
			
		||||
	} else {
 | 
			
		||||
		gitcmd = exec.CommandContext(ctx, verb, repoPath)
 | 
			
		||||
	gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin
 | 
			
		||||
	gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack
 | 
			
		||||
	if _, err := os.Stat(gitBinVerb); err != nil {
 | 
			
		||||
		// if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git
 | 
			
		||||
		// ps: Windows only has "git.exe" in the bin path, so Windows always uses this way
 | 
			
		||||
		verbFields := strings.SplitN(verb, "-", 2)
 | 
			
		||||
		if len(verbFields) == 2 {
 | 
			
		||||
			// use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ...
 | 
			
		||||
			gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if gitcmd == nil {
 | 
			
		||||
		// by default, use the verb (it has been checked above by allowedCommands)
 | 
			
		||||
		gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	process.SetSysProcAttribute(gitcmd)
 | 
			
		||||
 
 | 
			
		||||
@@ -1871,6 +1871,9 @@ ROUTER = console
 | 
			
		||||
;;
 | 
			
		||||
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
 | 
			
		||||
;MINIO_USE_SSL = false
 | 
			
		||||
;;
 | 
			
		||||
;; Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
;MINIO_INSECURE_SKIP_VERIFY = false
 | 
			
		||||
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
@@ -2552,6 +2555,9 @@ ROUTER = console
 | 
			
		||||
;;
 | 
			
		||||
;; Minio enabled ssl only available when STORAGE_TYPE is `minio`
 | 
			
		||||
;MINIO_USE_SSL = false
 | 
			
		||||
;;
 | 
			
		||||
;; Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
;MINIO_INSECURE_SKIP_VERIFY = false
 | 
			
		||||
 | 
			
		||||
;[proxy]
 | 
			
		||||
;; Enable the proxy, all requests to external via HTTP will be affected
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}-rootless
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
{{#unless contains "-rc" build.tag}}
 | 
			
		||||
{{#unless (contains "-rc" build.tag)}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
  - {{this}}-rootless
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
image: gitea/gitea:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}{{#if (hasPrefix "refs/heads/release/v" build.ref)}}{{trimPrefix "refs/heads/release/v" build.ref}}-{{/if}}dev{{/if}}
 | 
			
		||||
{{#if build.tags}}
 | 
			
		||||
{{#unless contains "-rc" build.tag }}
 | 
			
		||||
{{#unless (contains "-rc" build.tag)}}
 | 
			
		||||
tags:
 | 
			
		||||
{{#each build.tags}}
 | 
			
		||||
  - {{this}}
 | 
			
		||||
 
 | 
			
		||||
@@ -854,6 +854,7 @@ Default templates for project boards:
 | 
			
		||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when STORAGE_TYPE is `minio`
 | 
			
		||||
- `MINIO_BASE_PATH`: **attachments/**: Minio base path on the bucket only available when STORAGE_TYPE is `minio`
 | 
			
		||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when STORAGE_TYPE is `minio`
 | 
			
		||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
 | 
			
		||||
## Log (`log`)
 | 
			
		||||
 | 
			
		||||
@@ -1268,6 +1269,7 @@ is `data/lfs` and the default of `MINIO_BASE_PATH` is `lfs/`.
 | 
			
		||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_BASE_PATH`: **lfs/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
 | 
			
		||||
## Storage (`storage`)
 | 
			
		||||
 | 
			
		||||
@@ -1280,6 +1282,7 @@ Default storage configuration for attachments, lfs, avatars and etc.
 | 
			
		||||
- `MINIO_BUCKET`: **gitea**: Minio bucket to store the data only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
 | 
			
		||||
And you can also define a customize storage like below:
 | 
			
		||||
 | 
			
		||||
@@ -1298,6 +1301,8 @@ MINIO_BUCKET = gitea
 | 
			
		||||
MINIO_LOCATION = us-east-1
 | 
			
		||||
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
 | 
			
		||||
MINIO_USE_SSL = false
 | 
			
		||||
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
MINIO_INSECURE_SKIP_VERIFY = false
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
And used by `[attachment]`, `[lfs]` and etc. as `STORAGE_TYPE`.
 | 
			
		||||
@@ -1318,6 +1323,7 @@ is `data/repo-archive` and the default of `MINIO_BASE_PATH` is `repo-archive/`.
 | 
			
		||||
- `MINIO_LOCATION`: **us-east-1**: Minio location to create bucket only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_BASE_PATH`: **repo-archive/**: Minio base path on the bucket only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_USE_SSL`: **false**: Minio enabled ssl only available when `STORAGE_TYPE` is `minio`
 | 
			
		||||
- `MINIO_INSECURE_SKIP_VERIFY`: **false**: Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
 | 
			
		||||
## Proxy (`proxy`)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -431,6 +431,8 @@ MINIO_BUCKET = gitea
 | 
			
		||||
MINIO_LOCATION = us-east-1
 | 
			
		||||
; Minio enabled ssl only available when STORAGE_TYPE is `minio`
 | 
			
		||||
MINIO_USE_SSL = false
 | 
			
		||||
; Minio skip SSL verification available when STORAGE_TYPE is `minio`
 | 
			
		||||
MINIO_INSECURE_SKIP_VERIFY = false
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
然后你在 `[attachment]`, `[lfs]` 等中可以把这个名字用作 `STORAGE_TYPE` 的值。
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,11 @@ weight: 20
 | 
			
		||||
toc: false
 | 
			
		||||
draft: false
 | 
			
		||||
menu:
 | 
			
		||||
sidebar:
 | 
			
		||||
parent: "developers"
 | 
			
		||||
name: "Guidelines for Refactoring"
 | 
			
		||||
weight: 20
 | 
			
		||||
identifier: "guidelines-refactoring"
 | 
			
		||||
  sidebar:
 | 
			
		||||
    parent: "developers"
 | 
			
		||||
    name: "Guidelines for Refactoring"
 | 
			
		||||
    weight: 20
 | 
			
		||||
    identifier: "guidelines-refactoring"
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Guidelines for Refactoring
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								docs/content/doc/developers/hacking-on-gitea.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								docs/content/doc/developers/hacking-on-gitea.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
---
 | 
			
		||||
date: "2016-12-01T16:00:00+02:00"
 | 
			
		||||
title: "加入 Gitea 开源"
 | 
			
		||||
slug: "hacking-on-gitea"
 | 
			
		||||
weight: 10
 | 
			
		||||
toc: false
 | 
			
		||||
draft: false
 | 
			
		||||
menu:
 | 
			
		||||
  sidebar:
 | 
			
		||||
    parent: "developers"
 | 
			
		||||
    name: "加入 Gitea 开源"
 | 
			
		||||
    weight: 10
 | 
			
		||||
    identifier: "hacking-on-gitea"
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Hacking on Gitea
 | 
			
		||||
 | 
			
		||||
首先你需要一些运行环境,这和 [从源代码安装]({{< relref "doc/installation/from-source.zh-cn.md" >}}) 相同,如果你还没有设置好,可以先阅读那个章节。
 | 
			
		||||
 | 
			
		||||
如果你想为 Gitea 贡献代码,你需要 Fork 这个项目并且以 `master` 为开发分支。Gitea 使用 Govendor
 | 
			
		||||
来管理依赖,因此所有依赖项都被工具自动 copy 在 vendor 子目录下。用下面的命令来下载源码:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
go get -d code.gitea.io/gitea
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
然后你可以在 Github 上 fork [Gitea 项目](https://github.com/go-gitea/gitea),之后可以通过下面的命令进入源码目录:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
cd $GOPATH/src/code.gitea.io/gitea
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
要创建 pull requests 你还需要在源码中新增一个 remote 指向你 Fork 的地址,直接推送到 origin 的话会告诉你没有写权限:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
git remote rename origin upstream
 | 
			
		||||
git remote add origin git@github.com:<USERNAME>/gitea.git
 | 
			
		||||
git fetch --all --prune
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
然后你就可以开始开发了。你可以看一下 `Makefile` 的内容。`make test` 可以运行测试程序, `make build` 将生成一个 `gitea` 可运行文件在根目录。如果你的提交比较复杂,尽量多写一些单元测试代码。
 | 
			
		||||
 | 
			
		||||
好了,到这里你已经设置好了所有的开发 Gitea 所需的环境。欢迎成为 Gitea 的 Contributor。
 | 
			
		||||
@@ -9,7 +9,7 @@ menu:
 | 
			
		||||
    parent: "packages"
 | 
			
		||||
    name: "Overview"
 | 
			
		||||
    weight: 1
 | 
			
		||||
    identifier: "overview"
 | 
			
		||||
    identifier: "packages-overview"
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Package Registry
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ menu:
 | 
			
		||||
    parent: "secrets"
 | 
			
		||||
    name: "Overview"
 | 
			
		||||
    weight: 1
 | 
			
		||||
    identifier: "overview"
 | 
			
		||||
    identifier: "secrets-overview"
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
# Secrets
 | 
			
		||||
 
 | 
			
		||||
@@ -194,6 +194,7 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
 | 
			
		||||
		if len(needs) > 0 {
 | 
			
		||||
			status = StatusBlocked
 | 
			
		||||
		}
 | 
			
		||||
		job.Name, _ = util.SplitStringAtByteN(job.Name, 255)
 | 
			
		||||
		runJobs = append(runJobs, &ActionRunJob{
 | 
			
		||||
			RunID:             run.ID,
 | 
			
		||||
			RepoID:            run.RepoID,
 | 
			
		||||
 
 | 
			
		||||
@@ -298,8 +298,9 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
 | 
			
		||||
	if len(workflowJob.Steps) > 0 {
 | 
			
		||||
		steps := make([]*ActionTaskStep, len(workflowJob.Steps))
 | 
			
		||||
		for i, v := range workflowJob.Steps {
 | 
			
		||||
			name, _ := util.SplitStringAtByteN(v.String(), 255)
 | 
			
		||||
			steps[i] = &ActionTaskStep{
 | 
			
		||||
				Name:   v.String(),
 | 
			
		||||
				Name:   name,
 | 
			
		||||
				TaskID: task.ID,
 | 
			
		||||
				Index:  int64(i),
 | 
			
		||||
				RepoID: task.RepoID,
 | 
			
		||||
 
 | 
			
		||||
@@ -153,7 +153,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
 | 
			
		||||
		return DefaultAvatarLink()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	enableFederatedAvatar := system_model.GetSettingBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
 | 
			
		||||
	enableFederatedAvatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureEnableFederatedAvatar)
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	if enableFederatedAvatar && system_model.LibravatarService != nil {
 | 
			
		||||
@@ -174,7 +174,7 @@ func generateEmailAvatarLink(ctx context.Context, email string, size int, final
 | 
			
		||||
		return urlStr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
	disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
	if !disableGravatar {
 | 
			
		||||
		// copy GravatarSourceURL, because we will modify its Path.
 | 
			
		||||
		avatarURLCopy := *system_model.GravatarSourceURL
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ func enableGravatar(t *testing.T) {
 | 
			
		||||
	err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	setting.GravatarSource = gravatarSource
 | 
			
		||||
	err = system_model.Init()
 | 
			
		||||
	err = system_model.Init(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -134,7 +134,7 @@ func Find[T any](ctx context.Context, opts FindOptions, objects *[]T) error {
 | 
			
		||||
	if !opts.IsListAll() {
 | 
			
		||||
		sess.Limit(opts.GetSkipTake())
 | 
			
		||||
	}
 | 
			
		||||
	return sess.Find(&objects)
 | 
			
		||||
	return sess.Find(objects)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Count represents a common count function which accept an options interface
 | 
			
		||||
@@ -148,5 +148,5 @@ func FindAndCount[T any](ctx context.Context, opts FindOptions, objects *[]T) (i
 | 
			
		||||
	if !opts.IsListAll() {
 | 
			
		||||
		sess.Limit(opts.GetSkipTake())
 | 
			
		||||
	}
 | 
			
		||||
	return sess.FindAndCount(&objects)
 | 
			
		||||
	return sess.FindAndCount(objects)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								models/db/list_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								models/db/list_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package db_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type mockListOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *mockListOptions) IsListAll() bool {
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (opts *mockListOptions) ToConds() builder.Cond {
 | 
			
		||||
	return builder.NewCond()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFind(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	xe := unittest.GetXORMEngine()
 | 
			
		||||
	assert.NoError(t, xe.Sync(&repo_model.RepoUnit{}))
 | 
			
		||||
 | 
			
		||||
	opts := mockListOptions{}
 | 
			
		||||
	var repoUnits []repo_model.RepoUnit
 | 
			
		||||
	err := db.Find(db.DefaultContext, &opts, &repoUnits)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 83, len(repoUnits))
 | 
			
		||||
 | 
			
		||||
	cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, 83, cnt)
 | 
			
		||||
 | 
			
		||||
	repoUnits = make([]repo_model.RepoUnit, 0, 10)
 | 
			
		||||
	newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, cnt, newCnt)
 | 
			
		||||
}
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
  fork_id: 0
 | 
			
		||||
  is_template: false
 | 
			
		||||
  template_id: 0
 | 
			
		||||
  size: 6708
 | 
			
		||||
  size: 7028
 | 
			
		||||
  is_fsck_enabled: true
 | 
			
		||||
  close_issues_via_commit_in_any_branch: false
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,12 @@ package issues
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
@@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error {
 | 
			
		||||
	return util.ErrNotExist
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LabelColorPattern is a regexp witch can validate LabelColor
 | 
			
		||||
var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
 | 
			
		||||
 | 
			
		||||
// Label represents a label of repository for issues.
 | 
			
		||||
type Label struct {
 | 
			
		||||
	ID              int64 `xorm:"pk autoincr"`
 | 
			
		||||
@@ -109,12 +106,12 @@ func init() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues.
 | 
			
		||||
func (label *Label) CalOpenIssues() {
 | 
			
		||||
	label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
 | 
			
		||||
func (l *Label) CalOpenIssues() {
 | 
			
		||||
	l.NumOpenIssues = l.NumIssues - l.NumClosedIssues
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CalOpenOrgIssues calculates the open issues of a label for a specific repo
 | 
			
		||||
func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
 | 
			
		||||
func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) {
 | 
			
		||||
	counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{
 | 
			
		||||
		RepoID:   repoID,
 | 
			
		||||
		LabelIDs: []int64{labelID},
 | 
			
		||||
@@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for _, count := range counts {
 | 
			
		||||
		label.NumOpenRepoIssues += count
 | 
			
		||||
		l.NumOpenRepoIssues += count
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked
 | 
			
		||||
func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
 | 
			
		||||
func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) {
 | 
			
		||||
	var labelQuerySlice []string
 | 
			
		||||
	labelSelected := false
 | 
			
		||||
	labelID := strconv.FormatInt(label.ID, 10)
 | 
			
		||||
	labelScope := label.ExclusiveScope()
 | 
			
		||||
	labelID := strconv.FormatInt(l.ID, 10)
 | 
			
		||||
	labelScope := l.ExclusiveScope()
 | 
			
		||||
	for i, s := range currentSelectedLabels {
 | 
			
		||||
		if s == label.ID {
 | 
			
		||||
		if s == l.ID {
 | 
			
		||||
			labelSelected = true
 | 
			
		||||
		} else if -s == label.ID {
 | 
			
		||||
		} else if -s == l.ID {
 | 
			
		||||
			labelSelected = true
 | 
			
		||||
			label.IsExcluded = true
 | 
			
		||||
			l.IsExcluded = true
 | 
			
		||||
		} else if s != 0 {
 | 
			
		||||
			// Exclude other labels in the same scope from selection
 | 
			
		||||
			if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] {
 | 
			
		||||
@@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64,
 | 
			
		||||
	if !labelSelected {
 | 
			
		||||
		labelQuerySlice = append(labelQuerySlice, labelID)
 | 
			
		||||
	}
 | 
			
		||||
	label.IsSelected = labelSelected
 | 
			
		||||
	label.QueryString = strings.Join(labelQuerySlice, ",")
 | 
			
		||||
	l.IsSelected = labelSelected
 | 
			
		||||
	l.QueryString = strings.Join(labelQuerySlice, ",")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BelongsToOrg returns true if label is an organization label
 | 
			
		||||
func (label *Label) BelongsToOrg() bool {
 | 
			
		||||
	return label.OrgID > 0
 | 
			
		||||
func (l *Label) BelongsToOrg() bool {
 | 
			
		||||
	return l.OrgID > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BelongsToRepo returns true if label is a repository label
 | 
			
		||||
func (label *Label) BelongsToRepo() bool {
 | 
			
		||||
	return label.RepoID > 0
 | 
			
		||||
func (l *Label) BelongsToRepo() bool {
 | 
			
		||||
	return l.RepoID > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get color as RGB values in 0..255 range
 | 
			
		||||
func (label *Label) ColorRGB() (float64, float64, float64, error) {
 | 
			
		||||
	color, err := strconv.ParseUint(label.Color[1:], 16, 64)
 | 
			
		||||
func (l *Label) ColorRGB() (float64, float64, float64, error) {
 | 
			
		||||
	color, err := strconv.ParseUint(l.Color[1:], 16, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, 0, 0, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -176,9 +173,9 @@ func (label *Label) ColorRGB() (float64, float64, float64, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Determine if label text should be light or dark to be readable on background color
 | 
			
		||||
func (label *Label) UseLightTextColor() bool {
 | 
			
		||||
	if strings.HasPrefix(label.Color, "#") {
 | 
			
		||||
		if r, g, b, err := label.ColorRGB(); err == nil {
 | 
			
		||||
func (l *Label) UseLightTextColor() bool {
 | 
			
		||||
	if strings.HasPrefix(l.Color, "#") {
 | 
			
		||||
		if r, g, b, err := l.ColorRGB(); err == nil {
 | 
			
		||||
			// Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast
 | 
			
		||||
			// In the future WCAG 3 APCA may be a better solution
 | 
			
		||||
			brightness := (0.299*r + 0.587*g + 0.114*b) / 255
 | 
			
		||||
@@ -190,40 +187,26 @@ func (label *Label) UseLightTextColor() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Return scope substring of label name, or empty string if none exists
 | 
			
		||||
func (label *Label) ExclusiveScope() string {
 | 
			
		||||
	if !label.Exclusive {
 | 
			
		||||
func (l *Label) ExclusiveScope() string {
 | 
			
		||||
	if !l.Exclusive {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	lastIndex := strings.LastIndex(label.Name, "/")
 | 
			
		||||
	if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 {
 | 
			
		||||
	lastIndex := strings.LastIndex(l.Name, "/")
 | 
			
		||||
	if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return label.Name[:lastIndex]
 | 
			
		||||
	return l.Name[:lastIndex]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLabel creates a new label
 | 
			
		||||
func NewLabel(ctx context.Context, label *Label) error {
 | 
			
		||||
	if !LabelColorPattern.MatchString(label.Color) {
 | 
			
		||||
		return fmt.Errorf("bad color code: %s", label.Color)
 | 
			
		||||
func NewLabel(ctx context.Context, l *Label) error {
 | 
			
		||||
	color, err := label.NormalizeColor(l.Color)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	l.Color = color
 | 
			
		||||
 | 
			
		||||
	// normalize case
 | 
			
		||||
	label.Color = strings.ToLower(label.Color)
 | 
			
		||||
 | 
			
		||||
	// add leading hash
 | 
			
		||||
	if label.Color[0] != '#' {
 | 
			
		||||
		label.Color = "#" + label.Color
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// convert 3-character shorthand into 6-character version
 | 
			
		||||
	if len(label.Color) == 4 {
 | 
			
		||||
		r := label.Color[1]
 | 
			
		||||
		g := label.Color[2]
 | 
			
		||||
		b := label.Color[3]
 | 
			
		||||
		label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return db.Insert(ctx, label)
 | 
			
		||||
	return db.Insert(ctx, l)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLabels creates new labels
 | 
			
		||||
@@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error {
 | 
			
		||||
	}
 | 
			
		||||
	defer committer.Close()
 | 
			
		||||
 | 
			
		||||
	for _, label := range labels {
 | 
			
		||||
		if !LabelColorPattern.MatchString(label.Color) {
 | 
			
		||||
			return fmt.Errorf("bad color code: %s", label.Color)
 | 
			
		||||
	for _, l := range labels {
 | 
			
		||||
		color, err := label.NormalizeColor(l.Color)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := db.Insert(ctx, label); err != nil {
 | 
			
		||||
		l.Color = color
 | 
			
		||||
 | 
			
		||||
		if err := db.Insert(ctx, l); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error {
 | 
			
		||||
 | 
			
		||||
// UpdateLabel updates label information.
 | 
			
		||||
func UpdateLabel(l *Label) error {
 | 
			
		||||
	if !LabelColorPattern.MatchString(l.Color) {
 | 
			
		||||
		return fmt.Errorf("bad color code: %s", l.Color)
 | 
			
		||||
	color, err := label.NormalizeColor(l.Color)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	l.Color = color
 | 
			
		||||
 | 
			
		||||
	return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteLabel delete a label
 | 
			
		||||
func DeleteLabel(id, labelID int64) error {
 | 
			
		||||
	label, err := GetLabelByID(db.DefaultContext, labelID)
 | 
			
		||||
	l, err := GetLabelByID(db.DefaultContext, labelID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if IsErrLabelNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
@@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error {
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
	if label.BelongsToOrg() && label.OrgID != id {
 | 
			
		||||
	if l.BelongsToOrg() && l.OrgID != id {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if label.BelongsToRepo() && label.RepoID != id {
 | 
			
		||||
	if l.BelongsToRepo() && l.RepoID != id {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us
 | 
			
		||||
	if err = issue.LoadRepo(ctx); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, label := range labels {
 | 
			
		||||
	for _, l := range labels {
 | 
			
		||||
		// Don't add already present labels and invalid labels
 | 
			
		||||
		if HasIssueLabel(ctx, issue.ID, label.ID) ||
 | 
			
		||||
			(label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) {
 | 
			
		||||
		if HasIssueLabel(ctx, issue.ID, l.ID) ||
 | 
			
		||||
			(l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err = newIssueLabel(ctx, issue, label, doer); err != nil {
 | 
			
		||||
		if err = newIssueLabel(ctx, issue, l, doer); err != nil {
 | 
			
		||||
			return fmt.Errorf("newIssueLabel: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -778,7 +767,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
 | 
			
		||||
	norepo, err := db.GetEngine(ctx).Table("label").
 | 
			
		||||
		Where(builder.And(
 | 
			
		||||
			builder.Gt{"repo_id": 0},
 | 
			
		||||
			builder.NotIn("repo_id", builder.Select("id").From("repository")),
 | 
			
		||||
			builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
 | 
			
		||||
		)).
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -788,7 +777,7 @@ func CountOrphanedLabels(ctx context.Context) (int64, error) {
 | 
			
		||||
	noorg, err := db.GetEngine(ctx).Table("label").
 | 
			
		||||
		Where(builder.And(
 | 
			
		||||
			builder.Gt{"org_id": 0},
 | 
			
		||||
			builder.NotIn("org_id", builder.Select("id").From("user")),
 | 
			
		||||
			builder.NotIn("org_id", builder.Select("id").From("`user`")),
 | 
			
		||||
		)).
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -809,7 +798,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
 | 
			
		||||
	if _, err := db.GetEngine(ctx).
 | 
			
		||||
		Where(builder.And(
 | 
			
		||||
			builder.Gt{"repo_id": 0},
 | 
			
		||||
			builder.NotIn("repo_id", builder.Select("id").From("repository")),
 | 
			
		||||
			builder.NotIn("repo_id", builder.Select("id").From("`repository`")),
 | 
			
		||||
		)).
 | 
			
		||||
		Delete(Label{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -819,7 +808,7 @@ func DeleteOrphanedLabels(ctx context.Context) error {
 | 
			
		||||
	if _, err := db.GetEngine(ctx).
 | 
			
		||||
		Where(builder.And(
 | 
			
		||||
			builder.Gt{"org_id": 0},
 | 
			
		||||
			builder.NotIn("org_id", builder.Select("id").From("user")),
 | 
			
		||||
			builder.NotIn("org_id", builder.Select("id").From("`user`")),
 | 
			
		||||
		)).
 | 
			
		||||
		Delete(Label{}); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,6 @@ import (
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TODO TestGetLabelTemplateFile
 | 
			
		||||
 | 
			
		||||
func TestLabel_CalOpenIssues(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1})
 | 
			
		||||
 
 | 
			
		||||
@@ -111,6 +111,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ
 | 
			
		||||
	return prs, db.GetEngine(db.DefaultContext).
 | 
			
		||||
		Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
 | 
			
		||||
			repoID, branch, false, false).
 | 
			
		||||
		OrderBy("issue.updated_unix DESC").
 | 
			
		||||
		Join("INNER", "issue", "issue.id=pull_request.issue_id").
 | 
			
		||||
		Find(&prs)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,9 +39,9 @@ import (
 | 
			
		||||
var ItemsPerPage = 40
 | 
			
		||||
 | 
			
		||||
// Init initialize model
 | 
			
		||||
func Init() error {
 | 
			
		||||
func Init(ctx context.Context) error {
 | 
			
		||||
	unit.LoadUnitConfig()
 | 
			
		||||
	return system_model.Init()
 | 
			
		||||
	return system_model.Init(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteRepository deletes a repository for a user or organization.
 | 
			
		||||
 
 | 
			
		||||
@@ -79,8 +79,8 @@ func IsErrDataExpired(err error) bool {
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSettingNoCache returns specific setting without using the cache
 | 
			
		||||
func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
 | 
			
		||||
// GetSetting returns specific setting without using the cache
 | 
			
		||||
func GetSetting(ctx context.Context, key string) (*Setting, error) {
 | 
			
		||||
	v, err := GetSettings(ctx, []string{key})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -93,11 +93,11 @@ func GetSettingNoCache(ctx context.Context, key string) (*Setting, error) {
 | 
			
		||||
 | 
			
		||||
const contextCacheKey = "system_setting"
 | 
			
		||||
 | 
			
		||||
// GetSetting returns the setting value via the key
 | 
			
		||||
func GetSetting(ctx context.Context, key string) (string, error) {
 | 
			
		||||
// GetSettingWithCache returns the setting value via the key
 | 
			
		||||
func GetSettingWithCache(ctx context.Context, key string) (string, error) {
 | 
			
		||||
	return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
 | 
			
		||||
		return cache.GetString(genSettingCacheKey(key), func() (string, error) {
 | 
			
		||||
			res, err := GetSettingNoCache(ctx, key)
 | 
			
		||||
			res, err := GetSetting(ctx, key)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
@@ -110,6 +110,15 @@ func GetSetting(ctx context.Context, key string) (string, error) {
 | 
			
		||||
// none existing keys and errors are ignored and result in false
 | 
			
		||||
func GetSettingBool(ctx context.Context, key string) bool {
 | 
			
		||||
	s, _ := GetSetting(ctx, key)
 | 
			
		||||
	if s == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	v, _ := strconv.ParseBool(s.SettingValue)
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetSettingWithCacheBool(ctx context.Context, key string) bool {
 | 
			
		||||
	s, _ := GetSettingWithCache(ctx, key)
 | 
			
		||||
	v, _ := strconv.ParseBool(s)
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
@@ -120,7 +129,7 @@ func GetSettings(ctx context.Context, keys []string) (map[string]*Setting, error
 | 
			
		||||
		keys[i] = strings.ToLower(keys[i])
 | 
			
		||||
	}
 | 
			
		||||
	settings := make([]*Setting, 0, len(keys))
 | 
			
		||||
	if err := db.GetEngine(db.DefaultContext).
 | 
			
		||||
	if err := db.GetEngine(ctx).
 | 
			
		||||
		Where(builder.In("setting_key", keys)).
 | 
			
		||||
		Find(&settings); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -151,9 +160,9 @@ func (settings AllSettings) GetVersion(key string) int {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAllSettings returns all settings from user
 | 
			
		||||
func GetAllSettings() (AllSettings, error) {
 | 
			
		||||
func GetAllSettings(ctx context.Context) (AllSettings, error) {
 | 
			
		||||
	settings := make([]*Setting, 0, 5)
 | 
			
		||||
	if err := db.GetEngine(db.DefaultContext).
 | 
			
		||||
	if err := db.GetEngine(ctx).
 | 
			
		||||
		Find(&settings); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -168,12 +177,12 @@ func GetAllSettings() (AllSettings, error) {
 | 
			
		||||
func DeleteSetting(ctx context.Context, setting *Setting) error {
 | 
			
		||||
	cache.RemoveContextData(ctx, contextCacheKey, setting.SettingKey)
 | 
			
		||||
	cache.Remove(genSettingCacheKey(setting.SettingKey))
 | 
			
		||||
	_, err := db.GetEngine(db.DefaultContext).Delete(setting)
 | 
			
		||||
	_, err := db.GetEngine(ctx).Delete(setting)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SetSettingNoVersion(ctx context.Context, key, value string) error {
 | 
			
		||||
	s, err := GetSettingNoCache(ctx, key)
 | 
			
		||||
	s, err := GetSetting(ctx, key)
 | 
			
		||||
	if IsErrSettingIsNotExist(err) {
 | 
			
		||||
		return SetSetting(ctx, &Setting{
 | 
			
		||||
			SettingKey:   key,
 | 
			
		||||
@@ -189,7 +198,7 @@ func SetSettingNoVersion(ctx context.Context, key, value string) error {
 | 
			
		||||
 | 
			
		||||
// SetSetting updates a users' setting for a specific key
 | 
			
		||||
func SetSetting(ctx context.Context, setting *Setting) error {
 | 
			
		||||
	if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
 | 
			
		||||
	if err := upsertSettingValue(ctx, strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -205,8 +214,8 @@ func SetSetting(ctx context.Context, setting *Setting) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func upsertSettingValue(key, value string, version int) error {
 | 
			
		||||
	return db.WithTx(db.DefaultContext, func(ctx context.Context) error {
 | 
			
		||||
func upsertSettingValue(parentCtx context.Context, key, value string, version int) error {
 | 
			
		||||
	return db.WithTx(parentCtx, func(ctx context.Context) error {
 | 
			
		||||
		e := db.GetEngine(ctx)
 | 
			
		||||
 | 
			
		||||
		// here we use a general method to do a safe upsert for different databases (and most transaction levels)
 | 
			
		||||
@@ -249,9 +258,9 @@ var (
 | 
			
		||||
	LibravatarService *libravatar.Libravatar
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func Init() error {
 | 
			
		||||
func Init(ctx context.Context) error {
 | 
			
		||||
	var disableGravatar bool
 | 
			
		||||
	disableGravatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureDisableGravatar)
 | 
			
		||||
	disableGravatarSetting, err := GetSetting(ctx, KeyPictureDisableGravatar)
 | 
			
		||||
	if IsErrSettingIsNotExist(err) {
 | 
			
		||||
		disableGravatar = setting_module.GetDefaultDisableGravatar()
 | 
			
		||||
		disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
 | 
			
		||||
@@ -262,7 +271,7 @@ func Init() error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var enableFederatedAvatar bool
 | 
			
		||||
	enableFederatedAvatarSetting, err := GetSettingNoCache(db.DefaultContext, KeyPictureEnableFederatedAvatar)
 | 
			
		||||
	enableFederatedAvatarSetting, err := GetSetting(ctx, KeyPictureEnableFederatedAvatar)
 | 
			
		||||
	if IsErrSettingIsNotExist(err) {
 | 
			
		||||
		enableFederatedAvatar = setting_module.GetDefaultEnableFederatedAvatar(disableGravatar)
 | 
			
		||||
		enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
 | 
			
		||||
@@ -275,13 +284,13 @@ func Init() error {
 | 
			
		||||
	if setting_module.OfflineMode {
 | 
			
		||||
		disableGravatar = true
 | 
			
		||||
		enableFederatedAvatar = false
 | 
			
		||||
		if !GetSettingBool(db.DefaultContext, KeyPictureDisableGravatar) {
 | 
			
		||||
			if err := SetSettingNoVersion(db.DefaultContext, KeyPictureDisableGravatar, "true"); err != nil {
 | 
			
		||||
		if !GetSettingBool(ctx, KeyPictureDisableGravatar) {
 | 
			
		||||
			if err := SetSettingNoVersion(ctx, KeyPictureDisableGravatar, "true"); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to set setting %q: %w", KeyPictureDisableGravatar, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if GetSettingBool(db.DefaultContext, KeyPictureEnableFederatedAvatar) {
 | 
			
		||||
			if err := SetSettingNoVersion(db.DefaultContext, KeyPictureEnableFederatedAvatar, "false"); err != nil {
 | 
			
		||||
		if GetSettingBool(ctx, KeyPictureEnableFederatedAvatar) {
 | 
			
		||||
			if err := SetSettingNoVersion(ctx, KeyPictureEnableFederatedAvatar, "false"); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to set setting %q: %w", KeyPictureEnableFederatedAvatar, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,10 @@ func TestSettings(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	value, err := system.GetSetting(db.DefaultContext, keyName)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.EqualValues(t, updatedSetting.SettingValue, value)
 | 
			
		||||
	assert.EqualValues(t, updatedSetting.SettingValue, value.SettingValue)
 | 
			
		||||
 | 
			
		||||
	// get all settings
 | 
			
		||||
	settings, err = system.GetAllSettings()
 | 
			
		||||
	settings, err = system.GetAllSettings(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, settings, 3)
 | 
			
		||||
	assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
 | 
			
		||||
@@ -51,7 +51,7 @@ func TestSettings(t *testing.T) {
 | 
			
		||||
	// delete setting
 | 
			
		||||
	err = system.DeleteSetting(db.DefaultContext, &system.Setting{SettingKey: strings.ToLower(keyName)})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	settings, err = system.GetAllSettings()
 | 
			
		||||
	settings, err = system.GetAllSettings(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, settings, 2)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -113,7 +113,7 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
 | 
			
		||||
	if err = storage.Init(); err != nil {
 | 
			
		||||
		fatalTestError("storage.Init: %v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err = system_model.Init(); err != nil {
 | 
			
		||||
	if err = system_model.Init(db.DefaultContext); err != nil {
 | 
			
		||||
		fatalTestError("models.Init: %v\n", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
 | 
			
		||||
	useLocalAvatar := false
 | 
			
		||||
	autoGenerateAvatar := false
 | 
			
		||||
 | 
			
		||||
	disableGravatar := system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
	disableGravatar := system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
 | 
			
		||||
	switch {
 | 
			
		||||
	case u.UseCustomAvatar:
 | 
			
		||||
 
 | 
			
		||||
@@ -41,9 +41,8 @@ var RecommendedHashAlgorithms = []string{
 | 
			
		||||
	"pbkdf2_hi",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and dealias it to
 | 
			
		||||
// a complete algorithm specification.
 | 
			
		||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
 | 
			
		||||
// hashAlgorithmToSpec converts an algorithm name or a specification to a full algorithm specification
 | 
			
		||||
func hashAlgorithmToSpec(algorithmName string) string {
 | 
			
		||||
	if algorithmName == "" {
 | 
			
		||||
		algorithmName = DefaultHashAlgorithmName
 | 
			
		||||
	}
 | 
			
		||||
@@ -52,10 +51,26 @@ func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHas
 | 
			
		||||
		algorithmName = alias
 | 
			
		||||
		alias, has = aliasAlgorithmNames[algorithmName]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// algorithmName should now be a full algorithm specification
 | 
			
		||||
	// e.g. pbkdf2$50000$50 rather than pbdkf2
 | 
			
		||||
	DefaultHashAlgorithm = Parse(algorithmName)
 | 
			
		||||
 | 
			
		||||
	return algorithmName, DefaultHashAlgorithm
 | 
			
		||||
	return algorithmName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDefaultPasswordHashAlgorithm will take a provided algorithmName and de-alias it to
 | 
			
		||||
// a complete algorithm specification.
 | 
			
		||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
 | 
			
		||||
	algoSpec := hashAlgorithmToSpec(algorithmName)
 | 
			
		||||
	// now we get a full specification, e.g. pbkdf2$50000$50 rather than pbdkf2
 | 
			
		||||
	DefaultHashAlgorithm = Parse(algoSpec)
 | 
			
		||||
	return algoSpec, DefaultHashAlgorithm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConfigHashAlgorithm will try to find a "recommended algorithm name" defined by RecommendedHashAlgorithms for config
 | 
			
		||||
// This function is not fast and is only used for the installation page
 | 
			
		||||
func ConfigHashAlgorithm(algorithm string) string {
 | 
			
		||||
	algorithm = hashAlgorithmToSpec(algorithm)
 | 
			
		||||
	for _, recommAlgo := range RecommendedHashAlgorithms {
 | 
			
		||||
		if algorithm == hashAlgorithmToSpec(recommAlgo) {
 | 
			
		||||
			return recommAlgo
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return algorithm
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -312,7 +312,7 @@ func CheckGitVersionAtLeast(atLeast string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func configSet(key, value string) error {
 | 
			
		||||
	stdout, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	if err != nil && !err.IsExitCode(1) {
 | 
			
		||||
		return fmt.Errorf("failed to get git config %s, err: %w", key, err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -331,7 +331,7 @@ func configSet(key, value string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func configSetNonExist(key, value string) error {
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// already exist
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -349,7 +349,7 @@ func configSetNonExist(key, value string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func configAddNonExist(key, value string) error {
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// already exist
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -366,7 +366,7 @@ func configAddNonExist(key, value string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func configUnsetAll(key, value string) error {
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	_, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// exist, need to remove
 | 
			
		||||
		_, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@
 | 
			
		||||
package git
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
			
		||||
@@ -67,38 +66,6 @@ func (repo *Repository) IsCommitExist(name string) bool {
 | 
			
		||||
	return err == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature {
 | 
			
		||||
	if t.PGPSignature == "" {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var w strings.Builder
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if _, err = fmt.Fprintf(&w,
 | 
			
		||||
		"object %s\ntype %s\ntag %s\ntagger ",
 | 
			
		||||
		t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = t.Tagger.Encode(&w); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = fmt.Fprintf(&w, "\n\n"); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = fmt.Fprintf(&w, t.Message); err != nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &CommitGPGSignature{
 | 
			
		||||
		Signature: t.PGPSignature,
 | 
			
		||||
		Payload:   strings.TrimSpace(w.String()) + "\n",
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
			
		||||
	var tagObject *object.Tag
 | 
			
		||||
 | 
			
		||||
@@ -122,12 +89,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) {
 | 
			
		||||
	commit := convertCommit(gogitCommit)
 | 
			
		||||
	commit.repo = repo
 | 
			
		||||
 | 
			
		||||
	if tagObject != nil {
 | 
			
		||||
		commit.CommitMessage = strings.TrimSpace(tagObject.Message)
 | 
			
		||||
		commit.Author = &tagObject.Tagger
 | 
			
		||||
		commit.Signature = convertPGPSignatureForTag(tagObject)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tree, err := gogitCommit.Tree()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 
 | 
			
		||||
@@ -107,10 +107,6 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		commit.CommitMessage = strings.TrimSpace(tag.Message)
 | 
			
		||||
		commit.Author = tag.Tagger
 | 
			
		||||
		commit.Signature = tag.Signature
 | 
			
		||||
 | 
			
		||||
		return commit, nil
 | 
			
		||||
	case "commit":
 | 
			
		||||
		commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size))
 | 
			
		||||
 
 | 
			
		||||
@@ -43,12 +43,13 @@ func TestGetTagCommitWithSignature(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer bareRepo1.Close()
 | 
			
		||||
 | 
			
		||||
	commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a")
 | 
			
		||||
	// both the tag and the commit are signed here, this validates only the commit signature
 | 
			
		||||
	commit, err := bareRepo1.GetCommit("28b55526e7100924d864dd89e35c1ea62e7a5a32")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotNil(t, commit)
 | 
			
		||||
	assert.NotNil(t, commit.Signature)
 | 
			
		||||
	// test that signature is not in message
 | 
			
		||||
	assert.Equal(t, "tag", commit.CommitMessage)
 | 
			
		||||
	assert.Equal(t, "signed-commit\n", commit.CommitMessage)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetCommitWithBadCommitID(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -277,11 +277,18 @@ func (repo *Repository) GetPatch(base, head string, w io.Writer) error {
 | 
			
		||||
 | 
			
		||||
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits
 | 
			
		||||
func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, error) {
 | 
			
		||||
	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
 | 
			
		||||
	stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(base + ".." + head).RunStdString(&RunOpts{Dir: repo.Path})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Split(stdout, "\n"), err
 | 
			
		||||
	split := strings.Split(stdout, "\000")
 | 
			
		||||
 | 
			
		||||
	// Because Git will always emit filenames with a terminal NUL ignore the last entry in the split - which will always be empty.
 | 
			
		||||
	if len(split) > 0 {
 | 
			
		||||
		split = split[:len(split)-1]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return split, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDiffFromMergeBase generates and return patch data from merge base to head
 | 
			
		||||
 
 | 
			
		||||
@@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) {
 | 
			
		||||
	refs, err := bareRepo1.GetRefs()
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, refs, 5)
 | 
			
		||||
	assert.Len(t, refs, 6)
 | 
			
		||||
 | 
			
		||||
	expectedRefs := []string{
 | 
			
		||||
		BranchPrefix + "branch1",
 | 
			
		||||
		BranchPrefix + "branch2",
 | 
			
		||||
		BranchPrefix + "master",
 | 
			
		||||
		TagPrefix + "test",
 | 
			
		||||
		TagPrefix + "signed-tag",
 | 
			
		||||
		NotesRef,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -43,9 +44,12 @@ func TestRepository_GetRefsFiltered(t *testing.T) {
 | 
			
		||||
	refs, err := bareRepo1.GetRefsFiltered(TagPrefix)
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	if assert.Len(t, refs, 1) {
 | 
			
		||||
		assert.Equal(t, TagPrefix+"test", refs[0].Name)
 | 
			
		||||
	if assert.Len(t, refs, 2) {
 | 
			
		||||
		assert.Equal(t, TagPrefix+"signed-tag", refs[0].Name)
 | 
			
		||||
		assert.Equal(t, "tag", refs[0].Type)
 | 
			
		||||
		assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[0].Object.String())
 | 
			
		||||
		assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", refs[0].Object.String())
 | 
			
		||||
		assert.Equal(t, TagPrefix+"test", refs[1].Name)
 | 
			
		||||
		assert.Equal(t, "tag", refs[1].Type)
 | 
			
		||||
		assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[1].Object.String())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,9 @@ func TestRepository_GetCodeActivityStats(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NotNil(t, code)
 | 
			
		||||
 | 
			
		||||
	assert.EqualValues(t, 9, code.CommitCount)
 | 
			
		||||
	assert.EqualValues(t, 10, code.CommitCount)
 | 
			
		||||
	assert.EqualValues(t, 3, code.AuthorCount)
 | 
			
		||||
	assert.EqualValues(t, 9, code.CommitCountInAllBranches)
 | 
			
		||||
	assert.EqualValues(t, 10, code.CommitCountInAllBranches)
 | 
			
		||||
	assert.EqualValues(t, 10, code.Additions)
 | 
			
		||||
	assert.EqualValues(t, 1, code.Deletions)
 | 
			
		||||
	assert.Len(t, code.Authors, 3)
 | 
			
		||||
 
 | 
			
		||||
@@ -25,11 +25,14 @@ func TestRepository_GetTags(t *testing.T) {
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	assert.Len(t, tags, 1)
 | 
			
		||||
	assert.Len(t, tags, 2)
 | 
			
		||||
	assert.Equal(t, len(tags), total)
 | 
			
		||||
	assert.EqualValues(t, "test", tags[0].Name)
 | 
			
		||||
	assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String())
 | 
			
		||||
	assert.EqualValues(t, "signed-tag", tags[0].Name)
 | 
			
		||||
	assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String())
 | 
			
		||||
	assert.EqualValues(t, "tag", tags[0].Type)
 | 
			
		||||
	assert.EqualValues(t, "test", tags[1].Name)
 | 
			
		||||
	assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String())
 | 
			
		||||
	assert.EqualValues(t, "tag", tags[1].Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepository_GetTag(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,10 @@ func TestGetLatestCommitTime(t *testing.T) {
 | 
			
		||||
	bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
 | 
			
		||||
	lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	// Time is Sun Jul 21 22:43:13 2019 +0200
 | 
			
		||||
	// Time is Sun Nov 13 16:40:14 2022 +0100
 | 
			
		||||
	// which is the time of commit
 | 
			
		||||
	// feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master)
 | 
			
		||||
	assert.EqualValues(t, 1563741793, lct.Unix())
 | 
			
		||||
	// ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master)
 | 
			
		||||
	assert.EqualValues(t, 1668354014, lct.Unix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepoIsEmpty(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								modules/git/tests/repos/repo1_bare/index
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								modules/git/tests/repos/repo1_bare/index
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1 +1,2 @@
 | 
			
		||||
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200	push
 | 
			
		||||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100	push
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,2 @@
 | 
			
		||||
37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind <me@silverwind.io> 1563741799 +0200	push
 | 
			
		||||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind <me@silverwind.io> 1668354026 +0100	push
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -1 +1 @@
 | 
			
		||||
feaf4ba6bc635fec442f46ddd4512416ec43c2c2
 | 
			
		||||
ce064814f4a0d337b333e646ece456cd39fab612
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								modules/git/tests/repos/repo1_bare/refs/tags/signed-tag
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								modules/git/tests/repos/repo1_bare/refs/tags/signed-tag
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
36f97d9a96457e2bab511db30fe2db03893ebc64
 | 
			
		||||
							
								
								
									
										46
									
								
								modules/label/label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								modules/label/label.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package label
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// colorPattern is a regexp which can validate label color
 | 
			
		||||
var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$")
 | 
			
		||||
 | 
			
		||||
// Label represents label information loaded from template
 | 
			
		||||
type Label struct {
 | 
			
		||||
	Name        string `yaml:"name"`
 | 
			
		||||
	Color       string `yaml:"color"`
 | 
			
		||||
	Description string `yaml:"description,omitempty"`
 | 
			
		||||
	Exclusive   bool   `yaml:"exclusive,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NormalizeColor normalizes a color string to a 6-character hex code
 | 
			
		||||
func NormalizeColor(color string) (string, error) {
 | 
			
		||||
	// normalize case
 | 
			
		||||
	color = strings.TrimSpace(strings.ToLower(color))
 | 
			
		||||
 | 
			
		||||
	// add leading hash
 | 
			
		||||
	if len(color) == 6 || len(color) == 3 {
 | 
			
		||||
		color = "#" + color
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !colorPattern.MatchString(color) {
 | 
			
		||||
		return "", fmt.Errorf("bad color code: %s", color)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// convert 3-character shorthand into 6-character version
 | 
			
		||||
	if len(color) == 4 {
 | 
			
		||||
		r := color[1]
 | 
			
		||||
		g := color[2]
 | 
			
		||||
		b := color[3]
 | 
			
		||||
		color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return color, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								modules/label/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								modules/label/parser.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package label
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/options"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/yaml.v3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type labelFile struct {
 | 
			
		||||
	Labels []*Label `yaml:"labels"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrTemplateLoad represents a "ErrTemplateLoad" kind of error.
 | 
			
		||||
type ErrTemplateLoad struct {
 | 
			
		||||
	TemplateFile  string
 | 
			
		||||
	OriginalError error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrTemplateLoad checks if an error is a ErrTemplateLoad.
 | 
			
		||||
func IsErrTemplateLoad(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrTemplateLoad)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrTemplateLoad) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetTemplateFile loads the label template file by given name,
 | 
			
		||||
// then parses and returns a list of name-color pairs and optionally description.
 | 
			
		||||
func GetTemplateFile(name string) ([]*Label, error) {
 | 
			
		||||
	data, err := options.GetRepoInitFile("label", name+".yaml")
 | 
			
		||||
	if err == nil && len(data) > 0 {
 | 
			
		||||
		return parseYamlFormat(name+".yaml", data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err = options.GetRepoInitFile("label", name+".yml")
 | 
			
		||||
	if err == nil && len(data) > 0 {
 | 
			
		||||
		return parseYamlFormat(name+".yml", data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err = options.GetRepoInitFile("label", name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return parseLegacyFormat(name, data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseYamlFormat(name string, data []byte) ([]*Label, error) {
 | 
			
		||||
	lf := &labelFile{}
 | 
			
		||||
 | 
			
		||||
	if err := yaml.Unmarshal(data, lf); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Validate label data and fix colors
 | 
			
		||||
	for _, l := range lf.Labels {
 | 
			
		||||
		l.Color = strings.TrimSpace(l.Color)
 | 
			
		||||
		if len(l.Name) == 0 || len(l.Color) == 0 {
 | 
			
		||||
			return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")}
 | 
			
		||||
		}
 | 
			
		||||
		color, err := NormalizeColor(l.Color)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)}
 | 
			
		||||
		}
 | 
			
		||||
		l.Color = color
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return lf.Labels, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseLegacyFormat(name string, data []byte) ([]*Label, error) {
 | 
			
		||||
	lines := strings.Split(string(data), "\n")
 | 
			
		||||
	list := make([]*Label, 0, len(lines))
 | 
			
		||||
	for i := 0; i < len(lines); i++ {
 | 
			
		||||
		line := strings.TrimSpace(lines[i])
 | 
			
		||||
		if len(line) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		parts, description, _ := strings.Cut(line, ";")
 | 
			
		||||
 | 
			
		||||
		color, name, ok := strings.Cut(parts, " ")
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		color, err := NormalizeColor(color)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		list = append(list, &Label{
 | 
			
		||||
			Name:        strings.TrimSpace(name),
 | 
			
		||||
			Color:       color,
 | 
			
		||||
			Description: strings.TrimSpace(description),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return list, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadFormatted loads the labels' list of a template file as a string separated by comma
 | 
			
		||||
func LoadFormatted(name string) (string, error) {
 | 
			
		||||
	var buf strings.Builder
 | 
			
		||||
	list, err := GetTemplateFile(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(list); i++ {
 | 
			
		||||
		if i > 0 {
 | 
			
		||||
			buf.WriteString(", ")
 | 
			
		||||
		}
 | 
			
		||||
		buf.WriteString(list[i].Name)
 | 
			
		||||
	}
 | 
			
		||||
	return buf.String(), nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								modules/label/parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								modules/label/parser_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package label
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestYamlParser(t *testing.T) {
 | 
			
		||||
	data := []byte(`labels:
 | 
			
		||||
  - name: priority/low
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: "#0000ee"
 | 
			
		||||
    description: "Low priority"
 | 
			
		||||
  - name: priority/medium
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: "0e0"
 | 
			
		||||
    description: "Medium priority"
 | 
			
		||||
  - name: priority/high
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: "#ee0000"
 | 
			
		||||
    description: "High priority"
 | 
			
		||||
  - name: type/bug
 | 
			
		||||
    color: "#f00"
 | 
			
		||||
    description: "Bug"`)
 | 
			
		||||
 | 
			
		||||
	labels, err := parseYamlFormat("test", data)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Len(t, labels, 4)
 | 
			
		||||
	assert.Equal(t, "priority/low", labels[0].Name)
 | 
			
		||||
	assert.True(t, labels[0].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#0000ee", labels[0].Color)
 | 
			
		||||
	assert.Equal(t, "Low priority", labels[0].Description)
 | 
			
		||||
	assert.Equal(t, "priority/medium", labels[1].Name)
 | 
			
		||||
	assert.True(t, labels[1].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#00ee00", labels[1].Color)
 | 
			
		||||
	assert.Equal(t, "Medium priority", labels[1].Description)
 | 
			
		||||
	assert.Equal(t, "priority/high", labels[2].Name)
 | 
			
		||||
	assert.True(t, labels[2].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#ee0000", labels[2].Color)
 | 
			
		||||
	assert.Equal(t, "High priority", labels[2].Description)
 | 
			
		||||
	assert.Equal(t, "type/bug", labels[3].Name)
 | 
			
		||||
	assert.False(t, labels[3].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#ff0000", labels[3].Color)
 | 
			
		||||
	assert.Equal(t, "Bug", labels[3].Description)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLegacyParser(t *testing.T) {
 | 
			
		||||
	data := []byte(`#ee0701 bug   ;   Something is not working
 | 
			
		||||
#cccccc   duplicate ; This issue or pull request already exists
 | 
			
		||||
#84b6eb enhancement`)
 | 
			
		||||
 | 
			
		||||
	labels, err := parseLegacyFormat("test", data)
 | 
			
		||||
	require.NoError(t, err)
 | 
			
		||||
	require.Len(t, labels, 3)
 | 
			
		||||
	assert.Equal(t, "bug", labels[0].Name)
 | 
			
		||||
	assert.False(t, labels[0].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#ee0701", labels[0].Color)
 | 
			
		||||
	assert.Equal(t, "Something is not working", labels[0].Description)
 | 
			
		||||
	assert.Equal(t, "duplicate", labels[1].Name)
 | 
			
		||||
	assert.False(t, labels[1].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#cccccc", labels[1].Color)
 | 
			
		||||
	assert.Equal(t, "This issue or pull request already exists", labels[1].Description)
 | 
			
		||||
	assert.Equal(t, "enhancement", labels[2].Name)
 | 
			
		||||
	assert.False(t, labels[2].Exclusive)
 | 
			
		||||
	assert.Equal(t, "#84b6eb", labels[2].Color)
 | 
			
		||||
	assert.Empty(t, labels[2].Description)
 | 
			
		||||
}
 | 
			
		||||
@@ -132,6 +132,8 @@ func createDefaultPolicy() *bluemonday.Policy {
 | 
			
		||||
 | 
			
		||||
	policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
 | 
			
		||||
 | 
			
		||||
	policy.AllowAttrs("src", "autoplay", "controls").OnElements("video")
 | 
			
		||||
 | 
			
		||||
	policy.AllowAttrs("itemscope", "itemtype").OnElements("div")
 | 
			
		||||
 | 
			
		||||
	// FIXME: Need to handle longdesc in img but there is no easy way to do it
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										44
									
								
								modules/options/repo.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								modules/options/repo.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package options
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetRepoInitFile returns repository init files
 | 
			
		||||
func GetRepoInitFile(tp, name string) ([]byte, error) {
 | 
			
		||||
	cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
 | 
			
		||||
	relPath := path.Join("options", tp, cleanedName)
 | 
			
		||||
 | 
			
		||||
	// Use custom file when available.
 | 
			
		||||
	customPath := path.Join(setting.CustomPath, relPath)
 | 
			
		||||
	isFile, err := util.IsFile(customPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if isFile {
 | 
			
		||||
		return os.ReadFile(customPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch tp {
 | 
			
		||||
	case "readme":
 | 
			
		||||
		return Readme(cleanedName)
 | 
			
		||||
	case "gitignore":
 | 
			
		||||
		return Gitignore(cleanedName)
 | 
			
		||||
	case "license":
 | 
			
		||||
		return License(cleanedName)
 | 
			
		||||
	case "label":
 | 
			
		||||
		return Labels(cleanedName)
 | 
			
		||||
	default:
 | 
			
		||||
		return []byte{}, fmt.Errorf("Invalid init file type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -106,7 +106,7 @@ func enableGravatar(t *testing.T) {
 | 
			
		||||
	err := system_model.SetSettingNoVersion(db.DefaultContext, system_model.KeyPictureDisableGravatar, "false")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	setting.GravatarSource = "https://secure.gravatar.com/avatar"
 | 
			
		||||
	err = system_model.Init()
 | 
			
		||||
	err = system_model.Init(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/models/webhook"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
@@ -189,7 +190,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
 | 
			
		||||
 | 
			
		||||
	// Check if label template exist
 | 
			
		||||
	if len(opts.IssueLabels) > 0 {
 | 
			
		||||
		if _, err := GetLabelTemplateFile(opts.IssueLabels); err != nil {
 | 
			
		||||
		if _, err := label.GetTemplateFile(opts.IssueLabels); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import (
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/options"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -40,114 +41,6 @@ var (
 | 
			
		||||
	LabelTemplates map[string]string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error.
 | 
			
		||||
type ErrIssueLabelTemplateLoad struct {
 | 
			
		||||
	TemplateFile  string
 | 
			
		||||
	OriginalError error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrIssueLabelTemplateLoad checks if an error is a ErrIssueLabelTemplateLoad.
 | 
			
		||||
func IsErrIssueLabelTemplateLoad(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrIssueLabelTemplateLoad)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrIssueLabelTemplateLoad) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRepoInitFile returns repository init files
 | 
			
		||||
func GetRepoInitFile(tp, name string) ([]byte, error) {
 | 
			
		||||
	cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
 | 
			
		||||
	relPath := path.Join("options", tp, cleanedName)
 | 
			
		||||
 | 
			
		||||
	// Use custom file when available.
 | 
			
		||||
	customPath := path.Join(setting.CustomPath, relPath)
 | 
			
		||||
	isFile, err := util.IsFile(customPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
 | 
			
		||||
	}
 | 
			
		||||
	if isFile {
 | 
			
		||||
		return os.ReadFile(customPath)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch tp {
 | 
			
		||||
	case "readme":
 | 
			
		||||
		return options.Readme(cleanedName)
 | 
			
		||||
	case "gitignore":
 | 
			
		||||
		return options.Gitignore(cleanedName)
 | 
			
		||||
	case "license":
 | 
			
		||||
		return options.License(cleanedName)
 | 
			
		||||
	case "label":
 | 
			
		||||
		return options.Labels(cleanedName)
 | 
			
		||||
	default:
 | 
			
		||||
		return []byte{}, fmt.Errorf("Invalid init file type")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabelTemplateFile loads the label template file by given name,
 | 
			
		||||
// then parses and returns a list of name-color pairs and optionally description.
 | 
			
		||||
func GetLabelTemplateFile(name string) ([][3]string, error) {
 | 
			
		||||
	data, err := GetRepoInitFile("label", name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lines := strings.Split(string(data), "\n")
 | 
			
		||||
	list := make([][3]string, 0, len(lines))
 | 
			
		||||
	for i := 0; i < len(lines); i++ {
 | 
			
		||||
		line := strings.TrimSpace(lines[i])
 | 
			
		||||
		if len(line) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		parts := strings.SplitN(line, ";", 2)
 | 
			
		||||
 | 
			
		||||
		fields := strings.SplitN(parts[0], " ", 2)
 | 
			
		||||
		if len(fields) != 2 {
 | 
			
		||||
			return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		color := strings.Trim(fields[0], " ")
 | 
			
		||||
		if len(color) == 6 {
 | 
			
		||||
			color = "#" + color
 | 
			
		||||
		}
 | 
			
		||||
		if !issues_model.LabelColorPattern.MatchString(color) {
 | 
			
		||||
			return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var description string
 | 
			
		||||
 | 
			
		||||
		if len(parts) > 1 {
 | 
			
		||||
			description = strings.TrimSpace(parts[1])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fields[1] = strings.TrimSpace(fields[1])
 | 
			
		||||
		list = append(list, [3]string{fields[1], color, description})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return list, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadLabels(labelTemplate string) ([]string, error) {
 | 
			
		||||
	list, err := GetLabelTemplateFile(labelTemplate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	labels := make([]string, len(list))
 | 
			
		||||
	for i := 0; i < len(list); i++ {
 | 
			
		||||
		labels[i] = list[i][0]
 | 
			
		||||
	}
 | 
			
		||||
	return labels, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadLabelsFormatted loads the labels' list of a template file as a string separated by comma
 | 
			
		||||
func LoadLabelsFormatted(labelTemplate string) (string, error) {
 | 
			
		||||
	labels, err := loadLabels(labelTemplate)
 | 
			
		||||
	return strings.Join(labels, ", "), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadRepoConfig loads the repository config
 | 
			
		||||
func LoadRepoConfig() {
 | 
			
		||||
	// Load .gitignore and license files and readme templates.
 | 
			
		||||
@@ -158,6 +51,14 @@ func LoadRepoConfig() {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal("Failed to get %s files: %v", t, err)
 | 
			
		||||
		}
 | 
			
		||||
		if t == "label" {
 | 
			
		||||
			for i, f := range files {
 | 
			
		||||
				ext := strings.ToLower(filepath.Ext(f))
 | 
			
		||||
				if ext == ".yaml" || ext == ".yml" {
 | 
			
		||||
					files[i] = f[:len(f)-len(ext)]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		customPath := path.Join(setting.CustomPath, "options", t)
 | 
			
		||||
		isDir, err := util.IsDir(customPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -190,7 +91,7 @@ func LoadRepoConfig() {
 | 
			
		||||
	// Load label templates
 | 
			
		||||
	LabelTemplates = make(map[string]string)
 | 
			
		||||
	for _, templateFile := range LabelTemplatesFiles {
 | 
			
		||||
		labels, err := LoadLabelsFormatted(templateFile)
 | 
			
		||||
		labels, err := label.LoadFormatted(templateFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("Failed to load labels: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -235,7 +136,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// README
 | 
			
		||||
	data, err := GetRepoInitFile("readme", opts.Readme)
 | 
			
		||||
	data, err := options.GetRepoInitFile("readme", opts.Readme)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -263,7 +164,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		names := strings.Split(opts.Gitignores, ",")
 | 
			
		||||
		for _, name := range names {
 | 
			
		||||
			data, err = GetRepoInitFile("gitignore", name)
 | 
			
		||||
			data, err = options.GetRepoInitFile("gitignore", name)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -281,7 +182,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
 | 
			
		||||
 | 
			
		||||
	// LICENSE
 | 
			
		||||
	if len(opts.License) > 0 {
 | 
			
		||||
		data, err = GetRepoInitFile("license", opts.License)
 | 
			
		||||
		data, err = options.GetRepoInitFile("license", opts.License)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -443,7 +344,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re
 | 
			
		||||
 | 
			
		||||
// InitializeLabels adds a label set to a repository using a template
 | 
			
		||||
func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error {
 | 
			
		||||
	list, err := GetLabelTemplateFile(labelTemplate)
 | 
			
		||||
	list, err := label.GetTemplateFile(labelTemplate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@@ -451,9 +352,10 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg
 | 
			
		||||
	labels := make([]*issues_model.Label, len(list))
 | 
			
		||||
	for i := 0; i < len(list); i++ {
 | 
			
		||||
		labels[i] = &issues_model.Label{
 | 
			
		||||
			Name:        list[i][0],
 | 
			
		||||
			Description: list[i][2],
 | 
			
		||||
			Color:       list[i][1],
 | 
			
		||||
			Name:        list[i].Name,
 | 
			
		||||
			Exclusive:   list[i].Exclusive,
 | 
			
		||||
			Description: list[i].Description,
 | 
			
		||||
			Color:       list[i].Color,
 | 
			
		||||
		}
 | 
			
		||||
		if isOrg {
 | 
			
		||||
			labels[i].OrgID = id
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ func getStorage(rootCfg ConfigProvider, name, typ string, targetSec *ini.Section
 | 
			
		||||
	sec.Key("MINIO_BUCKET").MustString("gitea")
 | 
			
		||||
	sec.Key("MINIO_LOCATION").MustString("us-east-1")
 | 
			
		||||
	sec.Key("MINIO_USE_SSL").MustBool(false)
 | 
			
		||||
	sec.Key("MINIO_INSECURE_SKIP_VERIFY").MustBool(false)
 | 
			
		||||
 | 
			
		||||
	if targetSec == nil {
 | 
			
		||||
		targetSec, _ = rootCfg.NewSection(name)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,9 @@ package storage
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
@@ -42,13 +44,14 @@ const MinioStorageType Type = "minio"
 | 
			
		||||
 | 
			
		||||
// MinioStorageConfig represents the configuration for a minio storage
 | 
			
		||||
type MinioStorageConfig struct {
 | 
			
		||||
	Endpoint        string `ini:"MINIO_ENDPOINT"`
 | 
			
		||||
	AccessKeyID     string `ini:"MINIO_ACCESS_KEY_ID"`
 | 
			
		||||
	SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY"`
 | 
			
		||||
	Bucket          string `ini:"MINIO_BUCKET"`
 | 
			
		||||
	Location        string `ini:"MINIO_LOCATION"`
 | 
			
		||||
	BasePath        string `ini:"MINIO_BASE_PATH"`
 | 
			
		||||
	UseSSL          bool   `ini:"MINIO_USE_SSL"`
 | 
			
		||||
	Endpoint           string `ini:"MINIO_ENDPOINT"`
 | 
			
		||||
	AccessKeyID        string `ini:"MINIO_ACCESS_KEY_ID"`
 | 
			
		||||
	SecretAccessKey    string `ini:"MINIO_SECRET_ACCESS_KEY"`
 | 
			
		||||
	Bucket             string `ini:"MINIO_BUCKET"`
 | 
			
		||||
	Location           string `ini:"MINIO_LOCATION"`
 | 
			
		||||
	BasePath           string `ini:"MINIO_BASE_PATH"`
 | 
			
		||||
	UseSSL             bool   `ini:"MINIO_USE_SSL"`
 | 
			
		||||
	InsecureSkipVerify bool   `ini:"MINIO_INSECURE_SKIP_VERIFY"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MinioStorage returns a minio bucket storage
 | 
			
		||||
@@ -90,8 +93,9 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
 | 
			
		||||
	log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
 | 
			
		||||
 | 
			
		||||
	minioClient, err := minio.New(config.Endpoint, &minio.Options{
 | 
			
		||||
		Creds:  credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
 | 
			
		||||
		Secure: config.UseSSL,
 | 
			
		||||
		Creds:     credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, ""),
 | 
			
		||||
		Secure:    config.UseSSL,
 | 
			
		||||
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, convertMinioErr(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ func NewFuncMap() []template.FuncMap {
 | 
			
		||||
			return setting.AssetVersion
 | 
			
		||||
		},
 | 
			
		||||
		"DisableGravatar": func(ctx context.Context) bool {
 | 
			
		||||
			return system_model.GetSettingBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
			return system_model.GetSettingWithCacheBool(ctx, system_model.KeyPictureDisableGravatar)
 | 
			
		||||
		},
 | 
			
		||||
		"DefaultShowFullName": func() bool {
 | 
			
		||||
			return setting.UI.DefaultShowFullName
 | 
			
		||||
@@ -174,8 +174,9 @@ func NewFuncMap() []template.FuncMap {
 | 
			
		||||
		"RenderEmojiPlain":               emoji.ReplaceAliases,
 | 
			
		||||
		"ReactionToEmoji":                ReactionToEmoji,
 | 
			
		||||
		"RenderNote":                     RenderNote,
 | 
			
		||||
		"RenderMarkdownToHtml": func(input string) template.HTML {
 | 
			
		||||
		"RenderMarkdownToHtml": func(ctx context.Context, input string) template.HTML {
 | 
			
		||||
			output, err := markdown.RenderString(&markup.RenderContext{
 | 
			
		||||
				Ctx:       ctx,
 | 
			
		||||
				URLPrefix: setting.AppSubURL,
 | 
			
		||||
			}, input)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								options/label/Advanced.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								options/label/Advanced.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
labels:
 | 
			
		||||
  - name: "Kind/Bug"
 | 
			
		||||
    color: ee0701
 | 
			
		||||
    description: Something is not working
 | 
			
		||||
  - name: "Kind/Feature"
 | 
			
		||||
    color: 0288d1
 | 
			
		||||
    description: New functionality
 | 
			
		||||
  - name: "Kind/Enhancement"
 | 
			
		||||
    color: 84b6eb
 | 
			
		||||
    description: Improve existing functionality
 | 
			
		||||
  - name: "Kind/Security"
 | 
			
		||||
    color: 9c27b0
 | 
			
		||||
    description: This is security issue
 | 
			
		||||
  - name: "Kind/Testing"
 | 
			
		||||
    color: 795548
 | 
			
		||||
    description: Issue or pull request related to testing
 | 
			
		||||
  - name: "Kind/Breaking"
 | 
			
		||||
    color: c62828
 | 
			
		||||
    description: Breaking change that won't be backward compatible
 | 
			
		||||
  - name: "Kind/Documentation"
 | 
			
		||||
    color: 37474f
 | 
			
		||||
    description: Documentation changes
 | 
			
		||||
  - name: "Reviewed/Duplicate"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 616161
 | 
			
		||||
    description: This issue or pull request already exists
 | 
			
		||||
  - name: "Reviewed/Invalid"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 546e7a
 | 
			
		||||
    description: Invalid issue
 | 
			
		||||
  - name: "Reviewed/Confirmed"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 795548
 | 
			
		||||
    description: Issue has been confirmed
 | 
			
		||||
  - name: "Reviewed/Won't Fix"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: eeeeee
 | 
			
		||||
    description: This issue won't be fixed
 | 
			
		||||
  - name: "Status/Need More Info"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 424242
 | 
			
		||||
    description: Feedback is required to reproduce issue or to continue work
 | 
			
		||||
  - name: "Status/Blocked"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 880e4f
 | 
			
		||||
    description: Something is blocking this issue or pull request
 | 
			
		||||
  - name: "Status/Abandoned"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: "222222"
 | 
			
		||||
    description: Somebody has started to work on this but abandoned work
 | 
			
		||||
  - name: "Priority/Critical"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: b71c1c
 | 
			
		||||
    description: The priority is critical
 | 
			
		||||
    priority: critical
 | 
			
		||||
  - name: "Priority/High"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: d32f2f
 | 
			
		||||
    description: The priority is high
 | 
			
		||||
    priority: high
 | 
			
		||||
  - name: "Priority/Medium"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: e64a19
 | 
			
		||||
    description: The priority is medium
 | 
			
		||||
    priority: medium
 | 
			
		||||
  - name: "Priority/Low"
 | 
			
		||||
    exclusive: true
 | 
			
		||||
    color: 4caf50
 | 
			
		||||
    description: The priority is low
 | 
			
		||||
    priority: low
 | 
			
		||||
@@ -237,7 +237,6 @@ internal_token_failed = Failed to generate internal token: %v
 | 
			
		||||
secret_key_failed = Failed to generate secret key: %v
 | 
			
		||||
save_config_failed = Failed to save configuration: %v
 | 
			
		||||
invalid_admin_setting = Administrator account setting is invalid: %v
 | 
			
		||||
install_success = Welcome! Thank you for choosing Gitea. Have fun and take care!
 | 
			
		||||
invalid_log_root_path = The log path is invalid: %v
 | 
			
		||||
default_keep_email_private = Hide Email Addresses by Default
 | 
			
		||||
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
 | 
			
		||||
@@ -248,6 +247,7 @@ default_enable_timetracking_popup = Enable time tracking for new repositories by
 | 
			
		||||
no_reply_address = Hidden Email Domain
 | 
			
		||||
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
 | 
			
		||||
password_algorithm = Password Hash Algorithm
 | 
			
		||||
invalid_password_algorithm = Invalid password hash algorithm
 | 
			
		||||
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. `argon2` whilst having good characteristics uses a lot of memory and may be inappropriate for small systems.
 | 
			
		||||
enable_update_checker = Enable Update Checker
 | 
			
		||||
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										256
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										256
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -31,7 +31,7 @@
 | 
			
		||||
        "less": "4.1.3",
 | 
			
		||||
        "less-loader": "11.1.0",
 | 
			
		||||
        "license-checker-webpack-plugin": "0.2.1",
 | 
			
		||||
        "mermaid": "9.3.0",
 | 
			
		||||
        "mermaid": "10.0.2",
 | 
			
		||||
        "mini-css-extract-plugin": "2.7.2",
 | 
			
		||||
        "monaco-editor": "0.34.1",
 | 
			
		||||
        "monaco-editor-webpack-plugin": "7.0.1",
 | 
			
		||||
@@ -2680,6 +2680,14 @@
 | 
			
		||||
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cose-base": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "layout-base": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cosmiconfig": {
 | 
			
		||||
      "version": "8.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
 | 
			
		||||
@@ -2888,6 +2896,53 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cytoscape": {
 | 
			
		||||
      "version": "3.23.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
 | 
			
		||||
      "integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "heap": "^0.2.6",
 | 
			
		||||
        "lodash": "^4.17.21"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cytoscape-cose-bilkent": {
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cose-base": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "cytoscape": "^3.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cytoscape-fcose": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cose-base": "^2.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "cytoscape": "^3.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cytoscape-fcose/node_modules/cose-base": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "layout-base": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cytoscape-fcose/node_modules/layout-base": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3": {
 | 
			
		||||
      "version": "7.8.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.2.tgz",
 | 
			
		||||
@@ -3267,11 +3322,11 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dagre-d3-es": {
 | 
			
		||||
      "version": "7.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==",
 | 
			
		||||
      "version": "7.0.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz",
 | 
			
		||||
      "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3": "^7.7.0",
 | 
			
		||||
        "d3": "^7.8.2",
 | 
			
		||||
        "lodash-es": "^4.17.21"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -3298,6 +3353,11 @@
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dayjs": {
 | 
			
		||||
      "version": "1.11.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
 | 
			
		||||
      "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/debug": {
 | 
			
		||||
      "version": "4.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
			
		||||
@@ -3632,9 +3692,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dompurify": {
 | 
			
		||||
      "version": "2.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
 | 
			
		||||
      "version": "2.4.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
 | 
			
		||||
      "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/domutils": {
 | 
			
		||||
      "version": "3.0.1",
 | 
			
		||||
@@ -3681,6 +3741,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
 | 
			
		||||
      "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/elkjs": {
 | 
			
		||||
      "version": "0.8.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
 | 
			
		||||
      "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/emoji-regex": {
 | 
			
		||||
      "version": "8.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 | 
			
		||||
@@ -5043,6 +5108,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/heap": {
 | 
			
		||||
      "version": "0.2.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
 | 
			
		||||
      "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/hosted-git-info": {
 | 
			
		||||
      "version": "2.8.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
 | 
			
		||||
@@ -5877,6 +5947,11 @@
 | 
			
		||||
      "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/layout-base": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/less": {
 | 
			
		||||
      "version": "4.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
 | 
			
		||||
@@ -6049,8 +6124,7 @@
 | 
			
		||||
    "node_modules/lodash": {
 | 
			
		||||
      "version": "4.17.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 | 
			
		||||
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/lodash-es": {
 | 
			
		||||
      "version": "4.17.21",
 | 
			
		||||
@@ -6393,20 +6467,26 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mermaid": {
 | 
			
		||||
      "version": "9.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==",
 | 
			
		||||
      "version": "10.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-slwoB9WdNUT+/W9VhxLYRLZ0Ey12fIE+cAZjm3FmHTD+0F1uoJETfsNbVS1POnvQZhFYzfT6/z6hJZXgecqVBA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@braintree/sanitize-url": "^6.0.0",
 | 
			
		||||
        "d3": "^7.0.0",
 | 
			
		||||
        "dagre-d3-es": "7.0.6",
 | 
			
		||||
        "dompurify": "2.4.1",
 | 
			
		||||
        "cytoscape": "^3.23.0",
 | 
			
		||||
        "cytoscape-cose-bilkent": "^4.1.0",
 | 
			
		||||
        "cytoscape-fcose": "^2.1.0",
 | 
			
		||||
        "d3": "^7.4.0",
 | 
			
		||||
        "dagre-d3-es": "7.0.9",
 | 
			
		||||
        "dayjs": "^1.11.7",
 | 
			
		||||
        "dompurify": "2.4.3",
 | 
			
		||||
        "elkjs": "^0.8.2",
 | 
			
		||||
        "khroma": "^2.0.0",
 | 
			
		||||
        "lodash-es": "^4.17.21",
 | 
			
		||||
        "moment-mini": "^2.24.0",
 | 
			
		||||
        "non-layered-tidy-tree-layout": "^2.0.2",
 | 
			
		||||
        "stylis": "^4.1.2",
 | 
			
		||||
        "uuid": "^9.0.0"
 | 
			
		||||
        "ts-dedent": "^2.2.0",
 | 
			
		||||
        "uuid": "^9.0.0",
 | 
			
		||||
        "web-worker": "^1.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/micromatch": {
 | 
			
		||||
@@ -6531,11 +6611,6 @@
 | 
			
		||||
      "integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/moment-mini": {
 | 
			
		||||
      "version": "2.29.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz",
 | 
			
		||||
      "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/monaco-editor": {
 | 
			
		||||
      "version": "0.34.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
 | 
			
		||||
@@ -8807,6 +8882,14 @@
 | 
			
		||||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ts-dedent": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tsconfig-paths": {
 | 
			
		||||
      "version": "3.14.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
 | 
			
		||||
@@ -9315,6 +9398,11 @@
 | 
			
		||||
        "node": ">=10.13.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/web-worker": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/webidl-conversions": {
 | 
			
		||||
      "version": "7.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
 | 
			
		||||
@@ -11774,6 +11862,14 @@
 | 
			
		||||
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "cose-base": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "layout-base": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "cosmiconfig": {
 | 
			
		||||
      "version": "8.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.0.0.tgz",
 | 
			
		||||
@@ -11937,6 +12033,46 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
 | 
			
		||||
    },
 | 
			
		||||
    "cytoscape": {
 | 
			
		||||
      "version": "3.23.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.23.0.tgz",
 | 
			
		||||
      "integrity": "sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "heap": "^0.2.6",
 | 
			
		||||
        "lodash": "^4.17.21"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "cytoscape-cose-bilkent": {
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "cose-base": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "cytoscape-fcose": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "cose-base": "^2.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cose-base": {
 | 
			
		||||
          "version": "2.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "layout-base": "^2.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "layout-base": {
 | 
			
		||||
          "version": "2.0.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
 | 
			
		||||
          "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "d3": {
 | 
			
		||||
      "version": "7.8.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.2.tgz",
 | 
			
		||||
@@ -12208,11 +12344,11 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "dagre-d3-es": {
 | 
			
		||||
      "version": "7.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==",
 | 
			
		||||
      "version": "7.0.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz",
 | 
			
		||||
      "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "d3": "^7.7.0",
 | 
			
		||||
        "d3": "^7.8.2",
 | 
			
		||||
        "lodash-es": "^4.17.21"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
@@ -12233,6 +12369,11 @@
 | 
			
		||||
        "whatwg-url": "^11.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "dayjs": {
 | 
			
		||||
      "version": "1.11.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz",
 | 
			
		||||
      "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "debug": {
 | 
			
		||||
      "version": "4.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
 | 
			
		||||
@@ -12472,9 +12613,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "dompurify": {
 | 
			
		||||
      "version": "2.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA=="
 | 
			
		||||
      "version": "2.4.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
 | 
			
		||||
      "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "domutils": {
 | 
			
		||||
      "version": "3.0.1",
 | 
			
		||||
@@ -12518,6 +12659,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
 | 
			
		||||
      "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
 | 
			
		||||
    },
 | 
			
		||||
    "elkjs": {
 | 
			
		||||
      "version": "0.8.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.8.2.tgz",
 | 
			
		||||
      "integrity": "sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "emoji-regex": {
 | 
			
		||||
      "version": "8.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 | 
			
		||||
@@ -13548,6 +13694,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
 | 
			
		||||
    },
 | 
			
		||||
    "heap": {
 | 
			
		||||
      "version": "0.2.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
 | 
			
		||||
      "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
 | 
			
		||||
    },
 | 
			
		||||
    "hosted-git-info": {
 | 
			
		||||
      "version": "2.8.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
 | 
			
		||||
@@ -14129,6 +14280,11 @@
 | 
			
		||||
      "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "layout-base": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="
 | 
			
		||||
    },
 | 
			
		||||
    "less": {
 | 
			
		||||
      "version": "4.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz",
 | 
			
		||||
@@ -14251,8 +14407,7 @@
 | 
			
		||||
    "lodash": {
 | 
			
		||||
      "version": "4.17.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
 | 
			
		||||
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
 | 
			
		||||
    },
 | 
			
		||||
    "lodash-es": {
 | 
			
		||||
      "version": "4.17.21",
 | 
			
		||||
@@ -14531,20 +14686,26 @@
 | 
			
		||||
      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
 | 
			
		||||
    },
 | 
			
		||||
    "mermaid": {
 | 
			
		||||
      "version": "9.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==",
 | 
			
		||||
      "version": "10.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-slwoB9WdNUT+/W9VhxLYRLZ0Ey12fIE+cAZjm3FmHTD+0F1uoJETfsNbVS1POnvQZhFYzfT6/z6hJZXgecqVBA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@braintree/sanitize-url": "^6.0.0",
 | 
			
		||||
        "d3": "^7.0.0",
 | 
			
		||||
        "dagre-d3-es": "7.0.6",
 | 
			
		||||
        "dompurify": "2.4.1",
 | 
			
		||||
        "cytoscape": "^3.23.0",
 | 
			
		||||
        "cytoscape-cose-bilkent": "^4.1.0",
 | 
			
		||||
        "cytoscape-fcose": "^2.1.0",
 | 
			
		||||
        "d3": "^7.4.0",
 | 
			
		||||
        "dagre-d3-es": "7.0.9",
 | 
			
		||||
        "dayjs": "^1.11.7",
 | 
			
		||||
        "dompurify": "2.4.3",
 | 
			
		||||
        "elkjs": "^0.8.2",
 | 
			
		||||
        "khroma": "^2.0.0",
 | 
			
		||||
        "lodash-es": "^4.17.21",
 | 
			
		||||
        "moment-mini": "^2.24.0",
 | 
			
		||||
        "non-layered-tidy-tree-layout": "^2.0.2",
 | 
			
		||||
        "stylis": "^4.1.2",
 | 
			
		||||
        "uuid": "^9.0.0"
 | 
			
		||||
        "ts-dedent": "^2.2.0",
 | 
			
		||||
        "uuid": "^9.0.0",
 | 
			
		||||
        "web-worker": "^1.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "micromatch": {
 | 
			
		||||
@@ -14634,11 +14795,6 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "moment-mini": {
 | 
			
		||||
      "version": "2.29.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/moment-mini/-/moment-mini-2.29.4.tgz",
 | 
			
		||||
      "integrity": "sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg=="
 | 
			
		||||
    },
 | 
			
		||||
    "monaco-editor": {
 | 
			
		||||
      "version": "0.34.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.34.1.tgz",
 | 
			
		||||
@@ -16340,6 +16496,11 @@
 | 
			
		||||
      "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "ts-dedent": {
 | 
			
		||||
      "version": "2.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "tsconfig-paths": {
 | 
			
		||||
      "version": "3.14.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
 | 
			
		||||
@@ -16674,6 +16835,11 @@
 | 
			
		||||
        "graceful-fs": "^4.1.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "web-worker": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA=="
 | 
			
		||||
    },
 | 
			
		||||
    "webidl-conversions": {
 | 
			
		||||
      "version": "7.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
    "less": "4.1.3",
 | 
			
		||||
    "less-loader": "11.1.0",
 | 
			
		||||
    "license-checker-webpack-plugin": "0.2.1",
 | 
			
		||||
    "mermaid": "9.3.0",
 | 
			
		||||
    "mermaid": "10.0.2",
 | 
			
		||||
    "mini-css-extract-plugin": "2.7.2",
 | 
			
		||||
    "monaco-editor": "0.34.1",
 | 
			
		||||
    "monaco-editor-webpack-plugin": "7.0.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/actions"
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	actions_service "code.gitea.io/gitea/services/actions"
 | 
			
		||||
 | 
			
		||||
	runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
 | 
			
		||||
@@ -55,9 +56,10 @@ func (s *Service) Register(
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create new runner
 | 
			
		||||
	name, _ := util.SplitStringAtByteN(req.Msg.Name, 255)
 | 
			
		||||
	runner := &actions_model.ActionRunner{
 | 
			
		||||
		UUID:         gouuid.New().String(),
 | 
			
		||||
		Name:         req.Msg.Name,
 | 
			
		||||
		Name:         name,
 | 
			
		||||
		OwnerID:      runnerToken.OwnerID,
 | 
			
		||||
		RepoID:       runnerToken.RepoID,
 | 
			
		||||
		AgentLabels:  req.Msg.AgentLabels,
 | 
			
		||||
@@ -148,7 +150,7 @@ func (s *Service) UpdateTask(
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := actions_service.CreateCommitStatus(ctx, task.Job); err != nil {
 | 
			
		||||
		log.Error("Update commit status failed: %v", err)
 | 
			
		||||
		log.Error("Update commit status for job %v failed: %v", task.Job.ID, err)
 | 
			
		||||
		// go on
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,13 @@
 | 
			
		||||
package org
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
			
		||||
@@ -84,13 +84,12 @@ func CreateLabel(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateLabelOption)
 | 
			
		||||
	form.Color = strings.Trim(form.Color, " ")
 | 
			
		||||
	if len(form.Color) == 6 {
 | 
			
		||||
		form.Color = "#" + form.Color
 | 
			
		||||
	}
 | 
			
		||||
	if !issues_model.LabelColorPattern.MatchString(form.Color) {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
 | 
			
		||||
	color, err := label.NormalizeColor(form.Color)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "Color", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	form.Color = color
 | 
			
		||||
 | 
			
		||||
	label := &issues_model.Label{
 | 
			
		||||
		Name:        form.Name,
 | 
			
		||||
@@ -183,7 +182,7 @@ func EditLabel(ctx *context.APIContext) {
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditLabelOption)
 | 
			
		||||
	label, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
 | 
			
		||||
	l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if issues_model.IsErrOrgLabelNotExist(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
@@ -194,30 +193,28 @@ func EditLabel(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.Name != nil {
 | 
			
		||||
		label.Name = *form.Name
 | 
			
		||||
		l.Name = *form.Name
 | 
			
		||||
	}
 | 
			
		||||
	if form.Exclusive != nil {
 | 
			
		||||
		label.Exclusive = *form.Exclusive
 | 
			
		||||
		l.Exclusive = *form.Exclusive
 | 
			
		||||
	}
 | 
			
		||||
	if form.Color != nil {
 | 
			
		||||
		label.Color = strings.Trim(*form.Color, " ")
 | 
			
		||||
		if len(label.Color) == 6 {
 | 
			
		||||
			label.Color = "#" + label.Color
 | 
			
		||||
		}
 | 
			
		||||
		if !issues_model.LabelColorPattern.MatchString(label.Color) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
 | 
			
		||||
		color, err := label.NormalizeColor(*form.Color)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "Color", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		l.Color = color
 | 
			
		||||
	}
 | 
			
		||||
	if form.Description != nil {
 | 
			
		||||
		label.Description = *form.Description
 | 
			
		||||
		l.Description = *form.Description
 | 
			
		||||
	}
 | 
			
		||||
	if err := issues_model.UpdateLabel(label); err != nil {
 | 
			
		||||
	if err := issues_model.UpdateLabel(l); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser()))
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(l, nil, ctx.Org.Organization.AsUser()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteLabel delete a label for an organization
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,12 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
			
		||||
@@ -93,14 +92,14 @@ func GetLabel(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/Label"
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		label *issues_model.Label
 | 
			
		||||
		err   error
 | 
			
		||||
		l   *issues_model.Label
 | 
			
		||||
		err error
 | 
			
		||||
	)
 | 
			
		||||
	strID := ctx.Params(":id")
 | 
			
		||||
	if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
 | 
			
		||||
		label, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
 | 
			
		||||
		l, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID)
 | 
			
		||||
	} else {
 | 
			
		||||
		label, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
 | 
			
		||||
		l, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if issues_model.IsErrRepoLabelNotExist(err) {
 | 
			
		||||
@@ -111,7 +110,7 @@ func GetLabel(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateLabel create a label for a repository
 | 
			
		||||
@@ -145,28 +144,27 @@ func CreateLabel(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateLabelOption)
 | 
			
		||||
	form.Color = strings.Trim(form.Color, " ")
 | 
			
		||||
	if len(form.Color) == 6 {
 | 
			
		||||
		form.Color = "#" + form.Color
 | 
			
		||||
	}
 | 
			
		||||
	if !issues_model.LabelColorPattern.MatchString(form.Color) {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
 | 
			
		||||
 | 
			
		||||
	color, err := label.NormalizeColor(form.Color)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	form.Color = color
 | 
			
		||||
 | 
			
		||||
	label := &issues_model.Label{
 | 
			
		||||
	l := &issues_model.Label{
 | 
			
		||||
		Name:        form.Name,
 | 
			
		||||
		Exclusive:   form.Exclusive,
 | 
			
		||||
		Color:       form.Color,
 | 
			
		||||
		RepoID:      ctx.Repo.Repository.ID,
 | 
			
		||||
		Description: form.Description,
 | 
			
		||||
	}
 | 
			
		||||
	if err := issues_model.NewLabel(ctx, label); err != nil {
 | 
			
		||||
	if err := issues_model.NewLabel(ctx, l); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "NewLabel", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil))
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToLabel(l, ctx.Repo.Repository, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditLabel modify a label for a repository
 | 
			
		||||
@@ -206,7 +204,7 @@ func EditLabel(ctx *context.APIContext) {
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditLabelOption)
 | 
			
		||||
	label, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
 | 
			
		||||
	l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if issues_model.IsErrRepoLabelNotExist(err) {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
@@ -217,30 +215,28 @@ func EditLabel(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.Name != nil {
 | 
			
		||||
		label.Name = *form.Name
 | 
			
		||||
		l.Name = *form.Name
 | 
			
		||||
	}
 | 
			
		||||
	if form.Exclusive != nil {
 | 
			
		||||
		label.Exclusive = *form.Exclusive
 | 
			
		||||
		l.Exclusive = *form.Exclusive
 | 
			
		||||
	}
 | 
			
		||||
	if form.Color != nil {
 | 
			
		||||
		label.Color = strings.Trim(*form.Color, " ")
 | 
			
		||||
		if len(label.Color) == 6 {
 | 
			
		||||
			label.Color = "#" + label.Color
 | 
			
		||||
		}
 | 
			
		||||
		if !issues_model.LabelColorPattern.MatchString(label.Color) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
 | 
			
		||||
		color, err := label.NormalizeColor(*form.Color)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		l.Color = color
 | 
			
		||||
	}
 | 
			
		||||
	if form.Description != nil {
 | 
			
		||||
		label.Description = *form.Description
 | 
			
		||||
		l.Description = *form.Description
 | 
			
		||||
	}
 | 
			
		||||
	if err := issues_model.UpdateLabel(label); err != nil {
 | 
			
		||||
	if err := issues_model.UpdateLabel(l); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil))
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteLabel delete a label for a repository
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -248,7 +249,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
 | 
			
		||||
			ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
 | 
			
		||||
		} else if db.IsErrNameReserved(err) ||
 | 
			
		||||
			db.IsErrNamePatternNotAllowed(err) ||
 | 
			
		||||
			repo_module.IsErrIssueLabelTemplateLoad(err) {
 | 
			
		||||
			label.IsErrTemplateLoad(err) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "CreateRepository", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/routing"
 | 
			
		||||
 | 
			
		||||
	"github.com/chi-middleware/proxy"
 | 
			
		||||
	"github.com/go-chi/chi/v5/middleware"
 | 
			
		||||
	chi "github.com/go-chi/chi/v5"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Middlewares returns common middlewares
 | 
			
		||||
@@ -48,7 +48,8 @@ func Middlewares() []func(http.Handler) http.Handler {
 | 
			
		||||
		handlers = append(handlers, proxy.ForwardedHeaders(opt))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handlers = append(handlers, middleware.StripSlashes)
 | 
			
		||||
	// Strip slashes.
 | 
			
		||||
	handlers = append(handlers, stripSlashesMiddleware)
 | 
			
		||||
 | 
			
		||||
	if !setting.Log.DisableRouterLog {
 | 
			
		||||
		handlers = append(handlers, routing.NewLoggerHandler())
 | 
			
		||||
@@ -81,3 +82,33 @@ func Middlewares() []func(http.Handler) http.Handler {
 | 
			
		||||
	})
 | 
			
		||||
	return handlers
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func stripSlashesMiddleware(next http.Handler) http.Handler {
 | 
			
		||||
	return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		var urlPath string
 | 
			
		||||
		rctx := chi.RouteContext(req.Context())
 | 
			
		||||
		if rctx != nil && rctx.RoutePath != "" {
 | 
			
		||||
			urlPath = rctx.RoutePath
 | 
			
		||||
		} else if req.URL.RawPath != "" {
 | 
			
		||||
			urlPath = req.URL.RawPath
 | 
			
		||||
		} else {
 | 
			
		||||
			urlPath = req.URL.Path
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sanitizedPath := &strings.Builder{}
 | 
			
		||||
		prevWasSlash := false
 | 
			
		||||
		for _, chr := range strings.TrimRight(urlPath, "/") {
 | 
			
		||||
			if chr != '/' || !prevWasSlash {
 | 
			
		||||
				sanitizedPath.WriteRune(chr)
 | 
			
		||||
			}
 | 
			
		||||
			prevWasSlash = chr == '/'
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if rctx == nil {
 | 
			
		||||
			req.URL.Path = sanitizedPath.String()
 | 
			
		||||
		} else {
 | 
			
		||||
			rctx.RoutePath = sanitizedPath.String()
 | 
			
		||||
		}
 | 
			
		||||
		next.ServeHTTP(resp, req)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								routers/common/middleware_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								routers/common/middleware_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
package common
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStripSlashesMiddleware(t *testing.T) {
 | 
			
		||||
	type test struct {
 | 
			
		||||
		name         string
 | 
			
		||||
		expectedPath string
 | 
			
		||||
		inputPath    string
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tests := []test{
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with multiple slashes",
 | 
			
		||||
			inputPath:    "https://github.com///go-gitea//gitea.git",
 | 
			
		||||
			expectedPath: "/go-gitea/gitea.git",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with no slashes",
 | 
			
		||||
			inputPath:    "https://github.com/go-gitea/gitea.git",
 | 
			
		||||
			expectedPath: "/go-gitea/gitea.git",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with slashes in the middle",
 | 
			
		||||
			inputPath:    "https://git.data.coop//halfd/new-website.git",
 | 
			
		||||
			expectedPath: "/halfd/new-website.git",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with slashes in the middle",
 | 
			
		||||
			inputPath:    "https://git.data.coop//halfd/new-website.git",
 | 
			
		||||
			expectedPath: "/halfd/new-website.git",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with slashes in the end",
 | 
			
		||||
			inputPath:    "/user2//repo1/",
 | 
			
		||||
			expectedPath: "/user2/repo1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with slashes and query params",
 | 
			
		||||
			inputPath:    "/repo//migrate?service_type=3",
 | 
			
		||||
			expectedPath: "/repo/migrate",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:         "path with encoded slash",
 | 
			
		||||
			inputPath:    "/user2/%2F%2Frepo1",
 | 
			
		||||
			expectedPath: "/user2/%2F%2Frepo1",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
			assert.Equal(t, tt.expectedPath, r.URL.Path)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// pass the test middleware to validate the changes
 | 
			
		||||
		handlerToTest := stripSlashesMiddleware(testMiddleware)
 | 
			
		||||
		// create a mock request to use
 | 
			
		||||
		req := httptest.NewRequest("GET", tt.inputPath, nil)
 | 
			
		||||
		// call the handler using a mock response recorder
 | 
			
		||||
		handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -150,7 +150,7 @@ func GlobalInitInstalled(ctx context.Context) {
 | 
			
		||||
	mustInit(system.Init)
 | 
			
		||||
	mustInit(oauth2.Init)
 | 
			
		||||
 | 
			
		||||
	mustInit(models.Init)
 | 
			
		||||
	mustInitCtx(ctx, models.Init)
 | 
			
		||||
	mustInit(repo_service.Init)
 | 
			
		||||
 | 
			
		||||
	// Booting long running goroutines.
 | 
			
		||||
 
 | 
			
		||||
@@ -59,11 +59,6 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
	dbTypeNames := getSupportedDbTypeNames()
 | 
			
		||||
	return func(next http.Handler) http.Handler {
 | 
			
		||||
		return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 | 
			
		||||
			if setting.InstallLock {
 | 
			
		||||
				resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
 | 
			
		||||
				_ = rnd.HTML(resp, http.StatusOK, string(tplPostInstall), nil)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			locale := middleware.Locale(resp, req)
 | 
			
		||||
			startTime := time.Now()
 | 
			
		||||
			ctx := context.Context{
 | 
			
		||||
@@ -93,6 +88,11 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
 | 
			
		||||
// Install render installation page
 | 
			
		||||
func Install(ctx *context.Context) {
 | 
			
		||||
	if setting.InstallLock {
 | 
			
		||||
		InstallDone(ctx)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form := forms.InstallForm{}
 | 
			
		||||
 | 
			
		||||
	// Database settings
 | 
			
		||||
@@ -162,7 +162,7 @@ func Install(ctx *context.Context) {
 | 
			
		||||
	form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
 | 
			
		||||
	form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
 | 
			
		||||
	form.NoReplyAddress = setting.Service.NoReplyAddress
 | 
			
		||||
	form.PasswordAlgorithm = setting.PasswordHashAlgo
 | 
			
		||||
	form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
 | 
			
		||||
 | 
			
		||||
	middleware.AssignForm(form, ctx.Data)
 | 
			
		||||
	ctx.HTML(http.StatusOK, tplInstall)
 | 
			
		||||
@@ -234,6 +234,11 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
 | 
			
		||||
 | 
			
		||||
// SubmitInstall response for submit install items
 | 
			
		||||
func SubmitInstall(ctx *context.Context) {
 | 
			
		||||
	if setting.InstallLock {
 | 
			
		||||
		InstallDone(ctx)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	form := *web.GetForm(ctx).(*forms.InstallForm)
 | 
			
		||||
@@ -277,7 +282,6 @@ func SubmitInstall(ctx *context.Context) {
 | 
			
		||||
	setting.Database.Charset = form.Charset
 | 
			
		||||
	setting.Database.Path = form.DbPath
 | 
			
		||||
	setting.Database.LogSQL = !setting.IsProd
 | 
			
		||||
	setting.PasswordHashAlgo = form.PasswordAlgorithm
 | 
			
		||||
 | 
			
		||||
	if !checkDatabase(ctx, &form) {
 | 
			
		||||
		return
 | 
			
		||||
@@ -499,6 +503,12 @@ func SubmitInstall(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(form.PasswordAlgorithm) > 0 {
 | 
			
		||||
		var algorithm *hash.PasswordHashAlgorithm
 | 
			
		||||
		setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm)
 | 
			
		||||
		if algorithm == nil {
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -571,18 +581,26 @@ func SubmitInstall(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Info("First-time run install finished!")
 | 
			
		||||
	InstallDone(ctx)
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("install.install_success"))
 | 
			
		||||
 | 
			
		||||
	ctx.RespHeader().Add("Refresh", "1; url="+setting.AppURL+"user/login")
 | 
			
		||||
	ctx.HTML(http.StatusOK, tplPostInstall)
 | 
			
		||||
 | 
			
		||||
	// Now get the http.Server from this request and shut it down
 | 
			
		||||
	// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
 | 
			
		||||
	srv := ctx.Value(http.ServerContextKey).(*http.Server)
 | 
			
		||||
	go func() {
 | 
			
		||||
		// Sleep for a while to make sure the user's browser has loaded the post-install page and its assets (images, css, js)
 | 
			
		||||
		// What if this duration is not long enough? That's impossible -- if the user can't load the simple page in time, how could they install or use Gitea in the future ....
 | 
			
		||||
		time.Sleep(3 * time.Second)
 | 
			
		||||
 | 
			
		||||
		// Now get the http.Server from this request and shut it down
 | 
			
		||||
		// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
 | 
			
		||||
		srv := ctx.Value(http.ServerContextKey).(*http.Server)
 | 
			
		||||
		if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil {
 | 
			
		||||
			log.Error("Unable to shutdown the install server! Error: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// After the HTTP server for "install" shuts down, the `runWeb()` will continue to run the "normal" server
 | 
			
		||||
	}()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InstallDone shows the "post-install" page, makes it easier to develop the page.
 | 
			
		||||
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
 | 
			
		||||
func InstallDone(ctx *context.Context) { //nolint
 | 
			
		||||
	ctx.HTML(http.StatusOK, tplPostInstall)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ package install
 | 
			
		||||
import (
 | 
			
		||||
	goctx "context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
@@ -37,7 +38,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
 | 
			
		||||
				// Why we need this? The first recover will try to render a beautiful
 | 
			
		||||
				// error page for user, but the process can still panic again, then
 | 
			
		||||
				// we have to just recover twice and send a simple error page that
 | 
			
		||||
				// should not panic any more.
 | 
			
		||||
				// should not panic anymore.
 | 
			
		||||
				defer func() {
 | 
			
		||||
					if err := recover(); err != nil {
 | 
			
		||||
						combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
 | 
			
		||||
@@ -107,8 +108,9 @@ func Routes(ctx goctx.Context) *web.Route {
 | 
			
		||||
 | 
			
		||||
	r.Use(installRecovery(ctx))
 | 
			
		||||
	r.Use(Init(ctx))
 | 
			
		||||
	r.Get("/", Install)
 | 
			
		||||
	r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
 | 
			
		||||
	r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
 | 
			
		||||
	r.Get("/post-install", InstallDone)
 | 
			
		||||
	r.Get("/api/healthz", healthcheck.Check)
 | 
			
		||||
 | 
			
		||||
	r.NotFound(web.Wrap(installNotFound))
 | 
			
		||||
@@ -116,5 +118,10 @@ func Routes(ctx goctx.Context) *web.Route {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func installNotFound(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
	http.Redirect(w, req, setting.AppURL, http.StatusFound)
 | 
			
		||||
	w.Header().Add("Content-Type", "text/html; charset=utf-8")
 | 
			
		||||
	w.Header().Add("Refresh", fmt.Sprintf("1; url=%s", setting.AppSubURL+"/"))
 | 
			
		||||
	// do not use 30x status, because the "post-install" page needs to use 404/200 to detect if Gitea has been installed.
 | 
			
		||||
	// the fetch API could follow 30x requests to the page with 200 status.
 | 
			
		||||
	w.WriteHeader(http.StatusNotFound)
 | 
			
		||||
	_, _ = fmt.Fprintf(w, `Not Found. <a href="%s">Go to default page</a>.`, html.EscapeString(setting.AppSubURL+"/"))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,7 @@ func Config(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsAdmin"] = true
 | 
			
		||||
	ctx.Data["PageIsAdminConfig"] = true
 | 
			
		||||
 | 
			
		||||
	systemSettings, err := system_model.GetAllSettings()
 | 
			
		||||
	systemSettings, err := system_model.GetAllSettings(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("system_model.GetAllSettings", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,10 @@ func Repos(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["PageIsAdminRepositories"] = true
 | 
			
		||||
 | 
			
		||||
	explore.RenderRepoSearch(ctx, &explore.RepoSearchOptions{
 | 
			
		||||
		Private:  true,
 | 
			
		||||
		PageSize: setting.UI.Admin.RepoPagingNum,
 | 
			
		||||
		TplName:  tplRepos,
 | 
			
		||||
		Private:          true,
 | 
			
		||||
		PageSize:         setting.UI.Admin.RepoPagingNum,
 | 
			
		||||
		TplName:          tplRepos,
 | 
			
		||||
		OnlyShowRelevant: false,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,14 +23,16 @@ const (
 | 
			
		||||
 | 
			
		||||
// RepoSearchOptions when calling search repositories
 | 
			
		||||
type RepoSearchOptions struct {
 | 
			
		||||
	OwnerID    int64
 | 
			
		||||
	Private    bool
 | 
			
		||||
	Restricted bool
 | 
			
		||||
	PageSize   int
 | 
			
		||||
	TplName    base.TplName
 | 
			
		||||
	OwnerID          int64
 | 
			
		||||
	Private          bool
 | 
			
		||||
	Restricted       bool
 | 
			
		||||
	PageSize         int
 | 
			
		||||
	OnlyShowRelevant bool
 | 
			
		||||
	TplName          base.TplName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RenderRepoSearch render repositories search page
 | 
			
		||||
// This function is also used to render the Admin Repository Management page.
 | 
			
		||||
func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 | 
			
		||||
	// Sitemap index for sitemap paths
 | 
			
		||||
	page := int(ctx.ParamsInt64("idx"))
 | 
			
		||||
@@ -48,11 +50,10 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		repos            []*repo_model.Repository
 | 
			
		||||
		count            int64
 | 
			
		||||
		err              error
 | 
			
		||||
		orderBy          db.SearchOrderBy
 | 
			
		||||
		onlyShowRelevant bool
 | 
			
		||||
		repos   []*repo_model.Repository
 | 
			
		||||
		count   int64
 | 
			
		||||
		err     error
 | 
			
		||||
		orderBy db.SearchOrderBy
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["SortType"] = ctx.FormString("sort")
 | 
			
		||||
@@ -84,11 +85,9 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 | 
			
		||||
		orderBy = db.SearchOrderByRecentUpdated
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onlyShowRelevant = !ctx.FormBool(relevantReposOnlyParam)
 | 
			
		||||
 | 
			
		||||
	keyword := ctx.FormTrim("q")
 | 
			
		||||
 | 
			
		||||
	ctx.Data["OnlyShowRelevant"] = onlyShowRelevant
 | 
			
		||||
	ctx.Data["OnlyShowRelevant"] = opts.OnlyShowRelevant
 | 
			
		||||
 | 
			
		||||
	topicOnly := ctx.FormBool("topic")
 | 
			
		||||
	ctx.Data["TopicOnly"] = topicOnly
 | 
			
		||||
@@ -111,7 +110,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
 | 
			
		||||
		TopicOnly:          topicOnly,
 | 
			
		||||
		Language:           language,
 | 
			
		||||
		IncludeDescription: setting.UI.SearchRepoDescription,
 | 
			
		||||
		OnlyShowRelevant:   onlyShowRelevant,
 | 
			
		||||
		OnlyShowRelevant:   opts.OnlyShowRelevant,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("SearchRepository", err)
 | 
			
		||||
@@ -158,9 +157,10 @@ func Repos(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	RenderRepoSearch(ctx, &RepoSearchOptions{
 | 
			
		||||
		PageSize: setting.UI.ExplorePagingNum,
 | 
			
		||||
		OwnerID:  ownerID,
 | 
			
		||||
		Private:  ctx.Doer != nil,
 | 
			
		||||
		TplName:  tplExploreRepos,
 | 
			
		||||
		PageSize:         setting.UI.ExplorePagingNum,
 | 
			
		||||
		OwnerID:          ownerID,
 | 
			
		||||
		Private:          ctx.Doer != nil,
 | 
			
		||||
		TplName:          tplExploreRepos,
 | 
			
		||||
		OnlyShowRelevant: !ctx.FormBool(relevantReposOnlyParam),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
@@ -103,8 +104,8 @@ func InitializeLabels(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_module.InitializeLabels(ctx, ctx.Org.Organization.ID, form.TemplateName, true); err != nil {
 | 
			
		||||
		if repo_module.IsErrIssueLabelTemplateLoad(err) {
 | 
			
		||||
			originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
 | 
			
		||||
		if label.IsErrTemplateLoad(err) {
 | 
			
		||||
			originalErr := err.(label.ErrTemplateLoad).OriginalError
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
 | 
			
		||||
			ctx.Redirect(ctx.Org.OrgLink + "/settings/labels")
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	"code.gitea.io/gitea/modules/actions"
 | 
			
		||||
	context_module "code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
@@ -207,15 +208,18 @@ func Rerun(ctx *context_module.Context) {
 | 
			
		||||
	job.Stopped = 0
 | 
			
		||||
 | 
			
		||||
	if err := db.WithTx(ctx, func(ctx context.Context) error {
 | 
			
		||||
		if _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return actions_service.CreateCommitStatus(ctx, job)
 | 
			
		||||
		_, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, "task_id", "status", "started", "stopped")
 | 
			
		||||
		return err
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
		log.Error("Update commit status for job %v failed: %v", job.ID, err)
 | 
			
		||||
		// go on
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, struct{}{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -248,9 +252,6 @@ func Cancel(ctx *context_module.Context) {
 | 
			
		||||
			if err := actions_model.StopTask(ctx, job.TaskID, actions_model.StatusCancelled); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
@@ -258,6 +259,13 @@ func Cancel(ctx *context_module.Context) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, job := range jobs {
 | 
			
		||||
		if err := actions_service.CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
			log.Error("Update commit status for job %v failed: %v", job.ID, err)
 | 
			
		||||
			// go on
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, struct{}{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -228,7 +228,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !p.CanAccess(accessMode, unitType) {
 | 
			
		||||
					ctx.PlainText(http.StatusForbidden, "User permission denied")
 | 
			
		||||
					ctx.PlainText(http.StatusNotFound, "Repository not found")
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/organization"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
@@ -41,8 +42,8 @@ func InitializeLabels(ctx *context.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_module.InitializeLabels(ctx, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil {
 | 
			
		||||
		if repo_module.IsErrIssueLabelTemplateLoad(err) {
 | 
			
		||||
			originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError
 | 
			
		||||
		if label.IsErrTemplateLoad(err) {
 | 
			
		||||
			originalErr := err.(label.ErrTemplateLoad).OriginalError
 | 
			
		||||
			ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
 | 
			
		||||
			ctx.Redirect(ctx.Repo.RepoLink + "/labels")
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ const (
 | 
			
		||||
func NewDiffPatch(ctx *context.Context) {
 | 
			
		||||
	canCommit := renderCommitRights(ctx)
 | 
			
		||||
 | 
			
		||||
	ctx.Data["TreePath"] = ""
 | 
			
		||||
	ctx.Data["PageIsPatch"] = true
 | 
			
		||||
 | 
			
		||||
	ctx.Data["commit_summary"] = ""
 | 
			
		||||
	ctx.Data["commit_message"] = ""
 | 
			
		||||
@@ -51,7 +51,7 @@ func NewDiffPatchPost(ctx *context.Context) {
 | 
			
		||||
	if form.CommitChoice == frmCommitChoiceNewBranch {
 | 
			
		||||
		branchName = form.NewBranchName
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Data["TreePath"] = ""
 | 
			
		||||
	ctx.Data["PageIsPatch"] = true
 | 
			
		||||
	ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
 | 
			
		||||
	ctx.Data["FileContent"] = form.Content
 | 
			
		||||
	ctx.Data["commit_summary"] = form.CommitSummary
 | 
			
		||||
@@ -86,13 +86,14 @@ func NewDiffPatchPost(ctx *context.Context) {
 | 
			
		||||
		message += "\n\n" + form.CommitMessage
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
 | 
			
		||||
	fileResponse, err := files.ApplyDiffPatch(ctx, ctx.Repo.Repository, ctx.Doer, &files.ApplyDiffPatchOptions{
 | 
			
		||||
		LastCommitID: form.LastCommit,
 | 
			
		||||
		OldBranch:    ctx.Repo.BranchName,
 | 
			
		||||
		NewBranch:    branchName,
 | 
			
		||||
		Message:      message,
 | 
			
		||||
		Content:      strings.ReplaceAll(form.Content, "\r", ""),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if models.IsErrBranchAlreadyExists(err) {
 | 
			
		||||
			// User has specified a branch that already exists
 | 
			
		||||
			branchErr := err.(models.ErrBranchAlreadyExists)
 | 
			
		||||
@@ -111,6 +112,6 @@ func NewDiffPatchPost(ctx *context.Context) {
 | 
			
		||||
	if form.CommitChoice == frmCommitChoiceNewBranch && ctx.Repo.Repository.UnitEnabled(ctx, unit.TypePullRequests) {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ctx.Repo.BranchName) + "..." + util.PathEscapeSegments(form.NewBranchName))
 | 
			
		||||
	} else {
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
 | 
			
		||||
		ctx.Redirect(ctx.Repo.RepoLink + "/commit/" + fileResponse.Commit.SHA)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -186,7 +186,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	renderReadmeFile(ctx, readmeFile, treeLink)
 | 
			
		||||
	renderReadmeFile(ctx, readmeFile, fmt.Sprintf("%s/%s", treeLink, readmeFile.name))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// localizedExtensions prepends the provided language code with and without a
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
 | 
			
		||||
		return fmt.Errorf("find tasks: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	jobs := make([]*actions_model.ActionRunJob, 0, len(tasks))
 | 
			
		||||
	for _, task := range tasks {
 | 
			
		||||
		if err := db.WithTx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			if err := actions_model.StopTask(ctx, task.ID, actions_model.StatusFailure); err != nil {
 | 
			
		||||
@@ -51,7 +52,8 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
 | 
			
		||||
			if err := task.LoadJob(ctx); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			return CreateCommitStatus(ctx, task.Job)
 | 
			
		||||
			jobs = append(jobs, task.Job)
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			log.Warn("Cannot stop task %v: %v", task.ID, err)
 | 
			
		||||
			// go on
 | 
			
		||||
@@ -61,6 +63,14 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
 | 
			
		||||
			remove()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, job := range jobs {
 | 
			
		||||
		if err := CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
			log.Error("Update commit status for job %v failed: %v", job.ID, err)
 | 
			
		||||
			// go on
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -80,14 +90,16 @@ func CancelAbandonedJobs(ctx context.Context) error {
 | 
			
		||||
		job.Status = actions_model.StatusCancelled
 | 
			
		||||
		job.Stopped = now
 | 
			
		||||
		if err := db.WithTx(ctx, func(ctx context.Context) error {
 | 
			
		||||
			if _, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped"); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			return CreateCommitStatus(ctx, job)
 | 
			
		||||
			_, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
 | 
			
		||||
			return err
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			log.Warn("cancel abandoned job %v: %v", job.ID, err)
 | 
			
		||||
			// go on
 | 
			
		||||
		}
 | 
			
		||||
		if err := CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
			log.Error("Update commit status for job %v failed: %v", job.ID, err)
 | 
			
		||||
			// go on
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,16 @@ func CreateCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er
 | 
			
		||||
		return fmt.Errorf("GetPushEventPayload: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Since the payload comes from json data, we should check if it's broken, or it will cause panic
 | 
			
		||||
	switch {
 | 
			
		||||
	case payload.Repo == nil:
 | 
			
		||||
		return fmt.Errorf("repo is missing in event payload")
 | 
			
		||||
	case payload.Pusher == nil:
 | 
			
		||||
		return fmt.Errorf("pusher is missing in event payload")
 | 
			
		||||
	case payload.HeadCommit == nil:
 | 
			
		||||
		return fmt.Errorf("head commit is missing in event payload")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	creator, err := user_model.GetUserByID(ctx, payload.Pusher.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("GetUserByID: %w", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -180,7 +180,8 @@ func notify(ctx context.Context, input *notifyInput) error {
 | 
			
		||||
		} else {
 | 
			
		||||
			for _, job := range jobs {
 | 
			
		||||
				if err := CreateCommitStatus(ctx, job); err != nil {
 | 
			
		||||
					log.Error("CreateCommitStatus: %v", err)
 | 
			
		||||
					log.Error("Update commit status for job %v failed: %v", job.ID, err)
 | 
			
		||||
					// go on
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
 | 
			
		||||
		LastUpdateUnix: pm.LastUpdateUnix.FormatLong(),
 | 
			
		||||
		LastError:      pm.LastError,
 | 
			
		||||
		Interval:       pm.Interval.String(),
 | 
			
		||||
		SyncOnCommit:   pm.SyncOnCommit,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/label"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	base "code.gitea.io/gitea/modules/migration"
 | 
			
		||||
	repo_module "code.gitea.io/gitea/modules/repository"
 | 
			
		||||
@@ -217,18 +218,20 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err
 | 
			
		||||
// CreateLabels creates labels
 | 
			
		||||
func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
 | 
			
		||||
	lbs := make([]*issues_model.Label, 0, len(labels))
 | 
			
		||||
	for _, label := range labels {
 | 
			
		||||
		// We must validate color here:
 | 
			
		||||
		if !issues_model.LabelColorPattern.MatchString("#" + label.Color) {
 | 
			
		||||
			log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", label.Color, label.Name, g.repoOwner, g.repoName)
 | 
			
		||||
			label.Color = "ffffff"
 | 
			
		||||
	for _, l := range labels {
 | 
			
		||||
		if color, err := label.NormalizeColor(l.Color); err != nil {
 | 
			
		||||
			log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", l.Color, l.Name, g.repoOwner, g.repoName)
 | 
			
		||||
			l.Color = "#ffffff"
 | 
			
		||||
		} else {
 | 
			
		||||
			l.Color = color
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lbs = append(lbs, &issues_model.Label{
 | 
			
		||||
			RepoID:      g.repo.ID,
 | 
			
		||||
			Name:        label.Name,
 | 
			
		||||
			Description: label.Description,
 | 
			
		||||
			Color:       "#" + label.Color,
 | 
			
		||||
			Name:        l.Name,
 | 
			
		||||
			Exclusive:   l.Exclusive,
 | 
			
		||||
			Description: l.Description,
 | 
			
		||||
			Color:       l.Color,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -499,6 +499,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool {
 | 
			
		||||
			theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if newCommit, err := gitRepo.GetCommit(newCommitID); err != nil {
 | 
			
		||||
			log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err)
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID)
 | 
			
		||||
 | 
			
		||||
		notification.NotifySyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,12 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
 | 
			
		||||
	remoteRepoName := "head_repo"
 | 
			
		||||
	baseBranch := "base"
 | 
			
		||||
 | 
			
		||||
	fetchArgs := git.TrustedCmdArgs{"--no-tags"}
 | 
			
		||||
	if git.CheckGitVersionAtLeast("2.25.0") == nil {
 | 
			
		||||
		// Writing the commit graph can be slow and is not needed here
 | 
			
		||||
		fetchArgs = append(fetchArgs, "--no-write-commit-graph")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add head repo remote.
 | 
			
		||||
	addCacheRepo := func(staging, cache string) error {
 | 
			
		||||
		p := filepath.Join(staging, ".git", "objects", "info", "alternates")
 | 
			
		||||
@@ -108,7 +114,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
 | 
			
		||||
	outbuf.Reset()
 | 
			
		||||
	errbuf.Reset()
 | 
			
		||||
 | 
			
		||||
	if err := git.NewCommand(ctx, "fetch", "origin", "--no-tags").AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).
 | 
			
		||||
	if err := git.NewCommand(ctx, "fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch).
 | 
			
		||||
		Run(&git.RunOpts{
 | 
			
		||||
			Dir:    tmpBasePath,
 | 
			
		||||
			Stdout: &outbuf,
 | 
			
		||||
@@ -171,7 +177,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str
 | 
			
		||||
	} else {
 | 
			
		||||
		headBranch = pr.GetGitRefName()
 | 
			
		||||
	}
 | 
			
		||||
	if err := git.NewCommand(ctx, "fetch", "--no-tags").AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch).
 | 
			
		||||
	if err := git.NewCommand(ctx, "fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch).
 | 
			
		||||
		Run(&git.RunOpts{
 | 
			
		||||
			Dir:    tmpBasePath,
 | 
			
		||||
			Stdout: &outbuf,
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,7 @@
 | 
			
		||||
							<input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div id="ldap-group-options" class="ui segment secondary" {{if not $cfg.GroupsEnabled}}hidden{{end}}>
 | 
			
		||||
					<div id="ldap-group-options" class="ui segment secondary {{if not $cfg.GroupsEnabled}}gt-hidden{{end}}">
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<label>{{.locale.Tr "admin.auths.group_search_base"}}</label>
 | 
			
		||||
							<input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="e.g. ou=group,dc=mydomain,dc=com">
 | 
			
		||||
 
 | 
			
		||||
@@ -122,7 +122,7 @@
 | 
			
		||||
						<input name="allow_git_hook" type="checkbox" {{if .User.CanEditGitHook}}checked{{end}} {{if DisableGitHooks}}disabled{{end}}>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="inline field" {{if or (DisableImportLocal) (.DisableMigrations)}}hidden{{end}}>
 | 
			
		||||
				<div class="inline field {{if or (DisableImportLocal) (.DisableMigrations)}}gt-hidden{{end}}">
 | 
			
		||||
					<div class="ui checkbox">
 | 
			
		||||
						<label><strong>{{.locale.Tr "admin.users.allow_import_local"}}</strong></label>
 | 
			
		||||
						<input name="allow_import_local" type="checkbox" {{if .User.CanImportLocal}}checked{{end}} {{if DisableImportLocal}}disabled{{end}}>
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}hide{{end}}">
 | 
			
		||||
				<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .login_type "0-0"}}gt-hidden{{end}}">
 | 
			
		||||
					<label for="login_name">{{.locale.Tr "admin.users.auth_login_name"}}</label>
 | 
			
		||||
					<input id="login_name" name="login_name" value="{{.login_name}}">
 | 
			
		||||
				</div>
 | 
			
		||||
@@ -62,12 +62,12 @@
 | 
			
		||||
					<label for="email">{{.locale.Tr "email"}}</label>
 | 
			
		||||
					<input id="email" name="email" type="email" value="{{.email}}" required>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}hide{{end}}">
 | 
			
		||||
				<div class="required local field {{if .Err_Password}}error{{end}} {{if not (eq .login_type "0-0")}}gt-hidden{{end}}">
 | 
			
		||||
					<label for="password">{{.locale.Tr "password"}}</label>
 | 
			
		||||
					<input id="password" name="password" type="password" autocomplete="new-password" value="{{.password}}" {{if eq .login_type "0-0"}}required{{end}}>
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
				<div class="inline field local{{if ne .login_type "0-0"}} hide{{end}}">
 | 
			
		||||
				<div class="inline field local {{if ne .login_type "0-0"}}gt-hidden{{end}}">
 | 
			
		||||
					<div class="ui checkbox">
 | 
			
		||||
						<label><strong>{{.locale.Tr "auth.allow_password_change"}}</strong></label>
 | 
			
		||||
						<input name="must_change_password" type="checkbox" checked>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
	<meta charset="utf-8">
 | 
			
		||||
	<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
	<title>{{if .Title}}{{.Title | RenderEmojiPlain}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}}</title>
 | 
			
		||||
	<link rel="manifest" href="data:{{.ManifestData}}">
 | 
			
		||||
	{{if .ManifestData}}<link rel="manifest" href="data:{{.ManifestData}}">{{end}}
 | 
			
		||||
	<meta name="theme-color" content="{{ThemeColorMetaTag}}">
 | 
			
		||||
	<meta name="default-theme" content="{{DefaultTheme}}">
 | 
			
		||||
	<meta name="author" content="{{if .Repository}}{{.Owner.Name}}{{else}}{{MetaAuthor}}{{end}}">
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@
 | 
			
		||||
				</span>
 | 
			
		||||
			</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			<div class="ui icon button mobile-only" id="navbar-expand-toggle">
 | 
			
		||||
			<button class="ui icon button mobile-only" id="navbar-expand-toggle">
 | 
			
		||||
				{{svg "octicon-three-bars"}}
 | 
			
		||||
			</div>
 | 
			
		||||
			</button>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
	<title>{{.Subject}}</title>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
{{$repo_url := printf "<a href='%s'>%s</a>" (Escape .Issue.Repo.Link) (Escape .Issue.Repo.FullName)}}
 | 
			
		||||
{{$repo_url := printf "<a href='%s'>%s</a>" (Escape .Issue.Repo.HTMLURL) (Escape .Issue.Repo.FullName)}}
 | 
			
		||||
{{$link := printf "<a href='%s'>#%d</a>" (Escape .Link) .Issue.Index}}
 | 
			
		||||
<body>
 | 
			
		||||
	<p>
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,11 @@
 | 
			
		||||
	{{if eq .ActionName "push"}}
 | 
			
		||||
		<p>
 | 
			
		||||
			{{if .Comment.IsForcePush}}
 | 
			
		||||
				{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.Link .Comment.OldCommit}}
 | 
			
		||||
				{{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
 | 
			
		||||
				{{$oldShortSha := ShortSha .Comment.OldCommit}}
 | 
			
		||||
				{{$oldCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $oldCommitUrl) (Escape $oldShortSha)}}
 | 
			
		||||
 | 
			
		||||
				{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.Link .Comment.NewCommit}}
 | 
			
		||||
				{{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
 | 
			
		||||
				{{$newShortSha := ShortSha .Comment.NewCommit}}
 | 
			
		||||
				{{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $newCommitUrl) (Escape $newShortSha)}}
 | 
			
		||||
 | 
			
		||||
@@ -72,7 +72,7 @@
 | 
			
		||||
			<ul>
 | 
			
		||||
			{{range .Comment.Commits}}
 | 
			
		||||
				<li>
 | 
			
		||||
					<a href="{{$.Comment.Issue.PullRequest.BaseRepo.Link}}/commit/{{.ID}}">
 | 
			
		||||
					<a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.ID}}">
 | 
			
		||||
						{{ShortSha .ID.String}}
 | 
			
		||||
					</a>  -  {{.Summary}}
 | 
			
		||||
				</li>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@
 | 
			
		||||
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
{{$release_url := printf "<a href='%s'>%s</a>" (.Release.Link | Escape) (.Release.TagName | Escape)}}
 | 
			
		||||
{{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.Link | Escape) (.Release.Repo.FullName | Escape)}}
 | 
			
		||||
{{$release_url := printf "<a href='%s'>%s</a>" (.Release.HTMLURL | Escape) (.Release.TagName | Escape)}}
 | 
			
		||||
{{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.HTMLURL | Escape) (.Release.Repo.FullName | Escape)}}
 | 
			
		||||
<body>
 | 
			
		||||
	<p>
 | 
			
		||||
		{{.locale.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url | Str2html}}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ git-fetch-with-cli = true</code></pre></div>
 | 
			
		||||
	{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Readme}}
 | 
			
		||||
		<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
 | 
			
		||||
		{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}}
 | 
			
		||||
		{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml .PackageDescriptor.Metadata.Readme}}</div>{{end}}
 | 
			
		||||
		{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}}
 | 
			
		||||
	{{end}}
 | 
			
		||||
 | 
			
		||||
	{{if .PackageDescriptor.Metadata.Dependencies}}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
		<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
 | 
			
		||||
		<div class="ui attached segment">
 | 
			
		||||
			{{if .PackageDescriptor.Metadata.Description}}<p>{{.PackageDescriptor.Metadata.Description}}</p>{{end}}
 | 
			
		||||
			{{if .PackageDescriptor.Metadata.LongDescription}}{{RenderMarkdownToHtml .PackageDescriptor.Metadata.LongDescription}}{{end}}
 | 
			
		||||
			{{if .PackageDescriptor.Metadata.LongDescription}}{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.LongDescription}}{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@
 | 
			
		||||
		<div class="ui attached segment">
 | 
			
		||||
			{{if .PackageDescriptor.Metadata.Readme}}
 | 
			
		||||
			<div class="markup markdown">
 | 
			
		||||
				{{RenderMarkdownToHtml .PackageDescriptor.Metadata.Readme}}
 | 
			
		||||
				{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}
 | 
			
		||||
			</div>
 | 
			
		||||
			{{else if .PackageDescriptor.Metadata.Description}}
 | 
			
		||||
				{{.PackageDescriptor.Metadata.Description}}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user