diff --git a/.github/workflows/cron-licenses.yml b/.github/workflows/cron-licenses.yml index 12f52289b6..a8be1ffa59 100644 --- a/.github/workflows/cron-licenses.yml +++ b/.github/workflows/cron-licenses.yml @@ -9,8 +9,10 @@ jobs: cron-licenses: runs-on: ubuntu-latest if: github.repository == 'go-gitea/gitea' + permissions: + contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/cron-translations.yml b/.github/workflows/cron-translations.yml index ae2238ad2d..3a012e9876 100644 --- a/.github/workflows/cron-translations.yml +++ b/.github/workflows/cron-translations.yml @@ -9,8 +9,10 @@ jobs: crowdin-pull: runs-on: ubuntu-latest if: github.repository == 'go-gitea/gitea' + permissions: + contents: write steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: crowdin/github-action@v1 with: upload_sources: true diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index b21341a277..d18ee6e998 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -24,6 +24,8 @@ jobs: detect: runs-on: ubuntu-latest timeout-minutes: 3 + permissions: + contents: read outputs: backend: ${{ steps.changes.outputs.backend }} frontend: ${{ steps.changes.outputs.frontend }} @@ -34,7 +36,7 @@ jobs: swagger: ${{ steps.changes.outputs.swagger }} yaml: ${{ steps.changes.outputs.yaml }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: dorny/paths-filter@v3 id: changes with: diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index f73772e934..c146b439e0 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -10,13 +10,17 @@ concurrency: jobs: files-changed: uses: ./.github/workflows/files-changed.yml + permissions: + contents: read lint-backend: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -30,8 +34,10 @@ jobs: if: needs.files-changed.outputs.templates == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: astral-sh/setup-uv@v6 - run: uv python install 3.12 - uses: pnpm/action-setup@v4 @@ -46,8 +52,10 @@ jobs: if: needs.files-changed.outputs.yaml == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: astral-sh/setup-uv@v6 - run: uv python install 3.12 - run: make deps-py @@ -57,8 +65,10 @@ jobs: if: needs.files-changed.outputs.swagger == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v5 with: @@ -70,8 +80,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.templates == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -82,8 +94,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -99,8 +113,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -114,8 +130,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -127,8 +145,10 @@ jobs: if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v5 with: @@ -143,8 +163,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -175,8 +197,10 @@ jobs: if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 - uses: actions/setup-node@v5 with: @@ -188,8 +212,10 @@ jobs: if: needs.files-changed.outputs.actions == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml index 21ec76b48e..66f48d5af8 100644 --- a/.github/workflows/pull-db-tests.yml +++ b/.github/workflows/pull-db-tests.yml @@ -10,11 +10,15 @@ concurrency: jobs: files-changed: uses: ./.github/workflows/files-changed.yml + permissions: + contents: read test-pgsql: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read services: pgsql: image: postgres:14 @@ -38,7 +42,7 @@ jobs: ports: - "9000:9000" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -65,8 +69,10 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -90,6 +96,8 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read services: elasticsearch: image: elasticsearch:7.5.0 @@ -124,7 +132,7 @@ jobs: ports: - 10000:10000 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -152,6 +160,8 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read services: mysql: # the bitnami mysql image has more options than the official one, it's easier to customize @@ -177,7 +187,7 @@ jobs: - "587:587" - "993:993" steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod @@ -203,6 +213,8 @@ jobs: if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read services: mssql: image: mcr.microsoft.com/mssql/server:2019-latest @@ -217,7 +229,7 @@ jobs: ports: - 10000:10000 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version-file: go.mod diff --git a/.github/workflows/pull-docker-dryrun.yml b/.github/workflows/pull-docker-dryrun.yml index 9c9dd2ffe6..1cd1ba31dd 100644 --- a/.github/workflows/pull-docker-dryrun.yml +++ b/.github/workflows/pull-docker-dryrun.yml @@ -10,13 +10,17 @@ concurrency: jobs: files-changed: uses: ./.github/workflows/files-changed.yml + permissions: + contents: read container: if: needs.files-changed.outputs.docker == 'true' || needs.files-changed.outputs.actions == 'true' needs: files-changed runs-on: ubuntu-latest + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: docker/setup-buildx-action@v3 - name: Build regular container image uses: docker/build-push-action@v5 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index ada4c18d33..c8ce0aa787 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -11,8 +11,10 @@ concurrency: jobs: nightly-binary: runs-on: namespace-profile-gitea-release-binary + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -56,12 +58,14 @@ jobs: - name: upload binaries to s3 run: | aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress + nightly-container: runs-on: namespace-profile-gitea-release-docker permissions: + contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 35558933e0..ef36e55a94 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -12,8 +12,10 @@ concurrency: jobs: binary: runs-on: namespace-profile-gitea-release-binary + permissions: + contents: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -66,12 +68,14 @@ jobs: gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/* env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + container: runs-on: namespace-profile-gitea-release-docker permissions: + contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 56426d3bc3..a3838de3c0 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -15,9 +15,10 @@ jobs: binary: runs-on: namespace-profile-gitea-release-binary permissions: + contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force @@ -70,12 +71,14 @@ jobs: gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/* env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + container: runs-on: namespace-profile-gitea-release-docker permissions: + contents: read packages: write # to publish to ghcr.io steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 # fetch all commits instead of only the last as some branches are long lived and could have many between versions # fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567 - run: git fetch --unshallow --quiet --tags --force diff --git a/models/user/search.go b/models/user/search.go index db4b07f64a..36d1d3913b 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -18,6 +18,23 @@ import ( "xorm.io/xorm" ) +// AdminUserOrderByMap represents all possible admin user search orders +// This should only be used for admin API endpoints as we should not expose "updated" ordering which could expose recent user activity including logins. +var AdminUserOrderByMap = map[string]map[string]db.SearchOrderBy{ + "asc": { + "name": db.SearchOrderByAlphabetically, + "created": db.SearchOrderByOldest, + "updated": db.SearchOrderByLeastUpdated, + "id": db.SearchOrderByID, + }, + "desc": { + "name": db.SearchOrderByAlphabeticallyReverse, + "created": db.SearchOrderByNewest, + "updated": db.SearchOrderByRecentUpdated, + "id": db.SearchOrderByIDReverse, + }, +} + // SearchUserOptions contains the options for searching type SearchUserOptions struct { db.ListOptions diff --git a/modules/git/diff.go b/modules/git/diff.go index 437b26eb05..c97a2141bf 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -32,20 +32,6 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, writer return GetRepoRawDiffForFile(repo, "", commitID, diffType, "", writer) } -// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. -func GetReverseRawDiff(ctx context.Context, repoPath, commitID string, writer io.Writer) error { - stderr := new(bytes.Buffer) - if err := gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R"). - AddDynamicArguments(commitID). - WithDir(repoPath). - WithStdout(writer). - WithStderr(stderr). - Run(ctx); err != nil { - return fmt.Errorf("Run: %w - %s", err, stderr) - } - return nil -} - // GetRepoRawDiffForFile dumps diff results of file in given commit ID to io.Writer according given repository func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diffType RawDiffType, file string, writer io.Writer) error { commit, err := repo.GetCommit(endCommit) diff --git a/modules/git/repo.go b/modules/git/repo.go index 88acbd30e6..baf29432ec 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -123,6 +123,8 @@ type CloneRepoOptions struct { Depth int Filter string SkipTLSVerify bool + SingleBranch bool + Env []string } // Clone clones original repository to target path. @@ -157,6 +159,9 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { if opts.Filter != "" { cmd.AddArguments("--filter").AddDynamicArguments(opts.Filter) } + if opts.SingleBranch { + cmd.AddArguments("--single-branch") + } if len(opts.Branch) > 0 { cmd.AddArguments("-b").AddDynamicArguments(opts.Branch) } @@ -167,13 +172,17 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error { } envs := os.Environ() - u, err := url.Parse(from) - if err == nil { - envs = proxy.EnvWithProxy(u) + if opts.Env != nil { + envs = opts.Env + } else { + u, err := url.Parse(from) + if err == nil { + envs = proxy.EnvWithProxy(u) + } } stderr := new(bytes.Buffer) - if err = cmd. + if err := cmd. WithTimeout(opts.Timeout). WithEnv(envs). WithStdout(io.Discard). @@ -228,14 +237,3 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error { return nil } - -// GetLatestCommitTime returns time for latest commit in repository (across all branches) -func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) { - cmd := gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)") - stdout, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - if err != nil { - return time.Time{}, err - } - commitTime := strings.TrimSpace(stdout) - return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) -} diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index 26ee3a091a..776c297a34 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -10,16 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGetLatestCommitTime(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - lct, err := GetLatestCommitTime(t.Context(), bareRepo1Path) - assert.NoError(t, err) - // Time is Sun Nov 13 16:40:14 2022 +0100 - // which is the time of commit - // ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master) - assert.EqualValues(t, 1668354014, lct.Unix()) -} - func TestRepoIsEmpty(t *testing.T) { emptyRepo2Path := filepath.Join(testReposDir, "repo2_empty") repo, err := OpenRepository(t.Context(), emptyRepo2Path) diff --git a/modules/gitrepo/clone.go b/modules/gitrepo/clone.go index 8c437f657c..a0e4cc814c 100644 --- a/modules/gitrepo/clone.go +++ b/modules/gitrepo/clone.go @@ -18,3 +18,7 @@ func CloneExternalRepo(ctx context.Context, fromRemoteURL string, toRepo Reposit func CloneRepoToLocal(ctx context.Context, fromRepo Repository, toLocalPath string, opts git.CloneRepoOptions) error { return git.Clone(ctx, repoPath(fromRepo), toLocalPath, opts) } + +func Clone(ctx context.Context, fromRepo, toRepo Repository, opts git.CloneRepoOptions) error { + return git.Clone(ctx, repoPath(fromRepo), repoPath(toRepo), opts) +} diff --git a/modules/gitrepo/commit.go b/modules/gitrepo/commit.go index e0a87ac10b..da0f3b85a2 100644 --- a/modules/gitrepo/commit.go +++ b/modules/gitrepo/commit.go @@ -7,6 +7,7 @@ import ( "context" "strconv" "strings" + "time" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git/gitcmd" @@ -94,3 +95,18 @@ func AllCommitsCount(ctx context.Context, repo Repository, hidePRRefs bool, file return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) } + +func GetFullCommitID(ctx context.Context, repo Repository, shortID string) (string, error) { + return git.GetFullCommitID(ctx, repoPath(repo), shortID) +} + +// GetLatestCommitTime returns time for latest commit in repository (across all branches) +func GetLatestCommitTime(ctx context.Context, repo Repository) (time.Time, error) { + stdout, err := RunCmdString(ctx, repo, + gitcmd.NewCommand("for-each-ref", "--sort=-committerdate", git.BranchPrefix, "--count", "1", "--format=%(committerdate)")) + if err != nil { + return time.Time{}, err + } + commitTime := strings.TrimSpace(stdout) + return time.Parse("Mon Jan _2 15:04:05 2006 -0700", commitTime) +} diff --git a/modules/gitrepo/commit_test.go b/modules/gitrepo/commit_test.go index 93483f3e0d..05cedc39ef 100644 --- a/modules/gitrepo/commit_test.go +++ b/modules/gitrepo/commit_test.go @@ -33,3 +33,13 @@ func TestCommitsCountWithoutBase(t *testing.T) { assert.NoError(t, err) assert.Equal(t, int64(2), commitsCount) } + +func TestGetLatestCommitTime(t *testing.T) { + bareRepo1 := &mockRepository{path: "repo1_bare"} + lct, err := GetLatestCommitTime(t.Context(), bareRepo1) + assert.NoError(t, err) + // Time is Sun Nov 13 16:40:14 2022 +0100 + // which is the time of commit + // ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master) + assert.EqualValues(t, 1668354014, lct.Unix()) +} diff --git a/modules/gitrepo/diff.go b/modules/gitrepo/diff.go index c98c3ffcfe..ad7f24762f 100644 --- a/modules/gitrepo/diff.go +++ b/modules/gitrepo/diff.go @@ -4,8 +4,10 @@ package gitrepo import ( + "bytes" "context" "fmt" + "io" "regexp" "strconv" @@ -60,3 +62,15 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int, } return numFiles, totalAdditions, totalDeletions, err } + +// GetReverseRawDiff dumps the reverse diff results of repository in given commit ID to io.Writer. +func GetReverseRawDiff(ctx context.Context, repo Repository, commitID string, writer io.Writer) error { + stderr := new(bytes.Buffer) + if err := RunCmd(ctx, repo, gitcmd.NewCommand("show", "--pretty=format:revert %H%n", "-R"). + AddDynamicArguments(commitID). + WithStdout(writer). + WithStderr(stderr)); err != nil { + return fmt.Errorf("GetReverseRawDiff: %w - %s", err, stderr) + } + return nil +} diff --git a/modules/gitrepo/gitrepo.go b/modules/gitrepo/gitrepo.go index 4dd03c18fe..c78d2c767d 100644 --- a/modules/gitrepo/gitrepo.go +++ b/modules/gitrepo/gitrepo.go @@ -98,3 +98,23 @@ func UpdateServerInfo(ctx context.Context, repo Repository) error { func GetRepoFS(repo Repository) fs.FS { return os.DirFS(repoPath(repo)) } + +func IsRepoFileExist(ctx context.Context, repo Repository, relativeFilePath string) (bool, error) { + absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath) + return util.IsExist(absoluteFilePath) +} + +func IsRepoDirExist(ctx context.Context, repo Repository, relativeDirPath string) (bool, error) { + absoluteDirPath := filepath.Join(repoPath(repo), relativeDirPath) + return util.IsDir(absoluteDirPath) +} + +func RemoveRepoFile(ctx context.Context, repo Repository, relativeFilePath string) error { + absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath) + return util.Remove(absoluteFilePath) +} + +func CreateRepoFile(ctx context.Context, repo Repository, relativeFilePath string) (io.WriteCloser, error) { + absoluteFilePath := filepath.Join(repoPath(repo), relativeFilePath) + return os.Create(absoluteFilePath) +} diff --git a/modules/packages/npm/creator.go b/modules/packages/npm/creator.go index 11b5123c27..cc7695726b 100644 --- a/modules/packages/npm/creator.go +++ b/modules/packages/npm/creator.go @@ -62,7 +62,28 @@ type PackageMetadata struct { Author User `json:"author"` ReadmeFilename string `json:"readmeFilename,omitempty"` Users map[string]bool `json:"users,omitempty"` - License string `json:"license,omitempty"` + License License `json:"license,omitempty"` +} + +type License string + +func (l *License) UnmarshalJSON(data []byte) error { + switch data[0] { + case '"': + var value string + if err := json.Unmarshal(data, &value); err != nil { + return err + } + *l = License(value) + case '{': + var values map[string]any + if err := json.Unmarshal(data, &values); err != nil { + return err + } + value, _ := values["type"].(string) + *l = License(value) + } + return nil } // PackageMetadataVersion documentation: https://github.com/npm/registry/blob/master/docs/REGISTRY-API.md#version @@ -74,7 +95,7 @@ type PackageMetadataVersion struct { Description string `json:"description"` Author User `json:"author"` Homepage string `json:"homepage,omitempty"` - License string `json:"license,omitempty"` + License License `json:"license,omitempty"` Repository Repository `json:"repository"` Keywords []string `json:"keywords,omitempty"` Dependencies map[string]string `json:"dependencies,omitempty"` diff --git a/modules/packages/npm/creator_test.go b/modules/packages/npm/creator_test.go index 806377a52b..40c50de91f 100644 --- a/modules/packages/npm/creator_test.go +++ b/modules/packages/npm/creator_test.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/json" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestParsePackage(t *testing.T) { @@ -291,11 +292,36 @@ func TestParsePackage(t *testing.T) { assert.Equal(t, packageDescription, p.Metadata.Readme) assert.Equal(t, packageAuthor, p.Metadata.Author) assert.Equal(t, packageBin, p.Metadata.Bin["bin"]) - assert.Equal(t, "MIT", p.Metadata.License) + assert.Equal(t, "MIT", string(p.Metadata.License)) assert.Equal(t, "https://gitea.io/", p.Metadata.ProjectURL) assert.Contains(t, p.Metadata.Dependencies, "package") assert.Equal(t, "1.2.0", p.Metadata.Dependencies["package"]) assert.Equal(t, repository.Type, p.Metadata.Repository.Type) assert.Equal(t, repository.URL, p.Metadata.Repository.URL) }) + + t.Run("ValidLicenseMap", func(t *testing.T) { + packageJSON := `{ + "versions": { + "0.1.1": { + "name": "dev-null", + "version": "0.1.1", + "license": { + "type": "MIT" + }, + "dist": { + "integrity": "sha256-" + } + } + }, + "_attachments": { + "foo": { + "data": "AAAA" + } + } +}` + p, err := ParsePackage(strings.NewReader(packageJSON)) + require.NoError(t, err) + require.Equal(t, "MIT", string(p.Metadata.License)) + }) } diff --git a/modules/packages/npm/metadata.go b/modules/packages/npm/metadata.go index 362d0470d5..e6bbcb1177 100644 --- a/modules/packages/npm/metadata.go +++ b/modules/packages/npm/metadata.go @@ -12,7 +12,7 @@ type Metadata struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` Author string `json:"author,omitempty"` - License string `json:"license,omitempty"` + License License `json:"license,omitempty"` ProjectURL string `json:"project_url,omitempty"` Keywords []string `json:"keywords,omitempty"` Dependencies map[string]string `json:"dependencies,omitempty"` diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 6f1e2eb120..6bed410642 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -414,22 +414,116 @@ func SearchUsers(ctx *context.APIContext) { // in: query // description: page size of results // type: integer + // - name: sort + // in: query + // description: sort users by attribute. Supported values are + // "name", "created", "updated" and "id". + // Default is "name" + // type: string + // - name: order + // in: query + // description: sort order, either "asc" (ascending) or "desc" (descending). + // Default is "asc", ignored if "sort" is not specified. + // type: string + // - name: q + // in: query + // description: search term (username, full name, email) + // type: string + // - name: visibility + // in: query + // description: visibility filter. Supported values are + // "public", "limited" and "private". + // type: string + // - name: is_active + // in: query + // description: filter active users + // type: boolean + // - name: is_admin + // in: query + // description: filter admin users + // type: boolean + // - name: is_restricted + // in: query + // description: filter restricted users + // type: boolean + // - name: is_2fa_enabled + // in: query + // description: filter 2FA enabled users + // type: boolean + // - name: is_prohibit_login + // in: query + // description: filter login prohibited users + // type: boolean // responses: // "200": // "$ref": "#/responses/UserList" // "403": // "$ref": "#/responses/forbidden" + // "422": + // "$ref": "#/responses/validationError" listOptions := utils.GetListOptions(ctx) - users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{ - Actor: ctx.Doer, - Types: []user_model.UserType{user_model.UserTypeIndividual}, - LoginName: ctx.FormTrim("login_name"), - SourceID: ctx.FormInt64("source_id"), - OrderBy: db.SearchOrderByAlphabetically, - ListOptions: listOptions, - }) + orderBy := db.SearchOrderByAlphabetically + sortMode := ctx.FormString("sort") + if len(sortMode) > 0 { + sortOrder := ctx.FormString("order") + if len(sortOrder) == 0 { + sortOrder = "asc" + } + if searchModeMap, ok := user_model.AdminUserOrderByMap[sortOrder]; ok { + if order, ok := searchModeMap[sortMode]; ok { + orderBy = order + } else { + ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode)) + return + } + } else { + ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder)) + return + } + } + + var visible []api.VisibleType + visibilityParam := ctx.FormString("visibility") + if len(visibilityParam) > 0 { + if visibility, ok := api.VisibilityModes[visibilityParam]; ok { + visible = []api.VisibleType{visibility} + } else { + ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid visibility: \"%s\"", visibilityParam)) + return + } + } + + searchOpts := user_model.SearchUserOptions{ + Actor: ctx.Doer, + Types: []user_model.UserType{user_model.UserTypeIndividual}, + LoginName: ctx.FormTrim("login_name"), + SourceID: ctx.FormInt64("source_id"), + Keyword: ctx.FormTrim("q"), + Visible: visible, + OrderBy: orderBy, + ListOptions: listOptions, + SearchByEmail: true, + } + + if ctx.FormString("is_active") != "" { + searchOpts.IsActive = optional.Some(ctx.FormBool("is_active")) + } + if ctx.FormString("is_admin") != "" { + searchOpts.IsAdmin = optional.Some(ctx.FormBool("is_admin")) + } + if ctx.FormString("is_restricted") != "" { + searchOpts.IsRestricted = optional.Some(ctx.FormBool("is_restricted")) + } + if ctx.FormString("is_2fa_enabled") != "" { + searchOpts.IsTwoFactorEnabled = optional.Some(ctx.FormBool("is_2fa_enabled")) + } + if ctx.FormString("is_prohibit_login") != "" { + searchOpts.IsProhibitLogin = optional.Some(ctx.FormBool("is_prohibit_login")) + } + + users, maxResults, err := user_model.SearchUsers(ctx, searchOpts) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/web/repo/editor_cherry_pick.go b/routers/web/repo/editor_cherry_pick.go index 32e3c58e87..ca0e19517a 100644 --- a/routers/web/repo/editor_cherry_pick.go +++ b/routers/web/repo/editor_cherry_pick.go @@ -9,6 +9,7 @@ import ( "strings" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -35,9 +36,7 @@ func CherryPick(ctx *context.Context) { ctx.Data["commit_message"] = "revert " + cherryPickCommit.Message() } else { ctx.Data["CherryPickType"] = "cherry-pick" - splits := strings.SplitN(cherryPickCommit.Message(), "\n", 2) - ctx.Data["commit_summary"] = splits[0] - ctx.Data["commit_message"] = splits[1] + ctx.Data["commit_summary"], ctx.Data["commit_message"], _ = strings.Cut(cherryPickCommit.Message(), "\n") } ctx.HTML(http.StatusOK, tplCherryPick) @@ -66,7 +65,7 @@ func CherryPickPost(ctx *context.Context) { // Drop through to the "apply" method buf := &bytes.Buffer{} if parsed.form.Revert { - err = git.GetReverseRawDiff(ctx, ctx.Repo.Repository.RepoPath(), fromCommitID, buf) + err = gitrepo.GetReverseRawDiff(ctx, ctx.Repo.Repository, fromCommitID, buf) } else { err = git.GetRawDiff(ctx.Repo.GitRepo, fromCommitID, "patch", buf) } diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index 35124c5c3e..a3cb88e76a 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -111,7 +111,7 @@ func NewComment(ctx *context.Context) { ctx.ServerError("Unable to load base repo", err) return } - prHeadCommitID, err := git.GetFullCommitID(ctx, pull.BaseRepo.RepoPath(), prHeadRef) + prHeadCommitID, err := gitrepo.GetFullCommitID(ctx, pull.BaseRepo, prHeadRef) if err != nil { ctx.ServerError("Get head commit Id of pr fail", err) return @@ -128,7 +128,7 @@ func NewComment(ctx *context.Context) { return } headBranchRef := git.RefNameFromBranch(pull.HeadBranch) - headBranchCommitID, err := git.GetFullCommitID(ctx, pull.HeadRepo.RepoPath(), headBranchRef.String()) + headBranchCommitID, err := gitrepo.GetFullCommitID(ctx, pull.HeadRepo, headBranchRef.String()) if err != nil { ctx.ServerError("Get head commit Id of head branch fail", err) return diff --git a/services/doctor/misc.go b/services/doctor/misc.go index 445ff61ffb..89f3a63df2 100644 --- a/services/doctor/misc.go +++ b/services/doctor/misc.go @@ -6,9 +6,7 @@ package doctor import ( "context" "fmt" - "os" "os/exec" - "path/filepath" "strings" "code.gitea.io/gitea/models" @@ -20,7 +18,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" lru "github.com/hashicorp/golang-lru/v2" "xorm.io/builder" @@ -142,10 +139,10 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err } // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`) - isExist, err := util.IsExist(daemonExportFile) + daemonExportFile := `git-daemon-export-ok` + isExist, err := gitrepo.IsRepoFileExist(ctx, repo, daemonExportFile) if err != nil { - log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) + log.Error("Unable to check if %s:%s exists. Error: %v", repo.FullName(), daemonExportFile, err) return err } isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic @@ -154,12 +151,12 @@ func checkDaemonExport(ctx context.Context, logger log.Logger, autofix bool) err numNeedUpdate++ if autofix { if !isPublic && isExist { - if err = util.Remove(daemonExportFile); err != nil { - log.Error("Failed to remove %s: %v", daemonExportFile, err) + if err = gitrepo.RemoveRepoFile(ctx, repo, daemonExportFile); err != nil { + log.Error("Failed to remove %s:%s: %v", repo.FullName(), daemonExportFile, err) } } else if isPublic && !isExist { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error("Failed to create %s: %v", daemonExportFile, err) + if f, err := gitrepo.CreateRepoFile(ctx, repo, daemonExportFile); err != nil { + log.Error("Failed to create %s:%s: %v", repo.FullName(), daemonExportFile, err) } else { f.Close() } @@ -190,16 +187,16 @@ func checkCommitGraph(ctx context.Context, logger log.Logger, autofix bool) erro commitGraphExists := func() (bool, error) { // Check commit-graph exists - commitGraphFile := filepath.Join(repo.RepoPath(), `objects/info/commit-graph`) - isExist, err := util.IsExist(commitGraphFile) + commitGraphFile := `objects/info/commit-graph` + isExist, err := gitrepo.IsRepoFileExist(ctx, repo, commitGraphFile) if err != nil { logger.Error("Unable to check if %s exists. Error: %v", commitGraphFile, err) return false, err } if !isExist { - commitGraphsDir := filepath.Join(repo.RepoPath(), `objects/info/commit-graphs`) - isExist, err = util.IsExist(commitGraphsDir) + commitGraphsDir := `objects/info/commit-graphs` + isExist, err = gitrepo.IsRepoDirExist(ctx, repo, commitGraphsDir) if err != nil { logger.Error("Unable to check if %s exists. Error: %v", commitGraphsDir, err) return false, err diff --git a/services/mailer/sender/sender.go b/services/mailer/sender/sender.go index e470c2f2b3..30c6feaf7a 100644 --- a/services/mailer/sender/sender.go +++ b/services/mailer/sender/sender.go @@ -4,10 +4,8 @@ package sender import ( + "errors" "io" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" ) type Sender interface { @@ -16,23 +14,18 @@ type Sender interface { var Send = send -func send(sender Sender, msgs ...*Message) error { - if setting.MailService == nil { - log.Error("Mailer: Send is being invoked but mail service hasn't been initialized") - return nil +func send(sender Sender, msg *Message) error { + m := msg.ToMessage() + froms := m.GetFrom() + to, err := m.GetRecipients() + if err != nil { + return err } - for _, msg := range msgs { - m := msg.ToMessage() - froms := m.GetFrom() - to, err := m.GetRecipients() - if err != nil { - return err - } - // TODO: implement sending from multiple addresses - if err := sender.Send(froms[0].Address, to, m); err != nil { - return err - } + // TODO: implement sending from multiple addresses + if len(froms) == 0 { + // FIXME: no idea why sometimes the "froms" can be empty, need to figure out the root problem + return errors.New("no FROM specified") } - return nil + return sender.Send(froms[0].Address, to, m) } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index da58bbd1b6..f9c40049db 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -449,7 +449,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo_id: %v]: unable to GetMirrorByRepoID: %v", repoID, err) return false } - _ = m.GetRepository(ctx) // force load repository of mirror + repo := m.GetRepository(ctx) // force load repository of mirror ctx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Syncing Mirror %s/%s", m.Repo.OwnerName, m.Repo.Name)) defer finished() @@ -515,12 +515,12 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { } // Push commits - oldCommitID, err := git.GetFullCommitID(gitRepo.Ctx, gitRepo.Path, result.oldCommitID) + oldCommitID, err := gitrepo.GetFullCommitID(ctx, repo, result.oldCommitID) if err != nil { log.Error("SyncMirrors [repo: %-v]: unable to get GetFullCommitID[%s]: %v", m.Repo, result.oldCommitID, err) continue } - newCommitID, err := git.GetFullCommitID(gitRepo.Ctx, gitRepo.Path, result.newCommitID) + newCommitID, err := gitrepo.GetFullCommitID(ctx, repo, result.newCommitID) if err != nil { log.Error("SyncMirrors [repo: %-v]: unable to get GetFullCommitID [%s]: %v", m.Repo, result.newCommitID, err) continue @@ -560,7 +560,7 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { } if !isEmpty { // Get latest commit date and update to current repository updated time - commitDate, err := git.GetLatestCommitTime(ctx, m.Repo.RepoPath()) + commitDate, err := gitrepo.GetLatestCommitTime(ctx, m.Repo) if err != nil { log.Error("SyncMirrors [repo: %-v]: unable to GetLatestCommitDate: %v", m.Repo, err) return false diff --git a/services/pull/check.go b/services/pull/check.go index 5b28ec9658..5978a57aec 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -295,7 +295,7 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com // If merge-base successfully exits then prHeadRef is an ancestor of pr.BaseBranch // Find the head commit id - prHeadCommitID, err := git.GetFullCommitID(ctx, pr.BaseRepo.RepoPath(), prHeadRef) + prHeadCommitID, err := gitrepo.GetFullCommitID(ctx, pr.BaseRepo, prHeadRef) if err != nil { return nil, fmt.Errorf("GetFullCommitID(%s) in %s: %w", prHeadRef, pr.BaseRepo.FullName(), err) } diff --git a/services/pull/compare.go b/services/pull/compare.go index 2c4b77a772..c2d39752e8 100644 --- a/services/pull/compare.go +++ b/services/pull/compare.go @@ -48,14 +48,14 @@ func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Reposito compareInfo := new(CompareInfo) - compareInfo.HeadCommitID, err = git.GetFullCommitID(ctx, headGitRepo.Path, headBranch) + compareInfo.HeadCommitID, err = gitrepo.GetFullCommitID(ctx, headRepo, headBranch) if err != nil { compareInfo.HeadCommitID = headBranch } compareInfo.MergeBase, remoteBranch, err = headGitRepo.GetMergeBase(tmpRemote, baseBranch, headBranch) if err == nil { - compareInfo.BaseCommitID, err = git.GetFullCommitID(ctx, headGitRepo.Path, remoteBranch) + compareInfo.BaseCommitID, err = gitrepo.GetFullCommitID(ctx, headRepo, remoteBranch) if err != nil { compareInfo.BaseCommitID = remoteBranch } @@ -77,7 +77,7 @@ func GetCompareInfo(ctx context.Context, baseRepo, headRepo *repo_model.Reposito } } else { compareInfo.Commits = []*git.Commit{} - compareInfo.MergeBase, err = git.GetFullCommitID(ctx, headGitRepo.Path, remoteBranch) + compareInfo.MergeBase, err = gitrepo.GetFullCommitID(ctx, headRepo, remoteBranch) if err != nil { compareInfo.MergeBase = remoteBranch } diff --git a/services/repository/create.go b/services/repository/create.go index 0b57db988b..7439fc8f08 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -69,9 +69,10 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir ) // Clone to temporary path and do the init commit. - if stdout, _, err := gitcmd.NewCommand("clone").AddDynamicArguments(repo.RepoPath(), tmpDir). - WithEnv(env).RunStdString(ctx); err != nil { - log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err) + if err := gitrepo.CloneRepoToLocal(ctx, repo, tmpDir, git.CloneRepoOptions{ + Env: env, + }); err != nil { + log.Error("Failed to clone from %v into %s\nError: %v", repo, tmpDir, err) return fmt.Errorf("git clone: %w", err) } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index b7f4afdebc..aaf9566aec 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -55,12 +55,11 @@ func (t *TemporaryUploadRepository) Close() { // Clone the base repository to our path and set branch as the HEAD func (t *TemporaryUploadRepository) Clone(ctx context.Context, branch string, bare bool) error { - cmd := gitcmd.NewCommand("clone", "-s", "-b").AddDynamicArguments(branch, t.repo.RepoPath(), t.basePath) - if bare { - cmd.AddArguments("--bare") - } - - if _, _, err := cmd.RunStdString(ctx); err != nil { + if err := gitrepo.CloneRepoToLocal(ctx, t.repo, t.basePath, git.CloneRepoOptions{ + Bare: bare, + Branch: branch, + Shared: true, + }); err != nil { stderr := err.Error() if matched, _ := regexp.MatchString(".*Remote branch .* not found in upstream origin.*", stderr); matched { return git.ErrBranchNotExist{ diff --git a/services/repository/fork.go b/services/repository/fork.go index 2380666afb..f92af65605 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -15,7 +15,6 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -147,15 +146,16 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // 3 - Clone the repository - cloneCmd := gitcmd.NewCommand("clone", "--bare") - if opts.SingleBranch != "" { - cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) + cloneOpts := git.CloneRepoOptions{ + Bare: true, + Timeout: 10 * time.Minute, } - var stdout []byte - if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()). - WithTimeout(10 * time.Minute). - RunStdBytes(ctx); err != nil { - log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) + if opts.SingleBranch != "" { + cloneOpts.SingleBranch = true + cloneOpts.Branch = opts.SingleBranch + } + if err = gitrepo.Clone(ctx, opts.BaseRepo, repo, cloneOpts); err != nil { + log.Error("Fork Repository (git clone) Failed for %v (from %v):\nError: %v", repo, opts.BaseRepo, err) return nil, fmt.Errorf("git clone: %w", err) } diff --git a/services/repository/generate.go b/services/repository/generate.go index 3ec31dac22..b2913cd110 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -177,7 +177,7 @@ func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, t } generatedContent := generateExpansion(ctx, string(content), templateRepo, generateRepo) - substSubPath := filepath.Clean(filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))) + substSubPath := filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo)) newLocalPath := filepath.Join(tmpDir, substSubPath) regular, err := util.IsRegularFile(newLocalPath) if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite { @@ -358,5 +358,5 @@ func filePathSanitize(s string) string { } fields[i] = field } - return filepath.FromSlash(strings.Join(fields, "/")) + return filepath.Clean(filepath.FromSlash(strings.Trim(strings.Join(fields, "/"), "/"))) } diff --git a/services/repository/generate_test.go b/services/repository/generate_test.go index 9c01911ded..432de4dc59 100644 --- a/services/repository/generate_test.go +++ b/services/repository/generate_test.go @@ -54,19 +54,24 @@ text/*.txt } func TestFilePathSanitize(t *testing.T) { - assert.Equal(t, "test_CON", filePathSanitize("test_CON")) - assert.Equal(t, "test CON", filePathSanitize("test CON ")) - assert.Equal(t, "__/traverse/__", filePathSanitize(".. /traverse/ ..")) - assert.Equal(t, "./__/a/_git/b_", filePathSanitize("./../a/.git/ b: ")) + // path clean + assert.Equal(t, "a", filePathSanitize("//a/")) + assert.Equal(t, "_a", filePathSanitize(`\a`)) + assert.Equal(t, "__/a/__", filePathSanitize(".. /a/ ..")) + assert.Equal(t, "__/a/_git/b_", filePathSanitize("./../a/.git/ b: ")) + + // Windows reserved names assert.Equal(t, "_", filePathSanitize("CoN")) assert.Equal(t, "_", filePathSanitize("LpT1")) assert.Equal(t, "_", filePathSanitize("CoM1")) + assert.Equal(t, "test_CON", filePathSanitize("test_CON")) + assert.Equal(t, "test CON", filePathSanitize("test CON ")) + + // special chars assert.Equal(t, "_", filePathSanitize("\u0000")) - assert.Equal(t, "目标", filePathSanitize("目标")) - // unlike filepath.Clean, it only sanitizes, doesn't change the separator layout - assert.Equal(t, "", filePathSanitize("")) //nolint:testifylint // for easy reading + assert.Equal(t, ".", filePathSanitize("")) assert.Equal(t, ".", filePathSanitize(".")) - assert.Equal(t, "/", filePathSanitize("/")) + assert.Equal(t, ".", filePathSanitize("/")) } func TestProcessGiteaTemplateFile(t *testing.T) { diff --git a/services/repository/repository.go b/services/repository/repository.go index 93fbcb51f7..a4d82140c6 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -7,8 +7,6 @@ import ( "context" "errors" "fmt" - "os" - "path/filepath" "strings" activities_model "code.gitea.io/gitea/models/activities" @@ -28,7 +26,6 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" ) @@ -251,9 +248,8 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error } // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`) - - isExist, err := util.IsExist(daemonExportFile) + daemonExportFile := `git-daemon-export-ok` + isExist, err := gitrepo.IsRepoFileExist(ctx, repo, daemonExportFile) if err != nil { log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) return err @@ -261,11 +257,11 @@ func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic if !isPublic && isExist { - if err = util.Remove(daemonExportFile); err != nil { + if err = gitrepo.RemoveRepoFile(ctx, repo, daemonExportFile); err != nil { log.Error("Failed to remove %s: %v", daemonExportFile, err) } } else if isPublic && !isExist { - if f, err := os.Create(daemonExportFile); err != nil { + if f, err := gitrepo.CreateRepoFile(ctx, repo, daemonExportFile); err != nil { log.Error("Failed to create %s: %v", daemonExportFile, err) } else { f.Close() diff --git a/templates/package/content/pypi.tmpl b/templates/package/content/pypi.tmpl index 2625c160fe..15d8971eaa 100644 --- a/templates/package/content/pypi.tmpl +++ b/templates/package/content/pypi.tmpl @@ -4,7 +4,7 @@
pip install --index-url --extra-index-url https://pypi.org/ {{.PackageDescriptor.Package.Name}}pip install --index-url --extra-index-url https://pypi.org/simple {{.PackageDescriptor.Package.Name}}