mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
221 lines
6.6 KiB
Go
221 lines
6.6 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hanzoai/git/modules/log"
|
|
"github.com/hanzoai/git/modules/tempdir"
|
|
)
|
|
|
|
var (
|
|
// AppPath represents the path to the forge binary
|
|
AppPath string
|
|
|
|
// AppWorkPath is the "working directory" of the forge. It maps to the: WORK_PATH in app.ini, "--work-path" flag, environment variable GIT_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: GIT_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) {
|
|
InitWorkPathAndCfgProvider(getEnvFn, args)
|
|
LoadCommonSettings()
|
|
}
|
|
|
|
// InitWorkPathAndCfgProvider will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf
|
|
func InitWorkPathAndCfgProvider(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("GIT_WORK_DIR")
|
|
if envWorkPath != "" {
|
|
tmpWorkPath.Set(envWorkPath)
|
|
if !filepath.IsAbs(tmpWorkPath.Value) {
|
|
log.Fatal("GIT_WORK_DIR (work path) must be absolute path")
|
|
}
|
|
}
|
|
|
|
envCustomPath := getEnvFn("GIT_CUSTOM")
|
|
if envCustomPath != "" {
|
|
tmpCustomPath.Set(envCustomPath)
|
|
if !filepath.IsAbs(tmpCustomPath.Value) {
|
|
log.Fatal("GIT_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)
|
|
if HasInstallLock(CfgProvider) {
|
|
ClearEnvConfigKeys() // if the instance has been installed, do not pass the environment variables to sub-processes
|
|
}
|
|
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("GIT_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
|
|
}
|
|
|
|
func MockBuiltinPaths(workPath, customPath, customConf string) func() {
|
|
oldApp, oldCustom, oldConf := appWorkPathBuiltin, customPathBuiltin, customConfBuiltin
|
|
appWorkPathBuiltin, customPathBuiltin, customConfBuiltin = workPath, customPath, customConf
|
|
return func() { appWorkPathBuiltin, customPathBuiltin, customConfBuiltin = oldApp, oldCustom, oldConf }
|
|
}
|
|
|
|
// AppDataTempDir returns a managed temporary directory for the application data.
|
|
// Using empty sub will get the managed base temp directory, and it's safe to delete it.
|
|
// The forge only creates subdirectories under it, but not the APP_TEMP_PATH directory itself.
|
|
// * When APP_TEMP_PATH="/tmp": the managed temp directory is "/tmp/forge-tmp"
|
|
// * When APP_TEMP_PATH is not set: the managed temp directory is "/{APP_DATA_PATH}/tmp"
|
|
func AppDataTempDir(sub string) *tempdir.TempDir {
|
|
if appTempPathInternal != "" {
|
|
return tempdir.New(appTempPathInternal, "forge-tmp/"+sub)
|
|
}
|
|
if AppDataPath == "" {
|
|
panic("setting.AppDataPath is not set")
|
|
}
|
|
return tempdir.New(AppDataPath, "tmp/"+sub)
|
|
}
|