mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	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 <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -213,6 +213,10 @@ func serveInstalled(ctx *cli.Context) error { | |||||||
| 		log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath) | 		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 | 	// Override the provided port number within the configuration | ||||||
| 	if ctx.IsSet("port") { | 	if ctx.IsSet("port") { | ||||||
| 		if err := setPort(ctx.String("port")); err != nil { | 		if err := setPort(ctx.String("port")); err != nil { | ||||||
|   | |||||||
| @@ -197,13 +197,6 @@ RUN_USER = ; git | |||||||
| ;; relative paths are made absolute relative to the APP_DATA_PATH | ;; relative paths are made absolute relative to the APP_DATA_PATH | ||||||
| ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa | ;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 | ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false | ||||||
| ;SSH_AUTHORIZED_KEYS_BACKUP = false | ;SSH_AUTHORIZED_KEYS_BACKUP = false | ||||||
| ;; | ;; | ||||||
| @@ -294,6 +287,9 @@ RUN_USER = ; git | |||||||
| ;; Default path for App data | ;; Default path for App data | ||||||
| ;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_ | ;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 compression for runtime-generated content, static resources excluded | ||||||
| ;ENABLE_GZIP = false | ;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 | ;; 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, | ;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] | ;[repository.upload] | ||||||
| @@ -1087,9 +1074,6 @@ LEVEL = Info | |||||||
| ;; Whether repository file uploads are enabled. Defaults to `true` | ;; Whether repository file uploads are enabled. Defaults to `true` | ||||||
| ;ENABLED = 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. | ;; 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 = | ;ALLOWED_TYPES = | ||||||
| ;; | ;; | ||||||
| @@ -2594,9 +2578,6 @@ LEVEL = Info | |||||||
| ;; Currently, only `minio` and `azureblob` is supported. | ;; Currently, only `minio` and `azureblob` is supported. | ||||||
| ;SERVE_DIRECT = false | ;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) | ;; Maximum count of package versions a single owner can have (`-1` means no limits) | ||||||
| ;LIMIT_TOTAL_OWNER_COUNT = -1 | ;LIMIT_TOTAL_OWNER_COUNT = -1 | ||||||
| ;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) | ;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) | ||||||
|   | |||||||
| @@ -6,27 +6,13 @@ package asymkey | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"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" | 	"golang.org/x/crypto/ssh" | ||||||
| 	"xorm.io/builder" | 	"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 | // 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, | // 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 | 	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) { | func calcFingerprintNative(publicKeyContent string) (string, error) { | ||||||
| 	// Calculate fingerprint. | 	// Calculate fingerprint. | ||||||
| 	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) | 	pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) | ||||||
| @@ -75,15 +38,12 @@ func calcFingerprintNative(publicKeyContent string) (string, error) { | |||||||
|  |  | ||||||
| // CalcFingerprint calculate public key's fingerprint | // CalcFingerprint calculate public key's fingerprint | ||||||
| func CalcFingerprint(publicKeyContent string) (string, error) { | func CalcFingerprint(publicKeyContent string) (string, error) { | ||||||
| 	// Call the method based on configuration | 	fp, err := calcFingerprintNative(publicKeyContent) | ||||||
| 	useNative := setting.SSH.KeygenPath == "" |  | ||||||
| 	calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen) |  | ||||||
| 	fp, err := calcFn(publicKeyContent) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if IsErrKeyUnableVerify(err) { | 		if IsErrKeyUnableVerify(err) { | ||||||
| 			return "", 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 | 	return fp, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,12 +13,9 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math/big" | 	"math/big" | ||||||
| 	"os" |  | ||||||
| 	"strconv" |  | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/process" |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| @@ -175,20 +172,9 @@ func CheckPublicKeyString(content string) (_ string, err error) { | |||||||
| 		return content, nil | 		return content, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var ( | 	keyType, length, err := SSHNativeParsePublicKey(content) | ||||||
| 		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) |  | ||||||
| 	} |  | ||||||
| 	if err != nil { | 	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) | 	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()) | 	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 |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/42wim/sshsig" | 	"github.com/42wim/sshsig" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/require" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Test_SSHParsePublicKey(t *testing.T) { | 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.keyType, keyTypeN) | ||||||
| 				assert.Equal(t, tc.length, lengthN) | 				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.NoError(t, err) | ||||||
| 				assert.Equal(t, tc.fp, fpN) | 				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) |  | ||||||
| 			}) |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -845,6 +845,7 @@ func DeleteOrphanedIssues(ctx context.Context) error { | |||||||
|  |  | ||||||
| 	// Remove issue attachment files. | 	// Remove issue attachment files. | ||||||
| 	for i := range attachmentPaths { | 	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]) | 		system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i]) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/models/unittest" | 	"code.gitea.io/gitea/models/unittest" | ||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  | 	"code.gitea.io/gitea/modules/tempdir" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| 	"code.gitea.io/gitea/modules/testlogger" | 	"code.gitea.io/gitea/modules/testlogger" | ||||||
|  |  | ||||||
| @@ -114,15 +115,16 @@ func MainTest(m *testing.M) { | |||||||
| 		setting.CustomConf = giteaConf | 		setting.CustomConf = giteaConf | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tmpDataPath, err := os.MkdirTemp("", "data") | 	tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		testlogger.Fatalf("Unable to create temporary data path %v\n", err) | 		testlogger.Fatalf("Unable to create temporary data path %v\n", err) | ||||||
| 	} | 	} | ||||||
|  | 	defer cleanup() | ||||||
|  |  | ||||||
| 	setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") | 	setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") | ||||||
| 	setting.AppDataPath = tmpDataPath | 	setting.AppDataPath = tmpDataPath | ||||||
|  |  | ||||||
| 	unittest.InitSettings() | 	unittest.InitSettingsForTesting() | ||||||
| 	if err = git.InitFull(context.Background()); err != nil { | 	if err = git.InitFull(context.Background()); err != nil { | ||||||
| 		testlogger.Fatalf("Unable to InitFull: %v\n", err) | 		testlogger.Fatalf("Unable to InitFull: %v\n", err) | ||||||
| 	} | 	} | ||||||
| @@ -134,8 +136,5 @@ func MainTest(m *testing.M) { | |||||||
| 	if err := removeAllWithRetry(setting.RepoRootPath); err != nil { | 	if err := removeAllWithRetry(setting.RepoRootPath); err != nil { | ||||||
| 		fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) | 		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) | 	os.Exit(exitStatus) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -51,14 +51,10 @@ func init() { | |||||||
| 	db.RegisterModel(new(Upload)) | 	db.RegisterModel(new(Upload)) | ||||||
| } | } | ||||||
|  |  | ||||||
| // UploadLocalPath returns where uploads is stored in local file system based on given UUID. | // LocalPath returns where uploads are temporarily 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. |  | ||||||
| func (upload *Upload) LocalPath() string { | 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. | // NewUpload creates a new upload object. | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/setting/config" | 	"code.gitea.io/gitea/modules/setting/config" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
|  | 	"code.gitea.io/gitea/modules/tempdir" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| @@ -35,8 +36,8 @@ func fatalTestError(fmtStr string, args ...any) { | |||||||
| 	os.Exit(1) | 	os.Exit(1) | ||||||
| } | } | ||||||
|  |  | ||||||
| // InitSettings initializes config provider and load common settings for tests | // InitSettingsForTesting initializes config provider and load common settings for tests | ||||||
| func InitSettings() { | func InitSettingsForTesting() { | ||||||
| 	setting.IsInTesting = true | 	setting.IsInTesting = true | ||||||
| 	log.OsExiter = func(code int) { | 	log.OsExiter = func(code int) { | ||||||
| 		if code != 0 { | 		if code != 0 { | ||||||
| @@ -75,7 +76,7 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { | |||||||
| 	testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) | 	testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) | ||||||
| 	giteaRoot = test.SetupGiteaRoot() | 	giteaRoot = test.SetupGiteaRoot() | ||||||
| 	setting.CustomPath = filepath.Join(giteaRoot, "custom") | 	setting.CustomPath = filepath.Join(giteaRoot, "custom") | ||||||
| 	InitSettings() | 	InitSettingsForTesting() | ||||||
|  |  | ||||||
| 	fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} | 	fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} | ||||||
| 	if err := CreateTestEngine(fixturesOpts); err != nil { | 	if err := CreateTestEngine(fixturesOpts); err != nil { | ||||||
| @@ -92,15 +93,19 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { | |||||||
| 	setting.SSH.Domain = "try.gitea.io" | 	setting.SSH.Domain = "try.gitea.io" | ||||||
| 	setting.Database.Type = "sqlite3" | 	setting.Database.Type = "sqlite3" | ||||||
| 	setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" | 	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 { | 	if err != nil { | ||||||
| 		fatalTestError("TempDir: %v\n", err) | 		fatalTestError("TempDir: %v\n", err) | ||||||
| 	} | 	} | ||||||
|  | 	defer cleanup1() | ||||||
|  |  | ||||||
| 	setting.RepoRootPath = repoRootPath | 	setting.RepoRootPath = repoRootPath | ||||||
| 	appDataPath, err := os.MkdirTemp(os.TempDir(), "appdata") | 	appDataPath, cleanup2, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("appdata") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		fatalTestError("TempDir: %v\n", err) | 		fatalTestError("TempDir: %v\n", err) | ||||||
| 	} | 	} | ||||||
|  | 	defer cleanup2() | ||||||
|  |  | ||||||
| 	setting.AppDataPath = appDataPath | 	setting.AppDataPath = appDataPath | ||||||
| 	setting.AppWorkPath = giteaRoot | 	setting.AppWorkPath = giteaRoot | ||||||
| 	setting.StaticRootPath = giteaRoot | 	setting.StaticRootPath = giteaRoot | ||||||
| @@ -153,13 +158,6 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { | |||||||
| 			fatalTestError("tear down failed: %v\n", err) | 			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) | 	os.Exit(exitStatus) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"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 | // BlamePart represents block of blame - continuous lines with one sha | ||||||
| @@ -29,12 +29,13 @@ type BlameReader struct { | |||||||
| 	bufferedReader *bufio.Reader | 	bufferedReader *bufio.Reader | ||||||
| 	done           chan error | 	done           chan error | ||||||
| 	lastSha        *string | 	lastSha        *string | ||||||
| 	ignoreRevsFile *string | 	ignoreRevsFile string | ||||||
| 	objectFormat   ObjectFormat | 	objectFormat   ObjectFormat | ||||||
|  | 	cleanupFuncs   []func() | ||||||
| } | } | ||||||
|  |  | ||||||
| func (r *BlameReader) UsesIgnoreRevs() bool { | 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) | // 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.bufferedReader = nil | ||||||
| 	_ = r.reader.Close() | 	_ = r.reader.Close() | ||||||
| 	_ = r.output.Close() | 	_ = r.output.Close() | ||||||
| 	if r.ignoreRevsFile != nil { | 	for _, cleanup := range r.cleanupFuncs { | ||||||
| 		_ = util.Remove(*r.ignoreRevsFile) | 		if cleanup != nil { | ||||||
|  | 			cleanup() | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| // CreateBlameReader creates reader for given repository, commit and file | // 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) { | 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() | 	reader, stdout, err := os.Pipe() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if ignoreRevsFile != nil { |  | ||||||
| 			_ = util.Remove(*ignoreRevsFile) |  | ||||||
| 		} |  | ||||||
| 		return nil, err | 		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() { | 	go func() { | ||||||
| 		stderr := bytes.Buffer{} | 		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" | 		// 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) | 	bufferedReader := bufio.NewReader(reader) | ||||||
|  |  | ||||||
| 	return &BlameReader{ | 	return &BlameReader{ | ||||||
| 		output:         stdout, | 		output:         stdout, | ||||||
| 		reader:         reader, | 		reader:         reader, | ||||||
| 		bufferedReader: bufferedReader, | 		bufferedReader: bufferedReader, | ||||||
| 		done:           done, | 		done:           done, | ||||||
| 		ignoreRevsFile: ignoreRevsFile, | 		ignoreRevsFile: ignoreRevsFileName, | ||||||
| 		objectFormat:   objectFormat, | 		objectFormat:   objectFormat, | ||||||
|  | 		cleanupFuncs:   []func(){ignoreRevsFileCleanup}, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func tryCreateBlameIgnoreRevsFile(commit *Commit) *string { | func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) { | ||||||
| 	entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") | 	entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") | ||||||
| 	if err != nil { | 	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() | 	r, err := entry.Blob().DataAsync() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil | 		log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err) | ||||||
|  | 		return "", nil | ||||||
| 	} | 	} | ||||||
| 	defer r.Close() | 	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 { | 	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) | 	_, err = io.Copy(f, r) | ||||||
| 	_ = f.Close() | 	_ = f.Close() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		_ = util.Remove(f.Name()) | 		cleanup() | ||||||
| 		return nil | 		log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err) | ||||||
|  | 		return "", nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return util.ToPointer(f.Name()) | 	return filename, cleanup | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,10 +7,13 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestReadingBlameOutputSha256(t *testing.T) { | func TestReadingBlameOutputSha256(t *testing.T) { | ||||||
|  | 	setting.AppDataPath = t.TempDir() | ||||||
| 	ctx, cancel := context.WithCancel(t.Context()) | 	ctx, cancel := context.WithCancel(t.Context()) | ||||||
| 	defer cancel() | 	defer cancel() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,10 +7,13 @@ import ( | |||||||
| 	"context" | 	"context" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestReadingBlameOutput(t *testing.T) { | func TestReadingBlameOutput(t *testing.T) { | ||||||
|  | 	setting.AppDataPath = t.TempDir() | ||||||
| 	ctx, cancel := context.WithCancel(t.Context()) | 	ctx, cancel := context.WithCancel(t.Context()) | ||||||
| 	defer cancel() | 	defer cancel() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,18 +10,19 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"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/hashicorp/go-version" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func testRun(m *testing.M) error { | 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 { | 	if err != nil { | ||||||
| 		return fmt.Errorf("unable to create temp dir: %w", err) | 		return fmt.Errorf("unable to create temp dir: %w", err) | ||||||
| 	} | 	} | ||||||
| 	defer util.RemoveAll(gitHomePath) | 	defer cleanup() | ||||||
|  |  | ||||||
| 	setting.Git.HomePath = gitHomePath | 	setting.Git.HomePath = gitHomePath | ||||||
|  |  | ||||||
| 	if err = InitFull(context.Background()); err != nil { | 	if err = InitFull(context.Background()); err != nil { | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/proxy" | 	"code.gitea.io/gitea/modules/proxy" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // GPGSettings represents the default GPG settings for this repository | // 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 | // CreateBundle create bundle content to the target path | ||||||
| func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error { | 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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer os.RemoveAll(tmp) | 	defer cleanup() | ||||||
|  |  | ||||||
| 	env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) | 	env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) | ||||||
| 	_, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) | 	_, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) | ||||||
|   | |||||||
| @@ -10,8 +10,7 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // ReadTreeToIndex reads a treeish to the index | // 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 | 	tmpDir, cancel, err = setting.AppDataTempDir("git-repo-content").MkdirTempRandom("index") | ||||||
| 		return func() { |  | ||||||
| 			if err := util.RemoveAll(dir); err != nil { |  | ||||||
| 				log.Error("failed to remove tmp index dir: %v", err) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	tmpDir, err = os.MkdirTemp("", "index") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", "", nil, err | 		return "", "", nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index") | 	tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index") | ||||||
| 	cancel = removeDirFn(tmpDir) |  | ||||||
| 	err = repo.ReadTreeToIndex(treeish, tmpIndexFilename) | 	err = repo.ReadTreeToIndex(treeish, tmpIndexFilename) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", "", cancel, err | 		return "", "", cancel, err | ||||||
| 	} | 	} | ||||||
| 	return tmpIndexFilename, tmpDir, cancel, err | 	return tmpIndexFilename, tmpDir, cancel, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // EmptyIndex empties the index | // EmptyIndex empties the index | ||||||
|   | |||||||
| @@ -9,11 +9,14 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestRepository_GetLanguageStats(t *testing.T) { | func TestRepository_GetLanguageStats(t *testing.T) { | ||||||
|  | 	setting.AppDataPath = t.TempDir() | ||||||
| 	repoPath := filepath.Join(testReposDir, "language_stats_repo") | 	repoPath := filepath.Join(testReposDir, "language_stats_repo") | ||||||
| 	gitRepo, err := openRepositoryWithDefaultContext(repoPath) | 	gitRepo, err := openRepositoryWithDefaultContext(repoPath) | ||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								modules/markup/external/external.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								modules/markup/external/external.go
									
									
									
									
										vendored
									
									
								
							| @@ -12,11 +12,9 @@ import ( | |||||||
| 	"runtime" | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" |  | ||||||
| 	"code.gitea.io/gitea/modules/markup" | 	"code.gitea.io/gitea/modules/markup" | ||||||
| 	"code.gitea.io/gitea/modules/process" | 	"code.gitea.io/gitea/modules/process" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RegisterRenderers registers all supported third part renderers according settings | // 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 { | 	if p.IsInputFile { | ||||||
| 		// write to temp file | 		// write to temp file | ||||||
| 		f, err := os.CreateTemp("", "gitea_input") | 		f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("gitea_input") | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) | 			return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) | ||||||
| 		} | 		} | ||||||
| 		tmpPath := f.Name() | 		defer cleanup() | ||||||
| 		defer func() { |  | ||||||
| 			if err := util.Remove(tmpPath); err != nil { |  | ||||||
| 				log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
|  |  | ||||||
| 		_, err = io.Copy(f, input) | 		_, err = io.Copy(f, input) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package packages | |||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util/filebuffer" | 	"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 | // NewHashedBufferWithSize creates a hashed buffer with a specific memory size | ||||||
| func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { | func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { | ||||||
| 	b, err := filebuffer.New(maxMemorySize) | 	tempDir, err := setting.AppDataTempDir("package-hashed-buffer").MkdirAllSub("") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	b := filebuffer.New(maxMemorySize, tempDir) | ||||||
| 	hash := NewMultiHasher() | 	hash := NewMultiHasher() | ||||||
|  |  | ||||||
| 	combinedWriter := io.MultiWriter(b, hash) | 	combinedWriter := io.MultiWriter(b, hash) | ||||||
|   | |||||||
| @@ -9,10 +9,13 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestHashedBuffer(t *testing.T) { | func TestHashedBuffer(t *testing.T) { | ||||||
|  | 	setting.AppDataPath = t.TempDir() | ||||||
| 	cases := []struct { | 	cases := []struct { | ||||||
| 		MaxMemorySize int | 		MaxMemorySize int | ||||||
| 		Data          string | 		Data          string | ||||||
|   | |||||||
| @@ -9,6 +9,8 @@ import ( | |||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -17,6 +19,7 @@ fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB | |||||||
| AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==` | AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==` | ||||||
|  |  | ||||||
| func TestExtractPortablePdb(t *testing.T) { | func TestExtractPortablePdb(t *testing.T) { | ||||||
|  | 	setting.AppDataPath = t.TempDir() | ||||||
| 	createArchive := func(name string, content []byte) []byte { | 	createArchive := func(name string, content []byte) []byte { | ||||||
| 		var buf bytes.Buffer | 		var buf bytes.Buffer | ||||||
| 		archive := zip.NewWriter(&buf) | 		archive := zip.NewWriter(&buf) | ||||||
|   | |||||||
| @@ -4,41 +4,19 @@ | |||||||
| package repository | package repository | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"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 | // CreateTemporaryPath creates a temporary path | ||||||
| func CreateTemporaryPath(prefix string) (string, error) { | func CreateTemporaryPath(prefix string) (string, context.CancelFunc, error) { | ||||||
| 	if err := os.MkdirAll(LocalCopyPath(), os.ModePerm); err != nil { | 	basePath, cleanup, err := setting.AppDataTempDir("local-repo").MkdirTempRandom(prefix + ".git") | ||||||
| 		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") |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) | 		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 | 	return basePath, cleanup, 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 |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,8 +6,6 @@ package setting | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"math" | 	"math" | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" |  | ||||||
|  |  | ||||||
| 	"github.com/dustin/go-humanize" | 	"github.com/dustin/go-humanize" | ||||||
| ) | ) | ||||||
| @@ -67,14 +65,10 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { | |||||||
| 		return err | 		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 HasInstallLock(rootCfg) { | ||||||
| 		if err := os.MkdirAll(Packages.ChunkedUploadPath, os.ModePerm); err != nil { | 		Packages.ChunkedUploadPath, err = AppDataTempDir("package-upload").MkdirAllSub("") | ||||||
| 			return fmt.Errorf("unable to create chunked upload directory: %s (%v)", Packages.ChunkedUploadPath, err) | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("unable to create chunked upload directory: %w", err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/tempdir" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @@ -196,3 +197,18 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP | |||||||
| 	CustomPath = tmpCustomPath.Value | 	CustomPath = tmpCustomPath.Value | ||||||
| 	CustomConf = tmpCustomConf.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) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -62,17 +62,11 @@ var ( | |||||||
| 		// Repository upload settings | 		// Repository upload settings | ||||||
| 		Upload struct { | 		Upload struct { | ||||||
| 			Enabled      bool | 			Enabled      bool | ||||||
| 			TempPath     string |  | ||||||
| 			AllowedTypes string | 			AllowedTypes string | ||||||
| 			FileMaxSize  int64 | 			FileMaxSize  int64 | ||||||
| 			MaxFiles     int | 			MaxFiles     int | ||||||
| 		} `ini:"-"` | 		} `ini:"-"` | ||||||
|  |  | ||||||
| 		// Repository local settings |  | ||||||
| 		Local struct { |  | ||||||
| 			LocalCopyPath string |  | ||||||
| 		} `ini:"-"` |  | ||||||
|  |  | ||||||
| 		// Pull request settings | 		// Pull request settings | ||||||
| 		PullRequest struct { | 		PullRequest struct { | ||||||
| 			WorkInProgressPrefixes                   []string | 			WorkInProgressPrefixes                   []string | ||||||
| @@ -181,25 +175,16 @@ var ( | |||||||
| 		// Repository upload settings | 		// Repository upload settings | ||||||
| 		Upload: struct { | 		Upload: struct { | ||||||
| 			Enabled      bool | 			Enabled      bool | ||||||
| 			TempPath     string |  | ||||||
| 			AllowedTypes string | 			AllowedTypes string | ||||||
| 			FileMaxSize  int64 | 			FileMaxSize  int64 | ||||||
| 			MaxFiles     int | 			MaxFiles     int | ||||||
| 		}{ | 		}{ | ||||||
| 			Enabled:      true, | 			Enabled:      true, | ||||||
| 			TempPath:     "data/tmp/uploads", |  | ||||||
| 			AllowedTypes: "", | 			AllowedTypes: "", | ||||||
| 			FileMaxSize:  50, | 			FileMaxSize:  50, | ||||||
| 			MaxFiles:     5, | 			MaxFiles:     5, | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		// Repository local settings |  | ||||||
| 		Local: struct { |  | ||||||
| 			LocalCopyPath string |  | ||||||
| 		}{ |  | ||||||
| 			LocalCopyPath: "tmp/local-repo", |  | ||||||
| 		}, |  | ||||||
|  |  | ||||||
| 		// Pull request settings | 		// Pull request settings | ||||||
| 		PullRequest: struct { | 		PullRequest: struct { | ||||||
| 			WorkInProgressPrefixes                   []string | 			WorkInProgressPrefixes                   []string | ||||||
| @@ -308,8 +293,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { | |||||||
| 		log.Fatal("Failed to map Repository.Editor settings: %v", err) | 		log.Fatal("Failed to map Repository.Editor settings: %v", err) | ||||||
| 	} else if err = rootCfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil { | 	} else if err = rootCfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil { | ||||||
| 		log.Fatal("Failed to map Repository.Upload settings: %v", err) | 		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 { | 	} else if err = rootCfg.Section("repository.pull-request").MapTo(&Repository.PullRequest); err != nil { | ||||||
| 		log.Fatal("Failed to map Repository.PullRequest settings: %v", err) | 		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 { | 	if err := loadRepoArchiveFrom(rootCfg); err != nil { | ||||||
| 		log.Fatal("loadRepoArchiveFrom: %v", err) | 		log.Fatal("loadRepoArchiveFrom: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import ( | |||||||
| 	"encoding/base64" | 	"encoding/base64" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| @@ -59,6 +60,8 @@ var ( | |||||||
| 	// AssetVersion holds a opaque value that is used for cache-busting assets | 	// AssetVersion holds a opaque value that is used for cache-busting assets | ||||||
| 	AssetVersion string | 	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 | 	Protocol                   Scheme | ||||||
| 	UseProxyProtocol           bool // `ini:"USE_PROXY_PROTOCOL"` | 	UseProxyProtocol           bool // `ini:"USE_PROXY_PROTOCOL"` | ||||||
| 	ProxyProtocolTLSBridging   bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` | 	ProxyProtocolTLSBridging   bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` | ||||||
| @@ -330,6 +333,19 @@ func loadServerFrom(rootCfg ConfigProvider) { | |||||||
| 	if !filepath.IsAbs(AppDataPath) { | 	if !filepath.IsAbs(AppDataPath) { | ||||||
| 		AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, 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() | 	EnableGzip = sec.Key("ENABLE_GZIP").MustBool() | ||||||
| 	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) | 	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ | |||||||
| package setting | package setting | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"os" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"text/template" | 	"text/template" | ||||||
| @@ -31,8 +30,6 @@ var SSH = struct { | |||||||
| 	ServerKeyExchanges                    []string           `ini:"SSH_SERVER_KEY_EXCHANGES"` | 	ServerKeyExchanges                    []string           `ini:"SSH_SERVER_KEY_EXCHANGES"` | ||||||
| 	ServerMACs                            []string           `ini:"SSH_SERVER_MACS"` | 	ServerMACs                            []string           `ini:"SSH_SERVER_MACS"` | ||||||
| 	ServerHostKeys                        []string           `ini:"SSH_SERVER_HOST_KEYS"` | 	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"` | 	AuthorizedKeysBackup                  bool               `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` | ||||||
| 	AuthorizedPrincipalsBackup            bool               `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` | 	AuthorizedPrincipalsBackup            bool               `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` | ||||||
| 	AuthorizedKeysCommandTemplate         string             `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"` | 	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"}, | 	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"}, | 	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"}, | 	ServerMACs:                    []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, | ||||||
| 	KeygenPath:                    "", |  | ||||||
| 	MinimumKeySizeCheck:           true, | 	MinimumKeySizeCheck:           true, | ||||||
| 	MinimumKeySizes:               map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, | 	MinimumKeySizes:               map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, | ||||||
| 	ServerHostKeys:                []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, | 	ServerHostKeys:                []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, | ||||||
| @@ -123,7 +119,6 @@ func loadSSHFrom(rootCfg ConfigProvider) { | |||||||
| 	if len(serverMACs) > 0 { | 	if len(serverMACs) > 0 { | ||||||
| 		SSH.ServerMACs = serverMACs | 		SSH.ServerMACs = serverMACs | ||||||
| 	} | 	} | ||||||
| 	SSH.KeyTestPath = os.TempDir() |  | ||||||
| 	if err = sec.MapTo(&SSH); err != nil { | 	if err = sec.MapTo(&SSH); err != nil { | ||||||
| 		log.Fatal("Failed to map SSH settings: %v", err) | 		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.Port = sec.Key("SSH_PORT").MustInt(22) | ||||||
| 	SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) | 	SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) | ||||||
| 	SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) | 	SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) | ||||||
|   | |||||||
| @@ -32,11 +32,6 @@ func Init() error { | |||||||
|  |  | ||||||
| 	builtinUnused() | 	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 { | 	if len(setting.SSH.TrustedUserCAKeys) > 0 && setting.SSH.AuthorizedPrincipalsEnabled { | ||||||
| 		caKeysFileName := setting.SSH.TrustedUserCAKeysFile | 		caKeysFileName := setting.SSH.TrustedUserCAKeysFile | ||||||
| 		caKeysFileDir := filepath.Dir(caKeysFileName) | 		caKeysFileDir := filepath.Dir(caKeysFileName) | ||||||
|   | |||||||
							
								
								
									
										112
									
								
								modules/tempdir/tempdir.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								modules/tempdir/tempdir.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | } | ||||||
							
								
								
									
										75
									
								
								modules/tempdir/tempdir_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								modules/tempdir/tempdir_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
| @@ -56,7 +56,7 @@ var testMetas = map[string]string{ | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestMain(m *testing.M) { | func TestMain(m *testing.M) { | ||||||
| 	unittest.InitSettings() | 	unittest.InitSettingsForTesting() | ||||||
| 	if err := git.InitSimple(context.Background()); err != nil { | 	if err := git.InitSimple(context.Background()); err != nil { | ||||||
| 		log.Fatal("git init failed, err: %v", err) | 		log.Fatal("git init failed, err: %v", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -7,16 +7,10 @@ import ( | |||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
| 	"math" |  | ||||||
| 	"os" | 	"os" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ErrWriteAfterRead = errors.New("write is unsupported after a read operation") // occurs if Write is called after a read operation | ||||||
| 	// 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") |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type readAtSeeker interface { | type readAtSeeker interface { | ||||||
| 	io.ReadSeeker | 	io.ReadSeeker | ||||||
| @@ -30,34 +24,17 @@ type FileBackedBuffer struct { | |||||||
| 	maxMemorySize int64 | 	maxMemorySize int64 | ||||||
| 	size          int64 | 	size          int64 | ||||||
| 	buffer        bytes.Buffer | 	buffer        bytes.Buffer | ||||||
|  | 	tempDir       string | ||||||
| 	file          *os.File | 	file          *os.File | ||||||
| 	reader        readAtSeeker | 	reader        readAtSeeker | ||||||
| } | } | ||||||
|  |  | ||||||
| // New creates a file backed buffer with a specific maximum memory size | // New creates a file backed buffer with a specific maximum memory size | ||||||
| func New(maxMemorySize int) (*FileBackedBuffer, error) { | func New(maxMemorySize int, tempDir string) *FileBackedBuffer { | ||||||
| 	if maxMemorySize < 0 || maxMemorySize > math.MaxInt32 { |  | ||||||
| 		return nil, ErrInvalidMemorySize |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &FileBackedBuffer{ | 	return &FileBackedBuffer{ | ||||||
| 		maxMemorySize: int64(maxMemorySize), | 		maxMemorySize: int64(maxMemorySize), | ||||||
| 	}, nil | 		tempDir:       tempDir, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| // 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 |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	_, err = io.Copy(b, r) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return b, nil |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // Write implements io.Writer | // Write implements io.Writer | ||||||
| @@ -73,7 +50,7 @@ func (b *FileBackedBuffer) Write(p []byte) (int, error) { | |||||||
| 		n, err = b.file.Write(p) | 		n, err = b.file.Write(p) | ||||||
| 	} else { | 	} else { | ||||||
| 		if b.size+int64(len(p)) > b.maxMemorySize { | 		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 { | 			if err != nil { | ||||||
| 				return 0, err | 				return 0, err | ||||||
| 			} | 			} | ||||||
| @@ -148,7 +125,7 @@ func (b *FileBackedBuffer) Seek(offset int64, whence int) (int64, error) { | |||||||
| func (b *FileBackedBuffer) Close() error { | func (b *FileBackedBuffer) Close() error { | ||||||
| 	if b.file != nil { | 	if b.file != nil { | ||||||
| 		err := b.file.Close() | 		err := b.file.Close() | ||||||
| 		os.Remove(b.file.Name()) | 		_ = os.Remove(b.file.Name()) | ||||||
| 		b.file = nil | 		b.file = nil | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -21,7 +21,8 @@ func TestFileBackedBuffer(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, c := range cases { | 	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.NoError(t, err) | ||||||
|  |  | ||||||
| 		assert.EqualValues(t, len(c.Data), buf.Size()) | 		assert.EqualValues(t, len(c.Data), buf.Size()) | ||||||
|   | |||||||
| @@ -3287,8 +3287,6 @@ config.ssh_domain = SSH Server Domain | |||||||
| config.ssh_port = Port | config.ssh_port = Port | ||||||
| config.ssh_listen_port = Listen Port | config.ssh_listen_port = Listen Port | ||||||
| config.ssh_root_path = Root Path | 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_size_check = Minimum Key Size Check | ||||||
| config.ssh_minimum_key_sizes = Minimum Key Sizes | config.ssh_minimum_key_sizes = Minimum Key Sizes | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,7 +29,6 @@ import ( | |||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" | 	repo_module "code.gitea.io/gitea/modules/repository" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
| 	"code.gitea.io/gitea/services/context" | 	"code.gitea.io/gitea/services/context" | ||||||
| 	repo_service "code.gitea.io/gitea/services/repository" | 	repo_service "code.gitea.io/gitea/services/repository" | ||||||
|  |  | ||||||
| @@ -303,17 +302,12 @@ var ( | |||||||
|  |  | ||||||
| func dummyInfoRefs(ctx *context.Context) { | func dummyInfoRefs(ctx *context.Context) { | ||||||
| 	infoRefsOnce.Do(func() { | 	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 { | 		if err != nil { | ||||||
| 			log.Error("Failed to create temp dir for git-receive-pack cache: %v", err) | 			log.Error("Failed to create temp dir for git-receive-pack cache: %v", err) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
|  | 		defer cleanup() | ||||||
| 		defer func() { |  | ||||||
| 			if err := util.RemoveAll(tmpDir); err != nil { |  | ||||||
| 				log.Error("RemoveAll: %v", err) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
|  |  | ||||||
| 		if err := git.InitRepository(ctx, tmpDir, true, git.Sha1ObjectFormat.Name()); err != nil { | 		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) | 			log.Error("Failed to init bare repo for git-receive-pack cache: %v", err) | ||||||
|   | |||||||
| @@ -109,17 +109,13 @@ func LFSLocks(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Clone base repo. | 	// Clone base repo. | ||||||
| 	tmpBasePath, err := repo_module.CreateTemporaryPath("locks") | 	tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("locks") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("Failed to create temporary path: %v", err) | 		log.Error("Failed to create temporary path: %v", err) | ||||||
| 		ctx.ServerError("LFSLocks", err) | 		ctx.ServerError("LFSLocks", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer func() { | 	defer cleanup() | ||||||
| 		if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { |  | ||||||
| 			log.Error("LFSLocks: RemoveTemporaryPath: %v", err) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ | 	if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ | ||||||
| 		Bare:   true, | 		Bare:   true, | ||||||
|   | |||||||
| @@ -355,23 +355,19 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// 3b. Create a plain patch from head to base | 	// 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 { | 	if err != nil { | ||||||
| 		log.Error("Unable to create temporary patch file! Error: %v", err) | 		log.Error("Unable to create temporary patch file! Error: %v", err) | ||||||
| 		return false, fmt.Errorf("unable to create temporary patch file! Error: %w", err) | 		return false, fmt.Errorf("unable to create temporary patch file! Error: %w", err) | ||||||
| 	} | 	} | ||||||
| 	defer func() { | 	defer cleanup() | ||||||
| 		_ = util.Remove(tmpPatchFile.Name()) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil { | 	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) | 		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) | 		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() | 	stat, err := tmpPatchFile.Stat() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		tmpPatchFile.Close() |  | ||||||
| 		return false, fmt.Errorf("unable to stat patch file: %w", err) | 		return false, fmt.Errorf("unable to stat patch file: %w", err) | ||||||
| 	} | 	} | ||||||
| 	patchPath := tmpPatchFile.Name() | 	patchPath := tmpPatchFile.Name() | ||||||
|   | |||||||
| @@ -74,11 +74,13 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Clone base repo. | 	// Clone base repo. | ||||||
| 	tmpBasePath, err := repo_module.CreateTemporaryPath("pull") | 	tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("pull") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("CreateTemporaryPath[%-v]: %v", pr, err) | 		log.Error("CreateTemporaryPath[%-v]: %v", pr, err) | ||||||
| 		return nil, nil, err | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
|  | 	cancel = cleanup | ||||||
|  |  | ||||||
| 	prCtx = &prContext{ | 	prCtx = &prContext{ | ||||||
| 		Context:     ctx, | 		Context:     ctx, | ||||||
| 		tmpBasePath: tmpBasePath, | 		tmpBasePath: tmpBasePath, | ||||||
| @@ -86,11 +88,6 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) | |||||||
| 		outbuf:      &strings.Builder{}, | 		outbuf:      &strings.Builder{}, | ||||||
| 		errbuf:      &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() | 	baseRepoPath := pr.BaseRepo.RepoPath() | ||||||
| 	headRepoPath := pr.HeadRepo.RepoPath() | 	headRepoPath := pr.HeadRepo.RepoPath() | ||||||
|   | |||||||
| @@ -29,7 +29,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/templates/vars" | 	"code.gitea.io/gitea/modules/templates/vars" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CreateRepoOptions contains the create repository options | // 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. | 	// Initialize repository according to user's choice. | ||||||
| 	if opts.AutoInit { | 	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 { | 		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() { | 		defer cleanup() | ||||||
| 			if err := util.RemoveAll(tmpDir); err != nil { |  | ||||||
| 				log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) |  | ||||||
| 			} |  | ||||||
| 		}() |  | ||||||
|  |  | ||||||
| 		if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil { | 		if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil { | ||||||
| 			return fmt.Errorf("prepareRepoCommit: %w", err) | 			return fmt.Errorf("prepareRepoCommit: %w", err) | ||||||
|   | |||||||
| @@ -30,23 +30,24 @@ type TemporaryUploadRepository struct { | |||||||
| 	repo     *repo_model.Repository | 	repo     *repo_model.Repository | ||||||
| 	gitRepo  *git.Repository | 	gitRepo  *git.Repository | ||||||
| 	basePath string | 	basePath string | ||||||
|  | 	cleanup  func() | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewTemporaryUploadRepository creates a new temporary upload repository | // NewTemporaryUploadRepository creates a new temporary upload repository | ||||||
| func NewTemporaryUploadRepository(repo *repo_model.Repository) (*TemporaryUploadRepository, error) { | func NewTemporaryUploadRepository(repo *repo_model.Repository) (*TemporaryUploadRepository, error) { | ||||||
| 	basePath, err := repo_module.CreateTemporaryPath("upload") | 	basePath, cleanup, err := repo_module.CreateTemporaryPath("upload") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	t := &TemporaryUploadRepository{repo: repo, basePath: basePath} | 	t := &TemporaryUploadRepository{repo: repo, basePath: basePath, cleanup: cleanup} | ||||||
| 	return t, nil | 	return t, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Close the repository cleaning up all files | // Close the repository cleaning up all files | ||||||
| func (t *TemporaryUploadRepository) Close() { | func (t *TemporaryUploadRepository) Close() { | ||||||
| 	defer t.gitRepo.Close() | 	defer t.gitRepo.Close() | ||||||
| 	if err := repo_module.RemoveTemporaryPath(t.basePath); err != nil { | 	if t.cleanup != nil { | ||||||
| 		log.Error("Failed to remove temporary path %s: %v", t.basePath, err) | 		t.cleanup() | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/gitrepo" | 	"code.gitea.io/gitea/modules/gitrepo" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	repo_module "code.gitea.io/gitea/modules/repository" | 	repo_module "code.gitea.io/gitea/modules/repository" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| 	"github.com/gobwas/glob" | 	"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) { | 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 { | 	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 cleanup() | ||||||
| 	defer func() { |  | ||||||
| 		if err := util.RemoveAll(tmpDir); err != nil { |  | ||||||
| 			log.Error("RemoveAll: %v", err) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { | 	if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { | ||||||
| 		return fmt.Errorf("generateRepoCommit: %w", err) | 		return fmt.Errorf("generateRepoCommit: %w", err) | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ import ( | |||||||
| 	issues_model "code.gitea.io/gitea/models/issues" | 	issues_model "code.gitea.io/gitea/models/issues" | ||||||
| 	"code.gitea.io/gitea/models/organization" | 	"code.gitea.io/gitea/models/organization" | ||||||
| 	repo_model "code.gitea.io/gitea/models/repo" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	system_model "code.gitea.io/gitea/models/system" |  | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| 	"code.gitea.io/gitea/modules/graceful" | 	"code.gitea.io/gitea/modules/graceful" | ||||||
| @@ -102,8 +101,6 @@ func Init(ctx context.Context) error { | |||||||
| 	if err := repo_module.LoadRepoConfig(); err != nil { | 	if err := repo_module.LoadRepoConfig(); err != nil { | ||||||
| 		return err | 		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 { | 	if err := initPushQueue(); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -102,15 +102,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model | |||||||
|  |  | ||||||
| 	hasDefaultBranch := gitrepo.IsBranchExist(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch) | 	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 { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer func() { | 	defer cleanup() | ||||||
| 		if err := repo_module.RemoveTemporaryPath(basePath); err != nil { |  | ||||||
| 			log.Error("Merge: RemoveTemporaryPath: %s", err) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	cloneOpts := git.CloneRepoOptions{ | 	cloneOpts := git.CloneRepoOptions{ | ||||||
| 		Bare:   true, | 		Bare:   true, | ||||||
| @@ -264,15 +260,11 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model | |||||||
| 		return fmt.Errorf("InitWiki: %w", err) | 		return fmt.Errorf("InitWiki: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	basePath, err := repo_module.CreateTemporaryPath("update-wiki") | 	basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer func() { | 	defer cleanup() | ||||||
| 		if err := repo_module.RemoveTemporaryPath(basePath); err != nil { |  | ||||||
| 			log.Error("Merge: RemoveTemporaryPath: %s", err) |  | ||||||
| 		} |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{ | 	if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{ | ||||||
| 		Bare:   true, | 		Bare:   true, | ||||||
|   | |||||||
| @@ -69,10 +69,6 @@ | |||||||
| 					{{if not .SSH.StartBuiltinServer}} | 					{{if not .SSH.StartBuiltinServer}} | ||||||
| 						<dt>{{ctx.Locale.Tr "admin.config.ssh_root_path"}}</dt> | 						<dt>{{ctx.Locale.Tr "admin.config.ssh_root_path"}}</dt> | ||||||
| 						<dd>{{.SSH.RootPath}}</dd> | 						<dd>{{.SSH.RootPath}}</dd> | ||||||
| 						<dt>{{ctx.Locale.Tr "admin.config.ssh_key_test_path"}}</dt> |  | ||||||
| 						<dd>{{.SSH.KeyTestPath}}</dd> |  | ||||||
| 						<dt>{{ctx.Locale.Tr "admin.config.ssh_keygen_path"}}</dt> |  | ||||||
| 						<dd>{{.SSH.KeygenPath}}</dd> |  | ||||||
| 						<dt>{{ctx.Locale.Tr "admin.config.ssh_minimum_key_size_check"}}</dt> | 						<dt>{{ctx.Locale.Tr "admin.config.ssh_minimum_key_size_check"}}</dt> | ||||||
| 						<dd>{{svg (Iif .SSH.MinimumKeySizeCheck "octicon-check" "octicon-x")}}</dd> | 						<dd>{{svg (Iif .SSH.MinimumKeySizeCheck "octicon-check" "octicon-x")}}</dd> | ||||||
| 						{{if .SSH.MinimumKeySizeCheck}} | 						{{if .SSH.MinimumKeySizeCheck}} | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ func initMigrationTest(t *testing.T) func() { | |||||||
| 		setting.CustomConf = giteaConf | 		setting.CustomConf = giteaConf | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	unittest.InitSettings() | 	unittest.InitSettingsForTesting() | ||||||
|  |  | ||||||
| 	assert.NotEmpty(t, setting.RepoRootPath) | 	assert.NotEmpty(t, setting.RepoRootPath) | ||||||
| 	assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) | 	assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) | ||||||
|   | |||||||
| @@ -18,7 +18,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/git" | 	"code.gitea.io/gitea/modules/git" | ||||||
| 	"code.gitea.io/gitea/modules/graceful" | 	"code.gitea.io/gitea/modules/graceful" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"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/setting" | ||||||
| 	"code.gitea.io/gitea/modules/storage" | 	"code.gitea.io/gitea/modules/storage" | ||||||
| 	"code.gitea.io/gitea/modules/test" | 	"code.gitea.io/gitea/modules/test" | ||||||
| @@ -67,9 +66,8 @@ func InitTest(requireGitea bool) { | |||||||
| 		setting.CustomConf = giteaConf | 		setting.CustomConf = giteaConf | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	unittest.InitSettings() | 	unittest.InitSettingsForTesting() | ||||||
| 	setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" | 	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 { | 	if err := git.InitFull(context.Background()); err != nil { | ||||||
| 		log.Fatal("git.InitOnceWithSync: %v", err) | 		log.Fatal("git.InitOnceWithSync: %v", err) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Lunny Xiao
					Lunny Xiao