aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/gogs/git-module
diff options
context:
space:
mode:
authorUnknwon <u@gogs.io>2018-05-27 09:07:15 +0800
committerUnknwon <u@gogs.io>2018-05-27 09:07:15 +0800
commite33d9e77f43e6829ea967e47964d13f5a8aec5cc (patch)
treeace1a09414a66fd7e293b3837865633168ba4f6e /vendor/github.com/gogs/git-module
parentaff42082441715559bb2e62e11550af1a946a8c9 (diff)
vendor: rename "gogits" to "gogs"
Diffstat (limited to 'vendor/github.com/gogs/git-module')
-rw-r--r--vendor/github.com/gogs/git-module/LICENSE19
-rw-r--r--vendor/github.com/gogs/git-module/README.md13
-rw-r--r--vendor/github.com/gogs/git-module/blob.go35
-rw-r--r--vendor/github.com/gogs/git-module/command.go150
-rw-r--r--vendor/github.com/gogs/git-module/commit.go310
-rw-r--r--vendor/github.com/gogs/git-module/commit_archive.go33
-rw-r--r--vendor/github.com/gogs/git-module/error.go61
-rw-r--r--vendor/github.com/gogs/git-module/git.go80
-rw-r--r--vendor/github.com/gogs/git-module/hook.go111
-rw-r--r--vendor/github.com/gogs/git-module/repo.go295
-rw-r--r--vendor/github.com/gogs/git-module/repo_branch.go126
-rw-r--r--vendor/github.com/gogs/git-module/repo_commit.go381
-rw-r--r--vendor/github.com/gogs/git-module/repo_diff.go400
-rw-r--r--vendor/github.com/gogs/git-module/repo_hook.go13
-rw-r--r--vendor/github.com/gogs/git-module/repo_object.go14
-rw-r--r--vendor/github.com/gogs/git-module/repo_pull.go81
-rw-r--r--vendor/github.com/gogs/git-module/repo_tag.go209
-rw-r--r--vendor/github.com/gogs/git-module/repo_tree.go26
-rw-r--r--vendor/github.com/gogs/git-module/sha1.go93
-rw-r--r--vendor/github.com/gogs/git-module/signature.go48
-rw-r--r--vendor/github.com/gogs/git-module/submodule.go78
-rw-r--r--vendor/github.com/gogs/git-module/tag.go65
-rw-r--r--vendor/github.com/gogs/git-module/tree.go149
-rw-r--r--vendor/github.com/gogs/git-module/tree_blob.go57
-rw-r--r--vendor/github.com/gogs/git-module/tree_entry.go226
-rw-r--r--vendor/github.com/gogs/git-module/utils.go93
26 files changed, 3166 insertions, 0 deletions
diff --git a/vendor/github.com/gogs/git-module/LICENSE b/vendor/github.com/gogs/git-module/LICENSE
new file mode 100644
index 00000000..176d8dfe
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 All Gogs Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE. \ No newline at end of file
diff --git a/vendor/github.com/gogs/git-module/README.md b/vendor/github.com/gogs/git-module/README.md
new file mode 100644
index 00000000..f9569ceb
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/README.md
@@ -0,0 +1,13 @@
+# Git Module [![Build Status](https://travis-ci.org/gogits/git-module.svg?branch=master)](https://travis-ci.org/gogits/git-module)
+
+Package git-module is a Go module for Git access through shell commands.
+
+## Limitations
+
+- Go version must be at least **1.4**.
+- Git version must be no less than **1.7.1**, and greater than or equal to **1.8.3** is recommended.
+- For Windows users, try use as new a version as possible.
+
+## License
+
+This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/vendor/github.com/gogs/git-module/blob.go b/vendor/github.com/gogs/git-module/blob.go
new file mode 100644
index 00000000..7731226d
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/blob.go
@@ -0,0 +1,35 @@
+// Copyright 2015 The Gogs 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"
+)
+
+// Blob represents a Git object.
+type Blob struct {
+ repo *Repository
+ *TreeEntry
+}
+
+// Data gets content of blob all at once and wrap it as io.Reader.
+// This can be very slow and memory consuming for huge content.
+func (b *Blob) Data() (io.Reader, error) {
+ stdout := new(bytes.Buffer)
+ stderr := new(bytes.Buffer)
+
+ // Preallocate memory to save ~50% memory usage on big files.
+ stdout.Grow(int(b.Size() + 2048))
+
+ if err := b.DataPipeline(stdout, stderr); err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+ return stdout, nil
+}
+
+func (b *Blob) DataPipeline(stdout, stderr io.Writer) error {
+ return NewCommand("show", b.ID.String()).RunInDirPipeline(b.repo.Path, stdout, stderr)
+}
diff --git a/vendor/github.com/gogs/git-module/command.go b/vendor/github.com/gogs/git-module/command.go
new file mode 100644
index 00000000..80caea08
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/command.go
@@ -0,0 +1,150 @@
+// Copyright 2015 The Gogs 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"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+)
+
+// Command represents a command with its subcommands or arguments.
+type Command struct {
+ name string
+ args []string
+ envs []string
+}
+
+func (c *Command) String() string {
+ if len(c.args) == 0 {
+ return c.name
+ }
+ return fmt.Sprintf("%s %s", c.name, strings.Join(c.args, " "))
+}
+
+// NewCommand creates and returns a new Git Command based on given command and arguments.
+func NewCommand(args ...string) *Command {
+ return &Command{
+ name: "git",
+ args: args,
+ }
+}
+
+// AddArguments adds new argument(s) to the command.
+func (c *Command) AddArguments(args ...string) *Command {
+ c.args = append(c.args, args...)
+ return c
+}
+
+// AddEnvs adds new environment variables to the command.
+func (c *Command) AddEnvs(envs ...string) *Command {
+ c.envs = append(c.envs, envs...)
+ return c
+}
+
+const DEFAULT_TIMEOUT = 60 * time.Second
+
+// RunInDirTimeoutPipeline executes the command in given directory with given timeout,
+// it pipes stdout and stderr to given io.Writer.
+func (c *Command) RunInDirTimeoutPipeline(timeout time.Duration, dir string, stdout, stderr io.Writer) error {
+ if timeout == -1 {
+ timeout = DEFAULT_TIMEOUT
+ }
+
+ if len(dir) == 0 {
+ log(c.String())
+ } else {
+ log("%s: %v", dir, c)
+ }
+
+ cmd := exec.Command(c.name, c.args...)
+ if c.envs != nil {
+ cmd.Env = append(os.Environ(), c.envs...)
+ }
+ cmd.Dir = dir
+ cmd.Stdout = stdout
+ cmd.Stderr = stderr
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+
+ done := make(chan error)
+ go func() {
+ done <- cmd.Wait()
+ }()
+
+ var err error
+ select {
+ case <-time.After(timeout):
+ if cmd.Process != nil && cmd.ProcessState != nil && !cmd.ProcessState.Exited() {
+ if err := cmd.Process.Kill(); err != nil {
+ return fmt.Errorf("fail to kill process: %v", err)
+ }
+ }
+
+ <-done
+ return ErrExecTimeout{timeout}
+ case err = <-done:
+ }
+
+ return err
+}
+
+// RunInDirTimeout executes the command in given directory with given timeout,
+// and returns stdout in []byte and error (combined with stderr).
+func (c *Command) RunInDirTimeout(timeout time.Duration, dir string) ([]byte, error) {
+ stdout := new(bytes.Buffer)
+ stderr := new(bytes.Buffer)
+ if err := c.RunInDirTimeoutPipeline(timeout, dir, stdout, stderr); err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+
+ if stdout.Len() > 0 {
+ log("stdout:\n%s", stdout.Bytes()[:1024])
+ }
+ return stdout.Bytes(), nil
+}
+
+// RunInDirPipeline executes the command in given directory,
+// it pipes stdout and stderr to given io.Writer.
+func (c *Command) RunInDirPipeline(dir string, stdout, stderr io.Writer) error {
+ return c.RunInDirTimeoutPipeline(-1, dir, stdout, stderr)
+}
+
+// RunInDir executes the command in given directory
+// and returns stdout in []byte and error (combined with stderr).
+func (c *Command) RunInDirBytes(dir string) ([]byte, error) {
+ return c.RunInDirTimeout(-1, dir)
+}
+
+// RunInDir executes the command in given directory
+// and returns stdout in string and error (combined with stderr).
+func (c *Command) RunInDir(dir string) (string, error) {
+ stdout, err := c.RunInDirTimeout(-1, dir)
+ if err != nil {
+ return "", err
+ }
+ return string(stdout), nil
+}
+
+// RunTimeout executes the command in defualt working directory with given timeout,
+// and returns stdout in string and error (combined with stderr).
+func (c *Command) RunTimeout(timeout time.Duration) (string, error) {
+ stdout, err := c.RunInDirTimeout(timeout, "")
+ if err != nil {
+ return "", err
+ }
+ return string(stdout), nil
+}
+
+// Run executes the command in defualt working directory
+// and returns stdout in string and error (combined with stderr).
+func (c *Command) Run() (string, error) {
+ return c.RunTimeout(-1)
+}
diff --git a/vendor/github.com/gogs/git-module/commit.go b/vendor/github.com/gogs/git-module/commit.go
new file mode 100644
index 00000000..64831d7f
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/commit.go
@@ -0,0 +1,310 @@
+// Copyright 2015 The Gogs 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 (
+ "bufio"
+ "bytes"
+ "container/list"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/mcuadros/go-version"
+)
+
+// Commit represents a git commit.
+type Commit struct {
+ Tree
+ ID sha1 // The ID of this commit object
+ Author *Signature
+ Committer *Signature
+ CommitMessage string
+
+ parents []sha1 // SHA1 strings
+ submoduleCache *objectCache
+}
+
+// Message returns the commit message. Same as retrieving CommitMessage directly.
+func (c *Commit) Message() string {
+ return c.CommitMessage
+}
+
+// Summary returns first line of commit message.
+func (c *Commit) Summary() string {
+ return strings.Split(c.CommitMessage, "\n")[0]
+}
+
+// ParentID returns oid of n-th parent (0-based index).
+// It returns nil if no such parent exists.
+func (c *Commit) ParentID(n int) (sha1, error) {
+ if n >= len(c.parents) {
+ return sha1{}, ErrNotExist{"", ""}
+ }
+ return c.parents[n], nil
+}
+
+// Parent returns n-th parent (0-based index) of the commit.
+func (c *Commit) Parent(n int) (*Commit, error) {
+ id, err := c.ParentID(n)
+ if err != nil {
+ return nil, err
+ }
+ parent, err := c.repo.getCommit(id)
+ if err != nil {
+ return nil, err
+ }
+ return parent, nil
+}
+
+// ParentCount returns number of parents of the commit.
+// 0 if this is the root commit, otherwise 1,2, etc.
+func (c *Commit) ParentCount() int {
+ return len(c.parents)
+}
+
+func isImageFile(data []byte) (string, bool) {
+ contentType := http.DetectContentType(data)
+ if strings.Index(contentType, "image/") != -1 {
+ return contentType, true
+ }
+ return contentType, false
+}
+
+func (c *Commit) IsImageFile(name string) bool {
+ blob, err := c.GetBlobByPath(name)
+ if err != nil {
+ return false
+ }
+
+ dataRc, err := blob.Data()
+ if err != nil {
+ return false
+ }
+ buf := make([]byte, 1024)
+ n, _ := dataRc.Read(buf)
+ buf = buf[:n]
+ _, isImage := isImageFile(buf)
+ return isImage
+}
+
+// GetCommitByPath return the commit of relative path object.
+func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
+ return c.repo.getCommitByPathWithID(c.ID, relpath)
+}
+
+// AddAllChanges marks local changes to be ready for commit.
+func AddChanges(repoPath string, all bool, files ...string) error {
+ cmd := NewCommand("add")
+ if all {
+ cmd.AddArguments("--all")
+ }
+ _, err := cmd.AddArguments(files...).RunInDir(repoPath)
+ return err
+}
+
+type CommitChangesOptions struct {
+ Committer *Signature
+ Author *Signature
+ Message string
+}
+
+// CommitChanges commits local changes with given committer, author and message.
+// If author is nil, it will be the same as committer.
+func CommitChanges(repoPath string, opts CommitChangesOptions) error {
+ cmd := NewCommand()
+ if opts.Committer != nil {
+ cmd.AddEnvs("GIT_COMMITTER_NAME="+opts.Committer.Name, "GIT_COMMITTER_EMAIL="+opts.Committer.Email)
+ }
+ cmd.AddArguments("commit")
+
+ if opts.Author == nil {
+ opts.Author = opts.Committer
+ }
+ if opts.Author != nil {
+ cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", opts.Author.Name, opts.Author.Email))
+ }
+ cmd.AddArguments("-m", opts.Message)
+
+ _, err := cmd.RunInDir(repoPath)
+ // No stderr but exit status 1 means nothing to commit.
+ if err != nil && err.Error() == "exit status 1" {
+ return nil
+ }
+ return err
+}
+
+func commitsCount(repoPath, revision, relpath string) (int64, error) {
+ var cmd *Command
+ isFallback := false
+ if version.Compare(gitVersion, "1.8.0", "<") {
+ isFallback = true
+ cmd = NewCommand("log", "--pretty=format:''")
+ } else {
+ cmd = NewCommand("rev-list", "--count")
+ }
+ cmd.AddArguments(revision)
+ if len(relpath) > 0 {
+ cmd.AddArguments("--", relpath)
+ }
+
+ stdout, err := cmd.RunInDir(repoPath)
+ if err != nil {
+ return 0, err
+ }
+
+ if isFallback {
+ return int64(strings.Count(stdout, "\n")) + 1, nil
+ }
+ return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
+}
+
+// CommitsCount returns number of total commits of until given revision.
+func CommitsCount(repoPath, revision string) (int64, error) {
+ return commitsCount(repoPath, revision, "")
+}
+
+func (c *Commit) CommitsCount() (int64, error) {
+ return CommitsCount(c.repo.Path, c.ID.String())
+}
+
+func (c *Commit) CommitsByRangeSize(page, size int) (*list.List, error) {
+ return c.repo.CommitsByRangeSize(c.ID.String(), page, size)
+}
+
+func (c *Commit) CommitsByRange(page int) (*list.List, error) {
+ return c.repo.CommitsByRange(c.ID.String(), page)
+}
+
+func (c *Commit) CommitsBefore() (*list.List, error) {
+ return c.repo.getCommitsBefore(c.ID)
+}
+
+func (c *Commit) CommitsBeforeLimit(num int) (*list.List, error) {
+ return c.repo.getCommitsBeforeLimit(c.ID, num)
+}
+
+func (c *Commit) CommitsBeforeUntil(commitID string) (*list.List, error) {
+ endCommit, err := c.repo.GetCommit(commitID)
+ if err != nil {
+ return nil, err
+ }
+ return c.repo.CommitsBetween(c, endCommit)
+}
+
+func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
+ return c.repo.searchCommits(c.ID, keyword)
+}
+
+func (c *Commit) GetFilesChangedSinceCommit(pastCommit string) ([]string, error) {
+ return c.repo.getFilesChanged(pastCommit, c.ID.String())
+}
+
+func (c *Commit) GetSubModules() (*objectCache, error) {
+ if c.submoduleCache != nil {
+ return c.submoduleCache, nil
+ }
+
+ entry, err := c.GetTreeEntryByPath(".gitmodules")
+ if err != nil {
+ return nil, err
+ }
+ rd, err := entry.Blob().Data()
+ if err != nil {
+ return nil, err
+ }
+
+ scanner := bufio.NewScanner(rd)
+ c.submoduleCache = newObjectCache()
+ var ismodule bool
+ var path string
+ for scanner.Scan() {
+ if strings.HasPrefix(scanner.Text(), "[submodule") {
+ ismodule = true
+ continue
+ }
+ if ismodule {
+ fields := strings.Split(scanner.Text(), "=")
+ k := strings.TrimSpace(fields[0])
+ if k == "path" {
+ path = strings.TrimSpace(fields[1])
+ } else if k == "url" {
+ c.submoduleCache.Set(path, &SubModule{path, strings.TrimSpace(fields[1])})
+ ismodule = false
+ }
+ }
+ }
+
+ return c.submoduleCache, nil
+}
+
+func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
+ modules, err := c.GetSubModules()
+ if err != nil {
+ return nil, err
+ }
+
+ module, has := modules.Get(entryname)
+ if has {
+ return module.(*SubModule), nil
+ }
+ return nil, nil
+}
+
+// CommitFileStatus represents status of files in a commit.
+type CommitFileStatus struct {
+ Added []string
+ Removed []string
+ Modified []string
+}
+
+func NewCommitFileStatus() *CommitFileStatus {
+ return &CommitFileStatus{
+ []string{}, []string{}, []string{},
+ }
+}
+
+// GetCommitFileStatus returns file status of commit in given repository.
+func GetCommitFileStatus(repoPath, commitID string) (*CommitFileStatus, error) {
+ stdout, w := io.Pipe()
+ done := make(chan struct{})
+ fileStatus := NewCommitFileStatus()
+ go func() {
+ scanner := bufio.NewScanner(stdout)
+ for scanner.Scan() {
+ fields := strings.Fields(scanner.Text())
+ if len(fields) < 2 {
+ continue
+ }
+
+ switch fields[0][0] {
+ case 'A':
+ fileStatus.Added = append(fileStatus.Added, fields[1])
+ case 'D':
+ fileStatus.Removed = append(fileStatus.Removed, fields[1])
+ case 'M':
+ fileStatus.Modified = append(fileStatus.Modified, fields[1])
+ }
+ }
+ done <- struct{}{}
+ }()
+
+ stderr := new(bytes.Buffer)
+ err := NewCommand("log", "-1", "--name-status", "--pretty=format:''", commitID).RunInDirPipeline(repoPath, w, stderr)
+ w.Close() // Close writer to exit parsing goroutine
+ if err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+
+ <-done
+ return fileStatus, nil
+}
+
+// FileStatus returns file status of commit.
+func (c *Commit) FileStatus() (*CommitFileStatus, error) {
+ return GetCommitFileStatus(c.repo.Path, c.ID.String())
+}
diff --git a/vendor/github.com/gogs/git-module/commit_archive.go b/vendor/github.com/gogs/git-module/commit_archive.go
new file mode 100644
index 00000000..1066ba69
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/commit_archive.go
@@ -0,0 +1,33 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "path/filepath"
+ "strings"
+)
+
+type ArchiveType int
+
+const (
+ ZIP ArchiveType = iota + 1
+ TARGZ
+)
+
+func (c *Commit) CreateArchive(target string, archiveType ArchiveType) error {
+ var format string
+ switch archiveType {
+ case ZIP:
+ format = "zip"
+ case TARGZ:
+ format = "tar.gz"
+ default:
+ return fmt.Errorf("unknown format: %v", archiveType)
+ }
+
+ _, err := NewCommand("archive", "--prefix="+filepath.Base(strings.TrimSuffix(c.repo.Path, ".git"))+"/", "--format="+format, "-o", target, c.ID.String()).RunInDir(c.repo.Path)
+ return err
+}
diff --git a/vendor/github.com/gogs/git-module/error.go b/vendor/github.com/gogs/git-module/error.go
new file mode 100644
index 00000000..2fefaa1e
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/error.go
@@ -0,0 +1,61 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "time"
+)
+
+type ErrExecTimeout struct {
+ Duration time.Duration
+}
+
+func IsErrExecTimeout(err error) bool {
+ _, ok := err.(ErrExecTimeout)
+ return ok
+}
+
+func (err ErrExecTimeout) Error() string {
+ return fmt.Sprintf("execution is timeout [duration: %v]", err.Duration)
+}
+
+type ErrNotExist struct {
+ ID string
+ RelPath string
+}
+
+func IsErrNotExist(err error) bool {
+ _, ok := err.(ErrNotExist)
+ return ok
+}
+
+func (err ErrNotExist) Error() string {
+ return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath)
+}
+
+type ErrUnsupportedVersion struct {
+ Required string
+}
+
+func IsErrUnsupportedVersion(err error) bool {
+ _, ok := err.(ErrUnsupportedVersion)
+ return ok
+}
+
+func (err ErrUnsupportedVersion) Error() string {
+ return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
+}
+
+type ErrNoMergeBase struct{}
+
+func IsErrNoMergeBase(err error) bool {
+ _, ok := err.(ErrNoMergeBase)
+ return ok
+}
+
+func (err ErrNoMergeBase) Error() string {
+ return "no merge based found"
+}
diff --git a/vendor/github.com/gogs/git-module/git.go b/vendor/github.com/gogs/git-module/git.go
new file mode 100644
index 00000000..414d67cd
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/git.go
@@ -0,0 +1,80 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "strings"
+ "time"
+)
+
+const _VERSION = "0.6.4"
+
+func Version() string {
+ return _VERSION
+}
+
+var (
+ // Debug enables verbose logging on everything.
+ // This should be false in case Gogs starts in SSH mode.
+ Debug = false
+ Prefix = "[git-module] "
+)
+
+func log(format string, args ...interface{}) {
+ if !Debug {
+ return
+ }
+
+ fmt.Print(Prefix)
+ if len(args) == 0 {
+ fmt.Println(format)
+ } else {
+ fmt.Printf(format+"\n", args...)
+ }
+}
+
+var gitVersion string
+
+// Version returns current Git version from shell.
+func BinVersion() (string, error) {
+ if len(gitVersion) > 0 {
+ return gitVersion, nil
+ }
+
+ stdout, err := NewCommand("version").Run()
+ if err != nil {
+ return "", err
+ }
+
+ fields := strings.Fields(stdout)
+ if len(fields) < 3 {
+ return "", fmt.Errorf("not enough output: %s", stdout)
+ }
+
+ // Handle special case on Windows.
+ i := strings.Index(fields[2], "windows")
+ if i >= 1 {
+ gitVersion = fields[2][:i-1]
+ return gitVersion, nil
+ }
+
+ gitVersion = fields[2]
+ return gitVersion, nil
+}
+
+func init() {
+ BinVersion()
+}
+
+// Fsck verifies the connectivity and validity of the objects in the database
+func Fsck(repoPath string, timeout time.Duration, args ...string) error {
+ // Make sure timeout makes sense.
+ if timeout <= 0 {
+ timeout = -1
+ }
+ _, err := NewCommand("fsck").AddArguments(args...).RunInDirTimeout(timeout, repoPath)
+ return err
+}
diff --git a/vendor/github.com/gogs/git-module/hook.go b/vendor/github.com/gogs/git-module/hook.go
new file mode 100644
index 00000000..a0d0b16b
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/hook.go
@@ -0,0 +1,111 @@
+// Copyright 2015 The Gogs 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 (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path"
+ "strings"
+)
+
+var (
+ // Direcotry of hook and sample files. Can be changed to "custom_hooks" for very purpose.
+ HookDir = "hooks"
+ HookSampleDir = HookDir
+ // HookNames is a list of Git server hooks' name that are supported.
+ HookNames = []string{
+ "pre-receive",
+ "update",
+ "post-receive",
+ }
+)
+
+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
+}
+
+// Hook represents a Git hook.
+type Hook struct {
+ name string
+ IsActive bool // Indicates whether repository has this hook.
+ Content string // Content of hook if it's active.
+ Sample string // Sample content from Git.
+ path string // Hook file path.
+}
+
+// GetHook returns a Git hook by given name and repository.
+func GetHook(repoPath, name string) (*Hook, error) {
+ if !IsValidHookName(name) {
+ return nil, ErrNotValidHook
+ }
+ h := &Hook{
+ name: name,
+ path: path.Join(repoPath, HookDir, name),
+ }
+ if isFile(h.path) {
+ data, err := ioutil.ReadFile(h.path)
+ if err != nil {
+ return nil, err
+ }
+ h.IsActive = true
+ h.Content = string(data)
+ return h, nil
+ }
+
+ // Check sample file
+ samplePath := path.Join(repoPath, HookSampleDir, h.name) + ".sample"
+ if isFile(samplePath) {
+ data, err := ioutil.ReadFile(samplePath)
+ if err != nil {
+ return nil, err
+ }
+ h.Sample = string(data)
+ }
+ return h, nil
+}
+
+func (h *Hook) Name() string {
+ return h.name
+}
+
+// Update updates content hook file.
+func (h *Hook) Update() error {
+ if len(strings.TrimSpace(h.Content)) == 0 {
+ if isExist(h.path) {
+ return os.Remove(h.path)
+ }
+ return nil
+ }
+ os.MkdirAll(path.Dir(h.path), os.ModePerm)
+ return ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm)
+}
+
+// ListHooks returns a list of Git hooks of given repository.
+func ListHooks(repoPath string) (_ []*Hook, err error) {
+ if !isDir(path.Join(repoPath, "hooks")) {
+ return nil, errors.New("hooks path does not exist")
+ }
+
+ hooks := make([]*Hook, len(HookNames))
+ for i, name := range HookNames {
+ hooks[i], err = GetHook(repoPath, name)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return hooks, nil
+}
diff --git a/vendor/github.com/gogs/git-module/repo.go b/vendor/github.com/gogs/git-module/repo.go
new file mode 100644
index 00000000..ca4805f5
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo.go
@@ -0,0 +1,295 @@
+// Copyright 2015 The Gogs 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"
+ "container/list"
+ "errors"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/Unknwon/com"
+)
+
+// Repository represents a Git repository.
+type Repository struct {
+ Path string
+
+ commitCache *objectCache
+ tagCache *objectCache
+}
+
+const _PRETTY_LOG_FORMAT = `--pretty=format:%H`
+
+func (repo *Repository) parsePrettyFormatLogToList(logs []byte) (*list.List, error) {
+ l := list.New()
+ if len(logs) == 0 {
+ return l, nil
+ }
+
+ parts := bytes.Split(logs, []byte{'\n'})
+
+ for _, commitId := range parts {
+ commit, err := repo.GetCommit(string(commitId))
+ if err != nil {
+ return nil, err
+ }
+ l.PushBack(commit)
+ }
+
+ return l, nil
+}
+
+type NetworkOptions struct {
+ URL string
+ Timeout time.Duration
+}
+
+// IsRepoURLAccessible checks if given repository URL is accessible.
+func IsRepoURLAccessible(opts NetworkOptions) bool {
+ cmd := NewCommand("ls-remote", "-q", "-h", opts.URL, "HEAD")
+ if opts.Timeout <= 0 {
+ opts.Timeout = -1
+ }
+ _, err := cmd.RunTimeout(opts.Timeout)
+ if err != nil {
+ return false
+ }
+ return true
+}
+
+// InitRepository initializes a new Git repository.
+func InitRepository(repoPath string, bare bool) error {
+ os.MkdirAll(repoPath, os.ModePerm)
+
+ cmd := NewCommand("init")
+ if bare {
+ cmd.AddArguments("--bare")
+ }
+ _, err := cmd.RunInDir(repoPath)
+ return err
+}
+
+// OpenRepository opens the repository at the given path.
+func OpenRepository(repoPath string) (*Repository, error) {
+ repoPath, err := filepath.Abs(repoPath)
+ if err != nil {
+ return nil, err
+ } else if !isDir(repoPath) {
+ return nil, errors.New("no such file or directory")
+ }
+
+ return &Repository{
+ Path: repoPath,
+ commitCache: newObjectCache(),
+ tagCache: newObjectCache(),
+ }, nil
+}
+
+type CloneRepoOptions struct {
+ Mirror bool
+ Bare bool
+ Quiet bool
+ Branch string
+ Timeout time.Duration
+}
+
+// Clone clones original repository to target path.
+func Clone(from, to string, opts CloneRepoOptions) (err error) {
+ toDir := path.Dir(to)
+ if err = os.MkdirAll(toDir, os.ModePerm); err != nil {
+ return err
+ }
+
+ cmd := NewCommand("clone")
+ if opts.Mirror {
+ cmd.AddArguments("--mirror")
+ }
+ if opts.Bare {
+ cmd.AddArguments("--bare")
+ }
+ if opts.Quiet {
+ cmd.AddArguments("--quiet")
+ }
+ if len(opts.Branch) > 0 {
+ cmd.AddArguments("-b", opts.Branch)
+ }
+ cmd.AddArguments(from, to)
+
+ if opts.Timeout <= 0 {
+ opts.Timeout = -1
+ }
+ _, err = cmd.RunTimeout(opts.Timeout)
+ return err
+}
+
+type FetchRemoteOptions struct {
+ Prune bool
+ Timeout time.Duration
+}
+
+// Fetch fetches changes from remotes without merging.
+func Fetch(repoPath string, opts FetchRemoteOptions) error {
+ cmd := NewCommand("fetch")
+ if opts.Prune {
+ cmd.AddArguments("--prune")
+ }
+
+ if opts.Timeout <= 0 {
+ opts.Timeout = -1
+ }
+ _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
+ return err
+}
+
+type PullRemoteOptions struct {
+ All bool
+ Rebase bool
+ Remote string
+ Branch string
+ Timeout time.Duration
+}
+
+// Pull pulls changes from remotes.
+func Pull(repoPath string, opts PullRemoteOptions) error {
+ cmd := NewCommand("pull")
+ if opts.Rebase {
+ cmd.AddArguments("--rebase")
+ }
+ if opts.All {
+ cmd.AddArguments("--all")
+ } else {
+ cmd.AddArguments(opts.Remote)
+ cmd.AddArguments(opts.Branch)
+ }
+
+ if opts.Timeout <= 0 {
+ opts.Timeout = -1
+ }
+ _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
+ return err
+}
+
+// Push pushs local commits to given remote branch.
+func Push(repoPath, remote, branch string) error {
+ _, err := NewCommand("push", remote, branch).RunInDir(repoPath)
+ return err
+}
+
+type CheckoutOptions struct {
+ Branch string
+ OldBranch string
+ Timeout time.Duration
+}
+
+// Checkout checkouts a branch
+func Checkout(repoPath string, opts CheckoutOptions) error {
+ cmd := NewCommand("checkout")
+ if len(opts.OldBranch) > 0 {
+ cmd.AddArguments("-b")
+ }
+
+ cmd.AddArguments(opts.Branch)
+
+ if len(opts.OldBranch) > 0 {
+ cmd.AddArguments(opts.OldBranch)
+ }
+ if opts.Timeout <= 0 {
+ opts.Timeout = -1
+ }
+ _, err := cmd.RunInDirTimeout(opts.Timeout, repoPath)
+ return err
+}
+
+// ResetHEAD resets HEAD to given revision or head of branch.
+func ResetHEAD(repoPath string, hard bool, revision string) error {
+ cmd := NewCommand("reset")
+ if hard {
+ cmd.AddArguments("--hard")
+ }
+ _, err := cmd.AddArguments(revision).RunInDir(repoPath)
+ return err
+}
+
+// MoveFile moves a file to another file or directory.
+func MoveFile(repoPath, oldTreeName, newTreeName string) error {
+ _, err := NewCommand("mv").AddArguments(oldTreeName, newTreeName).RunInDir(repoPath)
+ return err
+}
+
+// CountObject represents disk usage report of Git repository.
+type CountObject struct {
+ Count int64
+ Size int64
+ InPack int64
+ Packs int64
+ SizePack int64
+ PrunePackable int64
+ Garbage int64
+ SizeGarbage int64
+}
+
+const (
+ _STAT_COUNT = "count: "
+ _STAT_SIZE = "size: "
+ _STAT_IN_PACK = "in-pack: "
+ _STAT_PACKS = "packs: "
+ _STAT_SIZE_PACK = "size-pack: "
+ _STAT_PRUNE_PACKABLE = "prune-packable: "
+ _STAT_GARBAGE = "garbage: "
+ _STAT_SIZE_GARBAGE = "size-garbage: "
+)
+
+// GetRepoSize returns disk usage report of repository in given path.
+func GetRepoSize(repoPath string) (*CountObject, error) {
+ cmd := NewCommand("count-objects", "-v")
+ stdout, err := cmd.RunInDir(repoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ countObject := new(CountObject)
+ for _, line := range strings.Split(stdout, "\n") {
+ switch {
+ case strings.HasPrefix(line, _STAT_COUNT):
+ countObject.Count = com.StrTo(line[7:]).MustInt64()
+ case strings.HasPrefix(line, _STAT_SIZE):
+ countObject.Size = com.StrTo(line[6:]).MustInt64() * 1024
+ case strings.HasPrefix(line, _STAT_IN_PACK):
+ countObject.InPack = com.StrTo(line[9:]).MustInt64()
+ case strings.HasPrefix(line, _STAT_PACKS):
+ countObject.Packs = com.StrTo(line[7:]).MustInt64()
+ case strings.HasPrefix(line, _STAT_SIZE_PACK):
+ countObject.SizePack = com.StrTo(line[11:]).MustInt64() * 1024
+ case strings.HasPrefix(line, _STAT_PRUNE_PACKABLE):
+ countObject.PrunePackable = com.StrTo(line[16:]).MustInt64()
+ case strings.HasPrefix(line, _STAT_GARBAGE):
+ countObject.Garbage = com.StrTo(line[9:]).MustInt64()
+ case strings.HasPrefix(line, _STAT_SIZE_GARBAGE):
+ countObject.SizeGarbage = com.StrTo(line[14:]).MustInt64() * 1024
+ }
+ }
+
+ return countObject, nil
+}
+
+// GetLatestCommitDate returns the date of latest commit of repository.
+// If branch is empty, it returns the latest commit across all branches.
+func GetLatestCommitDate(repoPath, branch string) (time.Time, error) {
+ cmd := NewCommand("for-each-ref", "--count=1", "--sort=-committerdate", "--format=%(committerdate:iso8601)")
+ if len(branch) > 0 {
+ cmd.AddArguments("refs/heads/" + branch)
+ }
+ stdout, err := cmd.RunInDir(repoPath)
+ if err != nil {
+ return time.Time{}, err
+ }
+
+ return time.Parse("2006-01-02 15:04:05 -0700", strings.TrimSpace(stdout))
+}
diff --git a/vendor/github.com/gogs/git-module/repo_branch.go b/vendor/github.com/gogs/git-module/repo_branch.go
new file mode 100644
index 00000000..de3f6ca1
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_branch.go
@@ -0,0 +1,126 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "strings"
+
+ "github.com/mcuadros/go-version"
+)
+
+const BRANCH_PREFIX = "refs/heads/"
+
+// IsReferenceExist returns true if given reference exists in the repository.
+func IsReferenceExist(repoPath, name string) bool {
+ _, err := NewCommand("show-ref", "--verify", name).RunInDir(repoPath)
+ return err == nil
+}
+
+// IsBranchExist returns true if given branch exists in the repository.
+func IsBranchExist(repoPath, name string) bool {
+ return IsReferenceExist(repoPath, BRANCH_PREFIX+name)
+}
+
+func (repo *Repository) IsBranchExist(name string) bool {
+ return IsBranchExist(repo.Path, name)
+}
+
+// Branch represents a Git branch.
+type Branch struct {
+ Name string
+ Path string
+}
+
+// GetHEADBranch returns corresponding branch of HEAD.
+func (repo *Repository) GetHEADBranch() (*Branch, error) {
+ stdout, err := NewCommand("symbolic-ref", "HEAD").RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ stdout = strings.TrimSpace(stdout)
+
+ if !strings.HasPrefix(stdout, BRANCH_PREFIX) {
+ return nil, fmt.Errorf("invalid HEAD branch: %v", stdout)
+ }
+
+ return &Branch{
+ Name: stdout[len(BRANCH_PREFIX):],
+ Path: stdout,
+ }, nil
+}
+
+// SetDefaultBranch sets default branch of repository.
+func (repo *Repository) SetDefaultBranch(name string) error {
+ if version.Compare(gitVersion, "1.7.10", "<") {
+ return ErrUnsupportedVersion{"1.7.10"}
+ }
+
+ _, err := NewCommand("symbolic-ref", "HEAD", BRANCH_PREFIX+name).RunInDir(repo.Path)
+ return err
+}
+
+// GetBranches returns all branches of the repository.
+func (repo *Repository) GetBranches() ([]string, error) {
+ stdout, err := NewCommand("show-ref", "--heads").RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ infos := strings.Split(stdout, "\n")
+ branches := make([]string, len(infos)-1)
+ for i, info := range infos[:len(infos)-1] {
+ fields := strings.Fields(info)
+ if len(fields) != 2 {
+ continue // NOTE: I should believe git will not give me wrong string.
+ }
+ branches[i] = strings.TrimPrefix(fields[1], BRANCH_PREFIX)
+ }
+ return branches, nil
+}
+
+// Option(s) for delete branch
+type DeleteBranchOptions struct {
+ Force bool
+}
+
+// DeleteBranch deletes a branch from given repository path.
+func DeleteBranch(repoPath, name string, opts DeleteBranchOptions) error {
+ cmd := NewCommand("branch")
+
+ if opts.Force {
+ cmd.AddArguments("-D")
+ } else {
+ cmd.AddArguments("-d")
+ }
+
+ cmd.AddArguments(name)
+ _, err := cmd.RunInDir(repoPath)
+
+ return err
+}
+
+// DeleteBranch deletes a branch from repository.
+func (repo *Repository) DeleteBranch(name string, opts DeleteBranchOptions) error {
+ return DeleteBranch(repo.Path, name, opts)
+}
+
+// AddRemote adds a new remote to repository.
+func (repo *Repository) AddRemote(name, url string, fetch bool) error {
+ cmd := NewCommand("remote", "add")
+ if fetch {
+ cmd.AddArguments("-f")
+ }
+ cmd.AddArguments(name, url)
+
+ _, err := cmd.RunInDir(repo.Path)
+ return err
+}
+
+// RemoveRemote removes a remote from repository.
+func (repo *Repository) RemoveRemote(name string) error {
+ _, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
+ return err
+}
diff --git a/vendor/github.com/gogs/git-module/repo_commit.go b/vendor/github.com/gogs/git-module/repo_commit.go
new file mode 100644
index 00000000..6b629d4d
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_commit.go
@@ -0,0 +1,381 @@
+// Copyright 2015 The Gogs 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"
+ "container/list"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/mcuadros/go-version"
+)
+
+const REMOTE_PREFIX = "refs/remotes/"
+
+// getRefCommitID returns the last commit ID string of given reference (branch or tag).
+func (repo *Repository) getRefCommitID(name string) (string, error) {
+ stdout, err := NewCommand("show-ref", "--verify", name).RunInDir(repo.Path)
+ if err != nil {
+ if strings.Contains(err.Error(), "not a valid ref") {
+ return "", ErrNotExist{name, ""}
+ }
+ return "", err
+ }
+ return strings.Split(stdout, " ")[0], nil
+}
+
+// GetBranchCommitID returns last commit ID string of given branch.
+func (repo *Repository) GetBranchCommitID(name string) (string, error) {
+ return repo.getRefCommitID(BRANCH_PREFIX + name)
+}
+
+// GetTagCommitID returns last commit ID string of given tag.
+func (repo *Repository) GetTagCommitID(name string) (string, error) {
+ return repo.getRefCommitID(TAG_PREFIX + name)
+}
+
+// GetRemoteBranchCommitID returns last commit ID string of given remote branch.
+func (repo *Repository) GetRemoteBranchCommitID(name string) (string, error) {
+ return repo.getRefCommitID(REMOTE_PREFIX + name)
+}
+
+// parseCommitData parses commit information from the (uncompressed) raw
+// data from the commit object.
+// \n\n separate headers from message
+func parseCommitData(data []byte) (*Commit, error) {
+ commit := new(Commit)
+ commit.parents = make([]sha1, 0, 1)
+ // we now have the contents of the commit object. Let's investigate...
+ nextline := 0
+l:
+ for {
+ eol := bytes.IndexByte(data[nextline:], '\n')
+ switch {
+ case eol > 0:
+ line := data[nextline : nextline+eol]
+ spacepos := bytes.IndexByte(line, ' ')
+ reftype := line[:spacepos]
+ switch string(reftype) {
+ case "tree", "object":
+ id, err := NewIDFromString(string(line[spacepos+1:]))
+ if err != nil {
+ return nil, err
+ }
+ commit.Tree.ID = id
+ case "parent":
+ // A commit can have one or more parents
+ oid, err := NewIDFromString(string(line[spacepos+1:]))
+ if err != nil {
+ return nil, err
+ }
+ commit.parents = append(commit.parents, oid)
+ case "author", "tagger":
+ sig, err := newSignatureFromCommitline(line[spacepos+1:])
+ if err != nil {
+ return nil, err
+ }
+ commit.Author = sig
+ case "committer":
+ sig, err := newSignatureFromCommitline(line[spacepos+1:])
+ if err != nil {
+ return nil, err
+ }
+ commit.Committer = sig
+ }
+ nextline += eol + 1
+ case eol == 0:
+ commit.CommitMessage = string(data[nextline+1:])
+ break l
+ default:
+ break l
+ }
+ }
+ return commit, nil
+}
+
+func (repo *Repository) getCommit(id sha1) (*Commit, error) {
+ c, ok := repo.commitCache.Get(id.String())
+ if ok {
+ log("Hit cache: %s", id)
+ return c.(*Commit), nil
+ }
+
+ data, err := NewCommand("cat-file", "commit", id.String()).RunInDirBytes(repo.Path)
+ if err != nil {
+ if strings.Contains(err.Error(), "exit status 128") {
+ return nil, ErrNotExist{id.String(), ""}
+ }
+ return nil, err
+ }
+
+ commit, err := parseCommitData(data)
+ if err != nil {
+ return nil, err
+ }
+ commit.repo = repo
+ commit.ID = id
+
+ repo.commitCache.Set(id.String(), commit)
+ return commit, nil
+}
+
+// GetCommit returns commit object of by ID string.
+func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
+ if len(commitID) != 40 {
+ var err error
+ commitID, err = NewCommand("rev-parse", commitID).RunInDir(repo.Path)
+ if err != nil {
+ if strings.Contains(err.Error(), "exit status 128") {
+ return nil, ErrNotExist{commitID, ""}
+ }
+ return nil, err
+ }
+ }
+ id, err := NewIDFromString(commitID)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
+
+// GetBranchCommit returns the last commit of given branch.
+func (repo *Repository) GetBranchCommit(name string) (*Commit, error) {
+ commitID, err := repo.GetBranchCommitID(name)
+ if err != nil {
+ return nil, err
+ }
+ return repo.GetCommit(commitID)
+}
+
+// GetTagCommit returns the commit of given tag.
+func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
+ commitID, err := repo.GetTagCommitID(name)
+ if err != nil {
+ return nil, err
+ }
+ return repo.GetCommit(commitID)
+}
+
+// GetRemoteBranchCommit returns the last commit of given remote branch.
+func (repo *Repository) GetRemoteBranchCommit(name string) (*Commit, error) {
+ commitID, err := repo.GetRemoteBranchCommitID(name)
+ if err != nil {
+ return nil, err
+ }
+ return repo.GetCommit(commitID)
+}
+
+func (repo *Repository) getCommitByPathWithID(id sha1, relpath string) (*Commit, error) {
+ // File name starts with ':' must be escaped.
+ if relpath[0] == ':' {
+ relpath = `\` + relpath
+ }
+
+ stdout, err := NewCommand("log", "-1", _PRETTY_LOG_FORMAT, id.String(), "--", relpath).RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ id, err = NewIDFromString(stdout)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
+
+// GetCommitByPath returns the last commit of relative path.
+func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
+ stdout, err := NewCommand("log", "-1", _PRETTY_LOG_FORMAT, "--", relpath).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ commits, err := repo.parsePrettyFormatLogToList(stdout)
+ if err != nil {
+ return nil, err
+ }
+ return commits.Front().Value.(*Commit), nil
+}
+
+func (repo *Repository) CommitsByRangeSize(revision string, page, size int) (*list.List, error) {
+ stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*size),
+ "--max-count="+strconv.Itoa(size), _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.parsePrettyFormatLogToList(stdout)
+}
+
+var DefaultCommitsPageSize = 30
+
+func (repo *Repository) CommitsByRange(revision string, page int) (*list.List, error) {
+ return repo.CommitsByRangeSize(revision, page, DefaultCommitsPageSize)
+}
+
+func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
+ stdout, err := NewCommand("log", id.String(), "-100", "-i", "--grep="+keyword, _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.parsePrettyFormatLogToList(stdout)
+}
+
+func (repo *Repository) getFilesChanged(id1 string, id2 string) ([]string, error) {
+ stdout, err := NewCommand("diff", "--name-only", id1, id2).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ return strings.Split(string(stdout), "\n"), nil
+}
+
+func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
+ return commitsCount(repo.Path, revision, file)
+}
+
+func (repo *Repository) CommitsByFileAndRangeSize(revision, file string, page, size int) (*list.List, error) {
+ stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*size),
+ "--max-count="+strconv.Itoa(size), _PRETTY_LOG_FORMAT, "--", file).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.parsePrettyFormatLogToList(stdout)
+}
+
+func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
+ return repo.CommitsByFileAndRangeSize(revision, file, page, DefaultCommitsPageSize)
+}
+
+func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
+ stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
+ if err != nil {
+ return 0, err
+ }
+ return len(strings.Split(stdout, "\n")) - 1, nil
+}
+
+// CommitsBetween returns a list that contains commits between [last, before).
+func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
+ if version.Compare(gitVersion, "1.8.0", ">=") {
+ stdout, err := NewCommand("rev-list", before.ID.String()+"..."+last.ID.String()).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ return repo.parsePrettyFormatLogToList(bytes.TrimSpace(stdout))
+ }
+
+ // Fallback to stupid solution, which iterates all commits of the repository
+ // if before is not an ancestor of last.
+ l := list.New()
+ if last == nil || last.ParentCount() == 0 {
+ return l, nil
+ }
+
+ var err error
+ cur := last
+ for {
+ if cur.ID.Equal(before.ID) {
+ break
+ }
+ l.PushBack(cur)
+ if cur.ParentCount() == 0 {
+ break
+ }
+ cur, err = cur.Parent(0)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return l, nil
+}
+
+func (repo *Repository) CommitsBetweenIDs(last, before string) (*list.List, error) {
+ lastCommit, err := repo.GetCommit(last)
+ if err != nil {
+ return nil, err
+ }
+ beforeCommit, err := repo.GetCommit(before)
+ if err != nil {
+ return nil, err
+ }
+ return repo.CommitsBetween(lastCommit, beforeCommit)
+}
+
+func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
+ return commitsCount(repo.Path, start+"..."+end, "")
+}
+
+// The limit is depth, not total number of returned commits.
+func (repo *Repository) commitsBefore(l *list.List, parent *list.Element, id sha1, current, limit int) error {
+ // Reach the limit
+ if limit > 0 && current > limit {
+ return nil
+ }
+
+ commit, err := repo.getCommit(id)
+ if err != nil {
+ return fmt.Errorf("getCommit: %v", err)
+ }
+
+ var e *list.Element
+ if parent == nil {
+ e = l.PushBack(commit)
+ } else {
+ var in = parent
+ for {
+ if in == nil {
+ break
+ } else if in.Value.(*Commit).ID.Equal(commit.ID) {
+ return nil
+ } else if in.Next() == nil {
+ break
+ }
+
+ if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
+ break
+ }
+
+ if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
+ in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
+ break
+ }
+
+ in = in.Next()
+ }
+
+ e = l.InsertAfter(commit, in)
+ }
+
+ pr := parent
+ if commit.ParentCount() > 1 {
+ pr = e
+ }
+
+ for i := 0; i < commit.ParentCount(); i++ {
+ id, err := commit.ParentID(i)
+ if err != nil {
+ return err
+ }
+ err = repo.commitsBefore(l, pr, id, current+1, limit)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
+ l := list.New()
+ return l, repo.commitsBefore(l, nil, id, 1, 0)
+}
+
+func (repo *Repository) getCommitsBeforeLimit(id sha1, num int) (*list.List, error) {
+ l := list.New()
+ return l, repo.commitsBefore(l, nil, id, 1, num)
+}
diff --git a/vendor/github.com/gogs/git-module/repo_diff.go b/vendor/github.com/gogs/git-module/repo_diff.go
new file mode 100644
index 00000000..258a166f
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_diff.go
@@ -0,0 +1,400 @@
+// Copyright 2017 The Gogs 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 (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// DiffLineType represents the type of a line in diff.
+type DiffLineType uint8
+
+const (
+ DIFF_LINE_PLAIN DiffLineType = iota + 1
+ DIFF_LINE_ADD
+ DIFF_LINE_DEL
+ DIFF_LINE_SECTION
+)
+
+// DiffFileType represents the file status in diff.
+type DiffFileType uint8
+
+const (
+ DIFF_FILE_ADD DiffFileType = iota + 1
+ DIFF_FILE_CHANGE
+ DIFF_FILE_DEL
+ DIFF_FILE_RENAME
+)
+
+// DiffLine represents a line in diff.
+type DiffLine struct {
+ LeftIdx int
+ RightIdx int
+ Type DiffLineType
+ Content string
+}
+
+func (d *DiffLine) GetType() int {
+ return int(d.Type)
+}
+
+// DiffSection represents a section in diff.
+type DiffSection struct {
+ Name string
+ Lines []*DiffLine
+}
+
+// Line returns a specific line by type (add or del) and file line number from a section.
+func (diffSection *DiffSection) Line(lineType DiffLineType, idx int) *DiffLine {
+ var (
+ difference = 0
+ addCount = 0
+ delCount = 0
+ matchDiffLine *DiffLine
+ )
+
+LOOP:
+ for _, diffLine := range diffSection.Lines {
+ switch diffLine.Type {
+ case DIFF_LINE_ADD:
+ addCount++
+ case DIFF_LINE_DEL:
+ delCount++
+ default:
+ if matchDiffLine != nil {
+ break LOOP
+ }
+ difference = diffLine.RightIdx - diffLine.LeftIdx
+ addCount = 0
+ delCount = 0
+ }
+
+ switch lineType {
+ case DIFF_LINE_DEL:
+ if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
+ matchDiffLine = diffLine
+ }
+ case DIFF_LINE_ADD:
+ if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
+ matchDiffLine = diffLine
+ }
+ }
+ }
+
+ if addCount == delCount {
+ return matchDiffLine
+ }
+ return nil
+}
+
+// DiffFile represents a file in diff.
+type DiffFile struct {
+ Name string
+ OldName string
+ Index string // 40-byte SHA, Changed/New: new SHA; Deleted: old SHA
+ Addition, Deletion int
+ Type DiffFileType
+ IsCreated bool
+ IsDeleted bool
+ IsBin bool
+ IsRenamed bool
+ IsSubmodule bool
+ Sections []*DiffSection
+ IsIncomplete bool
+}
+
+func (diffFile *DiffFile) GetType() int {
+ return int(diffFile.Type)
+}
+
+func (diffFile *DiffFile) NumSections() int {
+ return len(diffFile.Sections)
+}
+
+// Diff contains all information of a specific diff output.
+type Diff struct {
+ TotalAddition, TotalDeletion int
+ Files []*DiffFile
+ IsIncomplete bool
+}
+
+func (diff *Diff) NumFiles() int {
+ return len(diff.Files)
+}
+
+const _DIFF_HEAD = "diff --git "
+
+// ParsePatch takes a reader and parses everything it receives in diff format.
+func ParsePatch(done chan<- error, maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) *Diff {
+ var (
+ diff = &Diff{Files: make([]*DiffFile, 0)}
+
+ curFile *DiffFile
+ curSection = &DiffSection{
+ Lines: make([]*DiffLine, 0, 10),
+ }
+
+ leftLine, rightLine int
+ lineCount int
+ curFileLinesCount int
+ )
+ input := bufio.NewReader(reader)
+ isEOF := false
+ for !isEOF {
+ // TODO: would input.ReadBytes be more memory-efficient?
+ line, err := input.ReadString('\n')
+ if err != nil {
+ if err == io.EOF {
+ isEOF = true
+ } else {
+ done <- fmt.Errorf("ReadString: %v", err)
+ return nil
+ }
+ }
+
+ if len(line) > 0 && line[len(line)-1] == '\n' {
+ // Remove line break.
+ line = line[:len(line)-1]
+ }
+
+ if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
+ continue
+ }
+
+ curFileLinesCount++
+ lineCount++
+
+ // Diff data too large, we only show the first about maxlines lines
+ if curFileLinesCount >= maxLines || len(line) >= maxLineCharacteres {
+ curFile.IsIncomplete = true
+ }
+
+ switch {
+ case line[0] == ' ':
+ diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
+ leftLine++
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ case line[0] == '@':
+ curSection = &DiffSection{}
+ curFile.Sections = append(curFile.Sections, curSection)
+ ss := strings.Split(line, "@@")
+ diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
+ curSection.Lines = append(curSection.Lines, diffLine)
+
+ // Parse line number.
+ ranges := strings.Split(ss[1][1:], " ")
+ leftLine, _ = strconv.Atoi(strings.Split(ranges[0], ",")[0][1:])
+ if len(ranges) > 1 {
+ rightLine, _ = strconv.Atoi(strings.Split(ranges[1], ",")[0])
+ } else {
+ rightLine = leftLine
+ }
+ continue
+ case line[0] == '+':
+ curFile.Addition++
+ diff.TotalAddition++
+ diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
+ rightLine++
+ curSection.Lines = append(curSection.Lines, diffLine)
+ continue
+ case line[0] == '-':
+ curFile.Deletion++
+ diff.TotalDeletion++
+ diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
+ if leftLine > 0 {
+ leftLine++
+ }
+ curSection.Lines = append(curSection.Lines, diffLine)
+ case strings.HasPrefix(line, "Binary"):
+ curFile.IsBin = true
+ continue
+ }
+
+ // Get new file.
+ if strings.HasPrefix(line, _DIFF_HEAD) {
+ middle := -1
+
+ // Note: In case file name is surrounded by double quotes (it happens only in git-shell).
+ // e.g. diff --git "a/xxx" "b/xxx"
+ hasQuote := line[len(_DIFF_HEAD)] == '"'
+ if hasQuote {
+ middle = strings.Index(line, ` "b/`)
+ } else {
+ middle = strings.Index(line, " b/")
+ }
+
+ beg := len(_DIFF_HEAD)
+ a := line[beg+2 : middle]
+ b := line[middle+3:]
+ if hasQuote {
+ a = string(UnescapeChars([]byte(a[1 : len(a)-1])))
+ b = string(UnescapeChars([]byte(b[1 : len(b)-1])))
+ }
+
+ curFile = &DiffFile{
+ Name: a,
+ Type: DIFF_FILE_CHANGE,
+ Sections: make([]*DiffSection, 0, 10),
+ }
+ diff.Files = append(diff.Files, curFile)
+ if len(diff.Files) >= maxFiles {
+ diff.IsIncomplete = true
+ io.Copy(ioutil.Discard, reader)
+ break
+ }
+ curFileLinesCount = 0
+
+ // Check file diff type and submodule.
+ CHECK_TYPE:
+ for {
+ line, err := input.ReadString('\n')
+ if err != nil {
+ if err == io.EOF {
+ isEOF = true
+ } else {
+ done <- fmt.Errorf("ReadString: %v", err)
+ return nil
+ }
+ }
+
+ switch {
+ case strings.HasPrefix(line, "new file"):
+ curFile.Type = DIFF_FILE_ADD
+ curFile.IsCreated = true
+ curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
+ case strings.HasPrefix(line, "deleted"):
+ curFile.Type = DIFF_FILE_DEL
+ curFile.IsDeleted = true
+ curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
+ case strings.HasPrefix(line, "index"):
+ if curFile.IsDeleted {
+ curFile.Index = line[6:46]
+ } else if len(line) >= 88 {
+ curFile.Index = line[49:88]
+ } else {
+ curFile.Index = curFile.Name
+ }
+ break CHECK_TYPE
+ case strings.HasPrefix(line, "similarity index 100%"):
+ curFile.Type = DIFF_FILE_RENAME
+ curFile.IsRenamed = true
+ curFile.OldName = curFile.Name
+ curFile.Name = b
+ curFile.Index = b
+ break CHECK_TYPE
+ case strings.HasPrefix(line, "old mode"):
+ break CHECK_TYPE
+ }
+ }
+ }
+ }
+
+ done <- nil
+ return diff
+}
+
+// GetDiffRange returns a parsed diff object between given commits.
+func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ repo, err := OpenRepository(repoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ commit, err := repo.GetCommit(afterCommitID)
+ if err != nil {
+ return nil, err
+ }
+
+ cmd := NewCommand()
+ if len(beforeCommitID) == 0 {
+ // First commit of repository
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("show", "--full-index", afterCommitID)
+ } else {
+ c, _ := commit.Parent(0)
+ cmd.AddArguments("diff", "--full-index", "-M", c.ID.String(), afterCommitID)
+ }
+ } else {
+ cmd.AddArguments("diff", "--full-index", "-M", beforeCommitID, afterCommitID)
+ }
+
+ stdout, w := io.Pipe()
+ done := make(chan error)
+ var diff *Diff
+ go func() {
+ diff = ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, stdout)
+ }()
+
+ stderr := new(bytes.Buffer)
+ err = cmd.RunInDirTimeoutPipeline(2*time.Minute, repoPath, w, stderr)
+ w.Close() // Close writer to exit parsing goroutine
+ if err != nil {
+ return nil, concatenateError(err, stderr.String())
+ }
+
+ return diff, <-done
+}
+
+// RawDiffType represents the type of raw diff format.
+type RawDiffType string
+
+const (
+ RAW_DIFF_NORMAL RawDiffType = "diff"
+ RAW_DIFF_PATCH RawDiffType = "patch"
+)
+
+// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
+func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
+ repo, err := OpenRepository(repoPath)
+ if err != nil {
+ return fmt.Errorf("OpenRepository: %v", err)
+ }
+
+ commit, err := repo.GetCommit(commitID)
+ if err != nil {
+ return err
+ }
+
+ cmd := NewCommand()
+ switch diffType {
+ case RAW_DIFF_NORMAL:
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("show", commitID)
+ } else {
+ c, _ := commit.Parent(0)
+ cmd.AddArguments("diff", "-M", c.ID.String(), commitID)
+ }
+ case RAW_DIFF_PATCH:
+ if commit.ParentCount() == 0 {
+ cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root", commitID)
+ } else {
+ c, _ := commit.Parent(0)
+ query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
+ cmd.AddArguments("format-patch", "--no-signature", "--stdout", query)
+ }
+ default:
+ return fmt.Errorf("invalid diffType: %s", diffType)
+ }
+
+ stderr := new(bytes.Buffer)
+ if err = cmd.RunInDirPipeline(repoPath, writer, stderr); err != nil {
+ return concatenateError(err, stderr.String())
+ }
+ return nil
+}
+
+// GetDiffCommit returns a parsed diff object of given commit.
+func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
+ return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacteres, maxFiles)
+}
diff --git a/vendor/github.com/gogs/git-module/repo_hook.go b/vendor/github.com/gogs/git-module/repo_hook.go
new file mode 100644
index 00000000..7b49647e
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_hook.go
@@ -0,0 +1,13 @@
+// Copyright 2015 The Gogs 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
+
+func (repo *Repository) GetHook(name string) (*Hook, error) {
+ return GetHook(repo.Path, name)
+}
+
+func (repo *Repository) Hooks() ([]*Hook, error) {
+ return ListHooks(repo.Path)
+}
diff --git a/vendor/github.com/gogs/git-module/repo_object.go b/vendor/github.com/gogs/git-module/repo_object.go
new file mode 100644
index 00000000..416ee459
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_object.go
@@ -0,0 +1,14 @@
+// Copyright 2014 The Gogs 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
+
+type ObjectType string
+
+const (
+ OBJECT_COMMIT ObjectType = "commit"
+ OBJECT_TREE ObjectType = "tree"
+ OBJECT_BLOB ObjectType = "blob"
+ OBJECT_TAG ObjectType = "tag"
+)
diff --git a/vendor/github.com/gogs/git-module/repo_pull.go b/vendor/github.com/gogs/git-module/repo_pull.go
new file mode 100644
index 00000000..22ccb269
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_pull.go
@@ -0,0 +1,81 @@
+// Copyright 2015 The Gogs 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 (
+ "container/list"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// PullRequestInfo represents needed information for a pull request.
+type PullRequestInfo struct {
+ MergeBase string
+ Commits *list.List
+ NumFiles int
+}
+
+// GetMergeBase checks and returns merge base of two branches.
+func (repo *Repository) GetMergeBase(base, head string) (string, error) {
+ stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path)
+ if err != nil {
+ if strings.Contains(err.Error(), "exit status 1") {
+ return "", ErrNoMergeBase{}
+ }
+ return "", err
+ }
+ return strings.TrimSpace(stdout), nil
+}
+
+// GetPullRequestInfo generates and returns pull request information
+// between base and head branches of repositories.
+func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) {
+ var remoteBranch string
+
+ // We don't need a temporary remote for same repository.
+ if repo.Path != basePath {
+ // Add a temporary remote
+ tmpRemote := strconv.FormatInt(time.Now().UnixNano(), 10)
+ if err = repo.AddRemote(tmpRemote, basePath, true); err != nil {
+ return nil, fmt.Errorf("AddRemote: %v", err)
+ }
+ defer repo.RemoveRemote(tmpRemote)
+
+ remoteBranch = "remotes/" + tmpRemote + "/" + baseBranch
+ } else {
+ remoteBranch = baseBranch
+ }
+
+ prInfo := new(PullRequestInfo)
+ prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
+ if err != nil {
+ return nil, err
+ }
+
+ logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
+ if err != nil {
+ return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
+ }
+
+ // Count number of changed files.
+ stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
+
+ return prInfo, nil
+}
+
+// GetPatch generates and returns patch data between given revisions.
+func (repo *Repository) GetPatch(base, head string) ([]byte, error) {
+ return NewCommand("diff", "-p", "--binary", base, head).RunInDirBytes(repo.Path)
+}
diff --git a/vendor/github.com/gogs/git-module/repo_tag.go b/vendor/github.com/gogs/git-module/repo_tag.go
new file mode 100644
index 00000000..4cef496b
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_tag.go
@@ -0,0 +1,209 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "strings"
+
+ "github.com/mcuadros/go-version"
+)
+
+const TAG_PREFIX = "refs/tags/"
+
+// IsTagExist returns true if given tag exists in the repository.
+func IsTagExist(repoPath, name string) bool {
+ return IsReferenceExist(repoPath, TAG_PREFIX+name)
+}
+
+func (repo *Repository) IsTagExist(name string) bool {
+ return IsTagExist(repo.Path, name)
+}
+
+func (repo *Repository) CreateTag(name, revision string) error {
+ _, err := NewCommand("tag", name, revision).RunInDir(repo.Path)
+ return err
+}
+
+func (repo *Repository) getTag(id sha1) (*Tag, error) {
+ t, ok := repo.tagCache.Get(id.String())
+ if ok {
+ log("Hit cache: %s", id)
+ return t.(*Tag), nil
+ }
+
+ // Get tag type
+ tp, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ tp = strings.TrimSpace(tp)
+
+ // Tag is a commit.
+ if ObjectType(tp) == OBJECT_COMMIT {
+ tag := &Tag{
+ ID: id,
+ Object: id,
+ Type: string(OBJECT_COMMIT),
+ repo: repo,
+ }
+
+ repo.tagCache.Set(id.String(), tag)
+ return tag, nil
+ }
+
+ // Tag with message.
+ data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := parseTagData(data)
+ if err != nil {
+ return nil, err
+ }
+
+ tag.ID = id
+ tag.repo = repo
+
+ repo.tagCache.Set(id.String(), tag)
+ return tag, nil
+}
+
+// GetTag returns a Git tag by given name.
+func (repo *Repository) GetTag(name string) (*Tag, error) {
+ stdout, err := NewCommand("show-ref", "--tags", name).RunInDir(repo.Path)
+ if err != nil {
+ return nil, err
+ }
+
+ id, err := NewIDFromString(strings.Split(stdout, " ")[0])
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := repo.getTag(id)
+ if err != nil {
+ return nil, err
+ }
+ tag.Name = name
+ return tag, nil
+}
+
+// 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
+}
+
+type TagsResult struct {
+ // Indicates whether results include the latest tag.
+ HasLatest bool
+ // If results do not include the latest tag, a indicator 'after' to go back.
+ PreviousAfter string
+ // Indicates whether results include the oldest tag.
+ ReachEnd bool
+ // List of returned tags.
+ Tags []string
+}
+
+// GetTagsAfter returns list of tags 'after' (exlusive) given tag.
+func (repo *Repository) GetTagsAfter(after string, limit int) (*TagsResult, error) {
+ allTags, err := repo.GetTags()
+ if err != nil {
+ return nil, fmt.Errorf("GetTags: %v", err)
+ }
+
+ if limit < 0 {
+ limit = 0
+ }
+
+ numAllTags := len(allTags)
+ if len(after) == 0 && limit == 0 {
+ return &TagsResult{
+ HasLatest: true,
+ ReachEnd: true,
+ Tags: allTags,
+ }, nil
+ } else if len(after) == 0 && limit > 0 {
+ endIdx := limit
+ if limit >= numAllTags {
+ endIdx = numAllTags
+ }
+ return &TagsResult{
+ HasLatest: true,
+ ReachEnd: limit >= numAllTags,
+ Tags: allTags[:endIdx],
+ }, nil
+ }
+
+ previousAfter := ""
+ hasMatch := false
+ tags := make([]string, 0, len(allTags))
+ for i := range allTags {
+ if hasMatch {
+ tags = allTags[i:]
+ break
+ }
+ if allTags[i] == after {
+ hasMatch = true
+ if limit > 0 && i-limit >= 0 {
+ previousAfter = allTags[i-limit]
+ }
+ continue
+ }
+ }
+
+ if !hasMatch {
+ tags = allTags
+ }
+
+ // If all tags after match is equal to the limit, it reaches the oldest tag as well.
+ if limit == 0 || len(tags) <= limit {
+ return &TagsResult{
+ HasLatest: !hasMatch,
+ PreviousAfter: previousAfter,
+ ReachEnd: true,
+ Tags: tags,
+ }, nil
+ }
+ return &TagsResult{
+ HasLatest: !hasMatch,
+ PreviousAfter: previousAfter,
+ Tags: tags[:limit],
+ }, nil
+}
+
+// DeleteTag deletes a tag from the repository
+func (repo *Repository) DeleteTag(name string) error {
+ cmd := NewCommand("tag", "-d")
+
+ cmd.AddArguments(name)
+ _, err := cmd.RunInDir(repo.Path)
+
+ return err
+}
diff --git a/vendor/github.com/gogs/git-module/repo_tree.go b/vendor/github.com/gogs/git-module/repo_tree.go
new file mode 100644
index 00000000..baebb251
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/repo_tree.go
@@ -0,0 +1,26 @@
+// Copyright 2015 The Gogs 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
+
+func (repo *Repository) getTree(id sha1) (*Tree, error) {
+ treePath := filepathFromSHA1(repo.Path, id.String())
+ if isFile(treePath) {
+ _, err := NewCommand("ls-tree", id.String()).RunInDir(repo.Path)
+ if err != nil {
+ return nil, ErrNotExist{id.String(), ""}
+ }
+ }
+
+ return NewTree(repo, id), nil
+}
+
+// Find the tree object in the repository.
+func (repo *Repository) GetTree(idStr string) (*Tree, error) {
+ id, err := NewIDFromString(idStr)
+ if err != nil {
+ return nil, err
+ }
+ return repo.getTree(id)
+}
diff --git a/vendor/github.com/gogs/git-module/sha1.go b/vendor/github.com/gogs/git-module/sha1.go
new file mode 100644
index 00000000..7744275d
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/sha1.go
@@ -0,0 +1,93 @@
+// Copyright 2015 The Gogs 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 (
+ "encoding/hex"
+ "fmt"
+ "strings"
+)
+
+const EMPTY_SHA = "0000000000000000000000000000000000000000"
+
+type sha1 [20]byte
+
+// Equal returns true if s has the same sha1 as caller.
+// Support 40-length-string, []byte, sha1.
+func (id sha1) Equal(s2 interface{}) bool {
+ switch v := s2.(type) {
+ case string:
+ if len(v) != 40 {
+ return false
+ }
+ return v == id.String()
+ case []byte:
+ if len(v) != 20 {
+ return false
+ }
+ for i, v := range v {
+ if id[i] != v {
+ return false
+ }
+ }
+ case sha1:
+ for i, v := range v {
+ if id[i] != v {
+ return false
+ }
+ }
+ default:
+ return false
+ }
+ return true
+}
+
+// String returns string (hex) representation of the Oid.
+func (s sha1) String() string {
+ result := make([]byte, 0, 40)
+ hexvalues := []byte("0123456789abcdef")
+ for i := 0; i < 20; i++ {
+ result = append(result, hexvalues[s[i]>>4])
+ result = append(result, hexvalues[s[i]&0xf])
+ }
+ return string(result)
+}
+
+// MustID always creates a new sha1 from a [20]byte array with no validation of input.
+func MustID(b []byte) sha1 {
+ var id sha1
+ for i := 0; i < 20; i++ {
+ id[i] = b[i]
+ }
+ return id
+}
+
+// NewID creates a new sha1 from a [20]byte array.
+func NewID(b []byte) (sha1, error) {
+ if len(b) != 20 {
+ return sha1{}, fmt.Errorf("Length must be 20: %v", b)
+ }
+ return MustID(b), nil
+}
+
+// MustIDFromString always creates a new sha from a ID with no validation of input.
+func MustIDFromString(s string) sha1 {
+ b, _ := hex.DecodeString(s)
+ return MustID(b)
+}
+
+// NewIDFromString creates a new sha1 from a ID string of length 40.
+func NewIDFromString(s string) (sha1, error) {
+ var id sha1
+ s = strings.TrimSpace(s)
+ if len(s) != 40 {
+ return id, fmt.Errorf("Length must be 40: %s", s)
+ }
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ return id, err
+ }
+ return NewID(b)
+}
diff --git a/vendor/github.com/gogs/git-module/signature.go b/vendor/github.com/gogs/git-module/signature.go
new file mode 100644
index 00000000..95eb1bbe
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/signature.go
@@ -0,0 +1,48 @@
+// Copyright 2015 The Gogs 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"
+ "strconv"
+ "time"
+)
+
+// Signature represents the Author or Committer information.
+type Signature struct {
+ Email string
+ Name string
+ When time.Time
+}
+
+// Helper to get a signature from the commit line, which looks like these:
+// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
+// author Patrick Gundlach <gundlach@speedata.de> Thu, 07 Apr 2005 22:13:13 +0200
+// but without the "author " at the beginning (this method should)
+// be used for author and committer.
+//
+// FIXME: include timezone for timestamp!
+func newSignatureFromCommitline(line []byte) (_ *Signature, err error) {
+ sig := new(Signature)
+ emailStart := bytes.IndexByte(line, '<')
+ sig.Name = string(line[:emailStart-1])
+ emailEnd := bytes.IndexByte(line, '>')
+ sig.Email = string(line[emailStart+1 : emailEnd])
+
+ // Check date format.
+ firstChar := line[emailEnd+2]
+ if firstChar >= 48 && firstChar <= 57 {
+ timestop := bytes.IndexByte(line[emailEnd+2:], ' ')
+ timestring := string(line[emailEnd+2 : emailEnd+2+timestop])
+ seconds, _ := strconv.ParseInt(timestring, 10, 64)
+ sig.When = time.Unix(seconds, 0)
+ } else {
+ sig.When, err = time.Parse("Mon Jan _2 15:04:05 2006 -0700", string(line[emailEnd+2:]))
+ if err != nil {
+ return nil, err
+ }
+ }
+ return sig, nil
+}
diff --git a/vendor/github.com/gogs/git-module/submodule.go b/vendor/github.com/gogs/git-module/submodule.go
new file mode 100644
index 00000000..57773300
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/submodule.go
@@ -0,0 +1,78 @@
+// Copyright 2015 The Gogs 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 "strings"
+
+type SubModule struct {
+ Name string
+ URL string
+}
+
+// SubModuleFile represents a file with submodule type.
+type SubModuleFile struct {
+ *Commit
+
+ refURL string
+ refID string
+}
+
+func NewSubModuleFile(c *Commit, refURL, refID string) *SubModuleFile {
+ return &SubModuleFile{
+ Commit: c,
+ refURL: refURL,
+ refID: refID,
+ }
+}
+
+// RefURL guesses and returns reference URL.
+func (sf *SubModuleFile) RefURL(urlPrefix string, parentPath string) string {
+ if sf.refURL == "" {
+ return ""
+ }
+
+ url := strings.TrimSuffix(sf.refURL, ".git")
+
+ // git://xxx/user/repo
+ if strings.HasPrefix(url, "git://") {
+ return "http://" + strings.TrimPrefix(url, "git://")
+ }
+
+ // http[s]://xxx/user/repo
+ if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
+ return url
+ }
+
+ // Relative url prefix check (according to git submodule documentation)
+ if strings.HasPrefix(url, "./") || strings.HasPrefix(url, "../") {
+ // ...construct and return correct submodule url here...
+ idx := strings.Index(parentPath, "/src/")
+ if idx == -1 {
+ return url
+ }
+ return strings.TrimSuffix(urlPrefix, "/") + parentPath[:idx] + "/" + url
+ }
+
+ // sysuser@xxx:user/repo
+ i := strings.Index(url, "@")
+ j := strings.LastIndex(url, ":")
+
+ // Only process when i < j because git+ssh://git@git.forwardbias.in/npploader.git
+ if i > -1 && j > -1 && i < j {
+ // fix problem with reverse proxy works only with local server
+ if strings.Contains(urlPrefix, url[i+1:j]) {
+ return urlPrefix + url[j+1:]
+ } else {
+ return "http://" + url[i+1:j] + "/" + url[j+1:]
+ }
+ }
+
+ return url
+}
+
+// RefID returns reference ID.
+func (sf *SubModuleFile) RefID() string {
+ return sf.refID
+}
diff --git a/vendor/github.com/gogs/git-module/tag.go b/vendor/github.com/gogs/git-module/tag.go
new file mode 100644
index 00000000..f4bf7792
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/tag.go
@@ -0,0 +1,65 @@
+// Copyright 2015 The Gogs 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"
+
+// Tag represents a Git tag.
+type Tag struct {
+ Name string
+ ID sha1
+ repo *Repository
+ Object sha1 // The id of this commit object
+ Type string
+ Tagger *Signature
+ Message string
+}
+
+func (tag *Tag) Commit() (*Commit, error) {
+ return tag.repo.getCommit(tag.Object)
+}
+
+// Parse commit information from the (uncompressed) raw
+// data from the commit object.
+// \n\n separate headers from message
+func parseTagData(data []byte) (*Tag, error) {
+ tag := new(Tag)
+ // we now have the contents of the commit object. Let's investigate...
+ nextline := 0
+l:
+ for {
+ eol := bytes.IndexByte(data[nextline:], '\n')
+ switch {
+ case eol > 0:
+ line := data[nextline : nextline+eol]
+ spacepos := bytes.IndexByte(line, ' ')
+ reftype := line[:spacepos]
+ switch string(reftype) {
+ case "object":
+ id, err := NewIDFromString(string(line[spacepos+1:]))
+ if err != nil {
+ return nil, err
+ }
+ tag.Object = id
+ case "type":
+ // A commit can have one or more parents
+ tag.Type = string(line[spacepos+1:])
+ case "tagger":
+ sig, err := newSignatureFromCommitline(line[spacepos+1:])
+ if err != nil {
+ return nil, err
+ }
+ tag.Tagger = sig
+ }
+ nextline += eol + 1
+ case eol == 0:
+ tag.Message = string(data[nextline+1:])
+ break l
+ default:
+ break l
+ }
+ }
+ return tag, nil
+}
diff --git a/vendor/github.com/gogs/git-module/tree.go b/vendor/github.com/gogs/git-module/tree.go
new file mode 100644
index 00000000..789d4c9b
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/tree.go
@@ -0,0 +1,149 @@
+// Copyright 2015 The Gogs 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"
+ "fmt"
+ "strings"
+)
+
+// Tree represents a flat directory listing.
+type Tree struct {
+ ID sha1
+ repo *Repository
+
+ // parent tree
+ ptree *Tree
+
+ entries Entries
+ entriesParsed bool
+}
+
+func NewTree(repo *Repository, id sha1) *Tree {
+ return &Tree{
+ ID: id,
+ repo: repo,
+ }
+}
+
+// Predefine []byte variables to avoid runtime allocations.
+var (
+ escapedSlash = []byte(`\\`)
+ regularSlash = []byte(`\`)
+ escapedTab = []byte(`\t`)
+ regularTab = []byte("\t")
+)
+
+// UnescapeChars reverses escaped characters.
+func UnescapeChars(in []byte) []byte {
+ // LEGACY [Go 1.7]: use more expressive bytes.ContainsAny
+ if bytes.IndexAny(in, "\\\t") == -1 {
+ return in
+ }
+
+ out := bytes.Replace(in, escapedSlash, regularSlash, -1)
+ out = bytes.Replace(out, escapedTab, regularTab, -1)
+ return out
+}
+
+// parseTreeData parses tree information from the (uncompressed) raw
+// data from the tree object.
+func parseTreeData(tree *Tree, data []byte) ([]*TreeEntry, error) {
+ entries := make([]*TreeEntry, 0, 10)
+ l := len(data)
+ pos := 0
+ for pos < l {
+ entry := new(TreeEntry)
+ entry.ptree = tree
+ step := 6
+ switch string(data[pos : pos+step]) {
+ case "100644", "100664":
+ entry.mode = ENTRY_MODE_BLOB
+ entry.Type = OBJECT_BLOB
+ case "100755":
+ entry.mode = ENTRY_MODE_EXEC
+ entry.Type = OBJECT_BLOB
+ case "120000":
+ entry.mode = ENTRY_MODE_SYMLINK
+ entry.Type = OBJECT_BLOB
+ case "160000":
+ entry.mode = ENTRY_MODE_COMMIT
+ entry.Type = OBJECT_COMMIT
+
+ step = 8
+ case "040000":
+ entry.mode = ENTRY_MODE_TREE
+ entry.Type = OBJECT_TREE
+ default:
+ return nil, fmt.Errorf("unknown type: %v", string(data[pos:pos+step]))
+ }
+ pos += step + 6 // Skip string type of entry type.
+
+ step = 40
+ id, err := NewIDFromString(string(data[pos : pos+step]))
+ if err != nil {
+ return nil, err
+ }
+ entry.ID = id
+ pos += step + 1 // Skip half of sha1.
+
+ step = bytes.IndexByte(data[pos:], '\n')
+
+ // In case entry name is surrounded by double quotes(it happens only in git-shell).
+ if data[pos] == '"' {
+ entry.name = string(UnescapeChars(data[pos+1 : pos+step-1]))
+ } else {
+ entry.name = string(data[pos : pos+step])
+ }
+
+ pos += step + 1
+ entries = append(entries, entry)
+ }
+ return entries, nil
+}
+
+func (t *Tree) SubTree(rpath string) (*Tree, error) {
+ if len(rpath) == 0 {
+ return t, nil
+ }
+
+ paths := strings.Split(rpath, "/")
+ var (
+ err error
+ g = t
+ p = t
+ te *TreeEntry
+ )
+ for _, name := range paths {
+ te, err = p.GetTreeEntryByPath(name)
+ if err != nil {
+ return nil, err
+ }
+
+ g, err = t.repo.getTree(te.ID)
+ if err != nil {
+ return nil, err
+ }
+ g.ptree = p
+ p = g
+ }
+ return g, nil
+}
+
+// ListEntries returns all entries of current tree.
+func (t *Tree) ListEntries() (Entries, error) {
+ if t.entriesParsed {
+ return t.entries, nil
+ }
+ t.entriesParsed = true
+
+ stdout, err := NewCommand("ls-tree", t.ID.String()).RunInDirBytes(t.repo.Path)
+ if err != nil {
+ return nil, err
+ }
+ t.entries, err = parseTreeData(t, stdout)
+ return t.entries, err
+}
diff --git a/vendor/github.com/gogs/git-module/tree_blob.go b/vendor/github.com/gogs/git-module/tree_blob.go
new file mode 100644
index 00000000..e2e70152
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/tree_blob.go
@@ -0,0 +1,57 @@
+// Copyright 2015 The Gogs 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"
+ "strings"
+)
+
+func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
+ if len(relpath) == 0 {
+ return &TreeEntry{
+ ID: t.ID,
+ Type: OBJECT_TREE,
+ mode: ENTRY_MODE_TREE,
+ }, nil
+ }
+
+ 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
+ }
+ }
+ }
+ return nil, ErrNotExist{"", relpath}
+}
+
+func (t *Tree) GetBlobByPath(relpath string) (*Blob, error) {
+ entry, err := t.GetTreeEntryByPath(relpath)
+ if err != nil {
+ return nil, err
+ }
+
+ if !entry.IsDir() {
+ return entry.Blob(), nil
+ }
+
+ return nil, ErrNotExist{"", relpath}
+}
diff --git a/vendor/github.com/gogs/git-module/tree_entry.go b/vendor/github.com/gogs/git-module/tree_entry.go
new file mode 100644
index 00000000..54574035
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/tree_entry.go
@@ -0,0 +1,226 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "path"
+ "path/filepath"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+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 (
+ ENTRY_MODE_BLOB EntryMode = 0100644
+ ENTRY_MODE_EXEC EntryMode = 0100755
+ ENTRY_MODE_SYMLINK EntryMode = 0120000
+ ENTRY_MODE_COMMIT EntryMode = 0160000
+ ENTRY_MODE_TREE EntryMode = 0040000
+)
+
+type TreeEntry struct {
+ ID sha1
+ Type ObjectType
+
+ mode EntryMode
+ name string
+
+ ptree *Tree
+
+ commited bool
+
+ size int64
+ sized bool
+}
+
+func (te *TreeEntry) Name() string {
+ return te.name
+}
+
+func (te *TreeEntry) Size() int64 {
+ if te.IsDir() {
+ return 0
+ } else if te.sized {
+ return te.size
+ }
+
+ stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
+ if err != nil {
+ return 0
+ }
+
+ te.sized = true
+ te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
+ return te.size
+}
+
+func (te *TreeEntry) IsSubModule() bool {
+ return te.mode == ENTRY_MODE_COMMIT
+}
+
+func (te *TreeEntry) IsDir() bool {
+ return te.mode == ENTRY_MODE_TREE
+}
+
+func (te *TreeEntry) IsLink() bool {
+ return te.mode == ENTRY_MODE_SYMLINK
+}
+
+func (te *TreeEntry) Blob() *Blob {
+ return &Blob{
+ repo: te.ptree.repo,
+ TreeEntry: te,
+ }
+}
+
+type Entries []*TreeEntry
+
+var sorter = []func(t1, t2 *TreeEntry) bool{
+ func(t1, t2 *TreeEntry) bool {
+ return (t1.IsDir() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
+ },
+ func(t1, t2 *TreeEntry) bool {
+ return t1.name < t2.name
+ },
+}
+
+func (tes Entries) Len() int { return len(tes) }
+func (tes Entries) Swap(i, j int) { tes[i], tes[j] = tes[j], tes[i] }
+func (tes Entries) Less(i, j int) bool {
+ t1, t2 := tes[i], tes[j]
+ var k int
+ for k = 0; k < len(sorter)-1; k++ {
+ sort := sorter[k]
+ switch {
+ case sort(t1, t2):
+ return true
+ case sort(t2, t1):
+ return false
+ }
+ }
+ return sorter[k](t1, t2)
+}
+
+func (tes Entries) Sort() {
+ sort.Sort(tes)
+}
+
+var defaultConcurrency = runtime.NumCPU()
+
+type commitInfo struct {
+ entryName string
+ infos []interface{}
+ err error
+}
+
+// GetCommitsInfo takes advantages of concurrency to speed up getting information
+// of all commits that are corresponding to these entries. This method will automatically
+// choose the right number of goroutine (concurrency) to use related of the host CPU.
+func (tes Entries) GetCommitsInfo(commit *Commit, treePath string) ([][]interface{}, error) {
+ return tes.GetCommitsInfoWithCustomConcurrency(commit, treePath, 0)
+}
+
+// GetCommitsInfoWithCustomConcurrency takes advantages of concurrency to speed up getting information
+// of all commits that are corresponding to these entries. If the given maxConcurrency is negative or
+// equal to zero: the right number of goroutine (concurrency) to use will be choosen related of the
+// host CPU.
+func (tes Entries) GetCommitsInfoWithCustomConcurrency(commit *Commit, treePath string, maxConcurrency int) ([][]interface{}, error) {
+ if len(tes) == 0 {
+ return nil, nil
+ }
+
+ if maxConcurrency <= 0 {
+ maxConcurrency = defaultConcurrency
+ }
+
+ // Length of taskChan determines how many goroutines (subprocesses) can run at the same time.
+ // The length of revChan should be same as taskChan so goroutines whoever finished job can
+ // exit as early as possible, only store data inside channel.
+ taskChan := make(chan bool, maxConcurrency)
+ revChan := make(chan commitInfo, maxConcurrency)
+ doneChan := make(chan error)
+
+ // Receive loop will exit when it collects same number of data pieces as tree entries.
+ // It notifies doneChan before exits or notify early with possible error.
+ infoMap := make(map[string][]interface{}, len(tes))
+ go func() {
+ i := 0
+ for info := range revChan {
+ if info.err != nil {
+ doneChan <- info.err
+ return
+ }
+
+ infoMap[info.entryName] = info.infos
+ i++
+ if i == len(tes) {
+ break
+ }
+ }
+ doneChan <- nil
+ }()
+
+ for i := range tes {
+ // When taskChan is idle (or has empty slots), put operation will not block.
+ // However when taskChan is full, code will block and wait any running goroutines to finish.
+ taskChan <- true
+
+ if tes[i].Type != OBJECT_COMMIT {
+ go func(i int) {
+ cinfo := commitInfo{entryName: tes[i].Name()}
+ c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name()))
+ if err != nil {
+ cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err)
+ } else {
+ cinfo.infos = []interface{}{tes[i], c}
+ }
+ revChan <- cinfo
+ <-taskChan // Clear one slot from taskChan to allow new goroutines to start.
+ }(i)
+ continue
+ }
+
+ // Handle submodule
+ go func(i int) {
+ cinfo := commitInfo{entryName: tes[i].Name()}
+ sm, err := commit.GetSubModule(path.Join(treePath, tes[i].Name()))
+ if err != nil && !IsErrNotExist(err) {
+ cinfo.err = fmt.Errorf("GetSubModule (%s/%s): %v", treePath, tes[i].Name(), err)
+ revChan <- cinfo
+ return
+ }
+
+ smURL := ""
+ if sm != nil {
+ smURL = sm.URL
+ }
+
+ c, err := commit.GetCommitByPath(filepath.Join(treePath, tes[i].Name()))
+ if err != nil {
+ cinfo.err = fmt.Errorf("GetCommitByPath (%s/%s): %v", treePath, tes[i].Name(), err)
+ } else {
+ cinfo.infos = []interface{}{tes[i], NewSubModuleFile(c, smURL, tes[i].ID.String())}
+ }
+ revChan <- cinfo
+ <-taskChan
+ }(i)
+ }
+
+ if err := <-doneChan; err != nil {
+ return nil, err
+ }
+
+ commitsInfo := make([][]interface{}, len(tes))
+ for i := 0; i < len(tes); i++ {
+ commitsInfo[i] = infoMap[tes[i].Name()]
+ }
+ return commitsInfo, nil
+}
diff --git a/vendor/github.com/gogs/git-module/utils.go b/vendor/github.com/gogs/git-module/utils.go
new file mode 100644
index 00000000..da8c5817
--- /dev/null
+++ b/vendor/github.com/gogs/git-module/utils.go
@@ -0,0 +1,93 @@
+// Copyright 2015 The Gogs 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 (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+)
+
+// objectCache provides thread-safe cache opeations.
+type objectCache struct {
+ lock sync.RWMutex
+ cache map[string]interface{}
+}
+
+func newObjectCache() *objectCache {
+ return &objectCache{
+ cache: make(map[string]interface{}, 10),
+ }
+}
+
+func (oc *objectCache) Set(id string, obj interface{}) {
+ oc.lock.Lock()
+ defer oc.lock.Unlock()
+
+ oc.cache[id] = obj
+}
+
+func (oc *objectCache) Get(id string) (interface{}, bool) {
+ oc.lock.RLock()
+ defer oc.lock.RUnlock()
+
+ obj, has := oc.cache[id]
+ 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)
+}
+
+func concatenateError(err error, stderr string) error {
+ if len(stderr) == 0 {
+ return err
+ }
+ return fmt.Errorf("%v - %s", err, stderr)
+}
+
+// If the object is stored in its own file (i.e not in a pack file),
+// this function returns the full path to the object file.
+// It does not test if the file exists.
+func filepathFromSHA1(rootdir, sha1 string) string {
+ return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
+}
+
+func RefEndName(refStr string) string {
+ if strings.HasPrefix(refStr, BRANCH_PREFIX) {
+ return refStr[len(BRANCH_PREFIX):]
+ }
+
+ if strings.HasPrefix(refStr, TAG_PREFIX) {
+ return refStr[len(TAG_PREFIX):]
+ }
+
+ return refStr
+}