summaryrefslogtreecommitdiffstats
path: root/modules/git
diff options
context:
space:
mode:
authorUnknwon <joe2010xtmf@163.com>2014-07-26 00:24:27 -0400
committerUnknwon <joe2010xtmf@163.com>2014-07-26 00:24:27 -0400
commit8dd07c0ddd99ae626a1ec8c06f75f27fed51269f (patch)
tree261d3c9911dabc58c1ac54e4e36b3dee24d2032b /modules/git
parent0a739cf9ac901f54484c34bba8322418dedb09b0 (diff)
downloadgitea-8dd07c0ddd99ae626a1ec8c06f75f27fed51269f.tar.gz
gitea-8dd07c0ddd99ae626a1ec8c06f75f27fed51269f.zip
New UI merge in progress
Diffstat (limited to 'modules/git')
-rw-r--r--modules/git/blob.go26
-rw-r--r--modules/git/commit.go78
-rw-r--r--modules/git/commit_archive.go36
-rw-r--r--modules/git/repo.go27
-rw-r--r--modules/git/repo_branch.go38
-rw-r--r--modules/git/repo_commit.go234
-rw-r--r--modules/git/repo_object.go14
-rw-r--r--modules/git/repo_tag.go96
-rw-r--r--modules/git/repo_tree.go32
-rw-r--r--modules/git/sha1.go87
-rw-r--r--modules/git/signature.go40
-rw-r--r--modules/git/tag.go67
-rw-r--r--modules/git/tree.go124
-rw-r--r--modules/git/tree_blob.go46
-rw-r--r--modules/git/tree_entry.go109
-rw-r--r--modules/git/utils.go27
-rw-r--r--modules/git/version.go43
17 files changed, 1124 insertions, 0 deletions
diff --git a/modules/git/blob.go b/modules/git/blob.go
new file mode 100644
index 0000000000..3ce462a3c2
--- /dev/null
+++ b/modules/git/blob.go
@@ -0,0 +1,26 @@
+// 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
+
+import (
+ "bytes"
+ "errors"
+ "io"
+
+ "github.com/Unknwon/com"
+)
+
+type Blob struct {
+ repo *Repository
+ *TreeEntry
+}
+
+func (b *Blob) Data() (io.Reader, error) {
+ stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.Id.String())
+ if err != nil {
+ return nil, errors.New(string(stderr))
+ }
+ return bytes.NewBuffer(stdout), nil
+}
diff --git a/modules/git/commit.go b/modules/git/commit.go
new file mode 100644
index 0000000000..f61f2927b9
--- /dev/null
+++ b/modules/git/commit.go
@@ -0,0 +1,78 @@
+// 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
+
+import (
+ "container/list"
+ "strings"
+)
+
+// 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
+}
+
+// Return the commit message. Same as retrieving CommitMessage directly.
+func (c *Commit) Message() string {
+ return c.CommitMessage
+}
+
+func (c *Commit) Summary() string {
+ return strings.Split(c.CommitMessage, "\n")[0]
+}
+
+// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
+func (c *Commit) ParentId(n int) (id sha1, err error) {
+ if n >= len(c.parents) {
+ err = IdNotExist
+ return
+ }
+ return c.parents[n], nil
+}
+
+// Return parent number n (0-based index)
+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
+}
+
+// Return the number of parents of the commit. 0 if this is the
+// root commit, otherwise 1,2,...
+func (c *Commit) ParentCount() int {
+ return len(c.parents)
+}
+
+func (c *Commit) CommitsBefore() (*list.List, error) {
+ return c.repo.getCommitsBefore(c.Id)
+}
+
+func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
+ ec, err := c.repo.GetCommit(commitId)
+ if err != nil {
+ return nil, err
+ }
+ return c.repo.CommitsBetween(c, ec)
+}
+
+func (c *Commit) CommitsCount() (int, error) {
+ return c.repo.commitsCount(c.Id)
+}
+
+func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
+ return c.repo.getCommitOfRelPath(c.Id, relPath)
+}
diff --git a/modules/git/commit_archive.go b/modules/git/commit_archive.go
new file mode 100644
index 0000000000..23b4b058dd
--- /dev/null
+++ b/modules/git/commit_archive.go
@@ -0,0 +1,36 @@
+// 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
+
+import (
+ "fmt"
+
+ "github.com/Unknwon/com"
+)
+
+type ArchiveType int
+
+const (
+ ZIP ArchiveType = iota + 1
+ TARGZ
+)
+
+func (c *Commit) CreateArchive(path 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)
+ }
+
+ _, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.Id.String())
+ if err != nil {
+ return fmt.Errorf("%s", stderr)
+ }
+ return nil
+}
diff --git a/modules/git/repo.go b/modules/git/repo.go
new file mode 100644
index 0000000000..14ac39a3ed
--- /dev/null
+++ b/modules/git/repo.go
@@ -0,0 +1,27 @@
+// 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
+
+import (
+ "path/filepath"
+)
+
+// Repository represents a Git repository.
+type Repository struct {
+ Path string
+
+ commitCache map[sha1]*Commit
+ tagCache map[sha1]*Tag
+}
+
+// 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
+ }
+
+ return &Repository{Path: repoPath}, nil
+}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
new file mode 100644
index 0000000000..726019c1b1
--- /dev/null
+++ b/modules/git/repo_branch.go
@@ -0,0 +1,38 @@
+// 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
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+func IsBranchExist(repoPath, branchName string) bool {
+ _, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/heads/"+branchName)
+ return err == nil
+}
+
+func (repo *Repository) IsBranchExist(branchName string) bool {
+ return IsBranchExist(repo.Path, branchName)
+}
+
+func (repo *Repository) GetBranches() ([]string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--heads")
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+ infos := strings.Split(stdout, "\n")
+ branches := make([]string, len(infos)-1)
+ for i, info := range infos[:len(infos)-1] {
+ parts := strings.Split(info, " ")
+ if len(parts) != 2 {
+ continue // NOTE: I should believe git will not give me wrong string.
+ }
+ branches[i] = strings.TrimPrefix(parts[1], "refs/heads/")
+ }
+ return branches, nil
+}
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
new file mode 100644
index 0000000000..b1ea5a29a5
--- /dev/null
+++ b/modules/git/repo_commit.go
@@ -0,0 +1,234 @@
+// 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
+
+import (
+ "bytes"
+ "container/list"
+ "errors"
+ "strings"
+ "sync"
+
+ "github.com/Unknwon/com"
+)
+
+func (repo *Repository) getCommitIdOfRef(refpath string) (string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--verify", refpath)
+ if err != nil {
+ return "", errors.New(stderr)
+ }
+ return strings.Split(stdout, " ")[0], nil
+}
+
+func (repo *Repository) GetCommitIdOfBranch(branchName string) (string, error) {
+ return repo.getCommitIdOfRef("refs/heads/" + branchName)
+}
+
+// get branch's last commit or a special commit by id string
+func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
+ commitId, err := repo.GetCommitIdOfBranch(branchName)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.GetCommit(commitId)
+}
+
+// Parse 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":
+ 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":
+ 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) {
+ if repo.commitCache != nil {
+ if c, ok := repo.commitCache[id]; ok {
+ return c, nil
+ }
+ } else {
+ repo.commitCache = make(map[sha1]*Commit, 10)
+ }
+
+ data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
+ if err != nil {
+ return nil, errors.New(string(bytErr))
+ }
+
+ commit, err := parseCommitData(data)
+ if err != nil {
+ return nil, err
+ }
+ commit.repo = repo
+ commit.Id = id
+
+ repo.commitCache[id] = commit
+ return commit, nil
+}
+
+// Find the commit object in the repository.
+func (repo *Repository) GetCommit(commitId string) (*Commit, error) {
+ id, err := NewIdFromString(commitId)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
+
+func (repo *Repository) commitsCount(id sha1) (int, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", id.String())
+ if err != nil {
+ return 0, errors.New(stderr)
+ }
+ return com.StrTo(strings.TrimSpace(stdout)).Int()
+}
+
+// used only for single tree, (]
+func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
+ 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) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
+ commit, err := repo.getCommit(id)
+ if err != nil {
+ return 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)
+ }
+
+ var 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(lock, l, pr, id, 0)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
+ l := list.New()
+ lock := new(sync.Mutex)
+ err := repo.commitsBefore(lock, l, nil, id, 0)
+ return l, err
+}
+
+func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
+ stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
+ if err != nil {
+ return nil, err
+ }
+
+ id, err = NewIdFromString(string(stdout))
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go
new file mode 100644
index 0000000000..da79f2c731
--- /dev/null
+++ b/modules/git/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 (
+ COMMIT ObjectType = "commit"
+ TREE ObjectType = "tree"
+ BLOB ObjectType = "blob"
+ TAG ObjectType = "tag"
+)
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
new file mode 100644
index 0000000000..cefbe783ab
--- /dev/null
+++ b/modules/git/repo_tag.go
@@ -0,0 +1,96 @@
+// 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
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+func IsTagExist(repoPath, tagName string) bool {
+ _, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/tags/"+tagName)
+ return err == nil
+}
+
+func (repo *Repository) IsTagExist(tagName string) bool {
+ return IsTagExist(repo.Path, tagName)
+}
+
+// GetTags returns all tags of given repository.
+func (repo *Repository) GetTags() ([]string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l")
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+ tags := strings.Split(stdout, "\n")
+ return tags[:len(tags)-1], nil
+}
+
+func (repo *Repository) getTag(id sha1) (*Tag, error) {
+ if repo.tagCache != nil {
+ if t, ok := repo.tagCache[id]; ok {
+ return t, nil
+ }
+ } else {
+ repo.tagCache = make(map[sha1]*Tag, 10)
+ }
+
+ // Get tag type.
+ tp, stderr, err := com.ExecCmdDir(repo.Path, "git", "cat-file", "-t", id.String())
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+
+ // Tag is a commit.
+ if ObjectType(tp) == COMMIT {
+ tag := &Tag{
+ Id: id,
+ Object: id,
+ Type: string(COMMIT),
+ repo: repo,
+ }
+ repo.tagCache[id] = tag
+ return tag, nil
+ }
+
+ // Tag with message.
+ data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
+ if err != nil {
+ return nil, errors.New(string(bytErr))
+ }
+
+ tag, err := parseTagData(data)
+ if err != nil {
+ return nil, err
+ }
+
+ tag.Id = id
+ tag.repo = repo
+
+ repo.tagCache[id] = tag
+ return tag, nil
+}
+
+// GetTag returns a Git tag by given name.
+func (repo *Repository) GetTag(tagName string) (*Tag, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--tags", tagName)
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+
+ 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 = tagName
+ return tag, nil
+}
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
new file mode 100644
index 0000000000..da394c3605
--- /dev/null
+++ b/modules/git/repo_tree.go
@@ -0,0 +1,32 @@
+// 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
+
+import (
+ "fmt"
+
+ "github.com/Unknwon/com"
+)
+
+// 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)
+}
+
+func (repo *Repository) getTree(id sha1) (*Tree, error) {
+ treePath := filepathFromSHA1(repo.Path, id.String())
+ if !com.IsFile(treePath) {
+ _, _, err := com.ExecCmdDir(repo.Path, "git", "ls-tree", id.String())
+ if err != nil {
+ return nil, fmt.Errorf("repo.getTree: %v", ErrNotExist)
+ }
+ }
+
+ return NewTree(repo, id), nil
+}
diff --git a/modules/git/sha1.go b/modules/git/sha1.go
new file mode 100644
index 0000000000..5c57e89b6a
--- /dev/null
+++ b/modules/git/sha1.go
@@ -0,0 +1,87 @@
+// 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
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+)
+
+var (
+ IdNotExist = errors.New("sha1 id not exist")
+)
+
+type sha1 [20]byte
+
+// Return 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
+}
+
+// Return 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)
+}
+
+// Create a new sha1 from a 20 byte slice.
+func NewId(b []byte) (sha1, error) {
+ var id sha1
+ if len(b) != 20 {
+ return id, errors.New("Length must be 20")
+ }
+
+ for i := 0; i < 20; i++ {
+ id[i] = b[i]
+ }
+ return id, nil
+}
+
+// Create a new sha1 from a Sha1 string of length 40.
+func NewIdFromString(s string) (sha1, error) {
+ s = strings.TrimSpace(s)
+ var id sha1
+ if len(s) != 40 {
+ return id, fmt.Errorf("Length must be 40")
+ }
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ return id, err
+ }
+
+ return NewId(b)
+}
diff --git a/modules/git/signature.go b/modules/git/signature.go
new file mode 100644
index 0000000000..20f647d266
--- /dev/null
+++ b/modules/git/signature.go
@@ -0,0 +1,40 @@
+// 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
+
+import (
+ "bytes"
+ "strconv"
+ "time"
+)
+
+// Author and Committer information
+type Signature struct {
+ Email string
+ Name string
+ When time.Time
+}
+
+// Helper to get a signature from the commit line, which looks like this:
+// author Patrick Gundlach <gundlach@speedata.de> 1378823654 +0200
+// but without the "author " at the beginning (this method should)
+// be used for author and committer.
+//
+// FIXME: include timezone!
+func newSignatureFromCommitline(line []byte) (*Signature, error) {
+ sig := new(Signature)
+ emailstart := bytes.IndexByte(line, '<')
+ sig.Name = string(line[:emailstart-1])
+ emailstop := bytes.IndexByte(line, '>')
+ sig.Email = string(line[emailstart+1 : emailstop])
+ timestop := bytes.IndexByte(line[emailstop+2:], ' ')
+ timestring := string(line[emailstop+2 : emailstop+2+timestop])
+ seconds, err := strconv.ParseInt(timestring, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ sig.When = time.Unix(seconds, 0)
+ return sig, nil
+}
diff --git a/modules/git/tag.go b/modules/git/tag.go
new file mode 100644
index 0000000000..7fbbcb1cbf
--- /dev/null
+++ b/modules/git/tag.go
@@ -0,0 +1,67 @@
+// 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
+
+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
+ TagMessage 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.TagMessage = string(data[nextline+1:])
+ break l
+ default:
+ break l
+ }
+ }
+ return tag, nil
+}
diff --git a/modules/git/tree.go b/modules/git/tree.go
new file mode 100644
index 0000000000..03152c34ac
--- /dev/null
+++ b/modules/git/tree.go
@@ -0,0 +1,124 @@
+// 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
+
+import (
+ "bytes"
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+var (
+ ErrNotExist = errors.New("error not exist")
+)
+
+// A tree is a flat directory listing.
+type Tree struct {
+ Id sha1
+ repo *Repository
+
+ // parent tree
+ ptree *Tree
+
+ entries Entries
+ entriesParsed bool
+}
+
+// Parse 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":
+ entry.mode = ModeBlob
+ entry.Type = BLOB
+ case "100755":
+ entry.mode = ModeExec
+ entry.Type = BLOB
+ case "120000":
+ entry.mode = ModeSymlink
+ entry.Type = BLOB
+ case "160000":
+ entry.mode = ModeCommit
+ entry.Type = COMMIT
+ case "040000":
+ entry.mode = ModeTree
+ entry.Type = TREE
+ default:
+ return nil, errors.New("unknown type: " + 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')
+ 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
+ var g = t
+ var p = t
+ var 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
+}
+
+func (t *Tree) ListEntries(relpath string) (Entries, error) {
+ if t.entriesParsed {
+ return t.entries, nil
+ }
+ t.entriesParsed = true
+
+ stdout, _, err := com.ExecCmdDirBytes(t.repo.Path,
+ "git", "ls-tree", t.Id.String())
+ if err != nil {
+ return nil, err
+ }
+ t.entries, err = parseTreeData(t, stdout)
+ return t.entries, err
+}
+
+func NewTree(repo *Repository, id sha1) *Tree {
+ tree := new(Tree)
+ tree.Id = id
+ tree.repo = repo
+ return tree
+}
diff --git a/modules/git/tree_blob.go b/modules/git/tree_blob.go
new file mode 100644
index 0000000000..debc722bc9
--- /dev/null
+++ b/modules/git/tree_blob.go
@@ -0,0 +1,46 @@
+// 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
+
+import (
+ "fmt"
+ "path"
+ "strings"
+)
+
+func (t *Tree) GetTreeEntryByPath(relpath string) (*TreeEntry, error) {
+ if len(relpath) == 0 {
+ return &TreeEntry{
+ Id: t.Id,
+ Type: TREE,
+ mode: ModeTree,
+ }, nil
+ // return nil, fmt.Errorf("GetTreeEntryByPath(empty relpath): %v", ErrNotExist)
+ }
+
+ 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(path.Dir(relpath))
+ 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, fmt.Errorf("GetTreeEntryByPath: %v", ErrNotExist)
+}
diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go
new file mode 100644
index 0000000000..e842f2332a
--- /dev/null
+++ b/modules/git/tree_entry.go
@@ -0,0 +1,109 @@
+// 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
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+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 (
+ ModeBlob EntryMode = 0100644
+ ModeExec EntryMode = 0100755
+ ModeSymlink EntryMode = 0120000
+ ModeCommit EntryMode = 0160000
+ ModeTree 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
+ }
+
+ if te.sized {
+ return te.size
+ }
+
+ stdout, _, err := com.ExecCmdDir(te.ptree.repo.Path, "git", "cat-file", "-s", te.Id.String())
+ if err != nil {
+ return 0
+ }
+
+ te.sized = true
+ te.size = com.StrTo(strings.TrimSpace(stdout)).MustInt64()
+ return te.size
+}
+
+func (te *TreeEntry) IsDir() bool {
+ return te.mode == ModeTree
+}
+
+func (te *TreeEntry) EntryMode() EntryMode {
+ return te.mode
+}
+
+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() && !t2.IsDir()
+ },
+ func(t1, t2 *TreeEntry) bool {
+ return t1.name < t2.name
+ },
+}
+
+func (bs Entries) Len() int { return len(bs) }
+func (bs Entries) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
+func (bs Entries) Less(i, j int) bool {
+ t1, t2 := bs[i], bs[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 (bs Entries) Sort() {
+ sort.Sort(bs)
+}
diff --git a/modules/git/utils.go b/modules/git/utils.go
new file mode 100644
index 0000000000..3c0c60f235
--- /dev/null
+++ b/modules/git/utils.go
@@ -0,0 +1,27 @@
+// 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
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+const prettyLogFormat = `--pretty=format:%H`
+
+func RefEndName(refStr string) string {
+ index := strings.LastIndex(refStr, "/")
+ if index != -1 {
+ return refStr[index+1:]
+ }
+ return refStr
+}
+
+// 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:])
+}
diff --git a/modules/git/version.go b/modules/git/version.go
new file mode 100644
index 0000000000..683e859b47
--- /dev/null
+++ b/modules/git/version.go
@@ -0,0 +1,43 @@
+// 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
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+// Version represents version of Git.
+type Version struct {
+ Major, Minor, Patch int
+}
+
+// GetVersion returns current Git version installed.
+func GetVersion() (Version, error) {
+ stdout, stderr, err := com.ExecCmd("git", "version")
+ if err != nil {
+ return Version{}, errors.New(stderr)
+ }
+
+ infos := strings.Split(stdout, " ")
+ if len(infos) < 3 {
+ return Version{}, errors.New("not enough output")
+ }
+
+ v := Version{}
+ for i, s := range strings.Split(strings.TrimSpace(infos[2]), ".") {
+ switch i {
+ case 0:
+ v.Major, _ = com.StrTo(s).Int()
+ case 1:
+ v.Minor, _ = com.StrTo(s).Int()
+ case 2:
+ v.Patch, _ = com.StrTo(s).Int()
+ }
+ }
+ return v, nil
+}