0
0
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:
Karthik Bhandary 2026-02-25 13:34:27 +05:30 committed by GitHub
commit 226ace7eaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 343 additions and 95 deletions

View File

@ -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))

View File

@ -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)
} }
} }

View File

@ -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
} }

View File

@ -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(),
} }

View File

@ -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 == "" {

View File

@ -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)

View File

@ -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.

View File

@ -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")
})
}

View File

@ -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 dindentation",
"text_indent_size": "Taille de lindentation",
"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": "Ladresse 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 dajout comme travail en cours %s",
"repo.pulls.marked_as_ready_for_review_at": "a marqué la demande dajout 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 dajout est impossible par manque d'informations de bifurcation.", "repo.pulls.data_broken": "Cette demande dajout 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 dajout", "repo.pulls.close": "Fermer la demande dajout",
"repo.pulls.reopen": "Rouvrir la demande dajout",
"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 dajout",
"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 dajout peuvent utiliser une branche cible différente, telle que définie dans la section Demandes dajouts 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 dajout. Elle ne peut pas être supprimée.",
"repo.branch.default_branch_not_exist": "La branche par défaut « %s » nexiste pas.", "repo.branch.default_branch_not_exist": "La branche par défaut « %s » nexiste 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 nest pas terminée.", "actions.runs.not_done": "Cette exécution du flux de travail nest 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",

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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})
})
}

View File

@ -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)
} }

View File

@ -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

View File

@ -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)