From e97767e2eb247a16dc74cbb1b42f06f5eaa6d255 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Fri, 27 Nov 2015 00:24:02 -0500 Subject: [PATCH] migrate some commands --- blob.go | 23 ++++++++ command.go | 79 +++++++++++++++++++------- commit.go | 67 ++++++++++++++++++++++ error.go | 31 ++++++++-- git.go | 4 ++ repo.go | 82 ++++++++++++++++++++++++++- repo_branch.go | 42 ++++++++++++++ repo_commit.go | 137 ++++++++++++++++++++++++++++++++++++++++++++ repo_hook.go | 23 ++++++++ repo_object.go | 14 +++++ repo_tree.go | 17 ++++++ sha1.go | 51 +++++++++++++++++ signature.go | 32 +++++++++++ tree.go | 151 +++++++++++++++++++++++++++++++++++++++++++++++++ tree_blob.go | 57 +++++++++++++++++++ tree_entry.go | 45 +++++++++++++++ utlis.go | 45 +++++++++++++++ 17 files changed, 875 insertions(+), 25 deletions(-) create mode 100644 blob.go create mode 100644 commit.go create mode 100644 repo_branch.go create mode 100644 repo_commit.go create mode 100644 repo_hook.go create mode 100644 repo_object.go create mode 100644 repo_tree.go create mode 100644 sha1.go create mode 100644 tree.go create mode 100644 tree_blob.go create mode 100644 tree_entry.go create mode 100644 utlis.go diff --git a/blob.go b/blob.go new file mode 100644 index 0000000..7b57a70 --- /dev/null +++ b/blob.go @@ -0,0 +1,23 @@ +// 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" +) + +type Blob struct { + repo *Repository + *TreeEntry +} + +func (b *Blob) Data() (io.Reader, error) { + stdout, err := NewCommand("show", b.ID.String()).RunInDirBytes(b.repo.Path) + if err != nil { + return nil, err + } + return bytes.NewBuffer(stdout), nil +} diff --git a/command.go b/command.go index 58fd7fc..79367df 100644 --- a/command.go +++ b/command.go @@ -9,6 +9,7 @@ import ( "fmt" "os/exec" "strings" + "time" ) // Command represents a command with its subcommands or arguments. @@ -38,39 +39,79 @@ func (c *Command) AddArguments(args ...string) *Command { return c } -func (c *Command) run(dir string) (string, error) { +const DEFAULT_TIMEOUT = 60 * time.Second + +// 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) { + if timeout == -1 { + timeout = DEFAULT_TIMEOUT + } + stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - cmd := exec.Command(c.name, c.args...) - cmd.Dir = dir - cmd.Stdout = stdout - cmd.Stderr = stderr - if len(dir) == 0 { log(c.String()) } else { log("%s: %v", dir, c) } - if err := cmd.Run(); err != nil { - return stdout.String(), concatenateError(err, stderr.String()) + cmd := exec.Command(c.name, c.args...) + cmd.Dir = dir + cmd.Stdout = stdout + cmd.Stderr = stderr + if err := cmd.Start(); err != nil { + return nil, concatenateError(err, stderr.String()) } + + 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 nil, fmt.Errorf("fail to kill process: %v", err) + } + } + + <-done + return nil, ErrExecTimeout{timeout} + case err = <-done: + } + + if err != nil { + return nil, concatenateError(err, stderr.String()) + } + if stdout.Len() > 0 { log("stdout:\n%s", stdout) } - - return stdout.String(), nil -} - -// Run executes the command in defualt working directory -// and returns stdout and error (combined with stderr). -func (c *Command) Run() (string, error) { - return c.run("") + return stdout.Bytes(), nil } // RunInDir executes the command in given directory -// and returns stdout and error (combined with stderr). -func (c *Command) RunInDir(dir string) (string, error) { - return c.run(dir) +// 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 +} + +// 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.RunInDir("") } diff --git a/commit.go b/commit.go new file mode 100644 index 0000000..b1e135a --- /dev/null +++ b/commit.go @@ -0,0 +1,67 @@ +// 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" + "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 + // submodules map[string]*SubModule +} + +// 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 +} + +// CommitChanges commits local changes with given message and author. +func CommitChanges(repoPath, message string, author *Signature) error { + cmd := NewCommand("commit", "-m", message) + if author != nil { + cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", author.Name, author.Email)) + } + _, 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 +} + +// CommitsCount returns number of total commits of until given revision. +func CommitsCount(repoPath, revision string) (int64, error) { + if version.Compare(gitVersion, "1.8.0", "<") { + stdout, err := NewCommand("log", "--pretty=format:''", revision).RunInDir(repoPath) + if err != nil { + return 0, err + } + return int64(len(strings.Split(stdout, "\n"))), nil + } + + stdout, err := NewCommand("rev-list", "--count", revision).RunInDir(repoPath) + if err != nil { + return 0, err + } + return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64) +} diff --git a/error.go b/error.go index 08efd29..470ef5d 100644 --- a/error.go +++ b/error.go @@ -6,11 +6,32 @@ package git import ( "fmt" + "time" ) -func concatenateError(err error, stderr string) error { - if len(stderr) == 0 { - return err - } - return fmt.Errorf("%v - %s", err, stderr) +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) } diff --git a/git.go b/git.go index 28bd263..f8bbbeb 100644 --- a/git.go +++ b/git.go @@ -49,3 +49,7 @@ func Version() (string, error) { gitVersion = fields[2] return gitVersion, nil } + +func init() { + Version() +} diff --git a/repo.go b/repo.go index b3b9bc6..8b79737 100644 --- a/repo.go +++ b/repo.go @@ -5,10 +5,43 @@ package git import ( + "bytes" + "container/list" + "errors" "os" + "path" + "path/filepath" ) -// InitRepository initializes a new Git repository in choice of bare or not. +// Repository represents a Git repository. +type Repository struct { + Path string + + commitCache map[sha1]*Commit +} + +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 +} + +// InitRepository initializes a new Git repository. func InitRepository(repoPath string, bare bool) error { os.MkdirAll(repoPath, os.ModePerm) @@ -19,3 +52,50 @@ func InitRepository(repoPath string, bare bool) error { _, 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}, nil +} + +// Clone clones original repository to target path. +func Clone(from, to string) error { + toDir := path.Dir(to) + os.MkdirAll(toDir, os.ModePerm) + + _, err := NewCommand("clone", from, to).Run() + return err +} + +// Pull pulls changes from remotes. +func Pull(repoPath string, all bool) error { + cmd := NewCommand("pull") + if all { + cmd.AddArguments("--all") + } + _, err := cmd.RunInDir(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 +} + +// Reset resets HEAD to given revision or head of branch. +func Reset(repoPath string, hard bool, revision string) error { + cmd := NewCommand("reset") + if hard { + cmd.AddArguments("--hard") + } + _, err := cmd.AddArguments(revision).RunInDir(repoPath) + return err +} diff --git a/repo_branch.go b/repo_branch.go new file mode 100644 index 0000000..6ca25b5 --- /dev/null +++ b/repo_branch.go @@ -0,0 +1,42 @@ +// 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" +) + +const BRANCH_PREFIX = "refs/heads/" + +// 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 +} + +// IsBranchExist returns true if given branch exists in repository. +func IsBranchExist(repoPath, branch string) bool { + _, err := NewCommand("show-ref", "--verify", BRANCH_PREFIX+branch).RunInDir(repoPath) + return err == nil +} diff --git a/repo_commit.go b/repo_commit.go new file mode 100644 index 0000000..cc76f91 --- /dev/null +++ b/repo_commit.go @@ -0,0 +1,137 @@ +// 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" + "strings" +) + +// getCommitIDOfRef returns the last commit ID string of given reference (branch or tag). +func (repo *Repository) getCommitIDOfRef(refName string) (string, error) { + stdout, err := NewCommand("show-ref", "--verify", refName).RunInDir(repo.Path) + if err != nil { + return "", err + } + return strings.Split(stdout, " ")[0], nil +} + +// GetCommitIDOfBranch returns last commit ID string of given branch. +func (repo *Repository) GetCommitIDOfBranch(branch string) (string, error) { + return repo.getCommitIDOfRef(BRANCH_PREFIX + branch) +} + +// 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": + 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 { + log("Hit cache: %s", id) + if c, ok := repo.commitCache[id]; ok { + return c, nil + } + } else { + repo.commitCache = make(map[sha1]*Commit, 10) + } + + data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path) + if err != nil { + return nil, err + } + + commit, err := parseCommitData(data) + if err != nil { + return nil, err + } + commit.repo = repo + commit.ID = id + + repo.commitCache[id] = commit + return commit, nil +} + +// GetCommit returns commit object of by ID string. +func (repo *Repository) GetCommit(commitID string) (*Commit, error) { + id, err := NewIDFromString(commitID) + if err != nil { + return nil, err + } + + return repo.getCommit(id) +} + +// GetCommitOfBranch returns the last commit of given branch. +func (repo *Repository) GetCommitOfBranch(branch string) (*Commit, error) { + commitID, err := repo.GetCommitIDOfBranch(branch) + if err != nil { + return nil, err + } + return repo.GetCommit(commitID) +} + +// 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 +} diff --git a/repo_hook.go b/repo_hook.go new file mode 100644 index 0000000..4c58fbe --- /dev/null +++ b/repo_hook.go @@ -0,0 +1,23 @@ +// 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 ( + "io/ioutil" + "os" + "path" +) + +const ( + HOOK_PATH_UPDATE = "hooks/update" +) + +// SetUpdateHook writes given content to update hook of the reposiotry. +func SetUpdateHook(repoPath, content string) error { + log("Setting update hook: %s", repoPath) + hookPath := path.Join(repoPath, HOOK_PATH_UPDATE) + os.MkdirAll(path.Dir(hookPath), os.ModePerm) + return ioutil.WriteFile(hookPath, []byte(content), 0777) +} diff --git a/repo_object.go b/repo_object.go new file mode 100644 index 0000000..416ee45 --- /dev/null +++ b/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/repo_tree.go b/repo_tree.go new file mode 100644 index 0000000..2f7094a --- /dev/null +++ b/repo_tree.go @@ -0,0 +1,17 @@ +// 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 +} diff --git a/sha1.go b/sha1.go new file mode 100644 index 0000000..c1518ef --- /dev/null +++ b/sha1.go @@ -0,0 +1,51 @@ +// 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" +) + +type sha1 [20]byte + +// 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) +} + +// NewID creates a new sha1 from a [20]byte array. +func NewID(b []byte) (sha1, error) { + var id sha1 + if len(b) != 20 { + return id, fmt.Errorf("Length must be 20: %v", b) + } + + for i := 0; i < 20; i++ { + id[i] = b[i] + } + return id, nil +} + +// 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/signature.go b/signature.go index 9343f29..95eb1bb 100644 --- a/signature.go +++ b/signature.go @@ -5,6 +5,8 @@ package git import ( + "bytes" + "strconv" "time" ) @@ -14,3 +16,33 @@ type Signature struct { Name string When time.Time } + +// Helper to get a signature from the commit line, which looks like these: +// author Patrick Gundlach 1378823654 +0200 +// author Patrick Gundlach 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/tree.go b/tree.go new file mode 100644 index 0000000..c77adfb --- /dev/null +++ b/tree.go @@ -0,0 +1,151 @@ +// 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, + } +} + +var escapeChar = []byte("\\") + +// UnescapeChars reverses escaped characters. +func UnescapeChars(in []byte) []byte { + if bytes.Index(in, escapeChar) == -1 { + return in + } + + endIdx := len(in) - 1 + isEscape := false + out := make([]byte, 0, endIdx+1) + for i := range in { + if in[i] == '\\' && !isEscape { + isEscape = true + continue + } + isEscape = false + out = append(out, in[i]) + } + 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": + 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(relpath string) (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/tree_blob.go b/tree_blob.go new file mode 100644 index 0000000..039e652 --- /dev/null +++ b/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(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, 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/tree_entry.go b/tree_entry.go new file mode 100644 index 0000000..131a634 --- /dev/null +++ b/tree_entry.go @@ -0,0 +1,45 @@ +// 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 + +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) IsDir() bool { + return te.mode == ENTRY_MODE_TREE +} + +func (te *TreeEntry) Blob() *Blob { + return &Blob{ + repo: te.ptree.repo, + TreeEntry: te, + } +} + +type Entries []*TreeEntry diff --git a/utlis.go b/utlis.go new file mode 100644 index 0000000..6bf109b --- /dev/null +++ b/utlis.go @@ -0,0 +1,45 @@ +// 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" +) + +// 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() +} + +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:]) +}