diff --git a/refs.go b/refs.go new file mode 100644 index 000000000..a6f0d4c00 --- /dev/null +++ b/refs.go @@ -0,0 +1,73 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +func readPackedRefs(repoPath string) ([]string, error) { + path := repoPath + "/packed-refs" + + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil, nil + } + + refData, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + re := regexp.MustCompile("v\\d+\\.\\d+\\.\\d+$") + names := []string{} + + for _, ref := range bytes.Split(refData, []byte("\n")) { + if tag := re.Find(ref); tag != nil { + names = append(names, string(tag)) + } + } + + return names, nil +} + +func readRefDir(repoPath, prefix, relPath string) ([]string, error) { + dirPath := filepath.Join(repoPath, prefix, relPath) + f, err := os.Open(dirPath) + if err != nil { + return nil, err + } + defer f.Close() + + fis, err := f.Readdir(0) + if err != nil { + return nil, err + } + + names := make([]string, 0, len(fis)) + for _, fi := range fis { + if strings.Contains(fi.Name(), ".DS_Store") { + continue + } + + relFileName := filepath.Join(relPath, fi.Name()) + if fi.IsDir() { + subnames, err := readRefDir(repoPath, prefix, relFileName) + if err != nil { + return nil, err + } + names = append(names, subnames...) + continue + } + + names = append(names, relFileName) + } + + return names, nil +} diff --git a/repo_tag.go b/repo_tag.go index 11f1f3da7..8bcc28096 100644 --- a/repo_tag.go +++ b/repo_tag.go @@ -6,8 +6,6 @@ package git import ( "strings" - - "github.com/mcuadros/go-version" ) // TagPrefix tags prefix path on the repository @@ -130,28 +128,5 @@ func (repo *Repository) GetTagInfos() ([]*Tag, error) { // GetTags returns all tags of the repository. func (repo *Repository) GetTags() ([]string, error) { - cmd := NewCommand("tag", "-l") - if version.Compare(gitVersion, "2.0.0", ">=") { - cmd.AddArguments("--sort=-v:refname") - } - - stdout, err := cmd.RunInDir(repo.Path) - if err != nil { - return nil, err - } - - tags := strings.Split(stdout, "\n") - tags = tags[:len(tags)-1] - - if version.Compare(gitVersion, "2.0.0", "<") { - version.Sort(tags) - - // Reverse order - for i := 0; i < len(tags)/2; i++ { - j := len(tags) - i - 1 - tags[i], tags[j] = tags[j], tags[i] - } - } - - return tags, nil + return DefaultStorage.GetTags(repo.Path) } diff --git a/repo_test.go b/repo_test.go new file mode 100644 index 000000000..464ff29c4 --- /dev/null +++ b/repo_test.go @@ -0,0 +1,33 @@ +package git + +import "testing" + +func TestGetRefs(t *testing.T) { + expected := []string{"v1.0.0", "v1.2.1", "v2.0.0", "v3.0.0", "v3.1.0", "v3.10.1"} + + for _, p := range []string{ + "testdata/test_loose_refs.git", + "testdata/test_packed_refs.git", + "testdata/test_mixed_refs.git", + } { + r, err := OpenRepository(p) + if err != nil { + t.Fatal(err) + } + tags, err := r.GetTags() + if err != nil { + t.Fatal(err) + } + + if len(expected) != len(tags) { + t.Fatalf("[%s] wrong number of tags returned - expected [%d] got [%d]", p, len(expected), len(tags)) + } + + for i, v := range tags { + if expected[i] != v { + t.Fatalf("[%s] incorrect tag - expected [%s] got [%s]", p, expected[i], v) + } + } + } + +} diff --git a/storage.go b/storage.go new file mode 100644 index 000000000..774c4f4fa --- /dev/null +++ b/storage.go @@ -0,0 +1,87 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "path/filepath" + "strings" + + "github.com/Unknwon/com" + version "github.com/mcuadros/go-version" +) + +// Storage is an interface which describes how to fetch git objects from the git data storage placement. +type Storage interface { + GetTags(string) ([]string, error) +} + +// shellStorage is an implementation of Storage which use git shell to retrieve git objects from file system. +type shellStorage struct { +} + +// GetTags return the tag names according repoPath +func (shellStorage) GetTags(repoPath string) ([]string, error) { + cmd := NewCommand("tag", "-l") + if version.Compare(gitVersion, "2.0.0", ">=") { + cmd.AddArguments("--sort=-v:refname") + } + + stdout, err := cmd.RunInDir(repoPath) + if err != nil { + return nil, err + } + + tags := strings.Split(stdout, "\n") + tags = tags[:len(tags)-1] + + if version.Compare(gitVersion, "2.0.0", "<") { + version.Sort(tags) + + // Reverse order + for i := 0; i < len(tags)/2; i++ { + j := len(tags) - i - 1 + tags[i], tags[j] = tags[j], tags[i] + } + } + + return tags, nil +} + +type localStorage struct { +} + +// GetTags return the tag names according repoPath +func (localStorage) GetTags(repoPath string) ([]string, error) { + packed, err := readPackedRefs(repoPath) + if err != nil { + return nil, err + } + + if !com.IsExist(filepath.Join(repoPath, "refs/tags")) { + return packed, nil + } + + // Attempt loose files first as the /refs/tags folder should always + // exist whether it has files or not. + loose, err := readRefDir(repoPath, "refs/tags", "") + if err != nil { + return nil, err + } + + // If both loose refs and packed refs exist then it's highly + // likely that the loose refs are more recent than packed (created + // on top of packed older refs). Therefore we can append each + // together taking the packed refs first. + return append(packed, loose...), nil +} + +var ( + // ShellStorage provides methods to retrieve git objects via git shell + ShellStorage = new(shellStorage) + // LocalStorage provides methods to retrieve git objects via pure go + LocalStorage = new(localStorage) + // DefaultStorage is the default implementation of git data storage + DefaultStorage Storage = ShellStorage +) diff --git a/testdata/test_loose_refs.git/refs/tags/v1.0.0 b/testdata/test_loose_refs.git/refs/tags/v1.0.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v1.0.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_loose_refs.git/refs/tags/v1.2.1 b/testdata/test_loose_refs.git/refs/tags/v1.2.1 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v1.2.1 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_loose_refs.git/refs/tags/v2.0.0 b/testdata/test_loose_refs.git/refs/tags/v2.0.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v2.0.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_loose_refs.git/refs/tags/v3.0.0 b/testdata/test_loose_refs.git/refs/tags/v3.0.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v3.0.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_loose_refs.git/refs/tags/v3.1.0 b/testdata/test_loose_refs.git/refs/tags/v3.1.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v3.1.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_loose_refs.git/refs/tags/v3.10.1 b/testdata/test_loose_refs.git/refs/tags/v3.10.1 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_loose_refs.git/refs/tags/v3.10.1 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_mixed_refs.git/packed-refs b/testdata/test_mixed_refs.git/packed-refs new file mode 100644 index 000000000..bacc1721e --- /dev/null +++ b/testdata/test_mixed_refs.git/packed-refs @@ -0,0 +1,6 @@ +# pack-refs with: peeled fully-peeled +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/heads/master +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/remotes/origin/master +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v1.0.0 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v1.2.1 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v2.0.0 \ No newline at end of file diff --git a/testdata/test_mixed_refs.git/refs/tags/v3.0.0 b/testdata/test_mixed_refs.git/refs/tags/v3.0.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_mixed_refs.git/refs/tags/v3.0.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_mixed_refs.git/refs/tags/v3.1.0 b/testdata/test_mixed_refs.git/refs/tags/v3.1.0 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_mixed_refs.git/refs/tags/v3.1.0 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_mixed_refs.git/refs/tags/v3.10.1 b/testdata/test_mixed_refs.git/refs/tags/v3.10.1 new file mode 100644 index 000000000..9da2d06b9 --- /dev/null +++ b/testdata/test_mixed_refs.git/refs/tags/v3.10.1 @@ -0,0 +1 @@ +f821d04fc4f8ba72f88cf1880b69860c88a0a58b \ No newline at end of file diff --git a/testdata/test_packed_refs.git/packed-refs b/testdata/test_packed_refs.git/packed-refs new file mode 100644 index 000000000..96b63f072 --- /dev/null +++ b/testdata/test_packed_refs.git/packed-refs @@ -0,0 +1,9 @@ +# pack-refs with: peeled fully-peeled +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/heads/master +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/remotes/origin/master +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v1.0.0 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v1.2.1 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v2.0.0 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v3.0.0 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v3.1.0 +f821d04fc4f8ba72f88cf1880b69860c88a0a58b refs/tags/v3.10.1 \ No newline at end of file