mirror of https://github.com/go-gitea/gitea
Merge branch 'main' into add-issue-planned-time
This commit is contained in:
commit
721069d766
|
@ -5,7 +5,10 @@
|
|||
// installs nodejs into container
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version":"20"
|
||||
}
|
||||
},
|
||||
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
|
||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
|
||||
"ghcr.io/devcontainers/features/python:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
@ -20,7 +23,7 @@
|
|||
"Vue.volar",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"zixuanchen.vitest-explorer",
|
||||
"alexcvzz.vscode-sqlite"
|
||||
"qwtel.sqlite-viewer"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
@ -96,6 +96,7 @@ jobs:
|
|||
- run: make deps-frontend
|
||||
- run: make lint-frontend
|
||||
- run: make checks-frontend
|
||||
- run: make test-frontend
|
||||
- run: make frontend
|
||||
|
||||
backend:
|
||||
|
@ -110,7 +111,7 @@ jobs:
|
|||
check-latest: true
|
||||
# no frontend build here as backend should be able to build
|
||||
# even without any frontend files
|
||||
- run: make deps-backend deps-tools
|
||||
- run: make deps-backend
|
||||
- run: go build -o gitea_no_gcc # test if build succeeds without the sqlite tag
|
||||
- name: build-backend-arm64
|
||||
run: make backend # test cross compile
|
||||
|
|
|
@ -55,6 +55,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
||||
- run: git fetch --unshallow --quiet --tags --force
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- name: Get cleaned branch name
|
||||
|
@ -75,12 +78,14 @@ jobs:
|
|||
- name: build rootful docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}
|
||||
- name: build rootless docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: Dockerfile.rootless
|
||||
|
|
|
@ -53,8 +53,6 @@ cpu.out
|
|||
/bin
|
||||
/dist
|
||||
/custom/*
|
||||
!/custom/conf
|
||||
/custom/conf/*
|
||||
!/custom/conf/app.example.ini
|
||||
/data
|
||||
/indexers
|
||||
|
|
|
@ -34,7 +34,7 @@ vscode:
|
|||
- Vue.volar
|
||||
- ms-azuretools.vscode-docker
|
||||
- zixuanchen.vitest-explorer
|
||||
- alexcvzz.vscode-sqlite
|
||||
- qwtel.sqlite-viewer
|
||||
|
||||
ports:
|
||||
- name: Gitea
|
||||
|
|
|
@ -557,7 +557,7 @@ be reviewed by two maintainers and must pass the automatic tests.
|
|||
- And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically create a release and upload all the compiled binary. (But currently it doesn't add the release notes automatically. Maybe we should fix that.)
|
||||
- If needed send a frontport PR for the changelog to branch `main` and update the version in `docs/config.yaml` to refer to the new version.
|
||||
- Send PR to [blog repository](https://gitea.com/gitea/blog) announcing the release.
|
||||
- Verify all release assets were correctly published through CI on dl.gitea.io and GitHub releases. Once ACKed:
|
||||
- bump the version of https://dl.gitea.io/gitea/version.json
|
||||
- Verify all release assets were correctly published through CI on dl.gitea.com and GitHub releases. Once ACKed:
|
||||
- bump the version of https://dl.gitea.com/gitea/version.json
|
||||
- merge the blog post PR
|
||||
- announce the release in discord `#announcements`
|
||||
|
|
7
Makefile
7
Makefile
|
@ -79,6 +79,9 @@ endif
|
|||
STORED_VERSION_FILE := VERSION
|
||||
HUGO_VERSION ?= 0.111.3
|
||||
|
||||
GITHUB_REF_TYPE ?= branch
|
||||
GITHUB_REF_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
ifneq ($(GITHUB_REF_TYPE),branch)
|
||||
VERSION ?= $(subst v,,$(GITHUB_REF_NAME))
|
||||
GITEA_VERSION ?= $(GITHUB_REF_NAME)
|
||||
|
@ -1011,9 +1014,5 @@ docker:
|
|||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||
|
||||
.PHONY: docker-build
|
||||
docker-build:
|
||||
docker run -ti --rm -v "$(CURDIR):/srv/app/src/code.gitea.io/gitea" -w /srv/app/src/code.gitea.io/gitea -e TAGS="bindata $(TAGS)" LDFLAGS="$(LDFLAGS)" CGO_EXTRA_CFLAGS="$(CGO_EXTRA_CFLAGS)" webhippie/golang:edge make clean build
|
||||
|
||||
# This endif closes the if at the top of the file
|
||||
endif
|
||||
|
|
|
@ -173,8 +173,8 @@ for the full license text.
|
|||
|
||||
Looking for an overview of the interface? Check it out!
|
||||
|
||||
|![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|
||||
|![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)|
|
||||
|:---:|:---:|:---:|
|
||||
|![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|
||||
|![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|
||||
![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|
|
||||
|![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)|
|
||||
|![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|
||||
![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)|
|
||||
|
|
|
@ -91,8 +91,8 @@ Fork -> Patch -> Push -> Pull Request
|
|||
|
||||
## 截图
|
||||
|
||||
|![Dashboard](https://dl.gitea.io/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.io/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.io/screenshots/global_issues.png)|
|
||||
|![Dashboard](https://dl.gitea.com/screenshots/home_timeline.png)|![User Profile](https://dl.gitea.com/screenshots/user_profile.png)|![Global Issues](https://dl.gitea.com/screenshots/global_issues.png)|
|
||||
|:---:|:---:|:---:|
|
||||
|![Branches](https://dl.gitea.io/screenshots/branches.png)|![Web Editor](https://dl.gitea.io/screenshots/web_editor.png)|![Activity](https://dl.gitea.io/screenshots/activity.png)|
|
||||
|![New Migration](https://dl.gitea.io/screenshots/migration.png)|![Migrating](https://dl.gitea.io/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|
||||
![Pull Request Dark](https://dl.gitea.io/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.io/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.io/screenshots/diff_dark.png)|
|
||||
|![Branches](https://dl.gitea.com/screenshots/branches.png)|![Web Editor](https://dl.gitea.com/screenshots/web_editor.png)|![Activity](https://dl.gitea.com/screenshots/activity.png)|
|
||||
|![New Migration](https://dl.gitea.com/screenshots/migration.png)|![Migrating](https://dl.gitea.com/screenshots/migration.gif)|![Pull Request View](https://image.ibb.co/e02dSb/6.png)
|
||||
![Pull Request Dark](https://dl.gitea.com/screenshots/pull_requests_dark.png)|![Diff Review Dark](https://dl.gitea.com/screenshots/review_dark.png)|![Diff Dark](https://dl.gitea.com/screenshots/diff_dark.png)|
|
||||
|
|
|
@ -42,7 +42,7 @@ func runGenerateActionsRunnerToken(c *cli.Context) error {
|
|||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
scope := c.String("scope")
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ func confirm() (bool, error) {
|
|||
}
|
||||
|
||||
func initDB(ctx context.Context) error {
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
setting.InitSQLLoggersForCli(log.INFO)
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ func runRecreateTable(ctx *cli.Context) error {
|
|||
golog.SetOutput(log.LoggerToWriter(log.GetLogger(log.DEFAULT).Info))
|
||||
|
||||
debug := ctx.Bool("debug")
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
|
||||
if debug {
|
||||
|
|
|
@ -182,7 +182,7 @@ func runDump(ctx *cli.Context) error {
|
|||
}
|
||||
fileName += "." + outType
|
||||
}
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
// make sure we are logging to the console no matter what the configuration tells us do to
|
||||
// FIXME: don't use CfgProvider directly
|
||||
|
|
|
@ -99,11 +99,6 @@ type assetFile struct {
|
|||
func initEmbeddedExtractor(c *cli.Context) error {
|
||||
setupConsoleLogger(log.ERROR, log.CanColorStderr, os.Stderr)
|
||||
|
||||
// Read configuration file
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
|
||||
patterns, err := compileCollectPatterns(c.Args())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -16,7 +16,7 @@ func runSendMail(c *cli.Context) error {
|
|||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
if err := argsSet(c, "title"); err != nil {
|
||||
return err
|
||||
|
|
|
@ -51,7 +51,7 @@ func runRestoreRepository(c *cli.Context) error {
|
|||
ctx, cancel := installSignals()
|
||||
defer cancel()
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
var units []string
|
||||
if s := c.String("units"); s != "" {
|
||||
units = strings.Split(s, ",")
|
||||
|
|
|
@ -61,7 +61,7 @@ func setup(ctx context.Context, debug bool) {
|
|||
} else {
|
||||
setupConsoleLogger(log.FATAL, false, os.Stderr)
|
||||
}
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
if debug {
|
||||
setting.RunMode = "dev"
|
||||
}
|
||||
|
|
180
cmd/web.go
180
cmd/web.go
|
@ -101,6 +101,110 @@ func createPIDFile(pidPath string) {
|
|||
}
|
||||
}
|
||||
|
||||
func serveInstall(ctx *cli.Context) error {
|
||||
log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||
log.Info("App path: %s", setting.AppPath)
|
||||
log.Info("Work path: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Config file: %s", setting.CustomConf)
|
||||
log.Info("Prepare to run install page")
|
||||
|
||||
routers.InitWebInstallPage(graceful.GetManager().HammerContext())
|
||||
|
||||
// Flag for port number in case first time run conflict
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ctx.IsSet("install-port") {
|
||||
if err := setPort(ctx.String("install-port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c := install.Routes()
|
||||
err := listen(c, false)
|
||||
if err != nil {
|
||||
log.Critical("Unable to open listener for installer. Is Gitea already running?")
|
||||
graceful.GetManager().DoGracefulShutdown()
|
||||
}
|
||||
select {
|
||||
case <-graceful.GetManager().IsShutdown():
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
return err
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serveInstalled(ctx *cli.Context) error {
|
||||
setting.InitCfgProvider(setting.CustomConf)
|
||||
setting.LoadCommonSettings()
|
||||
setting.MustInstalled()
|
||||
|
||||
log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||
log.Info("App path: %s", setting.AppPath)
|
||||
log.Info("Work path: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Config file: %s", setting.CustomConf)
|
||||
log.Info("Run mode: %s", setting.RunMode)
|
||||
log.Info("Prepare to run web server")
|
||||
|
||||
if setting.AppWorkPathMismatch {
|
||||
log.Error("WORK_PATH from config %q doesn't match other paths from environment variables or command arguments. "+
|
||||
"Only WORK_PATH in config should be set and used. Please remove the other outdated work paths from environment variables and command arguments", setting.CustomConf)
|
||||
}
|
||||
|
||||
rootCfg := setting.CfgProvider
|
||||
if rootCfg.Section("").Key("WORK_PATH").String() == "" {
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
log.Error("Unable to prepare saving WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
|
||||
} else {
|
||||
rootCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
||||
saveCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
||||
if err = saveCfg.Save(); err != nil {
|
||||
log.Error("Unable to update WORK_PATH=%s to config %q: %v\nYou must set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routers.InitWebInstalled(graceful.GetManager().HammerContext())
|
||||
|
||||
// We check that AppDataPath exists here (it should have been created during installation)
|
||||
// We can't check it in `InitWebInstalled`, because some integration tests
|
||||
// use cmd -> InitWebInstalled, but the AppDataPath doesn't exist during those tests.
|
||||
if _, err := os.Stat(setting.AppDataPath); err != nil {
|
||||
log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
|
||||
}
|
||||
|
||||
// Override the provided port number within the configuration
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set up Chi routes
|
||||
c := routers.NormalRoutes()
|
||||
err := listen(c, true)
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func servePprof() {
|
||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
||||
log.Info("Starting pprof server on localhost:6060")
|
||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||
finished()
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) error {
|
||||
if ctx.Bool("verbose") {
|
||||
setupConsoleLogger(log.TRACE, log.CanColorStdout, os.Stdout)
|
||||
|
@ -128,75 +232,19 @@ func runWeb(ctx *cli.Context) error {
|
|||
createPIDFile(ctx.String("pid"))
|
||||
}
|
||||
|
||||
// Perform pre-initialization
|
||||
needsInstall := install.PreloadSettings(graceful.GetManager().HammerContext())
|
||||
if needsInstall {
|
||||
// Flag for port number in case first time run conflict
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if ctx.IsSet("install-port") {
|
||||
if err := setPort(ctx.String("install-port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c := install.Routes()
|
||||
err := listen(c, false)
|
||||
if err != nil {
|
||||
log.Critical("Unable to open listener for installer. Is Gitea already running?")
|
||||
graceful.GetManager().DoGracefulShutdown()
|
||||
}
|
||||
select {
|
||||
case <-graceful.GetManager().IsShutdown():
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
if !setting.InstallLock {
|
||||
if err := serveInstall(ctx); err != nil {
|
||||
return err
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
NoInstallListener()
|
||||
}
|
||||
|
||||
if setting.EnablePprof {
|
||||
go func() {
|
||||
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||
_, _, finished := process.GetManager().AddTypedContext(context.Background(), "Web: PProf Server", process.SystemProcessType, true)
|
||||
// The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment it's not worth to introduce a configurable option for it.
|
||||
log.Info("Starting pprof server on localhost:6060")
|
||||
log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
|
||||
finished()
|
||||
}()
|
||||
go servePprof()
|
||||
}
|
||||
|
||||
log.Info("Global init")
|
||||
// Perform global initialization
|
||||
setting.Init(&setting.Options{})
|
||||
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
|
||||
|
||||
// We check that AppDataPath exists here (it should have been created during installation)
|
||||
// We can't check it in `GlobalInitInstalled`, because some integration tests
|
||||
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
|
||||
if _, err := os.Stat(setting.AppDataPath); err != nil {
|
||||
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
|
||||
}
|
||||
|
||||
// Override the provided port number within the configuration
|
||||
if ctx.IsSet("port") {
|
||||
if err := setPort(ctx.String("port")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Set up Chi routes
|
||||
c := routers.NormalRoutes()
|
||||
err := listen(c, true)
|
||||
<-graceful.GetManager().Done()
|
||||
log.Info("PID: %d Gitea Web Finished", os.Getpid())
|
||||
log.GetManager().Close()
|
||||
return err
|
||||
return serveInstalled(ctx)
|
||||
}
|
||||
|
||||
func setPort(port string) error {
|
||||
|
@ -217,9 +265,15 @@ func setPort(port string) error {
|
|||
defaultLocalURL += ":" + setting.HTTPPort + "/"
|
||||
|
||||
// Save LOCAL_ROOT_URL if port changed
|
||||
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||
if err := setting.CfgProvider.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save config file: %v", err)
|
||||
rootCfg := setting.CfgProvider
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save config file: %v", err)
|
||||
}
|
||||
rootCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||
saveCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
|
||||
if err = saveCfg.Save(); err != nil {
|
||||
return fmt.Errorf("failed to save config file: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -81,8 +81,6 @@ func main() {
|
|||
},
|
||||
}
|
||||
app.Action = runEnvironmentToIni
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||
|
@ -90,12 +88,13 @@ func main() {
|
|||
}
|
||||
|
||||
func runEnvironmentToIni(c *cli.Context) error {
|
||||
providedCustom := c.String("custom-path")
|
||||
providedConf := c.String("config")
|
||||
providedWorkPath := c.String("work-path")
|
||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, setting.ArgWorkPathAndCustomConf{
|
||||
WorkPath: c.String("work-path"),
|
||||
CustomPath: c.String("custom-path"),
|
||||
CustomConf: c.String("config"),
|
||||
})
|
||||
|
||||
cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true})
|
||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
|
|
|
@ -344,9 +344,6 @@ NAME = gitea
|
|||
USER = root
|
||||
;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password.
|
||||
;SSL_MODE = false ; either "false" (default), "true", or "skip-verify"
|
||||
;CHARSET = utf8mb4 ;either "utf8" or "utf8mb4", default is "utf8mb4".
|
||||
;;
|
||||
;; NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
|
@ -2159,7 +2156,7 @@ LEVEL = Info
|
|||
;RUN_AT_START = false
|
||||
;ENABLE_SUCCESS_NOTICE = false
|
||||
;SCHEDULE = @every 168h
|
||||
;HTTP_ENDPOINT = https://dl.gitea.io/gitea/version.json
|
||||
;HTTP_ENDPOINT = https://dl.gitea.com/gitea/version.json
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -443,7 +443,6 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
|||
- `SQLITE_TIMEOUT`: **500**: Query timeout for SQLite3 only.
|
||||
- `SQLITE_JOURNAL_MODE`: **""**: Change journal mode for SQlite3. Can be used to enable [WAL mode](https://www.sqlite.org/wal.html) when high load causes write congestion. See [SQlite3 docs](https://www.sqlite.org/pragma.html#pragma_journal_mode) for possible values. Defaults to the default for the database file, often DELETE.
|
||||
- `ITERATE_BUFFER_SIZE`: **50**: Internal buffer size for iterating.
|
||||
- `CHARSET`: **utf8mb4**: For MySQL only, either "utf8" or "utf8mb4". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this.
|
||||
- `PATH`: **data/gitea.db**: For SQLite3 only, the database file path.
|
||||
- `LOG_SQL`: **true**: Log the executed SQL.
|
||||
- `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed.
|
||||
|
@ -1013,7 +1012,7 @@ Default templates for project boards:
|
|||
- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
|
||||
- `ENABLE_SUCCESS_NOTICE`: **true**: Set to false to switch off success notices.
|
||||
- `SCHEDULE`: **@every 168h**: Cron syntax for scheduling a work, e.g. `@every 168h`.
|
||||
- `HTTP_ENDPOINT`: **https://dl.gitea.io/gitea/version.json**: the endpoint that Gitea will check for newer versions
|
||||
- `HTTP_ENDPOINT`: **https://dl.gitea.com/gitea/version.json**: the endpoint that Gitea will check for newer versions
|
||||
|
||||
#### Cron - Delete all old system notices from database (`cron.delete_old_system_notices`)
|
||||
|
||||
|
|
|
@ -396,8 +396,6 @@ Please run `gitea convert`, or run `ALTER DATABASE database_name CHARACTER SET u
|
|||
for the database_name and run `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`
|
||||
for each table in the database.
|
||||
|
||||
You will also need to change the app.ini database charset to `CHARSET=utf8mb4`.
|
||||
|
||||
## Why are Emoji displaying only as placeholders or in monochrome
|
||||
|
||||
Gitea requires the system or browser to have one of the supported Emoji fonts installed, which are Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji and Twemoji Mozilla. Generally, the operating system should already provide one of these fonts, but especially on Linux, it may be necessary to install them manually.
|
||||
|
|
|
@ -31,7 +31,7 @@ menu:
|
|||
|
||||
**注意:**此示例也适用于Docker镜像!
|
||||
|
||||
在我们的[下载页面](https://dl.gitea.io/gitea/)上,您会看到一个1.7目录,以及1.7.0、1.7.1、1.7.2、1.7.3、1.7.4、1.7.5和1.7.6的目录。
|
||||
在我们的[下载页面](https://dl.gitea.com/gitea/)上,您会看到一个1.7目录,以及1.7.0、1.7.1、1.7.2、1.7.3、1.7.4、1.7.5和1.7.6的目录。
|
||||
|
||||
1.7目录和1.7.0目录是**不同**的。1.7目录是在每个合并到[`release/v1.7`](https://github.com/go-gitea/gitea/tree/release/v1.7)分支的提交上构建的。
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ menu:
|
|||
|
||||
# Installation avec le binaire pré-compilé
|
||||
|
||||
Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.io/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle:
|
||||
Tous les binaires sont livrés avec le support de SQLite, MySQL et PostgreSQL, et sont construits avec les ressources incorporées. Gardez à l'esprit que cela peut être différent pour les versions antérieures. L'installation basée sur nos binaires est assez simple, il suffit de choisir le fichier correspondant à votre plateforme à partir de la [page de téléchargement](https://dl.gitea.com/gitea). Copiez l'URL et remplacer l'URL dans les commandes suivantes par la nouvelle:
|
||||
|
||||
```
|
||||
wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||
wget -O gitea https://dl.gitea.com/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||
chmod +x gitea
|
||||
```
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ menu:
|
|||
|
||||
# 從執行檔安裝
|
||||
|
||||
所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.io/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了:
|
||||
所有的執行檔皆支援 SQLite, MySQL and PostgreSQL,且所有檔案都已經包在執行檔內,這一點跟之前的版本有所不同。關於執行檔的安裝方式非常簡單,只要從[下載頁面](https://dl.gitea.com/gitea)選擇相對應平台,複製下載連結,使用底下指令就可以完成了:
|
||||
|
||||
```
|
||||
wget -O gitea https://dl.gitea.io/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||
wget -O gitea https://dl.gitea.com/gitea/{{< version >}}/gitea-{{< version >}}-linux-amd64
|
||||
chmod +x gitea
|
||||
```
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ Gitea provides a Helm Chart to allow for installation on kubernetes.
|
|||
A non-customized install can be done with:
|
||||
|
||||
```
|
||||
helm repo add gitea-charts https://dl.gitea.io/charts/
|
||||
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||
helm install gitea gitea-charts/gitea
|
||||
```
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ Gitea 已经提供了便于在 Kubernetes 云原生环境中安装所需的 Helm
|
|||
默认安装指令为:
|
||||
|
||||
```bash
|
||||
helm repo add gitea https://dl.gitea.io/charts
|
||||
helm repo add gitea https://dl.gitea.com/charts
|
||||
helm repo update
|
||||
helm install gitea gitea/gitea
|
||||
```
|
||||
|
|
|
@ -22,7 +22,7 @@ Gitea 提供 Helm Chart 用來安裝於 kubernetes。
|
|||
非自訂安裝可使用下列指令:
|
||||
|
||||
```
|
||||
helm repo add gitea-charts https://dl.gitea.io/charts/
|
||||
helm repo add gitea-charts https://dl.gitea.com/charts/
|
||||
helm install gitea gitea-charts/gitea
|
||||
```
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
|
|||
|
||||
* Create a Gogs backup with `gogs backup`. This creates `gogs-backup-[timestamp].zip` file
|
||||
containing all important Gogs data. You would need it if you wanted to move to the `gogs` back later.
|
||||
* Download the file matching the destination platform from the [downloads page](https://dl.gitea.io/gitea/).
|
||||
* Download the file matching the destination platform from the [downloads page](https://dl.gitea.com/gitea/).
|
||||
It should be `1.0.x` version. Migrating from `gogs` to any other version is impossible.
|
||||
* Put the binary at the desired install location.
|
||||
* Copy `gogs/custom/conf/app.ini` to `gitea/custom/conf/app.ini`.
|
||||
|
@ -79,11 +79,11 @@ There are some basic steps to follow. On a Linux system run as the Gogs user:
|
|||
After successful migration from `gogs` to `gitea 1.0.x`, it is possible to upgrade `gitea` to a modern version
|
||||
in a two steps process.
|
||||
|
||||
Upgrade to [`gitea 1.6.4`](https://dl.gitea.io/gitea/1.6.4/) first. Download the file matching
|
||||
the destination platform from the [downloads page](https://dl.gitea.io/gitea/1.6.4/) and replace the binary.
|
||||
Upgrade to [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/) first. Download the file matching
|
||||
the destination platform from the [downloads page](https://dl.gitea.com/gitea/1.6.4/) and replace the binary.
|
||||
Run Gitea at least once and check that everything works as expected.
|
||||
|
||||
Then repeat the procedure, but this time using the [latest release](https://dl.gitea.io/gitea/{{< version >}}/).
|
||||
Then repeat the procedure, but this time using the [latest release](https://dl.gitea.com/gitea/{{< version >}}/).
|
||||
|
||||
## Upgrading from a more recent version of Gogs
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ menu:
|
|||
Veuillez suivre les étapes ci-dessous. Sur Unix, toute les commandes s'exécutent en tant que l'utilisateur utilisé pour votre installation de Gogs :
|
||||
|
||||
* Crééer une sauvegarde de Gogs avec la commande `gogs dump`. Le fichier nouvellement créé `gogs-dump-[timestamp].zip` contient toutes les données de votre instance de Gogs.
|
||||
* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.io/gitea).
|
||||
* Téléchargez le fichier correspondant à votre plateforme à partir de la [page de téléchargements](https://dl.gitea.com/gitea).
|
||||
* Mettez la binaire dans le répertoire d'installation souhaité.
|
||||
* Copiez le fichier `gogs/custom/conf/app.ini` vers `gitea/custom/conf/app.ini`.
|
||||
* Si vous avez personnalisé les répertoires `templates, public` dans `gogs/custom/`, copiez-les vers `gitea/custom/`.
|
||||
|
|
|
@ -27,7 +27,7 @@ menu:
|
|||
|
||||
- 使用 `gogs backup` 建立 Gogs 的備份。這會建立檔案 `gogs-backup-[timestamp].zip` 包含所有重要的 Gogs 資料。
|
||||
如果稍後您要恢復到 `gogs` 時會用到它。
|
||||
- 從[下載頁](https://dl.gitea.io/gitea/)下載對應您平臺的檔案。請下載 `1.0.x` 版,從 `gogs` 遷移到其它版本是不可行的。
|
||||
- 從[下載頁](https://dl.gitea.com/gitea/)下載對應您平臺的檔案。請下載 `1.0.x` 版,從 `gogs` 遷移到其它版本是不可行的。
|
||||
- 將二進位檔放到適當的安裝位置。
|
||||
- 複製 `gogs/custom/conf/app.ini` 到 `gitea/custom/conf/app.ini`。
|
||||
- 從 `gogs/custom/` 複製自訂 `templates, public` 到 `gitea/custom/`。
|
||||
|
@ -77,10 +77,10 @@ menu:
|
|||
|
||||
成功從 `gogs` 升級到 `gitea 1.0.x` 後再用 2 個步驟即可升級到最新版的 `gitea`。
|
||||
|
||||
請先升級到 [`gitea 1.6.4`](https://dl.gitea.io/gitea/1.6.4/),先從[下載頁](https://dl.gitea.io/gitea/1.6.4/)下載
|
||||
請先升級到 [`gitea 1.6.4`](https://dl.gitea.com/gitea/1.6.4/),先從[下載頁](https://dl.gitea.com/gitea/1.6.4/)下載
|
||||
您平臺的二進位檔取代既有的。至少執行一次 Gitea 並確認一切符合預期。
|
||||
|
||||
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.io/gitea/{{< version >}}/)。
|
||||
接著重複上述步驟,但這次請使用[最新發行版本](https://dl.gitea.com/gitea/{{< version >}}/)。
|
||||
|
||||
## 從更新版本的 Gogs 升級
|
||||
|
||||
|
|
|
@ -51,6 +51,8 @@ a/b/c/d.json
|
|||
|
||||
In any file matched by the above globs, certain variables will be expanded.
|
||||
|
||||
Matching filenames and paths can also be expanded, and are conservatively sanitized to support cross-platform filesystems.
|
||||
|
||||
All variables must be of the form `$VAR` or `${VAR}`. To escape an expansion, use a double `$$`, such as `$$VAR` or `$${VAR}`
|
||||
|
||||
| Variable | Expands To | Transformable |
|
||||
|
|
171
main.go
171
main.go
|
@ -33,30 +33,58 @@ var (
|
|||
Tags = ""
|
||||
// MakeVersion holds the current Make version if built with make
|
||||
MakeVersion = ""
|
||||
|
||||
originalAppHelpTemplate = ""
|
||||
originalCommandHelpTemplate = ""
|
||||
originalSubcommandHelpTemplate = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
setting.AppVer = Version
|
||||
setting.AppBuiltWith = formatBuiltWith()
|
||||
setting.AppStartTime = time.Now().UTC()
|
||||
}
|
||||
|
||||
// Grab the original help templates
|
||||
originalAppHelpTemplate = cli.AppHelpTemplate
|
||||
originalCommandHelpTemplate = cli.CommandHelpTemplate
|
||||
originalSubcommandHelpTemplate = cli.SubcommandHelpTemplate
|
||||
// cmdHelp is our own help subcommand with more information
|
||||
// test cases:
|
||||
// ./gitea help
|
||||
// ./gitea -h
|
||||
// ./gitea web help
|
||||
// ./gitea web -h (due to cli lib limitation, this won't call our cmdHelp, so no extra info)
|
||||
// ./gitea admin help auth
|
||||
// ./gitea -c /tmp/app.ini -h
|
||||
// ./gitea -c /tmp/app.ini help
|
||||
// ./gitea help -c /tmp/app.ini
|
||||
// GITEA_WORK_DIR=/tmp ./gitea help
|
||||
// GITEA_WORK_DIR=/tmp ./gitea help --work-path /tmp/other
|
||||
// GITEA_WORK_DIR=/tmp ./gitea help --config /tmp/app-other.ini
|
||||
var cmdHelp = cli.Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *cli.Context) (err error) {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
err = cli.ShowCommandHelp(c, args.First())
|
||||
} else {
|
||||
err = cli.ShowAppHelp(c)
|
||||
}
|
||||
_, _ = fmt.Fprintf(c.App.Writer, `
|
||||
DEFAULT CONFIGURATION:
|
||||
AppPath: %s
|
||||
WorkPath: %s
|
||||
CustomPath: %s
|
||||
ConfigFile: %s
|
||||
|
||||
`, setting.AppPath, setting.AppWorkPath, setting.CustomPath, setting.CustomConf)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Gitea"
|
||||
app.Usage = "A painless self-hosted Git service"
|
||||
app.Description = `By default, gitea will start serving using the webserver with no
|
||||
arguments - which can alternatively be run by running the subcommand web.`
|
||||
app.Description = `By default, Gitea will start serving using the web-server with no argument, which can alternatively be run by running the subcommand "web".`
|
||||
app.Version = Version + formatBuiltWith()
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdWeb,
|
||||
cmd.CmdServ,
|
||||
|
@ -77,118 +105,83 @@ arguments - which can alternatively be run by running the subcommand web.`
|
|||
cmd.CmdRestoreRepository,
|
||||
cmd.CmdActions,
|
||||
}
|
||||
// Now adjust these commands to add our global configuration options
|
||||
|
||||
// First calculate the default paths and set the AppHelpTemplates in this context
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setAppHelpTemplates()
|
||||
|
||||
// default configuration flags
|
||||
defaultFlags := []cli.Flag{
|
||||
globalFlags := []cli.Flag{
|
||||
cli.HelpFlag,
|
||||
cli.StringFlag{
|
||||
Name: "custom-path, C",
|
||||
Value: setting.CustomPath,
|
||||
Usage: "Custom path file path",
|
||||
Usage: "Set custom path (defaults to '{WorkPath}/custom')",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Value: setting.CustomConf,
|
||||
Usage: "Custom configuration file path",
|
||||
Usage: "Set custom config file (defaults to '{WorkPath}/custom/conf/app.ini')",
|
||||
},
|
||||
cli.VersionFlag,
|
||||
cli.StringFlag{
|
||||
Name: "work-path, w",
|
||||
Value: setting.AppWorkPath,
|
||||
Usage: "Set the gitea working path",
|
||||
Usage: "Set Gitea's working path (defaults to the Gitea's binary directory)",
|
||||
},
|
||||
}
|
||||
|
||||
// Set the default to be equivalent to cmdWeb and add the default flags
|
||||
app.Flags = append(app.Flags, globalFlags...)
|
||||
app.Flags = append(app.Flags, cmd.CmdWeb.Flags...)
|
||||
app.Flags = append(app.Flags, defaultFlags...)
|
||||
app.Action = cmd.CmdWeb.Action
|
||||
|
||||
// Add functions to set these paths and these flags to the commands
|
||||
app.Before = establishCustomPath
|
||||
app.Action = prepareWorkPathAndCustomConf(cmd.CmdWeb.Action)
|
||||
app.HideHelp = true // use our own help action to show helps (with more information like default config)
|
||||
app.Commands = append(app.Commands, cmdHelp)
|
||||
for i := range app.Commands {
|
||||
setFlagsAndBeforeOnSubcommands(&app.Commands[i], defaultFlags, establishCustomPath)
|
||||
prepareSubcommands(&app.Commands[i], globalFlags)
|
||||
}
|
||||
|
||||
app.EnableBashCompletion = true
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to run app with %s: %v", os.Args, err)
|
||||
_, _ = fmt.Fprintf(app.Writer, "\nFailed to run with %s: %v\n", os.Args, err)
|
||||
}
|
||||
|
||||
log.GetManager().Close()
|
||||
}
|
||||
|
||||
func setFlagsAndBeforeOnSubcommands(command *cli.Command, defaultFlags []cli.Flag, before cli.BeforeFunc) {
|
||||
func prepareSubcommands(command *cli.Command, defaultFlags []cli.Flag) {
|
||||
command.Flags = append(command.Flags, defaultFlags...)
|
||||
command.Before = establishCustomPath
|
||||
command.Action = prepareWorkPathAndCustomConf(command.Action)
|
||||
command.HideHelp = true
|
||||
if command.Name != "help" {
|
||||
command.Subcommands = append(command.Subcommands, cmdHelp)
|
||||
}
|
||||
for i := range command.Subcommands {
|
||||
setFlagsAndBeforeOnSubcommands(&command.Subcommands[i], defaultFlags, before)
|
||||
prepareSubcommands(&command.Subcommands[i], defaultFlags)
|
||||
}
|
||||
}
|
||||
|
||||
func establishCustomPath(ctx *cli.Context) error {
|
||||
var providedCustom string
|
||||
var providedConf string
|
||||
var providedWorkPath string
|
||||
|
||||
currentCtx := ctx
|
||||
for {
|
||||
if len(providedCustom) != 0 && len(providedConf) != 0 && len(providedWorkPath) != 0 {
|
||||
break
|
||||
}
|
||||
if currentCtx == nil {
|
||||
break
|
||||
}
|
||||
if currentCtx.IsSet("custom-path") && len(providedCustom) == 0 {
|
||||
providedCustom = currentCtx.String("custom-path")
|
||||
}
|
||||
if currentCtx.IsSet("config") && len(providedConf) == 0 {
|
||||
providedConf = currentCtx.String("config")
|
||||
}
|
||||
if currentCtx.IsSet("work-path") && len(providedWorkPath) == 0 {
|
||||
providedWorkPath = currentCtx.String("work-path")
|
||||
}
|
||||
currentCtx = currentCtx.Parent()
|
||||
|
||||
// prepareWorkPathAndCustomConf wraps the Action to prepare the work path and custom config
|
||||
// It can't use "Before", because each level's sub-command's Before will be called one by one, so the "init" would be done multiple times
|
||||
func prepareWorkPathAndCustomConf(a any) func(ctx *cli.Context) error {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath)
|
||||
|
||||
setAppHelpTemplates()
|
||||
|
||||
if ctx.IsSet("version") {
|
||||
cli.ShowVersion(ctx)
|
||||
os.Exit(0)
|
||||
action := a.(func(*cli.Context) error)
|
||||
return func(ctx *cli.Context) error {
|
||||
var args setting.ArgWorkPathAndCustomConf
|
||||
curCtx := ctx
|
||||
for curCtx != nil {
|
||||
if curCtx.IsSet("work-path") && args.WorkPath == "" {
|
||||
args.WorkPath = curCtx.String("work-path")
|
||||
}
|
||||
if curCtx.IsSet("custom-path") && args.CustomPath == "" {
|
||||
args.CustomPath = curCtx.String("custom-path")
|
||||
}
|
||||
if curCtx.IsSet("config") && args.CustomConf == "" {
|
||||
args.CustomConf = curCtx.String("config")
|
||||
}
|
||||
curCtx = curCtx.Parent()
|
||||
}
|
||||
setting.InitWorkPathAndCommonConfig(os.Getenv, args)
|
||||
if ctx.Bool("help") {
|
||||
return cmdHelp.Action.(func(ctx *cli.Context) error)(ctx)
|
||||
}
|
||||
return action(ctx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setAppHelpTemplates() {
|
||||
cli.AppHelpTemplate = adjustHelpTemplate(originalAppHelpTemplate)
|
||||
cli.CommandHelpTemplate = adjustHelpTemplate(originalCommandHelpTemplate)
|
||||
cli.SubcommandHelpTemplate = adjustHelpTemplate(originalSubcommandHelpTemplate)
|
||||
}
|
||||
|
||||
func adjustHelpTemplate(originalTemplate string) string {
|
||||
overridden := ""
|
||||
if _, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
|
||||
overridden = "(GITEA_CUSTOM)"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(`%s
|
||||
DEFAULT CONFIGURATION:
|
||||
CustomPath: %s %s
|
||||
CustomConf: %s
|
||||
AppPath: %s
|
||||
AppWorkPath: %s
|
||||
|
||||
`, originalTemplate, setting.CustomPath, overridden, setting.CustomConf, setting.AppPath, setting.AppWorkPath)
|
||||
}
|
||||
|
||||
func formatBuiltWith() string {
|
||||
|
|
|
@ -71,6 +71,7 @@ type FindRunOptions struct {
|
|||
WorkflowFileName string
|
||||
TriggerUserID int64
|
||||
Approved bool // not util.OptionalBool, it works only when it's true
|
||||
Status Status
|
||||
}
|
||||
|
||||
func (opts FindRunOptions) toConds() builder.Cond {
|
||||
|
@ -90,6 +91,9 @@ func (opts FindRunOptions) toConds() builder.Cond {
|
|||
if opts.Approved {
|
||||
cond = cond.And(builder.Gt{"approved_by": 0})
|
||||
}
|
||||
if opts.Status > StatusUnknown {
|
||||
cond = cond.And(builder.Eq{"status": opts.Status})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
|
@ -106,3 +110,34 @@ func FindRuns(ctx context.Context, opts FindRunOptions) (RunList, int64, error)
|
|||
func CountRuns(ctx context.Context, opts FindRunOptions) (int64, error) {
|
||||
return db.GetEngine(ctx).Where(opts.toConds()).Count(new(ActionRun))
|
||||
}
|
||||
|
||||
type StatusInfo struct {
|
||||
Status int
|
||||
DisplayedStatus string
|
||||
}
|
||||
|
||||
// GetStatusInfoList returns a slice of StatusInfo
|
||||
func GetStatusInfoList(ctx context.Context) []StatusInfo {
|
||||
// same as those in aggregateJobStatus
|
||||
allStatus := []Status{StatusSuccess, StatusFailure, StatusWaiting, StatusRunning}
|
||||
statusInfoList := make([]StatusInfo, 0, 4)
|
||||
for _, s := range allStatus {
|
||||
statusInfoList = append(statusInfoList, StatusInfo{
|
||||
Status: int(s),
|
||||
DisplayedStatus: s.String(),
|
||||
})
|
||||
}
|
||||
return statusInfoList
|
||||
}
|
||||
|
||||
// GetActors returns a slice of Actors
|
||||
func GetActors(ctx context.Context, repoID int64) ([]*user_model.User, error) {
|
||||
actors := make([]*user_model.User, 0, 10)
|
||||
|
||||
return actors, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`action_run`.trigger_user_id").From("`action_run`").
|
||||
GroupBy("`action_run`.trigger_user_id").
|
||||
Where(builder.Eq{"`action_run`.repo_id": repoID}))).
|
||||
Cols("id", "name", "full_name", "avatar", "avatar_email", "use_custom_avatar").
|
||||
OrderBy(user_model.GetOrderByName()).
|
||||
Find(&actors)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type ActionVariable struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(owner_repo_name)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
|
||||
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
|
||||
Data string `xorm:"LONGTEXT NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ActionVariable))
|
||||
}
|
||||
|
||||
func (v *ActionVariable) Validate() error {
|
||||
if v.OwnerID == 0 && v.RepoID == 0 {
|
||||
return errors.New("the variable is not bound to any scope")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InsertVariable(ctx context.Context, ownerID, repoID int64, name, data string) (*ActionVariable, error) {
|
||||
variable := &ActionVariable{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Name: strings.ToUpper(name),
|
||||
Data: data,
|
||||
}
|
||||
if err := variable.Validate(); err != nil {
|
||||
return variable, err
|
||||
}
|
||||
return variable, db.Insert(ctx, variable)
|
||||
}
|
||||
|
||||
type FindVariablesOpts struct {
|
||||
db.ListOptions
|
||||
OwnerID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func (opts *FindVariablesOpts) toConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.OwnerID > 0 {
|
||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.RepoID > 0 {
|
||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func FindVariables(ctx context.Context, opts FindVariablesOpts) ([]*ActionVariable, error) {
|
||||
var variables []*ActionVariable
|
||||
sess := db.GetEngine(ctx)
|
||||
if opts.PageSize != 0 {
|
||||
sess = db.SetSessionPagination(sess, &opts.ListOptions)
|
||||
}
|
||||
return variables, sess.Where(opts.toConds()).Find(&variables)
|
||||
}
|
||||
|
||||
func GetVariableByID(ctx context.Context, variableID int64) (*ActionVariable, error) {
|
||||
var variable ActionVariable
|
||||
has, err := db.GetEngine(ctx).Where("id=?", variableID).Get(&variable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("variable with id %d: %w", variableID, util.ErrNotExist)
|
||||
}
|
||||
return &variable, nil
|
||||
}
|
||||
|
||||
func UpdateVariable(ctx context.Context, variable *ActionVariable) (bool, error) {
|
||||
count, err := db.GetEngine(ctx).ID(variable.ID).Cols("name", "data").
|
||||
Update(&ActionVariable{
|
||||
Name: variable.Name,
|
||||
Data: variable.Data,
|
||||
})
|
||||
return count != 0, err
|
||||
}
|
|
@ -18,11 +18,11 @@ import (
|
|||
type CodeComments map[string]map[int64][]*Comment
|
||||
|
||||
// FetchCodeComments will return a 2d-map: ["Path"]["Line"] = Comments at line
|
||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User) (CodeComments, error) {
|
||||
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil)
|
||||
func FetchCodeComments(ctx context.Context, issue *Issue, currentUser *user_model.User, showOutdatedComments bool) (CodeComments, error) {
|
||||
return fetchCodeCommentsByReview(ctx, issue, currentUser, nil, showOutdatedComments)
|
||||
}
|
||||
|
||||
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review) (CodeComments, error) {
|
||||
func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) (CodeComments, error) {
|
||||
pathToLineToComment := make(CodeComments)
|
||||
if review == nil {
|
||||
review = &Review{ID: 0}
|
||||
|
@ -33,7 +33,7 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
|||
ReviewID: review.ID,
|
||||
}
|
||||
|
||||
comments, err := findCodeComments(ctx, opts, issue, currentUser, review)
|
||||
comments, err := findCodeComments(ctx, opts, issue, currentUser, review, showOutdatedComments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -47,15 +47,17 @@ func fetchCodeCommentsByReview(ctx context.Context, issue *Issue, currentUser *u
|
|||
return pathToLineToComment, nil
|
||||
}
|
||||
|
||||
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review) ([]*Comment, error) {
|
||||
func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issue, currentUser *user_model.User, review *Review, showOutdatedComments bool) ([]*Comment, error) {
|
||||
var comments CommentList
|
||||
if review == nil {
|
||||
review = &Review{ID: 0}
|
||||
}
|
||||
conds := opts.ToConds()
|
||||
if review.ID == 0 {
|
||||
|
||||
if !showOutdatedComments && review.ID == 0 {
|
||||
conds = conds.And(builder.Eq{"invalidated": false})
|
||||
}
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
if err := e.Where(conds).
|
||||
Asc("comment.created_unix").
|
||||
|
@ -118,12 +120,12 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu
|
|||
}
|
||||
|
||||
// FetchCodeCommentsByLine fetches the code comments for a given treePath and line number
|
||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64) ([]*Comment, error) {
|
||||
func FetchCodeCommentsByLine(ctx context.Context, issue *Issue, currentUser *user_model.User, treePath string, line int64, showOutdatedComments bool) ([]*Comment, error) {
|
||||
opts := FindCommentsOptions{
|
||||
Type: CommentTypeCode,
|
||||
IssueID: issue.ID,
|
||||
TreePath: treePath,
|
||||
Line: line,
|
||||
}
|
||||
return findCodeComments(ctx, opts, issue, currentUser, nil)
|
||||
return findCodeComments(ctx, opts, issue, currentUser, nil, showOutdatedComments)
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestFetchCodeComments(t *testing.T) {
|
|||
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2})
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user)
|
||||
res, err := issues_model.FetchCodeComments(db.DefaultContext, issue, user, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, res, "README.md")
|
||||
assert.Contains(t, res["README.md"], int64(4))
|
||||
|
@ -58,7 +58,7 @@ func TestFetchCodeComments(t *testing.T) {
|
|||
assert.Equal(t, int64(4), res["README.md"][4][0].ID)
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2)
|
||||
res, err = issues_model.FetchCodeComments(db.DefaultContext, issue, user2, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, res, 1)
|
||||
}
|
||||
|
|
|
@ -141,7 +141,7 @@ func (r *Review) LoadCodeComments(ctx context.Context) (err error) {
|
|||
if err = r.loadIssue(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r)
|
||||
r.CodeComments, err = fetchCodeCommentsByReview(ctx, r.Issue, nil, r, false)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -199,8 +199,8 @@ func addTime(ctx context.Context, user *user_model.User, issue *Issue, amount in
|
|||
return tt, db.Insert(ctx, tt)
|
||||
}
|
||||
|
||||
// TotalTimes returns the spent time for each user by an issue
|
||||
func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]string, error) {
|
||||
// TotalTimes returns the spent time in seconds for each user by an issue
|
||||
func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]int64, error) {
|
||||
trackedTimes, err := GetTrackedTimes(db.DefaultContext, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -211,7 +211,7 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]string,
|
|||
totalTimesByUser[t.UserID] += t.Time
|
||||
}
|
||||
|
||||
totalTimes := make(map[*user_model.User]string)
|
||||
totalTimes := make(map[*user_model.User]int64)
|
||||
// Fetching User and making time human readable
|
||||
for userID, total := range totalTimesByUser {
|
||||
user, err := user_model.GetUserByID(db.DefaultContext, userID)
|
||||
|
@ -221,12 +221,7 @@ func TotalTimes(options *FindTrackedTimesOptions) (map[*user_model.User]string,
|
|||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if total < 60 {
|
||||
totalTimes[user] = util.SecToTimeExact(total, true)
|
||||
} else {
|
||||
totalTimes[user] = util.SecToTimeExact(total, false)
|
||||
}
|
||||
totalTimes[user] = total
|
||||
}
|
||||
return totalTimes, nil
|
||||
}
|
||||
|
|
|
@ -86,8 +86,8 @@ func TestTotalTimes(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, total, 1)
|
||||
for user, time := range total {
|
||||
assert.Equal(t, int64(1), user.ID)
|
||||
assert.Equal(t, "6 minutes", time)
|
||||
assert.EqualValues(t, 1, user.ID)
|
||||
assert.EqualValues(t, 400, time)
|
||||
}
|
||||
|
||||
total, err = issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 2})
|
||||
|
@ -95,9 +95,9 @@ func TestTotalTimes(t *testing.T) {
|
|||
assert.Len(t, total, 2)
|
||||
for user, time := range total {
|
||||
if user.ID == 2 {
|
||||
assert.Equal(t, "1 hour 1 minute", time)
|
||||
assert.EqualValues(t, 3662, time)
|
||||
} else if user.ID == 1 {
|
||||
assert.Equal(t, "20 seconds", time)
|
||||
assert.EqualValues(t, 20, time)
|
||||
} else {
|
||||
assert.Error(t, assert.AnError)
|
||||
}
|
||||
|
@ -107,8 +107,8 @@ func TestTotalTimes(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Len(t, total, 1)
|
||||
for user, time := range total {
|
||||
assert.Equal(t, int64(2), user.ID)
|
||||
assert.Equal(t, "1 second", time)
|
||||
assert.EqualValues(t, 2, user.ID)
|
||||
assert.EqualValues(t, 1, time)
|
||||
}
|
||||
|
||||
total, err = issues_model.TotalTimes(&issues_model.FindTrackedTimesOptions{IssueID: 4})
|
||||
|
|
|
@ -147,9 +147,9 @@ func MainTest(m *testing.M) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||
setting.AppDataPath = tmpDataPath
|
||||
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
unittest.InitSettings()
|
||||
if err = git.InitFull(context.Background()); err != nil {
|
||||
fmt.Printf("Unable to InitFull: %v\n", err)
|
||||
|
|
|
@ -503,7 +503,11 @@ var migrations = []Migration{
|
|||
|
||||
// v260 -> v261
|
||||
NewMigration("Drop custom_labels column of action_runner table", v1_21.DropCustomLabelsColumnOfActionRunner),
|
||||
|
||||
// v261 -> v262
|
||||
NewMigration("Add variable table", v1_21.CreateVariableTable),
|
||||
|
||||
// v262 -> v263
|
||||
NewMigration("Add TimeEstimate to issue table", v1_21.AddTimeEstimateColumnToIssueTable),
|
||||
NewMigration("Add TimeTracked, TimeEstimate to comment table", v1_21.AddColumnsToCommentTable),
|
||||
}
|
||||
|
|
|
@ -4,22 +4,21 @@
|
|||
package v1_21 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
|
||||
type Issue struct {
|
||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
func CreateVariableTable(x *xorm.Engine) error {
|
||||
type ActionVariable struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(owner_repo_name)"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(owner_repo_name)"`
|
||||
Name string `xorm:"UNIQUE(owner_repo_name) NOT NULL"`
|
||||
Data string `xorm:"LONGTEXT NOT NULL"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Issue))
|
||||
}
|
||||
|
||||
func AddColumnsToCommentTable(x *xorm.Engine) error {
|
||||
type Comment struct {
|
||||
TimeTracked int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Comment))
|
||||
return x.Sync(new(ActionVariable))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_21 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func AddTimeEstimateColumnToIssueTable(x *xorm.Engine) error {
|
||||
type Issue struct {
|
||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Issue))
|
||||
}
|
||||
|
||||
func AddColumnsToCommentTable(x *xorm.Engine) error {
|
||||
type Comment struct {
|
||||
TimeTracked int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
TimeEstimate int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
return x.Sync(new(Comment))
|
||||
}
|
|
@ -5,38 +5,17 @@ package secret
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
secret_module "code.gitea.io/gitea/modules/secret"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
type ErrSecretInvalidValue struct {
|
||||
Name *string
|
||||
Data *string
|
||||
}
|
||||
|
||||
func (err ErrSecretInvalidValue) Error() string {
|
||||
if err.Name != nil {
|
||||
return fmt.Sprintf("secret name %q is invalid", *err.Name)
|
||||
}
|
||||
if err.Data != nil {
|
||||
return fmt.Sprintf("secret data %q is invalid", *err.Data)
|
||||
}
|
||||
return util.ErrInvalidArgument.Error()
|
||||
}
|
||||
|
||||
func (err ErrSecretInvalidValue) Unwrap() error {
|
||||
return util.ErrInvalidArgument
|
||||
}
|
||||
|
||||
// Secret represents a secret
|
||||
type Secret struct {
|
||||
ID int64
|
||||
|
@ -74,24 +53,11 @@ func init() {
|
|||
db.RegisterModel(new(Secret))
|
||||
}
|
||||
|
||||
var (
|
||||
secretNameReg = regexp.MustCompile("^[A-Z_][A-Z0-9_]*$")
|
||||
forbiddenSecretPrefixReg = regexp.MustCompile("^GIT(EA|HUB)_")
|
||||
)
|
||||
|
||||
// Validate validates the required fields and formats.
|
||||
func (s *Secret) Validate() error {
|
||||
switch {
|
||||
case len(s.Name) == 0 || len(s.Name) > 50:
|
||||
return ErrSecretInvalidValue{Name: &s.Name}
|
||||
case len(s.Data) == 0:
|
||||
return ErrSecretInvalidValue{Data: &s.Data}
|
||||
case !secretNameReg.MatchString(s.Name) ||
|
||||
forbiddenSecretPrefixReg.MatchString(s.Name):
|
||||
return ErrSecretInvalidValue{Name: &s.Name}
|
||||
default:
|
||||
return nil
|
||||
if s.OwnerID == 0 && s.RepoID == 0 {
|
||||
return errors.New("the secret is not bound to any scope")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FindSecretsOptions struct {
|
||||
|
|
|
@ -42,12 +42,14 @@ func fatalTestError(fmtStr string, args ...interface{}) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// InitSettings initializes config provider and load common setttings for tests
|
||||
// InitSettings initializes config provider and load common settings for tests
|
||||
func InitSettings(extraConfigs ...string) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
ExtraConfig: strings.Join(extraConfigs, "\n"),
|
||||
})
|
||||
if setting.CustomConf == "" {
|
||||
setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
|
||||
_ = os.Remove(setting.CustomConf)
|
||||
}
|
||||
setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n"))
|
||||
setting.LoadCommonSettings()
|
||||
|
||||
if err := setting.PrepareAppDataPath(); err != nil {
|
||||
log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
|
||||
|
@ -69,7 +71,7 @@ type TestOptions struct {
|
|||
// MainTest a reusable TestMain(..) function for unit tests that need to use a
|
||||
// test database. Creates the test database, and sets necessary settings.
|
||||
func MainTest(m *testing.M, testOpts *TestOptions) {
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom")
|
||||
InitSettings()
|
||||
|
||||
var err error
|
||||
|
|
|
@ -8,6 +8,8 @@ const (
|
|||
SettingsKeyHiddenCommentTypes = "issue.hidden_comment_types"
|
||||
// SettingsKeyDiffWhitespaceBehavior is the setting key for whitespace behavior of diff
|
||||
SettingsKeyDiffWhitespaceBehavior = "diff.whitespace_behaviour"
|
||||
// SettingsKeyShowOutdatedComments is the setting key wether or not to show outdated comments in PRs
|
||||
SettingsKeyShowOutdatedComments = "comment_code.show_outdated"
|
||||
// UserActivityPubPrivPem is user's private key
|
||||
UserActivityPubPrivPem = "activitypub.priv_pem"
|
||||
// UserActivityPubPubPem is user's public key
|
||||
|
|
|
@ -210,7 +210,7 @@ func EntryIcon(entry *git.TreeEntry) string {
|
|||
return "file-symlink-file"
|
||||
}
|
||||
if te.IsDir() {
|
||||
return "file-submodule"
|
||||
return "file-directory-symlink"
|
||||
}
|
||||
return "file-symlink-file"
|
||||
case entry.IsDir():
|
||||
|
|
|
@ -28,7 +28,7 @@ type Check struct {
|
|||
}
|
||||
|
||||
func initDBSkipLogger(ctx context.Context) error {
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
if err := db.InitEngine(ctx); err != nil {
|
||||
return fmt.Errorf("db.InitEngine: %w", err)
|
||||
|
|
|
@ -66,7 +66,7 @@ func checkConfigurationFiles(ctx context.Context, logger log.Logger, autofix boo
|
|||
return err
|
||||
}
|
||||
|
||||
setting.Init(&setting.Options{})
|
||||
setting.MustInstalled()
|
||||
|
||||
configurationFiles := []configurationFile{
|
||||
{"Configuration File Path", setting.CustomConf, false, true, false},
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/emoji"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -28,9 +29,7 @@ var localMetas = map[string]string{
|
|||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
unittest.InitSettings()
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
|
@ -33,9 +34,7 @@ var localMetas = map[string]string{
|
|||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
unittest.InitSettings()
|
||||
if err := git.InitSimple(context.Background()); err != nil {
|
||||
log.Fatal("git init failed, err: %v", err)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -48,7 +49,7 @@ var defaultTransformers = []transformer{
|
|||
{Name: "TITLE", Transform: util.ToTitleCase},
|
||||
}
|
||||
|
||||
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository) string {
|
||||
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository, sanitizeFileName bool) string {
|
||||
expansions := []expansion{
|
||||
{Name: "REPO_NAME", Value: generateRepo.Name, Transformers: defaultTransformers},
|
||||
{Name: "TEMPLATE_NAME", Value: templateRepo.Name, Transformers: defaultTransformers},
|
||||
|
@ -74,6 +75,9 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi
|
|||
|
||||
return os.Expand(src, func(key string) string {
|
||||
if expansion, ok := expansionMap[key]; ok {
|
||||
if sanitizeFileName {
|
||||
return fileNameSanitize(expansion)
|
||||
}
|
||||
return expansion
|
||||
}
|
||||
return key
|
||||
|
@ -191,10 +195,24 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
|||
}
|
||||
|
||||
if err := os.WriteFile(path,
|
||||
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
|
||||
[]byte(generateExpansion(string(content), templateRepo, generateRepo, false)),
|
||||
0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
substPath := filepath.FromSlash(filepath.Join(tmpDirSlash,
|
||||
generateExpansion(base, templateRepo, generateRepo, true)))
|
||||
|
||||
// Create parent subdirectories if needed or continue silently if it exists
|
||||
if err := os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Substitute filename variables
|
||||
if err := os.Rename(path, substPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -353,3 +371,13 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
|
|||
|
||||
return generateRepo, nil
|
||||
}
|
||||
|
||||
var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`)
|
||||
|
||||
// Sanitize user input to valid OS filenames
|
||||
//
|
||||
// Based on https://github.com/sindresorhus/filename-reserved-regex
|
||||
// Adds ".." to prevent directory traversal
|
||||
func fileNameSanitize(s string) string {
|
||||
return strings.TrimSpace(fileNameSanitizeRegexp.ReplaceAllString(s, "_"))
|
||||
}
|
||||
|
|
|
@ -54,3 +54,14 @@ func TestGiteaTemplate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileNameSanitize(t *testing.T) {
|
||||
assert.Equal(t, "test_CON", fileNameSanitize("test_CON"))
|
||||
assert.Equal(t, "test CON", fileNameSanitize("test CON "))
|
||||
assert.Equal(t, "__traverse__", fileNameSanitize("../traverse/.."))
|
||||
assert.Equal(t, "http___localhost_3003_user_test.git", fileNameSanitize("http://localhost:3003/user/test.git"))
|
||||
assert.Equal(t, "_", fileNameSanitize("CON"))
|
||||
assert.Equal(t, "_", fileNameSanitize("con"))
|
||||
assert.Equal(t, "_", fileNameSanitize("\u0000"))
|
||||
assert.Equal(t, "目标", fileNameSanitize("目标"))
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -51,12 +52,18 @@ type ConfigProvider interface {
|
|||
GetSection(name string) (ConfigSection, error)
|
||||
Save() error
|
||||
SaveTo(filename string) error
|
||||
|
||||
DisableSaving()
|
||||
PrepareSaving() (ConfigProvider, error)
|
||||
IsLoadedFromEmpty() bool
|
||||
}
|
||||
|
||||
type iniConfigProvider struct {
|
||||
opts *Options
|
||||
ini *ini.File
|
||||
newFile bool // whether the file has not existed previously
|
||||
file string
|
||||
ini *ini.File
|
||||
|
||||
disableSaving bool // disable the "Save" method because the config options could be polluted
|
||||
loadedFromEmpty bool // whether the file has not existed previously
|
||||
}
|
||||
|
||||
type iniConfigSection struct {
|
||||
|
@ -175,53 +182,43 @@ func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
|
|||
}
|
||||
cfg.NameMapper = ini.SnackCase
|
||||
return &iniConfigProvider{
|
||||
ini: cfg,
|
||||
newFile: true,
|
||||
ini: cfg,
|
||||
loadedFromEmpty: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
CustomConf string // the ini file path
|
||||
AllowEmpty bool // whether not finding configuration files is allowed
|
||||
ExtraConfig string
|
||||
|
||||
DisableLoadCommonSettings bool // only used by "Init()", not used by "NewConfigProvider()"
|
||||
}
|
||||
|
||||
// NewConfigProviderFromFile load configuration from file.
|
||||
// NOTE: do not print any log except error.
|
||||
func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) {
|
||||
cfg := ini.Empty()
|
||||
newFile := true
|
||||
func NewConfigProviderFromFile(file string, extraConfigs ...string) (ConfigProvider, error) {
|
||||
cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
|
||||
loadedFromEmpty := true
|
||||
|
||||
if opts.CustomConf != "" {
|
||||
isFile, err := util.IsFile(opts.CustomConf)
|
||||
if file != "" {
|
||||
isFile, err := util.IsFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to check if %s is a file. Error: %v", opts.CustomConf, err)
|
||||
return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
|
||||
}
|
||||
if isFile {
|
||||
if err := cfg.Append(opts.CustomConf); err != nil {
|
||||
return nil, fmt.Errorf("failed to load custom conf '%s': %v", opts.CustomConf, err)
|
||||
if err = cfg.Append(file); err != nil {
|
||||
return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
|
||||
}
|
||||
newFile = false
|
||||
loadedFromEmpty = false
|
||||
}
|
||||
}
|
||||
|
||||
if newFile && !opts.AllowEmpty {
|
||||
return nil, fmt.Errorf("unable to find configuration file: %q, please ensure you are running in the correct environment or set the correct configuration file with -c", CustomConf)
|
||||
}
|
||||
|
||||
if opts.ExtraConfig != "" {
|
||||
if err := cfg.Append([]byte(opts.ExtraConfig)); err != nil {
|
||||
return nil, fmt.Errorf("unable to append more config: %v", err)
|
||||
if len(extraConfigs) > 0 {
|
||||
for _, s := range extraConfigs {
|
||||
if err := cfg.Append([]byte(s)); err != nil {
|
||||
return nil, fmt.Errorf("unable to append more config: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg.NameMapper = ini.SnackCase
|
||||
return &iniConfigProvider{
|
||||
opts: opts,
|
||||
ini: cfg,
|
||||
newFile: newFile,
|
||||
file: file,
|
||||
ini: cfg,
|
||||
loadedFromEmpty: loadedFromEmpty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -252,22 +249,24 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
|
|||
return &iniConfigSection{sec: sec}, nil
|
||||
}
|
||||
|
||||
var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
|
||||
|
||||
// Save saves the content into file
|
||||
func (p *iniConfigProvider) Save() error {
|
||||
filename := p.opts.CustomConf
|
||||
if filename == "" {
|
||||
if !p.opts.AllowEmpty {
|
||||
return fmt.Errorf("custom config path must not be empty")
|
||||
}
|
||||
return nil
|
||||
if p.disableSaving {
|
||||
return errDisableSaving
|
||||
}
|
||||
if p.newFile {
|
||||
filename := p.file
|
||||
if filename == "" {
|
||||
return fmt.Errorf("config file path must not be empty")
|
||||
}
|
||||
if p.loadedFromEmpty {
|
||||
if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create '%s': %v", filename, err)
|
||||
return fmt.Errorf("failed to create %q: %v", filename, err)
|
||||
}
|
||||
}
|
||||
if err := p.ini.SaveTo(filename); err != nil {
|
||||
return fmt.Errorf("failed to save '%s': %v", filename, err)
|
||||
return fmt.Errorf("failed to save %q: %v", filename, err)
|
||||
}
|
||||
|
||||
// Change permissions to be more restrictive
|
||||
|
@ -285,9 +284,32 @@ func (p *iniConfigProvider) Save() error {
|
|||
}
|
||||
|
||||
func (p *iniConfigProvider) SaveTo(filename string) error {
|
||||
if p.disableSaving {
|
||||
return errDisableSaving
|
||||
}
|
||||
return p.ini.SaveTo(filename)
|
||||
}
|
||||
|
||||
// DisableSaving disables the saving function, use PrepareSaving to get clear config options.
|
||||
func (p *iniConfigProvider) DisableSaving() {
|
||||
p.disableSaving = true
|
||||
}
|
||||
|
||||
// PrepareSaving loads the ini from file again to get clear config options.
|
||||
// Otherwise, the "MustXxx" calls would have polluted the current config provider,
|
||||
// it makes the "Save" outputs a lot of garbage options
|
||||
// After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped.
|
||||
func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) {
|
||||
if p.file == "" {
|
||||
return nil, errors.New("no config file to save")
|
||||
}
|
||||
return NewConfigProviderFromFile(p.file)
|
||||
}
|
||||
|
||||
func (p *iniConfigProvider) IsLoadedFromEmpty() bool {
|
||||
return p.loadedFromEmpty
|
||||
}
|
||||
|
||||
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
|
||||
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
|
||||
log.Fatal("Failed to map %s settings: %v", sectionName, err)
|
||||
|
@ -324,8 +346,8 @@ func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, erro
|
|||
}
|
||||
iniFile.BlockMode = false
|
||||
return &iniConfigProvider{
|
||||
ini: iniFile,
|
||||
newFile: true,
|
||||
ini: iniFile,
|
||||
loadedFromEmpty: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -67,13 +67,14 @@ key = 123
|
|||
}
|
||||
|
||||
func TestNewConfigProviderFromFile(t *testing.T) {
|
||||
_, err := NewConfigProviderFromFile(&Options{CustomConf: "no-such.ini", AllowEmpty: false})
|
||||
assert.ErrorContains(t, err, "unable to find configuration file")
|
||||
cfg, err := NewConfigProviderFromFile("no-such.ini")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, cfg.IsLoadedFromEmpty())
|
||||
|
||||
// load non-existing file and save
|
||||
testFile := t.TempDir() + "/test.ini"
|
||||
testFile1 := t.TempDir() + "/test1.ini"
|
||||
cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
||||
cfg, err = NewConfigProviderFromFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
sec, _ := cfg.NewSection("foo")
|
||||
|
@ -84,14 +85,14 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
|||
|
||||
bs, err := os.ReadFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "[foo]\nk1=a\n", string(bs))
|
||||
assert.Equal(t, "[foo]\nk1 = a\n", string(bs))
|
||||
|
||||
bs, err = os.ReadFile(testFile1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "[foo]\nk1=a\nk2=b\n", string(bs))
|
||||
assert.Equal(t, "[foo]\nk1 = a\nk2 = b\n", string(bs))
|
||||
|
||||
// load existing file and save
|
||||
cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
|
||||
cfg, err = NewConfigProviderFromFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "a", cfg.Section("foo").Key("k1").String())
|
||||
sec, _ = cfg.NewSection("bar")
|
||||
|
@ -99,7 +100,7 @@ func TestNewConfigProviderFromFile(t *testing.T) {
|
|||
assert.NoError(t, cfg.Save())
|
||||
bs, err = os.ReadFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "[foo]\nk1=a\n\n[bar]\nk1=b\n", string(bs))
|
||||
assert.Equal(t, "[foo]\nk1 = a\n\n[bar]\nk1 = b\n", string(bs))
|
||||
}
|
||||
|
||||
func TestNewConfigProviderForLocale(t *testing.T) {
|
||||
|
@ -119,3 +120,27 @@ func TestNewConfigProviderForLocale(t *testing.T) {
|
|||
assert.Equal(t, "foo", cfg.Section("").Key("k1").String())
|
||||
assert.Equal(t, "xxx", cfg.Section("").Key("k2").String())
|
||||
}
|
||||
|
||||
func TestDisableSaving(t *testing.T) {
|
||||
testFile := t.TempDir() + "/test.ini"
|
||||
_ = os.WriteFile(testFile, []byte("k1=a\nk2=b"), 0o644)
|
||||
cfg, err := NewConfigProviderFromFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
cfg.DisableSaving()
|
||||
err = cfg.Save()
|
||||
assert.ErrorIs(t, err, errDisableSaving)
|
||||
|
||||
saveCfg, err := cfg.PrepareSaving()
|
||||
assert.NoError(t, err)
|
||||
|
||||
saveCfg.Section("").Key("k1").MustString("x")
|
||||
saveCfg.Section("").Key("k2").SetValue("y")
|
||||
saveCfg.Section("").Key("k3").SetValue("z")
|
||||
err = saveCfg.Save()
|
||||
assert.NoError(t, err)
|
||||
|
||||
bs, err := os.ReadFile(testFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "k1 = a\nk2 = y\nk3 = z\n", string(bs))
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -36,7 +34,7 @@ var (
|
|||
SSLMode string
|
||||
Path string
|
||||
LogSQL bool
|
||||
Charset string
|
||||
MysqlCharset string
|
||||
Timeout int // seconds
|
||||
SQLiteJournalMode string
|
||||
DBConnectRetries int
|
||||
|
@ -60,11 +58,6 @@ func LoadDBSetting() {
|
|||
func loadDBSetting(rootCfg ConfigProvider) {
|
||||
sec := rootCfg.Section("database")
|
||||
Database.Type = DatabaseType(sec.Key("DB_TYPE").String())
|
||||
defaultCharset := "utf8"
|
||||
|
||||
if Database.Type.IsMySQL() {
|
||||
defaultCharset = "utf8mb4"
|
||||
}
|
||||
|
||||
Database.Host = sec.Key("HOST").String()
|
||||
Database.Name = sec.Key("NAME").String()
|
||||
|
@ -74,10 +67,7 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||
}
|
||||
Database.Schema = sec.Key("SCHEMA").String()
|
||||
Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
|
||||
Database.Charset = sec.Key("CHARSET").In(defaultCharset, []string{"utf8", "utf8mb4"})
|
||||
if Database.Type.IsMySQL() && defaultCharset != "utf8mb4" {
|
||||
log.Error("Deprecated database mysql charset utf8 support, please use utf8mb4 or convert utf8 to utf8mb4.")
|
||||
}
|
||||
Database.MysqlCharset = sec.Key("MYSQL_CHARSET").MustString("utf8mb4") // do not document it, end users won't need it.
|
||||
|
||||
Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
|
||||
Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
|
||||
|
@ -101,9 +91,9 @@ func loadDBSetting(rootCfg ConfigProvider) {
|
|||
// DBConnStr returns database connection string
|
||||
func DBConnStr() (string, error) {
|
||||
var connStr string
|
||||
Param := "?"
|
||||
if strings.Contains(Database.Name, Param) {
|
||||
Param = "&"
|
||||
paramSep := "?"
|
||||
if strings.Contains(Database.Name, paramSep) {
|
||||
paramSep = "&"
|
||||
}
|
||||
switch Database.Type {
|
||||
case "mysql":
|
||||
|
@ -116,15 +106,15 @@ func DBConnStr() (string, error) {
|
|||
tls = "false"
|
||||
}
|
||||
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
|
||||
Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls)
|
||||
Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, Database.MysqlCharset, tls)
|
||||
case "postgres":
|
||||
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode)
|
||||
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, paramSep, Database.SSLMode)
|
||||
case "mssql":
|
||||
host, port := ParseMSSQLHostPort(Database.Host)
|
||||
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
|
||||
case "sqlite3":
|
||||
if !EnableSQLite3 {
|
||||
return "", errors.New("this binary version does not build support for SQLite3")
|
||||
return "", errors.New("this Gitea binary was not built with SQLite3 support")
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
|
||||
return "", fmt.Errorf("Failed to create directories: %w", err)
|
||||
|
@ -136,7 +126,7 @@ func DBConnStr() (string, error) {
|
|||
connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
|
||||
Database.Path, Database.Timeout, journalMode)
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown database type: %s", Database.Type)
|
||||
return "", fmt.Errorf("unknown database type: %s", Database.Type)
|
||||
}
|
||||
|
||||
return connStr, nil
|
||||
|
|
|
@ -53,19 +53,26 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
LFS.JWTSecretBase64 = loadSecret(rootCfg.Section("lfs"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
|
||||
|
||||
LFS.JWTSecretBytes = make([]byte, 32)
|
||||
n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
|
||||
|
||||
if err != nil || n != 32 {
|
||||
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error generating JWT Secret for custom config: %v", err)
|
||||
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
|
||||
}
|
||||
|
||||
// Save secret
|
||||
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||
if err := rootCfg.Save(); err != nil {
|
||||
return fmt.Errorf("Error saving JWT Secret for custom config: %v", err)
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||
}
|
||||
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
|
||||
if err := saveCfg.Save(); err != nil {
|
||||
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,6 +116,8 @@ func loadOAuth2From(rootCfg ConfigProvider) {
|
|||
return
|
||||
}
|
||||
|
||||
OAuth2.JWTSecretBase64 = loadSecret(rootCfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET")
|
||||
|
||||
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
|
||||
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
|
||||
}
|
||||
|
@ -130,8 +132,13 @@ func loadOAuth2From(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
||||
}
|
||||
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
||||
if err := rootCfg.Save(); err != nil {
|
||||
saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
|
||||
if err := saveCfg.Save(); err != nil {
|
||||
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// AppPath represents the path to the gitea binary
|
||||
AppPath string
|
||||
|
||||
// AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR.
|
||||
// If that is not set it is the default set here by the linker or failing that the directory of AppPath.
|
||||
// It is used as the base path for several other paths.
|
||||
AppWorkPath string
|
||||
CustomPath string // Custom directory path. Env: GITEA_CUSTOM
|
||||
CustomConf string
|
||||
|
||||
appWorkPathBuiltin string
|
||||
customPathBuiltin string
|
||||
customConfBuiltin string
|
||||
|
||||
AppWorkPathMismatch bool
|
||||
)
|
||||
|
||||
func getAppPath() (string, error) {
|
||||
var appPath string
|
||||
var err error
|
||||
if IsWindows && filepath.IsAbs(os.Args[0]) {
|
||||
appPath = filepath.Clean(os.Args[0])
|
||||
} else {
|
||||
appPath, err = exec.LookPath(os.Args[0])
|
||||
}
|
||||
if err != nil {
|
||||
if !errors.Is(err, exec.ErrDot) {
|
||||
return "", err
|
||||
}
|
||||
appPath, err = filepath.Abs(os.Args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
appPath, err = filepath.Abs(appPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note: (legacy code) we don't use path.Dir here because it does not handle case which path starts with two "/" in Windows: "//psf/Home/..."
|
||||
return strings.ReplaceAll(appPath, "\\", "/"), err
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
if AppPath, err = getAppPath(); err != nil {
|
||||
log.Fatal("Failed to get app path: %v", err)
|
||||
}
|
||||
|
||||
if AppWorkPath == "" {
|
||||
AppWorkPath = filepath.Dir(AppPath)
|
||||
}
|
||||
|
||||
appWorkPathBuiltin = AppWorkPath
|
||||
customPathBuiltin = CustomPath
|
||||
customConfBuiltin = CustomConf
|
||||
}
|
||||
|
||||
type ArgWorkPathAndCustomConf struct {
|
||||
WorkPath string
|
||||
CustomPath string
|
||||
CustomConf string
|
||||
}
|
||||
|
||||
type stringWithDefault struct {
|
||||
Value string
|
||||
IsSet bool
|
||||
}
|
||||
|
||||
func (s *stringWithDefault) Set(v string) {
|
||||
s.Value = v
|
||||
s.IsSet = true
|
||||
}
|
||||
|
||||
// InitWorkPathAndCommonConfig will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf and load common settings,
|
||||
func InitWorkPathAndCommonConfig(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) {
|
||||
tryAbsPath := func(paths ...string) string {
|
||||
s := paths[len(paths)-1]
|
||||
for i := len(paths) - 2; i >= 0; i-- {
|
||||
if filepath.IsAbs(s) {
|
||||
break
|
||||
}
|
||||
s = filepath.Join(paths[i], s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var err error
|
||||
tmpWorkPath := stringWithDefault{Value: appWorkPathBuiltin}
|
||||
if tmpWorkPath.Value == "" {
|
||||
tmpWorkPath.Value = filepath.Dir(AppPath)
|
||||
}
|
||||
tmpCustomPath := stringWithDefault{Value: customPathBuiltin}
|
||||
if tmpCustomPath.Value == "" {
|
||||
tmpCustomPath.Value = "custom"
|
||||
}
|
||||
tmpCustomConf := stringWithDefault{Value: customConfBuiltin}
|
||||
if tmpCustomConf.Value == "" {
|
||||
tmpCustomConf.Value = "conf/app.ini"
|
||||
}
|
||||
|
||||
readFromEnv := func() {
|
||||
envWorkPath := getEnvFn("GITEA_WORK_DIR")
|
||||
if envWorkPath != "" {
|
||||
tmpWorkPath.Set(envWorkPath)
|
||||
if !filepath.IsAbs(tmpWorkPath.Value) {
|
||||
log.Fatal("GITEA_WORK_DIR (work path) must be absolute path")
|
||||
}
|
||||
}
|
||||
|
||||
envCustomPath := getEnvFn("GITEA_CUSTOM")
|
||||
if envCustomPath != "" {
|
||||
tmpCustomPath.Set(envCustomPath)
|
||||
if !filepath.IsAbs(tmpCustomPath.Value) {
|
||||
log.Fatal("GITEA_CUSTOM (custom path) must be absolute path")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readFromArgs := func() {
|
||||
if args.WorkPath != "" {
|
||||
tmpWorkPath.Set(args.WorkPath)
|
||||
if !filepath.IsAbs(tmpWorkPath.Value) {
|
||||
log.Fatal("--work-path must be absolute path")
|
||||
}
|
||||
}
|
||||
if args.CustomPath != "" {
|
||||
tmpCustomPath.Set(args.CustomPath) // if it is not abs, it will be based on work-path, it shouldn't happen
|
||||
if !filepath.IsAbs(tmpCustomPath.Value) {
|
||||
log.Error("--custom-path must be absolute path")
|
||||
}
|
||||
}
|
||||
if args.CustomConf != "" {
|
||||
tmpCustomConf.Set(args.CustomConf)
|
||||
if !filepath.IsAbs(tmpCustomConf.Value) {
|
||||
// the config path can be relative to the real current working path
|
||||
if tmpCustomConf.Value, err = filepath.Abs(tmpCustomConf.Value); err != nil {
|
||||
log.Fatal("Failed to get absolute path of config %q: %v", tmpCustomConf.Value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readFromEnv()
|
||||
readFromArgs()
|
||||
|
||||
if !tmpCustomConf.IsSet {
|
||||
tmpCustomConf.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value, tmpCustomConf.Value))
|
||||
}
|
||||
|
||||
// only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready
|
||||
InitCfgProvider(tmpCustomConf.Value)
|
||||
configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH")
|
||||
if configWorkPath != "" {
|
||||
if !filepath.IsAbs(configWorkPath) {
|
||||
log.Fatal("WORK_PATH in %q must be absolute path", configWorkPath)
|
||||
}
|
||||
configWorkPath = filepath.Clean(configWorkPath)
|
||||
if tmpWorkPath.Value != "" && (getEnvFn("GITEA_WORK_DIR") != "" || args.WorkPath != "") {
|
||||
fi1, err1 := os.Stat(tmpWorkPath.Value)
|
||||
fi2, err2 := os.Stat(configWorkPath)
|
||||
if err1 != nil || err2 != nil || !os.SameFile(fi1, fi2) {
|
||||
AppWorkPathMismatch = true
|
||||
}
|
||||
}
|
||||
tmpWorkPath.Set(configWorkPath)
|
||||
}
|
||||
|
||||
tmpCustomPath.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value))
|
||||
|
||||
AppWorkPath = tmpWorkPath.Value
|
||||
CustomPath = tmpCustomPath.Value
|
||||
CustomConf = tmpCustomConf.Value
|
||||
|
||||
LoadCommonSettings()
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package setting
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type envVars map[string]string
|
||||
|
||||
func (e envVars) Getenv(key string) string {
|
||||
return e[key]
|
||||
}
|
||||
|
||||
func TestInitWorkPathAndCommonConfig(t *testing.T) {
|
||||
testInit := func(defaultWorkPath, defaultCustomPath, defaultCustomConf string) {
|
||||
AppWorkPathMismatch = false
|
||||
AppWorkPath = defaultWorkPath
|
||||
appWorkPathBuiltin = defaultWorkPath
|
||||
CustomPath = defaultCustomPath
|
||||
customPathBuiltin = defaultCustomPath
|
||||
CustomConf = defaultCustomConf
|
||||
customConfBuiltin = defaultCustomConf
|
||||
}
|
||||
|
||||
fp := filepath.Join
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
dirFoo := fp(tmpDir, "foo")
|
||||
dirBar := fp(tmpDir, "bar")
|
||||
dirXxx := fp(tmpDir, "xxx")
|
||||
dirYyy := fp(tmpDir, "yyy")
|
||||
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom"), CustomPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom/conf/app.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("WorkDir(env)", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirBar, AppWorkPath)
|
||||
assert.Equal(t, fp(dirBar, "custom"), CustomPath)
|
||||
assert.Equal(t, fp(dirBar, "custom/conf/app.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("WorkDir(env,arg)", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirXxx})
|
||||
assert.Equal(t, dirXxx, AppWorkPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom/conf/app.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("CustomPath(env)", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, fp(dirBar, "custom1"), CustomPath)
|
||||
assert.Equal(t, fp(dirBar, "custom1/conf/app.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("CustomPath(env,arg)", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": fp(dirBar, "custom1")}.Getenv, ArgWorkPathAndCustomConf{CustomPath: "custom2"})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom2"), CustomPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom2/conf/app.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("CustomConf", func(t *testing.T) {
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: "app1.ini"})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
cwd, _ := os.Getwd()
|
||||
assert.Equal(t, fp(cwd, "app1.ini"), CustomConf)
|
||||
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: fp(dirBar, "app1.ini")})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, fp(dirBar, "app1.ini"), CustomConf)
|
||||
})
|
||||
|
||||
t.Run("CustomConfOverrideWorkPath", func(t *testing.T) {
|
||||
iniWorkPath := fp(tmpDir, "app-workpath.ini")
|
||||
_ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644)
|
||||
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||
assert.Equal(t, dirXxx, AppWorkPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||
assert.Equal(t, iniWorkPath, CustomConf)
|
||||
assert.False(t, AppWorkPathMismatch)
|
||||
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirBar}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||
assert.Equal(t, dirXxx, AppWorkPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||
assert.Equal(t, iniWorkPath, CustomConf)
|
||||
assert.True(t, AppWorkPathMismatch)
|
||||
|
||||
testInit(dirFoo, "", "")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{WorkPath: dirBar, CustomConf: iniWorkPath})
|
||||
assert.Equal(t, dirXxx, AppWorkPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom"), CustomPath)
|
||||
assert.Equal(t, iniWorkPath, CustomConf)
|
||||
assert.True(t, AppWorkPathMismatch)
|
||||
})
|
||||
|
||||
t.Run("Builtin", func(t *testing.T) {
|
||||
testInit(dirFoo, dirBar, dirXxx)
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, dirBar, CustomPath)
|
||||
assert.Equal(t, dirXxx, CustomConf)
|
||||
|
||||
testInit(dirFoo, "custom1", "cfg.ini")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom1"), CustomPath)
|
||||
assert.Equal(t, fp(dirFoo, "custom1/cfg.ini"), CustomConf)
|
||||
|
||||
testInit(dirFoo, "custom1", "cfg.ini")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_WORK_DIR": dirYyy}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirYyy, AppWorkPath)
|
||||
assert.Equal(t, fp(dirYyy, "custom1"), CustomPath)
|
||||
assert.Equal(t, fp(dirYyy, "custom1/cfg.ini"), CustomConf)
|
||||
|
||||
testInit(dirFoo, "custom1", "cfg.ini")
|
||||
InitWorkPathAndCommonConfig(envVars{"GITEA_CUSTOM": dirYyy}.Getenv, ArgWorkPathAndCustomConf{})
|
||||
assert.Equal(t, dirFoo, AppWorkPath)
|
||||
assert.Equal(t, dirYyy, CustomPath)
|
||||
assert.Equal(t, fp(dirYyy, "cfg.ini"), CustomConf)
|
||||
|
||||
iniWorkPath := fp(tmpDir, "app-workpath.ini")
|
||||
_ = os.WriteFile(iniWorkPath, []byte("WORK_PATH="+dirXxx), 0o644)
|
||||
testInit(dirFoo, "custom1", "cfg.ini")
|
||||
InitWorkPathAndCommonConfig(envVars{}.Getenv, ArgWorkPathAndCustomConf{CustomConf: iniWorkPath})
|
||||
assert.Equal(t, dirXxx, AppWorkPath)
|
||||
assert.Equal(t, fp(dirXxx, "custom1"), CustomPath)
|
||||
assert.Equal(t, iniWorkPath, CustomConf)
|
||||
})
|
||||
}
|
|
@ -76,7 +76,7 @@ func loadSecret(sec ConfigSection, uriKey, verbatimKey string) string {
|
|||
|
||||
// only file URIs are allowed
|
||||
default:
|
||||
log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri)
|
||||
log.Fatal("Unsupported URI-Scheme %q (%q = %q)", tempURI.Scheme, uriKey, uri)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -89,8 +89,13 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
InternalToken = token
|
||||
saveCfg, err := rootCfg.PrepareSaving()
|
||||
if err != nil {
|
||||
log.Fatal("Error saving internal token: %v", err)
|
||||
}
|
||||
rootCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
|
||||
if err := rootCfg.Save(); err != nil {
|
||||
saveCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
|
||||
if err = saveCfg.Save(); err != nil {
|
||||
log.Fatal("Error saving internal token: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ var (
|
|||
AssetVersion string
|
||||
|
||||
// Server settings
|
||||
|
||||
Protocol Scheme
|
||||
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
||||
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
||||
|
@ -324,7 +325,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||
StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
|
||||
AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data"))
|
||||
if !filepath.IsAbs(AppDataPath) {
|
||||
log.Info("The provided APP_DATA_PATH: %s is not absolute - it will be made absolute against the work path: %s", AppDataPath, AppWorkPath)
|
||||
AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
|
||||
}
|
||||
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -28,19 +24,9 @@ var (
|
|||
// AppStartTime store time gitea has started
|
||||
AppStartTime time.Time
|
||||
|
||||
// AppPath represents the path to the gitea binary
|
||||
AppPath string
|
||||
// AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR.
|
||||
// If that is not set it is the default set here by the linker or failing that the directory of AppPath.
|
||||
//
|
||||
// AppWorkPath is used as the base path for several other paths.
|
||||
AppWorkPath string
|
||||
|
||||
// Other global setting objects
|
||||
|
||||
CfgProvider ConfigProvider
|
||||
CustomPath string // Custom directory path
|
||||
CustomConf string
|
||||
RunMode string
|
||||
RunUser string
|
||||
IsProd bool
|
||||
|
@ -51,62 +37,6 @@ var (
|
|||
IsInTesting = false
|
||||
)
|
||||
|
||||
func getAppPath() (string, error) {
|
||||
var appPath string
|
||||
var err error
|
||||
if IsWindows && filepath.IsAbs(os.Args[0]) {
|
||||
appPath = filepath.Clean(os.Args[0])
|
||||
} else {
|
||||
appPath, err = exec.LookPath(os.Args[0])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, exec.ErrDot) {
|
||||
return "", err
|
||||
}
|
||||
appPath, err = filepath.Abs(os.Args[0])
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
appPath, err = filepath.Abs(appPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note: we don't use path.Dir here because it does not handle case
|
||||
// which path starts with two "/" in Windows: "//psf/Home/..."
|
||||
return strings.ReplaceAll(appPath, "\\", "/"), err
|
||||
}
|
||||
|
||||
func getWorkPath(appPath string) string {
|
||||
workPath := AppWorkPath
|
||||
|
||||
if giteaWorkPath, ok := os.LookupEnv("GITEA_WORK_DIR"); ok {
|
||||
workPath = giteaWorkPath
|
||||
}
|
||||
if len(workPath) == 0 {
|
||||
i := strings.LastIndex(appPath, "/")
|
||||
if i == -1 {
|
||||
workPath = appPath
|
||||
} else {
|
||||
workPath = appPath[:i]
|
||||
}
|
||||
}
|
||||
workPath = strings.ReplaceAll(workPath, "\\", "/")
|
||||
if !filepath.IsAbs(workPath) {
|
||||
log.Info("Provided work path %s is not absolute - will be made absolute against the current working directory", workPath)
|
||||
|
||||
absPath, err := filepath.Abs(workPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to absolute %s against the current working directory %v. Will absolute against the AppPath %s", workPath, err, appPath)
|
||||
workPath = filepath.Join(appPath, workPath)
|
||||
} else {
|
||||
workPath = absPath
|
||||
}
|
||||
}
|
||||
return strings.ReplaceAll(workPath, "\\", "/")
|
||||
}
|
||||
|
||||
func init() {
|
||||
IsWindows = runtime.GOOS == "windows"
|
||||
if AppVer == "" {
|
||||
|
@ -116,12 +46,6 @@ func init() {
|
|||
// We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
|
||||
// By default set this logger at Info - we'll change it later, but we need to start with something.
|
||||
log.SetConsoleLogger(log.DEFAULT, "console", log.INFO)
|
||||
|
||||
var err error
|
||||
if AppPath, err = getAppPath(); err != nil {
|
||||
log.Fatal("Failed to get app path: %v", err)
|
||||
}
|
||||
AppWorkPath = getWorkPath(AppPath)
|
||||
}
|
||||
|
||||
// IsRunUserMatchCurrentUser returns false if configured run user does not match
|
||||
|
@ -137,36 +61,6 @@ func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
|
|||
return currentUser, runUser == currentUser
|
||||
}
|
||||
|
||||
// SetCustomPathAndConf will set CustomPath and CustomConf with reference to the
|
||||
// GITEA_CUSTOM environment variable and with provided overrides before stepping
|
||||
// back to the default
|
||||
func SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath string) {
|
||||
if len(providedWorkPath) != 0 {
|
||||
AppWorkPath = filepath.ToSlash(providedWorkPath)
|
||||
}
|
||||
if giteaCustom, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
|
||||
CustomPath = giteaCustom
|
||||
}
|
||||
if len(providedCustom) != 0 {
|
||||
CustomPath = providedCustom
|
||||
}
|
||||
if len(CustomPath) == 0 {
|
||||
CustomPath = path.Join(AppWorkPath, "custom")
|
||||
} else if !filepath.IsAbs(CustomPath) {
|
||||
CustomPath = path.Join(AppWorkPath, CustomPath)
|
||||
}
|
||||
|
||||
if len(providedConf) != 0 {
|
||||
CustomConf = providedConf
|
||||
}
|
||||
if len(CustomConf) == 0 {
|
||||
CustomConf = path.Join(CustomPath, "conf/app.ini")
|
||||
} else if !filepath.IsAbs(CustomConf) {
|
||||
CustomConf = path.Join(CustomPath, CustomConf)
|
||||
log.Warn("Using 'custom' directory as relative origin for configuration file: '%s'", CustomConf)
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareAppDataPath creates app data directory if necessary
|
||||
func PrepareAppDataPath() error {
|
||||
// FIXME: There are too many calls to MkdirAll in old code. It is incorrect.
|
||||
|
@ -196,25 +90,29 @@ func PrepareAppDataPath() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func Init(opts *Options) {
|
||||
if opts.CustomConf == "" {
|
||||
opts.CustomConf = CustomConf
|
||||
}
|
||||
func InitCfgProvider(file string, extraConfigs ...string) {
|
||||
var err error
|
||||
CfgProvider, err = NewConfigProviderFromFile(opts)
|
||||
if err != nil {
|
||||
log.Fatal("newConfigProviderFromFile[%v]: %v", opts, err)
|
||||
if CfgProvider, err = NewConfigProviderFromFile(file, extraConfigs...); err != nil {
|
||||
log.Fatal("Unable to init config provider from %q: %v", file, err)
|
||||
}
|
||||
if !opts.DisableLoadCommonSettings {
|
||||
if err := loadCommonSettingsFrom(CfgProvider); err != nil {
|
||||
log.Fatal("loadCommonSettingsFrom[%v]: %v", opts, err)
|
||||
}
|
||||
CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls
|
||||
}
|
||||
|
||||
func MustInstalled() {
|
||||
if !InstallLock {
|
||||
log.Fatal(`Unable to load config file for a installed Gitea instance, you should either use "--config" to set your config file (app.ini), or run "gitea web" command to install Gitea.`)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadCommonSettings() {
|
||||
if err := loadCommonSettingsFrom(CfgProvider); err != nil {
|
||||
log.Fatal("Unable to load settings from config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// loadCommonSettingsFrom loads common configurations from a configuration provider.
|
||||
func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
||||
// WARNNING: don't change the sequence except you know what you are doing.
|
||||
// WARNING: don't change the sequence except you know what you are doing.
|
||||
loadRunModeFrom(cfg)
|
||||
loadLogGlobalFrom(cfg)
|
||||
loadServerFrom(cfg)
|
||||
|
|
|
@ -68,7 +68,7 @@ func sessionHandler(session ssh.Session) {
|
|||
|
||||
log.Trace("SSH: Payload: %v", command)
|
||||
|
||||
args := []string{"serv", "key-" + keyID, "--config=" + setting.CustomConf}
|
||||
args := []string{"--config=" + setting.CustomConf, "serv", "key-" + keyID}
|
||||
log.Trace("SSH: Arguments: %v", args)
|
||||
|
||||
ctx, cancel := context.WithCancel(session.Context())
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
|
||||
// Permission represents a set of permissions
|
||||
type Permission struct {
|
||||
Admin bool `json:"admin"`
|
||||
Push bool `json:"push"`
|
||||
Pull bool `json:"pull"`
|
||||
Admin bool `json:"admin"` // Admin indicates if the user is an administrator of the repository.
|
||||
Push bool `json:"push"` // Push indicates if the user can push code to the repository.
|
||||
Pull bool `json:"pull"` // Pull indicates if the user can pull code from the repository.
|
||||
}
|
||||
|
||||
// InternalTracker represents settings for internal tracker
|
||||
|
|
|
@ -81,16 +81,16 @@ func RenderCommitMessageLinkSubject(ctx context.Context, msg, urlPrefix, urlDefa
|
|||
|
||||
// RenderCommitBody extracts the body of a commit message without its title.
|
||||
func RenderCommitBody(ctx context.Context, msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||
msgLine := strings.TrimRightFunc(msg, unicode.IsSpace)
|
||||
msgLine := strings.TrimSpace(msg)
|
||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||
if lineEnd > 0 {
|
||||
msgLine = msgLine[lineEnd+1:]
|
||||
} else {
|
||||
return template.HTML("")
|
||||
return ""
|
||||
}
|
||||
msgLine = strings.TrimLeftFunc(msgLine, unicode.IsSpace)
|
||||
if len(msgLine) == 0 {
|
||||
return template.HTML("")
|
||||
return ""
|
||||
}
|
||||
|
||||
renderedMessage, err := markup.RenderCommitMessage(&markup.RenderContext{
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package templates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderCommitBody(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
msg string
|
||||
urlPrefix string
|
||||
metas map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want template.HTML
|
||||
}{
|
||||
{
|
||||
name: "multiple lines",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
msg: "first line\nsecond line",
|
||||
},
|
||||
want: "second line",
|
||||
},
|
||||
{
|
||||
name: "multiple lines with leading newlines",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
msg: "\n\n\n\nfirst line\nsecond line",
|
||||
},
|
||||
want: "second line",
|
||||
},
|
||||
{
|
||||
name: "multiple lines with trailing newlines",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
msg: "first line\nsecond line\n\n\n",
|
||||
},
|
||||
want: "second line",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, RenderCommitBody(tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas), "RenderCommitBody(%v, %v, %v, %v)", tt.args.ctx, tt.args.msg, tt.args.urlPrefix, tt.args.metas)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -90,10 +90,11 @@ func (w *testLoggerWriterCloser) Reset() {
|
|||
|
||||
// PrintCurrentTest prints the current test to os.Stdout
|
||||
func PrintCurrentTest(t testing.TB, skip ...int) func() {
|
||||
t.Helper()
|
||||
start := time.Now()
|
||||
actualSkip := 1
|
||||
if len(skip) > 0 {
|
||||
actualSkip = skip[0]
|
||||
actualSkip = skip[0] + 1
|
||||
}
|
||||
_, filename, line, _ := runtime.Caller(actualSkip)
|
||||
|
||||
|
|
|
@ -222,6 +222,8 @@ func isOSWindows() bool {
|
|||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/")
|
||||
|
||||
// FileURLToPath extracts the path information from a file://... url.
|
||||
func FileURLToPath(u *url.URL) (string, error) {
|
||||
if u.Scheme != "file" {
|
||||
|
@ -235,8 +237,7 @@ func FileURLToPath(u *url.URL) (string, error) {
|
|||
}
|
||||
|
||||
// If it looks like there's a Windows drive letter at the beginning, strip off the leading slash.
|
||||
re := regexp.MustCompile("/[A-Za-z]:/")
|
||||
if re.MatchString(path) {
|
||||
if driveLetterRegexp.MatchString(path) {
|
||||
return path[1:], nil
|
||||
}
|
||||
return path, nil
|
||||
|
|
|
@ -132,6 +132,9 @@ show_full_screen = Show full screen
|
|||
|
||||
confirm_delete_selected = Confirm to delete all selected items?
|
||||
|
||||
name = Name
|
||||
value = Value
|
||||
|
||||
[aria]
|
||||
navbar = Navigation Bar
|
||||
footer = Footer
|
||||
|
@ -194,11 +197,9 @@ host = Host
|
|||
user = Username
|
||||
password = Password
|
||||
db_name = Database Name
|
||||
db_helper = Note to MySQL users: please use the InnoDB storage engine and if you use "utf8mb4", your InnoDB version must be greater than 5.6 .
|
||||
db_schema = Schema
|
||||
db_schema_helper = Leave blank for database default ("public").
|
||||
ssl_mode = SSL
|
||||
charset = Charset
|
||||
path = Path
|
||||
sqlite_helper = File path for the SQLite3 database.<br>Enter an absolute path if you run Gitea as a service.
|
||||
reinstall_error = You are trying to install into an existing Gitea database
|
||||
|
@ -661,7 +662,7 @@ comment_type_group_project = Project
|
|||
comment_type_group_issue_ref = Issue reference
|
||||
saved_successfully = Your settings were saved successfully.
|
||||
privacy = Privacy
|
||||
keep_activity_private = Hide the activity from the profile page
|
||||
keep_activity_private = Hide Activity from profile page
|
||||
keep_activity_private_popup = Makes the activity visible only for you and the admins
|
||||
|
||||
lookup_avatar_by_mail = Look Up Avatar by Email Address
|
||||
|
@ -1615,6 +1616,9 @@ issues.review.pending.tooltip = This comment is not currently visible to other u
|
|||
issues.review.review = Review
|
||||
issues.review.reviewers = Reviewers
|
||||
issues.review.outdated = Outdated
|
||||
issues.review.outdated_description = Content has changed since this comment was made
|
||||
issues.review.option.show_outdated_comments = Show outdated comments
|
||||
issues.review.option.hide_outdated_comments = Hide outdated comments
|
||||
issues.review.show_outdated = Show outdated
|
||||
issues.review.hide_outdated = Hide outdated
|
||||
issues.review.show_resolved = Show resolved
|
||||
|
@ -3396,8 +3400,6 @@ owner.settings.chef.keypair.description = Generate a key pair used to authentica
|
|||
secrets = Secrets
|
||||
description = Secrets will be passed to certain actions and cannot be read otherwise.
|
||||
none = There are no secrets yet.
|
||||
value = Value
|
||||
name = Name
|
||||
creation = Add Secret
|
||||
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
|
||||
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
|
||||
|
@ -3464,9 +3466,31 @@ runs.commit = Commit
|
|||
runs.pushed_by = Pushed by
|
||||
runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s
|
||||
runs.no_matching_runner_helper = No matching runner: %s
|
||||
runs.actor = Actor
|
||||
runs.status = Status
|
||||
runs.actors_no_select = All actors
|
||||
runs.status_no_select = All status
|
||||
runs.no_results = No results matched.
|
||||
runs.no_runs = The workflow has no runs yet.
|
||||
|
||||
need_approval_desc = Need approval to run workflows for fork pull request.
|
||||
|
||||
variables = Variables
|
||||
variables.management = Variables Management
|
||||
variables.creation = Add Variable
|
||||
variables.none = There are no variables yet.
|
||||
variables.deletion = Remove variable
|
||||
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
|
||||
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
|
||||
variables.id_not_exist = Variable with id %d not exists.
|
||||
variables.edit = Edit Variable
|
||||
variables.deletion.failed = Failed to remove variable.
|
||||
variables.deletion.success = The variable has been removed.
|
||||
variables.creation.failed = Failed to add variable.
|
||||
variables.creation.success = The variable "%s" has been added.
|
||||
variables.update.failed = Failed to edit variable.
|
||||
variables.update.success = The variable has been edited.
|
||||
|
||||
[projects]
|
||||
type-1.display_name = Individual Project
|
||||
type-2.display_name = Repository Project
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
"@github/relative-time-element": "4.3.0",
|
||||
"@github/text-expander-element": "2.5.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.3.0",
|
||||
"@primer/octicons": "19.4.0",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"ansi_up": "5.2.1",
|
||||
"asciinema-player": "3.4.0",
|
||||
"clippie": "4.0.1",
|
||||
"css-loader": "6.8.1",
|
||||
|
@ -1235,9 +1235,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@primer/octicons": {
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.3.0.tgz",
|
||||
"integrity": "sha512-hyIo54VPC3VI7ZyAgosiJcbhxq1gZLbBspZwN9cg1uImRd2E8T9JST3kGeezezJYPjG367FuF7p1L+gmLmeESw==",
|
||||
"version": "19.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-19.4.0.tgz",
|
||||
"integrity": "sha512-92eXALm3ucZkzqpJmJbC+fR9ldiuNd4W4s2MZQNQIBahpg14emJ+I9fdHqCummFlfgyohLzXn++7rz0NlkqAJA==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
|
@ -2465,6 +2465,14 @@
|
|||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi_up": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-5.2.1.tgz",
|
||||
"integrity": "sha512-5bz5T/7FRmlxA37zDXhG6cAwlcZtfnmNLDJra66EEIT3kYlw5aPJdbkJEhm59D6kA4Wi5ict6u6IDYHJaQlH+g==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
|
@ -2487,20 +2495,6 @@
|
|||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-to-html": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.7.2.tgz",
|
||||
"integrity": "sha512-v6MqmEpNlxF+POuyhKkidusCHWWkaLcGRURzivcU3I9tv7k4JVhFcnukrM5Rlk2rUywdZuzYAZ+kbZqWCnfN3g==",
|
||||
"dependencies": {
|
||||
"entities": "^2.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"ansi-to-html": "bin/ansi-to-html"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
|
@ -4210,14 +4204,6 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/envinfo": {
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz",
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
"@github/relative-time-element": "4.3.0",
|
||||
"@github/text-expander-element": "2.5.0",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@primer/octicons": "19.3.0",
|
||||
"@primer/octicons": "19.4.0",
|
||||
"@webcomponents/custom-elements": "1.6.0",
|
||||
"add-asset-webpack-plugin": "2.0.1",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"ansi_up": "5.2.1",
|
||||
"asciinema-player": "3.4.0",
|
||||
"clippie": "4.0.1",
|
||||
"css-loader": "6.8.1",
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-copilot-error" width="16" height="16" aria-hidden="true"><path d="M.865 2.759v.001l14.82 10.722a.755.755 0 0 0 .188.1.751.751 0 0 1-1.063 1.025l-1.415-1.024c-.274.147-.613.315-1.008.482C11.296 14.528 9.756 15 8 15c-1.756 0-3.296-.473-4.387-.934a11.947 11.947 0 0 1-1.654-.859l-.098-.065-.028-.018-.006-.004-.015-.01a10.19 10.19 0 0 1-.792-.597 5.145 5.145 0 0 1-.605-.58 2.185 2.185 0 0 1-.259-.366A1.193 1.193 0 0 1 0 11V9.736a2.75 2.75 0 0 1 1.52-2.46l.067-.033.167-.838c-.175-.442-.238-.936-.251-1.434L.31 4.107a.75.75 0 0 1 .555-1.348ZM7.86 1.77c.05.053.097.107.14.164.043-.057.09-.111.14-.164.681-.731 1.737-.9 2.943-.765 1.23.136 2.145.527 2.724 1.26.566.716.693 1.614.693 2.485 0 .572-.053 1.147-.254 1.655l.168.838.066.033A2.75 2.75 0 0 1 16 9.736V11c0 .24-.086.438-.156.567a1.59 1.59 0 0 1-.075.125L13 9.688V7.824l-.023-.115c-.49.21-1.075.291-1.727.291-.22 0-.43-.012-.633-.036L6.824 5.22c.082-.233.143-.503.182-.813.117-.936-.038-1.396-.242-1.614-.193-.207-.637-.414-1.681-.298-.707.079-1.144.243-1.424.434l-1.251-.905c.58-.579 1.422-.899 2.51-1.02 1.205-.133 2.26.035 2.943.766ZM4.75 8c-.652 0-1.237-.081-1.727-.291L3 7.825v4.26c.387.225.788.426 1.2.6.97.412 2.306.815 3.8.815 1.494 0 2.829-.403 3.801-.815.076-.033.15-.065.22-.097L5.594 7.934A5.158 5.158 0 0 1 4.75 8Zm4.486-5.207c-.204.218-.359.678-.242 1.614.091.726.303 1.23.618 1.553.299.304.784.54 1.638.54.922 0 1.28-.199 1.442-.38.179-.2.308-.578.308-1.37 0-.765-.123-1.242-.37-1.555-.233-.296-.693-.586-1.713-.7-1.044-.116-1.488.091-1.681.298Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-copilot-error" width="16" height="16" aria-hidden="true"><path d="M16 11.24c0 .112-.072.274-.21.467L13 9.688V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-.198 0-.388-.009-.571-.029L6.833 5.226a4.01 4.01 0 0 0 .17-.782c.117-.935-.037-1.395-.241-1.614-.193-.206-.637-.413-1.682-.297-.683.076-1.115.231-1.395.415l-1.257-.91c.579-.564 1.413-.877 2.485-.996 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095Zm-5.083-8.707c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Zm2.511 11.074c-1.393.776-3.272 1.428-5.43 1.428-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.18-.455-.241-.963-.252-1.475L.31 4.107A.747.747 0 0 1 0 3.509V3.49a.748.748 0 0 1 .625-.73c.156-.026.306.047.435.139l14.667 10.578a.592.592 0 0 1 .227.264.752.752 0 0 1 .046.249v.022a.75.75 0 0 1-1.19.596Zm-1.367-.991L5.635 7.964a5.128 5.128 0 0 1-.889.073c-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433 1.539 0 3.089-.505 4.063-.934Z"/></svg>
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-copilot-warning" width="16" height="16" aria-hidden="true"><path d="M7.86 1.77c.05.053.097.107.14.164.043-.057.09-.111.14-.164.681-.731 1.737-.9 2.943-.765 1.23.136 2.145.527 2.724 1.26.566.716.693 1.614.693 2.485 0 .463-.035.929-.155 1.359a6.015 6.015 0 0 0-1.398-.616c.034-.195.053-.439.053-.743 0-.766-.123-1.242-.37-1.555-.233-.296-.693-.586-1.713-.7-1.044-.116-1.488.091-1.681.298-.204.218-.359.678-.242 1.614.06.479.172.86.332 1.158a6.014 6.014 0 0 0-2.92 2.144C5.926 7.904 5.372 8 4.75 8c-.652 0-1.237-.082-1.727-.291L3 7.824v4.261c.02.013.043.025.065.038a10.83 10.83 0 0 0 2.495 1.035c.21.629.522 1.21.916 1.726a11.883 11.883 0 0 1-2.863-.819 12.28 12.28 0 0 1-1.296-.641 8.849 8.849 0 0 1-.456-.281l-.028-.02-.006-.003-.015-.01a10.593 10.593 0 0 1-.792-.596 5.264 5.264 0 0 1-.605-.58 2.133 2.133 0 0 1-.259-.367A1.189 1.189 0 0 1 0 11V9.736a2.75 2.75 0 0 1 1.52-2.46l.067-.033.167-.838C1.553 5.897 1.5 5.322 1.5 4.75c0-.87.127-1.77.693-2.485.579-.733 1.494-1.124 2.724-1.26 1.206-.134 2.262.034 2.944.765ZM6.765 2.793c-.193-.207-.637-.414-1.681-.298-1.02.114-1.48.404-1.713.7-.247.313-.37.79-.37 1.555 0 .792.129 1.17.308 1.37.162.181.52.38 1.442.38.854 0 1.339-.236 1.638-.54.315-.323.527-.827.618-1.553.117-.936-.038-1.396-.242-1.614Z"/><path d="M8.498 14.81v.001a4.5 4.5 0 1 1 5.503-7.12 4.5 4.5 0 0 1-5.503 7.119ZM10.5 8.75V11a.75.75 0 0 0 1.5 0V8.75a.75.75 0 0 0-1.5 0Zm.75 5.75a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-copilot-warning" width="16" height="16" aria-hidden="true"><path d="M8.498 14.811a4.53 4.53 0 0 1-1.161-1.337 4.476 4.476 0 0 1-.587-2.224 4.496 4.496 0 0 1 4.5-4.5 4.5 4.5 0 0 1 4.5 4.5 4.5 4.5 0 0 1-7.252 3.561ZM10.5 8.75V11a.75.75 0 0 0 1.5 0V8.75a.75.75 0 1 0-1.5 0Zm.75 5.75a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"/><path d="m14.354 6.114-.05-.029a5.949 5.949 0 0 0-1.351-.589c.03-.19.047-.422.047-.709 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.058.462.165.834.316 1.127A6.025 6.025 0 0 0 6.369 7.76c-.472.185-1.015.277-1.623.277-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.265.205 1.285.725 2.577 1.079a5.937 5.937 0 0 0 .939 1.736C2.733 14.407.111 12.027 0 11.286V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .452-.033.906-.146 1.327ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Z"/></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="svg octicon-file-directory-symlink" width="16" height="16" aria-hidden="true"><path d="M0 2.75C0 1.784.784 1 1.75 1H5a1.75 1.75 0 0 1 1.4.7l.9 1.2a.25.25 0 0 0 .2.1h6.75c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 15H5.375a.75.75 0 0 1 0-1.5h8.875a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25H7.5a1.75 1.75 0 0 1-1.4-.7l-.9-1.2a.25.25 0 0 0-.2-.1H1.75a.25.25 0 0 0-.25.25v3a.75.75 0 0 1-1.5 0v-3Z"/><path d="M1.5 12.237a2.25 2.25 0 0 1 2.262-2.249L4 9.989v1.938c0 .218.26.331.42.183l2.883-2.677a.25.25 0 0 0 0-.366L4.42 6.39a.25.25 0 0 0-.42.183v1.916l-.229-.001A3.75 3.75 0 0 0 0 12.237v1.013a.75.75 0 0 0 1.5 0v-1.013Z"/></svg>
|
After Width: | Height: | Size: 706 B |
|
@ -36,6 +36,7 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
|
|||
WorkflowPayload: t.Job.WorkflowPayload,
|
||||
Context: generateTaskContext(t),
|
||||
Secrets: getSecretsOfTask(ctx, t),
|
||||
Vars: getVariablesOfTask(ctx, t),
|
||||
}
|
||||
|
||||
if needs, err := findTaskNeeds(ctx, t); err != nil {
|
||||
|
@ -88,6 +89,29 @@ func getSecretsOfTask(ctx context.Context, task *actions_model.ActionTask) map[s
|
|||
return secrets
|
||||
}
|
||||
|
||||
func getVariablesOfTask(ctx context.Context, task *actions_model.ActionTask) map[string]string {
|
||||
variables := map[string]string{}
|
||||
|
||||
// Org / User level
|
||||
ownerVariables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{OwnerID: task.Job.Run.Repo.OwnerID})
|
||||
if err != nil {
|
||||
log.Error("find variables of org: %d, error: %v", task.Job.Run.Repo.OwnerID, err)
|
||||
}
|
||||
|
||||
// Repo level
|
||||
repoVariables, err := actions_model.FindVariables(ctx, actions_model.FindVariablesOpts{RepoID: task.Job.Run.RepoID})
|
||||
if err != nil {
|
||||
log.Error("find variables of repo: %d, error: %v", task.Job.Run.RepoID, err)
|
||||
}
|
||||
|
||||
// Level precedence: Repo > Org / User
|
||||
for _, v := range append(ownerVariables, repoVariables...) {
|
||||
variables[v.Name] = v.Data
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
|
||||
event := map[string]interface{}{}
|
||||
_ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event)
|
||||
|
|
|
@ -561,12 +561,12 @@ func GetTeamRepos(ctx *context.APIContext) {
|
|||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
access, err := access_model.AccessLevel(ctx, ctx.Doer, repo)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
|
||||
return
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, access)
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
ctx.SetTotalCountHeader(int64(team.NumRepos))
|
||||
ctx.JSON(http.StatusOK, repos)
|
||||
|
@ -612,13 +612,13 @@ func GetTeamRepo(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
access, err := access_model.AccessLevel(ctx, ctx.Doer, repo)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, access))
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
|
||||
// getRepositoryByParams get repository by a team's organization ID and repo name
|
||||
|
|
|
@ -60,12 +60,12 @@ func ListForks(ctx *context.APIContext) {
|
|||
}
|
||||
apiForks := make([]*api.Repository, len(forks))
|
||||
for i, fork := range forks {
|
||||
access, err := access_model.AccessLevel(ctx, ctx.Doer, fork)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
apiForks[i] = convert.ToRepo(ctx, fork, access)
|
||||
apiForks[i] = convert.ToRepo(ctx, fork, permission)
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumForks))
|
||||
|
@ -152,5 +152,5 @@ func CreateFork(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
// TODO change back to 201
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, fork, perm.AccessModeOwner))
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, fork, access_model.Permission{AccessMode: perm.AccessModeOwner}))
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/webhook"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
@ -185,7 +186,7 @@ func TestHook(ctx *context.APIContext) {
|
|||
Commits: []*api.PayloadCommit{commit},
|
||||
TotalCommits: 1,
|
||||
HeadCommit: commit,
|
||||
Repo: convert.ToRepo(ctx, ctx.Repo.Repository, perm.AccessModeNone),
|
||||
Repo: convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}),
|
||||
Pusher: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
|
||||
Sender: convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone),
|
||||
}); err != nil {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
@ -27,13 +28,13 @@ import (
|
|||
func appendPrivateInformation(ctx stdCtx.Context, apiKey *api.DeployKey, key *asymkey_model.DeployKey, repository *repo_model.Repository) (*api.DeployKey, error) {
|
||||
apiKey.ReadOnly = key.Mode == perm.AccessModeRead
|
||||
if repository.ID == key.RepoID {
|
||||
apiKey.Repository = convert.ToRepo(ctx, repository, key.Mode)
|
||||
apiKey.Repository = convert.ToRepo(ctx, repository, access_model.Permission{AccessMode: key.Mode})
|
||||
} else {
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID)
|
||||
if err != nil {
|
||||
return apiKey, err
|
||||
}
|
||||
apiKey.Repository = convert.ToRepo(ctx, repo, key.Mode)
|
||||
apiKey.Repository = convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: key.Mode})
|
||||
}
|
||||
return apiKey, nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
|
@ -211,7 +212,7 @@ func Migrate(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", repoOwner.Name, form.RepoName)
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, perm.AccessModeAdmin))
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
|
||||
}
|
||||
|
||||
func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, remoteAddr string, err error) {
|
||||
|
|
|
@ -211,14 +211,14 @@ func Search(ctx *context.APIContext) {
|
|||
})
|
||||
return
|
||||
}
|
||||
accessMode, err := access_model.AccessLevel(ctx, ctx.Doer, repo)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, api.SearchError{
|
||||
OK: false,
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, accessMode)
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
ctx.SetLinkHeader(int(count), opts.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
@ -272,7 +272,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
|||
ctx.Error(http.StatusInternalServerError, "GetRepositoryByID", err)
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, perm.AccessModeOwner))
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}))
|
||||
}
|
||||
|
||||
// Create one repository of mine
|
||||
|
@ -419,7 +419,7 @@ func Generate(ctx *context.APIContext) {
|
|||
}
|
||||
log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, perm.AccessModeOwner))
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}))
|
||||
}
|
||||
|
||||
// CreateOrgRepoDeprecated create one repository of the organization
|
||||
|
@ -537,7 +537,7 @@ func Get(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.AccessMode))
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
|
||||
}
|
||||
|
||||
// GetByID returns a single Repository
|
||||
|
@ -568,15 +568,15 @@ func GetByID(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
} else if !perm.HasAccess() {
|
||||
} else if !permission.HasAccess() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, perm.AccessMode))
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
|
||||
}
|
||||
|
||||
// Edit edit repository properties
|
||||
|
@ -638,7 +638,7 @@ func Edit(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, ctx.Repo.AccessMode))
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
|
||||
}
|
||||
|
||||
// updateBasicProperties updates the basic properties of a repo: Name, Description, Website and Visibility
|
||||
|
|
|
@ -264,7 +264,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.AccessMode))
|
||||
combiStatus := convert.ToCombinedStatus(ctx, statuses, convert.ToRepo(ctx, repo, ctx.Repo.Permission))
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, combiStatus)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
|
@ -122,12 +123,12 @@ func Transfer(ctx *context.APIContext) {
|
|||
|
||||
if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
|
||||
log.Trace("Repository transfer initiated: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, ctx.Repo.Repository, perm.AccessModeAdmin))
|
||||
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, perm.AccessModeAdmin))
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
|
||||
}
|
||||
|
||||
// AcceptTransfer accept a repo transfer
|
||||
|
@ -165,7 +166,7 @@ func AcceptTransfer(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.AccessMode))
|
||||
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
|
||||
}
|
||||
|
||||
// RejectTransfer reject a repo transfer
|
||||
|
@ -203,7 +204,7 @@ func RejectTransfer(ctx *context.APIContext) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.AccessMode))
|
||||
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
|
||||
}
|
||||
|
||||
func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"code.gitea.io/gitea/models/perm"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
@ -38,13 +39,13 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
|||
|
||||
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||
for i := range repos {
|
||||
access, err := access_model.AccessLevel(ctx, ctx.Doer, repos[i])
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || access >= perm.AccessModeRead {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], access))
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,11 +124,11 @@ func ListMyRepos(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "LoadOwner", err)
|
||||
return
|
||||
}
|
||||
accessMode, err := access_model.AccessLevel(ctx, ctx.Doer, repo)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, accessMode)
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(count), opts.ListOptions.PageSize)
|
||||
|
|
|
@ -28,11 +28,11 @@ func getStarredRepos(ctx std_context.Context, user *user_model.User, private boo
|
|||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
access, err := access_model.AccessLevel(ctx, user, starred)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, starred, access)
|
||||
repos[i] = convert.ToRepo(ctx, starred, permission)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ func getWatchedRepos(ctx std_context.Context, user *user_model.User, private boo
|
|||
|
||||
repos := make([]*api.Repository, len(watchedRepos))
|
||||
for i, watched := range watchedRepos {
|
||||
access, err := access_model.AccessLevel(ctx, user, watched)
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, watched, user)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, watched, access)
|
||||
repos[i] = convert.ToRepo(ctx, watched, permission)
|
||||
}
|
||||
return repos, total, nil
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/system"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
actions_router "code.gitea.io/gitea/routers/api/actions"
|
||||
packages_router "code.gitea.io/gitea/routers/api/packages"
|
||||
|
@ -101,21 +100,16 @@ func syncAppConfForGit(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GlobalInitInstalled is for global installed configuration.
|
||||
func GlobalInitInstalled(ctx context.Context) {
|
||||
if !setting.InstallLock {
|
||||
log.Fatal("Gitea is not installed")
|
||||
}
|
||||
func InitWebInstallPage(ctx context.Context) {
|
||||
translation.InitLocales(ctx)
|
||||
setting.LoadSettingsForInstall()
|
||||
mustInit(svg.Init)
|
||||
}
|
||||
|
||||
// InitWebInstalled is for global installed configuration.
|
||||
func InitWebInstalled(ctx context.Context) {
|
||||
mustInitCtx(ctx, git.InitFull)
|
||||
log.Info("Gitea Version: %s%s", setting.AppVer, setting.AppBuiltWith)
|
||||
log.Info("Git Version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
log.Info("Run Mode: %s", util.ToTitleCase(setting.RunMode))
|
||||
log.Info("Git version: %s (home: %s)", git.VersionInfo(), git.HomeDir())
|
||||
|
||||
// Setup i18n
|
||||
translation.InitLocales(ctx)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
||||
"gitea.com/go-chi/session"
|
||||
|
@ -98,7 +99,6 @@ func Install(ctx *context.Context) {
|
|||
form.DbName = setting.Database.Name
|
||||
form.DbPath = setting.Database.Path
|
||||
form.DbSchema = setting.Database.Schema
|
||||
form.Charset = setting.Database.Charset
|
||||
|
||||
curDBType := setting.Database.Type.String()
|
||||
var isCurDBTypeSupported bool
|
||||
|
@ -268,7 +268,6 @@ func SubmitInstall(ctx *context.Context) {
|
|||
setting.Database.Name = form.DbName
|
||||
setting.Database.Schema = form.DbSchema
|
||||
setting.Database.SSLMode = form.SSLMode
|
||||
setting.Database.Charset = form.Charset
|
||||
setting.Database.Path = form.DbPath
|
||||
setting.Database.LogSQL = !setting.IsProd
|
||||
|
||||
|
@ -370,11 +369,16 @@ func SubmitInstall(ctx *context.Context) {
|
|||
}
|
||||
|
||||
// Save settings.
|
||||
cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true})
|
||||
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
||||
if err != nil {
|
||||
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
||||
}
|
||||
|
||||
cfg.Section("").Key("APP_NAME").SetValue(form.AppName)
|
||||
cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
|
||||
cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
||||
cfg.Section("").Key("RUN_MODE").SetValue("prod")
|
||||
|
||||
cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type.String())
|
||||
cfg.Section("database").Key("HOST").SetValue(setting.Database.Host)
|
||||
cfg.Section("database").Key("NAME").SetValue(setting.Database.Name)
|
||||
|
@ -382,13 +386,10 @@ func SubmitInstall(ctx *context.Context) {
|
|||
cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd)
|
||||
cfg.Section("database").Key("SCHEMA").SetValue(setting.Database.Schema)
|
||||
cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode)
|
||||
cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset)
|
||||
cfg.Section("database").Key("PATH").SetValue(setting.Database.Path)
|
||||
cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful
|
||||
|
||||
cfg.Section("").Key("APP_NAME").SetValue(form.AppName)
|
||||
cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
|
||||
cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
|
||||
cfg.Section("server").Key("SSH_DOMAIN").SetValue(form.Domain)
|
||||
cfg.Section("server").Key("DOMAIN").SetValue(form.Domain)
|
||||
cfg.Section("server").Key("HTTP_PORT").SetValue(form.HTTPPort)
|
||||
|
@ -450,8 +451,6 @@ func SubmitInstall(ctx *context.Context) {
|
|||
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(fmt.Sprint(form.NoReplyAddress))
|
||||
cfg.Section("cron.update_checker").Key("ENABLED").SetValue(fmt.Sprint(form.EnableUpdateChecker))
|
||||
|
||||
cfg.Section("").Key("RUN_MODE").SetValue("prod")
|
||||
|
||||
cfg.Section("session").Key("PROVIDER").SetValue("file")
|
||||
|
||||
cfg.Section("log").Key("MODE").MustString("console")
|
||||
|
@ -514,7 +513,13 @@ func SubmitInstall(ctx *context.Context) {
|
|||
// ---- All checks are passed
|
||||
|
||||
// Reload settings (and re-initialize database connection)
|
||||
reloadSettings(ctx)
|
||||
setting.InitCfgProvider(setting.CustomConf)
|
||||
setting.LoadCommonSettings()
|
||||
setting.MustInstalled()
|
||||
setting.LoadDBSetting()
|
||||
if err := common.InitDBEngine(ctx); err != nil {
|
||||
log.Fatal("ORM engine initialization failed: %v", err)
|
||||
}
|
||||
|
||||
// Create admin account
|
||||
if len(form.AdminName) > 0 {
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package install
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
)
|
||||
|
||||
// PreloadSettings preloads the configuration to check if we need to run install
|
||||
func PreloadSettings(ctx context.Context) bool {
|
||||
setting.Init(&setting.Options{
|
||||
AllowEmpty: true,
|
||||
})
|
||||
if !setting.InstallLock {
|
||||
log.Info("AppPath: %s", setting.AppPath)
|
||||
log.Info("AppWorkPath: %s", setting.AppWorkPath)
|
||||
log.Info("Custom path: %s", setting.CustomPath)
|
||||
log.Info("Log path: %s", setting.Log.RootPath)
|
||||
log.Info("Configuration file: %s", setting.CustomConf)
|
||||
log.Info("Prepare to run install page")
|
||||
translation.InitLocales(ctx)
|
||||
if setting.EnableSQLite3 {
|
||||
log.Info("SQLite3 is supported")
|
||||
}
|
||||
|
||||
setting.LoadSettingsForInstall()
|
||||
_ = svg.Init()
|
||||
}
|
||||
|
||||
return !setting.InstallLock
|
||||
}
|
||||
|
||||
// reloadSettings reloads the existing settings and starts up the database
|
||||
func reloadSettings(ctx context.Context) {
|
||||
setting.Init(&setting.Options{})
|
||||
setting.LoadDBSetting()
|
||||
if setting.InstallLock {
|
||||
if err := common.InitDBEngine(ctx); err == nil {
|
||||
log.Info("ORM engine initialization successful!")
|
||||
} else {
|
||||
log.Fatal("ORM engine initialization failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -167,20 +166,6 @@ func Config(ctx *context.Context) {
|
|||
ctx.Data["SessionConfig"] = sessionCfg
|
||||
|
||||
ctx.Data["Git"] = setting.Git
|
||||
|
||||
type envVar struct {
|
||||
Name, Value string
|
||||
}
|
||||
|
||||
envVars := map[string]*envVar{}
|
||||
if len(os.Getenv("GITEA_WORK_DIR")) > 0 {
|
||||
envVars["GITEA_WORK_DIR"] = &envVar{"GITEA_WORK_DIR", os.Getenv("GITEA_WORK_DIR")}
|
||||
}
|
||||
if len(os.Getenv("GITEA_CUSTOM")) > 0 {
|
||||
envVars["GITEA_CUSTOM"] = &envVar{"GITEA_CUSTOM", os.Getenv("GITEA_CUSTOM")}
|
||||
}
|
||||
|
||||
ctx.Data["EnvVars"] = envVars
|
||||
ctx.Data["AccessLogTemplate"] = setting.Log.AccessLogTemplate
|
||||
ctx.Data["LogSQL"] = setting.Database.LogSQL
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ package actions
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
|
||||
"github.com/nektos/act/pkg/model"
|
||||
|
@ -125,7 +127,16 @@ func List(ctx *context.Context) {
|
|||
}
|
||||
|
||||
workflow := ctx.FormString("workflow")
|
||||
actorID := ctx.FormInt64("actor")
|
||||
status := ctx.FormInt("status")
|
||||
ctx.Data["CurWorkflow"] = workflow
|
||||
// if status or actor query param is not given to frontend href, (href="/<repoLink>/actions")
|
||||
// they will be 0 by default, which indicates get all status or actors
|
||||
ctx.Data["CurActor"] = actorID
|
||||
ctx.Data["CurStatus"] = status
|
||||
if actorID > 0 || status > int(actions_model.StatusUnknown) {
|
||||
ctx.Data["IsFiltered"] = true
|
||||
}
|
||||
|
||||
opts := actions_model.FindRunOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
|
@ -134,6 +145,8 @@ func List(ctx *context.Context) {
|
|||
},
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
WorkflowFileName: workflow,
|
||||
TriggerUserID: actorID,
|
||||
Status: actions_model.Status(status),
|
||||
}
|
||||
|
||||
runs, total, err := actions_model.FindRuns(ctx, opts)
|
||||
|
@ -153,9 +166,20 @@ func List(ctx *context.Context) {
|
|||
|
||||
ctx.Data["Runs"] = runs
|
||||
|
||||
actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx, actors)
|
||||
|
||||
ctx.Data["StatusInfoList"] = actions_model.GetStatusInfoList(ctx)
|
||||
|
||||
pager := context.NewPagination(int(total), opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("workflow", workflow)
|
||||
pager.AddParamString("actor", fmt.Sprint(actorID))
|
||||
pager.AddParamString("status", fmt.Sprint(status))
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplListActions)
|
||||
|
|
|
@ -685,7 +685,11 @@ func UploadFilePost(ctx *context.Context) {
|
|||
|
||||
message := strings.TrimSpace(form.CommitSummary)
|
||||
if len(message) == 0 {
|
||||
message = ctx.Tr("repo.editor.upload_files_to_dir", form.TreePath)
|
||||
dir := form.TreePath
|
||||
if dir == "" {
|
||||
dir = "/"
|
||||
}
|
||||
message = ctx.Tr("repo.editor.upload_files_to_dir", dir)
|
||||
}
|
||||
|
||||
form.CommitMessage = strings.TrimSpace(form.CommitMessage)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
)
|
||||
|
||||
func makeSelfOnTop(ctx *context.Context, users []*user.User) []*user.User {
|
||||
func MakeSelfOnTop(ctx *context.Context, users []*user.User) []*user.User {
|
||||
if ctx.Doer != nil {
|
||||
sort.Slice(users, func(i, j int) bool {
|
||||
if users[i].ID == users[j].ID {
|
||||
|
|
|
@ -13,15 +13,15 @@ import (
|
|||
)
|
||||
|
||||
func TestMakeSelfOnTop(t *testing.T) {
|
||||
users := makeSelfOnTop(&context.Context{}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
users := MakeSelfOnTop(&context.Context{}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 2, users[0].ID)
|
||||
|
||||
users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 1}}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
users = MakeSelfOnTop(&context.Context{Doer: &user.User{ID: 1}}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 1, users[0].ID)
|
||||
|
||||
users = makeSelfOnTop(&context.Context{Doer: &user.User{ID: 2}}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
users = MakeSelfOnTop(&context.Context{Doer: &user.User{ID: 2}}, []*user.User{{ID: 2}, {ID: 1}})
|
||||
assert.Len(t, users, 2)
|
||||
assert.EqualValues(t, 2, users[0].ID)
|
||||
}
|
||||
|
|
|
@ -313,7 +313,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
|
|||
ctx.ServerError("GetRepoAssignees", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
|
||||
ctx.Data["Assignees"] = MakeSelfOnTop(ctx, assigneeUsers)
|
||||
|
||||
handleTeamMentions(ctx)
|
||||
if ctx.Written() {
|
||||
|
@ -509,7 +509,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
|
|||
ctx.ServerError("GetRepoAssignees", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
|
||||
ctx.Data["Assignees"] = MakeSelfOnTop(ctx, assigneeUsers)
|
||||
|
||||
handleTeamMentions(ctx)
|
||||
}
|
||||
|
@ -3539,7 +3539,7 @@ func IssuePosters(ctx *context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
posters = makeSelfOnTop(ctx, posters)
|
||||
posters = MakeSelfOnTop(ctx, posters)
|
||||
|
||||
resp := &userSearchResponse{}
|
||||
resp.Results = make([]*userSearchInfo, len(posters))
|
||||
|
|
|
@ -5,6 +5,7 @@ package repo
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
@ -88,3 +89,27 @@ func SetWhitespaceBehavior(ctx *context.Context) {
|
|||
ctx.Data["WhitespaceBehavior"] = whitespaceBehavior
|
||||
}
|
||||
}
|
||||
|
||||
// SetShowOutdatedComments set the show outdated comments option as context variable
|
||||
func SetShowOutdatedComments(ctx *context.Context) {
|
||||
showOutdatedCommentsValue := ctx.FormString("show-outdated")
|
||||
// var showOutdatedCommentsValue string
|
||||
|
||||
if showOutdatedCommentsValue != "true" && showOutdatedCommentsValue != "false" {
|
||||
// invalid or no value for this form string -> use default or stored user setting
|
||||
if ctx.IsSigned {
|
||||
showOutdatedCommentsValue, _ = user_model.GetUserSetting(ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, "false")
|
||||
} else {
|
||||
// not logged in user -> use the default value
|
||||
showOutdatedCommentsValue = "false"
|
||||
}
|
||||
} else {
|
||||
// valid value -> update user setting if user is logged in
|
||||
if ctx.IsSigned {
|
||||
_ = user_model.SetUserSetting(ctx.Doer.ID, user_model.SettingsKeyShowOutdatedComments, showOutdatedCommentsValue)
|
||||
}
|
||||
}
|
||||
|
||||
showOutdatedComments, _ := strconv.ParseBool(showOutdatedCommentsValue)
|
||||
ctx.Data["ShowOutdatedComments"] = showOutdatedComments
|
||||
}
|
||||
|
|
|
@ -761,7 +761,7 @@ func ViewPullFiles(ctx *context.Context) {
|
|||
"numberOfViewedFiles": diff.NumViewedFiles,
|
||||
}
|
||||
|
||||
if err = diff.LoadComments(ctx, issue, ctx.Doer); err != nil {
|
||||
if err = diff.LoadComments(ctx, issue, ctx.Doer, ctx.Data["ShowOutdatedComments"].(bool)); err != nil {
|
||||
ctx.ServerError("LoadComments", err)
|
||||
return
|
||||
}
|
||||
|
@ -809,7 +809,7 @@ func ViewPullFiles(ctx *context.Context) {
|
|||
ctx.ServerError("GetRepoAssignees", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Assignees"] = makeSelfOnTop(ctx, assigneeUsers)
|
||||
ctx.Data["Assignees"] = MakeSelfOnTop(ctx, assigneeUsers)
|
||||
|
||||
handleTeamMentions(ctx)
|
||||
if ctx.Written() {
|
||||
|
|
|
@ -159,7 +159,7 @@ func UpdateResolveConversation(ctx *context.Context) {
|
|||
}
|
||||
|
||||
func renderConversation(ctx *context.Context, comment *issues_model.Comment) {
|
||||
comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line)
|
||||
comments, err := issues_model.FetchCodeCommentsByLine(ctx, comment.Issue, ctx.Doer, comment.TreePath, comment.Line, ctx.Data["ShowOutdatedComments"].(bool))
|
||||
if err != nil {
|
||||
ctx.ServerError("FetchCodeCommentsByLine", err)
|
||||
return
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue