From a2f9fb501f1154be9aefde41501bd9befe06697d Mon Sep 17 00:00:00 2001 From: forgejo-backport-action Date: Wed, 18 Mar 2026 21:11:52 +0100 Subject: [PATCH] [v14.0/forgejo] fix: remove template file from generated repo (#11722) **Backport:** https://codeberg.org/forgejo/forgejo/pulls/11691 This PR fixes a bug where the previously-implemented functionality to delete the `.gitea/template` or `.forgejo/template` file when generating a repository from a template was not working. The issue happened because the code was using `util.Remove()` with a relative path, but this resolves against the process working directory instead of the temporary clone directory. The fix was to use `root.Remove()` which is based on an `os.OpenRoot()` anchored at `tmpDir`. Updated integration tests and verified that they pass with this change and fail without it. Co-authored-by: Brandon Rothweiler Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11722 Reviewed-by: Gusted Co-authored-by: forgejo-backport-action Co-committed-by: forgejo-backport-action --- services/repository/generate_repo_commit.go | 16 ++++++++-------- .../repository/generate_repo_commit_legacy.go | 16 ++++++++-------- tests/integration/repo_generate_test.go | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/services/repository/generate_repo_commit.go b/services/repository/generate_repo_commit.go index 6c20ea41db..981649c677 100644 --- a/services/repository/generate_repo_commit.go +++ b/services/repository/generate_repo_commit.go @@ -54,19 +54,19 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } if gt != nil { - if err := util.Remove(gt.Path); err != nil { + // All file access should be done through `root` to avoid file traversal attacks, especially with symlinks + root, err := os.OpenRoot(tmpDir) + if err != nil { + return fmt.Errorf("open root: %w", err) + } + defer root.Close() + + if err := root.Remove(gt.Path); err != nil { return fmt.Errorf("remove .giteatemplate: %w", err) } // Avoid walking tree if there are no globs if len(gt.Globs()) > 0 { - // All file access should be done through `root` to avoid file traversal attacks, especially with symlinks - root, err := os.OpenRoot(tmpDir) - if err != nil { - return fmt.Errorf("open root: %w", err) - } - defer root.Close() - tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" if err := filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil { diff --git a/services/repository/generate_repo_commit_legacy.go b/services/repository/generate_repo_commit_legacy.go index 7f2aa8d814..a4d47c9975 100644 --- a/services/repository/generate_repo_commit_legacy.go +++ b/services/repository/generate_repo_commit_legacy.go @@ -55,19 +55,19 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } if gt != nil { - if err := util.Remove(gt.Path); err != nil { + // All file access should be done through `root` to avoid file traversal attacks, especially with symlinks + root, err := os.OpenRoot(tmpDir) + if err != nil { + return fmt.Errorf("open root: %w", err) + } + defer root.Close() + + if err := root.Remove(gt.Path); err != nil { return fmt.Errorf("remove .giteatemplate: %w", err) } // Avoid walking tree if there are no globs if len(gt.Globs()) > 0 { - // All file access should be done through `root` to avoid file traversal attacks, especially with symlinks - root, err := os.OpenRoot(tmpDir) - if err != nil { - return fmt.Errorf("open root: %w", err) - } - defer root.Close() - tmpDirSlash := strings.TrimSuffix(filepath.ToSlash(tmpDir), "/") + "/" if err := filepath.WalkDir(tmpDirSlash, func(path string, d os.DirEntry, walkErr error) error { if walkErr != nil { diff --git a/tests/integration/repo_generate_test.go b/tests/integration/repo_generate_test.go index af3415566a..637f0a478d 100644 --- a/tests/integration/repo_generate_test.go +++ b/tests/integration/repo_generate_test.go @@ -120,6 +120,10 @@ Clone URL: %s%s/%s.git`, req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/%s.log", generateOwner.Name, generateRepoName, generateRepoName) resp = session.MakeRequest(t, req, http.StatusOK) assert.Equal(t, generateRepoName, resp.Body.String()) + + // The .gitea/template file should not be present in the generated repo + req = NewRequestf(t, "GET", "/%s/%s/raw/branch/master/.gitea/template", generateOwner.Name, generateRepoName) + session.MakeRequest(t, req, http.StatusNotFound) } // test form elements before and after POST error response @@ -291,6 +295,16 @@ func TestRepoGenerateTemplating(t *testing.T) { user.Name, generatedName) assert.Equal(t, body, resp.Body.String()) + + // The .forgejo/template file should not be present in the generated repo + req = NewRequestf( + t, + "GET", "/%s/%s/raw/branch/%s/.forgejo/template", + user.Name, + generatedName, + template.DefaultBranch, + ) + session.MakeRequest(t, req, http.StatusNotFound) }) }