aboutsummaryrefslogtreecommitdiffstats
path: root/modules/git
diff options
context:
space:
mode:
Diffstat (limited to 'modules/git')
-rw-r--r--modules/git/attribute.go35
-rw-r--r--modules/git/attribute/attribute.go114
-rw-r--r--modules/git/attribute/attribute_test.go37
-rw-r--r--modules/git/attribute/batch.go216
-rw-r--r--modules/git/attribute/batch_test.go172
-rw-r--r--modules/git/attribute/checker.go101
-rw-r--r--modules/git/attribute/checker_test.go84
-rw-r--r--modules/git/attribute/main_test.go41
-rw-r--r--modules/git/batch.go13
-rw-r--r--modules/git/batch_reader.go12
-rw-r--r--modules/git/blame.go74
-rw-r--r--modules/git/blame_sha256_test.go5
-rw-r--r--modules/git/blame_test.go5
-rw-r--r--modules/git/blob.go84
-rw-r--r--modules/git/blob_test.go2
-rw-r--r--modules/git/cmdverb.go36
-rw-r--r--modules/git/command.go79
-rw-r--r--modules/git/command_race_test.go8
-rw-r--r--modules/git/command_test.go29
-rw-r--r--modules/git/commit.go41
-rw-r--r--modules/git/commit_info_nogogit.go46
-rw-r--r--modules/git/commit_info_test.go7
-rw-r--r--modules/git/commit_reader.go132
-rw-r--r--modules/git/commit_sha256_test.go14
-rw-r--r--modules/git/commit_submodule_file.go4
-rw-r--r--modules/git/commit_submodule_file_test.go13
-rw-r--r--modules/git/commit_test.go35
-rw-r--r--modules/git/config.go16
-rw-r--r--modules/git/diff.go12
-rw-r--r--modules/git/diff_test.go10
-rw-r--r--modules/git/error.go16
-rw-r--r--modules/git/foreachref/format.go2
-rw-r--r--modules/git/fsck.go2
-rw-r--r--modules/git/git.go4
-rw-r--r--modules/git/git_test.go7
-rw-r--r--modules/git/grep.go33
-rw-r--r--modules/git/grep_test.go15
-rw-r--r--modules/git/hook.go65
-rw-r--r--modules/git/key.go15
-rw-r--r--modules/git/languagestats/language_stats.go (renamed from modules/git/repo_language_stats.go)30
-rw-r--r--modules/git/languagestats/language_stats_gogit.go (renamed from modules/git/repo_language_stats_gogit.go)73
-rw-r--r--modules/git/languagestats/language_stats_nogogit.go (renamed from modules/git/repo_language_stats_nogogit.go)94
-rw-r--r--modules/git/languagestats/language_stats_test.go (renamed from modules/git/repo_language_stats_test.go)18
-rw-r--r--modules/git/languagestats/main_test.go41
-rw-r--r--modules/git/last_commit_cache.go2
-rw-r--r--modules/git/log_name_status.go30
-rw-r--r--modules/git/notes_test.go9
-rw-r--r--modules/git/object_id.go2
-rw-r--r--modules/git/parse.go16
-rw-r--r--modules/git/parse_nogogit.go2
-rw-r--r--modules/git/parse_nogogit_test.go4
-rw-r--r--modules/git/pipeline/catfile.go10
-rw-r--r--modules/git/pipeline/lfs_nogogit.go2
-rw-r--r--modules/git/pipeline/namerev.go2
-rw-r--r--modules/git/pipeline/revlist.go8
-rw-r--r--modules/git/ref.go4
-rw-r--r--modules/git/remote.go6
-rw-r--r--modules/git/repo.go44
-rw-r--r--modules/git/repo_archive.go4
-rw-r--r--modules/git/repo_attribute.go323
-rw-r--r--modules/git/repo_attribute_test.go97
-rw-r--r--modules/git/repo_base_gogit.go7
-rw-r--r--modules/git/repo_base_nogogit.go15
-rw-r--r--modules/git/repo_blame.go4
-rw-r--r--modules/git/repo_branch.go87
-rw-r--r--modules/git/repo_branch_gogit.go2
-rw-r--r--modules/git/repo_branch_nogogit.go2
-rw-r--r--modules/git/repo_branch_test.go14
-rw-r--r--modules/git/repo_commit.go95
-rw-r--r--modules/git/repo_commit_gogit.go2
-rw-r--r--modules/git/repo_commit_nogogit.go18
-rw-r--r--modules/git/repo_commitgraph.go2
-rw-r--r--modules/git/repo_commitgraph_gogit.go4
-rw-r--r--modules/git/repo_compare.go48
-rw-r--r--modules/git/repo_gpg.go20
-rw-r--r--modules/git/repo_index.go39
-rw-r--r--modules/git/repo_object.go20
-rw-r--r--modules/git/repo_ref.go11
-rw-r--r--modules/git/repo_ref_nogogit.go2
-rw-r--r--modules/git/repo_stats.go9
-rw-r--r--modules/git/repo_stats_test.go2
-rw-r--r--modules/git/repo_tag.go24
-rw-r--r--modules/git/repo_tag_nogogit.go7
-rw-r--r--modules/git/repo_tag_test.go60
-rw-r--r--modules/git/repo_test.go7
-rw-r--r--modules/git/repo_tree.go15
-rw-r--r--modules/git/repo_tree_gogit.go2
-rw-r--r--modules/git/repo_tree_nogogit.go6
-rw-r--r--modules/git/signature_test.go2
-rw-r--r--modules/git/submodule.go6
-rw-r--r--modules/git/submodule_test.go21
-rw-r--r--modules/git/tag.go5
-rw-r--r--modules/git/tests/repos/language_stats_repo/config2
-rw-r--r--modules/git/tests/repos/repo3_notes/config2
-rw-r--r--modules/git/tests/repos/repo4_commitsbetween/config2
-rw-r--r--modules/git/tree.go10
-rw-r--r--modules/git/tree_blob_gogit.go1
-rw-r--r--modules/git/tree_blob_nogogit.go36
-rw-r--r--modules/git/tree_entry.go98
-rw-r--r--modules/git/tree_entry_common_test.go76
-rw-r--r--modules/git/tree_entry_gogit.go10
-rw-r--r--modules/git/tree_entry_mode.go68
-rw-r--r--modules/git/tree_entry_nogogit.go14
-rw-r--r--modules/git/tree_entry_test.go47
-rw-r--r--modules/git/tree_gogit.go3
-rw-r--r--modules/git/tree_nogogit.go6
-rw-r--r--modules/git/tree_test.go6
-rw-r--r--modules/git/url/url.go10
-rw-r--r--modules/git/url/url_test.go14
-rw-r--r--modules/git/utils.go28
110 files changed, 1895 insertions, 1553 deletions
diff --git a/modules/git/attribute.go b/modules/git/attribute.go
deleted file mode 100644
index 4dfa510369..0000000000
--- a/modules/git/attribute.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2024 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
- "code.gitea.io/gitea/modules/optional"
-)
-
-const (
- AttributeLinguistVendored = "linguist-vendored"
- AttributeLinguistGenerated = "linguist-generated"
- AttributeLinguistDocumentation = "linguist-documentation"
- AttributeLinguistDetectable = "linguist-detectable"
- AttributeLinguistLanguage = "linguist-language"
- AttributeGitlabLanguage = "gitlab-language"
-)
-
-// true if "set"/"true", false if "unset"/"false", none otherwise
-func AttributeToBool(attr map[string]string, name string) optional.Option[bool] {
- switch attr[name] {
- case "set", "true":
- return optional.Some(true)
- case "unset", "false":
- return optional.Some(false)
- }
- return optional.None[bool]()
-}
-
-func AttributeToString(attr map[string]string, name string) optional.Option[string] {
- if value, has := attr[name]; has && value != "unspecified" {
- return optional.Some(value)
- }
- return optional.None[string]()
-}
diff --git a/modules/git/attribute/attribute.go b/modules/git/attribute/attribute.go
new file mode 100644
index 0000000000..adf323ef41
--- /dev/null
+++ b/modules/git/attribute/attribute.go
@@ -0,0 +1,114 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "strings"
+
+ "code.gitea.io/gitea/modules/optional"
+)
+
+type Attribute string
+
+const (
+ LinguistVendored = "linguist-vendored"
+ LinguistGenerated = "linguist-generated"
+ LinguistDocumentation = "linguist-documentation"
+ LinguistDetectable = "linguist-detectable"
+ LinguistLanguage = "linguist-language"
+ GitlabLanguage = "gitlab-language"
+ Lockable = "lockable"
+ Filter = "filter"
+)
+
+var LinguistAttributes = []string{
+ LinguistVendored,
+ LinguistGenerated,
+ LinguistDocumentation,
+ LinguistDetectable,
+ LinguistLanguage,
+ GitlabLanguage,
+}
+
+func (a Attribute) IsUnspecified() bool {
+ return a == "" || a == "unspecified"
+}
+
+func (a Attribute) ToString() optional.Option[string] {
+ if !a.IsUnspecified() {
+ return optional.Some(string(a))
+ }
+ return optional.None[string]()
+}
+
+// ToBool converts the attribute value to optional boolean: true if "set"/"true", false if "unset"/"false", none otherwise
+func (a Attribute) ToBool() optional.Option[bool] {
+ switch a {
+ case "set", "true":
+ return optional.Some(true)
+ case "unset", "false":
+ return optional.Some(false)
+ }
+ return optional.None[bool]()
+}
+
+type Attributes struct {
+ m map[string]Attribute
+}
+
+func NewAttributes() *Attributes {
+ return &Attributes{m: make(map[string]Attribute)}
+}
+
+func (attrs *Attributes) Get(name string) Attribute {
+ if value, has := attrs.m[name]; has {
+ return value
+ }
+ return ""
+}
+
+func (attrs *Attributes) GetVendored() optional.Option[bool] {
+ return attrs.Get(LinguistVendored).ToBool()
+}
+
+func (attrs *Attributes) GetGenerated() optional.Option[bool] {
+ return attrs.Get(LinguistGenerated).ToBool()
+}
+
+func (attrs *Attributes) GetDocumentation() optional.Option[bool] {
+ return attrs.Get(LinguistDocumentation).ToBool()
+}
+
+func (attrs *Attributes) GetDetectable() optional.Option[bool] {
+ return attrs.Get(LinguistDetectable).ToBool()
+}
+
+func (attrs *Attributes) GetLinguistLanguage() optional.Option[string] {
+ return attrs.Get(LinguistLanguage).ToString()
+}
+
+func (attrs *Attributes) GetGitlabLanguage() optional.Option[string] {
+ attrStr := attrs.Get(GitlabLanguage).ToString()
+ if attrStr.Has() {
+ raw := attrStr.Value()
+ // gitlab-language may have additional parameters after the language
+ // ignore them and just use the main language
+ // https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type
+ if idx := strings.IndexByte(raw, '?'); idx >= 0 {
+ return optional.Some(raw[:idx])
+ }
+ }
+ return attrStr
+}
+
+func (attrs *Attributes) GetLanguage() optional.Option[string] {
+ // prefer linguist-language over gitlab-language
+ // if linguist-language is not set, use gitlab-language
+ // if both are not set, return none
+ language := attrs.GetLinguistLanguage()
+ if language.Value() == "" {
+ language = attrs.GetGitlabLanguage()
+ }
+ return language
+}
diff --git a/modules/git/attribute/attribute_test.go b/modules/git/attribute/attribute_test.go
new file mode 100644
index 0000000000..dadb5582a3
--- /dev/null
+++ b/modules/git/attribute/attribute_test.go
@@ -0,0 +1,37 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func Test_Attribute(t *testing.T) {
+ assert.Empty(t, Attribute("").ToString().Value())
+ assert.Empty(t, Attribute("unspecified").ToString().Value())
+ assert.Equal(t, "python", Attribute("python").ToString().Value())
+ assert.Equal(t, "Java", Attribute("Java").ToString().Value())
+
+ attributes := Attributes{
+ m: map[string]Attribute{
+ LinguistGenerated: "true",
+ LinguistDocumentation: "false",
+ LinguistDetectable: "set",
+ LinguistLanguage: "Python",
+ GitlabLanguage: "Java",
+ "filter": "unspecified",
+ "test": "",
+ },
+ }
+
+ assert.Empty(t, attributes.Get("test").ToString().Value())
+ assert.Empty(t, attributes.Get("filter").ToString().Value())
+ assert.Equal(t, "Python", attributes.Get(LinguistLanguage).ToString().Value())
+ assert.Equal(t, "Java", attributes.Get(GitlabLanguage).ToString().Value())
+ assert.True(t, attributes.Get(LinguistGenerated).ToBool().Value())
+ assert.False(t, attributes.Get(LinguistDocumentation).ToBool().Value())
+ assert.True(t, attributes.Get(LinguistDetectable).ToBool().Value())
+}
diff --git a/modules/git/attribute/batch.go b/modules/git/attribute/batch.go
new file mode 100644
index 0000000000..4e31fda575
--- /dev/null
+++ b/modules/git/attribute/batch.go
@@ -0,0 +1,216 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/log"
+)
+
+// BatchChecker provides a reader for check-attribute content that can be long running
+type BatchChecker struct {
+ attributesNum int
+ repo *git.Repository
+ stdinWriter *os.File
+ stdOut *nulSeparatedAttributeWriter
+ ctx context.Context
+ cancel context.CancelFunc
+ cmd *git.Command
+}
+
+// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID
+// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
+func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) (checker *BatchChecker, returnedErr error) {
+ ctx, cancel := context.WithCancel(repo.Ctx)
+ defer func() {
+ if returnedErr != nil {
+ cancel()
+ }
+ }()
+
+ cmd, envs, cleanup, err := checkAttrCommand(repo, treeish, nil, attributes)
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if returnedErr != nil {
+ cleanup()
+ }
+ }()
+
+ cmd.AddArguments("--stdin")
+
+ checker = &BatchChecker{
+ attributesNum: len(attributes),
+ repo: repo,
+ ctx: ctx,
+ cmd: cmd,
+ cancel: func() {
+ cancel()
+ cleanup()
+ },
+ }
+
+ stdinReader, stdinWriter, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ checker.stdinWriter = stdinWriter
+
+ lw := new(nulSeparatedAttributeWriter)
+ lw.attributes = make(chan attributeTriple, len(attributes))
+ lw.closed = make(chan struct{})
+ checker.stdOut = lw
+
+ go func() {
+ defer func() {
+ _ = stdinReader.Close()
+ _ = lw.Close()
+ }()
+ stdErr := new(bytes.Buffer)
+ err := cmd.Run(ctx, &git.RunOpts{
+ Env: envs,
+ Dir: repo.Path,
+ Stdin: stdinReader,
+ Stdout: lw,
+ Stderr: stdErr,
+ })
+
+ if err != nil && !git.IsErrCanceledOrKilled(err) {
+ log.Error("Attribute checker for commit %s exits with error: %v", treeish, err)
+ }
+ checker.cancel()
+ }()
+
+ return checker, nil
+}
+
+// CheckPath check attr for given path
+func (c *BatchChecker) CheckPath(path string) (rs *Attributes, err error) {
+ defer func() {
+ if err != nil && err != c.ctx.Err() {
+ log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err)
+ }
+ }()
+
+ select {
+ case <-c.ctx.Done():
+ return nil, c.ctx.Err()
+ default:
+ }
+
+ if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
+ defer c.Close()
+ return nil, err
+ }
+
+ reportTimeout := func() error {
+ stdOutClosed := false
+ select {
+ case <-c.stdOut.closed:
+ stdOutClosed = true
+ default:
+ }
+ debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path))
+ debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed)
+ if c.cmd != nil {
+ debugMsg += fmt.Sprintf(", process state: %q", c.cmd.ProcessState())
+ }
+ _ = c.Close()
+ return fmt.Errorf("CheckPath timeout: %s", debugMsg)
+ }
+
+ rs = NewAttributes()
+ for i := 0; i < c.attributesNum; i++ {
+ select {
+ case <-time.After(5 * time.Second):
+ // there is no "hang" problem now. This code is just used to catch other potential problems.
+ return nil, reportTimeout()
+ case attr, ok := <-c.stdOut.ReadAttribute():
+ if !ok {
+ return nil, c.ctx.Err()
+ }
+ rs.m[attr.Attribute] = Attribute(attr.Value)
+ case <-c.ctx.Done():
+ return nil, c.ctx.Err()
+ }
+ }
+ return rs, nil
+}
+
+func (c *BatchChecker) Close() error {
+ c.cancel()
+ err := c.stdinWriter.Close()
+ return err
+}
+
+type attributeTriple struct {
+ Filename string
+ Attribute string
+ Value string
+}
+
+type nulSeparatedAttributeWriter struct {
+ tmp []byte
+ attributes chan attributeTriple
+ closed chan struct{}
+ working attributeTriple
+ pos int
+}
+
+func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
+ l, read := len(p), 0
+
+ nulIdx := bytes.IndexByte(p, '\x00')
+ for nulIdx >= 0 {
+ wr.tmp = append(wr.tmp, p[:nulIdx]...)
+ switch wr.pos {
+ case 0:
+ wr.working = attributeTriple{
+ Filename: string(wr.tmp),
+ }
+ case 1:
+ wr.working.Attribute = string(wr.tmp)
+ case 2:
+ wr.working.Value = string(wr.tmp)
+ }
+ wr.tmp = wr.tmp[:0]
+ wr.pos++
+ if wr.pos > 2 {
+ wr.attributes <- wr.working
+ wr.pos = 0
+ }
+ read += nulIdx + 1
+ if l > read {
+ p = p[nulIdx+1:]
+ nulIdx = bytes.IndexByte(p, '\x00')
+ } else {
+ return l, nil
+ }
+ }
+ wr.tmp = append(wr.tmp, p...)
+ return l, nil
+}
+
+func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
+ return wr.attributes
+}
+
+func (wr *nulSeparatedAttributeWriter) Close() error {
+ select {
+ case <-wr.closed:
+ return nil
+ default:
+ }
+ close(wr.attributes)
+ close(wr.closed)
+ return nil
+}
diff --git a/modules/git/attribute/batch_test.go b/modules/git/attribute/batch_test.go
new file mode 100644
index 0000000000..30a3d805fe
--- /dev/null
+++ b/modules/git/attribute/batch_test.go
@@ -0,0 +1,172 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "path/filepath"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
+ wr := &nulSeparatedAttributeWriter{
+ attributes: make(chan attributeTriple, 5),
+ }
+
+ testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
+
+ n, err := wr.Write([]byte(testStr))
+
+ assert.Len(t, testStr, n)
+ assert.NoError(t, err)
+ select {
+ case attr := <-wr.ReadAttribute():
+ assert.Equal(t, ".gitignore\"\n", attr.Filename)
+ assert.Equal(t, LinguistVendored, attr.Attribute)
+ assert.Equal(t, "unspecified", attr.Value)
+ case <-time.After(100 * time.Millisecond):
+ assert.FailNow(t, "took too long to read an attribute from the list")
+ }
+ // Write a second attribute again
+ n, err = wr.Write([]byte(testStr))
+
+ assert.Len(t, testStr, n)
+ assert.NoError(t, err)
+
+ select {
+ case attr := <-wr.ReadAttribute():
+ assert.Equal(t, ".gitignore\"\n", attr.Filename)
+ assert.Equal(t, LinguistVendored, attr.Attribute)
+ assert.Equal(t, "unspecified", attr.Value)
+ case <-time.After(100 * time.Millisecond):
+ assert.FailNow(t, "took too long to read an attribute from the list")
+ }
+
+ // Write a partial attribute
+ _, err = wr.Write([]byte("incomplete-file"))
+ assert.NoError(t, err)
+ _, err = wr.Write([]byte("name\x00"))
+ assert.NoError(t, err)
+
+ select {
+ case <-wr.ReadAttribute():
+ assert.FailNow(t, "There should not be an attribute ready to read")
+ case <-time.After(100 * time.Millisecond):
+ }
+ _, err = wr.Write([]byte("attribute\x00"))
+ assert.NoError(t, err)
+ select {
+ case <-wr.ReadAttribute():
+ assert.FailNow(t, "There should not be an attribute ready to read")
+ case <-time.After(100 * time.Millisecond):
+ }
+
+ _, err = wr.Write([]byte("value\x00"))
+ assert.NoError(t, err)
+
+ attr := <-wr.ReadAttribute()
+ assert.Equal(t, "incomplete-filename", attr.Filename)
+ assert.Equal(t, "attribute", attr.Attribute)
+ assert.Equal(t, "value", attr.Value)
+
+ _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
+ assert.NoError(t, err)
+ attr = <-wr.ReadAttribute()
+ assert.NoError(t, err)
+ assert.Equal(t, attributeTriple{
+ Filename: "shouldbe.vendor",
+ Attribute: LinguistVendored,
+ Value: "set",
+ }, attr)
+ attr = <-wr.ReadAttribute()
+ assert.NoError(t, err)
+ assert.Equal(t, attributeTriple{
+ Filename: "shouldbe.vendor",
+ Attribute: LinguistGenerated,
+ Value: "unspecified",
+ }, attr)
+ attr = <-wr.ReadAttribute()
+ assert.NoError(t, err)
+ assert.Equal(t, attributeTriple{
+ Filename: "shouldbe.vendor",
+ Attribute: LinguistLanguage,
+ Value: "unspecified",
+ }, attr)
+}
+
+func expectedAttrs() *Attributes {
+ return &Attributes{
+ m: map[string]Attribute{
+ LinguistGenerated: "unspecified",
+ LinguistDetectable: "unspecified",
+ LinguistDocumentation: "unspecified",
+ LinguistVendored: "unspecified",
+ LinguistLanguage: "Python",
+ GitlabLanguage: "unspecified",
+ },
+ }
+}
+
+func Test_BatchChecker(t *testing.T) {
+ setting.AppDataPath = t.TempDir()
+ repoPath := "../tests/repos/language_stats_repo"
+ gitRepo, err := git.OpenRepository(t.Context(), repoPath)
+ require.NoError(t, err)
+ defer gitRepo.Close()
+
+ commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3"
+
+ t.Run("Create index file to run git check-attr", func(t *testing.T) {
+ defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)()
+ checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes)
+ assert.NoError(t, err)
+ defer checker.Close()
+ attributes, err := checker.CheckPath("i-am-a-python.p")
+ assert.NoError(t, err)
+ assert.Equal(t, expectedAttrs(), attributes)
+ })
+
+ // run git check-attr on work tree
+ t.Run("Run git check-attr on git work tree", func(t *testing.T) {
+ dir := filepath.Join(t.TempDir(), "test-repo")
+ err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{
+ Shared: true,
+ Branch: "master",
+ })
+ assert.NoError(t, err)
+
+ tempRepo, err := git.OpenRepository(t.Context(), dir)
+ assert.NoError(t, err)
+ defer tempRepo.Close()
+
+ checker, err := NewBatchChecker(tempRepo, "", LinguistAttributes)
+ assert.NoError(t, err)
+ defer checker.Close()
+ attributes, err := checker.CheckPath("i-am-a-python.p")
+ assert.NoError(t, err)
+ assert.Equal(t, expectedAttrs(), attributes)
+ })
+
+ if !git.DefaultFeatures().SupportCheckAttrOnBare {
+ t.Skip("git version 2.40 is required to support run check-attr on bare repo")
+ return
+ }
+
+ t.Run("Run git check-attr in bare repository", func(t *testing.T) {
+ checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes)
+ assert.NoError(t, err)
+ defer checker.Close()
+
+ attributes, err := checker.CheckPath("i-am-a-python.p")
+ assert.NoError(t, err)
+ assert.Equal(t, expectedAttrs(), attributes)
+ })
+}
diff --git a/modules/git/attribute/checker.go b/modules/git/attribute/checker.go
new file mode 100644
index 0000000000..167b31416e
--- /dev/null
+++ b/modules/git/attribute/checker.go
@@ -0,0 +1,101 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "os"
+
+ "code.gitea.io/gitea/modules/git"
+)
+
+func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) {
+ cancel := func() {}
+ envs := []string{"GIT_FLUSH=1"}
+ cmd := git.NewCommand("check-attr", "-z")
+ if len(attributes) == 0 {
+ cmd.AddArguments("--all")
+ }
+
+ // there is treeish, read from bare repo or temp index created by "read-tree"
+ if treeish != "" {
+ if git.DefaultFeatures().SupportCheckAttrOnBare {
+ cmd.AddArguments("--source")
+ cmd.AddDynamicArguments(treeish)
+ } else {
+ indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(treeish)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ cmd.AddArguments("--cached")
+ envs = append(envs,
+ "GIT_INDEX_FILE="+indexFilename,
+ "GIT_WORK_TREE="+worktree,
+ )
+ cancel = deleteTemporaryFile
+ }
+ } else {
+ // Read from existing index, in cases where the repo is bare and has an index,
+ // or the work tree contains unstaged changes that shouldn't affect the attribute check.
+ // It is caller's responsibility to add changed ".gitattributes" into the index if they want to respect the new changes.
+ cmd.AddArguments("--cached")
+ }
+
+ cmd.AddDynamicArguments(attributes...)
+ if len(filenames) > 0 {
+ cmd.AddDashesAndList(filenames...)
+ }
+ return cmd, envs, cancel, nil
+}
+
+type CheckAttributeOpts struct {
+ Filenames []string
+ Attributes []string
+}
+
+// CheckAttributes return the attributes of the given filenames and attributes in the given treeish.
+// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo
+func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]*Attributes, error) {
+ cmd, envs, cancel, err := checkAttrCommand(gitRepo, treeish, opts.Filenames, opts.Attributes)
+ if err != nil {
+ return nil, err
+ }
+ defer cancel()
+
+ stdOut := new(bytes.Buffer)
+ stdErr := new(bytes.Buffer)
+
+ if err := cmd.Run(ctx, &git.RunOpts{
+ Env: append(os.Environ(), envs...),
+ Dir: gitRepo.Path,
+ Stdout: stdOut,
+ Stderr: stdErr,
+ }); err != nil {
+ return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
+ }
+
+ fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
+ if len(fields)%3 != 1 {
+ return nil, errors.New("wrong number of fields in return from check-attr")
+ }
+
+ attributesMap := make(map[string]*Attributes)
+ for i := 0; i < (len(fields) / 3); i++ {
+ filename := string(fields[3*i])
+ attribute := string(fields[3*i+1])
+ info := string(fields[3*i+2])
+ attribute2info, ok := attributesMap[filename]
+ if !ok {
+ attribute2info = NewAttributes()
+ attributesMap[filename] = attribute2info
+ }
+ attribute2info.m[attribute] = Attribute(info)
+ }
+
+ return attributesMap, nil
+}
diff --git a/modules/git/attribute/checker_test.go b/modules/git/attribute/checker_test.go
new file mode 100644
index 0000000000..67fbda8918
--- /dev/null
+++ b/modules/git/attribute/checker_test.go
@@ -0,0 +1,84 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "path/filepath"
+ "testing"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/test"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func Test_Checker(t *testing.T) {
+ setting.AppDataPath = t.TempDir()
+ repoPath := "../tests/repos/language_stats_repo"
+ gitRepo, err := git.OpenRepository(t.Context(), repoPath)
+ require.NoError(t, err)
+ defer gitRepo.Close()
+
+ commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3"
+
+ t.Run("Create index file to run git check-attr", func(t *testing.T) {
+ defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)()
+ attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+
+ // run git check-attr on work tree
+ t.Run("Run git check-attr on git work tree", func(t *testing.T) {
+ dir := filepath.Join(t.TempDir(), "test-repo")
+ err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{
+ Shared: true,
+ Branch: "master",
+ })
+ assert.NoError(t, err)
+
+ tempRepo, err := git.OpenRepository(t.Context(), dir)
+ assert.NoError(t, err)
+ defer tempRepo.Close()
+
+ attrs, err := CheckAttributes(t.Context(), tempRepo, "", CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+
+ t.Run("Run git check-attr in bare repository using index", func(t *testing.T) {
+ attrs, err := CheckAttributes(t.Context(), gitRepo, "", CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+
+ if !git.DefaultFeatures().SupportCheckAttrOnBare {
+ t.Skip("git version 2.40 is required to support run check-attr on bare repo without using index")
+ return
+ }
+
+ t.Run("Run git check-attr in bare repository", func(t *testing.T) {
+ attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+}
diff --git a/modules/git/attribute/main_test.go b/modules/git/attribute/main_test.go
new file mode 100644
index 0000000000..df8241bfb0
--- /dev/null
+++ b/modules/git/attribute/main_test.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package attribute
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+)
+
+func testRun(m *testing.M) error {
+ gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home")
+ if err != nil {
+ return fmt.Errorf("unable to create temp dir: %w", err)
+ }
+ defer util.RemoveAll(gitHomePath)
+ setting.Git.HomePath = gitHomePath
+
+ if err = git.InitFull(context.Background()); err != nil {
+ return fmt.Errorf("failed to call Init: %w", err)
+ }
+
+ exitCode := m.Run()
+ if exitCode != 0 {
+ return fmt.Errorf("run test failed, ExitCode=%d", exitCode)
+ }
+ return nil
+}
+
+func TestMain(m *testing.M) {
+ if err := testRun(m); err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "Test failed: %v", err)
+ os.Exit(1)
+ }
+}
diff --git a/modules/git/batch.go b/modules/git/batch.go
index 3ec4f1ddcc..f9e1748b54 100644
--- a/modules/git/batch.go
+++ b/modules/git/batch.go
@@ -14,25 +14,26 @@ type Batch struct {
Writer WriteCloserError
}
-func (repo *Repository) NewBatch(ctx context.Context) (*Batch, error) {
+// NewBatch creates a new batch for the given repository, the Close must be invoked before release the batch
+func NewBatch(ctx context.Context, repoPath string) (*Batch, error) {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
- if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
+ if err := ensureValidGitRepository(ctx, repoPath); err != nil {
return nil, err
}
var batch Batch
- batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repo.Path)
+ batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repoPath)
return &batch, nil
}
-func (repo *Repository) NewBatchCheck(ctx context.Context) (*Batch, error) {
+func NewBatchCheck(ctx context.Context, repoPath string) (*Batch, error) {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
- if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
+ if err := ensureValidGitRepository(ctx, repoPath); err != nil {
return nil, err
}
var check Batch
- check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repo.Path)
+ check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repoPath)
return &check, nil
}
diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go
index 33e54fe75c..7bbab76bb8 100644
--- a/modules/git/batch_reader.go
+++ b/modules/git/batch_reader.go
@@ -29,8 +29,8 @@ type WriteCloserError interface {
// This is needed otherwise the git cat-file will hang for invalid repositories.
func ensureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{}
- err := NewCommand(ctx, "rev-parse").
- Run(&RunOpts{
+ err := NewCommand("rev-parse").
+ Run(ctx, &RunOpts{
Dir: repoPath,
Stderr: &stderr,
})
@@ -61,8 +61,8 @@ func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
go func() {
stderr := strings.Builder{}
- err := NewCommand(ctx, "cat-file", "--batch-check").
- Run(&RunOpts{
+ err := NewCommand("cat-file", "--batch-check").
+ Run(ctx, &RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
@@ -109,8 +109,8 @@ func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufi
go func() {
stderr := strings.Builder{}
- err := NewCommand(ctx, "cat-file", "--batch").
- Run(&RunOpts{
+ err := NewCommand("cat-file", "--batch").
+ Run(ctx, &RunOpts{
Dir: repoPath,
Stdin: batchStdinReader,
Stdout: batchStdoutWriter,
diff --git a/modules/git/blame.go b/modules/git/blame.go
index cad720edf4..659dec34a1 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -11,7 +11,7 @@ import (
"os"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/setting"
)
// BlamePart represents block of blame - continuous lines with one sha
@@ -29,12 +29,13 @@ type BlameReader struct {
bufferedReader *bufio.Reader
done chan error
lastSha *string
- ignoreRevsFile *string
+ ignoreRevsFile string
objectFormat ObjectFormat
+ cleanupFuncs []func()
}
func (r *BlameReader) UsesIgnoreRevs() bool {
- return r.ignoreRevsFile != nil
+ return r.ignoreRevsFile != ""
}
// NextPart returns next part of blame (sequential code lines with the same commit)
@@ -122,40 +123,49 @@ func (r *BlameReader) Close() error {
r.bufferedReader = nil
_ = r.reader.Close()
_ = r.output.Close()
- if r.ignoreRevsFile != nil {
- _ = util.Remove(*r.ignoreRevsFile)
+ for _, cleanup := range r.cleanupFuncs {
+ if cleanup != nil {
+ cleanup()
+ }
}
return err
}
// CreateBlameReader creates reader for given repository, commit and file
-func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) {
- var ignoreRevsFile *string
+func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (rd *BlameReader, err error) {
+ var ignoreRevsFileName string
+ var ignoreRevsFileCleanup func()
+ defer func() {
+ if err != nil && ignoreRevsFileCleanup != nil {
+ ignoreRevsFileCleanup()
+ }
+ }()
+
+ cmd := NewCommandNoGlobals("blame", "--porcelain")
+
if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore {
- ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit)
+ ignoreRevsFileName, ignoreRevsFileCleanup, err = tryCreateBlameIgnoreRevsFile(commit)
+ if err != nil && !IsErrNotExist(err) {
+ return nil, err
+ }
+ if ignoreRevsFileName != "" {
+ // Possible improvement: use --ignore-revs-file /dev/stdin on unix
+ // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
+ cmd.AddOptionValues("--ignore-revs-file", ignoreRevsFileName)
+ }
}
- cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain")
- if ignoreRevsFile != nil {
- // Possible improvement: use --ignore-revs-file /dev/stdin on unix
- // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
- cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile)
- }
cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file)
+
+ done := make(chan error, 1)
reader, stdout, err := os.Pipe()
if err != nil {
- if ignoreRevsFile != nil {
- _ = util.Remove(*ignoreRevsFile)
- }
return nil, err
}
-
- done := make(chan error, 1)
-
go func() {
stderr := bytes.Buffer{}
// TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close"
- err := cmd.Run(&RunOpts{
+ err := cmd.Run(ctx, &RunOpts{
UseContextTimeout: true,
Dir: repoPath,
Stdout: stdout,
@@ -169,40 +179,40 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
}()
bufferedReader := bufio.NewReader(reader)
-
return &BlameReader{
output: stdout,
reader: reader,
bufferedReader: bufferedReader,
done: done,
- ignoreRevsFile: ignoreRevsFile,
+ ignoreRevsFile: ignoreRevsFileName,
objectFormat: objectFormat,
+ cleanupFuncs: []func(){ignoreRevsFileCleanup},
}, nil
}
-func tryCreateBlameIgnoreRevsFile(commit *Commit) *string {
+func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func(), error) {
entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs")
if err != nil {
- return nil
+ return "", nil, err
}
r, err := entry.Blob().DataAsync()
if err != nil {
- return nil
+ return "", nil, err
}
defer r.Close()
- f, err := os.CreateTemp("", "gitea_git-blame-ignore-revs")
+ f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs")
if err != nil {
- return nil
+ return "", nil, err
}
-
+ filename := f.Name()
_, err = io.Copy(f, r)
_ = f.Close()
if err != nil {
- _ = util.Remove(f.Name())
- return nil
+ cleanup()
+ return "", nil, err
}
- return util.ToPointer(f.Name())
+ return filename, cleanup, nil
}
diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go
index da451f22fc..c0a97bed3b 100644
--- a/modules/git/blame_sha256_test.go
+++ b/modules/git/blame_sha256_test.go
@@ -7,11 +7,14 @@ import (
"context"
"testing"
+ "code.gitea.io/gitea/modules/setting"
+
"github.com/stretchr/testify/assert"
)
func TestReadingBlameOutputSha256(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
+ setting.AppDataPath = t.TempDir()
+ ctx, cancel := context.WithCancel(t.Context())
defer cancel()
if isGogit {
diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go
index 4220c85600..809d6fbcf7 100644
--- a/modules/git/blame_test.go
+++ b/modules/git/blame_test.go
@@ -7,11 +7,14 @@ import (
"context"
"testing"
+ "code.gitea.io/gitea/modules/setting"
+
"github.com/stretchr/testify/assert"
)
func TestReadingBlameOutput(t *testing.T) {
- ctx, cancel := context.WithCancel(context.Background())
+ setting.AppDataPath = t.TempDir()
+ ctx, cancel := context.WithCancel(t.Context())
defer cancel()
t.Run("Without .git-blame-ignore-revs", func(t *testing.T) {
diff --git a/modules/git/blob.go b/modules/git/blob.go
index bcecb42e16..40d8f44e79 100644
--- a/modules/git/blob.go
+++ b/modules/git/blob.go
@@ -7,7 +7,9 @@ package git
import (
"bytes"
"encoding/base64"
+ "errors"
"io"
+ "strings"
"code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util"
@@ -20,22 +22,28 @@ func (b *Blob) Name() string {
return b.name
}
-// GetBlobContent Gets the limited content of the blob as raw text
-func (b *Blob) GetBlobContent(limit int64) (string, error) {
+// GetBlobBytes Gets the limited content of the blob
+func (b *Blob) GetBlobBytes(limit int64) ([]byte, error) {
if limit <= 0 {
- return "", nil
+ return nil, nil
}
dataRc, err := b.DataAsync()
if err != nil {
- return "", err
+ return nil, err
}
defer dataRc.Close()
- buf, err := util.ReadWithLimit(dataRc, int(limit))
+ return util.ReadWithLimit(dataRc, int(limit))
+}
+
+// GetBlobContent Gets the limited content of the blob as raw text
+func (b *Blob) GetBlobContent(limit int64) (string, error) {
+ buf, err := b.GetBlobBytes(limit)
return string(buf), err
}
-// GetBlobLineCount gets line count of the blob
-func (b *Blob) GetBlobLineCount() (int, error) {
+// GetBlobLineCount gets line count of the blob.
+// It will also try to write the content to w if it's not nil, then we could pre-fetch the content without reading it again.
+func (b *Blob) GetBlobLineCount(w io.Writer) (int, error) {
reader, err := b.DataAsync()
if err != nil {
return 0, err
@@ -44,59 +52,61 @@ func (b *Blob) GetBlobLineCount() (int, error) {
buf := make([]byte, 32*1024)
count := 1
lineSep := []byte{'\n'}
-
- c, err := reader.Read(buf)
- if c == 0 && err == io.EOF {
- return 0, nil
- }
for {
+ c, err := reader.Read(buf)
+ if w != nil {
+ if _, err := w.Write(buf[:c]); err != nil {
+ return count, err
+ }
+ }
count += bytes.Count(buf[:c], lineSep)
switch {
- case err == io.EOF:
+ case errors.Is(err, io.EOF):
return count, nil
case err != nil:
return count, err
}
- c, err = reader.Read(buf)
}
}
-// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string
-func (b *Blob) GetBlobContentBase64() (string, error) {
+// GetBlobContentBase64 Reads the content of the blob with a base64 encoding and returns the encoded string
+func (b *Blob) GetBlobContentBase64(originContent *strings.Builder) (string, error) {
dataRc, err := b.DataAsync()
if err != nil {
return "", err
}
defer dataRc.Close()
- pr, pw := io.Pipe()
- encoder := base64.NewEncoder(base64.StdEncoding, pw)
-
- go func() {
- _, err := io.Copy(encoder, dataRc)
- _ = encoder.Close()
-
- if err != nil {
- _ = pw.CloseWithError(err)
- } else {
- _ = pw.Close()
+ base64buf := &strings.Builder{}
+ encoder := base64.NewEncoder(base64.StdEncoding, base64buf)
+ buf := make([]byte, 32*1024)
+loop:
+ for {
+ n, err := dataRc.Read(buf)
+ if n > 0 {
+ if originContent != nil {
+ _, _ = originContent.Write(buf[:n])
+ }
+ if _, err := encoder.Write(buf[:n]); err != nil {
+ return "", err
+ }
+ }
+ switch {
+ case errors.Is(err, io.EOF):
+ break loop
+ case err != nil:
+ return "", err
}
- }()
-
- out, err := io.ReadAll(pr)
- if err != nil {
- return "", err
}
- return string(out), nil
+ _ = encoder.Close()
+ return base64buf.String(), nil
}
// GuessContentType guesses the content type of the blob.
func (b *Blob) GuessContentType() (typesniffer.SniffedType, error) {
- r, err := b.DataAsync()
+ buf, err := b.GetBlobBytes(typesniffer.SniffContentSize)
if err != nil {
return typesniffer.SniffedType{}, err
}
- defer r.Close()
-
- return typesniffer.DetectContentTypeFromReader(r)
+ return typesniffer.DetectContentType(buf), nil
}
diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go
index d0804350ed..f21e8d146d 100644
--- a/modules/git/blob_test.go
+++ b/modules/git/blob_test.go
@@ -47,7 +47,7 @@ func Benchmark_Blob_Data(b *testing.B) {
b.Fatal(err)
}
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
r, err := testBlob.DataAsync()
if err != nil {
b.Fatal(err)
diff --git a/modules/git/cmdverb.go b/modules/git/cmdverb.go
new file mode 100644
index 0000000000..3d6f4ae0c6
--- /dev/null
+++ b/modules/git/cmdverb.go
@@ -0,0 +1,36 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+const (
+ CmdVerbUploadPack = "git-upload-pack"
+ CmdVerbUploadArchive = "git-upload-archive"
+ CmdVerbReceivePack = "git-receive-pack"
+ CmdVerbLfsAuthenticate = "git-lfs-authenticate"
+ CmdVerbLfsTransfer = "git-lfs-transfer"
+
+ CmdSubVerbLfsUpload = "upload"
+ CmdSubVerbLfsDownload = "download"
+)
+
+func IsAllowedVerbForServe(verb string) bool {
+ switch verb {
+ case CmdVerbUploadPack,
+ CmdVerbUploadArchive,
+ CmdVerbReceivePack,
+ CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
+
+func IsAllowedVerbForServeLfs(verb string) bool {
+ switch verb {
+ case CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
diff --git a/modules/git/command.go b/modules/git/command.go
index 2584e3cc57..22f1d02339 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -18,6 +18,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
+ "code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/util"
@@ -43,9 +44,10 @@ const DefaultLocale = "C"
type Command struct {
prog string
args []string
- parentContext context.Context
globalArgsLength int
brokenArgs []string
+ cmd *exec.Cmd // for debug purpose only
+ configArgs []string
}
func logArgSanitize(arg string) string {
@@ -54,7 +56,7 @@ func logArgSanitize(arg string) string {
} else if filepath.IsAbs(arg) {
base := filepath.Base(arg)
dir := filepath.Dir(arg)
- return filepath.Join(filepath.Base(dir), base)
+ return ".../" + filepath.Join(filepath.Base(dir), base)
}
return arg
}
@@ -79,9 +81,16 @@ func (c *Command) LogString() string {
return strings.Join(a, " ")
}
+func (c *Command) ProcessState() string {
+ if c.cmd == nil {
+ return ""
+ }
+ return c.cmd.ProcessState.String()
+}
+
// NewCommand creates and returns a new Git Command based on given command and arguments.
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
-func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command {
+func NewCommand(args ...internal.CmdArg) *Command {
// Make an explicit copy of globalCommandArgs, otherwise append might overwrite it
cargs := make([]string, 0, len(globalCommandArgs)+len(args))
for _, arg := range globalCommandArgs {
@@ -93,31 +102,23 @@ func NewCommand(ctx context.Context, args ...internal.CmdArg) *Command {
return &Command{
prog: GitExecutable,
args: cargs,
- parentContext: ctx,
globalArgsLength: len(globalCommandArgs),
}
}
-// NewCommandContextNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
+// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specified args and don't use global command args
// Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead.
-func NewCommandContextNoGlobals(ctx context.Context, args ...internal.CmdArg) *Command {
+func NewCommandNoGlobals(args ...internal.CmdArg) *Command {
cargs := make([]string, 0, len(args))
for _, arg := range args {
cargs = append(cargs, string(arg))
}
return &Command{
- prog: GitExecutable,
- args: cargs,
- parentContext: ctx,
+ prog: GitExecutable,
+ args: cargs,
}
}
-// SetParentContext sets the parent context for this command
-func (c *Command) SetParentContext(ctx context.Context) *Command {
- c.parentContext = ctx
- return c
-}
-
// isSafeArgumentValue checks if the argument is safe to be used as a value (not an option)
func isSafeArgumentValue(s string) bool {
return s == "" || s[0] != '-'
@@ -196,6 +197,16 @@ func (c *Command) AddDashesAndList(list ...string) *Command {
return c
}
+func (c *Command) AddConfig(key, value string) *Command {
+ kv := key + "=" + value
+ if !isSafeArgumentValue(kv) {
+ c.brokenArgs = append(c.brokenArgs, key)
+ } else {
+ c.configArgs = append(c.configArgs, "-c", kv)
+ }
+ return c
+}
+
// ToTrustedCmdArgs converts a list of strings (trusted as argument) to TrustedCmdArgs
// In most cases, it shouldn't be used. Use NewCommand().AddXxx() function instead
func ToTrustedCmdArgs(args []string) TrustedCmdArgs {
@@ -276,11 +287,11 @@ func CommonCmdServEnvs() []string {
var ErrBrokenCommand = errors.New("git command is broken")
// Run runs the command with the RunOpts
-func (c *Command) Run(opts *RunOpts) error {
- return c.run(1, opts)
+func (c *Command) Run(ctx context.Context, opts *RunOpts) error {
+ return c.run(ctx, 1, opts)
}
-func (c *Command) run(skip int, opts *RunOpts) error {
+func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
if len(c.brokenArgs) != 0 {
log.Error("git command is broken: %s, broken args: %s", c.LogString(), strings.Join(c.brokenArgs, " "))
return ErrBrokenCommand
@@ -295,29 +306,34 @@ func (c *Command) run(skip int, opts *RunOpts) error {
timeout = defaultCommandExecutionTimeout
}
- var desc string
+ cmdLogString := c.LogString()
callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */)
if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
callerInfo = callerInfo[pos+1:]
}
// these logs are for debugging purposes only, so no guarantee of correctness or stability
- desc = fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), c.LogString())
+ desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString)
log.Debug("git.Command: %s", desc)
- var ctx context.Context
+ _, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanGitRun)
+ defer span.End()
+ span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo)
+ span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)
+
var cancel context.CancelFunc
var finished context.CancelFunc
if opts.UseContextTimeout {
- ctx, cancel, finished = process.GetManager().AddContext(c.parentContext, desc)
+ ctx, cancel, finished = process.GetManager().AddContext(ctx, desc)
} else {
- ctx, cancel, finished = process.GetManager().AddContextTimeout(c.parentContext, timeout, desc)
+ ctx, cancel, finished = process.GetManager().AddContextTimeout(ctx, timeout, desc)
}
defer finished()
startTime := time.Now()
- cmd := exec.CommandContext(ctx, c.prog, c.args...)
+ cmd := exec.CommandContext(ctx, c.prog, append(c.configArgs, c.args...)...)
+ c.cmd = cmd // for debug purpose only
if opts.Env == nil {
cmd.Env = os.Environ()
} else {
@@ -352,9 +368,10 @@ func (c *Command) run(skip int, opts *RunOpts) error {
// We need to check if the context is canceled by the program on Windows.
// This is because Windows does not have signal checking when terminating the process.
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
+ // `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
if runtime.GOOS == "windows" &&
err != nil &&
- err.Error() == "" &&
+ (err.Error() == "" || err.Error() == "exit status 1") &&
cmd.ProcessState.ExitCode() == 1 &&
ctx.Err() == context.Canceled {
return ctx.Err()
@@ -404,8 +421,8 @@ func IsErrorExitCode(err error, code int) bool {
}
// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
-func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
- stdoutBytes, stderrBytes, err := c.runStdBytes(opts)
+func (c *Command) RunStdString(ctx context.Context, opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
+ stdoutBytes, stderrBytes, err := c.runStdBytes(ctx, opts)
stdout = util.UnsafeBytesToString(stdoutBytes)
stderr = util.UnsafeBytesToString(stderrBytes)
if err != nil {
@@ -416,11 +433,11 @@ func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr Run
}
// RunStdBytes runs the command with options and returns stdout/stderr as bytes. and store stderr to returned error (err combined with stderr).
-func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
- return c.runStdBytes(opts)
+func (c *Command) RunStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
+ return c.runStdBytes(ctx, opts)
}
-func (c *Command) runStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
+func (c *Command) runStdBytes(ctx context.Context, opts *RunOpts) (stdout, stderr []byte, runErr RunStdError) {
if opts == nil {
opts = &RunOpts{}
}
@@ -443,7 +460,7 @@ func (c *Command) runStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
PipelineFunc: opts.PipelineFunc,
}
- err := c.run(2, newOpts)
+ err := c.run(ctx, 2, newOpts)
stderr = stderrBuf.Bytes()
if err != nil {
return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)}
diff --git a/modules/git/command_race_test.go b/modules/git/command_race_test.go
index f567406822..a6aa3a1580 100644
--- a/modules/git/command_race_test.go
+++ b/modules/git/command_race_test.go
@@ -15,9 +15,9 @@ func TestRunWithContextNoTimeout(t *testing.T) {
maxLoops := 10
// 'git --version' does not block so it must be finished before the timeout triggered.
- cmd := NewCommand(context.Background(), "--version")
+ cmd := NewCommand("--version")
for i := 0; i < maxLoops; i++ {
- if err := cmd.Run(&RunOpts{}); err != nil {
+ if err := cmd.Run(t.Context(), &RunOpts{}); err != nil {
t.Fatal(err)
}
}
@@ -27,9 +27,9 @@ func TestRunWithContextTimeout(t *testing.T) {
maxLoops := 10
// 'git hash-object --stdin' blocks on stdin so we can have the timeout triggered.
- cmd := NewCommand(context.Background(), "hash-object", "--stdin")
+ cmd := NewCommand("hash-object", "--stdin")
for i := 0; i < maxLoops; i++ {
- if err := cmd.Run(&RunOpts{Timeout: 1 * time.Millisecond}); err != nil {
+ if err := cmd.Run(t.Context(), &RunOpts{Timeout: 1 * time.Millisecond}); err != nil {
if err != context.DeadlineExceeded {
t.Fatalf("Testing %d/%d: %v", i, maxLoops, err)
}
diff --git a/modules/git/command_test.go b/modules/git/command_test.go
index 0823afd7f7..eb112707e7 100644
--- a/modules/git/command_test.go
+++ b/modules/git/command_test.go
@@ -4,21 +4,20 @@
package git
import (
- "context"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRunWithContextStd(t *testing.T) {
- cmd := NewCommand(context.Background(), "--version")
- stdout, stderr, err := cmd.RunStdString(&RunOpts{})
+ cmd := NewCommand("--version")
+ stdout, stderr, err := cmd.RunStdString(t.Context(), &RunOpts{})
assert.NoError(t, err)
assert.Empty(t, stderr)
assert.Contains(t, stdout, "git version")
- cmd = NewCommand(context.Background(), "--no-such-arg")
- stdout, stderr, err = cmd.RunStdString(&RunOpts{})
+ cmd = NewCommand("--no-such-arg")
+ stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{})
if assert.Error(t, err) {
assert.Equal(t, stderr, err.Stderr())
assert.Contains(t, err.Stderr(), "unknown option:")
@@ -26,17 +25,17 @@ func TestRunWithContextStd(t *testing.T) {
assert.Empty(t, stdout)
}
- cmd = NewCommand(context.Background())
+ cmd = NewCommand()
cmd.AddDynamicArguments("-test")
- assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
+ assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand)
- cmd = NewCommand(context.Background())
+ cmd = NewCommand()
cmd.AddDynamicArguments("--test")
- assert.ErrorIs(t, cmd.Run(&RunOpts{}), ErrBrokenCommand)
+ assert.ErrorIs(t, cmd.Run(t.Context(), &RunOpts{}), ErrBrokenCommand)
subCmd := "version"
- cmd = NewCommand(context.Background()).AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production
- stdout, stderr, err = cmd.RunStdString(&RunOpts{})
+ cmd = NewCommand().AddDynamicArguments(subCmd) // for test purpose only, the sub-command should never be dynamic for production
+ stdout, stderr, err = cmd.RunStdString(t.Context(), &RunOpts{})
assert.NoError(t, err)
assert.Empty(t, stderr)
assert.Contains(t, stdout, "git version")
@@ -54,9 +53,9 @@ func TestGitArgument(t *testing.T) {
}
func TestCommandString(t *testing.T) {
- cmd := NewCommandContextNoGlobals(context.Background(), "a", "-m msg", "it's a test", `say "hello"`)
- assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString())
+ cmd := NewCommandNoGlobals("a", "-m msg", "it's a test", `say "hello"`)
+ assert.Equal(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString())
- cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/", "/root/dir-a/dir-b")
- assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" dir-a/dir-b`, cmd.LogString())
+ cmd = NewCommandNoGlobals("url: https://a:b@c/", "/root/dir-a/dir-b")
+ assert.Equal(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
}
diff --git a/modules/git/commit.go b/modules/git/commit.go
index 0a50ba4356..ed4876e7b3 100644
--- a/modules/git/commit.go
+++ b/modules/git/commit.go
@@ -20,7 +20,8 @@ import (
// Commit represents a git commit.
type Commit struct {
- Tree
+ Tree // FIXME: bad design, this field can be nil if the commit is from "last commit cache"
+
ID ObjectID // The ID of this commit object
Author *Signature
Committer *Signature
@@ -34,7 +35,7 @@ type Commit struct {
// CommitSignature represents a git commit signature part.
type CommitSignature struct {
Signature string
- Payload string // TODO check if can be reconstruct from the rest of commit information to not have duplicate data
+ Payload string
}
// Message returns the commit message. Same as retrieving CommitMessage directly.
@@ -91,12 +92,12 @@ func AddChanges(repoPath string, all bool, files ...string) error {
// AddChangesWithArgs marks local changes to be ready for commit.
func AddChangesWithArgs(repoPath string, globalArgs TrustedCmdArgs, all bool, files ...string) error {
- cmd := NewCommandContextNoGlobals(DefaultContext, globalArgs...).AddArguments("add")
+ cmd := NewCommandNoGlobals(globalArgs...).AddArguments("add")
if all {
cmd.AddArguments("--all")
}
cmd.AddDashesAndList(files...)
- _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ _, _, err := cmd.RunStdString(DefaultContext, &RunOpts{Dir: repoPath})
return err
}
@@ -118,7 +119,7 @@ func CommitChanges(repoPath string, opts CommitChangesOptions) error {
// CommitChangesWithArgs commits local changes with given committer, author and message.
// If author is nil, it will be the same as committer.
func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChangesOptions) error {
- cmd := NewCommandContextNoGlobals(DefaultContext, args...)
+ cmd := NewCommandNoGlobals(args...)
if opts.Committer != nil {
cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name)
cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email)
@@ -133,7 +134,7 @@ func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChan
}
cmd.AddOptionFormat("--message=%s", opts.Message)
- _, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ _, _, err := cmd.RunStdString(DefaultContext, &RunOpts{Dir: repoPath})
// No stderr but exit status 1 means nothing to commit.
if err != nil && err.Error() == "exit status 1" {
return nil
@@ -143,7 +144,7 @@ func CommitChangesWithArgs(repoPath string, args TrustedCmdArgs, opts CommitChan
// AllCommitsCount returns count of all commits in repository
func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, files ...string) (int64, error) {
- cmd := NewCommand(ctx, "rev-list")
+ cmd := NewCommand("rev-list")
if hidePRRefs {
cmd.AddArguments("--exclude=" + PullPrefix + "*")
}
@@ -152,7 +153,7 @@ func AllCommitsCount(ctx context.Context, repoPath string, hidePRRefs bool, file
cmd.AddDashesAndList(files...)
}
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return 0, err
}
@@ -166,11 +167,13 @@ type CommitsCountOptions struct {
Not string
Revision []string
RelPath []string
+ Since string
+ Until string
}
// CommitsCount returns number of total commits of until given revision.
func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error) {
- cmd := NewCommand(ctx, "rev-list", "--count")
+ cmd := NewCommand("rev-list", "--count")
cmd.AddDynamicArguments(opts.Revision...)
@@ -182,7 +185,7 @@ func CommitsCount(ctx context.Context, opts CommitsCountOptions) (int64, error)
cmd.AddDashesAndList(opts.RelPath...)
}
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: opts.RepoPath})
+ stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: opts.RepoPath})
if err != nil {
return 0, err
}
@@ -199,8 +202,8 @@ func (c *Commit) CommitsCount() (int64, error) {
}
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
-func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
- return c.repo.commitsByRange(c.ID, page, pageSize, not)
+func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
+ return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
}
// CommitsBefore returns all the commits before current revision
@@ -217,7 +220,7 @@ func (c *Commit) HasPreviousCommit(objectID ObjectID) (bool, error) {
return false, nil
}
- _, _, err := NewCommand(c.repo.Ctx, "merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(&RunOpts{Dir: c.repo.Path})
+ _, _, err := NewCommand("merge-base", "--is-ancestor").AddDynamicArguments(that, this).RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path})
if err == nil {
return true, nil
}
@@ -275,8 +278,8 @@ func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommits
var keywords, authors, committers []string
var after, before string
- fields := strings.Fields(searchString)
- for _, k := range fields {
+ fields := strings.FieldsSeq(searchString)
+ for k := range fields {
switch {
case strings.HasPrefix(k, "author:"):
authors = append(authors, strings.TrimPrefix(k, "author:"))
@@ -358,12 +361,12 @@ func (c *Commit) GetFileContent(filename string, limit int) (string, error) {
// GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only')
func (c *Commit) GetBranchName() (string, error) {
- cmd := NewCommand(c.repo.Ctx, "name-rev")
+ cmd := NewCommand("name-rev")
if DefaultFeatures().CheckVersionAtLeast("2.13.0") {
cmd.AddArguments("--exclude", "refs/tags/*")
}
cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String())
- data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path})
+ data, _, err := cmd.RunStdString(c.repo.Ctx, &RunOpts{Dir: c.repo.Path})
if err != nil {
// handle special case where git can not describe commit
if strings.Contains(err.Error(), "cannot describe") {
@@ -441,7 +444,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
}()
stderr := new(bytes.Buffer)
- err := NewCommand(ctx, "log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(&RunOpts{
+ err := NewCommand("log", "--name-status", "-m", "--pretty=format:", "--first-parent", "--no-renames", "-z", "-1").AddDynamicArguments(commitID).Run(ctx, &RunOpts{
Dir: repoPath,
Stdout: w,
Stderr: stderr,
@@ -457,7 +460,7 @@ func GetCommitFileStatus(ctx context.Context, repoPath, commitID string) (*Commi
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
func GetFullCommitID(ctx context.Context, repoPath, shortID string) (string, error) {
- commitID, _, err := NewCommand(ctx, "rev-parse").AddDynamicArguments(shortID).RunStdString(&RunOpts{Dir: repoPath})
+ commitID, _, err := NewCommand("rev-parse").AddDynamicArguments(shortID).RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
if strings.Contains(err.Error(), "exit status 128") {
return "", ErrNotExist{shortID, ""}
diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go
index ef2df0b133..1b45fc8a6c 100644
--- a/modules/git/commit_info_nogogit.go
+++ b/modules/git/commit_info_nogogit.go
@@ -7,8 +7,7 @@ package git
import (
"context"
- "fmt"
- "io"
+ "maps"
"path"
"sort"
@@ -40,9 +39,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
return nil, nil, err
}
- for pth, found := range commits {
- revs[pth] = found
- }
+ maps.Copy(revs, commits)
}
} else {
sort.Strings(entryPaths)
@@ -65,7 +62,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
log.Debug("missing commit for %s", entry.Name())
}
- // If the entry if a submodule add a submodule file for this
+ // If the entry is a submodule add a submodule file for this
if entry.IsSubModule() {
subModuleURL := ""
var fullPath string
@@ -85,8 +82,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
}
// Retrieve the commit for the treePath itself (see above). We basically
- // get it for free during the tree traversal and it's used for listing
- // pages to display information about newest commit for a given path.
+ // get it for free during the tree traversal, and it's used for listing
+ // pages to display information about the newest commit for a given path.
var treeCommit *Commit
var ok bool
if treePath == "" {
@@ -124,48 +121,25 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
return nil, err
}
- batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
- if err != nil {
- return nil, err
- }
- defer cancel()
-
commitsMap := map[string]*Commit{}
commitsMap[commit.ID.String()] = commit
commitCommits := map[string]*Commit{}
for path, commitID := range revs {
- c, ok := commitsMap[commitID]
- if ok {
- commitCommits[path] = c
+ if len(commitID) == 0 {
continue
}
- if len(commitID) == 0 {
+ c, ok := commitsMap[commitID]
+ if ok {
+ commitCommits[path] = c
continue
}
- _, err := batchStdinWriter.Write([]byte(commitID + "\n"))
- if err != nil {
- return nil, err
- }
- _, typ, size, err := ReadBatchLine(batchReader)
+ c, err := commit.repo.GetCommit(commitID) // Ensure the commit exists in the repository
if err != nil {
return nil, err
}
- if typ != "commit" {
- if err := DiscardFull(batchReader, size+1); err != nil {
- return nil, err
- }
- return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID)
- }
- c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size))
- if err != nil {
- return nil, err
- }
- if _, err := batchReader.Discard(1); err != nil {
- return nil, err
- }
commitCommits[path] = c
}
diff --git a/modules/git/commit_info_test.go b/modules/git/commit_info_test.go
index 1e331fac00..ba518ab245 100644
--- a/modules/git/commit_info_test.go
+++ b/modules/git/commit_info_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"path/filepath"
"testing"
"time"
@@ -83,7 +82,7 @@ func testGetCommitsInfo(t *testing.T, repo1 *Repository) {
}
// FIXME: Context.TODO() - if graceful has started we should use its Shutdown context otherwise use install signals in TestMain.
- commitsInfo, treeCommit, err := entries.GetCommitsInfo(context.TODO(), commit, testCase.Path)
+ commitsInfo, treeCommit, err := entries.GetCommitsInfo(t.Context(), commit, testCase.Path)
assert.NoError(t, err, "Unable to get commit information for entries of subtree: %s in commit: %s from testcase due to error: %v", testCase.Path, testCase.CommitID, err)
if err != nil {
t.FailNow()
@@ -159,8 +158,8 @@ func BenchmarkEntries_GetCommitsInfo(b *testing.B) {
entries.Sort()
b.ResetTimer()
b.Run(benchmark.name, func(b *testing.B) {
- for i := 0; i < b.N; i++ {
- _, _, err := entries.GetCommitsInfo(context.Background(), commit, "")
+ for b.Loop() {
+ _, _, err := entries.GetCommitsInfo(b.Context(), commit, "")
if err != nil {
b.Fatal(err)
}
diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go
index 228bbaf314..eb8f4c6322 100644
--- a/modules/git/commit_reader.go
+++ b/modules/git/commit_reader.go
@@ -6,10 +6,44 @@ package git
import (
"bufio"
"bytes"
+ "fmt"
"io"
- "strings"
)
+const (
+ commitHeaderGpgsig = "gpgsig"
+ commitHeaderGpgsigSha256 = "gpgsig-sha256"
+)
+
+func assignCommitFields(gitRepo *Repository, commit *Commit, headerKey string, headerValue []byte) error {
+ if len(headerValue) > 0 && headerValue[len(headerValue)-1] == '\n' {
+ headerValue = headerValue[:len(headerValue)-1] // remove trailing newline
+ }
+ switch headerKey {
+ case "tree":
+ objID, err := NewIDFromString(string(headerValue))
+ if err != nil {
+ return fmt.Errorf("invalid tree ID %q: %w", string(headerValue), err)
+ }
+ commit.Tree = *NewTree(gitRepo, objID)
+ case "parent":
+ objID, err := NewIDFromString(string(headerValue))
+ if err != nil {
+ return fmt.Errorf("invalid parent ID %q: %w", string(headerValue), err)
+ }
+ commit.Parents = append(commit.Parents, objID)
+ case "author":
+ commit.Author.Decode(headerValue)
+ case "committer":
+ commit.Committer.Decode(headerValue)
+ case commitHeaderGpgsig, commitHeaderGpgsigSha256:
+ // if there are duplicate "gpgsig" and "gpgsig-sha256" headers, then the signature must have already been invalid
+ // so we don't need to handle duplicate headers here
+ commit.Signature = &CommitSignature{Signature: string(headerValue)}
+ }
+ return nil
+}
+
// CommitFromReader will generate a Commit from a provided reader
// We need this to interpret commits from cat-file or cat-file --batch
//
@@ -21,90 +55,46 @@ func CommitFromReader(gitRepo *Repository, objectID ObjectID, reader io.Reader)
Committer: &Signature{},
}
- payloadSB := new(strings.Builder)
- signatureSB := new(strings.Builder)
- messageSB := new(strings.Builder)
- message := false
- pgpsig := false
-
- bufReader, ok := reader.(*bufio.Reader)
- if !ok {
- bufReader = bufio.NewReader(reader)
- }
-
-readLoop:
+ bufReader := bufio.NewReader(reader)
+ inHeader := true
+ var payloadSB, messageSB bytes.Buffer
+ var headerKey string
+ var headerValue []byte
for {
line, err := bufReader.ReadBytes('\n')
- if err != nil {
- if err == io.EOF {
- if message {
- _, _ = messageSB.Write(line)
- }
- _, _ = payloadSB.Write(line)
- break readLoop
- }
- return nil, err
+ if err != nil && err != io.EOF {
+ return nil, fmt.Errorf("unable to read commit %q: %w", objectID.String(), err)
}
- if pgpsig {
- if len(line) > 0 && line[0] == ' ' {
- _, _ = signatureSB.Write(line[1:])
- continue
- }
- pgpsig = false
+ if len(line) == 0 {
+ break
}
- if !message {
- // This is probably not correct but is copied from go-gits interpretation...
- trimmed := bytes.TrimSpace(line)
- if len(trimmed) == 0 {
- message = true
- _, _ = payloadSB.Write(line)
- continue
- }
-
- split := bytes.SplitN(trimmed, []byte{' '}, 2)
- var data []byte
- if len(split) > 1 {
- data = split[1]
+ if inHeader {
+ inHeader = !(len(line) == 1 && line[0] == '\n') // still in header if line is not just a newline
+ k, v, _ := bytes.Cut(line, []byte{' '})
+ if len(k) != 0 || !inHeader {
+ if headerKey != "" {
+ if err = assignCommitFields(gitRepo, commit, headerKey, headerValue); err != nil {
+ return nil, fmt.Errorf("unable to parse commit %q: %w", objectID.String(), err)
+ }
+ }
+ headerKey = string(k) // it also resets the headerValue to empty string if not inHeader
+ headerValue = v
+ } else {
+ headerValue = append(headerValue, v...)
}
-
- switch string(split[0]) {
- case "tree":
- commit.Tree = *NewTree(gitRepo, MustIDFromString(string(data)))
+ if headerKey != commitHeaderGpgsig && headerKey != commitHeaderGpgsigSha256 {
_, _ = payloadSB.Write(line)
- case "parent":
- commit.Parents = append(commit.Parents, MustIDFromString(string(data)))
- _, _ = payloadSB.Write(line)
- case "author":
- commit.Author = &Signature{}
- commit.Author.Decode(data)
- _, _ = payloadSB.Write(line)
- case "committer":
- commit.Committer = &Signature{}
- commit.Committer.Decode(data)
- _, _ = payloadSB.Write(line)
- case "encoding":
- _, _ = payloadSB.Write(line)
- case "gpgsig":
- fallthrough
- case "gpgsig-sha256": // FIXME: no intertop, so only 1 exists at present.
- _, _ = signatureSB.Write(data)
- _ = signatureSB.WriteByte('\n')
- pgpsig = true
}
} else {
_, _ = messageSB.Write(line)
_, _ = payloadSB.Write(line)
}
}
+
commit.CommitMessage = messageSB.String()
- commit.Signature = &CommitSignature{
- Signature: signatureSB.String(),
- Payload: payloadSB.String(),
- }
- if len(commit.Signature.Signature) == 0 {
- commit.Signature = nil
+ if commit.Signature != nil {
+ commit.Signature.Payload = payloadSB.String()
}
-
return commit, nil
}
diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go
index f6ca83c9ed..97ccecdacc 100644
--- a/modules/git/commit_sha256_test.go
+++ b/modules/git/commit_sha256_test.go
@@ -60,8 +60,7 @@ func TestGetFullCommitIDErrorSha256(t *testing.T) {
}
func TestCommitFromReaderSha256(t *testing.T) {
- commitString := `9433b2a62b964c17a4485ae180f45f595d3e69d31b786087775e28c6b6399df0 commit 1114
-tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+ commitString := `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 1698676906 +0100
@@ -97,7 +96,7 @@ signed commit`
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
+ assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz
dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd
@@ -112,21 +111,20 @@ VAEUo6ecdDxSpyt2naeg9pKus/BRi7P6g4B1hkk/zZstUX/QP4IQuAJbXjkvsC+X
HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR
8NCJUL2Nwj0ywKIavC99Ipkb8AsFwpVT6U6effs6
=xybZ
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
- assert.EqualValues(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
+ assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e
parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8
author Adam Majer <amajer@suse.de> 1698676906 +0100
committer Adam Majer <amajer@suse.de> 1698676906 +0100
signed commit`, commitFromReader.Signature.Payload)
- assert.EqualValues(t, "Adam Majer <amajer@suse.de>", commitFromReader.Author.String())
+ assert.Equal(t, "Adam Majer <amajer@suse.de>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
- assert.EqualValues(t, commitFromReader, commitFromReader2)
+ assert.Equal(t, commitFromReader, commitFromReader2)
}
func TestHasPreviousCommitSha256(t *testing.T) {
diff --git a/modules/git/commit_submodule_file.go b/modules/git/commit_submodule_file.go
index 2ac744fbf6..729401f752 100644
--- a/modules/git/commit_submodule_file.go
+++ b/modules/git/commit_submodule_file.go
@@ -46,9 +46,9 @@ func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID
if len(optCommitID) == 2 {
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
} else if len(optCommitID) == 1 {
- commitLink = sf.repoLink + "/commit/" + optCommitID[0]
+ commitLink = sf.repoLink + "/tree/" + optCommitID[0]
} else {
- commitLink = sf.repoLink + "/commit/" + sf.refID
+ commitLink = sf.repoLink + "/tree/" + sf.refID
}
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
}
diff --git a/modules/git/commit_submodule_file_test.go b/modules/git/commit_submodule_file_test.go
index 4b5b767612..6581fa8712 100644
--- a/modules/git/commit_submodule_file_test.go
+++ b/modules/git/commit_submodule_file_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"testing"
"github.com/stretchr/testify/assert"
@@ -13,18 +12,18 @@ import (
func TestCommitSubmoduleLink(t *testing.T) {
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
- wl := sf.SubmoduleWebLink(context.Background())
+ wl := sf.SubmoduleWebLink(t.Context())
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
- assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink)
+ assert.Equal(t, "https://github.com/user/repo/tree/aaaa", wl.CommitWebLink)
- wl = sf.SubmoduleWebLink(context.Background(), "1111")
+ wl = sf.SubmoduleWebLink(t.Context(), "1111")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
- assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink)
+ assert.Equal(t, "https://github.com/user/repo/tree/1111", wl.CommitWebLink)
- wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222")
+ wl = sf.SubmoduleWebLink(t.Context(), "1111", "2222")
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
- wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(context.Background())
+ wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(t.Context())
assert.Nil(t, wl)
}
diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go
index 9560c2cd94..81fb91dfc6 100644
--- a/modules/git/commit_test.go
+++ b/modules/git/commit_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"os"
"path/filepath"
"strings"
@@ -60,8 +59,7 @@ func TestGetFullCommitIDError(t *testing.T) {
}
func TestCommitFromReader(t *testing.T) {
- commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
-tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
+ commitString := `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200
committer silverwind <me@silverwind.io> 1563741793 +0200
@@ -94,7 +92,7 @@ empty commit`
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
+ assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
iQIzBAABCAAdFiEEWPb2jX6FS2mqyJRQLmK0HJOGlEMFAl00zmEACgkQLmK0HJOG
lEMDFBAAhQKKqLD1VICygJMEB8t1gBmNLgvziOLfpX4KPWdPtBk3v/QJ7OrfMrVK
@@ -109,26 +107,24 @@ sD53z/f0J+We4VZjY+pidvA9BGZPFVdR3wd3xGs8/oH6UWaLJAMGkLG6dDb3qDLm
mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i
1pxsB104XLWjQHTjr1JtiaBQEwFh9r2OKTcpvaLcbNtYpo7CzOs=
=FRsO
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
- assert.EqualValues(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
+ assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930
parent 37991dec2c8e592043f47155ce4808d4580f9123
author silverwind <me@silverwind.io> 1563741793 +0200
committer silverwind <me@silverwind.io> 1563741793 +0200
empty commit`, commitFromReader.Signature.Payload)
- assert.EqualValues(t, "silverwind <me@silverwind.io>", commitFromReader.Author.String())
+ assert.Equal(t, "silverwind <me@silverwind.io>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
- assert.EqualValues(t, commitFromReader, commitFromReader2)
+ assert.Equal(t, commitFromReader, commitFromReader2)
}
func TestCommitWithEncodingFromReader(t *testing.T) {
- commitString := `feaf4ba6bc635fec442f46ddd4512416ec43c2c2 commit 1074
-tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
+ commitString := `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
@@ -160,7 +156,7 @@ ISO-8859-1`
assert.NoError(t, err)
require.NotNil(t, commitFromReader)
assert.EqualValues(t, sha, commitFromReader.ID)
- assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
+ assert.Equal(t, `-----BEGIN PGP SIGNATURE-----
iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow
Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR
@@ -173,22 +169,21 @@ SONRzusmu5n3DgV956REL7x62h7JuqmBz/12HZkr0z0zgXkcZ04q08pSJATX5N1F
yN+tWxTsWg+zhDk96d5Esdo9JMjcFvPv0eioo30GAERaz1hoD7zCMT4jgUFTQwgz
jw4YcO5u
=r3UU
------END PGP SIGNATURE-----
-`, commitFromReader.Signature.Signature)
- assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
+-----END PGP SIGNATURE-----`, commitFromReader.Signature.Signature)
+ assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5
parent 47b24e7ab977ed31c5a39989d570847d6d0052af
author KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
committer KN4CK3R <admin@oldschoolhack.me> 1711702962 +0100
encoding ISO-8859-1
ISO-8859-1`, commitFromReader.Signature.Payload)
- assert.EqualValues(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
+ assert.Equal(t, "KN4CK3R <admin@oldschoolhack.me>", commitFromReader.Author.String())
commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n"))
assert.NoError(t, err)
commitFromReader.CommitMessage += "\n\n"
commitFromReader.Signature.Payload += "\n\n"
- assert.EqualValues(t, commitFromReader, commitFromReader2)
+ assert.Equal(t, commitFromReader, commitFromReader2)
}
func TestHasPreviousCommit(t *testing.T) {
@@ -347,15 +342,15 @@ func TestGetCommitFileStatusMerges(t *testing.T) {
func Test_GetCommitBranchStart(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- repo, err := OpenRepository(context.Background(), bareRepo1Path)
+ repo, err := OpenRepository(t.Context(), bareRepo1Path)
assert.NoError(t, err)
defer repo.Close()
commit, err := repo.GetBranchCommit("branch1")
assert.NoError(t, err)
- assert.EqualValues(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commit.ID.String())
+ assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commit.ID.String())
startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String())
assert.NoError(t, err)
assert.NotEmpty(t, startCommitID)
- assert.EqualValues(t, "9c9aef8dd84e02bc7ec12641deb4c930a7c30185", startCommitID)
+ assert.Equal(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID)
}
diff --git a/modules/git/config.go b/modules/git/config.go
index 9c36cf1654..234be7b955 100644
--- a/modules/git/config.go
+++ b/modules/git/config.go
@@ -116,7 +116,7 @@ func syncGitConfig() (err error) {
}
func configSet(key, value string) error {
- stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+ stdout, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil)
if err != nil && !IsErrorExitCode(err, 1) {
return fmt.Errorf("failed to get git config %s, err: %w", key, err)
}
@@ -126,7 +126,7 @@ func configSet(key, value string) error {
return nil
}
- _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
+ _, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil)
if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
@@ -135,14 +135,14 @@ func configSet(key, value string) error {
}
func configSetNonExist(key, value string) error {
- _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+ _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil)
if err == nil {
// already exist
return nil
}
if IsErrorExitCode(err, 1) {
// not exist, set new config
- _, _, err = NewCommand(DefaultContext, "config", "--global").AddDynamicArguments(key, value).RunStdString(nil)
+ _, _, err = NewCommand("config", "--global").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil)
if err != nil {
return fmt.Errorf("failed to set git global config %s, err: %w", key, err)
}
@@ -153,14 +153,14 @@ func configSetNonExist(key, value string) error {
}
func configAddNonExist(key, value string) error {
- _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
+ _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(DefaultContext, nil)
if err == nil {
// already exist
return nil
}
if IsErrorExitCode(err, 1) {
// not exist, add new config
- _, _, err = NewCommand(DefaultContext, "config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(nil)
+ _, _, err = NewCommand("config", "--global", "--add").AddDynamicArguments(key, value).RunStdString(DefaultContext, nil)
if err != nil {
return fmt.Errorf("failed to add git global config %s, err: %w", key, err)
}
@@ -170,10 +170,10 @@ func configAddNonExist(key, value string) error {
}
func configUnsetAll(key, value string) error {
- _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil)
+ _, _, err := NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(DefaultContext, nil)
if err == nil {
// exist, need to remove
- _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil)
+ _, _, err = NewCommand("config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(DefaultContext, nil)
if err != nil {
return fmt.Errorf("failed to unset git global config %s, err: %w", key, err)
}
diff --git a/modules/git/diff.go b/modules/git/diff.go
index da0a2f26ba..c4df6b8063 100644
--- a/modules/git/diff.go
+++ b/modules/git/diff.go
@@ -34,8 +34,8 @@ func GetRawDiff(repo *Repository, commitID string, diffType RawDiffType, 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)
- cmd := NewCommand(ctx, "show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
- if err := cmd.Run(&RunOpts{
+ cmd := NewCommand("show", "--pretty=format:revert %H%n", "-R").AddDynamicArguments(commitID)
+ if err := cmd.Run(ctx, &RunOpts{
Dir: repoPath,
Stdout: writer,
Stderr: stderr,
@@ -56,7 +56,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
files = append(files, file)
}
- cmd := NewCommand(repo.Ctx)
+ cmd := NewCommand()
switch diffType {
case RawDiffNormal:
if len(startCommit) != 0 {
@@ -89,7 +89,7 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
}
stderr := new(bytes.Buffer)
- if err = cmd.Run(&RunOpts{
+ if err = cmd.Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: writer,
Stderr: stderr,
@@ -301,8 +301,8 @@ func GetAffectedFiles(repo *Repository, branchName, oldCommitID, newCommitID str
affectedFiles := make([]string, 0, 32)
// Run `git diff --name-only` to get the names of the changed files
- err = NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
- Run(&RunOpts{
+ err = NewCommand("diff", "--name-only").AddDynamicArguments(oldCommitID, newCommitID).
+ Run(repo.Ctx, &RunOpts{
Env: env,
Dir: repo.Path,
Stdout: stdoutWriter,
diff --git a/modules/git/diff_test.go b/modules/git/diff_test.go
index 0f865c52a8..7671fffcc1 100644
--- a/modules/git/diff_test.go
+++ b/modules/git/diff_test.go
@@ -154,7 +154,7 @@ func TestCutDiffAroundLine(t *testing.T) {
}
func BenchmarkCutDiffAroundLine(b *testing.B) {
- for n := 0; n < b.N; n++ {
+ for b.Loop() {
CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3)
}
}
@@ -177,8 +177,8 @@ func ExampleCutDiffAroundLine() {
func TestParseDiffHunkString(t *testing.T) {
leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER")
- assert.EqualValues(t, 19, leftLine)
- assert.EqualValues(t, 3, leftHunk)
- assert.EqualValues(t, 19, rightLine)
- assert.EqualValues(t, 5, rightHunk)
+ assert.Equal(t, 19, leftLine)
+ assert.Equal(t, 3, leftHunk)
+ assert.Equal(t, 19, rightLine)
+ assert.Equal(t, 5, rightHunk)
}
diff --git a/modules/git/error.go b/modules/git/error.go
index 10fb37be07..7d131345d0 100644
--- a/modules/git/error.go
+++ b/modules/git/error.go
@@ -32,22 +32,6 @@ func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist
}
-// ErrBadLink entry.FollowLink error
-type ErrBadLink struct {
- Name string
- Message string
-}
-
-func (err ErrBadLink) Error() string {
- return fmt.Sprintf("%s: %s", err.Name, err.Message)
-}
-
-// IsErrBadLink if some error is ErrBadLink
-func IsErrBadLink(err error) bool {
- _, ok := err.(ErrBadLink)
- return ok
-}
-
// ErrBranchNotExist represents a "BranchNotExist" kind of error.
type ErrBranchNotExist struct {
Name string
diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go
index 97e8ee4724..d9573a55d6 100644
--- a/modules/git/foreachref/format.go
+++ b/modules/git/foreachref/format.go
@@ -76,7 +76,7 @@ func (f Format) Parser(r io.Reader) *Parser {
// would turn into "%0a%00".
func (f Format) hexEscaped(delim []byte) string {
escaped := ""
- for i := 0; i < len(delim); i++ {
+ for i := range delim {
escaped += "%" + hex.EncodeToString([]byte{delim[i]})
}
return escaped
diff --git a/modules/git/fsck.go b/modules/git/fsck.go
index cec27f165b..a52684c84f 100644
--- a/modules/git/fsck.go
+++ b/modules/git/fsck.go
@@ -10,5 +10,5 @@ import (
// Fsck verifies the connectivity and validity of the objects in the database
func Fsck(ctx context.Context, repoPath string, timeout time.Duration, args TrustedCmdArgs) error {
- return NewCommand(ctx, "fsck").AddArguments(args...).Run(&RunOpts{Timeout: timeout, Dir: repoPath})
+ return NewCommand("fsck").AddArguments(args...).Run(ctx, &RunOpts{Timeout: timeout, Dir: repoPath})
}
diff --git a/modules/git/git.go b/modules/git/git.go
index e3e5b83274..a2ffd6d289 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -30,6 +30,7 @@ type Features struct {
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’
SupportedObjectFormats []ObjectFormat // sha1, sha256
+ SupportCheckAttrOnBare bool // >= 2.40
}
var (
@@ -60,7 +61,7 @@ func DefaultFeatures() *Features {
}
func loadGitVersionFeatures() (*Features, error) {
- stdout, _, runErr := NewCommand(DefaultContext, "version").RunStdString(nil)
+ stdout, _, runErr := NewCommand("version").RunStdString(DefaultContext, nil)
if runErr != nil {
return nil, runErr
}
@@ -77,6 +78,7 @@ func loadGitVersionFeatures() (*Features, error) {
if features.SupportHashSha256 {
features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat)
}
+ features.SupportCheckAttrOnBare = features.CheckVersionAtLeast("2.40")
return features, nil
}
diff --git a/modules/git/git_test.go b/modules/git/git_test.go
index 5472842b76..58ba01cabc 100644
--- a/modules/git/git_test.go
+++ b/modules/git/git_test.go
@@ -10,18 +10,19 @@ import (
"testing"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/tempdir"
"github.com/hashicorp/go-version"
"github.com/stretchr/testify/assert"
)
func testRun(m *testing.M) error {
- gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home")
+ gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home")
if err != nil {
return fmt.Errorf("unable to create temp dir: %w", err)
}
- defer util.RemoveAll(gitHomePath)
+ defer cleanup()
+
setting.Git.HomePath = gitHomePath
if err = InitFull(context.Background()); err != nil {
diff --git a/modules/git/grep.go b/modules/git/grep.go
index bf6b41a886..66711650c9 100644
--- a/modules/git/grep.go
+++ b/modules/git/grep.go
@@ -23,11 +23,19 @@ type GrepResult struct {
LineCodes []string
}
+type GrepModeType string
+
+const (
+ GrepModeExact GrepModeType = "exact"
+ GrepModeWords GrepModeType = "words"
+ GrepModeRegexp GrepModeType = "regexp"
+)
+
type GrepOptions struct {
RefName string
MaxResultLimit int
ContextLineNumber int
- IsFuzzy bool
+ GrepMode GrepModeType
MaxLineLength int // the maximum length of a line to parse, exceeding chars will be truncated
PathspecList []string
}
@@ -52,21 +60,30 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO
2^@repo: go-gitea/gitea
*/
var results []*GrepResult
- cmd := NewCommand(ctx, "grep", "--null", "--break", "--heading", "--fixed-strings", "--line-number", "--ignore-case", "--full-name")
- cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber))
- if opts.IsFuzzy {
+ cmd := NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name")
+ cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber))
+ switch opts.GrepMode {
+ case GrepModeExact:
+ cmd.AddArguments("--fixed-strings")
+ cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
+ case GrepModeRegexp:
+ cmd.AddArguments("--perl-regexp")
+ cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
+ default: /* words */
words := strings.Fields(search)
- for _, word := range words {
+ cmd.AddArguments("--fixed-strings", "--ignore-case")
+ for i, word := range words {
cmd.AddOptionValues("-e", strings.TrimLeft(word, "-"))
+ if i < len(words)-1 {
+ cmd.AddOptionValues("--and")
+ }
}
- } else {
- cmd.AddOptionValues("-e", strings.TrimLeft(search, "-"))
}
cmd.AddDynamicArguments(util.IfZero(opts.RefName, "HEAD"))
cmd.AddDashesAndList(opts.PathspecList...)
opts.MaxResultLimit = util.IfZero(opts.MaxResultLimit, 50)
stderr := bytes.Buffer{}
- err = cmd.Run(&RunOpts{
+ err = cmd.Run(ctx, &RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go
index 005d539726..0dce464b7c 100644
--- a/modules/git/grep_test.go
+++ b/modules/git/grep_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"path/filepath"
"testing"
@@ -16,7 +15,7 @@ func TestGrepSearch(t *testing.T) {
assert.NoError(t, err)
defer repo.Close()
- res, err := GrepSearch(context.Background(), repo, "void", GrepOptions{})
+ res, err := GrepSearch(t.Context(), repo, "void", GrepOptions{})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
@@ -31,7 +30,7 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
- res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
+ res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob)java-hello/*"}})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
@@ -41,7 +40,7 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
- res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
+ res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{PathspecList: []string{":(glob,exclude)java-hello/*"}})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
@@ -51,7 +50,7 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
- res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1})
+ res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
@@ -61,7 +60,7 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
- res, err = GrepSearch(context.Background(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39})
+ res, err = GrepSearch(t.Context(), repo, "void", GrepOptions{MaxResultLimit: 1, MaxLineLength: 39})
assert.NoError(t, err)
assert.Equal(t, []*GrepResult{
{
@@ -71,11 +70,11 @@ func TestGrepSearch(t *testing.T) {
},
}, res)
- res, err = GrepSearch(context.Background(), repo, "no-such-content", GrepOptions{})
+ res, err = GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{})
assert.NoError(t, err)
assert.Empty(t, res)
- res, err = GrepSearch(context.Background(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{})
+ res, err = GrepSearch(t.Context(), &Repository{Path: "no-such-git-repo"}, "no-such-content", GrepOptions{})
assert.Error(t, err)
assert.Empty(t, res)
}
diff --git a/modules/git/hook.go b/modules/git/hook.go
index 46f93ce13e..548a59971d 100644
--- a/modules/git/hook.go
+++ b/modules/git/hook.go
@@ -7,11 +7,10 @@ package git
import (
"errors"
"os"
- "path"
"path/filepath"
+ "slices"
"strings"
- "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)
@@ -27,12 +26,7 @@ var ErrNotValidHook = errors.New("not a valid Git hook")
// IsValidHookName returns true if given name is a valid Git hook.
func IsValidHookName(name string) bool {
- for _, hn := range hookNames {
- if hn == name {
- return true
- }
- }
- return false
+ return slices.Contains(hookNames, name)
}
// Hook represents a Git hook.
@@ -51,17 +45,28 @@ func GetHook(repoPath, name string) (*Hook, error) {
}
h := &Hook{
name: name,
- path: path.Join(repoPath, "hooks", name+".d", name),
+ path: filepath.Join(repoPath, "hooks", name+".d", name),
}
- samplePath := filepath.Join(repoPath, "hooks", name+".sample")
- if isFile(h.path) {
+ isFile, err := util.IsFile(h.path)
+ if err != nil {
+ return nil, err
+ }
+ if isFile {
data, err := os.ReadFile(h.path)
if err != nil {
return nil, err
}
h.IsActive = true
h.Content = string(data)
- } else if isFile(samplePath) {
+ return h, nil
+ }
+
+ samplePath := filepath.Join(repoPath, "hooks", name+".sample")
+ isFile, err = util.IsFile(samplePath)
+ if err != nil {
+ return nil, err
+ }
+ if isFile {
data, err := os.ReadFile(samplePath)
if err != nil {
return nil, err
@@ -79,7 +84,11 @@ func (h *Hook) Name() string {
// Update updates hook settings.
func (h *Hook) Update() error {
if len(strings.TrimSpace(h.Content)) == 0 {
- if isExist(h.path) {
+ exist, err := util.IsExist(h.path)
+ if err != nil {
+ return err
+ }
+ if exist {
err := util.Remove(h.path)
if err != nil {
return err
@@ -103,7 +112,10 @@ func (h *Hook) Update() error {
// ListHooks returns a list of Git hooks of given repository.
func ListHooks(repoPath string) (_ []*Hook, err error) {
- if !isDir(path.Join(repoPath, "hooks")) {
+ exist, err := util.IsDir(filepath.Join(repoPath, "hooks"))
+ if err != nil {
+ return nil, err
+ } else if !exist {
return nil, errors.New("hooks path does not exist")
}
@@ -116,28 +128,3 @@ func ListHooks(repoPath string) (_ []*Hook, err error) {
}
return hooks, nil
}
-
-const (
- // HookPathUpdate hook update path
- HookPathUpdate = "hooks/update"
-)
-
-// SetUpdateHook writes given content to update hook of the repository.
-func SetUpdateHook(repoPath, content string) (err error) {
- log.Debug("Setting update hook: %s", repoPath)
- hookPath := path.Join(repoPath, HookPathUpdate)
- isExist, err := util.IsExist(hookPath)
- if err != nil {
- log.Debug("Unable to check if %s exists. Error: %v", hookPath, err)
- return err
- }
- if isExist {
- err = util.Remove(hookPath)
- } else {
- err = os.MkdirAll(path.Dir(hookPath), os.ModePerm)
- }
- if err != nil {
- return err
- }
- return os.WriteFile(hookPath, []byte(content), 0o777)
-}
diff --git a/modules/git/key.go b/modules/git/key.go
new file mode 100644
index 0000000000..2513c048b7
--- /dev/null
+++ b/modules/git/key.go
@@ -0,0 +1,15 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+// Based on https://git-scm.com/docs/git-config#Documentation/git-config.txt-gpgformat
+const (
+ SigningKeyFormatOpenPGP = "openpgp" // for GPG keys, the expected default of git cli
+ SigningKeyFormatSSH = "ssh"
+)
+
+type SigningKey struct {
+ KeyID string
+ Format string
+}
diff --git a/modules/git/repo_language_stats.go b/modules/git/languagestats/language_stats.go
index 8551ea9d24..a71284c3e4 100644
--- a/modules/git/repo_language_stats.go
+++ b/modules/git/languagestats/language_stats.go
@@ -1,13 +1,15 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
-package git
+package languagestats
import (
+ "context"
"strings"
"unicode"
- "code.gitea.io/gitea/modules/optional"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/git/attribute"
)
const (
@@ -49,19 +51,15 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 {
return res
}
-func TryReadLanguageAttribute(attrs map[string]string) optional.Option[string] {
- language := AttributeToString(attrs, AttributeLinguistLanguage)
- if language.Value() == "" {
- language = AttributeToString(attrs, AttributeGitlabLanguage)
- if language.Has() {
- raw := language.Value()
- // gitlab-language may have additional parameters after the language
- // ignore them and just use the main language
- // https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type
- if idx := strings.IndexByte(raw, '?'); idx >= 0 {
- language = optional.Some(raw[:idx])
- }
- }
+// GetFileLanguage tries to get the (linguist) language of the file content
+func GetFileLanguage(ctx context.Context, gitRepo *git.Repository, treeish, treePath string) (string, error) {
+ attributesMap, err := attribute.CheckAttributes(ctx, gitRepo, treeish, attribute.CheckAttributeOpts{
+ Attributes: []string{attribute.LinguistLanguage, attribute.GitlabLanguage},
+ Filenames: []string{treePath},
+ })
+ if err != nil {
+ return "", err
}
- return language
+
+ return attributesMap[treePath].GetLanguage().Value(), nil
}
diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/languagestats/language_stats_gogit.go
index a34c03c781..418c05b157 100644
--- a/modules/git/repo_language_stats_gogit.go
+++ b/modules/git/languagestats/language_stats_gogit.go
@@ -3,13 +3,15 @@
//go:build gogit
-package git
+package languagestats
import (
"bytes"
"io"
"code.gitea.io/gitea/modules/analyze"
+ git_module "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/git/attribute"
"code.gitea.io/gitea/modules/optional"
"github.com/go-enry/go-enry/v2"
@@ -19,7 +21,7 @@ import (
)
// GetLanguageStats calculates language stats for git repository at specified commit
-func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
+func GetLanguageStats(repo *git_module.Repository, commitID string) (map[string]int64, error) {
r, err := git.PlainOpen(repo.Path)
if err != nil {
return nil, err
@@ -40,8 +42,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err
}
- checker, deferable := repo.CheckAttributeReader(commitID)
- defer deferable()
+ checker, err := attribute.NewBatchChecker(repo, commitID, attribute.LinguistAttributes)
+ if err != nil {
+ return nil, err
+ }
+ defer checker.Close()
// sizes contains the current calculated size of all files by language
sizes := make(map[string]int64)
@@ -62,43 +67,41 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
isDocumentation := optional.None[bool]()
isDetectable := optional.None[bool]()
- if checker != nil {
- attrs, err := checker.CheckPath(f.Name)
- if err == nil {
- isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
- if isVendored.ValueOrDefault(false) {
- return nil
- }
-
- isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
- if isGenerated.ValueOrDefault(false) {
- return nil
- }
+ attrs, err := checker.CheckPath(f.Name)
+ if err == nil {
+ isVendored = attrs.GetVendored()
+ if isVendored.ValueOrDefault(false) {
+ return nil
+ }
- isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation)
- if isDocumentation.ValueOrDefault(false) {
- return nil
- }
+ isGenerated = attrs.GetGenerated()
+ if isGenerated.ValueOrDefault(false) {
+ return nil
+ }
- isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable)
- if !isDetectable.ValueOrDefault(true) {
- return nil
- }
+ isDocumentation = attrs.GetDocumentation()
+ if isDocumentation.ValueOrDefault(false) {
+ return nil
+ }
- hasLanguage := TryReadLanguageAttribute(attrs)
- if hasLanguage.Value() != "" {
- language := hasLanguage.Value()
+ isDetectable = attrs.GetDetectable()
+ if !isDetectable.ValueOrDefault(true) {
+ return nil
+ }
- // group languages, such as Pug -> HTML; SCSS -> CSS
- group := enry.GetLanguageGroup(language)
- if len(group) != 0 {
- language = group
- }
+ hasLanguage := attrs.GetLanguage()
+ if hasLanguage.Value() != "" {
+ language := hasLanguage.Value()
- // this language will always be added to the size
- sizes[language] += f.Size
- return nil
+ // group languages, such as Pug -> HTML; SCSS -> CSS
+ group := enry.GetLanguageGroup(language)
+ if len(group) != 0 {
+ language = group
}
+
+ // this language will always be added to the size
+ sizes[language] += f.Size
+ return nil
}
}
diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/languagestats/language_stats_nogogit.go
index de7707bd6c..94cf9fff8c 100644
--- a/modules/git/repo_language_stats_nogogit.go
+++ b/modules/git/languagestats/language_stats_nogogit.go
@@ -3,13 +3,15 @@
//go:build !gogit
-package git
+package languagestats
import (
"bytes"
"io"
"code.gitea.io/gitea/modules/analyze"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/git/attribute"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
@@ -17,7 +19,7 @@ import (
)
// GetLanguageStats calculates language stats for git repository at specified commit
-func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
+func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64, error) {
// We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary.
// so let's create a batch stdin and stdout
batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
@@ -34,19 +36,19 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if err := writeID(commitID); err != nil {
return nil, err
}
- shaBytes, typ, size, err := ReadBatchLine(batchReader)
+ shaBytes, typ, size, err := git.ReadBatchLine(batchReader)
if typ != "commit" {
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
- return nil, ErrNotExist{commitID, ""}
+ return nil, git.ErrNotExist{ID: commitID}
}
- sha, err := NewIDFromString(string(shaBytes))
+ sha, err := git.NewIDFromString(string(shaBytes))
if err != nil {
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
- return nil, ErrNotExist{commitID, ""}
+ return nil, git.ErrNotExist{ID: commitID}
}
- commit, err := CommitFromReader(repo, sha, io.LimitReader(batchReader, size))
+ commit, err := git.CommitFromReader(repo, sha, io.LimitReader(batchReader, size))
if err != nil {
log.Debug("Unable to get commit for: %s. Err: %v", commitID, err)
return nil, err
@@ -62,8 +64,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err
}
- checker, deferable := repo.CheckAttributeReader(commitID)
- defer deferable()
+ checker, err := attribute.NewBatchChecker(repo, commitID, attribute.LinguistAttributes)
+ if err != nil {
+ return nil, err
+ }
+ defer checker.Close()
contentBuf := bytes.Buffer{}
var content []byte
@@ -92,47 +97,40 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
}
isVendored := optional.None[bool]()
- isGenerated := optional.None[bool]()
isDocumentation := optional.None[bool]()
isDetectable := optional.None[bool]()
- if checker != nil {
- attrs, err := checker.CheckPath(f.Name())
- if err == nil {
- isVendored = AttributeToBool(attrs, AttributeLinguistVendored)
- if isVendored.ValueOrDefault(false) {
- continue
- }
-
- isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated)
- if isGenerated.ValueOrDefault(false) {
- continue
- }
+ attrs, err := checker.CheckPath(f.Name())
+ attrLinguistGenerated := optional.None[bool]()
+ if err == nil {
+ if isVendored = attrs.GetVendored(); isVendored.ValueOrDefault(false) {
+ continue
+ }
- isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation)
- if isDocumentation.ValueOrDefault(false) {
- continue
- }
+ if attrLinguistGenerated = attrs.GetGenerated(); attrLinguistGenerated.ValueOrDefault(false) {
+ continue
+ }
- isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable)
- if !isDetectable.ValueOrDefault(true) {
- continue
- }
+ if isDocumentation = attrs.GetDocumentation(); isDocumentation.ValueOrDefault(false) {
+ continue
+ }
- hasLanguage := TryReadLanguageAttribute(attrs)
- if hasLanguage.Value() != "" {
- language := hasLanguage.Value()
+ if isDetectable = attrs.GetDetectable(); !isDetectable.ValueOrDefault(true) {
+ continue
+ }
- // group languages, such as Pug -> HTML; SCSS -> CSS
- group := enry.GetLanguageGroup(language)
- if len(group) != 0 {
- language = group
- }
+ if hasLanguage := attrs.GetLanguage(); hasLanguage.Value() != "" {
+ language := hasLanguage.Value()
- // this language will always be added to the size
- sizes[language] += f.Size()
- continue
+ // group languages, such as Pug -> HTML; SCSS -> CSS
+ group := enry.GetLanguageGroup(language)
+ if len(group) != 0 {
+ language = group
}
+
+ // this language will always be added to the size
+ sizes[language] += f.Size()
+ continue
}
}
@@ -149,7 +147,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
if err := writeID(f.ID.String()); err != nil {
return nil, err
}
- _, _, size, err := ReadBatchLine(batchReader)
+ _, _, size, err := git.ReadBatchLine(batchReader)
if err != nil {
log.Debug("Error reading blob: %s Err: %v", f.ID.String(), err)
return nil, err
@@ -167,11 +165,19 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err
return nil, err
}
content = contentBuf.Bytes()
- if err := DiscardFull(batchReader, discard); err != nil {
+ if err := git.DiscardFull(batchReader, discard); err != nil {
return nil, err
}
}
- if !isGenerated.Has() && enry.IsGenerated(f.Name(), content) {
+
+ // if "generated" attribute is set, use it, otherwise use enry.IsGenerated to guess
+ var isGenerated bool
+ if attrLinguistGenerated.Has() {
+ isGenerated = attrLinguistGenerated.Value()
+ } else {
+ isGenerated = enry.IsGenerated(f.Name(), content)
+ }
+ if isGenerated {
continue
}
diff --git a/modules/git/repo_language_stats_test.go b/modules/git/languagestats/language_stats_test.go
index 1ee5f4c3af..b908ae6413 100644
--- a/modules/git/repo_language_stats_test.go
+++ b/modules/git/languagestats/language_stats_test.go
@@ -3,34 +3,36 @@
//go:build !gogit
-package git
+package languagestats
import (
- "path/filepath"
"testing"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepository_GetLanguageStats(t *testing.T) {
- repoPath := filepath.Join(testReposDir, "language_stats_repo")
- gitRepo, err := openRepositoryWithDefaultContext(repoPath)
+ setting.AppDataPath = t.TempDir()
+ repoPath := "../tests/repos/language_stats_repo"
+ gitRepo, err := git.OpenRepository(t.Context(), repoPath)
require.NoError(t, err)
-
defer gitRepo.Close()
- stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3")
+ stats, err := GetLanguageStats(gitRepo, "8fee858da5796dfb37704761701bb8e800ad9ef3")
require.NoError(t, err)
- assert.EqualValues(t, map[string]int64{
+ assert.Equal(t, map[string]int64{
"Python": 134,
"Java": 112,
}, stats)
}
func TestMergeLanguageStats(t *testing.T) {
- assert.EqualValues(t, map[string]int64{
+ assert.Equal(t, map[string]int64{
"PHP": 1,
"python": 10,
"JAVA": 700,
diff --git a/modules/git/languagestats/main_test.go b/modules/git/languagestats/main_test.go
new file mode 100644
index 0000000000..707d268c81
--- /dev/null
+++ b/modules/git/languagestats/main_test.go
@@ -0,0 +1,41 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package languagestats
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "testing"
+
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
+)
+
+func testRun(m *testing.M) error {
+ gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home")
+ if err != nil {
+ return fmt.Errorf("unable to create temp dir: %w", err)
+ }
+ defer util.RemoveAll(gitHomePath)
+ setting.Git.HomePath = gitHomePath
+
+ if err = git.InitFull(context.Background()); err != nil {
+ return fmt.Errorf("failed to call Init: %w", err)
+ }
+
+ exitCode := m.Run()
+ if exitCode != 0 {
+ return fmt.Errorf("run test failed, ExitCode=%d", exitCode)
+ }
+ return nil
+}
+
+func TestMain(m *testing.M) {
+ if err := testRun(m); err != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "Test failed: %v", err)
+ os.Exit(1)
+ }
+}
diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go
index cf9c10d7b4..cff2556083 100644
--- a/modules/git/last_commit_cache.go
+++ b/modules/git/last_commit_cache.go
@@ -13,7 +13,7 @@ import (
)
func getCacheKey(repoPath, commitID, entryPath string) string {
- hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath)))
+ hashBytes := sha256.Sum256(fmt.Appendf(nil, "%s:%s:%s", repoPath, commitID, entryPath))
return fmt.Sprintf("last_commit:%x", hashBytes)
}
diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go
index 1fd58abfcd..dfdef38ef9 100644
--- a/modules/git/log_name_status.go
+++ b/modules/git/log_name_status.go
@@ -34,7 +34,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
_ = stdoutWriter.Close()
}
- cmd := NewCommand(ctx)
+ cmd := NewCommand()
cmd.AddArguments("log", "--name-status", "-c", "--format=commit%x00%H %P%x00", "--parents", "--no-renames", "-t", "-z").AddDynamicArguments(head)
var files []string
@@ -64,7 +64,7 @@ func LogNameStatusRepo(ctx context.Context, repository, head, treepath string, p
go func() {
stderr := strings.Builder{}
- err := cmd.Run(&RunOpts{
+ err := cmd.Run(ctx, &RunOpts{
Dir: repository,
Stdout: stdoutWriter,
Stderr: &stderr,
@@ -118,11 +118,12 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int
g.buffull = false
g.next, err = g.rd.ReadSlice('\x00')
if err != nil {
- if err == bufio.ErrBufferFull {
+ switch err {
+ case bufio.ErrBufferFull:
g.buffull = true
- } else if err == io.EOF {
+ case io.EOF:
return nil, nil
- } else {
+ default:
return nil, err
}
}
@@ -132,11 +133,12 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int
if bytes.Equal(g.next, []byte("commit\000")) {
g.next, err = g.rd.ReadSlice('\x00')
if err != nil {
- if err == bufio.ErrBufferFull {
+ switch err {
+ case bufio.ErrBufferFull:
g.buffull = true
- } else if err == io.EOF {
+ case io.EOF:
return nil, nil
- } else {
+ default:
return nil, err
}
}
@@ -214,11 +216,12 @@ diffloop:
}
g.next, err = g.rd.ReadSlice('\x00')
if err != nil {
- if err == bufio.ErrBufferFull {
+ switch err {
+ case bufio.ErrBufferFull:
g.buffull = true
- } else if err == io.EOF {
+ case io.EOF:
return &ret, nil
- } else {
+ default:
return nil, err
}
}
@@ -343,10 +346,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st
results := make([]string, len(paths))
remaining := len(paths)
- nextRestart := (len(paths) * 3) / 4
- if nextRestart > 70 {
- nextRestart = 70
- }
+ nextRestart := min((len(paths)*3)/4, 70)
lastEmptyParent := head.ID.String()
commitSinceLastEmptyParent := uint64(0)
commitSinceNextRestart := uint64(0)
diff --git a/modules/git/notes_test.go b/modules/git/notes_test.go
index 267671d8fa..ca05a9e525 100644
--- a/modules/git/notes_test.go
+++ b/modules/git/notes_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"path/filepath"
"testing"
@@ -18,7 +17,7 @@ func TestGetNotes(t *testing.T) {
defer bareRepo1.Close()
note := Note{}
- err = GetNote(context.Background(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", &note)
+ err = GetNote(t.Context(), bareRepo1, "95bb4d39648ee7e325106df01a621c530863a653", &note)
assert.NoError(t, err)
assert.Equal(t, []byte("Note contents\n"), note.Message)
assert.Equal(t, "Vladimir Panteleev", note.Commit.Author.Name)
@@ -31,10 +30,10 @@ func TestGetNestedNotes(t *testing.T) {
defer repo.Close()
note := Note{}
- err = GetNote(context.Background(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", &note)
+ err = GetNote(t.Context(), repo, "3e668dbfac39cbc80a9ff9c61eb565d944453ba4", &note)
assert.NoError(t, err)
assert.Equal(t, []byte("Note 2"), note.Message)
- err = GetNote(context.Background(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", &note)
+ err = GetNote(t.Context(), repo, "ba0a96fa63532d6c5087ecef070b0250ed72fa47", &note)
assert.NoError(t, err)
assert.Equal(t, []byte("Note 1"), note.Message)
}
@@ -46,7 +45,7 @@ func TestGetNonExistentNotes(t *testing.T) {
defer bareRepo1.Close()
note := Note{}
- err = GetNote(context.Background(), bareRepo1, "non_existent_sha", &note)
+ err = GetNote(t.Context(), bareRepo1, "non_existent_sha", &note)
assert.Error(t, err)
assert.IsType(t, ErrNotExist{}, err)
}
diff --git a/modules/git/object_id.go b/modules/git/object_id.go
index 82d30184df..25dfef3ec5 100644
--- a/modules/git/object_id.go
+++ b/modules/git/object_id.go
@@ -99,5 +99,5 @@ type ErrInvalidSHA struct {
}
func (err ErrInvalidSHA) Error() string {
- return fmt.Sprintf("invalid sha: %s", err.SHA)
+ return "invalid sha: " + err.SHA
}
diff --git a/modules/git/parse.go b/modules/git/parse.go
index eb26632cc0..a7f5c58e89 100644
--- a/modules/git/parse.go
+++ b/modules/git/parse.go
@@ -46,19 +46,9 @@ func parseLsTreeLine(line []byte) (*LsTreeEntry, error) {
entry.Size = optional.Some(size)
}
- switch string(entryMode) {
- case "100644":
- entry.EntryMode = EntryModeBlob
- case "100755":
- entry.EntryMode = EntryModeExec
- case "120000":
- entry.EntryMode = EntryModeSymlink
- case "160000":
- entry.EntryMode = EntryModeCommit
- case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
- entry.EntryMode = EntryModeTree
- default:
- return nil, fmt.Errorf("unknown type: %v", string(entryMode))
+ entry.EntryMode, err = ParseEntryMode(string(entryMode))
+ if err != nil || entry.EntryMode == EntryModeNoEntry {
+ return nil, fmt.Errorf("invalid ls-tree output (invalid mode): %q, err: %w", line, err)
}
entry.ID, err = NewIDFromString(string(entryObjectID))
diff --git a/modules/git/parse_nogogit.go b/modules/git/parse_nogogit.go
index 676bb3c76c..78a0162889 100644
--- a/modules/git/parse_nogogit.go
+++ b/modules/git/parse_nogogit.go
@@ -19,7 +19,7 @@ func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(data, nil)
}
-// parseTreeEntries FIXME this function's design is not right, it should make the caller read all data into memory
+// parseTreeEntries FIXME this function's design is not right, it should not make the caller read all data into memory
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
for pos := 0; pos < len(data); {
diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go
index a4436ce499..6594c84269 100644
--- a/modules/git/parse_nogogit_test.go
+++ b/modules/git/parse_nogogit_test.go
@@ -58,7 +58,7 @@ func TestParseTreeEntriesLong(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries {
- assert.EqualValues(t, testCase.Expected[i], entry)
+ assert.Equal(t, testCase.Expected[i], entry)
}
}
}
@@ -91,7 +91,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries {
- assert.EqualValues(t, testCase.Expected[i], entry)
+ assert.Equal(t, testCase.Expected[i], entry)
}
}
}
diff --git a/modules/git/pipeline/catfile.go b/modules/git/pipeline/catfile.go
index 4677218150..5ddc36cc01 100644
--- a/modules/git/pipeline/catfile.go
+++ b/modules/git/pipeline/catfile.go
@@ -25,8 +25,8 @@ func CatFileBatchCheck(ctx context.Context, shasToCheckReader *io.PipeReader, ca
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- cmd := git.NewCommand(ctx, "cat-file", "--batch-check")
- if err := cmd.Run(&git.RunOpts{
+ cmd := git.NewCommand("cat-file", "--batch-check")
+ if err := cmd.Run(ctx, &git.RunOpts{
Dir: tmpBasePath,
Stdin: shasToCheckReader,
Stdout: catFileCheckWriter,
@@ -43,8 +43,8 @@ func CatFileBatchCheckAllObjects(ctx context.Context, catFileCheckWriter *io.Pip
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- cmd := git.NewCommand(ctx, "cat-file", "--batch-check", "--batch-all-objects")
- if err := cmd.Run(&git.RunOpts{
+ cmd := git.NewCommand("cat-file", "--batch-check", "--batch-all-objects")
+ if err := cmd.Run(ctx, &git.RunOpts{
Dir: tmpBasePath,
Stdout: catFileCheckWriter,
Stderr: stderr,
@@ -64,7 +64,7 @@ func CatFileBatch(ctx context.Context, shasToBatchReader *io.PipeReader, catFile
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- if err := git.NewCommand(ctx, "cat-file", "--batch").Run(&git.RunOpts{
+ if err := git.NewCommand("cat-file", "--batch").Run(ctx, &git.RunOpts{
Dir: tmpBasePath,
Stdout: catFileBatchWriter,
Stdin: shasToBatchReader,
diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go
index 92e35c5a10..c5eed73701 100644
--- a/modules/git/pipeline/lfs_nogogit.go
+++ b/modules/git/pipeline/lfs_nogogit.go
@@ -32,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
go func() {
stderr := strings.Builder{}
- err := git.NewCommand(repo.Ctx, "rev-list", "--all").Run(&git.RunOpts{
+ err := git.NewCommand("rev-list", "--all").Run(repo.Ctx, &git.RunOpts{
Dir: repo.Path,
Stdout: revListWriter,
Stderr: &stderr,
diff --git a/modules/git/pipeline/namerev.go b/modules/git/pipeline/namerev.go
index ad583a7479..06731c5051 100644
--- a/modules/git/pipeline/namerev.go
+++ b/modules/git/pipeline/namerev.go
@@ -22,7 +22,7 @@ func NameRevStdin(ctx context.Context, shasToNameReader *io.PipeReader, nameRevS
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- if err := git.NewCommand(ctx, "name-rev", "--stdin", "--name-only", "--always").Run(&git.RunOpts{
+ if err := git.NewCommand("name-rev", "--stdin", "--name-only", "--always").Run(ctx, &git.RunOpts{
Dir: tmpBasePath,
Stdout: nameRevStdinWriter,
Stdin: shasToNameReader,
diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go
index d88ebe78ef..31627a0f3a 100644
--- a/modules/git/pipeline/revlist.go
+++ b/modules/git/pipeline/revlist.go
@@ -23,8 +23,8 @@ func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sy
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all")
- if err := cmd.Run(&git.RunOpts{
+ cmd := git.NewCommand("rev-list", "--objects", "--all")
+ if err := cmd.Run(ctx, &git.RunOpts{
Dir: basePath,
Stdout: revListWriter,
Stderr: stderr,
@@ -42,11 +42,11 @@ func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.
defer revListWriter.Close()
stderr := new(bytes.Buffer)
var errbuf strings.Builder
- cmd := git.NewCommand(ctx, "rev-list", "--objects").AddDynamicArguments(headSHA)
+ cmd := git.NewCommand("rev-list", "--objects").AddDynamicArguments(headSHA)
if baseSHA != "" {
cmd = cmd.AddArguments("--not").AddDynamicArguments(baseSHA)
}
- if err := cmd.Run(&git.RunOpts{
+ if err := cmd.Run(ctx, &git.RunOpts{
Dir: tmpBasePath,
Stdout: revListWriter,
Stderr: stderr,
diff --git a/modules/git/ref.go b/modules/git/ref.go
index f20a175e42..56b2db858a 100644
--- a/modules/git/ref.go
+++ b/modules/git/ref.go
@@ -109,8 +109,8 @@ func (ref RefName) IsFor() bool {
}
func (ref RefName) nameWithoutPrefix(prefix string) string {
- if strings.HasPrefix(string(ref), prefix) {
- return strings.TrimPrefix(string(ref), prefix)
+ if after, ok := strings.CutPrefix(string(ref), prefix); ok {
+ return after
}
return ""
}
diff --git a/modules/git/remote.go b/modules/git/remote.go
index ff8c040eb1..876c3d6acb 100644
--- a/modules/git/remote.go
+++ b/modules/git/remote.go
@@ -17,12 +17,12 @@ import (
func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) {
var cmd *Command
if DefaultFeatures().CheckVersionAtLeast("2.7") {
- cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName)
+ cmd = NewCommand("remote", "get-url").AddDynamicArguments(remoteName)
} else {
- cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
+ cmd = NewCommand("config", "--get").AddDynamicArguments("remote." + remoteName + ".url")
}
- result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ result, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
diff --git a/modules/git/repo.go b/modules/git/repo.go
index 0993a4ac37..f1f6902773 100644
--- a/modules/git/repo.go
+++ b/modules/git/repo.go
@@ -18,6 +18,7 @@ import (
"time"
"code.gitea.io/gitea/modules/proxy"
+ "code.gitea.io/gitea/modules/setting"
)
// GPGSettings represents the default GPG settings for this repository
@@ -27,6 +28,7 @@ type GPGSettings struct {
Email string
Name string
PublicKeyContent string
+ Format string
}
const prettyLogFormat = `--pretty=format:%H`
@@ -42,9 +44,9 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
return commits, nil
}
- parts := bytes.Split(logs, []byte{'\n'})
+ parts := bytes.SplitSeq(logs, []byte{'\n'})
- for _, commitID := range parts {
+ for commitID := range parts {
commit, err := repo.GetCommit(string(commitID))
if err != nil {
return nil, err
@@ -57,7 +59,7 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro
// IsRepoURLAccessible checks if given repository URL is accessible.
func IsRepoURLAccessible(ctx context.Context, url string) bool {
- _, _, err := NewCommand(ctx, "ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(nil)
+ _, _, err := NewCommand("ls-remote", "-q", "-h").AddDynamicArguments(url, "HEAD").RunStdString(ctx, nil)
return err == nil
}
@@ -68,7 +70,7 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
return err
}
- cmd := NewCommand(ctx, "init")
+ cmd := NewCommand("init")
if !IsValidObjectFormat(objectFormatName) {
return fmt.Errorf("invalid object format: %s", objectFormatName)
@@ -80,15 +82,15 @@ func InitRepository(ctx context.Context, repoPath string, bare bool, objectForma
if bare {
cmd.AddArguments("--bare")
}
- _, _, err = cmd.RunStdString(&RunOpts{Dir: repoPath})
+ _, _, err = cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
return err
}
// IsEmpty Check if repository is empty.
func (repo *Repository) IsEmpty() (bool, error) {
var errbuf, output strings.Builder
- if err := NewCommand(repo.Ctx).AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
- Run(&RunOpts{
+ if err := NewCommand().AddOptionFormat("--git-dir=%s", repo.Path).AddArguments("rev-list", "-n", "1", "--all").
+ Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: &output,
Stderr: &errbuf,
@@ -129,7 +131,7 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op
return err
}
- cmd := NewCommandContextNoGlobals(ctx, args...).AddArguments("clone")
+ cmd := NewCommandNoGlobals(args...).AddArguments("clone")
if opts.SkipTLSVerify {
cmd.AddArguments("-c", "http.sslVerify=false")
}
@@ -170,7 +172,7 @@ func CloneWithArgs(ctx context.Context, args TrustedCmdArgs, from, to string, op
}
stderr := new(bytes.Buffer)
- if err = cmd.Run(&RunOpts{
+ if err = cmd.Run(ctx, &RunOpts{
Timeout: opts.Timeout,
Env: envs,
Stdout: io.Discard,
@@ -193,7 +195,7 @@ type PushOptions struct {
// Push pushs local commits to given remote branch.
func Push(ctx context.Context, repoPath string, opts PushOptions) error {
- cmd := NewCommand(ctx, "push")
+ cmd := NewCommand("push")
if opts.Force {
cmd.AddArguments("-f")
}
@@ -206,7 +208,7 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
}
cmd.AddDashesAndList(remoteBranchArgs...)
- stdout, stderr, err := cmd.RunStdString(&RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
+ stdout, stderr, err := cmd.RunStdString(ctx, &RunOpts{Env: opts.Env, Timeout: opts.Timeout, Dir: repoPath})
if err != nil {
if strings.Contains(stderr, "non-fast-forward") {
return &ErrPushOutOfDate{StdOut: stdout, StdErr: stderr, Err: err}
@@ -225,8 +227,8 @@ func Push(ctx context.Context, repoPath string, opts PushOptions) error {
// GetLatestCommitTime returns time for latest commit in repository (across all branches)
func GetLatestCommitTime(ctx context.Context, repoPath string) (time.Time, error) {
- cmd := NewCommand(ctx, "for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ cmd := NewCommand("for-each-ref", "--sort=-committerdate", BranchPrefix, "--count", "1", "--format=%(committerdate)")
+ stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return time.Time{}, err
}
@@ -242,9 +244,9 @@ type DivergeObject struct {
// GetDivergingCommits returns the number of commits a targetBranch is ahead or behind a baseBranch
func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch string) (do DivergeObject, err error) {
- cmd := NewCommand(ctx, "rev-list", "--count", "--left-right").
+ cmd := NewCommand("rev-list", "--count", "--left-right").
AddDynamicArguments(baseBranch + "..." + targetBranch).AddArguments("--")
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return do, err
}
@@ -266,30 +268,30 @@ func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch
// CreateBundle create bundle content to the target path
func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error {
- tmp, err := os.MkdirTemp(os.TempDir(), "gitea-bundle")
+ tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle")
if err != nil {
return err
}
- defer os.RemoveAll(tmp)
+ defer cleanup()
env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects"))
- _, _, err = NewCommand(ctx, "init", "--bare").RunStdString(&RunOpts{Dir: tmp, Env: env})
+ _, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
- _, _, err = NewCommand(ctx, "reset", "--soft").AddDynamicArguments(commit).RunStdString(&RunOpts{Dir: tmp, Env: env})
+ _, _, err = NewCommand("reset", "--soft").AddDynamicArguments(commit).RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
- _, _, err = NewCommand(ctx, "branch", "-m", "bundle").RunStdString(&RunOpts{Dir: tmp, Env: env})
+ _, _, err = NewCommand("branch", "-m", "bundle").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
tmpFile := filepath.Join(tmp, "bundle")
- _, _, err = NewCommand(ctx, "bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(&RunOpts{Dir: tmp, Env: env})
+ _, _, err = NewCommand("bundle", "create").AddDynamicArguments(tmpFile, "bundle", "HEAD").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env})
if err != nil {
return err
}
diff --git a/modules/git/repo_archive.go b/modules/git/repo_archive.go
index 92f3e88f7c..0b2f6f2a45 100644
--- a/modules/git/repo_archive.go
+++ b/modules/git/repo_archive.go
@@ -53,7 +53,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
return fmt.Errorf("unknown format: %v", format)
}
- cmd := NewCommand(ctx, "archive")
+ cmd := NewCommand("archive")
if usePrefix {
cmd.AddOptionFormat("--prefix=%s", filepath.Base(strings.TrimSuffix(repo.Path, ".git"))+"/")
}
@@ -61,7 +61,7 @@ func (repo *Repository) CreateArchive(ctx context.Context, format ArchiveType, t
cmd.AddDynamicArguments(commitID)
var stderr strings.Builder
- err := cmd.Run(&RunOpts{
+ err := cmd.Run(ctx, &RunOpts{
Dir: repo.Path,
Stdout: target,
Stderr: &stderr,
diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go
deleted file mode 100644
index 90eb783fe8..0000000000
--- a/modules/git/repo_attribute.go
+++ /dev/null
@@ -1,323 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "os"
-
- "code.gitea.io/gitea/modules/log"
-)
-
-// CheckAttributeOpts represents the possible options to CheckAttribute
-type CheckAttributeOpts struct {
- CachedOnly bool
- AllAttributes bool
- Attributes []string
- Filenames []string
- IndexFile string
- WorkTree string
-}
-
-// CheckAttribute return the Blame object of file
-func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) {
- env := []string{}
-
- if len(opts.IndexFile) > 0 {
- env = append(env, "GIT_INDEX_FILE="+opts.IndexFile)
- }
- if len(opts.WorkTree) > 0 {
- env = append(env, "GIT_WORK_TREE="+opts.WorkTree)
- }
-
- if len(env) > 0 {
- env = append(os.Environ(), env...)
- }
-
- stdOut := new(bytes.Buffer)
- stdErr := new(bytes.Buffer)
-
- cmd := NewCommand(repo.Ctx, "check-attr", "-z")
-
- if opts.AllAttributes {
- cmd.AddArguments("-a")
- } else {
- for _, attribute := range opts.Attributes {
- if attribute != "" {
- cmd.AddDynamicArguments(attribute)
- }
- }
- }
-
- if opts.CachedOnly {
- cmd.AddArguments("--cached")
- }
-
- cmd.AddDashesAndList(opts.Filenames...)
-
- if err := cmd.Run(&RunOpts{
- Env: env,
- Dir: repo.Path,
- Stdout: stdOut,
- Stderr: stdErr,
- }); err != nil {
- return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String())
- }
-
- // FIXME: This is incorrect on versions < 1.8.5
- fields := bytes.Split(stdOut.Bytes(), []byte{'\000'})
-
- if len(fields)%3 != 1 {
- return nil, fmt.Errorf("wrong number of fields in return from check-attr")
- }
-
- name2attribute2info := make(map[string]map[string]string)
-
- for i := 0; i < (len(fields) / 3); i++ {
- filename := string(fields[3*i])
- attribute := string(fields[3*i+1])
- info := string(fields[3*i+2])
- attribute2info := name2attribute2info[filename]
- if attribute2info == nil {
- attribute2info = make(map[string]string)
- }
- attribute2info[attribute] = info
- name2attribute2info[filename] = attribute2info
- }
-
- return name2attribute2info, nil
-}
-
-// CheckAttributeReader provides a reader for check-attribute content that can be long running
-type CheckAttributeReader struct {
- // params
- Attributes []string
- Repo *Repository
- IndexFile string
- WorkTree string
-
- stdinReader io.ReadCloser
- stdinWriter *os.File
- stdOut attributeWriter
- cmd *Command
- env []string
- ctx context.Context
- cancel context.CancelFunc
-}
-
-// Init initializes the CheckAttributeReader
-func (c *CheckAttributeReader) Init(ctx context.Context) error {
- if len(c.Attributes) == 0 {
- lw := new(nulSeparatedAttributeWriter)
- lw.attributes = make(chan attributeTriple)
- lw.closed = make(chan struct{})
-
- c.stdOut = lw
- c.stdOut.Close()
- return fmt.Errorf("no provided Attributes to check")
- }
-
- c.ctx, c.cancel = context.WithCancel(ctx)
- c.cmd = NewCommand(c.ctx, "check-attr", "--stdin", "-z")
-
- if len(c.IndexFile) > 0 {
- c.cmd.AddArguments("--cached")
- c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile)
- }
-
- if len(c.WorkTree) > 0 {
- c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree)
- }
-
- c.env = append(c.env, "GIT_FLUSH=1")
-
- c.cmd.AddDynamicArguments(c.Attributes...)
-
- var err error
-
- c.stdinReader, c.stdinWriter, err = os.Pipe()
- if err != nil {
- c.cancel()
- return err
- }
-
- lw := new(nulSeparatedAttributeWriter)
- lw.attributes = make(chan attributeTriple, 5)
- lw.closed = make(chan struct{})
- c.stdOut = lw
- return nil
-}
-
-// Run run cmd
-func (c *CheckAttributeReader) Run() error {
- defer func() {
- _ = c.stdinReader.Close()
- _ = c.stdOut.Close()
- }()
- stdErr := new(bytes.Buffer)
- err := c.cmd.Run(&RunOpts{
- Env: c.env,
- Dir: c.Repo.Path,
- Stdin: c.stdinReader,
- Stdout: c.stdOut,
- Stderr: stdErr,
- })
- if err != nil && !IsErrCanceledOrKilled(err) {
- return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String())
- }
- return nil
-}
-
-// CheckPath check attr for given path
-func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) {
- defer func() {
- if err != nil && err != c.ctx.Err() {
- log.Error("Unexpected error when checking path %s in %s. Error: %v", path, c.Repo.Path, err)
- }
- }()
-
- select {
- case <-c.ctx.Done():
- return nil, c.ctx.Err()
- default:
- }
-
- if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil {
- defer c.Close()
- return nil, err
- }
-
- rs = make(map[string]string)
- for range c.Attributes {
- select {
- case attr, ok := <-c.stdOut.ReadAttribute():
- if !ok {
- return nil, c.ctx.Err()
- }
- rs[attr.Attribute] = attr.Value
- case <-c.ctx.Done():
- return nil, c.ctx.Err()
- }
- }
- return rs, nil
-}
-
-// Close close pip after use
-func (c *CheckAttributeReader) Close() error {
- c.cancel()
- err := c.stdinWriter.Close()
- return err
-}
-
-type attributeWriter interface {
- io.WriteCloser
- ReadAttribute() <-chan attributeTriple
-}
-
-type attributeTriple struct {
- Filename string
- Attribute string
- Value string
-}
-
-type nulSeparatedAttributeWriter struct {
- tmp []byte
- attributes chan attributeTriple
- closed chan struct{}
- working attributeTriple
- pos int
-}
-
-func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) {
- l, read := len(p), 0
-
- nulIdx := bytes.IndexByte(p, '\x00')
- for nulIdx >= 0 {
- wr.tmp = append(wr.tmp, p[:nulIdx]...)
- switch wr.pos {
- case 0:
- wr.working = attributeTriple{
- Filename: string(wr.tmp),
- }
- case 1:
- wr.working.Attribute = string(wr.tmp)
- case 2:
- wr.working.Value = string(wr.tmp)
- }
- wr.tmp = wr.tmp[:0]
- wr.pos++
- if wr.pos > 2 {
- wr.attributes <- wr.working
- wr.pos = 0
- }
- read += nulIdx + 1
- if l > read {
- p = p[nulIdx+1:]
- nulIdx = bytes.IndexByte(p, '\x00')
- } else {
- return l, nil
- }
- }
- wr.tmp = append(wr.tmp, p...)
- return len(p), nil
-}
-
-func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple {
- return wr.attributes
-}
-
-func (wr *nulSeparatedAttributeWriter) Close() error {
- select {
- case <-wr.closed:
- return nil
- default:
- }
- close(wr.attributes)
- close(wr.closed)
- return nil
-}
-
-// Create a check attribute reader for the current repository and provided commit ID
-func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) {
- indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID)
- if err != nil {
- return nil, func() {}
- }
-
- checker := &CheckAttributeReader{
- Attributes: []string{
- AttributeLinguistVendored,
- AttributeLinguistGenerated,
- AttributeLinguistDocumentation,
- AttributeLinguistDetectable,
- AttributeLinguistLanguage,
- AttributeGitlabLanguage,
- },
- Repo: repo,
- IndexFile: indexFilename,
- WorkTree: worktree,
- }
- ctx, cancel := context.WithCancel(repo.Ctx)
- if err := checker.Init(ctx); err != nil {
- log.Error("Unable to open checker for %s. Error: %v", commitID, err)
- } else {
- go func() {
- err := checker.Run()
- if err != nil && err != ctx.Err() {
- log.Error("Unable to open checker for %s. Error: %v", commitID, err)
- }
- cancel()
- }()
- }
- deferable := func() {
- _ = checker.Close()
- cancel()
- deleteTemporaryFile()
- }
-
- return checker, deferable
-}
diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go
deleted file mode 100644
index 0fcd94b4c7..0000000000
--- a/modules/git/repo_attribute_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package git
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) {
- wr := &nulSeparatedAttributeWriter{
- attributes: make(chan attributeTriple, 5),
- }
-
- testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00"
-
- n, err := wr.Write([]byte(testStr))
-
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
- select {
- case attr := <-wr.ReadAttribute():
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, AttributeLinguistVendored, attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
- }
- // Write a second attribute again
- n, err = wr.Write([]byte(testStr))
-
- assert.Len(t, testStr, n)
- assert.NoError(t, err)
-
- select {
- case attr := <-wr.ReadAttribute():
- assert.Equal(t, ".gitignore\"\n", attr.Filename)
- assert.Equal(t, AttributeLinguistVendored, attr.Attribute)
- assert.Equal(t, "unspecified", attr.Value)
- case <-time.After(100 * time.Millisecond):
- assert.FailNow(t, "took too long to read an attribute from the list")
- }
-
- // Write a partial attribute
- _, err = wr.Write([]byte("incomplete-file"))
- assert.NoError(t, err)
- _, err = wr.Write([]byte("name\x00"))
- assert.NoError(t, err)
-
- select {
- case <-wr.ReadAttribute():
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
- }
- _, err = wr.Write([]byte("attribute\x00"))
- assert.NoError(t, err)
- select {
- case <-wr.ReadAttribute():
- assert.FailNow(t, "There should not be an attribute ready to read")
- case <-time.After(100 * time.Millisecond):
- }
-
- _, err = wr.Write([]byte("value\x00"))
- assert.NoError(t, err)
-
- attr := <-wr.ReadAttribute()
- assert.Equal(t, "incomplete-filename", attr.Filename)
- assert.Equal(t, "attribute", attr.Attribute)
- assert.Equal(t, "value", attr.Value)
-
- _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00"))
- assert.NoError(t, err)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: AttributeLinguistVendored,
- Value: "set",
- }, attr)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: AttributeLinguistGenerated,
- Value: "unspecified",
- }, attr)
- attr = <-wr.ReadAttribute()
- assert.NoError(t, err)
- assert.EqualValues(t, attributeTriple{
- Filename: "shouldbe.vendor",
- Attribute: AttributeLinguistLanguage,
- Value: "unspecified",
- }, attr)
-}
diff --git a/modules/git/repo_base_gogit.go b/modules/git/repo_base_gogit.go
index 0ca1ea79c2..293aca159c 100644
--- a/modules/git/repo_base_gogit.go
+++ b/modules/git/repo_base_gogit.go
@@ -49,7 +49,12 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
- } else if !isDir(repoPath) {
+ }
+ exist, err := util.IsDir(repoPath)
+ if err != nil {
+ return nil, err
+ }
+ if !exist {
return nil, util.NewNotExistErrorf("no such file or directory")
}
diff --git a/modules/git/repo_base_nogogit.go b/modules/git/repo_base_nogogit.go
index 477e3b8742..6f9bfd4b43 100644
--- a/modules/git/repo_base_nogogit.go
+++ b/modules/git/repo_base_nogogit.go
@@ -47,7 +47,12 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
repoPath, err := filepath.Abs(repoPath)
if err != nil {
return nil, err
- } else if !isDir(repoPath) {
+ }
+ exist, err := util.IsDir(repoPath)
+ if err != nil {
+ return nil, err
+ }
+ if !exist {
return nil, util.NewNotExistErrorf("no such file or directory")
}
@@ -62,7 +67,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
if repo.batch == nil {
var err error
- repo.batch, err = repo.NewBatch(ctx)
+ repo.batch, err = NewBatch(ctx, repo.Path)
if err != nil {
return nil, nil, nil, err
}
@@ -76,7 +81,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bu
}
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
- tempBatch, err := repo.NewBatch(ctx)
+ tempBatch, err := NewBatch(ctx, repo.Path)
if err != nil {
return nil, nil, nil, err
}
@@ -87,7 +92,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bu
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
if repo.check == nil {
var err error
- repo.check, err = repo.NewBatchCheck(ctx)
+ repo.check, err = NewBatchCheck(ctx, repo.Path)
if err != nil {
return nil, nil, nil, err
}
@@ -101,7 +106,7 @@ func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError
}
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
- tempBatchCheck, err := repo.NewBatchCheck(ctx)
+ tempBatchCheck, err := NewBatchCheck(ctx, repo.Path)
if err != nil {
return nil, nil, nil, err
}
diff --git a/modules/git/repo_blame.go b/modules/git/repo_blame.go
index 139cdd7be9..6941a76c42 100644
--- a/modules/git/repo_blame.go
+++ b/modules/git/repo_blame.go
@@ -9,10 +9,10 @@ import (
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, path, file string, line uint) (*Commit, error) {
- res, _, err := NewCommand(repo.Ctx, "blame").
+ res, _, err := NewCommand("blame").
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
- AddDashesAndList(file).RunStdString(&RunOpts{Dir: path})
+ AddDashesAndList(file).RunStdString(repo.Ctx, &RunOpts{Dir: path})
if err != nil {
return nil, err
}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
index 552ae2bb8c..e7ecf53f51 100644
--- a/modules/git/repo_branch.go
+++ b/modules/git/repo_branch.go
@@ -7,7 +7,6 @@ package git
import (
"context"
"errors"
- "fmt"
"strings"
)
@@ -16,7 +15,7 @@ const BranchPrefix = "refs/heads/"
// IsReferenceExist returns true if given reference exists in the repository.
func IsReferenceExist(ctx context.Context, repoPath, name string) bool {
- _, _, err := NewCommand(ctx, "show-ref", "--verify").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repoPath})
+ _, _, err := NewCommand("show-ref", "--verify").AddDashesAndList(name).RunStdString(ctx, &RunOpts{Dir: repoPath})
return err == nil
}
@@ -25,38 +24,8 @@ func IsBranchExist(ctx context.Context, repoPath, name string) bool {
return IsReferenceExist(ctx, repoPath, BranchPrefix+name)
}
-// Branch represents a Git branch.
-type Branch struct {
- Name string
- Path string
-
- gitRepo *Repository
-}
-
-// GetHEADBranch returns corresponding branch of HEAD.
-func (repo *Repository) GetHEADBranch() (*Branch, error) {
- if repo == nil {
- return nil, fmt.Errorf("nil repo")
- }
- stdout, _, err := NewCommand(repo.Ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repo.Path})
- if err != nil {
- return nil, err
- }
- stdout = strings.TrimSpace(stdout)
-
- if !strings.HasPrefix(stdout, BranchPrefix) {
- return nil, fmt.Errorf("invalid HEAD branch: %v", stdout)
- }
-
- return &Branch{
- Name: stdout[len(BranchPrefix):],
- Path: stdout,
- gitRepo: repo,
- }, nil
-}
-
func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
- stdout, _, err := NewCommand(ctx, "symbolic-ref", "HEAD").RunStdString(&RunOpts{Dir: repoPath})
+ stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return "", err
}
@@ -67,37 +36,6 @@ func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) {
return strings.TrimPrefix(stdout, BranchPrefix), nil
}
-// GetBranch returns a branch by it's name
-func (repo *Repository) GetBranch(branch string) (*Branch, error) {
- if !repo.IsBranchExist(branch) {
- return nil, ErrBranchNotExist{branch}
- }
- return &Branch{
- Path: repo.Path,
- Name: branch,
- gitRepo: repo,
- }, nil
-}
-
-// GetBranches returns a slice of *git.Branch
-func (repo *Repository) GetBranches(skip, limit int) ([]*Branch, int, error) {
- brs, countAll, err := repo.GetBranchNames(skip, limit)
- if err != nil {
- return nil, 0, err
- }
-
- branches := make([]*Branch, len(brs))
- for i := range brs {
- branches[i] = &Branch{
- Path: repo.Path,
- Name: brs[i],
- gitRepo: repo,
- }
- }
-
- return branches, countAll, nil
-}
-
// DeleteBranchOptions Option(s) for delete branch
type DeleteBranchOptions struct {
Force bool
@@ -105,7 +43,7 @@ type DeleteBranchOptions struct {
// DeleteBranch delete a branch by name on repository.
func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
- cmd := NewCommand(repo.Ctx, "branch")
+ cmd := NewCommand("branch")
if opts.Force {
cmd.AddArguments("-D")
@@ -114,46 +52,41 @@ func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) erro
}
cmd.AddDashesAndList(name)
- _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// CreateBranch create a new branch
func (repo *Repository) CreateBranch(branch, oldbranchOrCommit string) error {
- cmd := NewCommand(repo.Ctx, "branch")
+ cmd := NewCommand("branch")
cmd.AddDashesAndList(branch, oldbranchOrCommit)
- _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// AddRemote adds a new remote to repository.
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
- cmd := NewCommand(repo.Ctx, "remote", "add")
+ cmd := NewCommand("remote", "add")
if fetch {
cmd.AddArguments("-f")
}
cmd.AddDynamicArguments(name, url)
- _, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
- _, _, err := NewCommand(repo.Ctx, "remote", "rm").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("remote", "rm").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
-// GetCommit returns the head commit of a branch
-func (branch *Branch) GetCommit() (*Commit, error) {
- return branch.gitRepo.GetBranchCommit(branch.Name)
-}
-
// RenameBranch rename a branch
func (repo *Repository) RenameBranch(from, to string) error {
- _, _, err := NewCommand(repo.Ctx, "branch", "-m").AddDynamicArguments(from, to).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
diff --git a/modules/git/repo_branch_gogit.go b/modules/git/repo_branch_gogit.go
index dbc4a5fedc..77aecb21eb 100644
--- a/modules/git/repo_branch_gogit.go
+++ b/modules/git/repo_branch_gogit.go
@@ -57,7 +57,7 @@ func (repo *Repository) IsBranchExist(name string) bool {
// 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 `-commiterdate` as the nogogit
+// 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) {
diff --git a/modules/git/repo_branch_nogogit.go b/modules/git/repo_branch_nogogit.go
index 0d2efd4a6b..0d11198523 100644
--- a/modules/git/repo_branch_nogogit.go
+++ b/modules/git/repo_branch_nogogit.go
@@ -109,7 +109,7 @@ func WalkShowRef(ctx context.Context, repoPath string, extraArgs TrustedCmdArgs,
stderrBuilder := &strings.Builder{}
args := TrustedCmdArgs{"for-each-ref", "--format=%(objectname) %(refname)"}
args = append(args, extraArgs...)
- err := NewCommand(ctx, args...).Run(&RunOpts{
+ err := NewCommand(args...).Run(ctx, &RunOpts{
Dir: repoPath,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go
index 5d3b8abb3a..8e8ea16fcd 100644
--- a/modules/git/repo_branch_test.go
+++ b/modules/git/repo_branch_test.go
@@ -21,21 +21,21 @@ func TestRepository_GetBranches(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, branches, 2)
- assert.EqualValues(t, 3, countAll)
+ 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.EqualValues(t, 3, countAll)
+ 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.EqualValues(t, 3, countAll)
+ assert.Equal(t, 3, countAll)
assert.ElementsMatch(t, []string{}, branches)
}
@@ -47,7 +47,7 @@ func BenchmarkRepository_GetBranches(b *testing.B) {
}
defer bareRepo1.Close()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _, err := bareRepo1.GetBranchNames(0, 0)
if err != nil {
b.Fatal(err)
@@ -71,15 +71,15 @@ func TestGetRefsBySha(t *testing.T) {
// refs/pull/1/head
branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix)
assert.NoError(t, err)
- assert.EqualValues(t, []string{"refs/pull/1/head"}, branches)
+ assert.Equal(t, []string{"refs/pull/1/head"}, branches)
branches, err = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", BranchPrefix)
assert.NoError(t, err)
- assert.EqualValues(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches)
+ assert.Equal(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches)
branches, err = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", BranchPrefix)
assert.NoError(t, err)
- assert.EqualValues(t, []string{"refs/heads/test-patch-1"}, branches)
+ assert.Equal(t, []string{"refs/heads/test-patch-1"}, branches)
}
func BenchmarkGetRefsBySha(b *testing.B) {
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
index 647894bb21..4066a1ca7b 100644
--- a/modules/git/repo_commit.go
+++ b/modules/git/repo_commit.go
@@ -59,7 +59,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
relpath = `\` + relpath
}
- stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDynamicArguments(id.String()).AddDashesAndList(relpath).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -74,7 +74,7 @@ func (repo *Repository) getCommitByPathWithID(id ObjectID, relpath string) (*Com
// GetCommitByPath returns the last commit of relative path.
func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
- stdout, _, runErr := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("log", "-1", prettyLogFormat).AddDashesAndList(relpath).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -89,8 +89,9 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
return commits[0], nil
}
-func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
- cmd := NewCommand(repo.Ctx, "log").
+// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
+func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
+ cmd := NewCommand("log").
AddOptionFormat("--skip=%d", (page-1)*pageSize).
AddOptionFormat("--max-count=%d", pageSize).
AddArguments(prettyLogFormat).
@@ -99,8 +100,14 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
if not != "" {
cmd.AddOptionValues("--not", not)
}
+ if since != "" {
+ cmd.AddOptionFormat("--since=%s", since)
+ }
+ if until != "" {
+ cmd.AddOptionFormat("--until=%s", until)
+ }
- stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@@ -134,7 +141,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
}
// create new git log command with limit of 100 commits
- cmd := NewCommand(repo.Ctx, "log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
+ cmd := NewCommand("log", "-100", prettyLogFormat).AddDynamicArguments(id.String())
// pretend that all refs along with HEAD were listed on command line as <commis>
// https://git-scm.com/docs/git-log#Documentation/git-log.txt---all
@@ -154,7 +161,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// search for commits matching given constraints and keywords in commit msg
addCommonSearchArgs(cmd)
- stdout, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@@ -168,14 +175,14 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// ignore anything not matching a valid sha pattern
if id.Type().IsValid(v) {
// create new git log command with 1 commit limit
- hashCmd := NewCommand(repo.Ctx, "log", "-1", prettyLogFormat)
+ hashCmd := NewCommand("log", "-1", prettyLogFormat)
// add previous arguments except for --grep and --all
addCommonSearchArgs(hashCmd)
// add keyword as <commit>
hashCmd.AddDynamicArguments(v)
// search with given constraints for commit matching sha hash of v
- hashMatching, _, err := hashCmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ hashMatching, _, err := hashCmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil || bytes.Contains(stdout, hashMatching) {
continue
}
@@ -190,7 +197,7 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([
// FileChangedBetweenCommits Returns true if the file changed between commit IDs id1 and id2
// You must ensure that id1 and id2 are valid commit ids.
func (repo *Repository) FileChangedBetweenCommits(filename, id1, id2 string) (bool, error) {
- stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("diff", "--name-only", "-z").AddDynamicArguments(id1, id2).AddDashesAndList(filename).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}
@@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
File string
Not string
Page int
+ Since string
+ Until string
}
// CommitsByFileAndRange return the commits according revision file and the page
@@ -223,7 +232,7 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
}()
go func() {
stderr := strings.Builder{}
- gitCmd := NewCommand(repo.Ctx, "rev-list").
+ gitCmd := NewCommand("rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision)
@@ -231,9 +240,15 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
if opts.Not != "" {
gitCmd.AddOptionValues("--not", opts.Not)
}
+ if opts.Since != "" {
+ gitCmd.AddOptionFormat("--since=%s", opts.Since)
+ }
+ if opts.Until != "" {
+ gitCmd.AddOptionFormat("--until=%s", opts.Until)
+ }
gitCmd.AddDashesAndList(opts.File)
- err := gitCmd.Run(&RunOpts{
+ err := gitCmd.Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: &stderr,
@@ -275,11 +290,11 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
// FilesCountBetween return the number of files changed between two commits
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
- stdout, _, err := NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID + "..." + endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID+"..."+endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// git >= 2.28 now returns an error if startCommitID and endCommitID have become unrelated.
// previously it would return the results of git diff --name-only startCommitID endCommitID so let's try that...
- stdout, _, err = NewCommand(repo.Ctx, "diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("diff", "--name-only").AddDynamicArguments(startCommitID, endCommitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
}
if err != nil {
return 0, err
@@ -293,13 +308,13 @@ func (repo *Repository) CommitsBetween(last, before *Commit) ([]*Commit, error)
var stdout []byte
var err error
if before == nil {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
} else {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
}
}
if err != nil {
@@ -313,22 +328,22 @@ func (repo *Repository) CommitsBetweenLimit(last, before *Commit, limit, skip in
var stdout []byte
var err error
if before == nil {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").
+ stdout, _, err = NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
- AddDynamicArguments(last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ AddDynamicArguments(last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
} else {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").
+ stdout, _, err = NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
- AddDynamicArguments(before.ID.String() + ".." + last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ AddDynamicArguments(before.ID.String()+".."+last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list --max-count n before last so let's try that...
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").
+ stdout, _, err = NewCommand("rev-list").
AddOptionValues("--max-count", strconv.Itoa(limit)).
AddOptionValues("--skip", strconv.Itoa(skip)).
- AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(&RunOpts{Dir: repo.Path})
+ AddDynamicArguments(before.ID.String(), last.ID.String()).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
}
}
if err != nil {
@@ -343,13 +358,13 @@ func (repo *Repository) CommitsBetweenNotBase(last, before *Commit, baseBranch s
var stdout []byte
var err error
if before == nil {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
} else {
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String()+".."+last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil && strings.Contains(err.Error(), "no merge base") {
// future versions of git >= 2.28 are likely to return an error if before and last have become unrelated.
// previously it would return the results of git rev-list before last so let's try that...
- stdout, _, err = NewCommand(repo.Ctx, "rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, err = NewCommand("rev-list").AddDynamicArguments(before.ID.String(), last.ID.String()).AddOptionValues("--not", baseBranch).RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
}
}
if err != nil {
@@ -395,13 +410,13 @@ func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
// commitsBefore the limit is depth, not total number of returned commits.
func (repo *Repository) commitsBefore(id ObjectID, limit int) ([]*Commit, error) {
- cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
+ cmd := NewCommand("log", prettyLogFormat)
if limit > 0 {
cmd.AddOptionFormat("-%d", limit)
}
cmd.AddDynamicArguments(id.String())
- stdout, _, runErr := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -438,10 +453,10 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit,
func (repo *Repository) getBranches(env []string, commitID string, limit int) ([]string, error) {
if DefaultFeatures().CheckVersionAtLeast("2.7.0") {
- stdout, _, err := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").
+ stdout, _, err := NewCommand("for-each-ref", "--format=%(refname:strip=2)").
AddOptionFormat("--count=%d", limit).
AddOptionValues("--contains", commitID, BranchPrefix).
- RunStdString(&RunOpts{
+ RunStdString(repo.Ctx, &RunOpts{
Dir: repo.Path,
Env: env,
})
@@ -453,7 +468,7 @@ func (repo *Repository) getBranches(env []string, commitID string, limit int) ([
return branches, nil
}
- stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commitID).RunStdString(&RunOpts{
+ stdout, _, err := NewCommand("branch").AddOptionValues("--contains", commitID).RunStdString(repo.Ctx, &RunOpts{
Dir: repo.Path,
Env: env,
})
@@ -495,7 +510,7 @@ func (repo *Repository) GetCommitsFromIDs(commitIDs []string) []*Commit {
// IsCommitInBranch check if the commit is on the branch
func (repo *Repository) IsCommitInBranch(commitID, branch string) (r bool, err error) {
- stdout, _, err := NewCommand(repo.Ctx, "branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("branch", "--contains").AddDynamicArguments(commitID, branch).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return false, err
}
@@ -519,11 +534,12 @@ func (repo *Repository) AddLastCommitCache(cacheKey, fullName, sha string) error
return nil
}
+// GetCommitBranchStart returns the commit where the branch diverged
func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID string) (string, error) {
- cmd := NewCommand(repo.Ctx, "log", prettyLogFormat)
+ cmd := NewCommand("log", prettyLogFormat)
cmd.AddDynamicArguments(endCommitID)
- stdout, _, runErr := cmd.RunStdBytes(&RunOpts{
+ stdout, _, runErr := cmd.RunStdBytes(repo.Ctx, &RunOpts{
Dir: repo.Path,
Env: env,
})
@@ -531,21 +547,20 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s
return "", runErr
}
- parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'})
+ parts := bytes.SplitSeq(bytes.TrimSpace(stdout), []byte{'\n'})
- var startCommitID string
- for _, commitID := range parts {
+ // check the commits one by one until we find a commit contained by another branch
+ // and we think this commit is the divergence point
+ for commitID := range parts {
branches, err := repo.getBranches(env, string(commitID), 2)
if err != nil {
return "", err
}
for _, b := range branches {
if b != branch {
- return startCommitID, nil
+ return string(commitID), nil
}
}
-
- startCommitID = string(commitID)
}
return "", nil
diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go
index 993013eef7..a88902e209 100644
--- a/modules/git/repo_commit_gogit.go
+++ b/modules/git/repo_commit_gogit.go
@@ -59,7 +59,7 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
}
}
- actualCommitID, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(&RunOpts{Dir: repo.Path})
+ actualCommitID, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
actualCommitID = strings.TrimSpace(actualCommitID)
if err != nil {
if strings.Contains(err.Error(), "unknown revision or path") ||
diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go
index f5ed282a45..3ead3e2216 100644
--- a/modules/git/repo_commit_nogogit.go
+++ b/modules/git/repo_commit_nogogit.go
@@ -16,7 +16,7 @@ import (
// ResolveReference resolves a name to a reference
func (repo *Repository) ResolveReference(name string) (string, error) {
- stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--hash").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("show-ref", "--hash").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
if strings.Contains(err.Error(), "not a valid ref") {
return "", ErrNotExist{name, ""}
@@ -52,13 +52,13 @@ func (repo *Repository) GetRefCommitID(name string) (string, error) {
// SetReference sets the commit ID string of given reference (e.g. branch or tag).
func (repo *Repository) SetReference(name, commitID string) error {
- _, _, err := NewCommand(repo.Ctx, "update-ref").AddDynamicArguments(name, commitID).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("update-ref").AddDynamicArguments(name, commitID).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// RemoveReference removes the given reference (e.g. branch or tag).
func (repo *Repository) RemoveReference(name string) error {
- _, _, err := NewCommand(repo.Ctx, "update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
@@ -68,7 +68,7 @@ func (repo *Repository) IsCommitExist(name string) bool {
log.Error("IsCommitExist: %v", err)
return false
}
- _, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("cat-file", "-e").AddDynamicArguments(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err == nil
}
@@ -81,10 +81,10 @@ func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
_, _ = wr.Write([]byte(id.String() + "\n"))
- return repo.getCommitFromBatchReader(rd, id)
+ return repo.getCommitFromBatchReader(wr, rd, id)
}
-func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) {
+func (repo *Repository) getCommitFromBatchReader(wr WriteCloserError, rd *bufio.Reader, id ObjectID) (*Commit, error) {
_, typ, size, err := ReadBatchLine(rd)
if err != nil {
if errors.Is(err, io.EOF) || IsErrNotExist(err) {
@@ -112,7 +112,11 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID)
return nil, err
}
- commit, err := tag.Commit(repo)
+ if _, err := wr.Write([]byte(tag.Object.String() + "\n")); err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.getCommitFromBatchReader(wr, rd, tag.Object)
if err != nil {
return nil, err
}
diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go
index 087d5bcec4..62c6378054 100644
--- a/modules/git/repo_commitgraph.go
+++ b/modules/git/repo_commitgraph.go
@@ -12,7 +12,7 @@ import (
// this requires git v2.18 to be installed
func WriteCommitGraph(ctx context.Context, repoPath string) error {
if DefaultFeatures().CheckVersionAtLeast("2.18") {
- if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil {
+ if _, _, err := NewCommand("commit-graph", "write").RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil {
return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err)
}
}
diff --git a/modules/git/repo_commitgraph_gogit.go b/modules/git/repo_commitgraph_gogit.go
index d3182f15c6..c0082b62c8 100644
--- a/modules/git/repo_commitgraph_gogit.go
+++ b/modules/git/repo_commitgraph_gogit.go
@@ -8,7 +8,7 @@ package git
import (
"os"
- "path"
+ "path/filepath"
gitealog "code.gitea.io/gitea/modules/log"
@@ -18,7 +18,7 @@ import (
// CommitNodeIndex returns the index for walking commit graph
func (r *Repository) CommitNodeIndex() (cgobject.CommitNodeIndex, *os.File) {
- indexPath := path.Join(r.Path, "objects", "info", "commit-graph")
+ indexPath := filepath.Join(r.Path, "objects", "info", "commit-graph")
file, err := os.Open(indexPath)
if err == nil {
diff --git a/modules/git/repo_compare.go b/modules/git/repo_compare.go
index 877a7ff3b8..ff44506e13 100644
--- a/modules/git/repo_compare.go
+++ b/modules/git/repo_compare.go
@@ -39,13 +39,13 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
if tmpRemote != "origin" {
tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
- _, _, err := NewCommand(repo.Ctx, "fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base + ":" + tmpBaseName).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err == nil {
base = tmpBaseName
}
}
- stdout, _, err := NewCommand(repo.Ctx, "merge-base").AddDashesAndList(base, head).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("merge-base").AddDashesAndList(base, head).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return strings.TrimSpace(stdout), base, err
}
@@ -94,9 +94,9 @@ func (repo *Repository) GetCompareInfo(basePath, baseBranch, headBranch string,
if !fileOnly {
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
var logs []byte
- logs, _, err = NewCommand(repo.Ctx, "log").AddArguments(prettyLogFormat).
- AddDynamicArguments(baseCommitID + separator + headBranch).AddArguments("--").
- RunStdBytes(&RunOpts{Dir: repo.Path})
+ logs, _, err = NewCommand("log").AddArguments(prettyLogFormat).
+ AddDynamicArguments(baseCommitID+separator+headBranch).AddArguments("--").
+ RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
@@ -150,8 +150,8 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
}
// avoid: ambiguous argument 'refs/a...refs/b': unknown revision or path not in the working tree. Use '--': 'git <command> [<revision>...] -- [<file>...]'
- if err := NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base + separator + head).AddArguments("--").
- Run(&RunOpts{
+ if err := NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base+separator+head).AddArguments("--").
+ Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
@@ -161,7 +161,7 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
// previously it would return the results of git diff -z --name-only base head so let's try that...
w = &lineCountWriter{}
stderr.Reset()
- if err = NewCommand(repo.Ctx, "diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(&RunOpts{
+ if err = NewCommand("diff", "-z", "--name-only").AddDynamicArguments(base, head).AddArguments("--").Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
@@ -174,23 +174,15 @@ func (repo *Repository) GetDiffNumChangedFiles(base, head string, directComparis
return w.numLines, nil
}
-// GetDiffShortStat counts number of changed files, number of additions and deletions
-func (repo *Repository) GetDiffShortStat(base, head string) (numFiles, totalAdditions, totalDeletions int, err error) {
- numFiles, totalAdditions, totalDeletions, err = GetDiffShortStat(repo.Ctx, repo.Path, nil, base+"..."+head)
- if err != nil && strings.Contains(err.Error(), "no merge base") {
- return GetDiffShortStat(repo.Ctx, repo.Path, nil, base, head)
- }
- return numFiles, totalAdditions, totalDeletions, err
-}
-
-// GetDiffShortStat counts number of changed files, number of additions and deletions
-func GetDiffShortStat(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
+// GetDiffShortStatByCmdArgs counts number of changed files, number of additions and deletions
+// TODO: it can be merged with another "GetDiffShortStat" in the future
+func GetDiffShortStatByCmdArgs(ctx context.Context, repoPath string, trustedArgs TrustedCmdArgs, dynamicArgs ...string) (numFiles, totalAdditions, totalDeletions int, err error) {
// Now if we call:
// $ git diff --shortstat 1ebb35b98889ff77299f24d82da426b434b0cca0...788b8b1440462d477f45b0088875
// we get:
// " 9902 files changed, 2034198 insertions(+), 298800 deletions(-)\n"
- cmd := NewCommand(ctx, "diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath})
+ cmd := NewCommand("diff", "--shortstat").AddArguments(trustedArgs...).AddDynamicArguments(dynamicArgs...)
+ stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath})
if err != nil {
return 0, 0, 0, err
}
@@ -236,8 +228,8 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
// GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer)
- return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(compareArg).
- Run(&RunOpts{
+ return NewCommand("diff", "-p").AddDynamicArguments(compareArg).
+ Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
@@ -246,7 +238,7 @@ func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
- return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(&RunOpts{
+ return NewCommand("diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: w,
})
@@ -255,8 +247,8 @@ func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
func (repo *Repository) GetPatch(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer)
- return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
- Run(&RunOpts{
+ return NewCommand("format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
+ Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
@@ -271,13 +263,13 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err
if err != nil {
return nil, err
}
- cmd := NewCommand(repo.Ctx, "diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
+ cmd := NewCommand("diff-tree", "--name-only", "--root", "--no-commit-id", "-r", "-z")
if base == objectFormat.EmptyObjectID().String() {
cmd.AddDynamicArguments(head)
} else {
cmd.AddDynamicArguments(base, head)
}
- stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := cmd.RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
diff --git a/modules/git/repo_gpg.go b/modules/git/repo_gpg.go
index e2b45064fd..0021a7bda7 100644
--- a/modules/git/repo_gpg.go
+++ b/modules/git/repo_gpg.go
@@ -6,6 +6,7 @@ package git
import (
"fmt"
+ "os"
"strings"
"code.gitea.io/gitea/modules/process"
@@ -13,6 +14,14 @@ import (
// LoadPublicKeyContent will load the key from gpg
func (gpgSettings *GPGSettings) LoadPublicKeyContent() error {
+ if gpgSettings.Format == SigningKeyFormatSSH {
+ content, err := os.ReadFile(gpgSettings.KeyID)
+ if err != nil {
+ return fmt.Errorf("unable to read SSH public key file: %s, %w", gpgSettings.KeyID, err)
+ }
+ gpgSettings.PublicKeyContent = string(content)
+ return nil
+ }
content, stderr, err := process.GetManager().Exec(
"gpg -a --export",
"gpg", "-a", "--export", gpgSettings.KeyID)
@@ -33,7 +42,7 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
Sign: true,
}
- value, _, _ := NewCommand(repo.Ctx, "config", "--get", "commit.gpgsign").RunStdString(&RunOpts{Dir: repo.Path})
+ value, _, _ := NewCommand("config", "--get", "commit.gpgsign").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
sign, valid := ParseBool(strings.TrimSpace(value))
if !sign || !valid {
gpgSettings.Sign = false
@@ -41,13 +50,16 @@ func (repo *Repository) GetDefaultPublicGPGKey(forceUpdate bool) (*GPGSettings,
return gpgSettings, nil
}
- signingKey, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.signingkey").RunStdString(&RunOpts{Dir: repo.Path})
+ signingKey, _, _ := NewCommand("config", "--get", "user.signingkey").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
gpgSettings.KeyID = strings.TrimSpace(signingKey)
- defaultEmail, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.email").RunStdString(&RunOpts{Dir: repo.Path})
+ format, _, _ := NewCommand("config", "--default", SigningKeyFormatOpenPGP, "--get", "gpg.format").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
+ gpgSettings.Format = strings.TrimSpace(format)
+
+ defaultEmail, _, _ := NewCommand("config", "--get", "user.email").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
gpgSettings.Email = strings.TrimSpace(defaultEmail)
- defaultName, _, _ := NewCommand(repo.Ctx, "config", "--get", "user.name").RunStdString(&RunOpts{Dir: repo.Path})
+ defaultName, _, _ := NewCommand("config", "--get", "user.name").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
gpgSettings.Name = strings.TrimSpace(defaultName)
if err := gpgSettings.LoadPublicKeyContent(); err != nil {
diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go
index f45b6e6191..4879121a41 100644
--- a/modules/git/repo_index.go
+++ b/modules/git/repo_index.go
@@ -10,8 +10,7 @@ import (
"path/filepath"
"strings"
- "code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/modules/setting"
)
// ReadTreeToIndex reads a treeish to the index
@@ -22,7 +21,7 @@ func (repo *Repository) ReadTreeToIndex(treeish string, indexFilename ...string)
}
if len(treeish) != objectFormat.FullLength() {
- res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(&RunOpts{Dir: repo.Path})
+ res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(treeish).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return err
}
@@ -42,7 +41,7 @@ func (repo *Repository) readTreeToIndex(id ObjectID, indexFilename ...string) er
if len(indexFilename) > 0 {
env = append(os.Environ(), "GIT_INDEX_FILE="+indexFilename[0])
}
- _, _, err := NewCommand(repo.Ctx, "read-tree").AddDynamicArguments(id.String()).RunStdString(&RunOpts{Dir: repo.Path, Env: env})
+ _, _, err := NewCommand("read-tree").AddDynamicArguments(id.String()).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path, Env: env})
if err != nil {
return err
}
@@ -59,43 +58,35 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena
}
}()
- removeDirFn := func(dir string) func() { // it can't use the return value "tmpDir" directly because it is empty when error occurs
- return func() {
- if err := util.RemoveAll(dir); err != nil {
- log.Error("failed to remove tmp index dir: %v", err)
- }
- }
- }
-
- tmpDir, err = os.MkdirTemp("", "index")
+ tmpDir, cancel, err = setting.AppDataTempDir("git-repo-content").MkdirTempRandom("index")
if err != nil {
return "", "", nil, err
}
tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index")
- cancel = removeDirFn(tmpDir)
+
err = repo.ReadTreeToIndex(treeish, tmpIndexFilename)
if err != nil {
return "", "", cancel, err
}
- return tmpIndexFilename, tmpDir, cancel, err
+ return tmpIndexFilename, tmpDir, cancel, nil
}
// EmptyIndex empties the index
func (repo *Repository) EmptyIndex() error {
- _, _, err := NewCommand(repo.Ctx, "read-tree", "--empty").RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("read-tree", "--empty").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// LsFiles checks if the given filenames are in the index
func (repo *Repository) LsFiles(filenames ...string) ([]string, error) {
- cmd := NewCommand(repo.Ctx, "ls-files", "-z").AddDashesAndList(filenames...)
- res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ cmd := NewCommand("ls-files", "-z").AddDashesAndList(filenames...)
+ res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
filelist := make([]string, 0, len(filenames))
- for _, line := range bytes.Split(res, []byte{'\000'}) {
+ for line := range bytes.SplitSeq(res, []byte{'\000'}) {
filelist = append(filelist, string(line))
}
@@ -108,7 +99,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
if err != nil {
return err
}
- cmd := NewCommand(repo.Ctx, "update-index", "--remove", "-z", "--index-info")
+ cmd := NewCommand("update-index", "--remove", "-z", "--index-info")
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
buffer := new(bytes.Buffer)
@@ -118,7 +109,7 @@ func (repo *Repository) RemoveFilesFromIndex(filenames ...string) error {
buffer.WriteString("0 blob " + objectFormat.EmptyObjectID().String() + "\t" + file + "\000")
}
}
- return cmd.Run(&RunOpts{
+ return cmd.Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout,
@@ -134,7 +125,7 @@ type IndexObjectInfo struct {
// AddObjectsToIndex adds the provided object hashes to the index at the provided filenames
func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error {
- cmd := NewCommand(repo.Ctx, "update-index", "--add", "--replace", "-z", "--index-info")
+ cmd := NewCommand("update-index", "--add", "--replace", "-z", "--index-info")
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
buffer := new(bytes.Buffer)
@@ -142,7 +133,7 @@ func (repo *Repository) AddObjectsToIndex(objects ...IndexObjectInfo) error {
// using format: mode SP type SP sha1 TAB path
buffer.WriteString(object.Mode + " blob " + object.Object.String() + "\t" + object.Filename + "\000")
}
- return cmd.Run(&RunOpts{
+ return cmd.Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdin: bytes.NewReader(buffer.Bytes()),
Stdout: stdout,
@@ -157,7 +148,7 @@ func (repo *Repository) AddObjectToIndex(mode string, object ObjectID, filename
// WriteTree writes the current index as a tree to the object db and returns its hash
func (repo *Repository) WriteTree() (*Tree, error) {
- stdout, _, runErr := NewCommand(repo.Ctx, "write-tree").RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("write-tree").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go
index 3d48b91c6d..08e0413311 100644
--- a/modules/git/repo_object.go
+++ b/modules/git/repo_object.go
@@ -68,13 +68,13 @@ func (repo *Repository) HashObject(reader io.Reader) (ObjectID, error) {
func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error) {
var cmd *Command
if save {
- cmd = NewCommand(repo.Ctx, "hash-object", "-w", "--stdin")
+ cmd = NewCommand("hash-object", "-w", "--stdin")
} else {
- cmd = NewCommand(repo.Ctx, "hash-object", "--stdin")
+ cmd = NewCommand("hash-object", "--stdin")
}
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
- err := cmd.Run(&RunOpts{
+ err := cmd.Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdin: reader,
Stdout: stdout,
@@ -85,17 +85,3 @@ func (repo *Repository) hashObject(reader io.Reader, save bool) (string, error)
}
return strings.TrimSpace(stdout.String()), nil
}
-
-// GetRefType gets the type of the ref based on the string
-func (repo *Repository) GetRefType(ref string) ObjectType {
- if repo.IsTagExist(ref) {
- return ObjectTag
- } else if repo.IsBranchExist(ref) {
- return ObjectBranch
- } else if repo.IsCommitExist(ref) {
- return ObjectCommit
- } else if _, err := repo.GetBlob(ref); err == nil {
- return ObjectBlob
- }
- return ObjectType("invalid")
-}
diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go
index 850ec65502..554f9f73e1 100644
--- a/modules/git/repo_ref.go
+++ b/modules/git/repo_ref.go
@@ -18,15 +18,16 @@ func (repo *Repository) GetRefs() ([]*Reference, error) {
// 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 := NewCommand(ctx)
- if refType == "branch" {
+ cmd := NewCommand()
+ switch refType {
+ case "branch":
cmd.AddArguments("branch")
- } else if refType == "tag" {
+ case "tag":
cmd.AddArguments("tag")
- } else {
+ 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).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
diff --git a/modules/git/repo_ref_nogogit.go b/modules/git/repo_ref_nogogit.go
index ac53d661b5..8d34713eaf 100644
--- a/modules/git/repo_ref_nogogit.go
+++ b/modules/git/repo_ref_nogogit.go
@@ -21,7 +21,7 @@ func (repo *Repository) GetRefsFiltered(pattern string) ([]*Reference, error) {
go func() {
stderrBuilder := &strings.Builder{}
- err := NewCommand(repo.Ctx, "for-each-ref").Run(&RunOpts{
+ err := NewCommand("for-each-ref").Run(repo.Ctx, &RunOpts{
Dir: repo.Path,
Stdout: stdoutWriter,
Stderr: stderrBuilder,
diff --git a/modules/git/repo_stats.go b/modules/git/repo_stats.go
index 83220104bd..8c6f31c38c 100644
--- a/modules/git/repo_stats.go
+++ b/modules/git/repo_stats.go
@@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
since := fromTime.Format(time.RFC3339)
- stdout, _, runErr := NewCommand(repo.Ctx, "rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
+ AddOptionFormat("--since=%s", since).
+ RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if runErr != nil {
return nil, runErr
}
@@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
_ = stdoutWriter.Close()
}()
- gitCmd := NewCommand(repo.Ctx, "log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
+ gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
+ AddOptionFormat("--since=%s", since)
if len(branch) == 0 {
gitCmd.AddArguments("--branches=*")
} else {
@@ -68,7 +71,7 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
}
stderr := new(strings.Builder)
- err = gitCmd.Run(&RunOpts{
+ err = gitCmd.Run(repo.Ctx, &RunOpts{
Env: []string{},
Dir: repo.Path,
Stdout: stdoutWriter,
diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go
index 3d032385ee..85d8807a6e 100644
--- a/modules/git/repo_stats_test.go
+++ b/modules/git/repo_stats_test.go
@@ -30,7 +30,7 @@ func TestRepository_GetCodeActivityStats(t *testing.T) {
assert.EqualValues(t, 10, code.Additions)
assert.EqualValues(t, 1, code.Deletions)
assert.Len(t, code.Authors, 3)
- assert.EqualValues(t, "tris.git@shoddynet.org", code.Authors[1].Email)
+ assert.Equal(t, "tris.git@shoddynet.org", code.Authors[1].Email)
assert.EqualValues(t, 3, code.Authors[1].Commits)
assert.EqualValues(t, 5, code.Authors[0].Commits)
}
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
index 2026a4c9f5..c8d72eee02 100644
--- a/modules/git/repo_tag.go
+++ b/modules/git/repo_tag.go
@@ -5,7 +5,6 @@
package git
import (
- "context"
"fmt"
"io"
"strings"
@@ -17,20 +16,15 @@ import (
// TagPrefix tags prefix path on the repository
const TagPrefix = "refs/tags/"
-// IsTagExist returns true if given tag exists in the repository.
-func IsTagExist(ctx context.Context, repoPath, name string) bool {
- return IsReferenceExist(ctx, repoPath, TagPrefix+name)
-}
-
// CreateTag create one tag in the repository
func (repo *Repository) CreateTag(name, revision string) error {
- _, _, err := NewCommand(repo.Ctx, "tag").AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("tag").AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
// CreateAnnotatedTag create one annotated tag in the repository
func (repo *Repository) CreateAnnotatedTag(name, message, revision string) error {
- _, _, err := NewCommand(repo.Ctx, "tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(&RunOpts{Dir: repo.Path})
+ _, _, err := NewCommand("tag", "-a", "-m").AddDynamicArguments(message).AddDashesAndList(name, revision).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
return err
}
@@ -40,13 +34,13 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
return "", fmt.Errorf("SHA is too short: %s", sha)
}
- stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags", "-d").RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("show-ref", "--tags", "-d").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return "", err
}
- tagRefs := strings.Split(stdout, "\n")
- for _, tagRef := range tagRefs {
+ tagRefs := strings.SplitSeq(stdout, "\n")
+ for tagRef := range tagRefs {
if len(strings.TrimSpace(tagRef)) > 0 {
fields := strings.Fields(tagRef)
if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) {
@@ -63,12 +57,12 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) {
// GetTagID returns the object ID for a tag (annotated tags have both an object SHA AND a commit SHA)
func (repo *Repository) GetTagID(name string) (string, error) {
- stdout, _, err := NewCommand(repo.Ctx, "show-ref", "--tags").AddDashesAndList(name).RunStdString(&RunOpts{Dir: repo.Path})
+ stdout, _, err := NewCommand("show-ref", "--tags").AddDashesAndList(name).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return "", err
}
// Make sure exact match is used: "v1" != "release/v1"
- for _, line := range strings.Split(stdout, "\n") {
+ for line := range strings.SplitSeq(stdout, "\n") {
fields := strings.Fields(line)
if len(fields) == 2 && fields[1] == "refs/tags/"+name {
return fields[0], nil
@@ -123,9 +117,9 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) {
rc := &RunOpts{Dir: repo.Path, Stdout: stdoutWriter, Stderr: &stderr}
go func() {
- err := NewCommand(repo.Ctx, "for-each-ref").
+ err := NewCommand("for-each-ref").
AddOptionFormat("--format=%s", forEachRefFmt.Flag()).
- AddArguments("--sort", "-*creatordate", "refs/tags").Run(rc)
+ AddArguments("--sort", "-*creatordate", "refs/tags").Run(repo.Ctx, rc)
if err != nil {
_ = stdoutWriter.CloseWithError(ConcatenateError(err, stderr.String()))
} else {
diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go
index e0a3104249..3d2b4f52bd 100644
--- a/modules/git/repo_tag_nogogit.go
+++ b/modules/git/repo_tag_nogogit.go
@@ -41,8 +41,11 @@ func (repo *Repository) GetTagType(id ObjectID) (string, error) {
return "", err
}
_, typ, _, err := ReadBatchLine(rd)
- if IsErrNotExist(err) {
- return "", ErrNotExist{ID: id.String()}
+ if err != nil {
+ if IsErrNotExist(err) {
+ return "", ErrNotExist{ID: id.String()}
+ }
+ return "", err
}
return typ, nil
}
diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go
index f1f081680a..f1f5ff6664 100644
--- a/modules/git/repo_tag_test.go
+++ b/modules/git/repo_tag_test.go
@@ -27,12 +27,12 @@ func TestRepository_GetTags(t *testing.T) {
}
assert.Len(t, tags, 2)
assert.Len(t, tags, total)
- assert.EqualValues(t, "signed-tag", tags[0].Name)
- assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String())
- assert.EqualValues(t, "tag", tags[0].Type)
- assert.EqualValues(t, "test", tags[1].Name)
- assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String())
- assert.EqualValues(t, "tag", tags[1].Type)
+ 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) {
@@ -64,18 +64,13 @@ func TestRepository_GetTag(t *testing.T) {
// and try to get the Tag for lightweight tag
lTag, err := bareRepo1.GetTag(lTagName)
- if err != nil {
- assert.NoError(t, err)
- return
- }
- if lTag == nil {
- assert.NotNil(t, lTag)
- assert.FailNow(t, "nil lTag: %s", lTagName)
- }
- assert.EqualValues(t, lTagName, lTag.Name)
- assert.EqualValues(t, lTagCommitID, lTag.ID.String())
- assert.EqualValues(t, lTagCommitID, lTag.Object.String())
- assert.EqualValues(t, "commit", lTag.Type)
+ require.NoError(t, err)
+ require.NotNil(t, lTag, "nil lTag: %s", lTagName)
+
+ assert.Equal(t, lTagName, lTag.Name)
+ assert.Equal(t, lTagCommitID, lTag.ID.String())
+ assert.Equal(t, lTagCommitID, lTag.Object.String())
+ assert.Equal(t, "commit", lTag.Type)
// ANNOTATED TAGS
aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0"
@@ -97,19 +92,14 @@ func TestRepository_GetTag(t *testing.T) {
}
aTag, err := bareRepo1.GetTag(aTagName)
- if err != nil {
- assert.NoError(t, err)
- return
- }
- if aTag == nil {
- assert.NotNil(t, aTag)
- assert.FailNow(t, "nil aTag: %s", aTagName)
- }
- assert.EqualValues(t, aTagName, aTag.Name)
- assert.EqualValues(t, aTagID, aTag.ID.String())
+ require.NoError(t, err)
+ require.NotNil(t, aTag, "nil aTag: %s", aTagName)
+
+ assert.Equal(t, aTagName, aTag.Name)
+ assert.Equal(t, aTagID, aTag.ID.String())
assert.NotEqual(t, aTagID, aTag.Object.String())
- assert.EqualValues(t, aTagCommitID, aTag.Object.String())
- assert.EqualValues(t, "tag", aTag.Type)
+ assert.Equal(t, aTagCommitID, aTag.Object.String())
+ assert.Equal(t, "tag", aTag.Type)
// RELEASE TAGS
@@ -127,14 +117,14 @@ func TestRepository_GetTag(t *testing.T) {
assert.NoError(t, err)
return
}
- assert.EqualValues(t, rTagCommitID, rTagID)
+ assert.Equal(t, rTagCommitID, rTagID)
oTagID, err := bareRepo1.GetTagID(lTagName)
if err != nil {
assert.NoError(t, err)
return
}
- assert.EqualValues(t, lTagCommitID, oTagID)
+ assert.Equal(t, lTagCommitID, oTagID)
}
func TestRepository_GetAnnotatedTag(t *testing.T) {
@@ -170,9 +160,9 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
return
}
assert.NotNil(t, tag)
- assert.EqualValues(t, aTagName, tag.Name)
- assert.EqualValues(t, aTagID, tag.ID.String())
- assert.EqualValues(t, "tag", tag.Type)
+ assert.Equal(t, aTagName, tag.Name)
+ assert.Equal(t, aTagID, tag.ID.String())
+ assert.Equal(t, "tag", tag.Type)
// Annotated tag's Commit ID should fail
tag2, err := bareRepo1.GetAnnotatedTag(aTagCommitID)
diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go
index 9db78153a1..4638bdac1f 100644
--- a/modules/git/repo_test.go
+++ b/modules/git/repo_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"path/filepath"
"testing"
@@ -33,21 +32,21 @@ func TestRepoIsEmpty(t *testing.T) {
func TestRepoGetDivergingCommits(t *testing.T) {
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
- do, err := GetDivergingCommits(context.Background(), bareRepo1Path, "master", "branch2")
+ do, err := GetDivergingCommits(t.Context(), bareRepo1Path, "master", "branch2")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 1,
Behind: 5,
}, do)
- do, err = GetDivergingCommits(context.Background(), bareRepo1Path, "master", "master")
+ do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "master")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 0,
Behind: 0,
}, do)
- do, err = GetDivergingCommits(context.Background(), bareRepo1Path, "master", "test")
+ do, err = GetDivergingCommits(t.Context(), bareRepo1Path, "master", "test")
assert.NoError(t, err)
assert.Equal(t, DivergeObject{
Ahead: 0,
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
index ab48d47d13..309a73d759 100644
--- a/modules/git/repo_tree.go
+++ b/modules/git/repo_tree.go
@@ -15,7 +15,7 @@ import (
type CommitTreeOpts struct {
Parents []string
Message string
- KeyID string
+ Key *SigningKey
NoGPGSign bool
AlwaysSign bool
}
@@ -33,7 +33,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
"GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
- cmd := NewCommand(repo.Ctx, "commit-tree").AddDynamicArguments(tree.ID.String())
+ cmd := NewCommand("commit-tree").AddDynamicArguments(tree.ID.String())
for _, parent := range opts.Parents {
cmd.AddArguments("-p").AddDynamicArguments(parent)
@@ -43,8 +43,13 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
_, _ = messageBytes.WriteString(opts.Message)
_, _ = messageBytes.WriteString("\n")
- if opts.KeyID != "" || opts.AlwaysSign {
- cmd.AddOptionFormat("-S%s", opts.KeyID)
+ if opts.Key != nil {
+ if opts.Key.Format != "" {
+ cmd.AddConfig("gpg.format", opts.Key.Format)
+ }
+ cmd.AddOptionFormat("-S%s", opts.Key.KeyID)
+ } else if opts.AlwaysSign {
+ cmd.AddOptionFormat("-S")
}
if opts.NoGPGSign {
@@ -53,7 +58,7 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
- err := cmd.Run(&RunOpts{
+ err := cmd.Run(repo.Ctx, &RunOpts{
Env: env,
Dir: repo.Path,
Stdin: messageBytes,
diff --git a/modules/git/repo_tree_gogit.go b/modules/git/repo_tree_gogit.go
index 651794a5aa..f77cd83612 100644
--- a/modules/git/repo_tree_gogit.go
+++ b/modules/git/repo_tree_gogit.go
@@ -36,7 +36,7 @@ func (repo *Repository) GetTree(idStr string) (*Tree, error) {
}
if len(idStr) != objectFormat.FullLength() {
- res, _, err := NewCommand(repo.Ctx, "rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(&RunOpts{Dir: repo.Path})
+ res, _, err := NewCommand("rev-parse", "--verify").AddDynamicArguments(idStr).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go
index d74769ccb2..1954f85162 100644
--- a/modules/git/repo_tree_nogogit.go
+++ b/modules/git/repo_tree_nogogit.go
@@ -35,7 +35,11 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
if err != nil {
return nil, err
}
- commit, err := tag.Commit(repo)
+
+ if _, err := wr.Write([]byte(tag.Object.String() + "\n")); err != nil {
+ return nil, err
+ }
+ commit, err := repo.getCommitFromBatchReader(wr, rd, tag.Object)
if err != nil {
return nil, err
}
diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go
index 92681feea9..b9b5aff61b 100644
--- a/modules/git/signature_test.go
+++ b/modules/git/signature_test.go
@@ -42,6 +42,6 @@ func TestParseSignatureFromCommitLine(t *testing.T) {
}
for _, test := range tests {
got := parseSignatureFromCommitLine(test.line)
- assert.EqualValues(t, test.want, got)
+ assert.Equal(t, test.want, got)
}
}
diff --git a/modules/git/submodule.go b/modules/git/submodule.go
index 017b644052..31a32f1a9e 100644
--- a/modules/git/submodule.go
+++ b/modules/git/submodule.go
@@ -45,7 +45,7 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul
return scanner.Err()
},
}
- err = NewCommand(ctx, "ls-tree", "-r", "--", "HEAD").Run(opts)
+ err = NewCommand("ls-tree", "-r", "--", "HEAD").Run(ctx, opts)
if err != nil {
return nil, fmt.Errorf("GetTemplateSubmoduleCommits: error running git ls-tree: %v", err)
}
@@ -56,8 +56,8 @@ func GetTemplateSubmoduleCommits(ctx context.Context, repoPath string) (submodul
// It is only for generating new repos based on existing template, requires the .gitmodules file to be already present in the work dir.
func AddTemplateSubmoduleIndexes(ctx context.Context, repoPath string, submodules []TemplateSubmoduleCommit) error {
for _, submodule := range submodules {
- cmd := NewCommand(ctx, "update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path)
- if stdout, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}); err != nil {
+ cmd := NewCommand("update-index", "--add", "--cacheinfo", "160000").AddDynamicArguments(submodule.Commit, submodule.Path)
+ if stdout, _, err := cmd.RunStdString(ctx, &RunOpts{Dir: repoPath}); err != nil {
log.Error("Unable to add %s as submodule to repo %s: stdout %s\nError: %v", submodule.Path, repoPath, stdout, err)
return err
}
diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go
index d53946a27d..7893b95e3a 100644
--- a/modules/git/submodule_test.go
+++ b/modules/git/submodule_test.go
@@ -4,7 +4,6 @@
package git
import (
- "context"
"os"
"path/filepath"
"testing"
@@ -20,29 +19,29 @@ func TestGetTemplateSubmoduleCommits(t *testing.T) {
assert.Len(t, submodules, 2)
- assert.EqualValues(t, "<°)))><", submodules[0].Path)
- assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit)
+ assert.Equal(t, "<°)))><", submodules[0].Path)
+ assert.Equal(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit)
- assert.EqualValues(t, "libtest", submodules[1].Path)
- assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit)
+ assert.Equal(t, "libtest", submodules[1].Path)
+ assert.Equal(t, "1234567890123456789012345678901234567890", submodules[1].Commit)
}
func TestAddTemplateSubmoduleIndexes(t *testing.T) {
- ctx := context.Background()
+ ctx := t.Context()
tmpDir := t.TempDir()
var err error
- _, _, err = NewCommand(ctx, "init").RunStdString(&RunOpts{Dir: tmpDir})
+ _, _, err = NewCommand("init").RunStdString(ctx, &RunOpts{Dir: tmpDir})
require.NoError(t, err)
_ = os.Mkdir(filepath.Join(tmpDir, "new-dir"), 0o755)
err = AddTemplateSubmoduleIndexes(ctx, tmpDir, []TemplateSubmoduleCommit{{Path: "new-dir", Commit: "1234567890123456789012345678901234567890"}})
require.NoError(t, err)
- _, _, err = NewCommand(ctx, "add", "--all").RunStdString(&RunOpts{Dir: tmpDir})
+ _, _, err = NewCommand("add", "--all").RunStdString(ctx, &RunOpts{Dir: tmpDir})
require.NoError(t, err)
- _, _, err = NewCommand(ctx, "-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(&RunOpts{Dir: tmpDir})
+ _, _, err = NewCommand("-c", "user.name=a", "-c", "user.email=b", "commit", "-m=test").RunStdString(ctx, &RunOpts{Dir: tmpDir})
require.NoError(t, err)
submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir)
require.NoError(t, err)
assert.Len(t, submodules, 1)
- assert.EqualValues(t, "new-dir", submodules[0].Path)
- assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit)
+ assert.Equal(t, "new-dir", submodules[0].Path)
+ assert.Equal(t, "1234567890123456789012345678901234567890", submodules[0].Commit)
}
diff --git a/modules/git/tag.go b/modules/git/tag.go
index f7666aa89b..8bf3658d62 100644
--- a/modules/git/tag.go
+++ b/modules/git/tag.go
@@ -21,11 +21,6 @@ type Tag struct {
Signature *CommitSignature
}
-// Commit return the commit of the tag reference
-func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) {
- return gitRepo.getCommit(tag.Object)
-}
-
func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) {
pos := messageStart
signStart, signEnd := -1, -1
diff --git a/modules/git/tests/repos/language_stats_repo/config b/modules/git/tests/repos/language_stats_repo/config
index 515f483629..a4ef456cbc 100644
--- a/modules/git/tests/repos/language_stats_repo/config
+++ b/modules/git/tests/repos/language_stats_repo/config
@@ -1,5 +1,5 @@
[core]
repositoryformatversion = 0
filemode = true
- bare = false
+ bare = true
logallrefupdates = true
diff --git a/modules/git/tests/repos/repo3_notes/config b/modules/git/tests/repos/repo3_notes/config
index d545cdabdb..5ed22e23d1 100644
--- a/modules/git/tests/repos/repo3_notes/config
+++ b/modules/git/tests/repos/repo3_notes/config
@@ -1,7 +1,7 @@
[core]
repositoryformatversion = 0
filemode = false
- bare = false
+ bare = true
logallrefupdates = true
symlinks = false
ignorecase = true
diff --git a/modules/git/tests/repos/repo4_commitsbetween/config b/modules/git/tests/repos/repo4_commitsbetween/config
index d545cdabdb..5ed22e23d1 100644
--- a/modules/git/tests/repos/repo4_commitsbetween/config
+++ b/modules/git/tests/repos/repo4_commitsbetween/config
@@ -1,7 +1,7 @@
[core]
repositoryformatversion = 0
filemode = false
- bare = false
+ bare = true
logallrefupdates = true
symlinks = false
ignorecase = true
diff --git a/modules/git/tree.go b/modules/git/tree.go
index 5a644f6c87..38fb45f3b1 100644
--- a/modules/git/tree.go
+++ b/modules/git/tree.go
@@ -48,15 +48,15 @@ func (t *Tree) SubTree(rpath string) (*Tree, error) {
// LsTree checks if the given filenames are in the tree
func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error) {
- cmd := NewCommand(repo.Ctx, "ls-tree", "-z", "--name-only").
+ cmd := NewCommand("ls-tree", "-z", "--name-only").
AddDashesAndList(append([]string{ref}, filenames...)...)
- res, _, err := cmd.RunStdBytes(&RunOpts{Dir: repo.Path})
+ res, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
filelist := make([]string, 0, len(filenames))
- for _, line := range bytes.Split(res, []byte{'\000'}) {
+ for line := range bytes.SplitSeq(res, []byte{'\000'}) {
filelist = append(filelist, string(line))
}
@@ -65,9 +65,9 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error
// GetTreePathLatestCommit returns the latest commit of a tree path
func (repo *Repository) GetTreePathLatestCommit(refName, treePath string) (*Commit, error) {
- stdout, _, err := NewCommand(repo.Ctx, "rev-list", "-1").
+ stdout, _, err := NewCommand("rev-list", "-1").
AddDynamicArguments(refName).AddDashesAndList(treePath).
- RunStdString(&RunOpts{Dir: repo.Path})
+ RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err != nil {
return nil, err
}
diff --git a/modules/git/tree_blob_gogit.go b/modules/git/tree_blob_gogit.go
index 92c25cb92c..f29e8f8b9e 100644
--- a/modules/git/tree_blob_gogit.go
+++ b/modules/git/tree_blob_gogit.go
@@ -21,6 +21,7 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
return &TreeEntry{
ID: t.ID,
// Type: ObjectTree,
+ ptree: t,
gogitTreeEntry: &object.TreeEntry{
Name: "",
Mode: filemode.Dir,
diff --git a/modules/git/tree_blob_nogogit.go b/modules/git/tree_blob_nogogit.go
index b7bcf40edd..b18d0fa05e 100644
--- a/modules/git/tree_blob_nogogit.go
+++ b/modules/git/tree_blob_nogogit.go
@@ -11,7 +11,7 @@ import (
)
// GetTreeEntryByPath get the tree entries according the sub dir
-func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
+func (t *Tree) GetTreeEntryByPath(relpath string) (_ *TreeEntry, err error) {
if len(relpath) == 0 {
return &TreeEntry{
ptree: t,
@@ -21,27 +21,25 @@ func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
}, nil
}
- // FIXME: This should probably use git cat-file --batch to be a bit more efficient
relpath = path.Clean(relpath)
parts := strings.Split(relpath, "/")
- var err error
+
tree := t
- for i, name := range parts {
- if i == len(parts)-1 {
- entries, err := tree.ListEntries()
- if err != nil {
- return nil, err
- }
- for _, v := range entries {
- if v.Name() == name {
- return v, nil
- }
- }
- } else {
- tree, err = tree.SubTree(name)
- if err != nil {
- return nil, err
- }
+ for _, name := range parts[:len(parts)-1] {
+ tree, err = tree.SubTree(name)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ name := parts[len(parts)-1]
+ entries, err := tree.ListEntries()
+ if err != nil {
+ return nil, err
+ }
+ for _, v := range entries {
+ if v.Name() == name {
+ return v, nil
}
}
return nil, ErrNotExist{"", relpath}
diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go
index 9513121487..5099d8ee79 100644
--- a/modules/git/tree_entry.go
+++ b/modules/git/tree_entry.go
@@ -5,9 +5,11 @@
package git
import (
- "io"
+ "path"
"sort"
"strings"
+
+ "code.gitea.io/gitea/modules/util"
)
// Type returns the type of the entry (commit, tree, blob)
@@ -22,83 +24,57 @@ func (te *TreeEntry) Type() string {
}
}
-// FollowLink returns the entry pointed to by a symlink
-func (te *TreeEntry) FollowLink() (*TreeEntry, error) {
+type EntryFollowResult struct {
+ SymlinkContent string
+ TargetFullPath string
+ TargetEntry *TreeEntry
+}
+
+func EntryFollowLink(commit *Commit, fullPath string, te *TreeEntry) (*EntryFollowResult, error) {
if !te.IsLink() {
- return nil, ErrBadLink{te.Name(), "not a symlink"}
+ return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q is not a symlink", fullPath)
}
- // read the link
- r, err := te.Blob().DataAsync()
- if err != nil {
- return nil, err
+ // git's filename max length is 4096, hopefully a link won't be longer than multiple of that
+ const maxSymlinkSize = 20 * 4096
+ if te.Blob().Size() > maxSymlinkSize {
+ return nil, util.ErrorWrap(util.ErrUnprocessableContent, "%q content exceeds symlink limit", fullPath)
}
- closed := false
- defer func() {
- if !closed {
- _ = r.Close()
- }
- }()
- buf := make([]byte, te.Size())
- _, err = io.ReadFull(r, buf)
+
+ link, err := te.Blob().GetBlobContent(maxSymlinkSize)
if err != nil {
return nil, err
}
- _ = r.Close()
- closed = true
-
- lnk := string(buf)
- t := te.ptree
-
- // traverse up directories
- for ; t != nil && strings.HasPrefix(lnk, "../"); lnk = lnk[3:] {
- t = t.ptree
+ if strings.HasPrefix(link, "/") {
+ // It's said that absolute path will be stored as is in Git
+ return &EntryFollowResult{SymlinkContent: link}, util.ErrorWrap(util.ErrUnprocessableContent, "%q is an absolute symlink", fullPath)
}
- if t == nil {
- return nil, ErrBadLink{te.Name(), "points outside of repo"}
- }
-
- target, err := t.GetTreeEntryByPath(lnk)
+ targetFullPath := path.Join(path.Dir(fullPath), link)
+ targetEntry, err := commit.GetTreeEntryByPath(targetFullPath)
if err != nil {
- if IsErrNotExist(err) {
- return nil, ErrBadLink{te.Name(), "broken link"}
- }
- return nil, err
+ return &EntryFollowResult{SymlinkContent: link}, err
}
- return target, nil
+ return &EntryFollowResult{SymlinkContent: link, TargetFullPath: targetFullPath, TargetEntry: targetEntry}, nil
}
-// FollowLinks returns the entry ultimately pointed to by a symlink
-func (te *TreeEntry) FollowLinks() (*TreeEntry, error) {
- if !te.IsLink() {
- return nil, ErrBadLink{te.Name(), "not a symlink"}
- }
- entry := te
- for i := 0; i < 999; i++ {
- if entry.IsLink() {
- next, err := entry.FollowLink()
- if err != nil {
- return nil, err
- }
- if next.ID == entry.ID {
- return nil, ErrBadLink{
- entry.Name(),
- "recursive link",
- }
- }
- entry = next
- } else {
+func EntryFollowLinks(commit *Commit, firstFullPath string, firstTreeEntry *TreeEntry, optLimit ...int) (res *EntryFollowResult, err error) {
+ limit := util.OptionalArg(optLimit, 10)
+ treeEntry, fullPath := firstTreeEntry, firstFullPath
+ for range limit {
+ res, err = EntryFollowLink(commit, fullPath, treeEntry)
+ if err != nil {
+ return res, err
+ }
+ treeEntry, fullPath = res.TargetEntry, res.TargetFullPath
+ if !treeEntry.IsLink() {
break
}
}
- if entry.IsLink() {
- return nil, ErrBadLink{
- te.Name(),
- "too many levels of symbolic links",
- }
+ if treeEntry.IsLink() {
+ return res, util.ErrorWrap(util.ErrUnprocessableContent, "%q has too many links", firstFullPath)
}
- return entry, nil
+ return res, nil
}
// returns the Tree pointed to by this TreeEntry, or nil if this is not a tree
diff --git a/modules/git/tree_entry_common_test.go b/modules/git/tree_entry_common_test.go
new file mode 100644
index 0000000000..8b63bbb993
--- /dev/null
+++ b/modules/git/tree_entry_common_test.go
@@ -0,0 +1,76 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/util"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestFollowLink(t *testing.T) {
+ r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare")
+ require.NoError(t, err)
+ defer r.Close()
+
+ commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123")
+ require.NoError(t, err)
+
+ // get the symlink
+ {
+ lnkFullPath := "foo/bar/link_to_hello"
+ lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
+ require.NoError(t, err)
+ assert.True(t, lnk.IsLink())
+
+ // should be able to dereference to target
+ res, err := EntryFollowLink(commit, lnkFullPath, lnk)
+ require.NoError(t, err)
+ assert.Equal(t, "hello", res.TargetEntry.Name())
+ assert.Equal(t, "foo/nar/hello", res.TargetFullPath)
+ assert.False(t, res.TargetEntry.IsLink())
+ assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", res.TargetEntry.ID.String())
+ }
+
+ {
+ // should error when called on a normal file
+ entry, err := commit.Tree.GetTreeEntryByPath("file1.txt")
+ require.NoError(t, err)
+ res, err := EntryFollowLink(commit, "file1.txt", entry)
+ assert.ErrorIs(t, err, util.ErrUnprocessableContent)
+ assert.Nil(t, res)
+ }
+
+ {
+ // should error for broken links
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/broken_link")
+ require.NoError(t, err)
+ assert.True(t, entry.IsLink())
+ res, err := EntryFollowLink(commit, "foo/broken_link", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "nar/broken_link", res.SymlinkContent)
+ }
+
+ {
+ // should error for external links
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/outside_repo")
+ require.NoError(t, err)
+ assert.True(t, entry.IsLink())
+ res, err := EntryFollowLink(commit, "foo/outside_repo", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "../../outside_repo", res.SymlinkContent)
+ }
+
+ {
+ // testing fix for short link bug
+ entry, err := commit.Tree.GetTreeEntryByPath("foo/link_short")
+ require.NoError(t, err)
+ res, err := EntryFollowLink(commit, "foo/link_short", entry)
+ assert.ErrorIs(t, err, util.ErrNotExist)
+ assert.Equal(t, "a", res.SymlinkContent)
+ }
+}
diff --git a/modules/git/tree_entry_gogit.go b/modules/git/tree_entry_gogit.go
index eb9b012681..e6845f1c77 100644
--- a/modules/git/tree_entry_gogit.go
+++ b/modules/git/tree_entry_gogit.go
@@ -19,16 +19,12 @@ type TreeEntry struct {
gogitTreeEntry *object.TreeEntry
ptree *Tree
- size int64
- sized bool
- fullName string
+ size int64
+ sized bool
}
// Name returns the name of the entry
func (te *TreeEntry) Name() string {
- if te.fullName != "" {
- return te.fullName
- }
return te.gogitTreeEntry.Name
}
@@ -55,7 +51,7 @@ func (te *TreeEntry) Size() int64 {
return te.size
}
-// IsSubModule if the entry is a sub module
+// IsSubModule if the entry is a submodule
func (te *TreeEntry) IsSubModule() bool {
return te.gogitTreeEntry.Mode == filemode.Submodule
}
diff --git a/modules/git/tree_entry_mode.go b/modules/git/tree_entry_mode.go
index a399118cf8..f36c07bc2a 100644
--- a/modules/git/tree_entry_mode.go
+++ b/modules/git/tree_entry_mode.go
@@ -3,7 +3,10 @@
package git
-import "strconv"
+import (
+ "fmt"
+ "strconv"
+)
// EntryMode the type of the object in the git tree
type EntryMode int
@@ -11,16 +14,15 @@ type EntryMode int
// There are only a few file modes in Git. They look like unix file modes, but they can only be
// one of these.
const (
- // EntryModeBlob
- EntryModeBlob EntryMode = 0o100644
- // EntryModeExec
- EntryModeExec EntryMode = 0o100755
- // EntryModeSymlink
+ // EntryModeNoEntry is possible if the file was added or removed in a commit. In the case of
+ // when adding the base commit doesn't have the file in its tree, a mode of 0o000000 is used.
+ EntryModeNoEntry EntryMode = 0o000000
+
+ EntryModeBlob EntryMode = 0o100644
+ EntryModeExec EntryMode = 0o100755
EntryModeSymlink EntryMode = 0o120000
- // EntryModeCommit
- EntryModeCommit EntryMode = 0o160000
- // EntryModeTree
- EntryModeTree EntryMode = 0o040000
+ EntryModeCommit EntryMode = 0o160000
+ EntryModeTree EntryMode = 0o040000
)
// String converts an EntryMode to a string
@@ -28,8 +30,46 @@ func (e EntryMode) String() string {
return strconv.FormatInt(int64(e), 8)
}
-// ToEntryMode converts a string to an EntryMode
-func ToEntryMode(value string) EntryMode {
- v, _ := strconv.ParseInt(value, 8, 32)
- return EntryMode(v)
+// IsSubModule if the entry is a submodule
+func (e EntryMode) IsSubModule() bool {
+ return e == EntryModeCommit
+}
+
+// IsDir if the entry is a sub dir
+func (e EntryMode) IsDir() bool {
+ return e == EntryModeTree
+}
+
+// IsLink if the entry is a symlink
+func (e EntryMode) IsLink() bool {
+ return e == EntryModeSymlink
+}
+
+// IsRegular if the entry is a regular file
+func (e EntryMode) IsRegular() bool {
+ return e == EntryModeBlob
+}
+
+// IsExecutable if the entry is an executable file (not necessarily binary)
+func (e EntryMode) IsExecutable() bool {
+ return e == EntryModeExec
+}
+
+func ParseEntryMode(mode string) (EntryMode, error) {
+ switch mode {
+ case "000000":
+ return EntryModeNoEntry, nil
+ case "100644":
+ return EntryModeBlob, nil
+ case "100755":
+ return EntryModeExec, nil
+ case "120000":
+ return EntryModeSymlink, nil
+ case "160000":
+ return EntryModeCommit, nil
+ case "040000", "040755": // git uses 040000 for tree object, but some users may get 040755 for unknown reasons
+ return EntryModeTree, nil
+ default:
+ return 0, fmt.Errorf("unparsable entry mode: %s", mode)
+ }
}
diff --git a/modules/git/tree_entry_nogogit.go b/modules/git/tree_entry_nogogit.go
index 81fb638d56..8fad96cdf8 100644
--- a/modules/git/tree_entry_nogogit.go
+++ b/modules/git/tree_entry_nogogit.go
@@ -18,7 +18,7 @@ type TreeEntry struct {
sized bool
}
-// Name returns the name of the entry
+// Name returns the name of the entry (base name)
func (te *TreeEntry) Name() string {
return te.name
}
@@ -57,29 +57,29 @@ func (te *TreeEntry) Size() int64 {
return te.size
}
-// IsSubModule if the entry is a sub module
+// IsSubModule if the entry is a submodule
func (te *TreeEntry) IsSubModule() bool {
- return te.entryMode == EntryModeCommit
+ return te.entryMode.IsSubModule()
}
// IsDir if the entry is a sub dir
func (te *TreeEntry) IsDir() bool {
- return te.entryMode == EntryModeTree
+ return te.entryMode.IsDir()
}
// IsLink if the entry is a symlink
func (te *TreeEntry) IsLink() bool {
- return te.entryMode == EntryModeSymlink
+ return te.entryMode.IsLink()
}
// IsRegular if the entry is a regular file
func (te *TreeEntry) IsRegular() bool {
- return te.entryMode == EntryModeBlob
+ return te.entryMode.IsRegular()
}
// IsExecutable if the entry is an executable file (not necessarily binary)
func (te *TreeEntry) IsExecutable() bool {
- return te.entryMode == EntryModeExec
+ return te.entryMode.IsExecutable()
}
// Blob returns the blob object the entry
diff --git a/modules/git/tree_entry_test.go b/modules/git/tree_entry_test.go
index 30eee13669..9ca82675e0 100644
--- a/modules/git/tree_entry_test.go
+++ b/modules/git/tree_entry_test.go
@@ -53,50 +53,3 @@ func TestEntriesCustomSort(t *testing.T) {
assert.Equal(t, "bcd", entries[6].Name())
assert.Equal(t, "abc", entries[7].Name())
}
-
-func TestFollowLink(t *testing.T) {
- r, err := openRepositoryWithDefaultContext("tests/repos/repo1_bare")
- assert.NoError(t, err)
- defer r.Close()
-
- commit, err := r.GetCommit("37991dec2c8e592043f47155ce4808d4580f9123")
- assert.NoError(t, err)
-
- // get the symlink
- lnk, err := commit.Tree.GetTreeEntryByPath("foo/bar/link_to_hello")
- assert.NoError(t, err)
- assert.True(t, lnk.IsLink())
-
- // should be able to dereference to target
- target, err := lnk.FollowLink()
- assert.NoError(t, err)
- assert.Equal(t, "hello", target.Name())
- assert.False(t, target.IsLink())
- assert.Equal(t, "b14df6442ea5a1b382985a6549b85d435376c351", target.ID.String())
-
- // should error when called on normal file
- target, err = commit.Tree.GetTreeEntryByPath("file1.txt")
- assert.NoError(t, err)
- _, err = target.FollowLink()
- assert.EqualError(t, err, "file1.txt: not a symlink")
-
- // should error for broken links
- target, err = commit.Tree.GetTreeEntryByPath("foo/broken_link")
- assert.NoError(t, err)
- assert.True(t, target.IsLink())
- _, err = target.FollowLink()
- assert.EqualError(t, err, "broken_link: broken link")
-
- // should error for external links
- target, err = commit.Tree.GetTreeEntryByPath("foo/outside_repo")
- assert.NoError(t, err)
- assert.True(t, target.IsLink())
- _, err = target.FollowLink()
- assert.EqualError(t, err, "outside_repo: points outside of repo")
-
- // testing fix for short link bug
- target, err = commit.Tree.GetTreeEntryByPath("foo/link_short")
- assert.NoError(t, err)
- _, err = target.FollowLink()
- assert.EqualError(t, err, "link_short: broken link")
-}
diff --git a/modules/git/tree_gogit.go b/modules/git/tree_gogit.go
index 421b0ecb0f..272b018ffd 100644
--- a/modules/git/tree_gogit.go
+++ b/modules/git/tree_gogit.go
@@ -69,7 +69,7 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
seen := map[plumbing.Hash]bool{}
walker := object.NewTreeWalker(t.gogitTree, true, seen)
for {
- fullName, entry, err := walker.Next()
+ _, entry, err := walker.Next()
if err == io.EOF {
break
}
@@ -84,7 +84,6 @@ func (t *Tree) ListEntriesRecursiveWithSize() (Entries, error) {
ID: ParseGogitHash(entry.Hash),
gogitTreeEntry: &entry,
ptree: t,
- fullName: fullName,
}
entries = append(entries, convertedEntry)
}
diff --git a/modules/git/tree_nogogit.go b/modules/git/tree_nogogit.go
index 993b98edc2..f88788418e 100644
--- a/modules/git/tree_nogogit.go
+++ b/modules/git/tree_nogogit.go
@@ -70,7 +70,7 @@ func (t *Tree) ListEntries() (Entries, error) {
}
}
- stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(&RunOpts{Dir: t.repo.Path})
+ stdout, _, runErr := NewCommand("ls-tree", "-l").AddDynamicArguments(t.ID.String()).RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path})
if runErr != nil {
if strings.Contains(runErr.Error(), "fatal: Not a valid object name") || strings.Contains(runErr.Error(), "fatal: not a tree object") {
return nil, ErrNotExist{
@@ -96,10 +96,10 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
return t.entriesRecursive, nil
}
- stdout, _, runErr := NewCommand(t.repo.Ctx, "ls-tree", "-t", "-r").
+ stdout, _, runErr := NewCommand("ls-tree", "-t", "-r").
AddArguments(extraArgs...).
AddDynamicArguments(t.ID.String()).
- RunStdBytes(&RunOpts{Dir: t.repo.Path})
+ RunStdBytes(t.repo.Ctx, &RunOpts{Dir: t.repo.Path})
if runErr != nil {
return nil, runErr
}
diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go
index 5fee64b038..cae11c4b1b 100644
--- a/modules/git/tree_test.go
+++ b/modules/git/tree_test.go
@@ -19,7 +19,7 @@ func TestSubTree_Issue29101(t *testing.T) {
assert.NoError(t, err)
// old code could produce a different error if called multiple times
- for i := 0; i < 10; i++ {
+ for range 10 {
_, err = commit.SubTree("file1.txt")
assert.Error(t, err)
assert.True(t, IsErrNotExist(err))
@@ -33,10 +33,10 @@ func Test_GetTreePathLatestCommit(t *testing.T) {
commitID, err := repo.GetBranchCommitID("master")
assert.NoError(t, err)
- assert.EqualValues(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID)
+ assert.Equal(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID)
commit, err := repo.GetTreePathLatestCommit("master", "blame.txt")
assert.NoError(t, err)
assert.NotNil(t, commit)
- assert.EqualValues(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String())
+ assert.Equal(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String())
}
diff --git a/modules/git/url/url.go b/modules/git/url/url.go
index 1c5e8377a6..aa6fa31c5e 100644
--- a/modules/git/url/url.go
+++ b/modules/git/url/url.go
@@ -133,12 +133,13 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er
}
}
- if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
+ switch parsed.URL.Scheme {
+ case "http", "https":
if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
return ret, nil
}
fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
- } else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
+ case "ssh", "git+ssh":
domainSSH := setting.SSH.Domain
domainCur := httplib.GuessCurrentHostDomain(ctx)
urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
@@ -166,9 +167,10 @@ func MakeRepositoryWebLink(repoURL *RepositoryURL) string {
// now, let's guess, for example:
// * git@github.com:owner/submodule.git
// * https://github.com/example/submodule1.git
- if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" {
+ switch repoURL.GitURL.Scheme {
+ case "http", "https":
return strings.TrimSuffix(repoURL.GitURL.String(), ".git")
- } else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" {
+ case "ssh", "git+ssh":
hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host)
hostname = util.IfZero(hostname, repoURL.GitURL.Host)
urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git")
diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go
index 9c020adb4d..6655c20be3 100644
--- a/modules/git/url/url_test.go
+++ b/modules/git/url/url_test.go
@@ -165,8 +165,8 @@ func TestParseGitURLs(t *testing.T) {
t.Run(kase.kase, func(t *testing.T) {
u, err := ParseGitURL(kase.kase)
assert.NoError(t, err)
- assert.EqualValues(t, kase.expected.extraMark, u.extraMark)
- assert.EqualValues(t, *kase.expected, *u)
+ assert.Equal(t, kase.expected.extraMark, u.extraMark)
+ assert.Equal(t, *kase.expected, *u)
})
}
}
@@ -179,7 +179,7 @@ func TestParseRepositoryURL(t *testing.T) {
ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
ctxReq.Host = ctxURL.Host
ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
- ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
+ ctx := context.WithValue(t.Context(), httplib.RequestContextKey, ctxReq)
cases := []struct {
input string
ownerName, repoName, remaining string
@@ -249,19 +249,19 @@ func TestMakeRepositoryBaseLink(t *testing.T) {
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
- u, err := ParseRepositoryURL(context.Background(), "https://localhost:3000/subpath/user/repo.git")
+ u, err := ParseRepositoryURL(t.Context(), "https://localhost:3000/subpath/user/repo.git")
assert.NoError(t, err)
assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u))
- u, err = ParseRepositoryURL(context.Background(), "https://github.com/owner/repo.git")
+ u, err = ParseRepositoryURL(t.Context(), "https://github.com/owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
- u, err = ParseRepositoryURL(context.Background(), "git@github.com:owner/repo.git")
+ u, err = ParseRepositoryURL(t.Context(), "git@github.com:owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
- u, err = ParseRepositoryURL(context.Background(), "git+ssh://other:123/owner/repo.git")
+ u, err = ParseRepositoryURL(t.Context(), "git+ssh://other:123/owner/repo.git")
assert.NoError(t, err)
assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u))
}
diff --git a/modules/git/utils.go b/modules/git/utils.go
index 56cba9087a..897306efd0 100644
--- a/modules/git/utils.go
+++ b/modules/git/utils.go
@@ -8,7 +8,6 @@ import (
"encoding/hex"
"fmt"
"io"
- "os"
"strconv"
"strings"
"sync"
@@ -41,33 +40,6 @@ func (oc *ObjectCache[T]) Get(id string) (T, bool) {
return obj, has
}
-// isDir returns true if given path is a directory,
-// or returns false when it's a file or does not exist.
-func isDir(dir string) bool {
- f, e := os.Stat(dir)
- if e != nil {
- return false
- }
- return f.IsDir()
-}
-
-// isFile returns true if given path is a file,
-// or returns false when it's a directory or does not exist.
-func isFile(filePath string) bool {
- f, e := os.Stat(filePath)
- if e != nil {
- return false
- }
- return !f.IsDir()
-}
-
-// isExist checks whether a file or directory exists.
-// It returns false when the file or directory does not exist.
-func isExist(path string) bool {
- _, err := os.Stat(path)
- return err == nil || os.IsExist(err)
-}
-
// ConcatenateError concatenats an error with stderr string
func ConcatenateError(err error, stderr string) error {
if len(stderr) == 0 {