diff --git a/cmd/admin.go b/cmd/admin.go index a01274b90e932..a280680409404 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -11,7 +11,6 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" @@ -122,11 +121,6 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { log.Trace("Processing next %d repos of %d", len(repos), count) for _, repo := range repos { log.Trace("Synchronizing repo %s with path %s", repo.FullName(), repo.RelativePath()) - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - log.Warn("OpenRepository: %v", err) - continue - } oldnum, err := getReleaseCount(ctx, repo.ID) if err != nil { @@ -134,22 +128,19 @@ func runRepoSyncReleases(ctx context.Context, _ *cli.Command) error { } log.Trace(" currentNumReleases is %d, running SyncReleasesWithTags", oldnum) - if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo); err != nil { log.Warn(" SyncReleasesWithTags: %v", err) - gitRepo.Close() continue } count, err = getReleaseCount(ctx, repo.ID) if err != nil { log.Warn(" GetReleaseCountByRepoID: %v", err) - gitRepo.Close() continue } log.Trace("repo %s releases synchronized to tags: from %d to %d", repo.FullName(), oldnum, count) - gitRepo.Close() } } diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go index 77aecb21ebd27..db1c9bfbd4c87 100644 --- a/modules/git/repo_branch_gogit.go +++ b/modules/git/repo_branch_gogit.go @@ -7,11 +7,7 @@ package git import ( - "sort" - "strings" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/storer" ) // IsObjectExist returns true if the given object exists in the repository. @@ -54,99 +50,3 @@ func (repo *Repository) IsBranchExist(name string) bool { } return reference.Type() != plumbing.InvalidReference } - -// GetBranches returns branches from the repository, skipping "skip" initial branches and -// returning at most "limit" branches, or all branches if "limit" is 0. -// Branches are returned with sort of `-committerdate` as the nogogit -// implementation. This requires full fetch, sort and then the -// skip/limit applies later as gogit returns in undefined order. -func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { - type BranchData struct { - name string - committerDate int64 - } - var branchData []BranchData - - branchIter, err := repo.gogitRepo.Branches() - if err != nil { - return nil, 0, err - } - - _ = branchIter.ForEach(func(branch *plumbing.Reference) error { - obj, err := repo.gogitRepo.CommitObject(branch.Hash()) - if err != nil { - // skip branch if can't find commit - return nil - } - - branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), BranchPrefix), obj.Committer.When.Unix()}) - return nil - }) - - sort.Slice(branchData, func(i, j int) bool { - return !(branchData[i].committerDate < branchData[j].committerDate) - }) - - var branchNames []string - maxPos := len(branchData) - if limit > 0 { - maxPos = min(skip+limit, maxPos) - } - for i := skip; i < maxPos; i++ { - branchNames = append(branchNames, branchData[i].name) - } - - return branchNames, len(branchData), nil -} - -// WalkReferences walks all the references from the repository -func (repo *Repository) WalkReferences(arg ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { - i := 0 - var iter storer.ReferenceIter - var err error - switch arg { - case ObjectTag: - iter, err = repo.gogitRepo.Tags() - case ObjectBranch: - iter, err = repo.gogitRepo.Branches() - default: - iter, err = repo.gogitRepo.References() - } - if err != nil { - return i, err - } - defer iter.Close() - - err = iter.ForEach(func(ref *plumbing.Reference) error { - if i < skip { - i++ - return nil - } - err := walkfn(ref.Hash().String(), string(ref.Name())) - i++ - if err != nil { - return err - } - if limit != 0 && i >= skip+limit { - return storer.ErrStop - } - return nil - }) - return i, err -} - -// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash -func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { - var revList []string - iter, err := repo.gogitRepo.References() - if err != nil { - return nil, err - } - err = iter.ForEach(func(ref *plumbing.Reference) error { - if ref.Hash().String() == sha && strings.HasPrefix(string(ref.Name()), prefix) { - revList = append(revList, string(ref.Name())) - } - return nil - }) - return revList, err -} diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go index 7eb8c48466188..20ad24e55ca2b 100644 --- a/modules/git/repo_branch_nogogit.go +++ b/modules/git/repo_branch_nogogit.go @@ -7,12 +7,8 @@ package git import ( - "bufio" - "context" - "io" "strings" - "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/log" ) @@ -61,137 +57,3 @@ func (repo *Repository) IsBranchExist(name string) bool { return repo.IsReferenceExist(BranchPrefix + name) } - -// GetBranchNames returns branches from the repository, skipping "skip" initial branches and -// returning at most "limit" branches, or all branches if "limit" is 0. -func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) { - return callShowRef(repo.Ctx, repo.Path, BranchPrefix, gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"}, skip, limit) -} - -// WalkReferences walks all the references from the repository -// refType should be empty, ObjectTag or ObjectBranch. All other values are equivalent to empty. -func (repo *Repository) WalkReferences(refType ObjectType, skip, limit int, walkfn func(sha1, refname string) error) (int, error) { - var args gitcmd.TrustedCmdArgs - switch refType { - case ObjectTag: - args = gitcmd.TrustedCmdArgs{TagPrefix, "--sort=-taggerdate"} - case ObjectBranch: - args = gitcmd.TrustedCmdArgs{BranchPrefix, "--sort=-committerdate"} - } - - return WalkShowRef(repo.Ctx, repo.Path, args, skip, limit, walkfn) -} - -// callShowRef return refs, if limit = 0 it will not limit -func callShowRef(ctx context.Context, repoPath, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { - countAll, err = WalkShowRef(ctx, repoPath, extraArgs, skip, limit, func(_, branchName string) error { - branchName = strings.TrimPrefix(branchName, trimPrefix) - branchNames = append(branchNames, branchName) - - return nil - }) - return branchNames, countAll, err -} - -func WalkShowRef(ctx context.Context, repoPath string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { - stdoutReader, stdoutWriter := io.Pipe() - defer func() { - _ = stdoutReader.Close() - _ = stdoutWriter.Close() - }() - - go func() { - stderrBuilder := &strings.Builder{} - args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} - args = append(args, extraArgs...) - err := gitcmd.NewCommand(args...). - WithDir(repoPath). - WithStdout(stdoutWriter). - WithStderr(stderrBuilder). - Run(ctx) - if err != nil { - if stderrBuilder.Len() == 0 { - _ = stdoutWriter.Close() - return - } - _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String())) - } else { - _ = stdoutWriter.Close() - } - }() - - i := 0 - bufReader := bufio.NewReader(stdoutReader) - for i < skip { - _, isPrefix, err := bufReader.ReadLine() - if err == io.EOF { - return i, nil - } - if err != nil { - return 0, err - } - if !isPrefix { - i++ - } - } - for limit == 0 || i < skip+limit { - // The output of show-ref is simply a list: - // SP LF - sha, err := bufReader.ReadString(' ') - if err == io.EOF { - return i, nil - } - if err != nil { - return 0, err - } - - branchName, err := bufReader.ReadString('\n') - if err == io.EOF { - // This shouldn't happen... but we'll tolerate it for the sake of peace - return i, nil - } - if err != nil { - return i, err - } - - if len(branchName) > 0 { - branchName = branchName[:len(branchName)-1] - } - - if len(sha) > 0 { - sha = sha[:len(sha)-1] - } - - err = walkfn(sha, branchName) - if err != nil { - return i, err - } - i++ - } - // count all refs - for limit != 0 { - _, isPrefix, err := bufReader.ReadLine() - if err == io.EOF { - return i, nil - } - if err != nil { - return 0, err - } - if !isPrefix { - i++ - } - } - return i, nil -} - -// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash -func (repo *Repository) GetRefsBySha(sha, prefix string) ([]string, error) { - var revList []string - _, err := WalkShowRef(repo.Ctx, repo.Path, nil, 0, 0, func(walkSha, refname string) error { - if walkSha == sha && strings.HasPrefix(refname, prefix) { - revList = append(revList, refname) - } - return nil - }) - return revList, err -} diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index 5d586954db76e..5e3c58e5f6944 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -11,91 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestRepository_GetBranches(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path) - assert.NoError(t, err) - defer bareRepo1.Close() - - branches, countAll, err := bareRepo1.GetBranchNames(0, 2) - - assert.NoError(t, err) - assert.Len(t, branches, 2) - assert.Equal(t, 3, countAll) - assert.ElementsMatch(t, []string{"master", "branch2"}, branches) - - branches, countAll, err = bareRepo1.GetBranchNames(0, 0) - - assert.NoError(t, err) - assert.Len(t, branches, 3) - assert.Equal(t, 3, countAll) - assert.ElementsMatch(t, []string{"master", "branch2", "branch1"}, branches) - - branches, countAll, err = bareRepo1.GetBranchNames(5, 1) - - assert.NoError(t, err) - assert.Empty(t, branches) - assert.Equal(t, 3, countAll) - assert.ElementsMatch(t, []string{}, branches) -} - -func BenchmarkRepository_GetBranches(b *testing.B) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepository(b.Context(), bareRepo1Path) - if err != nil { - b.Fatal(err) - } - defer bareRepo1.Close() - - for b.Loop() { - _, _, err := bareRepo1.GetBranchNames(0, 0) - if err != nil { - b.Fatal(err) - } - } -} - -func TestGetRefsBySha(t *testing.T) { - bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls") - bareRepo5, err := OpenRepository(t.Context(), bareRepo5Path) - if err != nil { - t.Fatal(err) - } - defer bareRepo5.Close() - - // do not exist - branches, err := bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") - assert.NoError(t, err) - assert.Empty(t, branches) - - // refs/pull/1/head - branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix) - assert.NoError(t, err) - assert.Equal(t, []string{"refs/pull/1/head"}, branches) - - branches, err = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", BranchPrefix) - assert.NoError(t, err) - assert.Equal(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches) - - branches, err = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", BranchPrefix) - assert.NoError(t, err) - assert.Equal(t, []string{"refs/heads/test-patch-1"}, branches) -} - -func BenchmarkGetRefsBySha(b *testing.B) { - bareRepo5Path := filepath.Join(testReposDir, "repo5_pulls") - bareRepo5, err := OpenRepository(b.Context(), bareRepo5Path) - if err != nil { - b.Fatal(err) - } - defer bareRepo5.Close() - - _, _ = bareRepo5.GetRefsBySha("8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") - _, _ = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", "") - _, _ = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", "") - _, _ = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", "") -} - func TestRepository_IsObjectExist(t *testing.T) { repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "repo1_bare")) require.NoError(t, err) diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index 2ddb52750209e..d907bd416c038 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -8,32 +8,11 @@ package git import ( "errors" "io" - "strings" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/log" ) -// ResolveReference resolves a name to a reference -func (repo *Repository) ResolveReference(name string) (string, error) { - stdout, _, err := gitcmd.NewCommand("show-ref", "--hash"). - AddDynamicArguments(name). - WithDir(repo.Path). - RunStdString(repo.Ctx) - if err != nil { - if strings.Contains(err.Error(), "not a valid ref") { - return "", ErrNotExist{name, ""} - } - return "", err - } - stdout = strings.TrimSpace(stdout) - if stdout == "" { - return "", ErrNotExist{name, ""} - } - - return stdout, nil -} - // GetRefCommitID returns the last commit ID string of given reference (branch or tag). func (repo *Repository) GetRefCommitID(name string) (string, error) { batch, cancel, err := repo.CatFileBatch(repo.Ctx) diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 8859a93a578d9..761adba71bbf0 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -4,11 +4,7 @@ package git import ( - "context" "strings" - - "code.gitea.io/gitea/modules/git/gitcmd" - "code.gitea.io/gitea/modules/util" ) // GetRefs returns all references of the repository. @@ -16,55 +12,6 @@ func (repo *Repository) GetRefs() ([]*Reference, error) { return repo.GetRefsFiltered("") } -// ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC -// refType should only be a literal "branch" or "tag" and nothing else -func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { - cmd := gitcmd.NewCommand() - switch refType { - case "branch": - cmd.AddArguments("branch") - case "tag": - cmd.AddArguments("tag") - default: - return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) - } - stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains"). - AddDynamicArguments(commitSHA).WithDir(repo.Path).RunStdString(ctx) - if err != nil { - return nil, err - } - - refs := strings.Split(strings.TrimSpace(stdout), "\n") - if refType == "branch" { - return parseBranches(refs), nil - } - return parseTags(refs), nil -} - -func parseBranches(refs []string) []string { - results := make([]string, 0, len(refs)) - for _, ref := range refs { - if strings.HasPrefix(ref, "* ") { // current branch (main branch) - results = append(results, ref[len("* "):]) - } else if strings.HasPrefix(ref, " ") { // all other branches - results = append(results, ref[len(" "):]) - } else if ref != "" { - results = append(results, ref) - } - } - return results -} - -func parseTags(refs []string) []string { - results := make([]string, 0, len(refs)) - for _, ref := range refs { - if ref != "" { - results = append(results, ref) - } - } - return results -} - // UnstableGuessRefByShortName does the best guess to see whether a "short name" provided by user is a branch, tag or commit. // It could guess wrongly if the input is already ambiguous. For example: // * "refs/heads/the-name" vs "refs/heads/refs/heads/the-name" diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 4ad0c6e5abc9b..d3a9cf23c8bc6 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -6,12 +6,9 @@ package git import ( "fmt" - "io" "strings" - "code.gitea.io/gitea/modules/git/foreachref" "code.gitea.io/gitea/modules/git/gitcmd" - "code.gitea.io/gitea/modules/util" ) // TagPrefix tags prefix path on the repository @@ -109,101 +106,6 @@ func (repo *Repository) GetTagWithID(idStr, name string) (*Tag, error) { return tag, nil } -// GetTagInfos returns all tag infos of the repository. -func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { - // Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode. - // https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname - forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature") - - stdoutReader, stdoutWriter := io.Pipe() - defer stdoutReader.Close() - defer stdoutWriter.Close() - stderr := strings.Builder{} - - go func() { - err := gitcmd.NewCommand("for-each-ref"). - AddOptionFormat("--format=%s", forEachRefFmt.Flag()). - AddArguments("--sort", "-*creatordate", "refs/tags"). - WithDir(repo.Path). - WithStdout(stdoutWriter). - WithStderr(&stderr). - Run(repo.Ctx) - if err != nil { - _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderr.String())) - } else { - _ = stdoutWriter.Close() - } - }() - - var tags []*Tag - parser := forEachRefFmt.Parser(stdoutReader) - for { - ref := parser.Next() - if ref == nil { - break - } - - tag, err := parseTagRef(ref) - if err != nil { - return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err) - } - tags = append(tags, tag) - } - if err := parser.Err(); err != nil { - return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err) - } - - sortTagsByTime(tags) - tagsTotal := len(tags) - if page != 0 { - tags = util.PaginateSlice(tags, page, pageSize).([]*Tag) - } - - return tags, tagsTotal, nil -} - -// parseTagRef parses a tag from a 'git for-each-ref'-produced reference. -func parseTagRef(ref map[string]string) (tag *Tag, err error) { - tag = &Tag{ - Type: ref["objecttype"], - Name: ref["refname:lstrip=2"], - } - - tag.ID, err = NewIDFromString(ref["objectname"]) - if err != nil { - return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err) - } - - if tag.Type == "commit" { - // lightweight tag - tag.Object = tag.ID - } else { - // annotated tag - tag.Object, err = NewIDFromString(ref["object"]) - if err != nil { - return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err) - } - } - - tag.Tagger = parseSignatureFromCommitLine(ref["creator"]) - tag.Message = ref["contents"] - - // strip any signature if present in contents field - _, tag.Message, _ = parsePayloadSignature(util.UnsafeStringToBytes(tag.Message), 0) - - // annotated tag with GPG signature - if tag.Type == "tag" && ref["contents:signature"] != "" { - payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n", - tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message)) - tag.Signature = &CommitSignature{ - Signature: ref["contents:signature"], - Payload: payload, - } - } - - return tag, nil -} - // GetAnnotatedTag returns a Git tag by its SHA, must be an annotated tag func (repo *Repository) GetAnnotatedTag(sha string) (*Tag, error) { id, err := NewIDFromString(sha) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index e6f8e75a0ebfd..5b7af63158eb1 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -11,30 +11,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestRepository_GetTagInfos(t *testing.T) { - bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") - bareRepo1, err := OpenRepository(t.Context(), bareRepo1Path) - if err != nil { - assert.NoError(t, err) - return - } - defer bareRepo1.Close() - - tags, total, err := bareRepo1.GetTagInfos(0, 0) - if err != nil { - assert.NoError(t, err) - return - } - assert.Len(t, tags, 2) - assert.Len(t, tags, total) - assert.Equal(t, "signed-tag", tags[0].Name) - assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) - assert.Equal(t, "tag", tags[0].Type) - assert.Equal(t, "test", tags[1].Name) - assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) - assert.Equal(t, "tag", tags[1].Type) -} - func TestRepository_GetTag(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") @@ -181,173 +157,3 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { assert.True(t, IsErrNotExist(err)) assert.Nil(t, tag4) } - -func TestRepository_parseTagRef(t *testing.T) { - tests := []struct { - name string - - givenRef map[string]string - - want *Tag - wantErr bool - expectedErr error - }{ - { - name: "lightweight tag", - - givenRef: map[string]string{ - "objecttype": "commit", - "refname:lstrip=2": "v1.9.1", - // object will be empty for lightweight tags - "object": "", - "objectname": "ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889", - "creator": "Foo Bar 1565789218 +0300", - "contents": `Add changelog of v1.9.1 (#7859) - -* add changelog of v1.9.1 -* Update CHANGELOG.md -`, - "contents:signature": "", - }, - - want: &Tag{ - Name: "v1.9.1", - ID: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), - Object: MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), - Type: "commit", - Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), - Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", - Signature: nil, - }, - }, - - { - name: "annotated tag", - - givenRef: map[string]string{ - "objecttype": "tag", - "refname:lstrip=2": "v0.0.1", - // object will refer to commit hash for annotated tag - "object": "3325fd8a973321fd59455492976c042dde3fd1ca", - "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", - "creator": "Foo Bar 1565789218 +0300", - "contents": `Add changelog of v1.9.1 (#7859) - -* add changelog of v1.9.1 -* Update CHANGELOG.md -`, - "contents:signature": "", - }, - - want: &Tag{ - Name: "v0.0.1", - ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), - Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), - Type: "tag", - Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), - Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", - Signature: nil, - }, - }, - - { - name: "annotated tag with signature", - - givenRef: map[string]string{ - "objecttype": "tag", - "refname:lstrip=2": "v0.0.1", - "object": "3325fd8a973321fd59455492976c042dde3fd1ca", - "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", - "creator": "Foo Bar 1565789218 +0300", - "contents": `Add changelog of v1.9.1 (#7859) - -* add changelog of v1.9.1 -* Update CHANGELOG.md ------BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, - "contents:signature": `-----BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, - }, - - want: &Tag{ - Name: "v0.0.1", - ID: MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), - Object: MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), - Type: "tag", - Tagger: parseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), - Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", - Signature: &CommitSignature{ - Signature: `-----BEGIN PGP SIGNATURE----- - -aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 -3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT -T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU -REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE -slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G -1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt -f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx -yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 -kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg -qbHDASXl -=2yGi ------END PGP SIGNATURE----- - -`, - Payload: `object 3325fd8a973321fd59455492976c042dde3fd1ca -type commit -tag v0.0.1 -tagger Foo Bar 1565789218 +0300 - -Add changelog of v1.9.1 (#7859) - -* add changelog of v1.9.1 -* Update CHANGELOG.md -`, - }, - }, - }, - } - - for _, test := range tests { - tc := test // don't close over loop variable - t.Run(tc.name, func(t *testing.T) { - got, err := parseTagRef(tc.givenRef) - - if tc.wantErr { - require.Error(t, err) - require.ErrorIs(t, err, tc.expectedErr) - } else { - require.NoError(t, err) - require.Equal(t, tc.want, got) - } - }) - } -} diff --git a/modules/git/signature.go b/modules/git/signature.go index f50a097758bdc..9ed00b0a60edd 100644 --- a/modules/git/signature.go +++ b/modules/git/signature.go @@ -18,7 +18,7 @@ import ( // // Haven't found the official reference for the standard format yet. // This function never fails, if the "line" can't be parsed, it returns a default Signature with "zero" time. -func parseSignatureFromCommitLine(line string) *Signature { +func ParseSignatureFromCommitLine(line string) *Signature { sig := &Signature{} s1, sx, ok1 := strings.Cut(line, " <") s2, s3, ok2 := strings.Cut(sx, "> ") diff --git a/modules/git/signature_nogogit.go b/modules/git/signature_nogogit.go index 0d19c0abdcc9b..372a66a90519d 100644 --- a/modules/git/signature_nogogit.go +++ b/modules/git/signature_nogogit.go @@ -26,5 +26,5 @@ func (s *Signature) String() string { // Decode decodes a byte array representing a signature to signature func (s *Signature) Decode(b []byte) { - *s = *parseSignatureFromCommitLine(util.UnsafeBytesToString(b)) + *s = *ParseSignatureFromCommitLine(util.UnsafeBytesToString(b)) } diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go index b9b5aff61b18b..43a92fb6466ce 100644 --- a/modules/git/signature_test.go +++ b/modules/git/signature_test.go @@ -41,7 +41,7 @@ func TestParseSignatureFromCommitLine(t *testing.T) { }, } for _, test := range tests { - got := parseSignatureFromCommitLine(test.line) + got := ParseSignatureFromCommitLine(test.line) assert.Equal(t, test.want, got) } } diff --git a/modules/git/tag.go b/modules/git/tag.go index 8bf3658d62fc1..4541d3eb3438c 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -5,7 +5,6 @@ package git import ( "bytes" - "sort" "code.gitea.io/gitea/modules/util" ) @@ -21,7 +20,7 @@ type Tag struct { Signature *CommitSignature } -func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) { +func ParsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) { pos := messageStart signStart, signEnd := -1, -1 for { @@ -82,34 +81,14 @@ func parseTagData(objectFormat ObjectFormat, data []byte) (*Tag, error) { case "type": tag.Type = string(val) // A commit can have one or more parents case "tagger": - tag.Tagger = parseSignatureFromCommitLine(util.UnsafeBytesToString(val)) + tag.Tagger = ParseSignatureFromCommitLine(util.UnsafeBytesToString(val)) } pos += eol + 1 } - payload, msg, sign := parsePayloadSignature(data, pos) + payload, msg, sign := ParsePayloadSignature(data, pos) tag.Message = msg if len(sign) > 0 { tag.Signature = &CommitSignature{Signature: sign, Payload: payload} } return tag, nil } - -type tagSorter []*Tag - -func (ts tagSorter) Len() int { - return len([]*Tag(ts)) -} - -func (ts tagSorter) Less(i, j int) bool { - return []*Tag(ts)[i].Tagger.When.After([]*Tag(ts)[j].Tagger.When) -} - -func (ts tagSorter) Swap(i, j int) { - []*Tag(ts)[i], []*Tag(ts)[j] = []*Tag(ts)[j], []*Tag(ts)[i] -} - -// sortTagsByTime -func sortTagsByTime(tags []*Tag) { - sorter := tagSorter(tags) - sort.Sort(sorter) -} diff --git a/modules/git/tree.go b/modules/git/tree.go index c1898b20cbe03..f8917387921a6 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -5,10 +5,7 @@ package git import ( - "bytes" "strings" - - "code.gitea.io/gitea/modules/git/gitcmd" ) type TreeCommon struct { @@ -57,32 +54,3 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) { } return g, nil } - -// LsTree checks if the given filenames are in the tree -func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) { - cmd := gitcmd.NewCommand("ls-tree", "-z", "--name-only"). - AddDashesAndList(append([]string{ref}, filenames...)...) - - res, _, err := cmd.WithDir(repo.Path).RunStdBytes(repo.Ctx) - if err != nil { - return nil, err - } - filelist := make([]string, 0, len(filenames)) - for line := range bytes.SplitSeq(res, []byte{'\000'}) { - filelist = append(filelist, string(line)) - } - - return filelist, err -} - -// GetTreePathLatestCommit returns the latest commit of a tree path -func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) { - stdout, _, err := gitcmd.NewCommand("rev-list", "-1"). - AddDynamicArguments(refName).AddDashesAndList(treePath). - WithDir(repo.Path). - RunStdString(repo.Ctx) - if err != nil { - return nil, err - } - return repo.GetCommit(strings.TrimSpace(stdout)) -} diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go index 67f95fe74894c..8489a250f0554 100644 --- a/modules/git/tree_test.go +++ b/modules/git/tree_test.go @@ -25,18 +25,3 @@ func TestSubTree_Issue29101(t *testing.T) { assert.True(t, IsErrNotExist(err)) } } - -func Test_GetTreePathLatestCommit(t *testing.T) { - repo, err := OpenRepository(t.Context(), filepath.Join(testReposDir, "repo6_blame")) - assert.NoError(t, err) - defer repo.Close() - - commitID, err := repo.GetBranchCommitID("master") - assert.NoError(t, err) - assert.Equal(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID) - - commit, err := repo.GetTreePathLatestCommit("master", "blame.txt") - assert.NoError(t, err) - assert.NotNil(t, commit) - assert.Equal(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String()) -} diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go index e05d75caf8393..05bdc14f3362e 100644 --- a/modules/gitrepo/branch.go +++ b/modules/gitrepo/branch.go @@ -12,18 +12,6 @@ import ( "code.gitea.io/gitea/modules/git/gitcmd" ) -// GetBranchesByPath returns a branch by its path -// if limit = 0 it will not limit -func GetBranchesByPath(ctx context.Context, repo Repository, skip, limit int) ([]string, int, error) { - gitRepo, err := OpenRepository(ctx, repo) - if err != nil { - return nil, 0, err - } - defer gitRepo.Close() - - return gitRepo.GetBranchNames(skip, limit) -} - func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (string, error) { gitRepo, err := OpenRepository(ctx, repo) if err != nil { diff --git a/modules/gitrepo/branch_gogit.go b/modules/gitrepo/branch_gogit.go new file mode 100644 index 0000000000000..4340155194195 --- /dev/null +++ b/modules/gitrepo/branch_gogit.go @@ -0,0 +1,67 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build gogit + +package gitrepo + +import ( + "context" + "sort" + "strings" + + "code.gitea.io/gitea/modules/git" + + "github.com/go-git/go-git/v5/plumbing" +) + +// GetBranches returns branches from the repository, skipping "skip" initial branches and +// returning at most "limit" branches, or all branches if "limit" is 0. +// Branches are returned with sort of `-committerdate` as the nogogit +// implementation. This requires full fetch, sort and then the +// skip/limit applies later as gogit returns in undefined order. +func GetBranchNames(ctx context.Context, repo Repository, skip, limit int) ([]string, int, error) { + type BranchData struct { + name string + committerDate int64 + } + var branchData []BranchData + + gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo) + if err != nil { + return nil, 0, err + } + defer closer.Close() + + branchIter, err := gitRepo.GoGitRepo().Branches() + if err != nil { + return nil, 0, err + } + defer branchIter.Close() + + _ = branchIter.ForEach(func(branch *plumbing.Reference) error { + obj, err := gitRepo.GoGitRepo().CommitObject(branch.Hash()) + if err != nil { + // skip branch if can't find commit + return nil + } + + branchData = append(branchData, BranchData{strings.TrimPrefix(branch.Name().String(), git.BranchPrefix), obj.Committer.When.Unix()}) + return nil + }) + + sort.Slice(branchData, func(i, j int) bool { + return !(branchData[i].committerDate < branchData[j].committerDate) + }) + + var branchNames []string + maxPos := len(branchData) + if limit > 0 { + maxPos = min(skip+limit, maxPos) + } + for i := skip; i < maxPos; i++ { + branchNames = append(branchNames, branchData[i].name) + } + + return branchNames, len(branchData), nil +} diff --git a/modules/gitrepo/branch_nogogit.go b/modules/gitrepo/branch_nogogit.go new file mode 100644 index 0000000000000..b894e0bb60c80 --- /dev/null +++ b/modules/gitrepo/branch_nogogit.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build !gogit + +package gitrepo + +import ( + "context" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/gitcmd" +) + +// GetBranchNames returns branches from the repository, skipping "skip" initial branches and +// returning at most "limit" branches, or all branches if "limit" is 0. +func GetBranchNames(ctx context.Context, repo Repository, skip, limit int) ([]string, int, error) { + return callShowRef(ctx, repo, git.BranchPrefix, gitcmd.TrustedCmdArgs{git.BranchPrefix, "--sort=-committerdate"}, skip, limit) +} diff --git a/modules/gitrepo/branch_test.go b/modules/gitrepo/branch_test.go new file mode 100644 index 0000000000000..b0e2d956b2333 --- /dev/null +++ b/modules/gitrepo/branch_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRepository_GetBranches(t *testing.T) { + storage := &mockRepository{path: "repo1_bare"} + + branches, countAll, err := GetBranchNames(t.Context(), storage, 0, 2) + + assert.NoError(t, err) + assert.Len(t, branches, 2) + assert.Equal(t, 3, countAll) + assert.ElementsMatch(t, []string{"master", "branch2"}, branches) + + branches, countAll, err = GetBranchNames(t.Context(), storage, 0, 0) + + assert.NoError(t, err) + assert.Len(t, branches, 3) + assert.Equal(t, 3, countAll) + assert.ElementsMatch(t, []string{"master", "branch2", "branch1"}, branches) + + branches, countAll, err = GetBranchNames(t.Context(), storage, 5, 1) + + assert.NoError(t, err) + assert.Empty(t, branches) + assert.Equal(t, 3, countAll) + assert.ElementsMatch(t, []string{}, branches) +} + +func BenchmarkRepository_GetBranches(b *testing.B) { + storage := &mockRepository{path: "repo1_bare"} + + for b.Loop() { + _, _, err := GetBranchNames(b.Context(), storage, 0, 0) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/modules/gitrepo/ref.go b/modules/gitrepo/ref.go index 5212528326875..3d7e0e275616d 100644 --- a/modules/gitrepo/ref.go +++ b/modules/gitrepo/ref.go @@ -5,8 +5,10 @@ package gitrepo import ( "context" + "strings" "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/util" ) func UpdateRef(ctx context.Context, repo Repository, refName, newCommitID string) error { @@ -17,3 +19,52 @@ func RemoveRef(ctx context.Context, repo Repository, refName string) error { return RunCmd(ctx, repo, gitcmd.NewCommand("update-ref", "--no-deref", "-d"). AddDynamicArguments(refName)) } + +// ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC +// refType should only be a literal "branch" or "tag" and nothing else +func ListOccurrences(ctx context.Context, repo Repository, refType, commitSHA string) ([]string, error) { + cmd := gitcmd.NewCommand() + switch refType { + case "branch": + cmd.AddArguments("branch") + case "tag": + cmd.AddArguments("tag") + default: + return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) + } + stdout, err := RunCmdString(ctx, repo, cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains"). + AddDynamicArguments(commitSHA)) + if err != nil { + return nil, err + } + + refs := strings.Split(strings.TrimSpace(stdout), "\n") + if refType == "branch" { + return parseBranches(refs), nil + } + return parseTags(refs), nil +} + +func parseBranches(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if strings.HasPrefix(ref, "* ") { // current branch (main branch) + results = append(results, ref[len("* "):]) + } else if strings.HasPrefix(ref, " ") { // all other branches + results = append(results, ref[len(" "):]) + } else if ref != "" { + results = append(results, ref) + } + } + return results +} + +func parseTags(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if ref != "" { + results = append(results, ref) + } + } + return results +} diff --git a/modules/gitrepo/ref_gogit.go b/modules/gitrepo/ref_gogit.go new file mode 100644 index 0000000000000..349f9108fac68 --- /dev/null +++ b/modules/gitrepo/ref_gogit.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build gogit + +package gitrepo + +import ( + "context" + "strings" + + "github.com/go-git/go-git/v5/plumbing" +) + +// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash +func GetRefsBySha(ctx context.Context, repo Repository, sha, prefix string) ([]string, error) { + var revList []string + gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo) + if err != nil { + return nil, err + } + defer closer.Close() + + iter, err := gitRepo.GoGitRepo().References() + if err != nil { + return nil, err + } + defer iter.Close() + + err = iter.ForEach(func(ref *plumbing.Reference) error { + if ref.Hash().String() == sha && strings.HasPrefix(string(ref.Name()), prefix) { + revList = append(revList, string(ref.Name())) + } + return nil + }) + return revList, err +} diff --git a/modules/gitrepo/ref_nogogit.go b/modules/gitrepo/ref_nogogit.go new file mode 100644 index 0000000000000..519b1b8ebda82 --- /dev/null +++ b/modules/gitrepo/ref_nogogit.go @@ -0,0 +1,23 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build !gogit + +package gitrepo + +import ( + "context" + "strings" +) + +// GetRefsBySha returns all references filtered with prefix that belong to a sha commit hash +func GetRefsBySha(ctx context.Context, repo Repository, sha, prefix string) ([]string, error) { + var revList []string + _, err := WalkShowRef(ctx, repo, nil, 0, 0, func(walkSha, refname string) error { + if walkSha == sha && strings.HasPrefix(refname, prefix) { + revList = append(revList, refname) + } + return nil + }) + return revList, err +} diff --git a/modules/gitrepo/ref_test.go b/modules/gitrepo/ref_test.go new file mode 100644 index 0000000000000..ec4467514f8c6 --- /dev/null +++ b/modules/gitrepo/ref_test.go @@ -0,0 +1,43 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "testing" + + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" +) + +func TestGetRefsBySha(t *testing.T) { + storage := &mockRepository{path: "repo5_pulls"} + + // do not exist + branches, err := GetRefsBySha(t.Context(), storage, "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") + assert.NoError(t, err) + assert.Empty(t, branches) + + // refs/pull/1/head + branches, err = GetRefsBySha(t.Context(), storage, "c83380d7056593c51a699d12b9c00627bd5743e9", git.PullPrefix) + assert.NoError(t, err) + assert.Equal(t, []string{"refs/pull/1/head"}, branches) + + branches, err = GetRefsBySha(t.Context(), storage, "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", git.BranchPrefix) + assert.NoError(t, err) + assert.Equal(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches) + + branches, err = GetRefsBySha(t.Context(), storage, "58a4bcc53ac13e7ff76127e0fb518b5262bf09af", git.BranchPrefix) + assert.NoError(t, err) + assert.Equal(t, []string{"refs/heads/test-patch-1"}, branches) +} + +func BenchmarkGetRefsBySha(b *testing.B) { + storage := &mockRepository{path: "repo5_pulls"} + + _, _ = GetRefsBySha(b.Context(), storage, "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0", "") + _, _ = GetRefsBySha(b.Context(), storage, "d8e0bbb45f200e67d9a784ce55bd90821af45ebd", "") + _, _ = GetRefsBySha(b.Context(), storage, "c83380d7056593c51a699d12b9c00627bd5743e9", "") + _, _ = GetRefsBySha(b.Context(), storage, "58a4bcc53ac13e7ff76127e0fb518b5262bf09af", "") +} diff --git a/modules/gitrepo/tag.go b/modules/gitrepo/tag.go index 58ed204a99fbf..d0ad34441321d 100644 --- a/modules/gitrepo/tag.go +++ b/modules/gitrepo/tag.go @@ -5,11 +5,131 @@ package gitrepo import ( "context" + "fmt" + "io" + "sort" + "strings" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/foreachref" + "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/modules/util" ) // IsTagExist returns true if given tag exists in the repository. func IsTagExist(ctx context.Context, repo Repository, name string) bool { return IsReferenceExist(ctx, repo, git.TagPrefix+name) } + +// GetTagInfos returns all tag infos of the repository. +func GetTagInfos(ctx context.Context, repo Repository, page, pageSize int) ([]*git.Tag, int, error) { + // Generally, refname:short should be equal to refname:lstrip=2 except core.warnAmbiguousRefs is used to select the strict abbreviation mode. + // https://git-scm.com/docs/git-for-each-ref#Documentation/git-for-each-ref.txt-refname + forEachRefFmt := foreachref.NewFormat("objecttype", "refname:lstrip=2", "object", "objectname", "creator", "contents", "contents:signature") + + stdoutReader, stdoutWriter := io.Pipe() + defer stdoutReader.Close() + defer stdoutWriter.Close() + stderr := strings.Builder{} + + go func() { + err := RunCmd(ctx, repo, gitcmd.NewCommand("for-each-ref"). + AddOptionFormat("--format=%s", forEachRefFmt.Flag()). + AddArguments("--sort", "-*creatordate", "refs/tags"). + WithStdout(stdoutWriter). + WithStderr(&stderr)) + if err != nil { + _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderr.String())) + } else { + _ = stdoutWriter.Close() + } + }() + + var tags []*git.Tag + parser := forEachRefFmt.Parser(stdoutReader) + for { + ref := parser.Next() + if ref == nil { + break + } + + tag, err := parseTagRef(ref) + if err != nil { + return nil, 0, fmt.Errorf("GetTagInfos: parse tag: %w", err) + } + tags = append(tags, tag) + } + if err := parser.Err(); err != nil { + return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err) + } + + sortTagsByTime(tags) + tagsTotal := len(tags) + if page != 0 { + tags = util.PaginateSlice(tags, page, pageSize).([]*git.Tag) + } + + return tags, tagsTotal, nil +} + +// parseTagRef parses a tag from a 'git for-each-ref'-produced reference. +func parseTagRef(ref map[string]string) (tag *git.Tag, err error) { + tag = &git.Tag{ + Type: ref["objecttype"], + Name: ref["refname:lstrip=2"], + } + + tag.ID, err = git.NewIDFromString(ref["objectname"]) + if err != nil { + return nil, fmt.Errorf("parse objectname '%s': %w", ref["objectname"], err) + } + + if tag.Type == "commit" { + // lightweight tag + tag.Object = tag.ID + } else { + // annotated tag + tag.Object, err = git.NewIDFromString(ref["object"]) + if err != nil { + return nil, fmt.Errorf("parse object '%s': %w", ref["object"], err) + } + } + + tag.Tagger = git.ParseSignatureFromCommitLine(ref["creator"]) + tag.Message = ref["contents"] + + // strip any signature if present in contents field + _, tag.Message, _ = git.ParsePayloadSignature(util.UnsafeStringToBytes(tag.Message), 0) + + // annotated tag with GPG signature + if tag.Type == "tag" && ref["contents:signature"] != "" { + payload := fmt.Sprintf("object %s\ntype commit\ntag %s\ntagger %s\n\n%s\n", + tag.Object, tag.Name, ref["creator"], strings.TrimSpace(tag.Message)) + tag.Signature = &git.CommitSignature{ + Signature: ref["contents:signature"], + Payload: payload, + } + } + + return tag, nil +} + +type tagSorter []*git.Tag + +func (ts tagSorter) Len() int { + return len([]*git.Tag(ts)) +} + +func (ts tagSorter) Less(i, j int) bool { + return []*git.Tag(ts)[i].Tagger.When.After([]*git.Tag(ts)[j].Tagger.When) +} + +func (ts tagSorter) Swap(i, j int) { + []*git.Tag(ts)[i], []*git.Tag(ts)[j] = []*git.Tag(ts)[j], []*git.Tag(ts)[i] +} + +// sortTagsByTime +func sortTagsByTime(tags []*git.Tag) { + sorter := tagSorter(tags) + sort.Sort(sorter) +} diff --git a/modules/gitrepo/tag_test.go b/modules/gitrepo/tag_test.go new file mode 100644 index 0000000000000..c7d391cf50d1f --- /dev/null +++ b/modules/gitrepo/tag_test.go @@ -0,0 +1,201 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "testing" + + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_GetTagInfos(t *testing.T) { + storage := &mockRepository{path: "repo1_bare"} + + tags, total, err := GetTagInfos(t.Context(), storage, 0, 0) + if err != nil { + assert.NoError(t, err) + return + } + assert.Len(t, tags, 2) + assert.Len(t, tags, total) + assert.Equal(t, "signed-tag", tags[0].Name) + assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) + assert.Equal(t, "tag", tags[0].Type) + assert.Equal(t, "test", tags[1].Name) + assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) + assert.Equal(t, "tag", tags[1].Type) +} + +func TestRepository_parseTagRef(t *testing.T) { + tests := []struct { + name string + + givenRef map[string]string + + want *git.Tag + wantErr bool + expectedErr error + }{ + { + name: "lightweight tag", + + givenRef: map[string]string{ + "objecttype": "commit", + "refname:lstrip=2": "v1.9.1", + // object will be empty for lightweight tags + "object": "", + "objectname": "ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + "contents:signature": "", + }, + + want: &git.Tag{ + Name: "v1.9.1", + ID: git.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + Object: git.MustIDFromString("ab23e4b7f4cd0caafe0174c0e7ef6d651ba72889"), + Type: "commit", + Tagger: git.ParseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", + Signature: nil, + }, + }, + + { + name: "annotated tag", + + givenRef: map[string]string{ + "objecttype": "tag", + "refname:lstrip=2": "v0.0.1", + // object will refer to commit hash for annotated tag + "object": "3325fd8a973321fd59455492976c042dde3fd1ca", + "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + "contents:signature": "", + }, + + want: &git.Tag{ + Name: "v0.0.1", + ID: git.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: git.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + Type: "tag", + Tagger: git.ParseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md\n", + Signature: nil, + }, + }, + + { + name: "annotated tag with signature", + + givenRef: map[string]string{ + "objecttype": "tag", + "refname:lstrip=2": "v0.0.1", + "object": "3325fd8a973321fd59455492976c042dde3fd1ca", + "objectname": "8c68a1f06fc59c655b7e3905b159d761e91c53c9", + "creator": "Foo Bar 1565789218 +0300", + "contents": `Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + "contents:signature": `-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + }, + + want: &git.Tag{ + Name: "v0.0.1", + ID: git.MustIDFromString("8c68a1f06fc59c655b7e3905b159d761e91c53c9"), + Object: git.MustIDFromString("3325fd8a973321fd59455492976c042dde3fd1ca"), + Type: "tag", + Tagger: git.ParseSignatureFromCommitLine("Foo Bar 1565789218 +0300"), + Message: "Add changelog of v1.9.1 (#7859)\n\n* add changelog of v1.9.1\n* Update CHANGELOG.md", + Signature: &git.CommitSignature{ + Signature: `-----BEGIN PGP SIGNATURE----- + +aBCGzBAABCgAdFiEEyWRwv/q1Q6IjSv+D4IPOwzt33PoFAmI8jbIACgkQ4IPOwzt3 +3PoRuAv9FVSbPBXvzECubls9KQd7urwEvcfG20Uf79iBwifQJUv+egNQojrs6APT +T4CdIXeGRpwJZaGTUX9RWnoDO1SLXAWnc82CypWraNwrHq8Go2YeoVu0Iy3vb0EU +REdob/tXYZecMuP8AjhUR0XfdYaERYAvJ2dYsH/UkFrqDjM3V4kPXWG+R5DCaZiE +slB5U01i4Dwb/zm/ckzhUGEcOgcnpOKX8SnY5kYRVDY47dl/yJZ1u2XWir3mu60G +1geIitH7StBddHi/8rz+sJwTfcVaLjn2p59p/Dr9aGbk17GIaKq1j0pZA2lKT0Xt +f9jDqU+9vCxnKgjSDhrwN69LF2jT47ZFjEMGV/wFPOa1EBxVWpgQ/CfEolBlbUqx +yVpbxi/6AOK2lmG130e9jEZJcu+WeZUeq851WgKSEkf2d5f/JpwtSTEOlOedu6V6 +kl845zu5oE2nKM4zMQ7XrYQn538I31ps+VGQ0H8R07WrZP8WKUWugL2cU8KmXFwg +qbHDASXl +=2yGi +-----END PGP SIGNATURE----- + +`, + Payload: `object 3325fd8a973321fd59455492976c042dde3fd1ca +type commit +tag v0.0.1 +tagger Foo Bar 1565789218 +0300 + +Add changelog of v1.9.1 (#7859) + +* add changelog of v1.9.1 +* Update CHANGELOG.md +`, + }, + }, + }, + } + + for _, test := range tests { + tc := test // don't close over loop variable + t.Run(tc.name, func(t *testing.T) { + got, err := parseTagRef(tc.givenRef) + + if tc.wantErr { + require.Error(t, err) + require.ErrorIs(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.want, got) + } + }) + } +} diff --git a/modules/gitrepo/tree.go b/modules/gitrepo/tree.go new file mode 100644 index 0000000000000..4c4142abdcf3f --- /dev/null +++ b/modules/gitrepo/tree.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "bytes" + "context" + "strings" + + "code.gitea.io/gitea/modules/git/gitcmd" +) + +// LsTree checks if the given filenames are in the tree +func LsTree(ctx context.Context, repo Repository, ref string, filenames ...string) ([]string, error) { + res, _, err := RunCmdBytes(ctx, repo, gitcmd.NewCommand("ls-tree", "-z", "--name-only"). + AddDashesAndList(append([]string{ref}, filenames...)...)) + if err != nil { + return nil, err + } + filelist := make([]string, 0, len(filenames)) + for line := range bytes.SplitSeq(res, []byte{'\000'}) { + filelist = append(filelist, string(line)) + } + + return filelist, err +} + +// GetTreePathLatestCommitID returns the latest commit of a tree path +func GetTreePathLatestCommitID(ctx context.Context, repo Repository, refName, treePath string) (string, error) { + stdout, err := RunCmdString(ctx, repo, gitcmd.NewCommand("rev-list", "-1"). + AddDynamicArguments(refName).AddDashesAndList(treePath)) + if err != nil { + return "", err + } + return strings.TrimSpace(stdout), nil +} diff --git a/modules/gitrepo/tree_test.go b/modules/gitrepo/tree_test.go new file mode 100644 index 0000000000000..d040219b476ba --- /dev/null +++ b/modules/gitrepo/tree_test.go @@ -0,0 +1,18 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package gitrepo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_GetTreePathLatestCommit(t *testing.T) { + storage := &mockRepository{path: "repo6_blame"} + + commitID, err := GetTreePathLatestCommitID(t.Context(), storage, "master", "blame.txt") + assert.NoError(t, err) + assert.Equal(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commitID) +} diff --git a/modules/gitrepo/walk_nogogit.go b/modules/gitrepo/walk_nogogit.go index ff9555996dff5..7841224d0ecbd 100644 --- a/modules/gitrepo/walk_nogogit.go +++ b/modules/gitrepo/walk_nogogit.go @@ -6,12 +6,115 @@ package gitrepo import ( + "bufio" "context" + "io" + "strings" - "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/gitcmd" ) // WalkReferences walks all the references from the repository func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) { - return git.WalkShowRef(ctx, repoPath(repo), nil, 0, 0, walkfn) + return WalkShowRef(ctx, repo, nil, 0, 0, walkfn) +} + +// callShowRef return refs, if limit = 0 it will not limit +func callShowRef(ctx context.Context, repo Repository, trimPrefix string, extraArgs gitcmd.TrustedCmdArgs, skip, limit int) (branchNames []string, countAll int, err error) { + countAll, err = WalkShowRef(ctx, repo, extraArgs, skip, limit, func(_, branchName string) error { + branchName = strings.TrimPrefix(branchName, trimPrefix) + branchNames = append(branchNames, branchName) + + return nil + }) + return branchNames, countAll, err +} + +func WalkShowRef(ctx context.Context, repo Repository, extraArgs gitcmd.TrustedCmdArgs, skip, limit int, walkfn func(sha1, refname string) error) (countAll int, err error) { + stdoutReader, stdoutWriter := io.Pipe() + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + go func() { + stderrBuilder := &strings.Builder{} + args := gitcmd.TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"} + args = append(args, extraArgs...) + err := RunCmd(ctx, repo, gitcmd.NewCommand(args...). + WithStdout(stdoutWriter). + WithStderr(stderrBuilder)) + if err != nil { + if stderrBuilder.Len() == 0 { + _ = stdoutWriter.Close() + return + } + _ = stdoutWriter.CloseWithError(gitcmd.ConcatenateError(err, stderrBuilder.String())) + } else { + _ = stdoutWriter.Close() + } + }() + + i := 0 + bufReader := bufio.NewReader(stdoutReader) + for i < skip { + _, isPrefix, err := bufReader.ReadLine() + if err == io.EOF { + return i, nil + } + if err != nil { + return 0, err + } + if !isPrefix { + i++ + } + } + for limit == 0 || i < skip+limit { + // The output of show-ref is simply a list: + // SP LF + sha, err := bufReader.ReadString(' ') + if err == io.EOF { + return i, nil + } + if err != nil { + return 0, err + } + + branchName, err := bufReader.ReadString('\n') + if err == io.EOF { + // This shouldn't happen... but we'll tolerate it for the sake of peace + return i, nil + } + if err != nil { + return i, err + } + + if len(branchName) > 0 { + branchName = branchName[:len(branchName)-1] + } + + if len(sha) > 0 { + sha = sha[:len(sha)-1] + } + + err = walkfn(sha, branchName) + if err != nil { + return i, err + } + i++ + } + // count all refs + for limit != 0 { + _, isPrefix, err := bufReader.ReadLine() + if err == io.EOF { + return i, nil + } + if err != nil { + return 0, err + } + if !isPrefix { + i++ + } + } + return i, nil } diff --git a/modules/repository/branch.go b/modules/repository/branch.go index 30aa0a6e85ec1..7d43dc098cd27 100644 --- a/modules/repository/branch.go +++ b/modules/repository/branch.go @@ -50,7 +50,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository, allBranches := container.Set[string]{} { - branches, _, err := gitRepo.GetBranchNames(0, 0) + branches, _, err := gitrepo.GetBranchNames(ctx, repo, 0, 0) if err != nil { return 0, err } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 9353043f91d2f..3d2b006ff6dbc 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -47,13 +47,7 @@ func SyncRepoTags(ctx context.Context, repoID int64) error { return err } - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - return err - } - defer gitRepo.Close() - - return SyncReleasesWithTags(ctx, repo, gitRepo) + return SyncReleasesWithTags(ctx, repo) } // StoreMissingLfsObjectsInRepository downloads missing LFS objects @@ -177,9 +171,9 @@ func (shortRelease) TableName() string { // upstream. Hence, after each sync we want the release set to be // identical to the upstream tag set. This is much more efficient for // repositories like https://github.com/vim/vim (with over 13000 tags). -func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error { +func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository) error { log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) - tags, _, err := gitRepo.GetTagInfos(0, 0) + tags, _, err := gitrepo.GetTagInfos(ctx, repo, 0, 0) if err != nil { return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index deb68963c2045..eac54526de548 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -16,6 +16,7 @@ import ( git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/lfs" @@ -240,7 +241,12 @@ func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEn return nil, nil, nil } - latestCommit, err := ctx.Repo.GitRepo.GetTreePathLatestCommit(ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) + latestCommitID, err := gitrepo.GetTreePathLatestCommitID(ctx, ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) + if err != nil { + ctx.APIErrorInternal(err) + return nil, nil, nil + } + latestCommit, err := ctx.Repo.GitRepo.GetCommit(latestCommitID) if err != nil { ctx.APIErrorInternal(err) return nil, nil, nil diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 9e77637282a3e..e18966455e449 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/gitrepo" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" @@ -55,7 +56,8 @@ func ListTags(ctx *context.APIContext) { listOpts := utils.GetListOptions(ctx) - tags, total, err := ctx.Repo.GitRepo.GetTagInfos(listOpts.Page, listOpts.PageSize) + // TODO: get tags from database directly + tags, total, err := gitrepo.GetTagInfos(ctx, ctx.Repo.Repository, listOpts.Page, listOpts.PageSize) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index 6f394aae27d02..6ad2e159a1d65 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -9,6 +9,7 @@ import ( git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -97,9 +98,14 @@ func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) { return nil, nil } - latestCommit, err := ctx.Repo.GitRepo.GetTreePathLatestCommit(ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) + latestCommitID, err := gitrepo.GetTreePathLatestCommitID(ctx, ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), ctx.Repo.TreePath) if err != nil { - ctx.ServerError("GetTreePathLatestCommit", err) + ctx.ServerError("GetTreePathLatestCommitID", err) + return nil, nil + } + latestCommit, err := ctx.Repo.GitRepo.GetCommit(latestCommitID) + if err != nil { + ctx.ServerError("GetCommit", err) return nil, nil } lastModified := &latestCommit.Committer.When diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 00d30bedef5ed..17a2a11acc526 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -231,7 +231,7 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { } else if reallyEmpty { showEmpty = true // the repo is really empty updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) - } else if branches, _, _ := ctx.Repo.GitRepo.GetBranchNames(0, 1); len(branches) == 0 { + } else if branches, _, _ := gitrepo.GetBranchNames(ctx, ctx.Repo.Repository, 0, 1); len(branches) == 0 { showEmpty = true // it is not really empty, but there is no branch // at the moment, other repo units like "actions" are not able to handle such case, // so we just mark the repo as empty to prevent from displaying these units. diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index e145f93f044aa..2930001bf7a86 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -104,13 +104,7 @@ func StartPRCheckAndAutoMergeBySHA(ctx context.Context, sha string, repo *repo_m } func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) { - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - return nil, err - } - defer gitRepo.Close() - - refs, err := gitRepo.GetRefsBySha(sha, "") + refs, err := gitrepo.GetRefsBySha(ctx, repo, sha, "") if err != nil { return nil, err } diff --git a/services/context/repo.go b/services/context/repo.go index 381333537464a..7f66049c968f5 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -859,7 +859,7 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { if reqPath == "" { refShortName = ctx.Repo.Repository.DefaultBranch if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) { - brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 1) + brs, _, err := gitrepo.GetBranchNames(ctx, ctx.Repo.Repository, 0, 1) if err == nil && len(brs) != 0 { refShortName = brs[0] } else if len(brs) == 0 { diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index f32c095d76a9f..899cb702bb455 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -363,7 +363,7 @@ func (g *GiteaLocalUploader) CreateReleases(ctx context.Context, releases ...*ba // SyncTags syncs releases with tags in the database func (g *GiteaLocalUploader) SyncTags(ctx context.Context) error { - return repo_module.SyncReleasesWithTags(ctx, g.repo, g.gitRepo) + return repo_module.SyncReleasesWithTags(ctx, g.repo) } func (g *GiteaLocalUploader) SyncBranches(ctx context.Context) error { diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index f9c40049db64f..b7a03a7436efa 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -343,7 +343,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo } log.Trace("SyncMirrors [repo: %-v]: syncing releases with tags...", m.Repo) - if err = repo_module.SyncReleasesWithTags(ctx, m.Repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, m.Repo); err != nil { log.Error("SyncMirrors [repo: %-v]: failed to synchronize tags to releases: %v", m.Repo, err) } gitRepo.Close() @@ -407,7 +407,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo } log.Trace("SyncMirrors [repo: %-v]: invalidating mirror branch caches...", m.Repo) - branches, _, err := gitrepo.GetBranchesByPath(ctx, m.Repo, 0, 0) + branches, _, err := gitrepo.GetBranchNames(ctx, m.Repo, 0, 0) if err != nil { log.Error("SyncMirrors [repo: %-v]: failed to GetBranches: %v", m.Repo, err) return nil, false diff --git a/services/pull/pull.go b/services/pull/pull.go index ff5c56200111b..ea9a721f6bea9 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -748,7 +748,7 @@ func AdjustPullsCausedByBranchDeleted(ctx context.Context, doer *user_model.User // CloseRepoBranchesPulls close all pull requests which head branches are in the given repository, but only whose base repo is not in the given repository func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *repo_model.Repository) error { - branches, _, err := gitrepo.GetBranchesByPath(ctx, repo, 0, 0) + branches, _, err := gitrepo.GetBranchNames(ctx, repo, 0, 0) if err != nil { return err } diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 18d70d1bee360..9328cf363de46 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -151,7 +151,7 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr return fmt.Errorf("SyncRepoBranchesWithRepo: %w", err) } - if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo); err != nil { return fmt.Errorf("SyncReleasesWithTags: %w", err) } diff --git a/services/repository/commit.go b/services/repository/commit.go index e8c0262ef41c7..e13289f7ce128 100644 --- a/services/repository/commit.go +++ b/services/repository/commit.go @@ -7,6 +7,7 @@ import ( "context" "fmt" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/util" gitea_ctx "code.gitea.io/gitea/services/context" ) @@ -24,11 +25,11 @@ type namedLink struct { // TODO: better name? // LoadBranchesAndTags creates a new repository branch func LoadBranchesAndTags(ctx context.Context, baseRepo *gitea_ctx.Repository, commitSHA string) (*ContainedLinks, error) { - containedTags, err := baseRepo.GitRepo.ListOccurrences(ctx, "tag", commitSHA) + containedTags, err := gitrepo.ListOccurrences(ctx, baseRepo.Repository, "tag", commitSHA) if err != nil { return nil, fmt.Errorf("encountered a problem while querying %s: %w", "tags", err) } - containedBranches, err := baseRepo.GitRepo.ListOccurrences(ctx, "branch", commitSHA) + containedBranches, err := gitrepo.ListOccurrences(ctx, baseRepo.Repository, "branch", commitSHA) if err != nil { return nil, fmt.Errorf("encountered a problem while querying %s: %w", "branches", err) } diff --git a/services/repository/fork.go b/services/repository/fork.go index f92af656059da..632639ee81144 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -180,7 +180,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil { return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err) } - if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo); err != nil { return nil, fmt.Errorf("Sync releases from git tags failed: %v", err) } diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 8f515326ad60d..8c64133f2726b 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -153,7 +153,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, // otherwise, the releases sync will be done out of this function if !opts.Releases { repo.IsMirror = opts.Mirror - if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + if err = repo_module.SyncReleasesWithTags(ctx, repo); err != nil { log.Error("Failed to synchronize tags to releases for repository: %v", err) } } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index f4115038cbd2c..9e13c871e0a9a 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -52,12 +52,12 @@ func InitWiki(ctx context.Context, repo *repo_model.Repository) error { // prepareGitPath try to find a suitable file path with file name by the given raw wiki name. // return: existence, prepared file path with name, error -func prepareGitPath(gitRepo *git.Repository, defaultWikiBranch string, wikiPath WebPath) (bool, string, error) { +func prepareGitPath(ctx context.Context, repo gitrepo.Repository, defaultWikiBranch string, wikiPath WebPath) (bool, string, error) { unescaped := string(wikiPath) + ".md" gitPath := WebPathToGitPath(wikiPath) // Look for both files - filesInIndex, err := gitRepo.LsTree(defaultWikiBranch, unescaped, gitPath) + filesInIndex, err := gitrepo.LsTree(ctx, repo, defaultWikiBranch, unescaped, gitPath) if err != nil { if strings.Contains(err.Error(), "Not a valid object name") { return false, gitPath, nil // branch doesn't exist @@ -137,7 +137,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model } } - isWikiExist, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, newWikiName) + isWikiExist, newWikiPath, err := prepareGitPath(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch, newWikiName) if err != nil { return err } @@ -153,7 +153,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model isOldWikiExist := true oldWikiPath := newWikiPath if oldWikiName != newWikiName { - isOldWikiExist, oldWikiPath, err = prepareGitPath(gitRepo, repo.DefaultWikiBranch, oldWikiName) + isOldWikiExist, oldWikiPath, err = prepareGitPath(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch, oldWikiName) if err != nil { return err } @@ -294,7 +294,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return fmt.Errorf("unable to read HEAD tree to index in: %s %w", basePath, err) } - found, wikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, wikiName) + found, wikiPath, err := prepareGitPath(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch, wikiName) if err != nil { return err } diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index e571e093b6fe3..d173ce10aa36e 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -11,7 +11,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" repo_service "code.gitea.io/gitea/services/repository" @@ -252,10 +251,6 @@ func TestRepository_DeleteWikiPage(t *testing.T) { func TestPrepareWikiFileName(t *testing.T) { unittest.PrepareTestEnv(t) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - gitRepo, err := gitrepo.OpenRepository(t.Context(), repo.WikiStorageRepo()) - require.NoError(t, err) - - defer gitRepo.Close() tests := []struct { name string @@ -279,7 +274,7 @@ func TestPrepareWikiFileName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { webPath := UserTitleToWebPath("", tt.arg) - existence, newWikiPath, err := prepareGitPath(gitRepo, repo.DefaultWikiBranch, webPath) + existence, newWikiPath, err := prepareGitPath(t.Context(), repo.WikiStorageRepo(), repo.DefaultWikiBranch, webPath) if (err != nil) != tt.wantErr { assert.NoError(t, err) return @@ -299,18 +294,19 @@ func TestPrepareWikiFileName(t *testing.T) { func TestPrepareWikiFileName_FirstPage(t *testing.T) { unittest.PrepareTestEnv(t) - // Now create a temporaryDirectory - tmpDir := t.TempDir() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - err := git.InitRepository(t.Context(), tmpDir, true, git.Sha1ObjectFormat.Name()) + // Now create a temporaryDirectory + repo, err := repo_service.CreateRepositoryDirectly(t.Context(), user2, user2, repo_service.CreateRepoOptions{ + Name: "wiki-test", + AutoInit: false, + }, true) assert.NoError(t, err) - gitRepo, err := git.OpenRepository(t.Context(), tmpDir) - require.NoError(t, err) - - defer gitRepo.Close() + err = gitrepo.InitRepository(t.Context(), repo.WikiStorageRepo(), repo.ObjectFormatName) + assert.NoError(t, err) - existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home") + existence, newWikiPath, err := prepareGitPath(t.Context(), repo.WikiStorageRepo(), "master", "Home") assert.False(t, existence) assert.NoError(t, err) assert.Equal(t, "Home.md", newWikiPath) diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index 5521c786d9ecc..7aa48f0ea2cb2 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -240,11 +240,7 @@ func Test_MigrateFromGiteaToGitea(t *testing.T) { assert.False(t, pr13.HasMerged) assert.True(t, pr13.Issue.IsLocked) - gitRepo, err := gitrepo.OpenRepository(t.Context(), migratedRepo) - require.NoError(t, err) - defer gitRepo.Close() - - branches, _, err := gitRepo.GetBranchNames(0, 0) + branches, _, err := gitrepo.GetBranchNames(t.Context(), migratedRepo, 0, 0) require.NoError(t, err) assert.ElementsMatch(t, []string{"6543-patch-1", "master", "6543-forks/add-xkcd-2199"}, branches) // last branch comes from the pull request @@ -254,7 +250,7 @@ func Test_MigrateFromGiteaToGitea(t *testing.T) { require.NoError(t, err) assert.ElementsMatch(t, []string{"6543-patch-1", "master", "6543-forks/add-xkcd-2199"}, branchNames) - tags, _, err := gitRepo.GetTagInfos(0, 0) + tags, _, err := gitrepo.GetTagInfos(t.Context(), migratedRepo, 0, 0) require.NoError(t, err) tagNames := make([]string, 0, len(tags)) for _, tag := range tags { diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 53e68432ccde6..ac6afc41caac2 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -875,7 +875,7 @@ func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { masterCommitID, err := baseGitRepo.GetBranchCommitID("master") assert.NoError(t, err) - branches, _, err := baseGitRepo.GetBranchNames(0, 100) + branches, _, err := gitrepo.GetBranchNames(t.Context(), baseRepo, 0, 100) assert.NoError(t, err) assert.ElementsMatch(t, []string{"sub-home-md-img-check", "home-md-img-check", "pr-to-update", "branch2", "DefaultBranch", "develop", "feature/1", "master"}, branches) baseGitRepo.Close()