mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-17 14:30:06 +02:00
Merge branch 'go-gitea:main' into main
This commit is contained in:
commit
226ace7eaf
@ -29,8 +29,10 @@ func TestShortSha(t *testing.T) {
|
|||||||
func TestVerifyTimeLimitCode(t *testing.T) {
|
func TestVerifyTimeLimitCode(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.InstallLock, true)()
|
defer test.MockVariableValue(&setting.InstallLock, true)()
|
||||||
initGeneralSecret := func(secret string) {
|
initGeneralSecret := func(secret string) {
|
||||||
setting.InstallLock = true
|
|
||||||
setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
|
setting.CfgProvider, _ = setting.NewConfigProviderFromData(fmt.Sprintf(`
|
||||||
|
[security]
|
||||||
|
INTERNAL_TOKEN = dummy
|
||||||
|
INSTALL_LOCK = true
|
||||||
[oauth2]
|
[oauth2]
|
||||||
JWT_SECRET = %s
|
JWT_SECRET = %s
|
||||||
`, secret))
|
`, secret))
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -49,9 +50,11 @@ func TestRunWithContextStd(t *testing.T) {
|
|||||||
stdout, stderr, err := cmd.RunStdString(t.Context())
|
stdout, stderr, err := cmd.RunStdString(t.Context())
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, stderr, err.Stderr())
|
assert.Equal(t, stderr, err.Stderr())
|
||||||
assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr())
|
stderrLower := strings.ToLower(stderr) // see: IsStdErrorNotValidObjectName
|
||||||
|
assert.Equal(t, "fatal: not a valid object name no-such\n", stderrLower)
|
||||||
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
|
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
|
||||||
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error())
|
errLower := strings.ToLower(err.Error())
|
||||||
|
assert.Equal(t, "exit status 128 - fatal: not a valid object name no-such", errLower)
|
||||||
assert.Empty(t, stdout)
|
assert.Empty(t, stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,9 +64,11 @@ func TestRunWithContextStd(t *testing.T) {
|
|||||||
stdout, stderr, err := cmd.RunStdBytes(t.Context())
|
stdout, stderr, err := cmd.RunStdBytes(t.Context())
|
||||||
if assert.Error(t, err) {
|
if assert.Error(t, err) {
|
||||||
assert.Equal(t, string(stderr), err.Stderr())
|
assert.Equal(t, string(stderr), err.Stderr())
|
||||||
assert.Equal(t, "fatal: Not a valid object name no-such\n", err.Stderr())
|
stderrLower := strings.ToLower(err.Stderr()) // see: IsStdErrorNotValidObjectName
|
||||||
|
assert.Equal(t, "fatal: not a valid object name no-such\n", stderrLower)
|
||||||
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
|
// FIXME: GIT-CMD-STDERR: it is a bad design, the stderr should not be put in the error message
|
||||||
assert.Equal(t, "exit status 128 - fatal: Not a valid object name no-such", err.Error())
|
errLower := strings.ToLower(err.Error())
|
||||||
|
assert.Equal(t, "exit status 128 - fatal: not a valid object name no-such", errLower)
|
||||||
assert.Empty(t, stdout)
|
assert.Empty(t, stdout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,13 @@ func IsErrorCanceledOrKilled(err error) bool {
|
|||||||
return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err)
|
return errors.Is(err, context.Canceled) || IsErrorSignalKilled(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsStdErrorNotValidObjectName(err error) bool {
|
||||||
|
stderr, ok := ErrorAsStderr(err)
|
||||||
|
// Git is lowercasing the "fatal: Not a valid object name" error message
|
||||||
|
// ref: https://lore.kernel.org/git/pull.2052.git.1771836302101.gitgitgadget@gmail.com
|
||||||
|
return ok && strings.Contains(strings.ToLower(stderr), "fatal: not a valid object name")
|
||||||
|
}
|
||||||
|
|
||||||
type pipelineError struct {
|
type pipelineError struct {
|
||||||
error
|
error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ func (t *Tree) ListEntries() (Entries, error) {
|
|||||||
|
|
||||||
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx)
|
stdout, _, runErr := gitcmd.NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).WithDir(t.repo.Path).RunStdBytes(t.repo.Ctx)
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") {
|
if gitcmd.IsStdErrorNotValidObjectName(runErr) || strings.Contains(runErr.Error(), "fatal: not a tree object") {
|
||||||
return nil, ErrNotExist{
|
return nil, ErrNotExist{
|
||||||
ID: t.ID.String(),
|
ID: t.ID.String(),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,6 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
|
|||||||
|
|
||||||
func loadSecurityFrom(rootCfg ConfigProvider) {
|
func loadSecurityFrom(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("security")
|
sec := rootCfg.Section("security")
|
||||||
InstallLock = HasInstallLock(rootCfg)
|
|
||||||
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31)
|
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(31)
|
||||||
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
|
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
|
||||||
if SecretKey == "" {
|
if SecretKey == "" {
|
||||||
|
|||||||
@ -108,6 +108,9 @@ func LoadCommonSettings() {
|
|||||||
|
|
||||||
// loadCommonSettingsFrom loads common configurations from a configuration provider.
|
// loadCommonSettingsFrom loads common configurations from a configuration provider.
|
||||||
func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
||||||
|
// a lot of logic depends on InstallLock value, so it must be loaded before any other settings
|
||||||
|
InstallLock = HasInstallLock(cfg)
|
||||||
|
|
||||||
// WARNING: don't change the sequence except you know what you are doing.
|
// WARNING: don't change the sequence except you know what you are doing.
|
||||||
loadRunModeFrom(cfg)
|
loadRunModeFrom(cfg)
|
||||||
loadLogGlobalFrom(cfg)
|
loadLogGlobalFrom(cfg)
|
||||||
|
|||||||
@ -64,7 +64,7 @@ func PathJoinRelX(elem ...string) string {
|
|||||||
return PathJoinRel(elems...)
|
return PathJoinRel(elems...)
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathSeparator = string(os.PathSeparator)
|
const filepathSeparator = string(os.PathSeparator)
|
||||||
|
|
||||||
// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
|
// FilePathJoinAbs joins the path elements into a single file path, each element is cleaned by filepath.Clean separately.
|
||||||
// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators.
|
// All slashes/backslashes are converted to path separators before cleaning, the result only contains path separators.
|
||||||
@ -82,7 +82,7 @@ func FilePathJoinAbs(base string, sub ...string) string {
|
|||||||
if isOSWindows() {
|
if isOSWindows() {
|
||||||
elems[0] = filepath.Clean(base)
|
elems[0] = filepath.Clean(base)
|
||||||
} else {
|
} else {
|
||||||
elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator))
|
elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", filepathSeparator))
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(elems[0]) {
|
if !filepath.IsAbs(elems[0]) {
|
||||||
// This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead
|
// This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead
|
||||||
@ -93,9 +93,9 @@ func FilePathJoinAbs(base string, sub ...string) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if isOSWindows() {
|
if isOSWindows() {
|
||||||
elems = append(elems, filepath.Clean(pathSeparator+s))
|
elems = append(elems, filepath.Clean(filepathSeparator+s))
|
||||||
} else {
|
} else {
|
||||||
elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator)))
|
elems = append(elems, filepath.Clean(filepathSeparator+strings.ReplaceAll(s, "\\", filepathSeparator)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// the elems[0] must be an absolute path, just join them together
|
// the elems[0] must be an absolute path, just join them together
|
||||||
@ -115,12 +115,72 @@ func IsDir(dir string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsRegularFile(filePath string) (bool, error) {
|
var ErrNotRegularPathFile = errors.New("not a regular file")
|
||||||
f, err := os.Lstat(filePath)
|
|
||||||
if err == nil {
|
// ReadRegularPathFile reads a file with given sub path in root dir.
|
||||||
return f.Mode().IsRegular(), nil
|
// It returns error when the path is not a regular file, or any parent path is not a regular directory.
|
||||||
|
func ReadRegularPathFile(root, filePathIn string, limit int) ([]byte, error) {
|
||||||
|
pathFields := strings.Split(PathJoinRelX(filePathIn), "/")
|
||||||
|
|
||||||
|
targetPathBuilder := strings.Builder{}
|
||||||
|
targetPathBuilder.Grow(len(root) + len(filePathIn) + 2)
|
||||||
|
targetPathBuilder.WriteString(root)
|
||||||
|
targetPathString := root
|
||||||
|
for i, subPath := range pathFields {
|
||||||
|
targetPathBuilder.WriteByte(filepath.Separator)
|
||||||
|
targetPathBuilder.WriteString(subPath)
|
||||||
|
targetPathString = targetPathBuilder.String()
|
||||||
|
|
||||||
|
expectFile := i == len(pathFields)-1
|
||||||
|
st, err := os.Lstat(targetPathString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if expectFile && !st.Mode().IsRegular() || !expectFile && !st.Mode().IsDir() {
|
||||||
|
return nil, fmt.Errorf("%w: %s", ErrNotRegularPathFile, filePathIn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false, err
|
f, err := os.Open(targetPathString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
return ReadWithLimit(f, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteRegularPathFile writes data to a file with given sub path in root dir, it creates parent directories if necessary.
|
||||||
|
// The file is created with fileMode, and the directories are created with dirMode.
|
||||||
|
// It returns error when the path already exists but is not a regular file, or any parent path is not a regular directory.
|
||||||
|
func WriteRegularPathFile(root, filePathIn string, data []byte, dirMode, fileMode os.FileMode) error {
|
||||||
|
pathFields := strings.Split(PathJoinRelX(filePathIn), "/")
|
||||||
|
|
||||||
|
targetPathBuilder := strings.Builder{}
|
||||||
|
targetPathBuilder.Grow(len(root) + len(filePathIn) + 2)
|
||||||
|
targetPathBuilder.WriteString(root)
|
||||||
|
targetPathString := root
|
||||||
|
for i, subPath := range pathFields {
|
||||||
|
targetPathBuilder.WriteByte(filepath.Separator)
|
||||||
|
targetPathBuilder.WriteString(subPath)
|
||||||
|
targetPathString = targetPathBuilder.String()
|
||||||
|
|
||||||
|
expectFile := i == len(pathFields)-1
|
||||||
|
st, err := os.Lstat(targetPathString)
|
||||||
|
if err == nil {
|
||||||
|
if expectFile && !st.Mode().IsRegular() || !expectFile && !st.Mode().IsDir() {
|
||||||
|
return fmt.Errorf("%w: %s", ErrNotRegularPathFile, filePathIn)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !expectFile {
|
||||||
|
if err = os.Mkdir(targetPathString, dirMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.WriteFile(targetPathString, data, fileMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExist checks whether a file or directory exists.
|
// IsExist checks whether a file or directory exists.
|
||||||
|
|||||||
@ -6,6 +6,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -230,3 +231,70 @@ func TestListDirRecursively(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.ElementsMatch(t, []string{"d1/f-d1", "d1/s1/f-d1s1"}, res)
|
assert.ElementsMatch(t, []string{"d1/f-d1", "d1/s1/f-d1s1"}, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadWriteRegularPathFile(t *testing.T) {
|
||||||
|
const readLimit = 10000
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
rootDir := tmpDir + "/root"
|
||||||
|
_ = os.Mkdir(rootDir, 0o755)
|
||||||
|
_ = os.WriteFile(tmpDir+"/other-file", []byte("other-content"), 0o755)
|
||||||
|
_ = os.Mkdir(rootDir+"/real-dir", 0o755)
|
||||||
|
_ = os.WriteFile(rootDir+"/real-dir/real-file", []byte("dummy-content"), 0o644)
|
||||||
|
_ = os.Symlink(rootDir+"/real-dir", rootDir+"/link-dir")
|
||||||
|
_ = os.Symlink(rootDir+"/real-dir/real-file", rootDir+"/real-dir/link-file")
|
||||||
|
|
||||||
|
t.Run("Read", func(t *testing.T) {
|
||||||
|
content, err := os.ReadFile(filepath.Join(rootDir, "../other-file"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "other-content", string(content))
|
||||||
|
|
||||||
|
content, err = ReadRegularPathFile(rootDir, "../other-file", readLimit)
|
||||||
|
require.ErrorIs(t, err, os.ErrNotExist)
|
||||||
|
assert.Empty(t, string(content))
|
||||||
|
|
||||||
|
content, err = ReadRegularPathFile(rootDir, "real-dir/real-file", readLimit)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "dummy-content", string(content))
|
||||||
|
|
||||||
|
_, err = ReadRegularPathFile(rootDir, "link-dir/real-file", readLimit)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
_, err = ReadRegularPathFile(rootDir, "real-dir/link-file", readLimit)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
_, err = ReadRegularPathFile(rootDir, "link-dir/link-file", readLimit)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Write", func(t *testing.T) {
|
||||||
|
assertFileContent := func(path, expected string) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if expected == "" {
|
||||||
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expected, string(data), "file content mismatch for %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := WriteRegularPathFile(rootDir, "new-dir/new-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertFileContent(rootDir+"/new-dir/new-file", "new-content")
|
||||||
|
|
||||||
|
err = WriteRegularPathFile(rootDir, "link-dir/real-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
err = WriteRegularPathFile(rootDir, "link-dir/link-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
err = WriteRegularPathFile(rootDir, "link-dir/new-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
err = WriteRegularPathFile(rootDir, "real-dir/link-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.ErrorIs(t, err, ErrNotRegularPathFile)
|
||||||
|
|
||||||
|
err = WriteRegularPathFile(rootDir, "../other-file", []byte("new-content"), 0o755, 0o644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertFileContent(rootDir+"/../other-file", "other-content")
|
||||||
|
assertFileContent(rootDir+"/other-file", "new-content")
|
||||||
|
|
||||||
|
err = WriteRegularPathFile(rootDir, "real-dir/real-file", []byte("changed-content"), 0o755, 0o644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assertFileContent(rootDir+"/real-dir/real-file", "changed-content")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -148,6 +148,13 @@
|
|||||||
"filter.private": "Privé",
|
"filter.private": "Privé",
|
||||||
"no_results_found": "Aucun résultat trouvé.",
|
"no_results_found": "Aucun résultat trouvé.",
|
||||||
"internal_error_skipped": "Une erreur interne est survenue, mais ignorée : %s",
|
"internal_error_skipped": "Une erreur interne est survenue, mais ignorée : %s",
|
||||||
|
"characters_spaces": "Espaces",
|
||||||
|
"characters_tabs": "Tabulations",
|
||||||
|
"text_indent_style": "Style d’indentation",
|
||||||
|
"text_indent_size": "Taille de l’indentation",
|
||||||
|
"text_line_wrap": "Retour à la ligne",
|
||||||
|
"text_line_nowrap": "Pas de retour à la ligne",
|
||||||
|
"text_line_wrap_mode": "Mode de retour automatique à la ligne",
|
||||||
"search.search": "Rechercher…",
|
"search.search": "Rechercher…",
|
||||||
"search.type_tooltip": "Type de recherche",
|
"search.type_tooltip": "Type de recherche",
|
||||||
"search.fuzzy": "Approximative",
|
"search.fuzzy": "Approximative",
|
||||||
@ -751,6 +758,7 @@
|
|||||||
"settings.add_email": "Ajouter un courriel",
|
"settings.add_email": "Ajouter un courriel",
|
||||||
"settings.add_openid": "Ajouter une URI OpenID",
|
"settings.add_openid": "Ajouter une URI OpenID",
|
||||||
"settings.add_email_confirmation_sent": "Un courriel de confirmation a été envoyé à « %s ». Veuillez vérifier votre boîte de réception dans les %s suivants pour confirmer votre adresse.",
|
"settings.add_email_confirmation_sent": "Un courriel de confirmation a été envoyé à « %s ». Veuillez vérifier votre boîte de réception dans les %s suivants pour confirmer votre adresse.",
|
||||||
|
"settings.email_primary_not_found": "L’adresse courriel sélectionnée est introuvable.",
|
||||||
"settings.add_email_success": "La nouvelle adresse a été ajoutée.",
|
"settings.add_email_success": "La nouvelle adresse a été ajoutée.",
|
||||||
"settings.email_preference_set_success": "Le courriel de préférence a été défini avec succès.",
|
"settings.email_preference_set_success": "Le courriel de préférence a été défini avec succès.",
|
||||||
"settings.add_openid_success": "La nouvelle adresse OpenID a été ajoutée.",
|
"settings.add_openid_success": "La nouvelle adresse OpenID a été ajoutée.",
|
||||||
@ -1490,6 +1498,7 @@
|
|||||||
"repo.issues.filter_sort.feweststars": "Favoris (croissant)",
|
"repo.issues.filter_sort.feweststars": "Favoris (croissant)",
|
||||||
"repo.issues.filter_sort.mostforks": "Bifurcations (décroissant)",
|
"repo.issues.filter_sort.mostforks": "Bifurcations (décroissant)",
|
||||||
"repo.issues.filter_sort.fewestforks": "Bifurcations (croissant)",
|
"repo.issues.filter_sort.fewestforks": "Bifurcations (croissant)",
|
||||||
|
"repo.issues.quick_goto": "Allez au ticket",
|
||||||
"repo.issues.action_open": "Ouvrir",
|
"repo.issues.action_open": "Ouvrir",
|
||||||
"repo.issues.action_close": "Fermer",
|
"repo.issues.action_close": "Fermer",
|
||||||
"repo.issues.action_label": "Label",
|
"repo.issues.action_label": "Label",
|
||||||
@ -1777,6 +1786,8 @@
|
|||||||
"repo.pulls.title_desc": "souhaite fusionner %[1]d révision(s) depuis <code>%[2]s</code> vers <code id=\"branch_target\">%[3]s</code>",
|
"repo.pulls.title_desc": "souhaite fusionner %[1]d révision(s) depuis <code>%[2]s</code> vers <code id=\"branch_target\">%[3]s</code>",
|
||||||
"repo.pulls.merged_title_desc": "a fusionné %[1]d révision(s) à partir de <code>%[2]s</code> vers <code>%[3]s</code> %[4]s",
|
"repo.pulls.merged_title_desc": "a fusionné %[1]d révision(s) à partir de <code>%[2]s</code> vers <code>%[3]s</code> %[4]s",
|
||||||
"repo.pulls.change_target_branch_at": "a remplacée la branche cible <b><strike>%s</strike></b> par <b>%s</b> %s.",
|
"repo.pulls.change_target_branch_at": "a remplacée la branche cible <b><strike>%s</strike></b> par <b>%s</b> %s.",
|
||||||
|
"repo.pulls.marked_as_work_in_progress_at": "a marqué la demande d’ajout comme travail en cours %s",
|
||||||
|
"repo.pulls.marked_as_ready_for_review_at": "a marqué la demande d’ajout comme prête pour relecture %s",
|
||||||
"repo.pulls.tab_conversation": "Discussion",
|
"repo.pulls.tab_conversation": "Discussion",
|
||||||
"repo.pulls.tab_commits": "Révisions",
|
"repo.pulls.tab_commits": "Révisions",
|
||||||
"repo.pulls.tab_files": "Fichiers Modifiés",
|
"repo.pulls.tab_files": "Fichiers Modifiés",
|
||||||
@ -1795,6 +1806,7 @@
|
|||||||
"repo.pulls.remove_prefix": "Enlever le préfixe <strong>%s</strong>",
|
"repo.pulls.remove_prefix": "Enlever le préfixe <strong>%s</strong>",
|
||||||
"repo.pulls.data_broken": "Cette demande d’ajout est impossible par manque d'informations de bifurcation.",
|
"repo.pulls.data_broken": "Cette demande d’ajout est impossible par manque d'informations de bifurcation.",
|
||||||
"repo.pulls.files_conflicted": "Cette demande d'ajout contient des modifications en conflit avec la branche ciblée.",
|
"repo.pulls.files_conflicted": "Cette demande d'ajout contient des modifications en conflit avec la branche ciblée.",
|
||||||
|
"repo.pulls.files_conflicted_no_listed_files": "(Aucun fichier en conflit répertorié)",
|
||||||
"repo.pulls.is_checking": "Recherche de conflits de fusion…",
|
"repo.pulls.is_checking": "Recherche de conflits de fusion…",
|
||||||
"repo.pulls.is_ancestor": "Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.",
|
"repo.pulls.is_ancestor": "Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.",
|
||||||
"repo.pulls.is_empty": "Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.",
|
"repo.pulls.is_empty": "Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.",
|
||||||
@ -1865,6 +1877,7 @@
|
|||||||
"repo.pulls.update_not_allowed": "Vous n'êtes pas autorisé à mettre à jour la branche",
|
"repo.pulls.update_not_allowed": "Vous n'êtes pas autorisé à mettre à jour la branche",
|
||||||
"repo.pulls.outdated_with_base_branch": "Cette branche est désynchronisée avec la branche de base",
|
"repo.pulls.outdated_with_base_branch": "Cette branche est désynchronisée avec la branche de base",
|
||||||
"repo.pulls.close": "Fermer la demande d’ajout",
|
"repo.pulls.close": "Fermer la demande d’ajout",
|
||||||
|
"repo.pulls.reopen": "Rouvrir la demande d’ajout",
|
||||||
"repo.pulls.closed_at": "a fermé cette demande d'ajout <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>.",
|
"repo.pulls.closed_at": "a fermé cette demande d'ajout <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>.",
|
||||||
"repo.pulls.reopened_at": "a rouvert cette demande d'ajout <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>.",
|
"repo.pulls.reopened_at": "a rouvert cette demande d'ajout <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>.",
|
||||||
"repo.pulls.cmd_instruction_hint": "Voir les instructions en ligne de commande",
|
"repo.pulls.cmd_instruction_hint": "Voir les instructions en ligne de commande",
|
||||||
@ -2120,6 +2133,8 @@
|
|||||||
"repo.settings.pulls.ignore_whitespace": "Ignorer les espaces lors des conflits",
|
"repo.settings.pulls.ignore_whitespace": "Ignorer les espaces lors des conflits",
|
||||||
"repo.settings.pulls.enable_autodetect_manual_merge": "Activer la détection automatique de la fusion manuelle (Remarque : dans certains cas particuliers, des erreurs de détection peuvent se produire)",
|
"repo.settings.pulls.enable_autodetect_manual_merge": "Activer la détection automatique de la fusion manuelle (Remarque : dans certains cas particuliers, des erreurs de détection peuvent se produire)",
|
||||||
"repo.settings.pulls.allow_rebase_update": "Activer la mise à jour de demande d'ajout par rebase",
|
"repo.settings.pulls.allow_rebase_update": "Activer la mise à jour de demande d'ajout par rebase",
|
||||||
|
"repo.settings.pulls.default_target_branch": "Branche cible par défaut pour les nouvelles demandes d’ajout",
|
||||||
|
"repo.settings.pulls.default_target_branch_default": "Branche par défaut (%s)",
|
||||||
"repo.settings.pulls.default_delete_branch_after_merge": "Supprimer la branche après la fusion par default",
|
"repo.settings.pulls.default_delete_branch_after_merge": "Supprimer la branche après la fusion par default",
|
||||||
"repo.settings.pulls.default_allow_edits_from_maintainers": "Autoriser les modifications par les mainteneurs par défaut",
|
"repo.settings.pulls.default_allow_edits_from_maintainers": "Autoriser les modifications par les mainteneurs par défaut",
|
||||||
"repo.settings.releases_desc": "Activer les publications du dépôt",
|
"repo.settings.releases_desc": "Activer les publications du dépôt",
|
||||||
@ -2432,7 +2447,8 @@
|
|||||||
"repo.settings.block_outdated_branch_desc": "La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.",
|
"repo.settings.block_outdated_branch_desc": "La fusion ne sera pas possible lorsque la branche principale est derrière la branche de base.",
|
||||||
"repo.settings.block_admin_merge_override": "Les administrateurs doivent respecter les règles de protection des branches",
|
"repo.settings.block_admin_merge_override": "Les administrateurs doivent respecter les règles de protection des branches",
|
||||||
"repo.settings.block_admin_merge_override_desc": "Les administrateurs doivent respecter les règles de protection des branches et ne peuvent pas les contourner.",
|
"repo.settings.block_admin_merge_override_desc": "Les administrateurs doivent respecter les règles de protection des branches et ne peuvent pas les contourner.",
|
||||||
"repo.settings.default_branch_desc": "Sélectionnez une branche par défaut pour les demandes de fusion et les révisions :",
|
"repo.settings.default_branch_desc": "Sélectionnez une branche par défaut pour les révisions.",
|
||||||
|
"repo.settings.default_target_branch_desc": "Les demandes d’ajout peuvent utiliser une branche cible différente, telle que définie dans la section Demandes d’ajouts des Paramètres avancés du dépôt.",
|
||||||
"repo.settings.merge_style_desc": "Styles de fusion",
|
"repo.settings.merge_style_desc": "Styles de fusion",
|
||||||
"repo.settings.default_merge_style_desc": "Méthode de fusion par défaut",
|
"repo.settings.default_merge_style_desc": "Méthode de fusion par défaut",
|
||||||
"repo.settings.choose_branch": "Choisissez une branche…",
|
"repo.settings.choose_branch": "Choisissez une branche…",
|
||||||
@ -2646,7 +2662,7 @@
|
|||||||
"repo.branch.restore_success": "La branche \"%s\" a été restaurée.",
|
"repo.branch.restore_success": "La branche \"%s\" a été restaurée.",
|
||||||
"repo.branch.restore_failed": "Impossible de restaurer la branche \"%s\".",
|
"repo.branch.restore_failed": "Impossible de restaurer la branche \"%s\".",
|
||||||
"repo.branch.protected_deletion_failed": "La branche \"%s\" est protégé. Elle ne peut pas être supprimée.",
|
"repo.branch.protected_deletion_failed": "La branche \"%s\" est protégé. Elle ne peut pas être supprimée.",
|
||||||
"repo.branch.default_deletion_failed": "La branche \"%s\" est la branche par défaut. Elle ne peut pas être supprimée.",
|
"repo.branch.default_deletion_failed": "« %s » est la branche par défaut ou la cible de demandes d’ajout. Elle ne peut pas être supprimée.",
|
||||||
"repo.branch.default_branch_not_exist": "La branche par défaut « %s » n‘existe pas.",
|
"repo.branch.default_branch_not_exist": "La branche par défaut « %s » n‘existe pas.",
|
||||||
"repo.branch.restore": "Restaurer la branche \"%s\"",
|
"repo.branch.restore": "Restaurer la branche \"%s\"",
|
||||||
"repo.branch.download": "Télécharger la branche \"%s\"",
|
"repo.branch.download": "Télécharger la branche \"%s\"",
|
||||||
@ -2663,7 +2679,7 @@
|
|||||||
"repo.branch.new_branch_from": "Créer une nouvelle branche à partir de \"%s\"",
|
"repo.branch.new_branch_from": "Créer une nouvelle branche à partir de \"%s\"",
|
||||||
"repo.branch.renamed": "La branche %s à été renommée en %s.",
|
"repo.branch.renamed": "La branche %s à été renommée en %s.",
|
||||||
"repo.branch.rename_default_or_protected_branch_error": "Seuls les administrateurs peuvent renommer les branches par défaut ou protégées.",
|
"repo.branch.rename_default_or_protected_branch_error": "Seuls les administrateurs peuvent renommer les branches par défaut ou protégées.",
|
||||||
"repo.branch.rename_protected_branch_failed": "Cette branche est protégée par des règles de protection basées sur des globs.",
|
"repo.branch.rename_protected_branch_failed": "Impossible de renommer cette branche en raison des règles de protection de branche.",
|
||||||
"repo.branch.commits_divergence_from": "Divergence de révisions : %[1]d en retard et %[2]d en avance sur %[3]s",
|
"repo.branch.commits_divergence_from": "Divergence de révisions : %[1]d en retard et %[2]d en avance sur %[3]s",
|
||||||
"repo.branch.commits_no_divergence": "Identique à la branche %[1]s",
|
"repo.branch.commits_no_divergence": "Identique à la branche %[1]s",
|
||||||
"repo.tag.create_tag": "Créer l'étiquette %s",
|
"repo.tag.create_tag": "Créer l'étiquette %s",
|
||||||
@ -3679,6 +3695,7 @@
|
|||||||
"actions.runs.delete.description": "Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.",
|
"actions.runs.delete.description": "Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.",
|
||||||
"actions.runs.not_done": "Cette exécution du flux de travail n’est pas terminée.",
|
"actions.runs.not_done": "Cette exécution du flux de travail n’est pas terminée.",
|
||||||
"actions.runs.view_workflow_file": "Voir le fichier du flux de travail",
|
"actions.runs.view_workflow_file": "Voir le fichier du flux de travail",
|
||||||
|
"actions.runs.workflow_graph": "Graphique du flux",
|
||||||
"actions.workflow.disable": "Désactiver le flux de travail",
|
"actions.workflow.disable": "Désactiver le flux de travail",
|
||||||
"actions.workflow.disable_success": "Le flux de travail « %s » a bien été désactivé.",
|
"actions.workflow.disable_success": "Le flux de travail « %s » a bien été désactivé.",
|
||||||
"actions.workflow.enable": "Activer le flux de travail",
|
"actions.workflow.enable": "Activer le flux de travail",
|
||||||
|
|||||||
@ -148,6 +148,13 @@
|
|||||||
"filter.private": "Príobháideach",
|
"filter.private": "Príobháideach",
|
||||||
"no_results_found": "Níor aimsíodh aon torthaí.",
|
"no_results_found": "Níor aimsíodh aon torthaí.",
|
||||||
"internal_error_skipped": "Tharla earráid inmheánach ach éirithe as: %s",
|
"internal_error_skipped": "Tharla earráid inmheánach ach éirithe as: %s",
|
||||||
|
"characters_spaces": "Spásanna",
|
||||||
|
"characters_tabs": "Cluaisíní",
|
||||||
|
"text_indent_style": "Stíl eangaithe",
|
||||||
|
"text_indent_size": "Méid an línithe",
|
||||||
|
"text_line_wrap": "Fillte",
|
||||||
|
"text_line_nowrap": "Gan fillte",
|
||||||
|
"text_line_wrap_mode": "Mód fillte líne",
|
||||||
"search.search": "Cuardaigh…",
|
"search.search": "Cuardaigh…",
|
||||||
"search.type_tooltip": "Cineál cuardaigh",
|
"search.type_tooltip": "Cineál cuardaigh",
|
||||||
"search.fuzzy": "Doiléir",
|
"search.fuzzy": "Doiléir",
|
||||||
@ -751,6 +758,7 @@
|
|||||||
"settings.add_email": "Cuir Seoladh R-phoist leis",
|
"settings.add_email": "Cuir Seoladh R-phoist leis",
|
||||||
"settings.add_openid": "Cuir OpenID URI",
|
"settings.add_openid": "Cuir OpenID URI",
|
||||||
"settings.add_email_confirmation_sent": "Seoladh ríomhphost deimhnithe chuig “%s”. Seiceáil do bhosca isteach laistigh den chéad %s eile chun do sheoladh ríomhphoist a dhearbhú.",
|
"settings.add_email_confirmation_sent": "Seoladh ríomhphost deimhnithe chuig “%s”. Seiceáil do bhosca isteach laistigh den chéad %s eile chun do sheoladh ríomhphoist a dhearbhú.",
|
||||||
|
"settings.email_primary_not_found": "Níorbh fhéidir an seoladh ríomhphoist roghnaithe a aimsiú.",
|
||||||
"settings.add_email_success": "Cuireadh an seoladh ríomhphoist nua leis.",
|
"settings.add_email_success": "Cuireadh an seoladh ríomhphoist nua leis.",
|
||||||
"settings.email_preference_set_success": "Socraíodh rogha ríomhphoist go rathúil.",
|
"settings.email_preference_set_success": "Socraíodh rogha ríomhphoist go rathúil.",
|
||||||
"settings.add_openid_success": "Cuireadh an seoladh OpenID nua leis.",
|
"settings.add_openid_success": "Cuireadh an seoladh OpenID nua leis.",
|
||||||
@ -1525,7 +1533,7 @@
|
|||||||
"repo.issues.comment_pull_merged_at": "cumasc tiomantas %[1]s le %[2]s %[3]s",
|
"repo.issues.comment_pull_merged_at": "cumasc tiomantas %[1]s le %[2]s %[3]s",
|
||||||
"repo.issues.comment_manually_pull_merged_at": "cumasc tiomantas %[1]s le %[2]s %[3]s",
|
"repo.issues.comment_manually_pull_merged_at": "cumasc tiomantas %[1]s le %[2]s %[3]s",
|
||||||
"repo.issues.close_comment_issue": "Dún le trácht",
|
"repo.issues.close_comment_issue": "Dún le trácht",
|
||||||
"repo.issues.reopen_issue": "Athoscail",
|
"repo.issues.reopen_issue": "Athoscail an Cheist",
|
||||||
"repo.issues.reopen_comment_issue": "Athoscail le trácht",
|
"repo.issues.reopen_comment_issue": "Athoscail le trácht",
|
||||||
"repo.issues.create_comment": "Trácht",
|
"repo.issues.create_comment": "Trácht",
|
||||||
"repo.issues.comment.blocked_user": "Ní féidir trácht a chruthú nó a chur in eagar toisc go bhfuil an tráchtaire nó úinéir an stórais bac ort.",
|
"repo.issues.comment.blocked_user": "Ní féidir trácht a chruthú nó a chur in eagar toisc go bhfuil an tráchtaire nó úinéir an stórais bac ort.",
|
||||||
@ -1869,6 +1877,7 @@
|
|||||||
"repo.pulls.update_not_allowed": "Ní cheadaítear duit brainse a nuashonrú",
|
"repo.pulls.update_not_allowed": "Ní cheadaítear duit brainse a nuashonrú",
|
||||||
"repo.pulls.outdated_with_base_branch": "Tá an brainse seo as dáta leis an mbunbhrainse",
|
"repo.pulls.outdated_with_base_branch": "Tá an brainse seo as dáta leis an mbunbhrainse",
|
||||||
"repo.pulls.close": "Dún Iarratas Tarraing",
|
"repo.pulls.close": "Dún Iarratas Tarraing",
|
||||||
|
"repo.pulls.reopen": "Athoscail Iarratas Tarraingthe",
|
||||||
"repo.pulls.closed_at": "dhún an t-iarratas tarraingthe seo <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
"repo.pulls.closed_at": "dhún an t-iarratas tarraingthe seo <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||||
"repo.pulls.reopened_at": "athoscail an t-iarratas tarraingthe seo <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
"repo.pulls.reopened_at": "athoscail an t-iarratas tarraingthe seo <a id=\"%[1]s\" href=\"#%[1]s\">%[2]s</a>",
|
||||||
"repo.pulls.cmd_instruction_hint": "Féach ar threoracha na n-orduithe",
|
"repo.pulls.cmd_instruction_hint": "Féach ar threoracha na n-orduithe",
|
||||||
@ -3686,6 +3695,7 @@
|
|||||||
"actions.runs.delete.description": "An bhfuil tú cinnte gur mian leat an rith sreabha oibre seo a scriosadh go buan? Ní féidir an gníomh seo a chealú.",
|
"actions.runs.delete.description": "An bhfuil tú cinnte gur mian leat an rith sreabha oibre seo a scriosadh go buan? Ní féidir an gníomh seo a chealú.",
|
||||||
"actions.runs.not_done": "Níl an rith sreabha oibre seo críochnaithe.",
|
"actions.runs.not_done": "Níl an rith sreabha oibre seo críochnaithe.",
|
||||||
"actions.runs.view_workflow_file": "Féach ar chomhad sreabha oibre",
|
"actions.runs.view_workflow_file": "Féach ar chomhad sreabha oibre",
|
||||||
|
"actions.runs.workflow_graph": "Graf Sreabhadh Oibre",
|
||||||
"actions.workflow.disable": "Díchumasaigh sreabhadh oibre",
|
"actions.workflow.disable": "Díchumasaigh sreabhadh oibre",
|
||||||
"actions.workflow.disable_success": "D'éirigh le sreabhadh oibre '%s' a dhíchumasú.",
|
"actions.workflow.disable_success": "D'éirigh le sreabhadh oibre '%s' a dhíchumasú.",
|
||||||
"actions.workflow.enable": "Cumasaigh sreabhadh oibre",
|
"actions.workflow.enable": "Cumasaigh sreabhadh oibre",
|
||||||
|
|||||||
@ -282,7 +282,7 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue)
|
|||||||
compareInfo, err := git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo,
|
compareInfo, err := git_service.GetCompareInfo(ctx, ctx.Repo.Repository, ctx.Repo.Repository, ctx.Repo.GitRepo,
|
||||||
git.RefName(baseCommit), git.RefName(pull.GetGitHeadRefName()), false, false)
|
git.RefName(baseCommit), git.RefName(pull.GetGitHeadRefName()), false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "fatal: Not a valid object name") || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
|
if gitcmd.IsStdErrorNotValidObjectName(err) || strings.Contains(err.Error(), "unknown revision or path not in the working tree") {
|
||||||
ctx.Data["IsPullRequestBroken"] = true
|
ctx.Data["IsPullRequestBroken"] = true
|
||||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||||
ctx.Data["NumCommits"] = 0
|
ctx.Data["NumCommits"] = 0
|
||||||
@ -442,7 +442,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
|
|||||||
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
|
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
|
||||||
git.RefName(pull.MergeBase), git.RefName(pull.GetGitHeadRefName()), false, false)
|
git.RefName(pull.MergeBase), git.RefName(pull.GetGitHeadRefName()), false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
|
if gitcmd.IsStdErrorNotValidObjectName(err) {
|
||||||
ctx.Data["IsPullRequestBroken"] = true
|
ctx.Data["IsPullRequestBroken"] = true
|
||||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||||
ctx.Data["NumCommits"] = 0
|
ctx.Data["NumCommits"] = 0
|
||||||
@ -584,7 +584,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s
|
|||||||
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
|
compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo,
|
||||||
git.RefNameFromBranch(pull.BaseBranch), git.RefName(pull.GetGitHeadRefName()), false, false)
|
git.RefNameFromBranch(pull.BaseBranch), git.RefName(pull.GetGitHeadRefName()), false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "fatal: Not a valid object name") {
|
if gitcmd.IsStdErrorNotValidObjectName(err) {
|
||||||
ctx.Data["IsPullRequestBroken"] = true
|
ctx.Data["IsPullRequestBroken"] = true
|
||||||
ctx.Data["BaseTarget"] = pull.BaseBranch
|
ctx.Data["BaseTarget"] = pull.BaseBranch
|
||||||
ctx.Data["NumCommits"] = 0
|
ctx.Data["NumCommits"] = 0
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/label"
|
"code.gitea.io/gitea/modules/label"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
@ -226,7 +227,21 @@ func ToStopWatches(ctx context.Context, doer *user_model.User, sws []*issues_mod
|
|||||||
// ToTrackedTimeList converts TrackedTimeList to API format
|
// ToTrackedTimeList converts TrackedTimeList to API format
|
||||||
func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
|
func ToTrackedTimeList(ctx context.Context, doer *user_model.User, tl issues_model.TrackedTimeList) api.TrackedTimeList {
|
||||||
result := make([]*api.TrackedTime, 0, len(tl))
|
result := make([]*api.TrackedTime, 0, len(tl))
|
||||||
|
permCache := cache.NewEphemeralCache()
|
||||||
for _, t := range tl {
|
for _, t := range tl {
|
||||||
|
// If the issue is not loaded, conservatively skip this entry to avoid bypassing permission checks.
|
||||||
|
if t.Issue == nil || t.Issue.Repo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
perm, err := cache.GetWithEphemeralCache(ctx, permCache, "repo-perm", t.Issue.RepoID, func(ctx context.Context, repoID int64) (access_model.Permission, error) {
|
||||||
|
return access_model.GetUserRepoPermission(ctx, t.Issue.Repo, doer)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !perm.CanReadIssuesOrPulls(t.Issue.IsPull) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
result = append(result, ToTrackedTime(ctx, doer, t))
|
result = append(result, ToTrackedTime(ctx, doer, t))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLabel_ToLabel(t *testing.T) {
|
func TestLabel_ToLabel(t *testing.T) {
|
||||||
@ -83,3 +84,43 @@ func TestToStopWatchesRespectsPermissions(t *testing.T) {
|
|||||||
assert.Len(t, visibleAdmin, 2)
|
assert.Len(t, visibleAdmin, 2)
|
||||||
assert.ElementsMatch(t, []string{"repo1", "repo3"}, []string{visibleAdmin[0].RepoName, visibleAdmin[1].RepoName})
|
assert.ElementsMatch(t, []string{"repo1", "repo3"}, []string{visibleAdmin[0].RepoName, visibleAdmin[1].RepoName})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToTrackedTime(t *testing.T) {
|
||||||
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
publicIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: 1})
|
||||||
|
privateIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: 3})
|
||||||
|
regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||||
|
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
publicTrackedTime := &issues_model.TrackedTime{IssueID: publicIssue.ID, UserID: regularUser.ID, Time: 3600}
|
||||||
|
privateTrackedTime := &issues_model.TrackedTime{IssueID: privateIssue.ID, UserID: regularUser.ID, Time: 1800}
|
||||||
|
require.NoError(t, db.Insert(ctx, publicTrackedTime))
|
||||||
|
require.NoError(t, db.Insert(ctx, privateTrackedTime))
|
||||||
|
|
||||||
|
t.Run("NilIssues", func(t *testing.T) {
|
||||||
|
list := ToTrackedTimeList(ctx, regularUser, issues_model.TrackedTimeList{publicTrackedTime, privateTrackedTime})
|
||||||
|
assert.Empty(t, list)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NilRepo", func(t *testing.T) {
|
||||||
|
badTrackedTime := &issues_model.TrackedTime{Issue: &issues_model.Issue{RepoID: 999999}}
|
||||||
|
visible := ToTrackedTimeList(ctx, regularUser, issues_model.TrackedTimeList{badTrackedTime})
|
||||||
|
assert.Empty(t, visible)
|
||||||
|
})
|
||||||
|
|
||||||
|
trackedTimes := issues_model.TrackedTimeList{publicTrackedTime, privateTrackedTime}
|
||||||
|
require.NoError(t, trackedTimes.LoadAttributes(ctx))
|
||||||
|
|
||||||
|
t.Run("ToRegularUser", func(t *testing.T) {
|
||||||
|
list := ToTrackedTimeList(ctx, regularUser, trackedTimes)
|
||||||
|
require.Len(t, list, 1)
|
||||||
|
assert.Equal(t, "repo1", list[0].Issue.Repo.Name)
|
||||||
|
})
|
||||||
|
t.Run("ToAdminUser", func(t *testing.T) {
|
||||||
|
list := ToTrackedTimeList(ctx, adminUser, trackedTimes)
|
||||||
|
require.Len(t, list, 2)
|
||||||
|
assert.ElementsMatch(t, []string{"repo1", "repo3"}, []string{list[0].Issue.Repo.Name, list[1].Issue.Repo.Name})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -103,12 +103,12 @@ func generateExpansion(ctx context.Context, src string, templateRepo, generateRe
|
|||||||
|
|
||||||
// giteaTemplateFileMatcher holds information about a .gitea/template file
|
// giteaTemplateFileMatcher holds information about a .gitea/template file
|
||||||
type giteaTemplateFileMatcher struct {
|
type giteaTemplateFileMatcher struct {
|
||||||
LocalFullPath string
|
relPath string
|
||||||
globs []glob.Glob
|
globs []glob.Glob
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGiteaTemplateFileMatcher(fullPath string, content []byte) *giteaTemplateFileMatcher {
|
func newGiteaTemplateFileMatcher(relPath string, content []byte) *giteaTemplateFileMatcher {
|
||||||
gt := &giteaTemplateFileMatcher{LocalFullPath: fullPath}
|
gt := &giteaTemplateFileMatcher{relPath: relPath}
|
||||||
gt.globs = make([]glob.Glob, 0)
|
gt.globs = make([]glob.Glob, 0)
|
||||||
scanner := bufio.NewScanner(bytes.NewReader(content))
|
scanner := bufio.NewScanner(bytes.NewReader(content))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@ -139,64 +139,44 @@ func (gt *giteaTemplateFileMatcher) Match(s string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func readLocalTmpRepoFileContent(localPath string, limit int) ([]byte, error) {
|
|
||||||
ok, err := util.IsRegularFile(localPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !ok {
|
|
||||||
return nil, fs.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(localPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return util.ReadWithLimit(f, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) {
|
func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) {
|
||||||
localPath := filepath.Join(tmpDir, ".gitea", "template")
|
templateRelPath := filepath.Join(".gitea", "template")
|
||||||
content, err := readLocalTmpRepoFileContent(localPath, 1024*1024)
|
content, err := util.ReadRegularPathFile(tmpDir, templateRelPath, 1024*1024)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, util.Iif(errors.Is(err, util.ErrNotRegularPathFile), os.ErrNotExist, err)
|
||||||
}
|
}
|
||||||
return newGiteaTemplateFileMatcher(localPath, content), nil
|
return newGiteaTemplateFileMatcher(templateRelPath, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error {
|
func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error {
|
||||||
tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath)
|
content, err := util.ReadRegularPathFile(tmpDir, tmpDirSubPath, 1024*1024)
|
||||||
content, err := readLocalTmpRepoFileContent(tmpFullPath, 1024*1024)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return util.Iif(errors.Is(err, fs.ErrNotExist), nil, err)
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
if err := util.Remove(tmpFullPath); err != nil {
|
if err := os.Remove(util.FilePathJoinAbs(tmpDir, tmpDirSubPath)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedContent := generateExpansion(ctx, string(content), templateRepo, generateRepo)
|
generatedContent := generateExpansion(ctx, string(content), templateRepo, generateRepo)
|
||||||
substSubPath := filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))
|
substSubPath := filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))
|
||||||
newLocalPath := filepath.Join(tmpDir, substSubPath)
|
return util.WriteRegularPathFile(tmpDir, substSubPath, []byte(generatedContent), 0o755, 0o644)
|
||||||
regular, err := util.IsRegularFile(newLocalPath)
|
|
||||||
if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(newLocalPath, []byte(generatedContent), 0o644)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, generateRepo *repo_model.Repository, fileMatcher *giteaTemplateFileMatcher) error {
|
// processGiteaTemplateFile processes and removes the .gitea/template file, does variable expansion for template files
|
||||||
if err := util.Remove(fileMatcher.LocalFullPath); err != nil {
|
// and save the processed files to the filesystem. It returns a list of skipped files that are not regular paths.
|
||||||
return fmt.Errorf("unable to remove .gitea/template: %w", err)
|
func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo, generateRepo *repo_model.Repository, fileMatcher *giteaTemplateFileMatcher) (skippedFiles []string, _ error) {
|
||||||
|
// Why not use "os.Root" here: symlink is unsafe even in the same root but "os.Root" can't help, it's more difficult to use "os.Root" to do the WalkDir.
|
||||||
|
if err := os.Remove(util.FilePathJoinAbs(tmpDir, fileMatcher.relPath)); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to remove .gitea/template: %w", err)
|
||||||
}
|
}
|
||||||
if !fileMatcher.HasRules() {
|
if !fileMatcher.HasRules() {
|
||||||
return nil // Avoid walking tree if there are no globs
|
return skippedFiles, nil // Avoid walking tree if there are no globs
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.WalkDir(tmpDir, func(fullPath string, d os.DirEntry, walkErr error) error {
|
err := filepath.WalkDir(tmpDir, func(fullPath string, d os.DirEntry, walkErr error) error {
|
||||||
if walkErr != nil {
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
@ -208,10 +188,22 @@ func processGiteaTemplateFile(ctx context.Context, tmpDir string, templateRepo,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if fileMatcher.Match(filepath.ToSlash(tmpDirSubPath)) {
|
if fileMatcher.Match(filepath.ToSlash(tmpDirSubPath)) {
|
||||||
return substGiteaTemplateFile(ctx, tmpDir, tmpDirSubPath, templateRepo, generateRepo)
|
err := substGiteaTemplateFile(ctx, tmpDir, tmpDirSubPath, templateRepo, generateRepo)
|
||||||
|
if errors.Is(err, util.ErrNotRegularPathFile) {
|
||||||
|
skippedFiles = append(skippedFiles, tmpDirSubPath)
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}) // end: WalkDir
|
}) // end: WalkDir
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = util.RemoveAll(util.FilePathJoinAbs(tmpDir, ".git")); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return skippedFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error {
|
func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository, tmpDir string) error {
|
||||||
@ -236,7 +228,7 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
|
|||||||
// Variable expansion
|
// Variable expansion
|
||||||
fileMatcher, err := readGiteaTemplateFile(tmpDir)
|
fileMatcher, err := readGiteaTemplateFile(tmpDir)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher)
|
_, err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("processGiteaTemplateFile: %w", err)
|
return fmt.Errorf("processGiteaTemplateFile: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,7 +74,7 @@ func TestFilePathSanitize(t *testing.T) {
|
|||||||
assert.Equal(t, ".", filePathSanitize("/"))
|
assert.Equal(t, ".", filePathSanitize("/"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessGiteaTemplateFile(t *testing.T) {
|
func TestProcessGiteaTemplateFileGenerate(t *testing.T) {
|
||||||
tmpDir := filepath.Join(t.TempDir(), "gitea-template-test")
|
tmpDir := filepath.Join(t.TempDir(), "gitea-template-test")
|
||||||
|
|
||||||
assertFileContent := func(path, expected string) {
|
assertFileContent := func(path, expected string) {
|
||||||
@ -97,6 +97,8 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
|||||||
assert.Equal(t, expected, link, "symlink target mismatch for %s", path)
|
assert.Equal(t, expected, link, "symlink target mismatch for %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
require.NoError(t, os.MkdirAll(tmpDir+"/.git", 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(tmpDir+"/.git/config", []byte("git-config-dummy"), 0o644))
|
||||||
require.NoError(t, os.MkdirAll(tmpDir+"/.gitea", 0o755))
|
require.NoError(t, os.MkdirAll(tmpDir+"/.gitea", 0o755))
|
||||||
require.NoError(t, os.WriteFile(tmpDir+"/.gitea/template", []byte("*\ninclude/**"), 0o644))
|
require.NoError(t, os.WriteFile(tmpDir+"/.gitea/template", []byte("*\ninclude/**"), 0o644))
|
||||||
require.NoError(t, os.MkdirAll(tmpDir+"/sub", 0o755))
|
require.NoError(t, os.MkdirAll(tmpDir+"/sub", 0o755))
|
||||||
@ -127,10 +129,20 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
|||||||
assertFileContent("subst-${TEMPLATE_NAME}-to-link", toLinkContent)
|
assertFileContent("subst-${TEMPLATE_NAME}-to-link", toLinkContent)
|
||||||
assertFileContent("subst-${TEMPLATE_NAME}-from-link", fromLinkContent)
|
assertFileContent("subst-${TEMPLATE_NAME}-from-link", fromLinkContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// case-5
|
||||||
|
{
|
||||||
|
require.NoError(t, os.MkdirAll(tmpDir+"/real-dir", 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(tmpDir+"/real-dir/real-file", []byte("origin content"), 0o644))
|
||||||
|
require.NoError(t, os.MkdirAll(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir", 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(tmpDir+"/include/subst-${TEMPLATE_NAME}-link-dir/real-file", []byte("template content"), 0o644))
|
||||||
|
require.NoError(t, os.Symlink(tmpDir+"/real-dir", tmpDir+"/include/subst-TemplateRepoName-link-dir"))
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// will succeed
|
// will succeed
|
||||||
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-normal", []byte("dummy subst template name normal"), 0o644))
|
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-normal", []byte("dummy subst template name normal"), 0o644))
|
||||||
// will skil if the path subst result is a link
|
// will be skipped if the path subst result is a link
|
||||||
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-to-link", []byte("dummy subst template name to link"), 0o644))
|
require.NoError(t, os.WriteFile(tmpDir+"/subst-${TEMPLATE_NAME}-to-link", []byte("dummy subst template name to link"), 0o644))
|
||||||
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/subst-TemplateRepoName-to-link"))
|
require.NoError(t, os.Symlink(tmpDir+"/sub/link-target", tmpDir+"/subst-TemplateRepoName-to-link"))
|
||||||
// will be skipped since the source is a symlink
|
// will be skipped since the source is a symlink
|
||||||
@ -143,9 +155,20 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
|||||||
{
|
{
|
||||||
templateRepo := &repo_model.Repository{Name: "TemplateRepoName"}
|
templateRepo := &repo_model.Repository{Name: "TemplateRepoName"}
|
||||||
generatedRepo := &repo_model.Repository{Name: "/../.gIt/name"}
|
generatedRepo := &repo_model.Repository{Name: "/../.gIt/name"}
|
||||||
|
assertFileContent(".git/config", "git-config-dummy")
|
||||||
fileMatcher, _ := readGiteaTemplateFile(tmpDir)
|
fileMatcher, _ := readGiteaTemplateFile(tmpDir)
|
||||||
err := processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fileMatcher)
|
skippedFiles, err := processGiteaTemplateFile(t.Context(), tmpDir, templateRepo, generatedRepo, fileMatcher)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"include/subst-${TEMPLATE_NAME}-link-dir/real-file",
|
||||||
|
"include/subst-TemplateRepoName-link-dir",
|
||||||
|
"link",
|
||||||
|
"subst-${TEMPLATE_NAME}-from-link",
|
||||||
|
"subst-${TEMPLATE_NAME}-to-link",
|
||||||
|
"subst-TemplateRepoName-to-link",
|
||||||
|
}, skippedFiles)
|
||||||
|
assertFileContent(".git/config", "")
|
||||||
|
assertFileContent(".gitea/template", "")
|
||||||
assertFileContent("include/foo/bar/test.txt", "include subdir TemplateRepoName")
|
assertFileContent("include/foo/bar/test.txt", "include subdir TemplateRepoName")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,32 +205,38 @@ func TestProcessGiteaTemplateFile(t *testing.T) {
|
|||||||
assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target")
|
assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// case-5
|
||||||
{
|
{
|
||||||
templateFilePath := tmpDir + "/.gitea/template"
|
assertFileContent("real-dir/real-file", "origin content")
|
||||||
|
|
||||||
_ = os.Remove(templateFilePath)
|
|
||||||
_, err := os.Lstat(templateFilePath)
|
|
||||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
||||||
_, err = readGiteaTemplateFile(tmpDir) // no template file
|
|
||||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
||||||
|
|
||||||
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
|
|
||||||
_ = os.Symlink(templateFilePath+".target", templateFilePath)
|
|
||||||
content, _ := os.ReadFile(templateFilePath)
|
|
||||||
require.Equal(t, "test-data-target", string(content))
|
|
||||||
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
|
|
||||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
|
||||||
|
|
||||||
_ = os.Remove(templateFilePath)
|
|
||||||
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
|
|
||||||
content, _ = os.ReadFile(templateFilePath)
|
|
||||||
require.Equal(t, "test-data-regular", string(content))
|
|
||||||
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, fm.globs, 1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProcessGiteaTemplateFileRead(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
_ = os.Mkdir(tmpDir+"/.gitea", 0o755)
|
||||||
|
templateFilePath := tmpDir + "/.gitea/template"
|
||||||
|
_ = os.Remove(templateFilePath)
|
||||||
|
_, err := os.Lstat(templateFilePath)
|
||||||
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||||
|
_, err = readGiteaTemplateFile(tmpDir) // no template file
|
||||||
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||||
|
|
||||||
|
_ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644)
|
||||||
|
_ = os.Symlink(templateFilePath+".target", templateFilePath)
|
||||||
|
content, _ := os.ReadFile(templateFilePath)
|
||||||
|
require.Equal(t, "test-data-target", string(content))
|
||||||
|
_, err = readGiteaTemplateFile(tmpDir) // symlinked template file
|
||||||
|
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||||
|
|
||||||
|
_ = os.Remove(templateFilePath)
|
||||||
|
_ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644)
|
||||||
|
content, _ = os.ReadFile(templateFilePath)
|
||||||
|
require.Equal(t, "test-data-regular", string(content))
|
||||||
|
fm, err := readGiteaTemplateFile(tmpDir) // regular template file
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, fm.globs, 1)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTransformers(t *testing.T) {
|
func TestTransformers(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -16,6 +15,7 @@ import (
|
|||||||
"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/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/globallock"
|
"code.gitea.io/gitea/modules/globallock"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
@ -59,7 +59,7 @@ func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath
|
|||||||
// Look for both files
|
// Look for both files
|
||||||
filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, unescaped, gitPath)
|
filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, unescaped, gitPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "Not a valid object name") {
|
if gitcmd.IsStdErrorNotValidObjectName(err) {
|
||||||
return false, gitPath, nil // branch doesn't exist
|
return false, gitPath, nil // branch doesn't exist
|
||||||
}
|
}
|
||||||
log.Error("Wiki LsTree failed, err: %v", err)
|
log.Error("Wiki LsTree failed, err: %v", err)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user