diff options
author | Unknwon <joe2010xtmf@163.com> | 2014-07-26 00:24:27 -0400 |
---|---|---|
committer | Unknwon <joe2010xtmf@163.com> | 2014-07-26 00:24:27 -0400 |
commit | 8dd07c0ddd99ae626a1ec8c06f75f27fed51269f (patch) | |
tree | 261d3c9911dabc58c1ac54e4e36b3dee24d2032b /modules/git | |
parent | 0a739cf9ac901f54484c34bba8322418dedb09b0 (diff) | |
download | gitea-8dd07c0ddd99ae626a1ec8c06f75f27fed51269f.tar.gz gitea-8dd07c0ddd99ae626a1ec8c06f75f27fed51269f.zip |
New UI merge in progress
Diffstat (limited to 'modules/git')
-rw-r--r-- | modules/git/blob.go | 26 | ||||
-rw-r--r-- | modules/git/commit.go | 78 | ||||
-rw-r--r-- | modules/git/commit_archive.go | 36 | ||||
-rw-r--r-- | modules/git/repo.go | 27 | ||||
-rw-r--r-- | modules/git/repo_branch.go | 38 | ||||
-rw-r--r-- | modules/git/repo_commit.go | 234 | ||||
-rw-r--r-- | modules/git/repo_object.go | 14 | ||||
-rw-r--r-- | modules/git/repo_tag.go | 96 | ||||
-rw-r--r-- | modules/git/repo_tree.go | 32 | ||||
-rw-r--r-- | modules/git/sha1.go | 87 | ||||
-rw-r--r-- | modules/git/signature.go | 40 | ||||
-rw-r--r-- | modules/git/tag.go | 67 | ||||
-rw-r--r-- | modules/git/tree.go | 124 | ||||
-rw-r--r-- | modules/git/tree_blob.go | 46 | ||||
-rw-r--r-- | modules/git/tree_entry.go | 109 | ||||
-rw-r--r-- | modules/git/utils.go | 27 | ||||
-rw-r--r-- | modules/git/version.go | 43 |
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 +} |