зеркало из https://github.com/go-gitea/git.git
complete migration from gogs module
This commit is contained in:
Родитель
d14b1e8b81
Коммит
4cc1cbbfea
151
commit.go
151
commit.go
|
@ -5,7 +5,10 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -20,8 +23,18 @@ type Commit struct {
|
|||
Committer *Signature
|
||||
CommitMessage string
|
||||
|
||||
parents []sha1 // SHA1 strings
|
||||
// submodules map[string]*SubModule
|
||||
parents []sha1 // SHA1 strings
|
||||
submodules map[string]*SubModule
|
||||
}
|
||||
|
||||
// Message returns the commit message. Same as retrieving CommitMessage directly.
|
||||
func (c *Commit) Message() string {
|
||||
return c.CommitMessage
|
||||
}
|
||||
|
||||
// Summary returns first line of commit message.
|
||||
func (c *Commit) Summary() string {
|
||||
return strings.Split(c.CommitMessage, "\n")[0]
|
||||
}
|
||||
|
||||
// ParentID returns oid of n-th parent (0-based index).
|
||||
|
@ -47,14 +60,41 @@ func (c *Commit) Parent(n int) (*Commit, error) {
|
|||
}
|
||||
|
||||
// ParentCount returns number of parents of the commit.
|
||||
// 0 if this is the root commit, otherwise 1,2, etc.
|
||||
// 0 if this is the root commit, otherwise 1,2, etc.
|
||||
func (c *Commit) ParentCount() int {
|
||||
return len(c.parents)
|
||||
}
|
||||
|
||||
// GetCommitOfRelPath return the commit of relative path object.
|
||||
func (c *Commit) GetCommitOfRelPath(relpath string) (*Commit, error) {
|
||||
return c.repo.getCommitOfRelPath(c.ID, relpath)
|
||||
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
|
||||
}
|
||||
|
||||
// GetCommitByPath return the commit of relative path object.
|
||||
func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) {
|
||||
return c.repo.getCommitByPathWithID(c.ID, relpath)
|
||||
}
|
||||
|
||||
// AddAllChanges marks local changes to be ready for commit.
|
||||
|
@ -82,19 +122,102 @@ func CommitChanges(repoPath, message string, author *Signature) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// CommitsCount returns number of total commits of until given revision.
|
||||
func CommitsCount(repoPath, revision string) (int64, error) {
|
||||
func commitsCount(repoPath, revision, relpath string) (int64, error) {
|
||||
var cmd *Command
|
||||
isFallback := false
|
||||
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
|
||||
isFallback = true
|
||||
cmd = NewCommand("log", "--pretty=format:''")
|
||||
} else {
|
||||
cmd = NewCommand("rev-list", "--count")
|
||||
}
|
||||
cmd.AddArguments(revision)
|
||||
if len(relpath) > 0 {
|
||||
cmd.AddArguments("--", relpath)
|
||||
}
|
||||
|
||||
stdout, err := NewCommand("rev-list", "--count", revision).RunInDir(repoPath)
|
||||
stdout, err := cmd.RunInDir(repoPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isFallback {
|
||||
return int64(len(strings.Split(stdout, "\n"))), nil
|
||||
}
|
||||
return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
||||
}
|
||||
|
||||
// CommitsCount returns number of total commits of until given revision.
|
||||
func CommitsCount(repoPath, revision string) (int64, error) {
|
||||
return commitsCount(repoPath, revision, "")
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsCount() (int64, error) {
|
||||
return CommitsCount(c.repo.Path, c.ID.String())
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
|
||||
return c.repo.commitsByRange(c.ID, page)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBefore() (*list.List, error) {
|
||||
return c.repo.getCommitsBefore(c.ID)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBeforeUntil(commitID string) (*list.List, error) {
|
||||
endCommit, err := c.repo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.repo.CommitsBetween(c, endCommit)
|
||||
}
|
||||
|
||||
func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
|
||||
return c.repo.searchCommits(c.ID, keyword)
|
||||
}
|
||||
|
||||
func (c *Commit) 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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// 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 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)
|
||||
}
|
||||
|
||||
_, err := NewCommand("archive", "--format="+format, "-o", path, c.ID.String()).RunInDir(c.repo.Path)
|
||||
return err
|
||||
}
|
13
error.go
13
error.go
|
@ -35,3 +35,16 @@ func IsErrNotExist(err error) bool {
|
|||
func (err ErrNotExist) Error() string {
|
||||
return fmt.Sprintf("object does not exist [id: %s, rel_path: %s]", err.ID, err.RelPath)
|
||||
}
|
||||
|
||||
type ErrUnsupportedVersion struct {
|
||||
Required string
|
||||
}
|
||||
|
||||
func IsErrUnsupportedVersion(err error) bool {
|
||||
_, ok := err.(ErrUnsupportedVersion)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUnsupportedVersion) Error() string {
|
||||
return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
|
||||
}
|
||||
|
|
10
git.go
10
git.go
|
@ -10,6 +10,12 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const _VERSION = "0.1.0"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
}
|
||||
|
||||
var (
|
||||
// Debug enables verbose logging on everything.
|
||||
// This should be false in case Gogs starts in SSH mode.
|
||||
|
@ -33,7 +39,7 @@ func log(format string, args ...interface{}) {
|
|||
var gitVersion string
|
||||
|
||||
// Version returns current Git version from shell.
|
||||
func Version() (string, error) {
|
||||
func BinVersion() (string, error) {
|
||||
if len(gitVersion) > 0 {
|
||||
return gitVersion, nil
|
||||
}
|
||||
|
@ -53,7 +59,7 @@ func Version() (string, error) {
|
|||
}
|
||||
|
||||
func init() {
|
||||
Version()
|
||||
BinVersion()
|
||||
}
|
||||
|
||||
// Fsck verifies the connectivity and validity of the objects in the database
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
1
repo.go
1
repo.go
|
@ -19,6 +19,7 @@ type Repository struct {
|
|||
Path string
|
||||
|
||||
commitCache map[sha1]*Commit
|
||||
tagCache map[sha1]*Tag
|
||||
}
|
||||
|
||||
const _PRETTY_LOG_FORMAT = `--pretty=format:%H`
|
||||
|
|
|
@ -7,10 +7,27 @@ package git
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mcuadros/go-version"
|
||||
)
|
||||
|
||||
const BRANCH_PREFIX = "refs/heads/"
|
||||
|
||||
// IsReferenceExist returns true if given reference exists in the repository.
|
||||
func IsReferenceExist(repoPath, name string) bool {
|
||||
_, err := NewCommand("show-ref", "--verify", name).RunInDir(repoPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsBranchExist returns true if given branch exists in the repository.
|
||||
func IsBranchExist(repoPath, name string) bool {
|
||||
return IsReferenceExist(repoPath, BRANCH_PREFIX+name)
|
||||
}
|
||||
|
||||
func (repo *Repository) IsBranchExist(name string) bool {
|
||||
return IsBranchExist(repo.Path, name)
|
||||
}
|
||||
|
||||
// Branch represents a Git branch.
|
||||
type Branch struct {
|
||||
Name string
|
||||
|
@ -35,8 +52,49 @@ func (repo *Repository) GetHEADBranch() (*Branch, error) {
|
|||
}, 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
|
||||
// SetDefaultBranch sets default branch of repository.
|
||||
func (repo *Repository) SetDefaultBranch(name string) error {
|
||||
if version.Compare(gitVersion, "1.7.10", "<") {
|
||||
return ErrUnsupportedVersion{"1.7.10"}
|
||||
}
|
||||
|
||||
_, err := NewCommand("symbolic-ref", "HEAD", "refs/heads/"+name).RunInDir(repo.Path)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetBranches returns all branches of the repository.
|
||||
func (repo *Repository) GetBranches() ([]string, error) {
|
||||
stdout, err := NewCommand("show-ref", "--heads").RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
infos := strings.Split(stdout, "\n")
|
||||
branches := make([]string, len(infos)-1)
|
||||
for i, info := range infos[:len(infos)-1] {
|
||||
fields := strings.Fields(info)
|
||||
if len(fields) != 2 {
|
||||
continue // NOTE: I should believe git will not give me wrong string.
|
||||
}
|
||||
branches[i] = strings.TrimPrefix(fields[1], "refs/heads/")
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
// AddRemote adds a new remote to repository.
|
||||
func (repo *Repository) AddRemote(name, url string, fetch bool) error {
|
||||
cmd := NewCommand("remote", "add")
|
||||
if fetch {
|
||||
cmd.AddArguments("-f")
|
||||
}
|
||||
cmd.AddArguments(name, url)
|
||||
|
||||
_, err := cmd.RunInDir(repo.Path)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveRemote removes a remote from repository.
|
||||
func (repo *Repository) RemoveRemote(name string) error {
|
||||
_, err := NewCommand("remote", "remove", name).RunInDir(repo.Path)
|
||||
return err
|
||||
}
|
||||
|
|
175
repo_commit.go
175
repo_commit.go
|
@ -6,21 +6,29 @@ package git
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"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)
|
||||
// getRefCommitID returns the last commit ID string of given reference (branch or tag).
|
||||
func (repo *Repository) getRefCommitID(name string) (string, error) {
|
||||
stdout, err := NewCommand("show-ref", "--verify", name).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
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)
|
||||
// GetBranchCommitID returns last commit ID string of given branch.
|
||||
func (repo *Repository) GetBranchCommitID(name string) (string, error) {
|
||||
return repo.getRefCommitID(BRANCH_PREFIX + name)
|
||||
}
|
||||
|
||||
// GetTagCommitID returns last commit ID string of given tag.
|
||||
func (repo *Repository) GetTagCommitID(name string) (string, error) {
|
||||
return repo.getRefCommitID(TAG_PREFIX + name)
|
||||
}
|
||||
|
||||
// parseCommitData parses commit information from the (uncompressed) raw
|
||||
|
@ -113,16 +121,24 @@ func (repo *Repository) GetCommit(commitID string) (*Commit, error) {
|
|||
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)
|
||||
// GetBranchCommit returns the last commit of given branch.
|
||||
func (repo *Repository) GetBranchCommit(name string) (*Commit, error) {
|
||||
commitID, err := repo.GetBranchCommitID(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.GetCommit(commitID)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitOfRelPath(id sha1, relpath string) (*Commit, error) {
|
||||
func (repo *Repository) GetTagCommit(name string) (*Commit, error) {
|
||||
commitID, err := repo.GetTagCommitID(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.GetCommit(commitID)
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitByPathWithID(id sha1, relpath string) (*Commit, error) {
|
||||
stdout, err := NewCommand("log", "-1", _PRETTY_LOG_FORMAT, id.String(), "--", relpath).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -149,3 +165,142 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
|
|||
}
|
||||
return commits.Front().Value.(*Commit), nil
|
||||
}
|
||||
|
||||
var CommitsRangeSize = 50
|
||||
|
||||
func (repo *Repository) commitsByRange(id sha1, page int) (*list.List, error) {
|
||||
stdout, err := NewCommand("log", id.String(), "--skip="+strconv.Itoa((page-1)*CommitsRangeSize),
|
||||
"--max-count="+strconv.Itoa(CommitsRangeSize), _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.parsePrettyFormatLogToList(stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
|
||||
stdout, err := NewCommand("log", id.String(), "-100", "-i", "--grep="+keyword, _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.parsePrettyFormatLogToList(stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) FileCommitsCount(revision, file string) (int64, error) {
|
||||
return commitsCount(repo.Path, revision, file)
|
||||
}
|
||||
|
||||
func (repo *Repository) CommitsByFileAndRange(revision, file string, page int) (*list.List, error) {
|
||||
stdout, err := NewCommand("log", revision, "--skip="+strconv.Itoa((page-1)*50),
|
||||
"--max-count="+strconv.Itoa(CommitsRangeSize), _PRETTY_LOG_FORMAT, "--", file).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.parsePrettyFormatLogToList(stdout)
|
||||
}
|
||||
|
||||
func (repo *Repository) FilesCountBetween(startCommitID, endCommitID string) (int, error) {
|
||||
stdout, err := NewCommand("diff", "--name-only", startCommitID+"..."+endCommitID).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(strings.Split(stdout, "\n")) - 1, nil
|
||||
}
|
||||
|
||||
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) CommitsBetweenIDs(last, before string) (*list.List, error) {
|
||||
lastCommit, err := repo.GetCommit(last)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
beforeCommit, err := repo.GetCommit(before)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.CommitsBetween(lastCommit, beforeCommit)
|
||||
}
|
||||
|
||||
func (repo *Repository) CommitsCountBetween(start, end string) (int64, error) {
|
||||
return commitsCount(repo.Path, start+"..."+end, "")
|
||||
}
|
||||
|
||||
func (repo *Repository) commitsBefore(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(l, pr, id, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
|
||||
l := list.New()
|
||||
return l, repo.commitsBefore(l, nil, id, 0)
|
||||
}
|
||||
|
|
22
repo_hook.go
22
repo_hook.go
|
@ -4,20 +4,10 @@
|
|||
|
||||
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)
|
||||
func (repo *Repository) GetHook(name string) (*Hook, error) {
|
||||
return GetHook(repo.Path, name)
|
||||
}
|
||||
|
||||
func (repo *Repository) Hooks() ([]*Hook, error) {
|
||||
return ListHooks(repo.Path)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PullRequestInfo represents needed information for a pull request.
|
||||
type PullRequestInfo struct {
|
||||
MergeBase string
|
||||
Commits *list.List
|
||||
NumFiles int
|
||||
}
|
||||
|
||||
// GetMergeBase checks and returns merge base of two branches.
|
||||
func (repo *Repository) GetMergeBase(base, head string) (string, error) {
|
||||
stdout, err := NewCommand("merge-base", base, head).RunInDir(repo.Path)
|
||||
return strings.TrimSpace(stdout), err
|
||||
}
|
||||
|
||||
// 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 := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
if err = repo.AddRemote(tmpRemote, basePath, true); 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)
|
||||
}
|
||||
|
||||
logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, _PRETTY_LOG_FORMAT).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
|
||||
}
|
||||
|
||||
// Count number of changed files.
|
||||
stdout, err := NewCommand("diff", "--name-only", remoteBranch+"..."+headBranch).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prInfo.NumFiles = len(strings.Split(stdout, "\n")) - 1
|
||||
|
||||
return prInfo, nil
|
||||
}
|
||||
|
||||
// GetPatch generates and returns patch data between given revisions.
|
||||
func (repo *Repository) GetPatch(base, head string) ([]byte, error) {
|
||||
return NewCommand("diff", "-p", "--binary", base, head).RunInDirBytes(repo.Path)
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mcuadros/go-version"
|
||||
)
|
||||
|
||||
const TAG_PREFIX = "refs/tags/"
|
||||
|
||||
// IsTagExist returns true if given tag exists in the repository.
|
||||
func IsTagExist(repoPath, name string) bool {
|
||||
return IsReferenceExist(repoPath, TAG_PREFIX+name)
|
||||
}
|
||||
|
||||
func (repo *Repository) IsTagExist(name string) bool {
|
||||
return IsTagExist(repo.Path, name)
|
||||
}
|
||||
|
||||
func (repo *Repository) CreateTag(name, revision string) error {
|
||||
_, err := NewCommand("tag", name, revision).RunInDir(repo.Path)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *Repository) getTag(id sha1) (*Tag, error) {
|
||||
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, err := NewCommand("cat-file", "-t", id.String()).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tp = strings.TrimSpace(tp)
|
||||
|
||||
// Tag is a commit.
|
||||
if ObjectType(tp) == OBJECT_COMMIT {
|
||||
tag := &Tag{
|
||||
ID: id,
|
||||
Object: id,
|
||||
Type: string(OBJECT_COMMIT),
|
||||
repo: repo,
|
||||
}
|
||||
repo.tagCache[id] = tag
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// Tag with message.
|
||||
data, err := NewCommand("cat-file", "-p", id.String()).RunInDirBytes(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := parseTagData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag.ID = id
|
||||
tag.repo = repo
|
||||
|
||||
repo.tagCache[id] = tag
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// GetTag returns a Git tag by given name.
|
||||
func (repo *Repository) GetTag(name string) (*Tag, error) {
|
||||
stdout, err := NewCommand("show-ref", "--tags", name).RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := NewIDFromString(strings.Split(stdout, " ")[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := repo.getTag(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Name = name
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
// GetTags returns all tags of the repository.
|
||||
func (repo *Repository) GetTags() ([]string, error) {
|
||||
cmd := NewCommand("tag", "-l")
|
||||
if version.Compare(gitVersion, "2.0.0", ">=") {
|
||||
cmd.AddArguments("--sort=-v:refname")
|
||||
}
|
||||
|
||||
stdout, err := cmd.RunInDir(repo.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags := strings.Split(stdout, "\n")
|
||||
return tags[:len(tags)-1], nil
|
||||
}
|
|
@ -15,3 +15,12 @@ func (repo *Repository) getTree(id sha1) (*Tree, error) {
|
|||
|
||||
return NewTree(repo, id), nil
|
||||
}
|
||||
|
||||
// Find the tree object in the repository.
|
||||
func (repo *Repository) GetTree(idStr string) (*Tree, error) {
|
||||
id, err := NewIDFromString(idStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.getTree(id)
|
||||
}
|
||||
|
|
30
sha1.go
30
sha1.go
|
@ -12,6 +12,36 @@ import (
|
|||
|
||||
type sha1 [20]byte
|
||||
|
||||
// Equal returns true if s has the same sha1 as caller.
|
||||
// Support 40-length-string, []byte, sha1.
|
||||
func (id sha1) Equal(s2 interface{}) bool {
|
||||
switch v := s2.(type) {
|
||||
case string:
|
||||
if len(v) != 40 {
|
||||
return false
|
||||
}
|
||||
return v == id.String()
|
||||
case []byte:
|
||||
if len(v) != 20 {
|
||||
return false
|
||||
}
|
||||
for i, v := range v {
|
||||
if id[i] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
case sha1:
|
||||
for i, v := range v {
|
||||
if id[i] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns string (hex) representation of the Oid.
|
||||
func (s sha1) String() string {
|
||||
result := make([]byte, 0, 40)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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,
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove import of setting
|
||||
// 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
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package git
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Tag represents a Git tag.
|
||||
type Tag struct {
|
||||
Name string
|
||||
ID sha1
|
||||
repo *Repository
|
||||
Object sha1 // The id of this commit object
|
||||
Type string
|
||||
Tagger *Signature
|
||||
Message string
|
||||
}
|
||||
|
||||
func (tag *Tag) Commit() (*Commit, error) {
|
||||
return tag.repo.getCommit(tag.Object)
|
||||
}
|
||||
|
||||
// Parse commit information from the (uncompressed) raw
|
||||
// data from the commit object.
|
||||
// \n\n separate headers from message
|
||||
func parseTagData(data []byte) (*Tag, error) {
|
||||
tag := new(Tag)
|
||||
// we now have the contents of the commit object. Let's investigate...
|
||||
nextline := 0
|
||||
l:
|
||||
for {
|
||||
eol := bytes.IndexByte(data[nextline:], '\n')
|
||||
switch {
|
||||
case eol > 0:
|
||||
line := data[nextline : nextline+eol]
|
||||
spacepos := bytes.IndexByte(line, ' ')
|
||||
reftype := line[:spacepos]
|
||||
switch string(reftype) {
|
||||
case "object":
|
||||
id, err := NewIDFromString(string(line[spacepos+1:]))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Object = id
|
||||
case "type":
|
||||
// A commit can have one or more parents
|
||||
tag.Type = string(line[spacepos+1:])
|
||||
case "tagger":
|
||||
sig, err := newSignatureFromCommitline(line[spacepos+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tag.Tagger = sig
|
||||
}
|
||||
nextline += eol + 1
|
||||
case eol == 0:
|
||||
tag.Message = string(data[nextline+1:])
|
||||
break l
|
||||
default:
|
||||
break l
|
||||
}
|
||||
}
|
||||
return tag, nil
|
||||
}
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
package git
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type EntryMode int
|
||||
|
||||
// There are only a few file modes in Git. They look like unix file modes, but they can only be
|
||||
|
@ -35,6 +41,27 @@ func (te *TreeEntry) Name() string {
|
|||
return te.name
|
||||
}
|
||||
|
||||
func (te *TreeEntry) Size() int64 {
|
||||
if te.IsDir() {
|
||||
return 0
|
||||
} else if te.sized {
|
||||
return te.size
|
||||
}
|
||||
|
||||
stdout, err := NewCommand("cat-file", "-s", te.ID.String()).RunInDir(te.ptree.repo.Path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
te.sized = true
|
||||
te.size, _ = strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
|
||||
return te.size
|
||||
}
|
||||
|
||||
func (te *TreeEntry) IsSubModule() bool {
|
||||
return te.mode == ENTRY_MODE_COMMIT
|
||||
}
|
||||
|
||||
func (te *TreeEntry) IsDir() bool {
|
||||
return te.mode == ENTRY_MODE_TREE
|
||||
}
|
||||
|
@ -47,3 +74,33 @@ func (te *TreeEntry) Blob() *Blob {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
21
utlis.go
21
utlis.go
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isDir returns true if given path is a directory,
|
||||
|
@ -30,6 +31,13 @@ func isFile(filePath string) bool {
|
|||
return !f.IsDir()
|
||||
}
|
||||
|
||||
// isExist checks whether a file or directory exists.
|
||||
// It returns false when the file or directory does not exist.
|
||||
func isExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
}
|
||||
|
||||
func concatenateError(err error, stderr string) error {
|
||||
if len(stderr) == 0 {
|
||||
return err
|
||||
|
@ -43,3 +51,16 @@ func concatenateError(err error, stderr string) error {
|
|||
func filepathFromSHA1(rootdir, sha1 string) string {
|
||||
return filepath.Join(rootdir, "objects", sha1[:2], sha1[2:])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче