aboutsummaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/access.go28
-rw-r--r--models/action.go85
-rw-r--r--models/error.go89
-rw-r--r--models/git_diff.go50
-rw-r--r--models/issue.go265
-rw-r--r--models/migrations/migrations.go100
-rw-r--r--models/models.go5
-rw-r--r--models/publickey.go65
-rw-r--r--models/pull.go558
-rw-r--r--models/release.go36
-rw-r--r--models/repo.go161
-rw-r--r--models/update.go65
-rw-r--r--models/user.go49
-rw-r--r--models/webhook.go82
14 files changed, 1136 insertions, 502 deletions
diff --git a/models/access.go b/models/access.go
index fe8bf2c1..8a7729b7 100644
--- a/models/access.go
+++ b/models/access.go
@@ -67,9 +67,8 @@ func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, u, repo, testMode)
}
-// GetAccessibleRepositories finds all repositories where a user has access to,
-// besides he/she owns.
-func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
+// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
+func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
accesses := make([]*Access, 0, 10)
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
return nil, err
@@ -80,7 +79,7 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
repo, err := GetRepositoryByID(access.RepoID)
if err != nil {
if IsErrRepoNotExist(err) {
- log.Error(4, "%v", err)
+ log.Error(4, "GetRepositoryByID: %v", err)
continue
}
return nil, err
@@ -92,11 +91,28 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
}
repos[repo] = access.Mode
}
-
- // FIXME: should we generate an ordered list here? Random looks weird.
return repos, nil
}
+// GetAccessibleRepositories finds all repositories where a user has access but does not own.
+func (u *User) GetAccessibleRepositories() ([]*Repository, error) {
+ accesses := make([]*Access, 0, 10)
+ if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
+ return nil, err
+ }
+
+ if len(accesses) == 0 {
+ return []*Repository{}, nil
+ }
+
+ repoIDs := make([]int64, 0, len(accesses))
+ for _, access := range accesses {
+ repoIDs = append(repoIDs, access.RepoID)
+ }
+ repos := make([]*Repository, 0, len(repoIDs))
+ return repos, x.Where("owner_id != ?", u.Id).In("id", repoIDs).Desc("updated").Find(&repos)
+}
+
func maxAccessMode(modes ...AccessMode) AccessMode {
max := ACCESS_MODE_NONE
for _, mode := range modes {
diff --git a/models/action.go b/models/action.go
index e38cf593..8dd80074 100644
--- a/models/action.go
+++ b/models/action.go
@@ -14,6 +14,7 @@ import (
"time"
"unicode"
+ "github.com/Unknwon/com"
"github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
@@ -136,6 +137,26 @@ func (a Action) GetIssueInfos() []string {
return strings.SplitN(a.Content, "|", 2)
}
+func (a Action) GetIssueTitle() string {
+ index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
+ issue, err := GetIssueByIndex(a.RepoID, index)
+ if err != nil {
+ log.Error(4, "GetIssueByIndex: %v", err)
+ return "500 when get issue"
+ }
+ return issue.Name
+}
+
+func (a Action) GetIssueContent() string {
+ index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
+ issue, err := GetIssueByIndex(a.RepoID, index)
+ if err != nil {
+ log.Error(4, "GetIssueByIndex: %v", err)
+ return "500 when get issue"
+ }
+ return issue.Content
+}
+
func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
if err = notifyWatchers(e, &Action{
ActUserID: u.Id,
@@ -147,7 +168,7 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
RepoName: repo.Name,
IsPrivate: repo.IsPrivate,
}); err != nil {
- return fmt.Errorf("notify watchers '%d/%s': %v", u.Id, repo.ID, err)
+ return fmt.Errorf("notify watchers '%d/%d': %v", u.Id, repo.ID, err)
}
log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
@@ -187,8 +208,48 @@ func issueIndexTrimRight(c rune) bool {
return !unicode.IsDigit(c)
}
+type PushCommit struct {
+ Sha1 string
+ Message string
+ AuthorEmail string
+ AuthorName string
+}
+
+type PushCommits struct {
+ Len int
+ Commits []*PushCommit
+ CompareUrl string
+
+ avatars map[string]string
+}
+
+func NewPushCommits() *PushCommits {
+ return &PushCommits{
+ avatars: make(map[string]string),
+ }
+}
+
+// AvatarLink tries to match user in database with e-mail
+// in order to show custom avatar, and falls back to general avatar link.
+func (push *PushCommits) AvatarLink(email string) string {
+ _, ok := push.avatars[email]
+ if !ok {
+ u, err := GetUserByEmail(email)
+ if err != nil {
+ push.avatars[email] = base.AvatarLink(email)
+ if !IsErrUserNotExist(err) {
+ log.Error(4, "GetUserByEmail: %v", err)
+ }
+ } else {
+ push.avatars[email] = u.AvatarLink()
+ }
+ }
+
+ return push.avatars[email]
+}
+
// updateIssuesCommit checks if issues are manipulated by commit message.
-func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
+func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*PushCommit) error {
// Commits are appended in the reverse order.
for i := len(commits) - 1; i >= 0; i-- {
c := commits[i]
@@ -322,7 +383,7 @@ func CommitRepoAction(
repoID int64,
repoUserName, repoName string,
refFullName string,
- commit *base.PushCommits,
+ commit *PushCommits,
oldCommitID string, newCommitID string) error {
u, err := GetUserByID(userID)
@@ -337,12 +398,18 @@ func CommitRepoAction(
return fmt.Errorf("GetOwner: %v", err)
}
+ // Change repository bare status and update last updated time.
+ repo.IsBare = false
+ if err = UpdateRepository(repo, false); err != nil {
+ return fmt.Errorf("UpdateRepository: %v", err)
+ }
+
isNewBranch := false
opType := COMMIT_REPO
// Check it's tag push or branch.
if strings.HasPrefix(refFullName, "refs/tags/") {
opType = PUSH_TAG
- commit = &base.PushCommits{}
+ commit = &PushCommits{}
} else {
// if not the first commit, set the compareUrl
if !strings.HasPrefix(oldCommitID, "0000000") {
@@ -351,12 +418,10 @@ func CommitRepoAction(
isNewBranch = true
}
- // Change repository bare status and update last updated time.
- repo.IsBare = false
- if err = UpdateRepository(repo, false); err != nil {
- return fmt.Errorf("UpdateRepository: %v", err)
+ // NOTE: limit to detect latest 100 commits.
+ if len(commit.Commits) > 100 {
+ commit.Commits = commit.Commits[len(commit.Commits)-100:]
}
-
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
log.Error(4, "updateIssuesCommit: %v", err)
}
@@ -488,7 +553,7 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
IsPrivate: repo.IsPrivate,
Content: path.Join(oldOwner.LowerName, repo.LowerName),
}); err != nil {
- return fmt.Errorf("notify watchers '%d/%s': %v", actUser.Id, repo.ID, err)
+ return fmt.Errorf("notify watchers '%d/%d': %v", actUser.Id, repo.ID, err)
}
// Remove watch for organization.
diff --git a/models/error.go b/models/error.go
index 1986c590..f8fea6f5 100644
--- a/models/error.go
+++ b/models/error.go
@@ -18,7 +18,7 @@ func IsErrNameReserved(err error) bool {
}
func (err ErrNameReserved) Error() string {
- return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
+ return fmt.Sprintf("name is reserved [name: %s]", err.Name)
}
type ErrNamePatternNotAllowed struct {
@@ -31,7 +31,7 @@ func IsErrNamePatternNotAllowed(err error) bool {
}
func (err ErrNamePatternNotAllowed) Error() string {
- return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
+ return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
}
// ____ ___
@@ -51,7 +51,7 @@ func IsErrUserAlreadyExist(err error) bool {
}
func (err ErrUserAlreadyExist) Error() string {
- return fmt.Sprintf("user already exists: [name: %s]", err.Name)
+ return fmt.Sprintf("user already exists [name: %s]", err.Name)
}
type ErrUserNotExist struct {
@@ -65,7 +65,7 @@ func IsErrUserNotExist(err error) bool {
}
func (err ErrUserNotExist) Error() string {
- return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
+ return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
}
type ErrEmailAlreadyUsed struct {
@@ -78,7 +78,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
}
func (err ErrEmailAlreadyUsed) Error() string {
- return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
+ return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
}
type ErrUserOwnRepos struct {
@@ -91,7 +91,7 @@ func IsErrUserOwnRepos(err error) bool {
}
func (err ErrUserOwnRepos) Error() string {
- return fmt.Sprintf("user still has ownership of repositories: [uid: %d]", err.UID)
+ return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
}
type ErrUserHasOrgs struct {
@@ -104,7 +104,7 @@ func IsErrUserHasOrgs(err error) bool {
}
func (err ErrUserHasOrgs) Error() string {
- return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
+ return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
}
// __________ ___. .__ .__ ____ __.
@@ -124,7 +124,7 @@ func IsErrKeyNotExist(err error) bool {
}
func (err ErrKeyNotExist) Error() string {
- return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
+ return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
}
type ErrKeyAlreadyExist struct {
@@ -138,7 +138,7 @@ func IsErrKeyAlreadyExist(err error) bool {
}
func (err ErrKeyAlreadyExist) Error() string {
- return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
+ return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
}
type ErrKeyNameAlreadyUsed struct {
@@ -152,7 +152,7 @@ func IsErrKeyNameAlreadyUsed(err error) bool {
}
func (err ErrKeyNameAlreadyUsed) Error() string {
- return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
+ return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
}
type ErrDeployKeyAlreadyExist struct {
@@ -166,7 +166,7 @@ func IsErrDeployKeyAlreadyExist(err error) bool {
}
func (err ErrDeployKeyAlreadyExist) Error() string {
- return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
+ return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
}
type ErrDeployKeyNameAlreadyUsed struct {
@@ -180,7 +180,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool {
}
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
- return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
+ return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
}
// _____ ___________ __
@@ -200,7 +200,7 @@ func IsErrAccessTokenNotExist(err error) bool {
}
func (err ErrAccessTokenNotExist) Error() string {
- return fmt.Sprintf("access token does not exist: [sha: %s]", err.SHA)
+ return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
}
// ________ .__ __ .__
@@ -220,7 +220,7 @@ func IsErrLastOrgOwner(err error) bool {
}
func (err ErrLastOrgOwner) Error() string {
- return fmt.Sprintf("user is the last member of owner team: [uid: %d]", err.UID)
+ return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
}
// __________ .__ __
@@ -259,6 +259,61 @@ func (err ErrRepoAlreadyExist) Error() string {
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
}
+type ErrInvalidCloneAddr struct {
+ IsURLError bool
+ IsInvalidPath bool
+ IsPermissionDenied bool
+}
+
+func IsErrInvalidCloneAddr(err error) bool {
+ _, ok := err.(ErrInvalidCloneAddr)
+ return ok
+}
+
+func (err ErrInvalidCloneAddr) Error() string {
+ return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
+ err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
+}
+
+type ErrUpdateTaskNotExist struct {
+ UUID string
+}
+
+func IsErrUpdateTaskNotExist(err error) bool {
+ _, ok := err.(ErrUpdateTaskNotExist)
+ return ok
+}
+
+func (err ErrUpdateTaskNotExist) Error() string {
+ return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
+}
+
+type ErrReleaseAlreadyExist struct {
+ TagName string
+}
+
+func IsErrReleaseAlreadyExist(err error) bool {
+ _, ok := err.(ErrReleaseAlreadyExist)
+ return ok
+}
+
+func (err ErrReleaseAlreadyExist) Error() string {
+ return fmt.Sprintf("Release tag already exist [tag_name: %s]", err.TagName)
+}
+
+type ErrReleaseNotExist struct {
+ TagName string
+}
+
+func IsErrReleaseNotExist(err error) bool {
+ _, ok := err.(ErrReleaseNotExist)
+ return ok
+}
+
+func (err ErrReleaseNotExist) Error() string {
+ return fmt.Sprintf("Release tag does not exist [tag_name: %s]", err.TagName)
+}
+
// __ __ ___. .__ __
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
@@ -310,7 +365,7 @@ func (err ErrIssueNotExist) Error() string {
type ErrPullRequestNotExist struct {
ID int64
- PullID int64
+ IssueID int64
HeadRepoID int64
BaseRepoID int64
HeadBarcnh string
@@ -323,8 +378,8 @@ func IsErrPullRequestNotExist(err error) bool {
}
func (err ErrPullRequestNotExist) Error() string {
- return fmt.Sprintf("pull request does not exist [id: %d, pull_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
- err.ID, err.PullID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
+ return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
+ err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
}
// _________ __
diff --git a/models/git_diff.go b/models/git_diff.go
index 66985650..4cc9ebf8 100644
--- a/models/git_diff.go
+++ b/models/git_diff.go
@@ -37,6 +37,7 @@ const (
DIFF_FILE_ADD = iota + 1
DIFF_FILE_CHANGE
DIFF_FILE_DEL
+ DIFF_FILE_RENAME
)
type DiffLine struct {
@@ -57,12 +58,14 @@ type DiffSection struct {
type DiffFile struct {
Name string
+ OldName string
Index int
Addition, Deletion int
Type int
IsCreated bool
IsDeleted bool
IsBin bool
+ IsRenamed bool
Sections []*DiffSection
}
@@ -86,7 +89,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
}
leftLine, rightLine int
- isTooLong bool
// FIXME: Should use cache in the future.
buf bytes.Buffer
)
@@ -95,7 +97,7 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
var i int
for scanner.Scan() {
line := scanner.Text()
- // fmt.Println(i, line)
+
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
continue
}
@@ -107,9 +109,10 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
i = i + 1
// Diff data too large, we only show the first about maxlines lines
- if i == maxlines {
- isTooLong = true
+ if i >= maxlines {
log.Warn("Diff data too large")
+ diff.Files = nil
+ return diff, nil
}
switch {
@@ -120,10 +123,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
curSection.Lines = append(curSection.Lines, diffLine)
continue
case line[0] == '@':
- if isTooLong {
- break
- }
-
curSection = &DiffSection{}
curFile.Sections = append(curFile.Sections, curSection)
ss := strings.Split(line, "@@")
@@ -162,21 +161,27 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
// Get new file.
if strings.HasPrefix(line, DIFF_HEAD) {
- if isTooLong {
- break
+ middle := -1
+
+ // Note: In case file name is surrounded by double quotes(it happens only in git-shell).
+ hasQuote := strings.Index(line, `\"`) > -1
+ if hasQuote {
+ line = strings.Replace(line, `\"`, `"`, -1)
+ middle = strings.Index(line, ` "b/`)
+ } else {
+ middle = strings.Index(line, " b/")
}
beg := len(DIFF_HEAD)
- a := line[beg : (len(line)-beg)/2+beg]
-
- // In case file name is surrounded by double quotes(it happens only in git-shell).
- if a[0] == '"' {
+ a := line[beg+2 : middle]
+ b := line[middle+3:]
+ if hasQuote {
a = a[1 : len(a)-1]
- a = strings.Replace(a, `\"`, `"`, -1)
+ b = b[1 : len(b)-1]
}
curFile = &DiffFile{
- Name: a[strings.Index(a, "/")+1:],
+ Name: a,
Index: len(diff.Files) + 1,
Type: DIFF_FILE_CHANGE,
Sections: make([]*DiffSection, 0, 10),
@@ -188,16 +193,17 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
switch {
case strings.HasPrefix(scanner.Text(), "new file"):
curFile.Type = DIFF_FILE_ADD
- curFile.IsDeleted = false
curFile.IsCreated = true
case strings.HasPrefix(scanner.Text(), "deleted"):
curFile.Type = DIFF_FILE_DEL
- curFile.IsCreated = false
curFile.IsDeleted = true
case strings.HasPrefix(scanner.Text(), "index"):
curFile.Type = DIFF_FILE_CHANGE
- curFile.IsCreated = false
- curFile.IsDeleted = false
+ case strings.HasPrefix(scanner.Text(), "similarity index 100%"):
+ curFile.Type = DIFF_FILE_RENAME
+ curFile.IsRenamed = true
+ curFile.OldName = curFile.Name
+ curFile.Name = b
}
if curFile.Type > 0 {
break
@@ -252,10 +258,10 @@ func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxline
cmd = exec.Command("git", "show", afterCommitId)
} else {
c, _ := commit.Parent(0)
- cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
+ cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitId)
}
} else {
- cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
+ cmd = exec.Command("git", "diff", "-M", beforeCommitId, afterCommitId)
}
cmd.Dir = repoPath
cmd.Stdout = wr
diff --git a/models/issue.go b/models/issue.go
index 0dd0b663..077e945c 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"io"
- "io/ioutil"
"mime/multipart"
"os"
"path"
@@ -20,9 +19,7 @@ import (
"github.com/go-xorm/xorm"
"github.com/gogits/gogs/modules/base"
- "github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
- "github.com/gogits/gogs/modules/process"
"github.com/gogits/gogs/modules/setting"
gouuid "github.com/gogits/gogs/modules/uuid"
)
@@ -95,15 +92,6 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
if err != nil {
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
}
- case "is_pull":
- if !i.IsPull {
- return
- }
-
- i.PullRequest, err = GetPullRequestByPullID(i.ID)
- if err != nil {
- log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
- }
case "created":
i.Created = regulateTimeZone(i.Created)
}
@@ -236,7 +224,7 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err er
}
i.IsClosed = isClosed
- if err = updateIssue(e, i); err != nil {
+ if err = updateIssueCols(e, i, "is_closed"); err != nil {
return err
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
return err
@@ -285,6 +273,15 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
return sess.Commit()
}
+func (i *Issue) GetPullRequest() (err error) {
+ if i.PullRequest != nil {
+ return nil
+ }
+
+ i.PullRequest, err = GetPullRequestByIssueID(i.ID)
+ return err
+}
+
// It's caller's responsibility to create action.
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
if _, err = e.Insert(issue); err != nil {
@@ -821,11 +818,17 @@ func updateIssue(e Engine, issue *Issue) error {
return err
}
-// UpdateIssue updates information of issue.
+// UpdateIssue updates all fields of given issue.
func UpdateIssue(issue *Issue) error {
return updateIssue(x, issue)
}
+// updateIssueCols updates specific fields of given issue.
+func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
+ _, err := e.Id(issue.ID).Cols(cols...).Update(issue)
+ return err
+}
+
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
return err
@@ -894,240 +897,6 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
return nil
}
-// __________ .__ .__ __________ __
-// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
-// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
-// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
-// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
-// \/ \/ |__| \/ \/
-
-type PullRequestType int
-
-const (
- PULL_REQUEST_GOGS = iota
- PLLL_ERQUEST_GIT
-)
-
-// PullRequest represents relation between pull request and repositories.
-type PullRequest struct {
- ID int64 `xorm:"pk autoincr"`
- PullID int64 `xorm:"INDEX"`
- Pull *Issue `xorm:"-"`
- PullIndex int64
- HeadRepoID int64
- HeadRepo *Repository `xorm:"-"`
- BaseRepoID int64
- HeadUserName string
- HeadBarcnh string
- BaseBranch string
- MergeBase string `xorm:"VARCHAR(40)"`
- MergedCommitID string `xorm:"VARCHAR(40)"`
- Type PullRequestType
- CanAutoMerge bool
- HasMerged bool
- Merged time.Time
- MergerID int64
- Merger *User `xorm:"-"`
-}
-
-// Note: don't try to get Pull because will end up recursive querying.
-func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
- var err error
- switch colName {
- case "head_repo_id":
- // FIXME: shouldn't show error if it's known that head repository has been removed.
- pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
- if err != nil {
- log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
- }
- case "merger_id":
- if !pr.HasMerged {
- return
- }
-
- pr.Merger, err = GetUserByID(pr.MergerID)
- if err != nil {
- if IsErrUserNotExist(err) {
- pr.MergerID = -1
- pr.Merger = NewFakeUser()
- } else {
- log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
- }
- }
- case "merged":
- if !pr.HasMerged {
- return
- }
-
- pr.Merged = regulateTimeZone(pr.Merged)
- }
-}
-
-// Merge merges pull request to base repository.
-func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
- sess := x.NewSession()
- defer sessionRelease(sess)
- if err = sess.Begin(); err != nil {
- return err
- }
-
- if err = pr.Pull.changeStatus(sess, doer, true); err != nil {
- return fmt.Errorf("Pull.changeStatus: %v", err)
- }
-
- headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
- headGitRepo, err := git.OpenRepository(headRepoPath)
- if err != nil {
- return fmt.Errorf("OpenRepository: %v", err)
- }
- pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh)
- if err != nil {
- return fmt.Errorf("GetCommitIdOfBranch: %v", err)
- }
-
- if err = mergePullRequestAction(sess, doer, pr.Pull.Repo, pr.Pull); err != nil {
- return fmt.Errorf("mergePullRequestAction: %v", err)
- }
-
- pr.HasMerged = true
- pr.Merged = time.Now()
- pr.MergerID = doer.Id
- if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
- return fmt.Errorf("update pull request: %v", err)
- }
-
- // Clone base repo.
- tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
- os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
- defer os.RemoveAll(path.Dir(tmpBasePath))
-
- var stderr string
- if _, stderr, err = process.ExecTimeout(5*time.Minute,
- fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
- "git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
- return fmt.Errorf("git clone: %s", stderr)
- }
-
- // Check out base branch.
- if _, stderr, err = process.ExecDir(-1, tmpBasePath,
- fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
- "git", "checkout", pr.BaseBranch); err != nil {
- return fmt.Errorf("git checkout: %s", stderr)
- }
-
- // Pull commits.
- if _, stderr, err = process.ExecDir(-1, tmpBasePath,
- fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
- "git", "pull", headRepoPath, pr.HeadBarcnh); err != nil {
- return fmt.Errorf("git pull[%s / %s -> %s]: %s", headRepoPath, pr.HeadBarcnh, tmpBasePath, stderr)
- }
-
- // Push back to upstream.
- if _, stderr, err = process.ExecDir(-1, tmpBasePath,
- fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
- "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
- return fmt.Errorf("git push: %s", stderr)
- }
-
- return sess.Commit()
-}
-
-// NewPullRequest creates new pull request with labels for repository.
-func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
- sess := x.NewSession()
- defer sessionRelease(sess)
- if err = sess.Begin(); err != nil {
- return err
- }
-
- if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
- return fmt.Errorf("newIssue: %v", err)
- }
-
- // Notify watchers.
- act := &Action{
- ActUserID: pull.Poster.Id,
- ActUserName: pull.Poster.Name,
- ActEmail: pull.Poster.Email,
- OpType: CREATE_PULL_REQUEST,
- Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
- RepoID: repo.ID,
- RepoUserName: repo.Owner.Name,
- RepoName: repo.Name,
- IsPrivate: repo.IsPrivate,
- }
- if err = notifyWatchers(sess, act); err != nil {
- return err
- }
-
- // Test apply patch.
- if err = repo.UpdateLocalCopy(); err != nil {
- return fmt.Errorf("UpdateLocalCopy: %v", err)
- }
-
- repoPath, err := repo.RepoPath()
- if err != nil {
- return fmt.Errorf("RepoPath: %v", err)
- }
- patchPath := path.Join(repoPath, "pulls", com.ToStr(pull.ID)+".patch")
-
- os.MkdirAll(path.Dir(patchPath), os.ModePerm)
- if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
- return fmt.Errorf("save patch: %v", err)
- }
-
- pr.CanAutoMerge = true
- _, stderr, err := process.ExecDir(-1, repo.LocalCopyPath(),
- fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
- "git", "apply", "--check", patchPath)
- if err != nil {
- if strings.Contains(stderr, "patch does not apply") {
- pr.CanAutoMerge = false
- } else {
- return fmt.Errorf("git apply --check: %v - %s", err, stderr)
- }
- }
-
- pr.PullID = pull.ID
- pr.PullIndex = pull.Index
- if _, err = sess.Insert(pr); err != nil {
- return fmt.Errorf("insert pull repo: %v", err)
- }
-
- return sess.Commit()
-}
-
-// GetUnmergedPullRequest returnss a pull request hasn't been merged by given info.
-func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
- pr := &PullRequest{
- HeadRepoID: headRepoID,
- BaseRepoID: baseRepoID,
- HeadBarcnh: headBranch,
- BaseBranch: baseBranch,
- }
-
- has, err := x.Where("has_merged=?", false).Get(pr)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
- }
-
- return pr, nil
-}
-
-// GetPullRequestByPullID returns pull repo by given pull ID.
-func GetPullRequestByPullID(pullID int64) (*PullRequest, error) {
- pr := new(PullRequest)
- has, err := x.Where("pull_id=?", pullID).Get(pr)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
- }
- return pr, nil
-}
-
// .____ ___. .__
// | | _____ \_ |__ ____ | |
// | | \__ \ | __ \_/ __ \| |
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 08a503e6..0d17cf26 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -11,6 +11,7 @@ import (
"io/ioutil"
"os"
"path"
+ "path/filepath"
"strings"
"time"
@@ -65,6 +66,8 @@ var migrations = []Migration{
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
+ NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
+ NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
}
// Migrate database to current version
@@ -453,7 +456,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
pushCommits = new(PushCommits)
if err = json.Unmarshal(action["content"], pushCommits); err != nil {
- return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
+ return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
}
infos := strings.Split(pushCommits.CompareUrl, "/")
@@ -464,7 +467,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
p, err := json.Marshal(pushCommits)
if err != nil {
- return fmt.Errorf("marshal action content[%s]: %v", actID, err)
+ return fmt.Errorf("marshal action content[%d]: %v", actID, err)
}
if _, err = sess.Id(actID).Update(&Action{
@@ -606,3 +609,96 @@ func attachmentRefactor(x *xorm.Engine) error {
return sess.Commit()
}
+
+func renamePullRequestFields(x *xorm.Engine) (err error) {
+ type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+ PullID int64 `xorm:"INDEX"`
+ PullIndex int64
+ HeadBarcnh string
+
+ IssueID int64 `xorm:"INDEX"`
+ Index int64
+ HeadBranch string
+ }
+
+ if err = x.Sync(new(PullRequest)); err != nil {
+ return fmt.Errorf("sync: %v", err)
+ }
+
+ results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
+ if err != nil {
+ if strings.Contains(err.Error(), "no such column") {
+ return nil
+ }
+ return fmt.Errorf("select pull requests: %v", err)
+ }
+
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ var pull *PullRequest
+ for _, pr := range results {
+ pull = &PullRequest{
+ ID: com.StrTo(pr["id"]).MustInt64(),
+ IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
+ Index: com.StrTo(pr["pull_index"]).MustInt64(),
+ HeadBranch: string(pr["head_barcnh"]),
+ }
+ if _, err = sess.Id(pull.ID).Update(pull); err != nil {
+ return err
+ }
+ }
+
+ return sess.Commit()
+}
+
+func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
+ type (
+ User struct {
+ ID int64 `xorm:"pk autoincr"`
+ LowerName string
+ }
+ Repository struct {
+ ID int64 `xorm:"pk autoincr"`
+ OwnerID int64
+ LowerName string
+ }
+ )
+
+ repos := make([]*Repository, 0, 25)
+ if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
+ return fmt.Errorf("select all non-mirror repositories: %v", err)
+ }
+ var user *User
+ for _, repo := range repos {
+ user = &User{ID: repo.OwnerID}
+ has, err := x.Get(user)
+ if err != nil {
+ return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
+ } else if !has {
+ continue
+ }
+
+ configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
+
+ // In case repository file is somehow missing.
+ if !com.IsFile(configPath) {
+ continue
+ }
+
+ cfg, err := ini.Load(configPath)
+ if err != nil {
+ return fmt.Errorf("open config file: %v", err)
+ }
+ cfg.DeleteSection("remote \"origin\"")
+ if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
+ return fmt.Errorf("save config file: %v", err)
+ }
+ }
+
+ return nil
+}
diff --git a/models/models.go b/models/models.go
index 2410ecd4..2249fee4 100644
--- a/models/models.go
+++ b/models/models.go
@@ -52,9 +52,8 @@ func regulateTimeZone(t time.Time) time.Time {
}
zone := t.Local().Format("-0700")
- log.Trace("regulateTimeZone: %s - %s", t.Local(), zone)
-
if len(zone) != 5 {
+ log.Error(4, "Unprocessable timezone: %s - %s", t.Local(), zone)
return t
}
hour := com.StrTo(zone[2:3]).MustInt()
@@ -91,7 +90,7 @@ func init() {
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
new(Notice), new(EmailAddress))
- gonicNames := []string{"UID", "SSL"}
+ gonicNames := []string{"SSL"}
for _, name := range gonicNames {
core.LintGonicMapper[name] = true
}
diff --git a/models/publickey.go b/models/publickey.go
index 6c0ffc0c..0f041c09 100644
--- a/models/publickey.go
+++ b/models/publickey.go
@@ -13,7 +13,6 @@ import (
"io"
"io/ioutil"
"os"
- "os/exec"
"path"
"path/filepath"
"strings"
@@ -38,20 +37,7 @@ var (
)
var sshOpLocker = sync.Mutex{}
-
-var (
- SSHPath string // SSH directory.
- appPath string // Execution(binary) path.
-)
-
-// exePath returns the executable path.
-func exePath() (string, error) {
- file, err := exec.LookPath(os.Args[0])
- if err != nil {
- return "", err
- }
- return filepath.Abs(file)
-}
+var SSHPath string // SSH directory.
// homeDir returns the home directory of current user.
func homeDir() string {
@@ -63,16 +49,9 @@ func homeDir() string {
}
func init() {
- var err error
-
- if appPath, err = exePath(); err != nil {
- log.Fatal(4, "fail to get app path: %v\n", err)
- }
- appPath = strings.Replace(appPath, "\\", "/", -1)
-
// Determine and create .ssh path.
SSHPath = filepath.Join(homeDir(), ".ssh")
- if err = os.MkdirAll(SSHPath, 0700); err != nil {
+ if err := os.MkdirAll(SSHPath, 0700); err != nil {
log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
}
}
@@ -114,17 +93,7 @@ func (k *PublicKey) OmitEmail() string {
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
func (key *PublicKey) GetAuthorizedString() string {
- return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
-}
-
-var minimumKeySizes = map[string]int{
- "(ED25519)": 256,
- "(ECDSA)": 256,
- "(NTRU)": 1087,
- "(MCE)": 1702,
- "(McE)": 1702,
- "(RSA)": 1024,
- "(DSA)": 1024,
+ return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
}
func extractTypeFromBase64Key(key string) (string, error) {
@@ -228,9 +197,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
tmpFile.Close()
// Check if ssh-keygen recognizes its contents.
- stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
+ stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
if err != nil {
- return "", errors.New("ssh-keygen -l -f: " + stderr)
+ return "", errors.New("ssh-keygen -lf: " + stderr)
} else if len(stdout) < 2 {
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
}
@@ -251,9 +220,10 @@ func CheckPublicKeyString(content string) (_ string, err error) {
if keySize == 0 {
return "", errors.New("cannot get key size of the given key")
}
- keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
- if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
- return "", errors.New("sorry, unrecognized public key type")
+
+ keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
+ if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
+ return "", fmt.Errorf("unrecognized public key type: %s", keyType)
} else if keySize < minimumKeySize {
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
}
@@ -321,9 +291,9 @@ func addKey(e Engine, key *PublicKey) (err error) {
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
return err
}
- stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
+ stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
if err != nil {
- return errors.New("ssh-keygen -l -f: " + stderr)
+ return errors.New("ssh-keygen -lf: " + stderr)
} else if len(stdout) < 2 {
return errors.New("not enough output for calculating fingerprint: " + stdout)
}
@@ -382,6 +352,19 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
return key, nil
}
+// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
+// and returns public key found.
+func SearchPublicKeyByContent(content string) (*PublicKey, error) {
+ key := new(PublicKey)
+ has, err := x.Where("content like ?", content+"%").Get(key)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrKeyNotExist{}
+ }
+ return key, nil
+}
+
// ListPublicKeys returns a list of public keys belongs to given user.
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
keys := make([]*PublicKey, 0, 5)
diff --git a/models/pull.go b/models/pull.go
new file mode 100644
index 00000000..5c7d4a3f
--- /dev/null
+++ b/models/pull.go
@@ -0,0 +1,558 @@
+// 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 models
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "strings"
+ "time"
+
+ "github.com/Unknwon/com"
+ "github.com/go-xorm/xorm"
+
+ "github.com/gogits/gogs/modules/git"
+ "github.com/gogits/gogs/modules/log"
+ "github.com/gogits/gogs/modules/process"
+ "github.com/gogits/gogs/modules/setting"
+)
+
+type PullRequestType int
+
+const (
+ PULL_REQUEST_GOGS PullRequestType = iota
+ PLLL_ERQUEST_GIT
+)
+
+type PullRequestStatus int
+
+const (
+ PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
+ PULL_REQUEST_STATUS_CHECKING
+ PULL_REQUEST_STATUS_MERGEABLE
+)
+
+// PullRequest represents relation between pull request and repositories.
+type PullRequest struct {
+ ID int64 `xorm:"pk autoincr"`
+ Type PullRequestType
+ Status PullRequestStatus
+
+ IssueID int64 `xorm:"INDEX"`
+ Issue *Issue `xorm:"-"`
+ Index int64
+
+ HeadRepoID int64
+ HeadRepo *Repository `xorm:"-"`
+ BaseRepoID int64
+ BaseRepo *Repository `xorm:"-"`
+ HeadUserName string
+ HeadBranch string
+ BaseBranch string
+ MergeBase string `xorm:"VARCHAR(40)"`
+
+ HasMerged bool
+ MergedCommitID string `xorm:"VARCHAR(40)"`
+ Merged time.Time
+ MergerID int64
+ Merger *User `xorm:"-"`
+}
+
+// Note: don't try to get Pull because will end up recursive querying.
+func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
+ switch colName {
+ case "merged":
+ if !pr.HasMerged {
+ return
+ }
+
+ pr.Merged = regulateTimeZone(pr.Merged)
+ }
+}
+
+func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
+ pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
+ if err != nil && !IsErrRepoNotExist(err) {
+ return fmt.Errorf("getRepositoryByID(head): %v", err)
+ }
+ return nil
+}
+
+func (pr *PullRequest) GetHeadRepo() (err error) {
+ return pr.getHeadRepo(x)
+}
+
+func (pr *PullRequest) GetBaseRepo() (err error) {
+ if pr.BaseRepo != nil {
+ return nil
+ }
+
+ pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
+ if err != nil {
+ return fmt.Errorf("GetRepositoryByID(base): %v", err)
+ }
+ return nil
+}
+
+func (pr *PullRequest) GetMerger() (err error) {
+ if !pr.HasMerged || pr.Merger != nil {
+ return nil
+ }
+
+ pr.Merger, err = GetUserByID(pr.MergerID)
+ if IsErrUserNotExist(err) {
+ pr.MergerID = -1
+ pr.Merger = NewFakeUser()
+ } else if err != nil {
+ return fmt.Errorf("GetUserByID: %v", err)
+ }
+ return nil
+}
+
+// IsChecking returns true if this pull request is still checking conflict.
+func (pr *PullRequest) IsChecking() bool {
+ return pr.Status == PULL_REQUEST_STATUS_CHECKING
+}
+
+// CanAutoMerge returns true if this pull request can be merged automatically.
+func (pr *PullRequest) CanAutoMerge() bool {
+ return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
+}
+
+// Merge merges pull request to base repository.
+func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = pr.Issue.changeStatus(sess, doer, true); err != nil {
+ return fmt.Errorf("Issue.changeStatus: %v", err)
+ }
+
+ if err = pr.getHeadRepo(sess); err != nil {
+ return fmt.Errorf("getHeadRepo: %v", err)
+ }
+
+ headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
+ headGitRepo, err := git.OpenRepository(headRepoPath)
+ if err != nil {
+ return fmt.Errorf("OpenRepository: %v", err)
+ }
+ pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBranch)
+ if err != nil {
+ return fmt.Errorf("GetCommitIdOfBranch: %v", err)
+ }
+
+ if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
+ return fmt.Errorf("mergePullRequestAction: %v", err)
+ }
+
+ pr.HasMerged = true
+ pr.Merged = time.Now()
+ pr.MergerID = doer.Id
+ if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
+ return fmt.Errorf("update pull request: %v", err)
+ }
+
+ // Clone base repo.
+ tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
+ os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
+ defer os.RemoveAll(path.Dir(tmpBasePath))
+
+ var stderr string
+ if _, stderr, err = process.ExecTimeout(5*time.Minute,
+ fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
+ "git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
+ return fmt.Errorf("git clone: %s", stderr)
+ }
+
+ // Check out base branch.
+ if _, stderr, err = process.ExecDir(-1, tmpBasePath,
+ fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
+ "git", "checkout", pr.BaseBranch); err != nil {
+ return fmt.Errorf("git checkout: %s", stderr)
+ }
+
+ // Add head repo remote.
+ if _, stderr, err = process.ExecDir(-1, tmpBasePath,
+ fmt.Sprintf("PullRequest.Merge(git remote add): %s", tmpBasePath),
+ "git", "remote", "add", "head_repo", headRepoPath); err != nil {
+ return fmt.Errorf("git remote add[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
+ }
+
+ // Merge commits.
+ if _, stderr, err = process.ExecDir(-1, tmpBasePath,
+ fmt.Sprintf("PullRequest.Merge(git fetch): %s", tmpBasePath),
+ "git", "fetch", "head_repo"); err != nil {
+ return fmt.Errorf("git fetch[%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
+ }
+
+ if _, stderr, err = process.ExecDir(-1, tmpBasePath,
+ fmt.Sprintf("PullRequest.Merge(git merge): %s", tmpBasePath),
+ "git", "merge", "--no-ff", "-m",
+ fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch),
+ "head_repo/"+pr.HeadBranch); err != nil {
+ return fmt.Errorf("git merge[%s]: %s", tmpBasePath, stderr)
+ }
+
+ // Push back to upstream.
+ if _, stderr, err = process.ExecDir(-1, tmpBasePath,
+ fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
+ "git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
+ return fmt.Errorf("git push: %s", stderr)
+ }
+
+ return sess.Commit()
+}
+
+// patchConflicts is a list of conflit description from Git.
+var patchConflicts = []string{
+ "patch does not apply",
+ "already exists in working directory",
+ "unrecognized input",
+}
+
+// testPatch checks if patch can be merged to base repository without conflit.
+// FIXME: make a mechanism to clean up stable local copies.
+func (pr *PullRequest) testPatch() (err error) {
+ if pr.BaseRepo == nil {
+ pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
+ if err != nil {
+ return fmt.Errorf("GetRepositoryByID: %v", err)
+ }
+ }
+
+ patchPath, err := pr.BaseRepo.PatchPath(pr.Index)
+ if err != nil {
+ return fmt.Errorf("BaseRepo.PatchPath: %v", err)
+ }
+
+ // Fast fail if patch does not exist, this assumes data is cruppted.
+ if !com.IsFile(patchPath) {
+ log.Trace("PullRequest[%d].testPatch: ignored cruppted data", pr.ID)
+ return nil
+ }
+
+ log.Trace("PullRequest[%d].testPatch(patchPath): %s", pr.ID, patchPath)
+
+ if err := pr.BaseRepo.UpdateLocalCopy(); err != nil {
+ return fmt.Errorf("UpdateLocalCopy: %v", err)
+ }
+
+ // Checkout base branch.
+ _, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
+ fmt.Sprintf("PullRequest.Merge(git checkout): %s", pr.BaseRepo.ID),
+ "git", "checkout", pr.BaseBranch)
+ if err != nil {
+ return fmt.Errorf("git checkout: %s", stderr)
+ }
+
+ pr.Status = PULL_REQUEST_STATUS_CHECKING
+ _, stderr, err = process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
+ fmt.Sprintf("testPatch(git apply --check): %d", pr.BaseRepo.ID),
+ "git", "apply", "--check", patchPath)
+ if err != nil {
+ for i := range patchConflicts {
+ if strings.Contains(stderr, patchConflicts[i]) {
+ log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID)
+ fmt.Println(stderr)
+ pr.Status = PULL_REQUEST_STATUS_CONFLICT
+ return nil
+ }
+ }
+
+ return fmt.Errorf("git apply --check: %v - %s", err, stderr)
+ }
+ return nil
+}
+
+// NewPullRequest creates new pull request with labels for repository.
+func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
+ sess := x.NewSession()
+ defer sessionRelease(sess)
+ if err = sess.Begin(); err != nil {
+ return err
+ }
+
+ if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
+ return fmt.Errorf("newIssue: %v", err)
+ }
+
+ // Notify watchers.
+ act := &Action{
+ ActUserID: pull.Poster.Id,
+ ActUserName: pull.Poster.Name,
+ ActEmail: pull.Poster.Email,
+ OpType: CREATE_PULL_REQUEST,
+ Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
+ RepoID: repo.ID,
+ RepoUserName: repo.Owner.Name,
+ RepoName: repo.Name,
+ IsPrivate: repo.IsPrivate,
+ }
+ if err = notifyWatchers(sess, act); err != nil {
+ return err
+ }
+
+ pr.Index = pull.Index
+ if err = repo.SavePatch(pr.Index, patch); err != nil {
+ return fmt.Errorf("SavePatch: %v", err)
+ }
+
+ pr.BaseRepo = repo
+ if err = pr.testPatch(); err != nil {
+ return fmt.Errorf("testPatch: %v", err)
+ }
+ if pr.Status == PULL_REQUEST_STATUS_CHECKING {
+ pr.Status = PULL_REQUEST_STATUS_MERGEABLE
+ }
+
+ pr.IssueID = pull.ID
+ if _, err = sess.Insert(pr); err != nil {
+ return fmt.Errorf("insert pull repo: %v", err)
+ }
+
+ return sess.Commit()
+}
+
+// GetUnmergedPullRequest returnss a pull request that is open and has not been merged
+// by given head/base and repo/branch.
+func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
+ pr := new(PullRequest)
+ has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
+ headRepoID, headBranch, baseRepoID, baseBranch, false, false).
+ Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
+ }
+
+ return pr, nil
+}
+
+// GetUnmergedPullRequestsByHeadInfo returnss all pull requests that are open and has not been merged
+// by given head information (repo and branch).
+func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
+ prs := make([]*PullRequest, 0, 2)
+ return prs, x.Where("head_repo_id=? AND head_branch=? AND has_merged=? AND issue.is_closed=?",
+ repoID, branch, false, false).
+ Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
+}
+
+// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged
+// by given base information (repo and branch).
+func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
+ prs := make([]*PullRequest, 0, 2)
+ return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
+ repoID, branch, false, false).
+ Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
+}
+
+// GetPullRequestByID returns a pull request by given ID.
+func GetPullRequestByID(id int64) (*PullRequest, error) {
+ pr := new(PullRequest)
+ has, err := x.Id(id).Get(pr)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
+ }
+ return pr, nil
+}
+
+// GetPullRequestByIssueID returns pull request by given issue ID.
+func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
+ pr := &PullRequest{
+ IssueID: issueID,
+ }
+ has, err := x.Get(pr)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
+ }
+ return pr, nil
+}
+
+// Update updates all fields of pull request.
+func (pr *PullRequest) Update() error {
+ _, err := x.Id(pr.ID).AllCols().Update(pr)
+ return err
+}
+
+// Update updates specific fields of pull request.
+func (pr *PullRequest) UpdateCols(cols ...string) error {
+ _, err := x.Id(pr.ID).Cols(cols...).Update(pr)
+ return err
+}
+
+var PullRequestQueue = NewUniqueQueue(setting.Repository.PullRequestQueueLength)
+
+// UpdatePatch generates and saves a new patch.
+func (pr *PullRequest) UpdatePatch() (err error) {
+ if err = pr.GetHeadRepo(); err != nil {
+ return fmt.Errorf("GetHeadRepo: %v", err)
+ } else if pr.HeadRepo == nil {
+ log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
+ return nil
+ }
+
+ if err = pr.GetBaseRepo(); err != nil {
+ return fmt.Errorf("GetBaseRepo: %v", err)
+ } else if err = pr.BaseRepo.GetOwner(); err != nil {
+ return fmt.Errorf("GetOwner: %v", err)
+ }
+
+ headRepoPath, err := pr.HeadRepo.RepoPath()
+ if err != nil {
+ return fmt.Errorf("HeadRepo.RepoPath: %v", err)
+ }
+
+ headGitRepo, err := git.OpenRepository(headRepoPath)
+ if err != nil {
+ return fmt.Errorf("OpenRepository: %v", err)
+ }
+
+ // Add a temporary remote.
+ tmpRemote := com.ToStr(time.Now().UnixNano())
+ if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.Owner.Name, pr.BaseRepo.Name)); err != nil {
+ return fmt.Errorf("AddRemote: %v", err)
+ }
+ defer func() {
+ headGitRepo.RemoveRemote(tmpRemote)
+ }()
+ remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
+ pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
+ if err != nil {
+ return fmt.Errorf("GetMergeBase: %v", err)
+ } else if err = pr.Update(); err != nil {
+ return fmt.Errorf("Update: %v", err)
+ }
+
+ patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
+ if err != nil {
+ return fmt.Errorf("GetPatch: %v", err)
+ }
+
+ if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
+ return fmt.Errorf("BaseRepo.SavePatch: %v", err)
+ }
+
+ return nil
+}
+
+// AddToTaskQueue adds itself to pull request test task queue.
+func (pr *PullRequest) AddToTaskQueue() {
+ go PullRequestQueue.AddFunc(pr.ID, func() {
+ pr.Status = PULL_REQUEST_STATUS_CHECKING
+ if err := pr.UpdateCols("status"); err != nil {
+ log.Error(5, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
+ }
+ })
+}
+
+func addHeadRepoTasks(prs []*PullRequest) {
+ for _, pr := range prs {
+ log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
+ if err := pr.UpdatePatch(); err != nil {
+ log.Error(4, "UpdatePatch: %v", err)
+ continue
+ }
+
+ pr.AddToTaskQueue()
+ }
+}
+
+// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
+// and generate new patch for testing as needed.
+func AddTestPullRequestTask(repoID int64, branch string) {
+ log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
+ prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
+ if err != nil {
+ log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
+ return
+ }
+ addHeadRepoTasks(prs)
+
+ log.Trace("AddTestPullRequestTask[base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
+ prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
+ if err != nil {
+ log.Error(4, "Find pull requests[base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
+ return
+ }
+ for _, pr := range prs {
+ pr.AddToTaskQueue()
+ }
+}
+
+// checkAndUpdateStatus checks if pull request is possible to levaing checking status,
+// and set to be either conflict or mergeable.
+func (pr *PullRequest) checkAndUpdateStatus() {
+ // Status is not changed to conflict means mergeable.
+ if pr.Status == PULL_REQUEST_STATUS_CHECKING {
+ pr.Status = PULL_REQUEST_STATUS_MERGEABLE
+ }
+
+ // Make sure there is no waiting test to process before levaing the checking status.
+ if !PullRequestQueue.Exist(pr.ID) {
+ if err := pr.UpdateCols("status"); err != nil {
+ log.Error(4, "Update[%d]: %v", pr.ID, err)
+ }
+ }
+}
+
+// TestPullRequests checks and tests untested patches of pull requests.
+// TODO: test more pull requests at same time.
+func TestPullRequests() {
+ prs := make([]*PullRequest, 0, 10)
+ x.Iterate(PullRequest{
+ Status: PULL_REQUEST_STATUS_CHECKING,
+ },
+ func(idx int, bean interface{}) error {
+ pr := bean.(*PullRequest)
+
+ if err := pr.GetBaseRepo(); err != nil {
+ log.Error(3, "GetBaseRepo: %v", err)
+ return nil
+ }
+
+ if err := pr.testPatch(); err != nil {
+ log.Error(3, "testPatch: %v", err)
+ return nil
+ }
+ prs = append(prs, pr)
+ return nil
+ })
+
+ // Update pull request status.
+ for _, pr := range prs {
+ pr.checkAndUpdateStatus()
+ }
+
+ // Start listening on new test requests.
+ for prID := range PullRequestQueue.Queue() {
+ log.Trace("TestPullRequests[%v]: processing test task", prID)
+ PullRequestQueue.Remove(prID)
+
+ pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64())
+ if err != nil {
+ log.Error(4, "GetPullRequestByID[%d]: %v", prID, err)
+ continue
+ } else if err = pr.testPatch(); err != nil {
+ log.Error(4, "testPatch[%d]: %v", pr.ID, err)
+ continue
+ }
+
+ pr.checkAndUpdateStatus()
+ }
+}
+
+func InitTestPullRequests() {
+ go TestPullRequests()
+}
diff --git a/models/release.go b/models/release.go
index 027491d9..1c9c7d60 100644
--- a/models/release.go
+++ b/models/release.go
@@ -5,7 +5,6 @@
package models
import (
- "errors"
"sort"
"strings"
"time"
@@ -15,16 +14,11 @@ import (
"github.com/gogits/gogs/modules/git"
)
-var (
- ErrReleaseAlreadyExist = errors.New("Release already exist")
- ErrReleaseNotExist = errors.New("Release does not exist")
-)
-
// Release represents a release of repository.
type Release struct {
- Id int64
- RepoId int64
- PublisherId int64
+ ID int64 `xorm:"pk autoincr"`
+ RepoID int64
+ PublisherID int64
Publisher *User `xorm:"-"`
TagName string
LowerTagName string
@@ -47,12 +41,12 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
}
// IsReleaseExist returns true if release with given tag name already exists.
-func IsReleaseExist(repoId int64, tagName string) (bool, error) {
+func IsReleaseExist(repoID int64, tagName string) (bool, error) {
if len(tagName) == 0 {
return false, nil
}
- return x.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
+ return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
}
func createTag(gitRepo *git.Repository, rel *Release) error {
@@ -64,7 +58,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
return err
}
- if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
+ if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
return err
}
} else {
@@ -84,11 +78,11 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
// CreateRelease creates a new release of repository.
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
- isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
+ isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
if err != nil {
return err
} else if isExist {
- return ErrReleaseAlreadyExist
+ return ErrReleaseAlreadyExist{rel.TagName}
}
if err = createTag(gitRepo, rel); err != nil {
@@ -100,22 +94,22 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
}
// GetRelease returns release by given ID.
-func GetRelease(repoId int64, tagName string) (*Release, error) {
- isExist, err := IsReleaseExist(repoId, tagName)
+func GetRelease(repoID int64, tagName string) (*Release, error) {
+ isExist, err := IsReleaseExist(repoID, tagName)
if err != nil {
return nil, err
} else if !isExist {
- return nil, ErrReleaseNotExist
+ return nil, ErrReleaseNotExist{tagName}
}
- rel := &Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}
+ rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
_, err = x.Get(rel)
return rel, err
}
// GetReleasesByRepoId returns a list of releases of repository.
-func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
- err = x.Desc("created").Find(&rels, Release{RepoId: repoId})
+func GetReleasesByRepoId(repoID int64) (rels []*Release, err error) {
+ err = x.Desc("created").Find(&rels, Release{RepoID: repoID})
return rels, err
}
@@ -150,6 +144,6 @@ func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
if err = createTag(gitRepo, rel); err != nil {
return err
}
- _, err = x.Id(rel.Id).AllCols().Update(rel)
+ _, err = x.Id(rel.ID).AllCols().Update(rel)
return err
}
diff --git a/models/repo.go b/models/repo.go
index 75e1cd3a..174fbd9d 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -23,6 +23,7 @@ import (
"github.com/Unknwon/cae/zip"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
+ "gopkg.in/ini.v1"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/bindata"
@@ -48,7 +49,7 @@ var (
Gitignores, Licenses, Readmes []string
// Maximum items per page in forks, watchers and stars of a repo
- ItemsPerPage = 54
+ ItemsPerPage = 40
)
func LoadRepoConfig() {
@@ -105,6 +106,7 @@ func NewRepoContext() {
if ver.LessThan(reqVer) {
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
}
+ log.Info("Git Version: %s", ver.String())
// Git requires setting user.name and user.email in order to commit changes.
for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
@@ -182,9 +184,11 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
}
func (repo *Repository) getOwner(e Engine) (err error) {
- if repo.Owner == nil {
- repo.Owner, err = getUserByID(e, repo.OwnerID)
+ if repo.Owner != nil {
+ return nil
}
+
+ repo.Owner, err = getUserByID(e, repo.OwnerID)
return err
}
@@ -297,7 +301,7 @@ func (repo *Repository) DescriptionHtml() template.HTML {
}
func (repo *Repository) LocalCopyPath() string {
- return path.Join(setting.RepoRootPath, "local", com.ToStr(repo.ID))
+ return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
}
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
@@ -316,7 +320,7 @@ func (repo *Repository) UpdateLocalCopy() error {
}
} else {
_, stderr, err := process.ExecDir(-1, localPath,
- fmt.Sprintf("UpdateLocalCopy(git pull): %s", repoPath), "git", "pull")
+ fmt.Sprintf("UpdateLocalCopy(git pull --all): %s", repoPath), "git", "pull", "--all")
if err != nil {
return fmt.Errorf("git pull: %v - %s", err, stderr)
}
@@ -325,6 +329,30 @@ func (repo *Repository) UpdateLocalCopy() error {
return nil
}
+// PatchPath returns corresponding patch file path of repository by given issue ID.
+func (repo *Repository) PatchPath(index int64) (string, error) {
+ if err := repo.GetOwner(); err != nil {
+ return "", err
+ }
+
+ return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil
+}
+
+// SavePatch saves patch data to corresponding location by given issue ID.
+func (repo *Repository) SavePatch(index int64, patch []byte) error {
+ patchPath, err := repo.PatchPath(index)
+ if err != nil {
+ return fmt.Errorf("PatchPath: %v", err)
+ }
+
+ os.MkdirAll(path.Dir(patchPath), os.ModePerm)
+ if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
+ return fmt.Errorf("WriteFile: %v", err)
+ }
+
+ return nil
+}
+
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
has, err := e.Get(&Repository{
OwnerID: u.Id,
@@ -352,11 +380,11 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) {
}
if setting.SSHPort != 22 {
- cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
+ cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.Name, repo.Name)
} else {
- cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
+ cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.Name, repo.Name)
}
- cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
+ cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.Name, repo.Name)
return cl, nil
}
@@ -453,13 +481,21 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
return nil
}
+type MigrateRepoOptions struct {
+ Name string
+ Description string
+ IsPrivate bool
+ IsMirror bool
+ RemoteAddr string
+}
+
// MigrateRepository migrates a existing repository from other project hosting.
-func MigrateRepository(u *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
+func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
repo, err := CreateRepository(u, CreateRepoOptions{
- Name: name,
- Description: desc,
- IsPrivate: private,
- IsMirror: mirror,
+ Name: opts.Name,
+ Description: opts.Description,
+ IsPrivate: opts.IsPrivate,
+ IsMirror: opts.IsMirror,
})
if err != nil {
return nil, err
@@ -469,7 +505,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(tmpDir, os.ModePerm)
- repoPath := RepoPath(u.Name, name)
+ repoPath := RepoPath(u.Name, opts.Name)
if u.IsOrganization() {
t, err := u.GetOwnerTeam()
@@ -482,8 +518,8 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
}
repo.IsBare = false
- if mirror {
- if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, url); err != nil {
+ if opts.IsMirror {
+ if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
return repo, err
}
repo.IsMirror = true
@@ -495,13 +531,24 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
// FIXME: this command could for both migrate and mirror
_, stderr, err := process.ExecTimeout(10*time.Minute,
fmt.Sprintf("MigrateRepository: %s", repoPath),
- "git", "clone", "--mirror", "--bare", "--quiet", url, repoPath)
+ "git", "clone", "--mirror", "--bare", "--quiet", opts.RemoteAddr, repoPath)
if err != nil {
return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
} else if err = createUpdateHook(repoPath); err != nil {
return repo, fmt.Errorf("create update hook: %v", err)
}
+ // Clean up mirror info which prevents "push --all".
+ configPath := filepath.Join(repoPath, "/config")
+ cfg, err := ini.Load(configPath)
+ if err != nil {
+ return repo, fmt.Errorf("open config file: %v", err)
+ }
+ cfg.DeleteSection("remote \"origin\"")
+ if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
+ return repo, fmt.Errorf("save config file: %v", err)
+ }
+
// Check if repository is empty.
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
if err != nil {
@@ -552,7 +599,7 @@ func createUpdateHook(repoPath string) error {
hookPath := path.Join(repoPath, "hooks/update")
os.MkdirAll(path.Dir(hookPath), os.ModePerm)
return ioutil.WriteFile(hookPath,
- []byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+appPath+"\"", setting.CustomConf)), 0777)
+ []byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf)), 0777)
}
type CreateRepoOptions struct {
@@ -863,9 +910,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
// Remove redundant collaborators.
- collaborators, err := repo.GetCollaborators()
+ collaborators, err := repo.getCollaborators(sess)
if err != nil {
- return fmt.Errorf("GetCollaborators: %v", err)
+ return fmt.Errorf("getCollaborators: %v", err)
}
// Dummy object.
@@ -901,9 +948,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
}
if newOwner.IsOrganization() {
- t, err := newOwner.GetOwnerTeam()
+ t, err := newOwner.getOwnerTeam(sess)
if err != nil {
- return fmt.Errorf("GetOwnerTeam: %v", err)
+ return fmt.Errorf("getOwnerTeam: %v", err)
} else if err = t.addRepository(sess, repo); err != nil {
return fmt.Errorf("add to owner team: %v", err)
}
@@ -1069,7 +1116,7 @@ func DeleteRepository(uid, repoID int64) error {
return err
} else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
return err
- } else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
+ } else if _, err = sess.Delete(&Release{RepoID: repoID}); err != nil {
return err
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil {
return err
@@ -1124,7 +1171,7 @@ func DeleteRepository(uid, repoID int64) error {
desc := fmt.Sprintf("delete repository files[%s]: %v", repoPath, err)
log.Warn(desc)
if err = CreateRepositoryNotice(desc); err != nil {
- log.Error(4, "add notice: %v", err)
+ log.Error(4, "CreateRepositoryNotice: %v", err)
}
}
@@ -1267,10 +1314,14 @@ func DeleteRepositoryArchives() error {
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
- if err := repo.GetOwner(); err != nil {
- return err
+ repoPath, err := repo.RepoPath()
+ if err != nil {
+ if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepositoryArchives[%d]: %v", repo.ID, err)); err2 != nil {
+ log.Error(4, "CreateRepositoryNotice: %v", err2)
+ }
+ return nil
}
- return os.RemoveAll(filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "archives"))
+ return os.RemoveAll(filepath.Join(repoPath, "archives"))
})
}
@@ -1279,10 +1330,14 @@ func RewriteRepositoryUpdateHook() error {
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
- if err := repo.GetOwner(); err != nil {
- return err
+ repoPath, err := repo.RepoPath()
+ if err != nil {
+ if err2 := CreateRepositoryNotice(fmt.Sprintf("RewriteRepositoryUpdateHook[%d]: %v", repo.ID, err)); err2 != nil {
+ log.Error(4, "CreateRepositoryNotice: %v", err2)
+ }
+ return nil
}
- return createUpdateHook(RepoPath(repo.Owner.Name, repo.Name))
+ return createUpdateHook(repoPath)
})
}
@@ -1449,6 +1504,12 @@ func CheckRepoStats() {
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?",
"user count 'num_repos'",
},
+ // Issue.NumComments
+ {
+ "SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)",
+ "UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?",
+ "issue count 'num_comments'",
+ },
}
for i := range checkers {
repoStatsCheck(checkers[i])
@@ -1635,25 +1696,21 @@ func WatchRepo(uid, repoId int64, watch bool) (err error) {
return watchRepo(x, uid, repoId, watch)
}
-func getWatchers(e Engine, rid int64) ([]*Watch, error) {
+func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
watches := make([]*Watch, 0, 10)
- err := e.Find(&watches, &Watch{RepoID: rid})
- return watches, err
+ return watches, e.Find(&watches, &Watch{RepoID: repoID})
}
// GetWatchers returns all watchers of given repository.
-func GetWatchers(rid int64) ([]*Watch, error) {
- return getWatchers(x, rid)
+func GetWatchers(repoID int64) ([]*Watch, error) {
+ return getWatchers(x, repoID)
}
-// Repository.GetWatchers returns all users watching given repository.
-func (repo *Repository) GetWatchers(offset int) ([]*User, error) {
- users := make([]*User, 0, 10)
- offset = (offset - 1) * ItemsPerPage
-
- err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
-
- return users, err
+// Repository.GetWatchers returns range of users watching given repository.
+func (repo *Repository) GetWatchers(page int) ([]*User, error) {
+ users := make([]*User, 0, ItemsPerPage)
+ return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
+ Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
}
func notifyWatchers(e Engine, act *Action) error {
@@ -1733,13 +1790,10 @@ func IsStaring(uid, repoId int64) bool {
return has
}
-func (repo *Repository) GetStars(offset int) ([]*User, error) {
- users := make([]*User, 0, 10)
- offset = (offset - 1) * ItemsPerPage
-
- err := x.Limit(ItemsPerPage, offset).Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
-
- return users, err
+func (repo *Repository) GetStargazers(page int) ([]*User, error) {
+ users := make([]*User, 0, ItemsPerPage)
+ return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
+ Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
}
// ___________ __
@@ -1811,9 +1865,6 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
}
func (repo *Repository) GetForks() ([]*Repository, error) {
- forks := make([]*Repository, 0, 10)
-
- err := x.Find(&forks, &Repository{ForkID: repo.ID})
-
- return forks, err
+ forks := make([]*Repository, 0, repo.NumForks)
+ return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
}
diff --git a/models/update.go b/models/update.go
index cbaf0e66..14e56ce8 100644
--- a/models/update.go
+++ b/models/update.go
@@ -10,17 +10,16 @@ import (
"os/exec"
"strings"
- "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/git"
"github.com/gogits/gogs/modules/log"
)
type UpdateTask struct {
- Id int64
- Uuid string `xorm:"index"`
+ ID int64 `xorm:"pk autoincr"`
+ UUID string `xorm:"index"`
RefName string
- OldCommitId string
- NewCommitId string
+ OldCommitID string
+ NewCommitID string
}
func AddUpdateTask(task *UpdateTask) error {
@@ -28,27 +27,29 @@ func AddUpdateTask(task *UpdateTask) error {
return err
}
-func GetUpdateTasksByUuid(uuid string) ([]*UpdateTask, error) {
+// GetUpdateTaskByUUID returns update task by given UUID.
+func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
task := &UpdateTask{
- Uuid: uuid,
+ UUID: uuid,
}
- tasks := make([]*UpdateTask, 0)
- err := x.Find(&tasks, task)
+ has, err := x.Get(task)
if err != nil {
return nil, err
+ } else if !has {
+ return nil, ErrUpdateTaskNotExist{uuid}
}
- return tasks, nil
+ return task, nil
}
-func DelUpdateTasksByUuid(uuid string) error {
- _, err := x.Delete(&UpdateTask{Uuid: uuid})
+func DeleteUpdateTaskByUUID(uuid string) error {
+ _, err := x.Delete(&UpdateTask{UUID: uuid})
return err
}
-func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
- isNew := strings.HasPrefix(oldCommitId, "0000000")
+func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
+ isNew := strings.HasPrefix(oldCommitID, "0000000")
if isNew &&
- strings.HasPrefix(newCommitId, "0000000") {
+ strings.HasPrefix(newCommitID, "0000000") {
return fmt.Errorf("old rev and new rev both 000000")
}
@@ -58,23 +59,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
gitUpdate.Dir = f
gitUpdate.Run()
- isDel := strings.HasPrefix(newCommitId, "0000000")
+ isDel := strings.HasPrefix(newCommitID, "0000000")
if isDel {
- log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
+ log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userID)
return nil
}
- repo, err := git.OpenRepository(f)
+ gitRepo, err := git.OpenRepository(f)
if err != nil {
return fmt.Errorf("runUpdate.Open repoId: %v", err)
}
- ru, err := GetUserByName(repoUserName)
+ user, err := GetUserByName(repoUserName)
if err != nil {
return fmt.Errorf("runUpdate.GetUserByName: %v", err)
}
- repos, err := GetRepositoryByName(ru.Id, repoName)
+ repo, err := GetRepositoryByName(user.Id, repoName)
if err != nil {
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
}
@@ -82,7 +83,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
// Push tags.
if strings.HasPrefix(refName, "refs/tags/") {
tagName := git.RefEndName(refName)
- tag, err := repo.GetTag(tagName)
+ tag, err := gitRepo.GetTag(tagName)
if err != nil {
log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
}
@@ -98,16 +99,16 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
actEmail = cmt.Committer.Email
}
- commit := &base.PushCommits{}
+ commit := &PushCommits{}
- if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
- repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
+ if err = CommitRepoAction(userID, user.Id, userName, actEmail,
+ repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return err
}
- newCommit, err := repo.GetCommit(newCommitId)
+ newCommit, err := gitRepo.GetCommit(newCommitID)
if err != nil {
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
}
@@ -117,12 +118,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
if isNew {
l, err = newCommit.CommitsBefore()
if err != nil {
- return fmt.Errorf("Find CommitsBefore erro: %v", err)
+ return fmt.Errorf("CommitsBefore: %v", err)
}
} else {
- l, err = newCommit.CommitsBeforeUntil(oldCommitId)
+ l, err = newCommit.CommitsBeforeUntil(oldCommitID)
if err != nil {
- return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
+ return fmt.Errorf("CommitsBeforeUntil: %v", err)
}
}
@@ -131,7 +132,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
}
// Push commits.
- commits := make([]*base.PushCommit, 0)
+ commits := make([]*PushCommit, 0)
var actEmail string
for e := l.Front(); e != nil; e = e.Next() {
commit := e.Value.(*git.Commit)
@@ -139,15 +140,15 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
actEmail = commit.Committer.Email
}
commits = append(commits,
- &base.PushCommit{commit.Id.String(),
+ &PushCommit{commit.ID.String(),
commit.Message(),
commit.Author.Email,
commit.Author.Name,
})
}
- if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
- repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
+ if err = CommitRepoAction(userID, user.Id, userName, actEmail,
+ repo.ID, repoUserName, repoName, refName, &PushCommits{l.Len(), commits, "", nil}, oldCommitID, newCommitID); err != nil {
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
}
return nil
diff --git a/models/user.go b/models/user.go
index 152ae428..3131a88d 100644
--- a/models/user.go
+++ b/models/user.go
@@ -14,6 +14,7 @@ import (
"image"
"image/jpeg"
_ "image/jpeg"
+ "image/png"
"os"
"path"
"path/filepath"
@@ -71,13 +72,14 @@ type User struct {
Created time.Time `xorm:"CREATED"`
Updated time.Time `xorm:"UPDATED"`
- // Remember visibility choice for convenience.
+ // Remember visibility choice for convenience, true for private
LastRepoVisibility bool
// Permissions.
- IsActive bool
- IsAdmin bool
- AllowGitHook bool
+ IsActive bool
+ IsAdmin bool
+ AllowGitHook bool
+ AllowImportLocal bool // Allow migrate repository by local path
// Avatar.
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
@@ -107,6 +109,22 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
}
}
+// HasForkedRepo checks if user has already forked a repository with given ID.
+func (u *User) HasForkedRepo(repoID int64) bool {
+ _, has := HasForkedRepo(u.Id, repoID)
+ return has
+}
+
+// CanEditGitHook returns true if user can edit Git hooks.
+func (u *User) CanEditGitHook() bool {
+ return u.IsAdmin || u.AllowGitHook
+}
+
+// CanImportLocal returns true if user can migrate repository by local path.
+func (u *User) CanImportLocal() bool {
+ return u.IsAdmin || u.AllowImportLocal
+}
+
// EmailAdresses is the list of all email addresses of a user. Can contain the
// primary email address, but is not obligatory
type EmailAddress struct {
@@ -242,14 +260,12 @@ func (u *User) ValidatePassword(passwd string) bool {
// UploadAvatar saves custom avatar for user.
// FIXME: split uploads to different subdirs in case we have massive users.
func (u *User) UploadAvatar(data []byte) error {
- u.UseCustomAvatar = true
-
img, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
- return err
+ return fmt.Errorf("Decode: %v", err)
}
- m := resize.Resize(234, 234, img, resize.NearestNeighbor)
+ m := resize.Resize(290, 290, img, resize.NearestNeighbor)
sess := x.NewSession()
defer sessionRelease(sess)
@@ -257,19 +273,20 @@ func (u *User) UploadAvatar(data []byte) error {
return err
}
- if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
- return err
+ u.UseCustomAvatar = true
+ if err = updateUser(sess, u); err != nil {
+ return fmt.Errorf("updateUser: %v", err)
}
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
fw, err := os.Create(u.CustomAvatarPath())
if err != nil {
- return err
+ return fmt.Errorf("Create: %v", err)
}
defer fw.Close()
- if err = jpeg.Encode(fw, m, nil); err != nil {
- return err
+ if err = png.Encode(fw, m); err != nil {
+ return fmt.Errorf("Encode: %v", err)
}
return sess.Commit()
@@ -717,9 +734,9 @@ func UserPath(userName string) string {
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
}
-func GetUserByKeyId(keyId int64) (*User, error) {
+func GetUserByKeyID(keyID int64) (*User, error) {
user := new(User)
- has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
+ has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user)
if err != nil {
return nil, err
} else if !has {
@@ -980,7 +997,7 @@ func GetUserByEmail(email string) (*User, error) {
return GetUserByID(emailAddress.UID)
}
- return nil, ErrUserNotExist{0, "email"}
+ return nil, ErrUserNotExist{0, email}
}
// SearchUserByName returns given number of users whose name contains keyword.
diff --git a/models/webhook.go b/models/webhook.go
index 75380d17..b4d7dc9c 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -13,6 +13,7 @@ import (
"sync"
"time"
+ "github.com/Unknwon/com"
"github.com/go-xorm/xorm"
api "github.com/gogits/go-gogs-client"
@@ -435,39 +436,65 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
return nil
}
-type hookQueue struct {
- // Make sure one repository only occur once in the queue.
- lock sync.Mutex
- repoIDs map[int64]bool
+// UniqueQueue represents a queue that guarantees only one instance of same ID is in the line.
+type UniqueQueue struct {
+ lock sync.Mutex
+ ids map[string]bool
- queue chan int64
+ queue chan string
}
-func (q *hookQueue) removeRepoID(id int64) {
+func (q *UniqueQueue) Queue() <-chan string {
+ return q.queue
+}
+
+func NewUniqueQueue(queueLength int) *UniqueQueue {
+ if queueLength <= 0 {
+ queueLength = 100
+ }
+
+ return &UniqueQueue{
+ ids: make(map[string]bool),
+ queue: make(chan string, queueLength),
+ }
+}
+
+func (q *UniqueQueue) Remove(id interface{}) {
q.lock.Lock()
defer q.lock.Unlock()
- delete(q.repoIDs, id)
+ delete(q.ids, com.ToStr(id))
}
-func (q *hookQueue) addRepoID(id int64) {
- q.lock.Lock()
- if q.repoIDs[id] {
- q.lock.Unlock()
+func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
+ newid := com.ToStr(id)
+
+ if q.Exist(id) {
return
}
- q.repoIDs[id] = true
+
+ q.lock.Lock()
+ q.ids[newid] = true
+ if fn != nil {
+ fn()
+ }
q.lock.Unlock()
- q.queue <- id
+ q.queue <- newid
}
-// AddRepoID adds repository ID to hook delivery queue.
-func (q *hookQueue) AddRepoID(id int64) {
- go q.addRepoID(id)
+func (q *UniqueQueue) Add(id interface{}) {
+ q.AddFunc(id, nil)
+}
+
+func (q *UniqueQueue) Exist(id interface{}) bool {
+ q.lock.Lock()
+ defer q.lock.Unlock()
+
+ return q.ids[com.ToStr(id)]
}
-var HookQueue *hookQueue
+var HookQueue = NewUniqueQueue(setting.Webhook.QueueLength)
-func deliverHook(t *HookTask) {
+func (t *HookTask) deliver() {
t.IsDelivered = true
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
@@ -549,12 +576,13 @@ func deliverHook(t *HookTask) {
}
// DeliverHooks checks and delivers undelivered hooks.
+// TODO: shoot more hooks at same time.
func DeliverHooks() {
tasks := make([]*HookTask, 0, 10)
x.Where("is_delivered=?", false).Iterate(new(HookTask),
func(idx int, bean interface{}) error {
t := bean.(*HookTask)
- deliverHook(t)
+ t.deliver()
tasks = append(tasks, t)
return nil
})
@@ -566,15 +594,10 @@ func DeliverHooks() {
}
}
- HookQueue = &hookQueue{
- lock: sync.Mutex{},
- repoIDs: make(map[int64]bool),
- queue: make(chan int64, setting.Webhook.QueueLength),
- }
-
// Start listening on new hook requests.
- for repoID := range HookQueue.queue {
- HookQueue.removeRepoID(repoID)
+ for repoID := range HookQueue.Queue() {
+ log.Trace("DeliverHooks[%v]: processing delivery hooks", repoID)
+ HookQueue.Remove(repoID)
tasks = make([]*HookTask, 0, 5)
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
@@ -582,9 +605,10 @@ func DeliverHooks() {
continue
}
for _, t := range tasks {
- deliverHook(t)
+ t.deliver()
if err := UpdateHookTask(t); err != nil {
- log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
+ log.Error(4, "UpdateHookTask[%d]: %v", t.ID, err)
+ continue
}
}
}