1
0
Fork 0
forked from forgejo/forgejo

move out git module and #1573 send push hook

This commit is contained in:
Unknwon 2015-12-09 20:46:05 -05:00
parent bd5dc626e8
commit 9a2e43bff2
45 changed files with 185 additions and 2002 deletions

View file

@ -1,26 +0,0 @@
// 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
}

View file

@ -1,162 +0,0 @@
// 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 (
"bufio"
"container/list"
"net/http"
"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
submodules map[string]*SubModule
}
// 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) SearchCommits(keyword string) (*list.List, error) {
return c.repo.searchCommits(c.ID, keyword)
}
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
return c.repo.commitsByRange(c.ID, page)
}
func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
return c.repo.getCommitOfRelPath(c.ID, relPath)
}
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
modules, err := c.GetSubModules()
if err != nil {
return nil, err
}
return modules[entryname], nil
}
func (c *Commit) GetSubModules() (map[string]*SubModule, error) {
if c.submodules != nil {
return c.submodules, 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.submodules = make(map[string]*SubModule)
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.submodules[path] = &SubModule{path, strings.TrimSpace(fields[1])}
ismodule = false
}
}
}
return c.submodules, nil
}
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)
if n > 0 {
buf = buf[:n]
}
_, isImage := isImageFile(buf)
return isImage
}

View file

@ -1,36 +0,0 @@
// 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
}

View file

@ -1,22 +0,0 @@
// 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"
)
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)
}

View file

@ -1,125 +0,0 @@
// 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"
"io/ioutil"
"os"
"path"
"strings"
"github.com/Unknwon/com"
)
// hookNames is a list of Git hooks' name that are supported.
var hookNames = []string{
"applypatch-msg",
"pre-applypatch",
"post-applypatch",
"pre-commit",
"prepare-commit-msg",
"commit-msg",
"post-commit",
"pre-rebase",
"post-checkout",
"post-merge",
"pre-push",
"pre-receive",
// "update",
"post-receive",
"post-update",
"push-to-checkout",
"pre-auto-gc",
"post-rewrite",
}
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, "hooks", name),
}
if isFile(h.path) {
data, err := ioutil.ReadFile(h.path)
if err != nil {
return nil, err
}
h.IsActive = true
h.Content = string(data)
} else if isFile(h.path + ".sample") {
data, err := ioutil.ReadFile(h.path + ".sample")
if err != nil {
return nil, err
}
h.Sample = string(data)
}
return h, nil
}
func (h *Hook) Name() string {
return h.name
}
// Update updates hook settings.
func (h *Hook) Update() error {
if len(strings.TrimSpace(h.Content)) == 0 {
if com.IsExist(h.path) {
return os.Remove(h.path)
}
return nil
}
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
}
func (repo *Repository) GetHook(name string) (*Hook, error) {
return GetHook(repo.Path, name)
}
func (repo *Repository) Hooks() ([]*Hook, error) {
return ListHooks(repo.Path)
}

View file

@ -1,30 +0,0 @@
// 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"
"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
} else if !isDir(repoPath) {
return nil, errors.New("no such file or directory")
}
return &Repository{Path: repoPath}, nil
}

View file

@ -1,50 +0,0 @@
// 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 (
"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, concatenateError(err, 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
}
// SetDefaultBranch sets default branch of repository.
func (repo *Repository) SetDefaultBranch(branchName string) error {
if gitVer.LessThan(MustParseVersion("1.7.10")) {
return ErrUnsupportedVersion{"1.7.10"}
}
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "symbolic-ref", "HEAD", "refs/heads/"+branchName)
if err != nil {
return concatenateError(err, stderr)
}
return nil
}

View file

