From 32b97b3ce8eeccdcfd51280f83e659a29a18caa2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 8 Apr 2025 09:15:28 -0700 Subject: [PATCH] Uniform all temporary directories and allow customizing temp path (#32352) This PR uniform all temporary directory usage so that it will be easier to manage. Relate to #31792 - [x] Added a new setting to allow users to configure the global temporary directory. - [x] Move all temporary files and directories to be placed under os.Temp()/gitea. - [x] `setting.Repository.Local.LocalCopyPath` now will be `setting.TempPath/local-repo` and the customized path is removed. ```diff -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;[repository.local] -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Path for local repository copy. Defaults to TEMP_PATH + `local-repo`, this is deprecated and cannot be changed -;LOCAL_COPY_PATH = local-repo ``` - [x] `setting.Repository.Upload.TempPath` now will be `settting.TempPath/uploads` and the customized path is removed. ```diff ;[repository.upload] -;; -;; Path for uploads. Defaults to TEMP_PATH + `uploads` -;TEMP_PATH = uploads ``` - [x] `setting.Packages.ChunkedUploadPath` now will be `settting.TempPath/package-upload` and the customized path is removed. ```diff ;[packages] -;; -;; Path for chunked uploads. Defaults it's `package-upload` under `TEMP_PATH` unless it's an absolute path. -;CHUNKED_UPLOAD_PATH = package-upload ``` - [x] `setting.SSH.KeyTestPath` now will be `settting.TempPath/ssh_key_test` and the customized path is removed. ```diff [server] -;; -;; Directory to create temporary files in when testing public keys using ssh-keygen, -;; default is the system temporary directory. -;SSH_KEY_TEST_PATH = ``` TODO: - [ ] setting.PprofDataPath haven't been changed because it may need to be kept until somebody read it but temp path may be clean up any time. --------- Co-authored-by: wxiaoguang --- cmd/web.go | 4 + custom/conf/app.example.ini | 25 +--- models/asymkey/ssh_key_fingerprint.go | 44 +------ models/asymkey/ssh_key_parse.go | 71 +---------- models/asymkey/ssh_key_test.go | 30 ----- models/issues/issue_update.go | 1 + models/migrations/base/tests.go | 9 +- models/repo/upload.go | 10 +- models/unittest/testdb.go | 22 ++-- modules/git/blame.go | 70 ++++++----- modules/git/blame_sha256_test.go | 3 + modules/git/blame_test.go | 3 + modules/git/git_test.go | 7 +- modules/git/repo.go | 5 +- modules/git/repo_index.go | 17 +-- modules/git/repo_language_stats_test.go | 3 + modules/markup/external/external.go | 11 +- modules/packages/hashed_buffer.go | 5 +- modules/packages/hashed_buffer_test.go | 3 + .../packages/nuget/symbol_extractor_test.go | 3 + modules/repository/temp.go | 32 +---- modules/setting/packages.go | 12 +- modules/setting/path.go | 16 +++ modules/setting/repository.go | 21 ---- modules/setting/server.go | 16 +++ modules/setting/ssh.go | 6 - modules/ssh/init.go | 5 - modules/tempdir/tempdir.go | 112 ++++++++++++++++++ modules/tempdir/tempdir_test.go | 75 ++++++++++++ modules/templates/util_render_test.go | 2 +- modules/util/filebuffer/file_backed_buffer.go | 35 +----- .../filebuffer/file_backed_buffer_test.go | 3 +- options/locale/locale_en-US.ini | 2 - routers/web/repo/githttp.go | 10 +- routers/web/repo/setting/lfs.go | 8 +- services/pull/patch.go | 8 +- services/pull/temp_repo.go | 9 +- services/repository/create.go | 11 +- services/repository/files/temp_repo.go | 9 +- services/repository/generate.go | 12 +- services/repository/repository.go | 3 - services/wiki/wiki.go | 16 +-- templates/admin/config.tmpl | 4 - .../migration-test/migration_test.go | 2 +- tests/test_utils.go | 4 +- 45 files changed, 361 insertions(+), 418 deletions(-) create mode 100644 modules/tempdir/tempdir.go create mode 100644 modules/tempdir/tempdir_test.go diff --git a/cmd/web.go b/cmd/web.go index dc5c6de48a..e47b171455 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -213,6 +213,10 @@ func serveInstalled(ctx *cli.Context) error { log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath) } + // the AppDataTempDir is fully managed by us with a safe sub-path + // so it's safe to automatically remove the outdated files + setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour) + // Override the provided port number within the configuration if ctx.IsSet("port") { if err := setPort(ctx.String("port")); err != nil { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 07a6ebdcf2..d1124371e0 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -197,13 +197,6 @@ RUN_USER = ; git ;; relative paths are made absolute relative to the APP_DATA_PATH ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa ;; -;; Directory to create temporary files in when testing public keys using ssh-keygen, -;; default is the system temporary directory. -;SSH_KEY_TEST_PATH = -;; -;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. -;SSH_KEYGEN_PATH = -;; ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false ;SSH_AUTHORIZED_KEYS_BACKUP = false ;; @@ -294,6 +287,9 @@ RUN_USER = ; git ;; Default path for App data ;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_ ;; +;; Base path for App's temp files, leave empty to use the managed tmp directory in APP_DATA_PATH +;APP_TEMP_PATH = +;; ;; Enable gzip compression for runtime-generated content, static resources excluded ;ENABLE_GZIP = false ;; @@ -1069,15 +1065,6 @@ LEVEL = Info ;; Separate extensions with a comma. To line wrap files without an extension, just put a comma ;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd, -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;[repository.local] -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart) -;LOCAL_COPY_PATH = tmp/local-repo - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[repository.upload] @@ -1087,9 +1074,6 @@ LEVEL = Info ;; Whether repository file uploads are enabled. Defaults to `true` ;ENABLED = true ;; -;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart) -;TEMP_PATH = data/tmp/uploads -;; ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = ;; @@ -2594,9 +2578,6 @@ LEVEL = Info ;; Currently, only `minio` and `azureblob` is supported. ;SERVE_DIRECT = false ;; -;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload` -;CHUNKED_UPLOAD_PATH = tmp/package-upload -;; ;; Maximum count of package versions a single owner can have (`-1` means no limits) ;LIMIT_TOTAL_OWNER_COUNT = -1 ;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go index 1ed3b5df2a..4dcfe1f279 100644 --- a/models/asymkey/ssh_key_fingerprint.go +++ b/models/asymkey/ssh_key_fingerprint.go @@ -6,27 +6,13 @@ package asymkey import ( "context" "fmt" - "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "golang.org/x/crypto/ssh" "xorm.io/builder" ) -// ___________.__ .__ __ -// \_ _____/|__| ____ ____ ________________________|__| _____/ |_ -// | __) | |/ \ / ___\_/ __ \_ __ \____ \_ __ \ |/ \ __\ -// | \ | | | \/ /_/ > ___/| | \/ |_> > | \/ | | \ | -// \___ / |__|___| /\___ / \___ >__| | __/|__| |__|___| /__| -// \/ \//_____/ \/ |__| \/ -// -// This file contains functions for fingerprinting SSH keys -// // The database is used in checkKeyFingerprint however most of these functions probably belong in a module // checkKeyFingerprint only checks if key fingerprint has been used as public key, @@ -41,29 +27,6 @@ func checkKeyFingerprint(ctx context.Context, fingerprint string) error { return nil } -func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) { - // Calculate fingerprint. - tmpPath, err := writeTmpKeyFile(publicKeyContent) - if err != nil { - return "", err - } - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpPath, err) - } - }() - stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath) - if err != nil { - if strings.Contains(stderr, "is not a public key file") { - return "", ErrKeyUnableVerify{stderr} - } - return "", util.NewInvalidArgumentErrorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr) - } else if len(stdout) < 2 { - return "", util.NewInvalidArgumentErrorf("not enough output for calculating fingerprint: %s", stdout) - } - return strings.Split(stdout, " ")[1], nil -} - func calcFingerprintNative(publicKeyContent string) (string, error) { // Calculate fingerprint. pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) @@ -75,15 +38,12 @@ func calcFingerprintNative(publicKeyContent string) (string, error) { // CalcFingerprint calculate public key's fingerprint func CalcFingerprint(publicKeyContent string) (string, error) { - // Call the method based on configuration - useNative := setting.SSH.KeygenPath == "" - calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen) - fp, err := calcFn(publicKeyContent) + fp, err := calcFingerprintNative(publicKeyContent) if err != nil { if IsErrKeyUnableVerify(err) { return "", err } - return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err) + return "", fmt.Errorf("CalcFingerprint: %w", err) } return fp, nil } diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go index c843525718..46dcf4d894 100644 --- a/models/asymkey/ssh_key_parse.go +++ b/models/asymkey/ssh_key_parse.go @@ -13,12 +13,9 @@ import ( "errors" "fmt" "math/big" - "os" - "strconv" "strings" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -175,20 +172,9 @@ func CheckPublicKeyString(content string) (_ string, err error) { return content, nil } - var ( - fnName string - keyType string - length int - ) - if len(setting.SSH.KeygenPath) == 0 { - fnName = "SSHNativeParsePublicKey" - keyType, length, err = SSHNativeParsePublicKey(content) - } else { - fnName = "SSHKeyGenParsePublicKey" - keyType, length, err = SSHKeyGenParsePublicKey(content) - } + keyType, length, err := SSHNativeParsePublicKey(content) if err != nil { - return "", fmt.Errorf("%s: %w", fnName, err) + return "", fmt.Errorf("SSHNativeParsePublicKey: %w", err) } log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length) @@ -258,56 +244,3 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { } return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type()) } - -// writeTmpKeyFile writes key content to a temporary file -// and returns the name of that file, along with any possible errors. -func writeTmpKeyFile(content string) (string, error) { - tmpFile, err := os.CreateTemp(setting.SSH.KeyTestPath, "gitea_keytest") - if err != nil { - return "", fmt.Errorf("TempFile: %w", err) - } - defer tmpFile.Close() - - if _, err = tmpFile.WriteString(content); err != nil { - return "", fmt.Errorf("WriteString: %w", err) - } - return tmpFile.Name(), nil -} - -// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen. -func SSHKeyGenParsePublicKey(key string) (string, int, error) { - tmpName, err := writeTmpKeyFile(key) - if err != nil { - return "", 0, fmt.Errorf("writeTmpKeyFile: %w", err) - } - defer func() { - if err := util.Remove(tmpName); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpName, err) - } - }() - - keygenPath := setting.SSH.KeygenPath - if len(keygenPath) == 0 { - keygenPath = "ssh-keygen" - } - - stdout, stderr, err := process.GetManager().Exec("SSHKeyGenParsePublicKey", keygenPath, "-lf", tmpName) - if err != nil { - return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr) - } - if strings.Contains(stdout, "is not a public key file") { - return "", 0, ErrKeyUnableVerify{stdout} - } - - fields := strings.Split(stdout, " ") - if len(fields) < 4 { - return "", 0, fmt.Errorf("invalid public key line: %s", stdout) - } - - keyType := strings.Trim(fields[len(fields)-1], "()\r\n") - length, err := strconv.ParseInt(fields[0], 10, 32) - if err != nil { - return "", 0, err - } - return strings.ToLower(keyType), int(length), nil -} diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index b33d16030d..21e4ddf62e 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -18,7 +18,6 @@ import ( "github.com/42wim/sshsig" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_SSHParsePublicKey(t *testing.T) { @@ -45,27 +44,6 @@ func Test_SSHParsePublicKey(t *testing.T) { assert.Equal(t, tc.keyType, keyTypeN) assert.Equal(t, tc.length, lengthN) }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - keyTypeK, lengthK, err := SSHKeyGenParsePublicKey(tc.content) - if err != nil { - // Some servers do not support ecdsa format. - if !strings.Contains(err.Error(), "line 1 too long:") { - require.NoError(t, err) - } - } - assert.Equal(t, tc.keyType, keyTypeK) - assert.Equal(t, tc.length, lengthK) - }) - t.Run("SSHParseKeyNative", func(t *testing.T) { - keyTypeK, lengthK, err := SSHNativeParsePublicKey(tc.content) - require.NoError(t, err) - - assert.Equal(t, tc.keyType, keyTypeK) - assert.Equal(t, tc.length, lengthK) - }) }) } } @@ -186,14 +164,6 @@ func Test_calcFingerprint(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.fp, fpN) }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - fpK, err := calcFingerprintSSHKeygen(tc.content) - assert.NoError(t, err) - assert.Equal(t, tc.fp, fpK) - }) }) } } diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 5e9c012c05..7ddf7ee901 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -845,6 +845,7 @@ func DeleteOrphanedIssues(ctx context.Context) error { // Remove issue attachment files. for i := range attachmentPaths { + // FIXME: it's not right, because the attachment might not be on local filesystem system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i]) } return nil diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index fe6de9c517..7da426fef0 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/tempdir" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/testlogger" @@ -114,15 +115,16 @@ func MainTest(m *testing.M) { setting.CustomConf = giteaConf } - tmpDataPath, err := os.MkdirTemp("", "data") + tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data") if err != nil { testlogger.Fatalf("Unable to create temporary data path %v\n", err) } + defer cleanup() setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") setting.AppDataPath = tmpDataPath - unittest.InitSettings() + unittest.InitSettingsForTesting() if err = git.InitFull(context.Background()); err != nil { testlogger.Fatalf("Unable to InitFull: %v\n", err) } @@ -134,8 +136,5 @@ func MainTest(m *testing.M) { if err := removeAllWithRetry(setting.RepoRootPath); err != nil { fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) } - if err := removeAllWithRetry(tmpDataPath); err != nil { - fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) - } os.Exit(exitStatus) } diff --git a/models/repo/upload.go b/models/repo/upload.go index cae11df96a..fb57fb6c51 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -51,14 +51,10 @@ func init() { db.RegisterModel(new(Upload)) } -// UploadLocalPath returns where uploads is stored in local file system based on given UUID. -func UploadLocalPath(uuid string) string { - return filepath.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid) -} - -// LocalPath returns where uploads are temporarily stored in local file system. +// LocalPath returns where uploads are temporarily stored in local file system based on given UUID. func (upload *Upload) LocalPath() string { - return UploadLocalPath(upload.UUID) + uuid := upload.UUID + return setting.AppDataTempDir("repo-uploads").JoinPath(uuid[0:1], uuid[1:2], uuid) } // NewUpload creates a new upload object. diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 7a9ca9698d..cb60cf5f85 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/tempdir" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" @@ -35,8 +36,8 @@ func fatalTestError(fmtStr string, args ...any) { os.Exit(1) } -// InitSettings initializes config provider and load common settings for tests -func InitSettings() { +// InitSettingsForTesting initializes config provider and load common settings for tests +func InitSettingsForTesting() { setting.IsInTesting = true log.OsExiter = func(code int) { if code != 0 { @@ -75,7 +76,7 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) giteaRoot = test.SetupGiteaRoot() setting.CustomPath = filepath.Join(giteaRoot, "custom") - InitSettings() + InitSettingsForTesting() fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} if err := CreateTestEngine(fixturesOpts); err != nil { @@ -92,15 +93,19 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { setting.SSH.Domain = "try.gitea.io" setting.Database.Type = "sqlite3" setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" - repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos") + repoRootPath, cleanup1, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("repos") if err != nil { fatalTestError("TempDir: %v\n", err) } + defer cleanup1() + setting.RepoRootPath = repoRootPath - appDataPath, err := os.MkdirTemp(os.TempDir(), "appdata") + appDataPath, cleanup2, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("appdata") if err != nil { fatalTestError("TempDir: %v\n", err) } + defer cleanup2() + setting.AppDataPath = appDataPath setting.AppWorkPath = giteaRoot setting.StaticRootPath = giteaRoot @@ -153,13 +158,6 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { fatalTestError("tear down failed: %v\n", err) } } - - if err = util.RemoveAll(repoRootPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } - if err = util.RemoveAll(appDataPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } os.Exit(exitStatus) } diff --git a/modules/git/blame.go b/modules/git/blame.go index d1d732c716..6eb583a6b9 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -11,7 +11,7 @@ import ( "os" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/setting" ) // BlamePart represents block of blame - continuous lines with one sha @@ -29,12 +29,13 @@ type BlameReader struct { bufferedReader *bufio.Reader done chan error lastSha *string - ignoreRevsFile *string + ignoreRevsFile string objectFormat ObjectFormat + cleanupFuncs []func() } func (r *BlameReader) UsesIgnoreRevs() bool { - return r.ignoreRevsFile != nil + return r.ignoreRevsFile != "" } // NextPart returns next part of blame (sequential code lines with the same commit) @@ -122,36 +123,37 @@ func (r *BlameReader) Close() error { r.bufferedReader = nil _ = r.reader.Close() _ = r.output.Close() - if r.ignoreRevsFile != nil { - _ = util.Remove(*r.ignoreRevsFile) + for _, cleanup := range r.cleanupFuncs { + if cleanup != nil { + cleanup() + } } return err } // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { - var ignoreRevsFile *string - if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { - ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) - } - - cmd := NewCommandNoGlobals("blame", "--porcelain") - if ignoreRevsFile != nil { - // Possible improvement: use --ignore-revs-file /dev/stdin on unix - // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. - cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile) - } - cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) reader, stdout, err := os.Pipe() if err != nil { - if ignoreRevsFile != nil { - _ = util.Remove(*ignoreRevsFile) - } return nil, err } - done := make(chan error, 1) + cmd := NewCommandNoGlobals("blame", "--porcelain") + var ignoreRevsFileName string + var ignoreRevsFileCleanup func() // TODO: maybe it should check the returned err in a defer func to make sure the cleanup could always be executed correctly + if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { + ignoreRevsFileName, ignoreRevsFileCleanup = tryCreateBlameIgnoreRevsFile(commit) + if ignoreRevsFileName != "" { + // Possible improvement: use --ignore-revs-file /dev/stdin on unix + // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. + cmd.AddOptionValues("--ignore-revs-file", ignoreRevsFileName) + } + } + + cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) + + done := make(chan error, 1) go func() { stderr := bytes.Buffer{} // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" @@ -169,40 +171,44 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath }() bufferedReader := bufio.NewReader(reader) - return &BlameReader{ output: stdout, reader: reader, bufferedReader: bufferedReader, done: done, - ignoreRevsFile: ignoreRevsFile, + ignoreRevsFile: ignoreRevsFileName, objectFormat: objectFormat, + cleanupFuncs: []func(){ignoreRevsFileCleanup}, }, nil } -func tryCreateBlameIgnoreRevsFile(commit *Commit) *string { +func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) { entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file: GetTreeEntryByPath: %v", err) + return "", nil } r, err := entry.Blob().DataAsync() if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err) + return "", nil } defer r.Close() - f, err := os.CreateTemp("", "gitea_git-blame-ignore-revs") + f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs") if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file data: CreateTempFileRandom: %v", err) + return "", nil } - + filename := f.Name() _, err = io.Copy(f, r) _ = f.Close() if err != nil { - _ = util.Remove(f.Name()) - return nil + cleanup() + log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err) + return "", nil } - return util.ToPointer(f.Name()) + return filename, cleanup } diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go index 99c23429e2..c0a97bed3b 100644 --- a/modules/git/blame_sha256_test.go +++ b/modules/git/blame_sha256_test.go @@ -7,10 +7,13 @@ import ( "context" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestReadingBlameOutputSha256(t *testing.T) { + setting.AppDataPath = t.TempDir() ctx, cancel := context.WithCancel(t.Context()) defer cancel() diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 36b5fb9349..809d6fbcf7 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -7,10 +7,13 @@ import ( "context" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestReadingBlameOutput(t *testing.T) { + setting.AppDataPath = t.TempDir() ctx, cancel := context.WithCancel(t.Context()) defer cancel() diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 5472842b76..58ba01cabc 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -10,18 +10,19 @@ import ( "testing" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/tempdir" "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" ) func testRun(m *testing.M) error { - gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") + gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home") if err != nil { return fmt.Errorf("unable to create temp dir: %w", err) } - defer util.RemoveAll(gitHomePath) + defer cleanup() + setting.Git.HomePath = gitHomePath if err = InitFull(context.Background()); err != nil { diff --git a/modules/git/repo.go b/modules/git/repo.go index 6459adf851..45937a8d5f 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -18,6 +18,7 @@ import ( "time" "code.gitea.io/gitea/modules/proxy" + "code.gitea.io/gitea/modules/setting" ) // GPGSettings represents the default GPG settings for this repository @@ -266,11 +267,11 @@ func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch // CreateBundle create bundle content to the target path func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error { - tmp, err := os.MkdirTemp(os.TempDir(), "gitea-bundle") + tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle") if err != nil { return err } - defer os.RemoveAll(tmp) + defer cleanup() env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) _, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 1c7fcc063e..443a3a20d1 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -10,8 +10,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/setting" ) // ReadTreeToIndex reads a treeish to the index @@ -59,26 +58,18 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena } }() - removeDirFn := func(dir string) func() { // it can't use the return value "tmpDir" directly because it is empty when error occurs - return func() { - if err := util.RemoveAll(dir); err != nil { - log.Error("failed to remove tmp index dir: %v", err) - } - } - } - - tmpDir, err = os.MkdirTemp("", "index") + tmpDir, cancel, err = setting.AppDataTempDir("git-repo-content").MkdirTempRandom("index") if err != nil { return "", "", nil, err } tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index") - cancel = removeDirFn(tmpDir) + err = repo.ReadTreeToIndex(treeish, tmpIndexFilename) if err != nil { return "", "", cancel, err } - return tmpIndexFilename, tmpDir, cancel, err + return tmpIndexFilename, tmpDir, cancel, nil } // EmptyIndex empties the index diff --git a/modules/git/repo_language_stats_test.go b/modules/git/repo_language_stats_test.go index 81f130bacb..12ce958c6e 100644 --- a/modules/git/repo_language_stats_test.go +++ b/modules/git/repo_language_stats_test.go @@ -9,11 +9,14 @@ import ( "path/filepath" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRepository_GetLanguageStats(t *testing.T) { + setting.AppDataPath = t.TempDir() repoPath := filepath.Join(testReposDir, "language_stats_repo") gitRepo, err := openRepositoryWithDefaultContext(repoPath) require.NoError(t, err) diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index f708457853..39861ade12 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -12,11 +12,9 @@ import ( "runtime" "strings" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) // RegisterRenderers registers all supported third part renderers according settings @@ -88,16 +86,11 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. if p.IsInputFile { // write to temp file - f, err := os.CreateTemp("", "gitea_input") + f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("gitea_input") if err != nil { return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) } - tmpPath := f.Name() - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err) - } - }() + defer cleanup() _, err = io.Copy(f, input) if err != nil { diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go index 4ab45edcec..0cd657cd44 100644 --- a/modules/packages/hashed_buffer.go +++ b/modules/packages/hashed_buffer.go @@ -6,6 +6,7 @@ package packages import ( "io" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util/filebuffer" ) @@ -34,11 +35,11 @@ func NewHashedBuffer() (*HashedBuffer, error) { // NewHashedBufferWithSize creates a hashed buffer with a specific memory size func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { - b, err := filebuffer.New(maxMemorySize) + tempDir, err := setting.AppDataTempDir("package-hashed-buffer").MkdirAllSub("") if err != nil { return nil, err } - + b := filebuffer.New(maxMemorySize, tempDir) hash := NewMultiHasher() combinedWriter := io.MultiWriter(b, hash) diff --git a/modules/packages/hashed_buffer_test.go b/modules/packages/hashed_buffer_test.go index 564e782f18..5104c1fb25 100644 --- a/modules/packages/hashed_buffer_test.go +++ b/modules/packages/hashed_buffer_test.go @@ -9,10 +9,13 @@ import ( "strings" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestHashedBuffer(t *testing.T) { + setting.AppDataPath = t.TempDir() cases := []struct { MaxMemorySize int Data string diff --git a/modules/packages/nuget/symbol_extractor_test.go b/modules/packages/nuget/symbol_extractor_test.go index fa1b80ee82..711ad6d096 100644 --- a/modules/packages/nuget/symbol_extractor_test.go +++ b/modules/packages/nuget/symbol_extractor_test.go @@ -9,6 +9,8 @@ import ( "encoding/base64" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) @@ -17,6 +19,7 @@ fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==` func TestExtractPortablePdb(t *testing.T) { + setting.AppDataPath = t.TempDir() createArchive := func(name string, content []byte) []byte { var buf bytes.Buffer archive := zip.NewWriter(&buf) diff --git a/modules/repository/temp.go b/modules/repository/temp.go index 76b9bda4ad..d7253d9e02 100644 --- a/modules/repository/temp.go +++ b/modules/repository/temp.go @@ -4,41 +4,19 @@ package repository import ( + "context" "fmt" - "os" - "path/filepath" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) -// LocalCopyPath returns the local repository temporary copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return filepath.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - // CreateTemporaryPath creates a temporary path -func CreateTemporaryPath(prefix string) (string, error) { - if err := os.MkdirAll(LocalCopyPath(), os.ModePerm); err != nil { - log.Error("Unable to create localcopypath directory: %s (%v)", LocalCopyPath(), err) - return "", fmt.Errorf("Failed to create localcopypath directory %s: %w", LocalCopyPath(), err) - } - basePath, err := os.MkdirTemp(LocalCopyPath(), prefix+".git") +func CreateTemporaryPath(prefix string) (string, context.CancelFunc, error) { + basePath, cleanup, err := setting.AppDataTempDir("local-repo").MkdirTempRandom(prefix + ".git") if err != nil { log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) - return "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err) + return "", nil, fmt.Errorf("failed to create dir %s-*.git: %w", prefix, err) } - return basePath, nil -} - -// RemoveTemporaryPath removes the temporary path -func RemoveTemporaryPath(basePath string) error { - if _, err := os.Stat(basePath); !os.IsNotExist(err) { - return util.RemoveAll(basePath) - } - return nil + return basePath, cleanup, nil } diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 3f618cfd64..845f6d4e12 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -6,8 +6,6 @@ package setting import ( "fmt" "math" - "os" - "path/filepath" "github.com/dustin/go-humanize" ) @@ -67,14 +65,10 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { return err } - Packages.ChunkedUploadPath = filepath.ToSlash(sec.Key("CHUNKED_UPLOAD_PATH").MustString("tmp/package-upload")) - if !filepath.IsAbs(Packages.ChunkedUploadPath) { - Packages.ChunkedUploadPath = filepath.ToSlash(filepath.Join(AppDataPath, Packages.ChunkedUploadPath)) - } - if HasInstallLock(rootCfg) { - if err := os.MkdirAll(Packages.ChunkedUploadPath, os.ModePerm); err != nil { - return fmt.Errorf("unable to create chunked upload directory: %s (%v)", Packages.ChunkedUploadPath, err) + Packages.ChunkedUploadPath, err = AppDataTempDir("package-upload").MkdirAllSub("") + if err != nil { + return fmt.Errorf("unable to create chunked upload directory: %w", err) } } diff --git a/modules/setting/path.go b/modules/setting/path.go index 0fdc305aa1..f51457a620 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/tempdir" ) var ( @@ -196,3 +197,18 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP CustomPath = tmpCustomPath.Value CustomConf = tmpCustomConf.Value } + +// 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. +// Gitea only creates subdirectories under it, but not the APP_TEMP_PATH directory itself. +// * When APP_TEMP_PATH="/tmp": the managed temp directory is "/tmp/gitea-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, "gitea-tmp/"+sub) + } + if AppDataPath == "" { + panic("setting.AppDataPath is not set") + } + return tempdir.New(AppDataPath, "tmp/"+sub) +} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 43bfb3256d..f99c854e4f 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -62,17 +62,11 @@ var ( // Repository upload settings Upload struct { Enabled bool - TempPath string AllowedTypes string FileMaxSize int64 MaxFiles int } `ini:"-"` - // Repository local settings - Local struct { - LocalCopyPath string - } `ini:"-"` - // Pull request settings PullRequest struct { WorkInProgressPrefixes []string @@ -181,25 +175,16 @@ var ( // Repository upload settings Upload: struct { Enabled bool - TempPath string AllowedTypes string FileMaxSize int64 MaxFiles int }{ Enabled: true, - TempPath: "data/tmp/uploads", AllowedTypes: "", FileMaxSize: 50, MaxFiles: 5, }, - // Repository local settings - Local: struct { - LocalCopyPath string - }{ - LocalCopyPath: "tmp/local-repo", - }, - // Pull request settings PullRequest: struct { WorkInProgressPrefixes []string @@ -308,8 +293,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { log.Fatal("Failed to map Repository.Editor settings: %v", err) } else if err = rootCfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil { log.Fatal("Failed to map Repository.Upload settings: %v", err) - } else if err = rootCfg.Section("repository.local").MapTo(&Repository.Local); err != nil { - log.Fatal("Failed to map Repository.Local settings: %v", err) } else if err = rootCfg.Section("repository.pull-request").MapTo(&Repository.PullRequest); err != nil { log.Fatal("Failed to map Repository.PullRequest settings: %v", err) } @@ -361,10 +344,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { } } - if !filepath.IsAbs(Repository.Upload.TempPath) { - Repository.Upload.TempPath = filepath.Join(AppWorkPath, Repository.Upload.TempPath) - } - if err := loadRepoArchiveFrom(rootCfg); err != nil { log.Fatal("loadRepoArchiveFrom: %v", err) } diff --git a/modules/setting/server.go b/modules/setting/server.go index e15b790906..ca635c8abe 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "net" "net/url" + "os" "path/filepath" "strconv" "strings" @@ -59,6 +60,8 @@ var ( // AssetVersion holds a opaque value that is used for cache-busting assets AssetVersion string + appTempPathInternal string // the temporary path for the app, it is only an internal variable, do not use it, always use AppDataTempDir + Protocol Scheme UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"` ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` @@ -330,6 +333,19 @@ func loadServerFrom(rootCfg ConfigProvider) { if !filepath.IsAbs(AppDataPath) { AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) } + if IsInTesting && HasInstallLock(rootCfg) { + // FIXME: in testing, the "app data" directory is not correctly initialized before loading settings + if _, err := os.Stat(AppDataPath); err != nil { + _ = os.MkdirAll(AppDataPath, os.ModePerm) + } + } + + appTempPathInternal = sec.Key("APP_TEMP_PATH").String() + if appTempPathInternal != "" { + if _, err := os.Stat(appTempPathInternal); err != nil { + log.Fatal("APP_TEMP_PATH %q is not accessible: %v", appTempPathInternal, err) + } + } EnableGzip = sec.Key("ENABLE_GZIP").MustBool() EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index 46eb49bfd4..da8cdf58d2 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -4,7 +4,6 @@ package setting import ( - "os" "path/filepath" "strings" "text/template" @@ -31,8 +30,6 @@ var SSH = struct { ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` ServerMACs []string `ini:"SSH_SERVER_MACS"` ServerHostKeys []string `ini:"SSH_SERVER_HOST_KEYS"` - KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` - KeygenPath string `ini:"SSH_KEYGEN_PATH"` AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` AuthorizedKeysCommandTemplate string `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"` @@ -57,7 +54,6 @@ var SSH = struct { ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"}, ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"}, ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, - KeygenPath: "", MinimumKeySizeCheck: true, MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, @@ -123,7 +119,6 @@ func loadSSHFrom(rootCfg ConfigProvider) { if len(serverMACs) > 0 { SSH.ServerMACs = serverMACs } - SSH.KeyTestPath = os.TempDir() if err = sec.MapTo(&SSH); err != nil { log.Fatal("Failed to map SSH settings: %v", err) } @@ -133,7 +128,6 @@ func loadSSHFrom(rootCfg ConfigProvider) { } } - SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").String() SSH.Port = sec.Key("SSH_PORT").MustInt(22) SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) diff --git a/modules/ssh/init.go b/modules/ssh/init.go index 21d4f89936..fdc11632e2 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -32,11 +32,6 @@ func Init() error { builtinUnused() - // FIXME: why 0o644 for a directory ..... - if err := os.MkdirAll(setting.SSH.KeyTestPath, 0o644); err != nil { - return fmt.Errorf("failed to create directory %q for ssh key test: %w", setting.SSH.KeyTestPath, err) - } - if len(setting.SSH.TrustedUserCAKeys) > 0 && setting.SSH.AuthorizedPrincipalsEnabled { caKeysFileName := setting.SSH.TrustedUserCAKeysFile caKeysFileDir := filepath.Dir(caKeysFileName) diff --git a/modules/tempdir/tempdir.go b/modules/tempdir/tempdir.go new file mode 100644 index 0000000000..22c2e4ea16 --- /dev/null +++ b/modules/tempdir/tempdir.go @@ -0,0 +1,112 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package tempdir + +import ( + "os" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +type TempDir struct { + // base is the base directory for temporary files, it must exist before accessing and won't be created automatically. + // for example: base="/system-tmpdir", sub="gitea-tmp" + base, sub string +} + +func (td *TempDir) JoinPath(elems ...string) string { + return filepath.Join(append([]string{td.base, td.sub}, elems...)...) +} + +// MkdirAllSub works like os.MkdirAll, but the base directory must exist +func (td *TempDir) MkdirAllSub(dir string) (string, error) { + if _, err := os.Stat(td.base); err != nil { + return "", err + } + full := filepath.Join(td.base, td.sub, dir) + if err := os.MkdirAll(full, os.ModePerm); err != nil { + return "", err + } + return full, nil +} + +func (td *TempDir) prepareDirWithPattern(elems ...string) (dir, pattern string, err error) { + if _, err = os.Stat(td.base); err != nil { + return "", "", err + } + dir, pattern = filepath.Split(filepath.Join(append([]string{td.base, td.sub}, elems...)...)) + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return "", "", err + } + return dir, pattern, nil +} + +// MkdirTempRandom works like os.MkdirTemp, the last path field is the "pattern" +func (td *TempDir) MkdirTempRandom(elems ...string) (string, func(), error) { + dir, pattern, err := td.prepareDirWithPattern(elems...) + if err != nil { + return "", nil, err + } + dir, err = os.MkdirTemp(dir, pattern) + if err != nil { + return "", nil, err + } + return dir, func() { + if err := util.RemoveAll(dir); err != nil { + log.Error("Failed to remove temp directory %s: %v", dir, err) + } + }, nil +} + +// CreateTempFileRandom works like os.CreateTemp, the last path field is the "pattern" +func (td *TempDir) CreateTempFileRandom(elems ...string) (*os.File, func(), error) { + dir, pattern, err := td.prepareDirWithPattern(elems...) + if err != nil { + return nil, nil, err + } + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return nil, nil, err + } + filename := f.Name() + return f, func() { + _ = f.Close() + if err := util.Remove(filename); err != nil { + log.Error("Unable to remove temporary file: %s: Error: %v", filename, err) + } + }, err +} + +func (td *TempDir) RemoveOutdated(d time.Duration) { + var remove func(path string) + remove = func(path string) { + entries, _ := os.ReadDir(path) + for _, entry := range entries { + full := filepath.Join(path, entry.Name()) + if entry.IsDir() { + remove(full) + _ = os.Remove(full) + continue + } + info, err := entry.Info() + if err == nil && time.Since(info.ModTime()) > d { + _ = os.Remove(full) + } + } + } + remove(td.JoinPath("")) +} + +// New create a new TempDir instance, "base" must be an existing directory, +// "sub" could be a multi-level directory and will be created if not exist +func New(base, sub string) *TempDir { + return &TempDir{base: base, sub: sub} +} + +func OsTempDir(sub string) *TempDir { + return New(os.TempDir(), sub) +} diff --git a/modules/tempdir/tempdir_test.go b/modules/tempdir/tempdir_test.go new file mode 100644 index 0000000000..d6afcb7bed --- /dev/null +++ b/modules/tempdir/tempdir_test.go @@ -0,0 +1,75 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package tempdir + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTempDir(t *testing.T) { + base := t.TempDir() + + t.Run("Create", func(t *testing.T) { + td := New(base, "sub1/sub2") // make sure the sub dir supports "/" in the path + assert.Equal(t, filepath.Join(base, "sub1", "sub2"), td.JoinPath()) + assert.Equal(t, filepath.Join(base, "sub1", "sub2/test"), td.JoinPath("test")) + + t.Run("MkdirTempRandom", func(t *testing.T) { + s, cleanup, err := td.MkdirTempRandom("foo") + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(s, filepath.Join(base, "sub1/sub2", "foo"))) + + _, err = os.Stat(s) + assert.NoError(t, err) + cleanup() + _, err = os.Stat(s) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("CreateTempFileRandom", func(t *testing.T) { + f, cleanup, err := td.CreateTempFileRandom("foo", "bar") + filename := f.Name() + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(filename, filepath.Join(base, "sub1/sub2", "foo", "bar"))) + _, err = os.Stat(filename) + assert.NoError(t, err) + cleanup() + _, err = os.Stat(filename) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("RemoveOutDated", func(t *testing.T) { + fa1, _, err := td.CreateTempFileRandom("dir-a", "f1") + assert.NoError(t, err) + fa2, _, err := td.CreateTempFileRandom("dir-a", "f2") + assert.NoError(t, err) + _ = os.Chtimes(fa2.Name(), time.Now().Add(-time.Hour), time.Now().Add(-time.Hour)) + fb1, _, err := td.CreateTempFileRandom("dir-b", "f1") + assert.NoError(t, err) + _ = os.Chtimes(fb1.Name(), time.Now().Add(-time.Hour), time.Now().Add(-time.Hour)) + _, _, _ = fa1.Close(), fa2.Close(), fb1.Close() + + td.RemoveOutdated(time.Minute) + + _, err = os.Stat(fa1.Name()) + assert.NoError(t, err) + _, err = os.Stat(fa2.Name()) + assert.ErrorIs(t, err, os.ErrNotExist) + _, err = os.Stat(fb1.Name()) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + }) + + t.Run("BaseNotExist", func(t *testing.T) { + td := New(filepath.Join(base, "not-exist"), "sub") + _, _, err := td.MkdirTempRandom("foo") + assert.ErrorIs(t, err, os.ErrNotExist) + }) +} diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 26cd1eb348..460b9dc190 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -56,7 +56,7 @@ var testMetas = map[string]string{ } func TestMain(m *testing.M) { - unittest.InitSettings() + unittest.InitSettingsForTesting() if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } diff --git a/modules/util/filebuffer/file_backed_buffer.go b/modules/util/filebuffer/file_backed_buffer.go index 739543e297..0731ba30c8 100644 --- a/modules/util/filebuffer/file_backed_buffer.go +++ b/modules/util/filebuffer/file_backed_buffer.go @@ -7,16 +7,10 @@ import ( "bytes" "errors" "io" - "math" "os" ) -var ( - // ErrInvalidMemorySize occurs if the memory size is not in a valid range - ErrInvalidMemorySize = errors.New("Memory size must be greater 0 and lower math.MaxInt32") - // ErrWriteAfterRead occurs if Write is called after a read operation - ErrWriteAfterRead = errors.New("Write is unsupported after a read operation") -) +var ErrWriteAfterRead = errors.New("write is unsupported after a read operation") // occurs if Write is called after a read operation type readAtSeeker interface { io.ReadSeeker @@ -30,34 +24,17 @@ type FileBackedBuffer struct { maxMemorySize int64 size int64 buffer bytes.Buffer + tempDir string file *os.File reader readAtSeeker } // New creates a file backed buffer with a specific maximum memory size -func New(maxMemorySize int) (*FileBackedBuffer, error) { - if maxMemorySize < 0 || maxMemorySize > math.MaxInt32 { - return nil, ErrInvalidMemorySize - } - +func New(maxMemorySize int, tempDir string) *FileBackedBuffer { return &FileBackedBuffer{ maxMemorySize: int64(maxMemorySize), - }, nil -} - -// CreateFromReader creates a file backed buffer and copies the provided reader data into it. -func CreateFromReader(r io.Reader, maxMemorySize int) (*FileBackedBuffer, error) { - b, err := New(maxMemorySize) - if err != nil { - return nil, err + tempDir: tempDir, } - - _, err = io.Copy(b, r) - if err != nil { - return nil, err - } - - return b, nil } // Write implements io.Writer @@ -73,7 +50,7 @@ func (b *FileBackedBuffer) Write(p []byte) (int, error) { n, err = b.file.Write(p) } else { if b.size+int64(len(p)) > b.maxMemorySize { - b.file, err = os.CreateTemp("", "gitea-buffer-") + b.file, err = os.CreateTemp(b.tempDir, "gitea-buffer-") if err != nil { return 0, err } @@ -148,7 +125,7 @@ func (b *FileBackedBuffer) Seek(offset int64, whence int) (int64, error) { func (b *FileBackedBuffer) Close() error { if b.file != nil { err := b.file.Close() - os.Remove(b.file.Name()) + _ = os.Remove(b.file.Name()) b.file = nil return err } diff --git a/modules/util/filebuffer/file_backed_buffer_test.go b/modules/util/filebuffer/file_backed_buffer_test.go index 16d5a1965f..3f13c6ac7b 100644 --- a/modules/util/filebuffer/file_backed_buffer_test.go +++ b/modules/util/filebuffer/file_backed_buffer_test.go @@ -21,7 +21,8 @@ func TestFileBackedBuffer(t *testing.T) { } for _, c := range cases { - buf, err := CreateFromReader(strings.NewReader(c.Data), c.MaxMemorySize) + buf := New(c.MaxMemorySize, t.TempDir()) + _, err := io.Copy(buf, strings.NewReader(c.Data)) assert.NoError(t, err) assert.EqualValues(t, len(c.Data), buf.Size()) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 96c99615f5..d7da975a21 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3287,8 +3287,6 @@ config.ssh_domain = SSH Server Domain config.ssh_port = Port config.ssh_listen_port = Listen Port config.ssh_root_path = Root Path -config.ssh_key_test_path = Key Test Path -config.ssh_keygen_path = Keygen ('ssh-keygen') Path config.ssh_minimum_key_size_check = Minimum Key Size Check config.ssh_minimum_key_sizes = Minimum Key Sizes diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index f4ac9d769b..61606f8c5f 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -29,7 +29,6 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" repo_service "code.gitea.io/gitea/services/repository" @@ -303,17 +302,12 @@ var ( func dummyInfoRefs(ctx *context.Context) { infoRefsOnce.Do(func() { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-info-refs-cache") + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-info-refs-cache") if err != nil { log.Error("Failed to create temp dir for git-receive-pack cache: %v", err) return } - - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() + defer cleanup() if err := git.InitRepository(ctx, tmpDir, true, git.Sha1ObjectFormat.Name()); err != nil { log.Error("Failed to init bare repo for git-receive-pack cache: %v", err) diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 655291d25c..efda9bda58 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -109,17 +109,13 @@ func LFSLocks(ctx *context.Context) { } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("locks") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("locks") if err != nil { log.Error("Failed to create temporary path: %v", err) ctx.ServerError("LFSLocks", err) return } - defer func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("LFSLocks: RemoveTemporaryPath: %v", err) - } - }() + defer cleanup() if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ Bare: true, diff --git a/services/pull/patch.go b/services/pull/patch.go index 68f3f02669..7a24237724 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -355,23 +355,19 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * } // 3b. Create a plain patch from head to base - tmpPatchFile, err := os.CreateTemp("", "patch") + tmpPatchFile, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) return false, fmt.Errorf("unable to create temporary patch file! Error: %w", err) } - defer func() { - _ = util.Remove(tmpPatchFile.Name()) - }() + defer cleanup() if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil { - tmpPatchFile.Close() log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) return false, fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } stat, err := tmpPatchFile.Stat() if err != nil { - tmpPatchFile.Close() return false, fmt.Errorf("unable to stat patch file: %w", err) } patchPath := tmpPatchFile.Name() diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 3f33370798..d543e3d4a3 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -74,11 +74,13 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("pull") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("pull") if err != nil { log.Error("CreateTemporaryPath[%-v]: %v", pr, err) return nil, nil, err } + cancel = cleanup + prCtx = &prContext{ Context: ctx, tmpBasePath: tmpBasePath, @@ -86,11 +88,6 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) outbuf: &strings.Builder{}, errbuf: &strings.Builder{}, } - cancel = func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("Error whilst removing removing temporary repo for %-v: %v", pr, err) - } - } baseRepoPath := pr.BaseRepo.RepoPath() headRepoPath := pr.HeadRepo.RepoPath() diff --git a/services/repository/create.go b/services/repository/create.go index 050544ce11..5a9b2ecd2a 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -29,7 +29,6 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates/vars" - "code.gitea.io/gitea/modules/util" ) // CreateRepoOptions contains the create repository options @@ -150,15 +149,11 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re // Initialize repository according to user's choice. if opts.AutoInit { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("repos-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) - } - }() + defer cleanup() if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil { return fmt.Errorf("prepareRepoCommit: %w", err) diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 1969676ab4..493ff9998d 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -30,23 +30,24 @@ type TemporaryUploadRepository struct { repo *repo_model.Repository gitRepo *git.Repository basePath string + cleanup func() } // NewTemporaryUploadRepository creates a new temporary upload repository func NewTemporaryUploadRepository(repo *repo_model.Repository) (*TemporaryUploadRepository, error) { - basePath, err := repo_module.CreateTemporaryPath("upload") + basePath, cleanup, err := repo_module.CreateTemporaryPath("upload") if err != nil { return nil, err } - t := &TemporaryUploadRepository{repo: repo, basePath: basePath} + t := &TemporaryUploadRepository{repo: repo, basePath: basePath, cleanup: cleanup} return t, nil } // Close the repository cleaning up all files func (t *TemporaryUploadRepository) Close() { defer t.gitRepo.Close() - if err := repo_module.RemoveTemporaryPath(t.basePath); err != nil { - log.Error("Failed to remove temporary path %s: %v", t.basePath, err) + if t.cleanup != nil { + t.cleanup() } } diff --git a/services/repository/generate.go b/services/repository/generate.go index 1c4d3b7b63..b02f7c9482 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -255,16 +256,11 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() + defer cleanup() if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { return fmt.Errorf("generateRepoCommit: %w", err) diff --git a/services/repository/repository.go b/services/repository/repository.go index 10f175d989..cd56010546 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -13,7 +13,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/graceful" @@ -102,8 +101,6 @@ func Init(ctx context.Context) error { if err := repo_module.LoadRepoConfig(); err != nil { return err } - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repositories", repo_module.LocalCopyPath()) if err := initPushQueue(); err != nil { return err } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index b21f46639d..45a08dc5d6 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -102,15 +102,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model hasDefaultBranch := gitrepo.IsBranchExist(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch) - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() cloneOpts := git.CloneRepoOptions{ Bare: true, @@ -264,15 +260,11 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return fmt.Errorf("InitWiki: %w", err) } - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{ Bare: true, diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 88dadeb3ee..806347c720 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -69,10 +69,6 @@ {{if not .SSH.StartBuiltinServer}}
{{ctx.Locale.Tr "admin.config.ssh_root_path"}}
{{.SSH.RootPath}}
-
{{ctx.Locale.Tr "admin.config.ssh_key_test_path"}}
-
{{.SSH.KeyTestPath}}
-
{{ctx.Locale.Tr "admin.config.ssh_keygen_path"}}
-
{{.SSH.KeygenPath}}
{{ctx.Locale.Tr "admin.config.ssh_minimum_key_size_check"}}
{{svg (Iif .SSH.MinimumKeySizeCheck "octicon-check" "octicon-x")}}
{{if .SSH.MinimumKeySizeCheck}} diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go index ffb8afa9c5..0fc8a6e24d 100644 --- a/tests/integration/migration-test/migration_test.go +++ b/tests/integration/migration-test/migration_test.go @@ -52,7 +52,7 @@ func initMigrationTest(t *testing.T) func() { setting.CustomConf = giteaConf } - unittest.InitSettings() + unittest.InitSettingsForTesting() assert.NotEmpty(t, setting.RepoRootPath) assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) diff --git a/tests/test_utils.go b/tests/test_utils.go index 6c95716e67..4d8a8635d6 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -18,7 +18,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" - repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/test" @@ -67,9 +66,8 @@ func InitTest(requireGitea bool) { setting.CustomConf = giteaConf } - unittest.InitSettings() + unittest.InitSettingsForTesting() setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" - _ = util.RemoveAll(repo_module.LocalCopyPath()) if err := git.InitFull(context.Background()); err != nil { log.Fatal("git.InitOnceWithSync: %v", err)