@ -1,341 +0,0 @@
// 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"
"fmt"
"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)
}
func (repo *Repository) GetCommitIdOfTag(tagName string) (string, error) {
return repo.getCommitIdOfRef("refs/tags/" + tagName)
}
func (repo *Repository) GetCommitOfTag(tagName string) (*Commit, error) {
tag, err := repo.GetTag(tagName)
if err != nil {
return nil, err
}
return tag.Commit()
}
// 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, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
if err != nil {
return nil, concatenateError(err, string(stderr))
}
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) {
if gitVer.LessThan(MustParseVersion("1.8.0")) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
"--pretty=format:''", id.String())
if err != nil {
return 0, errors.New(string(stderr))
}
return len(bytes.Split(stdout, []byte("\n"))), nil
}
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()
}
func (repo *Repository) CommitsCount(commitId string) (int, error) {
id, err := NewIdFromString(commitId)
if err != nil {
return 0, err
}
return repo.commitsCount(id)
}
func (repo *Repository) commitsCountBetween(start, end sha1) (int, error) {
if gitVer.LessThan(MustParseVersion("1.8.0")) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log",
"--pretty=format:''", start.String()+"..."+end.String())
if err != nil {
return 0, errors.New(string(stderr))
}
return len(bytes.Split(stdout, []byte("\n"))), nil
}
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
start.String()+"..."+end.String())
if err != nil {
return 0, errors.New(stderr)
}
return com.StrTo(strings.TrimSpace(stdout)).Int()
}
func (repo *Repository) CommitsCountBetween(startCommitID, endCommitID string) (int, error) {
start, err := NewIdFromString(startCommitID)
if err != nil {
return 0, err
}
end, err := NewIdFromString(endCommitID)
if err != nil {
return 0, err
}
return repo.commitsCountBetween(start, end)
}
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "diff", "--name-only",
startCommitID+"..."+endCommitID)
if err != nil {
return 0, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
}
return len(strings.Split(stdout, "\n")) - 1, nil
}
// 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 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)
}
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) FileCommitsCount(branch, file string) (int, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count",
branch, "--", file)
if err != nil {
return 0, errors.New(stderr)
}
return com.StrTo(strings.TrimSpace(stdout)).Int()
}
func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", branch,
"--skip="+com.ToStr((page-1)*50), "--max-count=50", prettyLogFormat, "--", file)
if err != nil {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
l := list.New()
lock := new(sync.Mutex)
return l, repo.commitsBefore(lock, l, nil, id, 0)
}
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(), "-100",
"-i", "--grep="+keyword, prettyLogFormat)
if err != nil {
return nil, err
} else if len(stderr) > 0 {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
var CommitsRangeSize = 50
func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "log", id.String(),
"--skip="+com.ToStr((page-1)*CommitsRangeSize), "--max-count="+com.ToStr(CommitsRangeSize), prettyLogFormat)
if err != nil {
return nil, errors.New(string(stderr))
}
return parsePrettyFormatLog(repo, stdout)
}
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)
}

View file

@ -1,14 +0,0 @@
// 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"
)

View file

@ -1,104 +0,0 @@
// 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"
"strings"
"time"
"github.com/Unknwon/com"
)
type PullRequestInfo struct {
MergeBase string
Commits *list.List
// Diff *Diff
NumFiles int
}
// GetMergeBase checks and returns merge base of two branches.
func (repo *Repository) GetMergeBase(remoteBranch, headBranch string) (string, error) {
// Get merge base commit.
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
if err != nil {
return "", fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
}
return strings.TrimSpace(stdout), nil
}
// AddRemote adds a remote to repository.
func (repo *Repository) AddRemote(name, path string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", name, path)
if err != nil {
return fmt.Errorf("add remote(%s - %s): %v", name, path, concatenateError(err, stderr))
}
return nil
}
// RemoveRemote removes a remote from repository.
func (repo *Repository) RemoveRemote(name string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "remove", name)
if err != nil {
return fmt.Errorf("remove remote(%s): %v", name, concatenateError(err, stderr))
}
return 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) {
// Add a temporary remote.
tmpRemote := com.ToStr(time.Now().UnixNano())
if err = repo.AddRemote(tmpRemote, basePath); err != nil {
return nil, fmt.Errorf("AddRemote: %v", err)
}
defer func() {
repo.RemoveRemote(tmpRemote)
}()
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
prInfo := new(PullRequestInfo)
prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
if err != nil {
return nil, fmt.Errorf("GetMergeBase: %v", err)
}
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat)
if err != nil {
return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr))
}
prInfo.Commits, err = parsePrettyFormatLog(repo, []byte(stdout))
if err != nil {
return nil, fmt.Errorf("parsePrettyFormatLog: %v", err)
}
// Count number of changed files.
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "diff", "--name-only", remoteBranch+"..."+headBranch)
if err != nil {
return nil, fmt.Errorf("list changed files: %v", concatenateError(err, stderr))
}
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
return prInfo, nil
}
// GetPatch generates and returns patch data between given branches.
func (repo *Repository) GetPatch(mergeBase, headBranch string) ([]byte, error) {
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", "--binary", mergeBase, headBranch)
if err != nil {
return nil, concatenateError(err, string(stderr))
}
return stdout, nil
}
// Merge merges pull request from head repository and branch.
func (repo *Repository) Merge(headRepoPath string, baseBranch, headBranch string) error {
return nil
}

View file

@ -1,117 +0,0 @@
// 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)
}
func (repo *Repository) getTagsReversed() ([]string, error) {
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l", "--sort=-v:refname")
if err != nil {
return nil, concatenateError(err, stderr)
}
tags := strings.Split(stdout, "\n")
return tags[:len(tags)-1], nil
}
// GetTags returns all tags of given repository.
func (repo *Repository) GetTags() ([]string, error) {
if gitVer.AtLeast(MustParseVersion("2.0.0")) {
return repo.getTagsReversed()
}
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l")
if err != nil {
return nil, concatenateError(err, stderr)
}
tags := strings.Split(stdout, "\n")
return tags[:len(tags)-1], nil
}
func (repo *Repository) CreateTag(tagName, idStr string) error {
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", tagName, idStr)
if err != nil {
return errors.New(stderr)
}
return 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)
}
tp = strings.TrimSpace(tp)
// 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
}

View file

@ -1,32 +0,0 @@
// 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
}

View file

@ -1,87 +0,0 @@
// 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 does 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)
}

View file

@ -1,51 +0,0 @@
// 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 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, err := strconv.ParseInt(timestring, 10, 64)
if err != nil {
return nil, err
}
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
}

View file

@ -1,20 +0,0 @@
// 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 (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_newSignatureFromCommitline(t *testing.T) {
Convey("Parse signature from commit line", t, func() {
line := "Intern <intern@macbook-intern.(none)> 1445412825 +0200"
sig, err := newSignatureFromCommitline([]byte(line))
So(err, ShouldBeNil)
So(sig, ShouldNotBeNil)
})
}

View file

@ -1,70 +0,0 @@
// 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 (
"strings"
"github.com/gogits/gogs/modules/setting"
)
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() 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
}
// sysuser@xxx:user/repo
i := strings.Index(url, "@")
j := strings.LastIndex(url, ":")
if i > -1 && j > -1 {
// fix problem with reverse proxy works only with local server
if strings.Contains(setting.AppUrl, url[i+1:j]) {
return setting.AppUrl + 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
}

View file

@ -1,67 +0,0 @@
// 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
}

View file

@ -1,157 +0,0 @@
// 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
}
var escapeChar = []byte("\\")
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
}
// 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
step = 8
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')
// 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
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, stderr, err := com.ExecCmdDirBytes(t.repo.Path,
"git", "ls-tree", t.ID.String())
if err != nil {
if strings.Contains(err.Error(), "exit status 128") {
return nil, errors.New(strings.TrimSpace(string(stderr)))
}
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
}

View file

@ -1,59 +0,0 @@
// 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)
}
func (t *Tree) GetBlobByPath(rpath string) (*Blob, error) {
entry, err := t.GetTreeEntryByPath(rpath)
if err != nil {
return nil, err
}
if !entry.IsDir() {
return entry.Blob(), nil
}
return nil, ErrNotExist
}

View file

@ -1,113 +0,0 @@
// 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) IsSubModule() bool {
return te.mode == ModeCommit
}
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() || t1.IsSubModule()) && !t2.IsDir() && !t2.IsSubModule()
},
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)
}

View file

@ -1,82 +0,0 @@
// 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"
"fmt"
"os"
"path/filepath"
"strings"
)
const prettyLogFormat = `--pretty=format:%H`
func parsePrettyFormatLog(repo *Repository, logByts []byte) (*list.List, error) {
l := list.New()
if len(logByts) == 0 {
return l, nil
}
parts := bytes.Split(logByts, []byte{'\n'})
for _, commitId := range parts {
commit, err := repo.GetCommit(string(commitId))
if err != nil {
return nil, err
}
l.PushBack(commit)
}
return l, nil
}
func RefEndName(refStr string) string {
if strings.HasPrefix(refStr, "refs/heads/") {
// trim the "refs/heads/"
return refStr[len("refs/heads/"):]
}
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:])
}
// 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)
}

View file

@ -1,104 +0,0 @@
// 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"
"fmt"
"strings"
"github.com/Unknwon/com"
)
var (
// Cached Git version.
gitVer *Version
)
// Version represents version of Git.
type Version struct {
Major, Minor, Patch int
}
func ParseVersion(verStr string) (*Version, error) {
infos := strings.Split(verStr, ".")
if len(infos) < 3 {
return nil, errors.New("incorrect version input")
}
v := &Version{}
for i, s := range infos {
switch i {
case 0:
v.Major, _ = com.StrTo(s).Int()
case 1:
v.Minor, _ = com.StrTo(s).Int()
case 2:
v.Patch, _ = com.StrTo(strings.TrimSpace(s)).Int()
}
}
return v, nil
}
func MustParseVersion(verStr string) *Version {
v, _ := ParseVersion(verStr)
return v
}
// Compare compares two versions,
// it returns 1 if original is greater, -1 if original is smaller, 0 if equal.
func (v *Version) Compare(that *Version) int {
if v.Major > that.Major {
return 1
} else if v.Major < that.Major {
return -1
}
if v.Minor > that.Minor {
return 1
} else if v.Minor < that.Minor {
return -1
}
if v.Patch > that.Patch {
return 1
} else if v.Patch < that.Patch {
return -1
}
return 0
}
func (v *Version) LessThan(that *Version) bool {
return v.Compare(that) < 0
}
func (v *Version) AtLeast(that *Version) bool {
return v.Compare(that) >= 0
}
func (v *Version) String() string {
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
// GetVersion returns current Git version installed.
func GetVersion() (*Version, error) {
if gitVer != nil {
return gitVer, nil
}
stdout, stderr, err := com.ExecCmd("git", "version")
if err != nil {
return nil, errors.New(stderr)
}
infos := strings.Split(stdout, " ")
if len(infos) < 3 {
return nil, errors.New("not enough output")
}
gitVer, err = ParseVersion(infos[2])
return gitVer, err
}