} | } | ||||
if requestedMode == models.ACCESS_MODE_WRITE { | if requestedMode == models.ACCESS_MODE_WRITE { | ||||
tasks, err := models.GetUpdateTasksByUuid(uuid) | |||||
task, err := models.GetUpdateTaskByUUID(uuid) | |||||
if err != nil { | if err != nil { | ||||
log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err) | |||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err) | |||||
} | } | ||||
for _, task := range tasks { | |||||
err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId, | |||||
user.Name, repoUserName, repoName, user.Id) | |||||
if err != nil { | |||||
log.GitLogger.Error(2, "Failed to update: %v", err) | |||||
} | |||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID, | |||||
user.Name, repoUserName, repoName, user.Id); err != nil { | |||||
log.GitLogger.Error(2, "Update: %v", err) | |||||
} | } | ||||
if err = models.DelUpdateTasksByUuid(uuid); err != nil { | |||||
log.GitLogger.Fatal(2, "DelUpdateTasksByUuid: %v", err) | |||||
if err = models.DeleteUpdateTaskByUUID(uuid); err != nil { | |||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err) | |||||
} | } | ||||
} | |||||
// Send deliver hook request. | |||||
reqURL := setting.AppUrl + repoUserName + "/" + repoName + "/hooks/trigger" | |||||
resp, err := httplib.Head(reqURL).Response() | |||||
if err == nil { | |||||
resp.Body.Close() | |||||
log.GitLogger.Trace("Trigger hook: %s", reqURL) | |||||
} else { | |||||
log.GitLogger.Error(2, "Fail to trigger hook: %v", err) | |||||
// Ask for running deliver hook and test pull request tasks. | |||||
reqURL := setting.AppUrl + repoUserName + "/" + repoName + "/tasks/trigger?branch=" + | |||||
strings.TrimPrefix(task.RefName, "refs/heads/") | |||||
log.GitLogger.Trace("Trigger task: %s", reqURL) | |||||
resp, err := httplib.Head(reqURL).Response() | |||||
if err == nil { | |||||
resp.Body.Close() | |||||
if resp.StatusCode/100 != 2 { | |||||
log.GitLogger.Error(2, "Fail to trigger task: not 2xx response code") | |||||
} | |||||
} else { | |||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err) | |||||
} | |||||
} | } | ||||
// Update user key activity. | // Update user key activity. |
uuid := os.Getenv("uuid") | uuid := os.Getenv("uuid") | ||||
task := models.UpdateTask{ | task := models.UpdateTask{ | ||||
Uuid: uuid, | |||||
UUID: uuid, | |||||
RefName: args[0], | RefName: args[0], | ||||
OldCommitId: args[1], | |||||
NewCommitId: args[2], | |||||
OldCommitID: args[1], | |||||
NewCommitID: args[2], | |||||
} | } | ||||
if err := models.AddUpdateTask(&task); err != nil { | if err := models.AddUpdateTask(&task); err != nil { | ||||
log.GitLogger.Fatal(2, err.Error()) | |||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err) | |||||
} | } | ||||
} | } |
}, ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef()) | }, ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef()) | ||||
m.Group("/:reponame", func() { | m.Group("/:reponame", func() { | ||||
m.Any("/*", ignSignInAndCsrf, repo.Http) | |||||
m.Head("/hooks/trigger", repo.TriggerHook) | |||||
m.Any("/*", ignSignInAndCsrf, repo.HTTP) | |||||
m.Head("/tasks/trigger", repo.TriggerTask) | |||||
}) | }) | ||||
}) | }) | ||||
// ***** END: Repository ***** | // ***** END: Repository ***** |
pulls.merged = Merged | pulls.merged = Merged | ||||
pulls.has_merged = This pull request has been merged successfully! | pulls.has_merged = This pull request has been merged successfully! | ||||
pulls.data_broken = Data of this pull request has been broken due to deletion of fork information. | pulls.data_broken = Data of this pull request has been broken due to deletion of fork information. | ||||
pulls.is_checking = The conflit checking is still in progress, please refresh page in few moments. | |||||
pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request. | pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request. | ||||
pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits. | pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits. | ||||
pulls.cannot_auto_merge_helper = Please use command line tool to solve it. | pulls.cannot_auto_merge_helper = Please use command line tool to solve it. |
"github.com/gogits/gogs/modules/setting" | "github.com/gogits/gogs/modules/setting" | ||||
) | ) | ||||
const APP_VER = "0.6.16.1023 Beta" | |||||
const APP_VER = "0.6.17.1024 Beta" | |||||
func init() { | func init() { | ||||
runtime.GOMAXPROCS(runtime.NumCPU()) | runtime.GOMAXPROCS(runtime.NumCPU()) |
return numOpen, numClosed | return numOpen, numClosed | ||||
} | } | ||||
// updateIssue updates all fields of given issue. | |||||
func updateIssue(e Engine, issue *Issue) error { | func updateIssue(e Engine, issue *Issue) error { | ||||
_, err := e.Id(issue.ID).AllCols().Update(issue) | _, err := e.Id(issue.ID).AllCols().Update(issue) | ||||
return err | return err | ||||
} | } | ||||
// updateIssueCols update specific fields of given 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 { | func updateIssueCols(e Engine, issue *Issue, cols ...string) error { | ||||
_, err := e.Id(issue.ID).Cols(cols...).Update(issue) | _, err := e.Id(issue.ID).Cols(cols...).Update(issue) | ||||
return err | return err | ||||
} | } | ||||
// UpdateIssue updates information of issue. | |||||
func UpdateIssue(issue *Issue) error { | |||||
return updateIssue(x, issue) | |||||
} | |||||
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error { | func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error { | ||||
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID) | _, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID) | ||||
return err | return err |
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"io/ioutil" | |||||
"os" | "os" | ||||
"path" | "path" | ||||
"strings" | "strings" | ||||
"github.com/gogits/gogs/modules/git" | "github.com/gogits/gogs/modules/git" | ||||
"github.com/gogits/gogs/modules/log" | "github.com/gogits/gogs/modules/log" | ||||
"github.com/gogits/gogs/modules/process" | "github.com/gogits/gogs/modules/process" | ||||
"github.com/gogits/gogs/modules/setting" | |||||
) | ) | ||||
type PullRequestType int | type PullRequestType int | ||||
Issue *Issue `xorm:"-"` | Issue *Issue `xorm:"-"` | ||||
Index int64 | Index int64 | ||||
HeadRepoID int64 | |||||
HeadRepo *Repository `xorm:"-"` | |||||
BaseRepoID int64 | |||||
HeadUserName string | |||||
HeadBranch string | |||||
BaseBranch string | |||||
MergeBase string `xorm:"VARCHAR(40)"` | |||||
MergedCommitID string `xorm:"VARCHAR(40)"` | |||||
HeadRepoID int64 | |||||
HeadRepo *Repository `xorm:"-"` | |||||
BaseRepoID int64 | |||||
BaseRepo *Repository `xorm:"-"` | |||||
HeadUserName string | |||||
HeadBranch string | |||||
BaseBranch string | |||||
MergeBase string `xorm:"VARCHAR(40)"` | |||||
HasMerged bool | |||||
Merged time.Time | |||||
MergerID int64 | |||||
Merger *User `xorm:"-"` | |||||
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. | // Note: don't try to get Pull because will end up recursive querying. | ||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) { | ||||
var err error | |||||
switch colName { | 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": | case "merged": | ||||
if !pr.HasMerged { | if !pr.HasMerged { | ||||
return | return | ||||
} | } | ||||
} | } | ||||
func (pr *PullRequest) GetHeadRepo() (err error) { | |||||
pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID) | |||||
if err != nil && !IsErrRepoNotExist(err) { | |||||
return fmt.Errorf("GetRepositoryByID (head): %v", err) | |||||
} | |||||
return nil | |||||
} | |||||
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. | // CanAutoMerge returns true if this pull request can be merged automatically. | ||||
func (pr *PullRequest) CanAutoMerge() bool { | func (pr *PullRequest) CanAutoMerge() bool { | ||||
return pr.Status == PULL_REQUEST_STATUS_MERGEABLE | return pr.Status == PULL_REQUEST_STATUS_MERGEABLE | ||||
} | } | ||||
if err = pr.Issue.changeStatus(sess, doer, true); err != nil { | if err = pr.Issue.changeStatus(sess, doer, true); err != nil { | ||||
return fmt.Errorf("Pull.changeStatus: %v", err) | |||||
return fmt.Errorf("Issue.changeStatus: %v", err) | |||||
} | |||||
if err = pr.GetHeadRepo(); err != nil { | |||||
return fmt.Errorf("GetHeadRepo: %v", err) | |||||
} | } | ||||
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name) | ||||
return fmt.Errorf("git checkout: %s", stderr) | return fmt.Errorf("git checkout: %s", stderr) | ||||
} | } | ||||
// Pull commits. | |||||
// 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, | if _, stderr, err = process.ExecDir(-1, tmpBasePath, | ||||
fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath), | |||||
"git", "pull", headRepoPath, pr.HeadBranch); err != nil { | |||||
return fmt.Errorf("git pull[%s / %s -> %s]: %s", headRepoPath, pr.HeadBranch, tmpBasePath, stderr) | |||||
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. | // Push back to upstream. | ||||
return sess.Commit() | return sess.Commit() | ||||
} | } | ||||
// testPatch checks if patch can be merged to base repository without conflit. | |||||
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) | |||||
} | |||||
log.Trace("PullRequest[%d].testPatch(patchPath): %s", pr.ID, patchPath) | |||||
if err := pr.BaseRepo.UpdateLocalCopy(); err != nil { | |||||
return fmt.Errorf("UpdateLocalCopy: %v", err) | |||||
} | |||||
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 { | |||||
if strings.Contains(stderr, "patch does not apply") { | |||||
log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID) | |||||
pr.Status = PULL_REQUEST_STATUS_CONFLICT | |||||
} else { | |||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr) | |||||
} | |||||
} | |||||
return nil | |||||
} | |||||
// NewPullRequest creates new pull request with labels for repository. | // 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) { | func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) { | ||||
sess := x.NewSession() | sess := x.NewSession() | ||||
return err | return err | ||||
} | } | ||||
// Test apply patch. | |||||
if err = repo.UpdateLocalCopy(); err != nil { | |||||
return fmt.Errorf("UpdateLocalCopy: %v", err) | |||||
if err = repo.SavePatch(pr.Index, patch); err != nil { | |||||
return fmt.Errorf("SavePatch: %v", err) | |||||
} | } | ||||
repoPath, err := repo.RepoPath() | |||||
if err != nil { | |||||
return fmt.Errorf("RepoPath: %v", err) | |||||
pr.BaseRepo = repo | |||||
if err = pr.testPatch(); err != nil { | |||||
return fmt.Errorf("testPatch: %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.Status = PULL_REQUEST_STATUS_MERGEABLE | |||||
_, 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.Status = PULL_REQUEST_STATUS_CONFLICT | |||||
} else { | |||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr) | |||||
} | |||||
if pr.Status == PULL_REQUEST_STATUS_CHECKING { | |||||
pr.Status = PULL_REQUEST_STATUS_MERGEABLE | |||||
} | } | ||||
pr.IssueID = pull.ID | pr.IssueID = pull.ID | ||||
// by given head/base and repo/branch. | // by given head/base and repo/branch. | ||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) { | ||||
pr := new(PullRequest) | 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=?", | 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). | headRepoID, headBranch, baseRepoID, baseBranch, false, false). | ||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr) | Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr) | ||||
return pr, nil | 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(headRepoID int64, headBranch 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=?", | |||||
headRepoID, headBranch, 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. | // GetPullRequestByIssueID returns pull request by given issue ID. | ||||
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) { | func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) { | ||||
pr := &PullRequest{ | pr := &PullRequest{ | ||||
} | } | ||||
return pr, nil | 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) | |||||
// 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) | |||||
} | |||||
} | |||||
} | |||||
// AddTestPullRequestTask adds new test tasks by given head repository and head branch, | |||||
// and generate new patch for testing as needed. | |||||
func AddTestPullRequestTask(headRepoID int64, headBranch string) { | |||||
log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", headRepoID, headBranch) | |||||
prs, err := GetUnmergedPullRequestsByHeadInfo(headRepoID, headBranch) | |||||
if err != nil { | |||||
log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", headRepoID, headBranch, err) | |||||
return | |||||
} | |||||
for _, pr := range prs { | |||||
log.Trace("AddTestPullRequestTask[%d]: composing new test task", pr.ID) | |||||
if err := pr.GetHeadRepo(); err != nil { | |||||
log.Error(4, "GetHeadRepo[%d]: %v", pr.ID, err) | |||||
continue | |||||
} else if pr.HeadRepo == nil { | |||||
log.Trace("AddTestPullRequestTask[%d]: ignored cruppted data", pr.ID) | |||||
continue | |||||
} | |||||
if err := pr.GetBaseRepo(); err != nil { | |||||
log.Error(4, "GetBaseRepo[%d]: %v", pr.ID, err) | |||||
continue | |||||
} | |||||
headRepoPath, err := pr.HeadRepo.RepoPath() | |||||
if err != nil { | |||||
log.Error(4, "HeadRepo.RepoPath[%d]: %v", pr.ID, err) | |||||
continue | |||||
} | |||||
headGitRepo, err := git.OpenRepository(headRepoPath) | |||||
if err != nil { | |||||
log.Error(4, "OpenRepository[%d]: %v", pr.ID, err) | |||||
continue | |||||
} | |||||
// Generate patch. | |||||
patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch) | |||||
if err != nil { | |||||
log.Error(4, "GetPatch[%d]: %v", pr.ID, err) | |||||
continue | |||||
} | |||||
if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil { | |||||
log.Error(4, "BaseRepo.SavePatch[%d]: %v", pr.ID, err) | |||||
continue | |||||
} | |||||
if !PullRequestQueue.Exist(pr.ID) { | |||||
go func() { | |||||
PullRequestQueue.Add(pr.ID) | |||||
pr.Status = PULL_REQUEST_STATUS_CHECKING | |||||
if err = pr.UpdateCols("status"); err != nil { | |||||
log.Error(5, "AddTestPullRequestTask.UpdateCols[%d].(add to queue): %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() | |||||
} |
} | } | ||||
func (repo *Repository) getOwner(e Engine) (err error) { | 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 | return err | ||||
} | } | ||||
return nil | 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) { | func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { | ||||
has, err := e.Get(&Repository{ | has, err := e.Get(&Repository{ | ||||
OwnerID: u.Id, | OwnerID: u.Id, |
) | ) | ||||
type UpdateTask struct { | type UpdateTask struct { | ||||
Id int64 | |||||
Uuid string `xorm:"index"` | |||||
ID int64 `xorm:"pk autoincr"` | |||||
UUID string `xorm:"index"` | |||||
RefName string | RefName string | ||||
OldCommitId string | |||||
NewCommitId string | |||||
OldCommitID string | |||||
NewCommitID string | |||||
} | } | ||||
func AddUpdateTask(task *UpdateTask) error { | func AddUpdateTask(task *UpdateTask) error { | ||||
return err | return err | ||||
} | } | ||||
func GetUpdateTasksByUuid(uuid string) ([]*UpdateTask, error) { | |||||
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) { | |||||
task := &UpdateTask{ | task := &UpdateTask{ | ||||
Uuid: uuid, | |||||
UUID: uuid, | |||||
} | } | ||||
tasks := make([]*UpdateTask, 0) | |||||
err := x.Find(&tasks, task) | |||||
has, err := x.Get(task) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} else if !has { | |||||
return nil, fmt.Errorf("task does not exist: %s", 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 | return err | ||||
} | } | ||||
"sync" | "sync" | ||||
"time" | "time" | ||||
"github.com/Unknwon/com" | |||||
"github.com/go-xorm/xorm" | "github.com/go-xorm/xorm" | ||||
api "github.com/gogits/go-gogs-client" | api "github.com/gogits/go-gogs-client" | ||||
return nil | 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() | q.lock.Lock() | ||||
defer q.lock.Unlock() | 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) Add(id interface{}) { | |||||
newid := com.ToStr(id) | |||||
if q.Exist(id) { | |||||
return | return | ||||
} | } | ||||
q.repoIDs[id] = true | |||||
q.lock.Lock() | |||||
q.ids[newid] = true | |||||
q.lock.Unlock() | 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) 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 | t.IsDelivered = true | ||||
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second | timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second | ||||
} | } | ||||
// DeliverHooks checks and delivers undelivered hooks. | // DeliverHooks checks and delivers undelivered hooks. | ||||
// TODO: shoot more hooks at same time. | |||||
func DeliverHooks() { | func DeliverHooks() { | ||||
tasks := make([]*HookTask, 0, 10) | tasks := make([]*HookTask, 0, 10) | ||||
x.Where("is_delivered=?", false).Iterate(new(HookTask), | x.Where("is_delivered=?", false).Iterate(new(HookTask), | ||||
func(idx int, bean interface{}) error { | func(idx int, bean interface{}) error { | ||||
t := bean.(*HookTask) | t := bean.(*HookTask) | ||||
deliverHook(t) | |||||
t.deliver() | |||||
tasks = append(tasks, t) | tasks = append(tasks, t) | ||||
return nil | return nil | ||||
}) | }) | ||||
} | } | ||||
} | } | ||||
HookQueue = &hookQueue{ | |||||
lock: sync.Mutex{}, | |||||
repoIDs: make(map[int64]bool), | |||||
queue: make(chan int64, setting.Webhook.QueueLength), | |||||
} | |||||
// Start listening on new hook requests. | // 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) | tasks = make([]*HookTask, 0, 5) | ||||
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { | if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { | ||||
continue | continue | ||||
} | } | ||||
for _, t := range tasks { | for _, t := range tasks { | ||||
deliverHook(t) | |||||
t.deliver() | |||||
if err := UpdateHookTask(t); err != nil { | 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 | |||||
} | } | ||||
} | } | ||||
} | } |
} | } | ||||
// Repository settings. | // Repository settings. | ||||
Repository struct { | |||||
PullRequestQueueLength int | |||||
} | |||||
RepoRootPath string | RepoRootPath string | ||||
ScriptType string | ScriptType string | ||||
AnsiCharset string | AnsiCharset string | ||||
homeDir = strings.Replace(homeDir, "\\", "/", -1) | homeDir = strings.Replace(homeDir, "\\", "/", -1) | ||||
sec = Cfg.Section("repository") | sec = Cfg.Section("repository") | ||||
Repository.PullRequestQueueLength = 10000 | |||||
RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gogs-repositories")) | RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gogs-repositories")) | ||||
forcePathSeparator(RepoRootPath) | forcePathSeparator(RepoRootPath) | ||||
if !filepath.IsAbs(RepoRootPath) { | if !filepath.IsAbs(RepoRootPath) { |
.ui .text.purple { | .ui .text.purple { | ||||
color: #6e5494!important; | color: #6e5494!important; | ||||
} | } | ||||
.ui .text.yellow { | |||||
color: #FBBD08!important; | |||||
} | |||||
.ui .text.left { | .ui .text.left { | ||||
text-align: left!important; | text-align: left!important; | ||||
} | } | ||||
border: solid 1px #ccc; | border: solid 1px #ccc; | ||||
border-bottom-color: #bbb; | border-bottom-color: #bbb; | ||||
border-radius: 3px; | border-radius: 3px; | ||||
box-shadow: inset 0 -1px 0 #bbbbbb; | |||||
box-shadow: inset 0 -1px 0 #bbb; | |||||
} | } | ||||
.markdown .csv-data td, | .markdown .csv-data td, | ||||
.markdown .csv-data th { | .markdown .csv-data th { |
&.purple { | &.purple { | ||||
color: #6e5494!important; | color: #6e5494!important; | ||||
} | } | ||||
&.yellow { | |||||
color: #FBBD08!important; | |||||
} | |||||
&.left { | &.left { | ||||
text-align: left!important; | text-align: left!important; |
models.HasEngine = true | models.HasEngine = true | ||||
cron.NewContext() | cron.NewContext() | ||||
models.InitDeliverHooks() | models.InitDeliverHooks() | ||||
models.InitTestPullRequests() | |||||
log.NewGitLogger(path.Join(setting.LogRootPath, "http.log")) | log.NewGitLogger(path.Join(setting.LogRootPath, "http.log")) | ||||
} | } | ||||
if models.EnableSQLite3 { | if models.EnableSQLite3 { |
ctx.HTML(401, base.TplName("status/401")) | ctx.HTML(401, base.TplName("status/401")) | ||||
} | } | ||||
func Http(ctx *middleware.Context) { | |||||
func HTTP(ctx *middleware.Context) { | |||||
username := ctx.Params(":username") | username := ctx.Params(":username") | ||||
reponame := ctx.Params(":reponame") | reponame := ctx.Params(":reponame") | ||||
if strings.HasSuffix(reponame, ".git") { | if strings.HasSuffix(reponame, ".git") { | ||||
// FIXME: handle error. | // FIXME: handle error. | ||||
if err = models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id); err == nil { | if err = models.Update(refName, oldCommitId, newCommitId, authUsername, username, reponame, authUser.Id); err == nil { | ||||
models.HookQueue.AddRepoID(repo.ID) | |||||
go models.HookQueue.Add(repo.ID) | |||||
go models.AddTestPullRequestTask(repo.ID, strings.TrimPrefix(refName, "refs/heads/")) | |||||
} | } | ||||
} | } |
import ( | import ( | ||||
"container/list" | "container/list" | ||||
"errors" | |||||
"path" | "path" | ||||
"strings" | "strings" | ||||
if err = issue.GetPoster(); err != nil { | if err = issue.GetPoster(); err != nil { | ||||
ctx.Handle(500, "GetPoster", err) | ctx.Handle(500, "GetPoster", err) | ||||
return nil | return nil | ||||
} else if issue.GetHeadRepo(); err != nil { | |||||
ctx.Handle(500, "GetHeadRepo", err) | |||||
return nil | |||||
} | } | ||||
if ctx.IsSigned { | if ctx.IsSigned { | ||||
var err error | var err error | ||||
if err = pull.GetMerger(); err != nil { | |||||
ctx.Handle(500, "GetMerger", err) | |||||
return | |||||
} | |||||
ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch | ctx.Data["HeadTarget"] = pull.HeadUserName + "/" + pull.HeadBranch | ||||
ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch | ctx.Data["BaseTarget"] = ctx.Repo.Owner.Name + "/" + pull.BaseBranch | ||||
headGitRepo *git.Repository | headGitRepo *git.Repository | ||||
err error | err error | ||||
) | ) | ||||
if err = pull.GetHeadRepo(); err != nil { | |||||
ctx.Handle(500, "GetHeadRepo", err) | |||||
return nil | |||||
} | |||||
if pull.HeadRepo != nil { | if pull.HeadRepo != nil { | ||||
headRepoPath, err := pull.HeadRepo.RepoPath() | headRepoPath, err := pull.HeadRepo.RepoPath() | ||||
if err != nil { | if err != nil { | ||||
log.Trace("Pull request created: %d/%d", repo.ID, pull.ID) | log.Trace("Pull request created: %d/%d", repo.ID, pull.ID) | ||||
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pull.Index)) | ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pull.Index)) | ||||
} | } | ||||
func TriggerTask(ctx *middleware.Context) { | |||||
_, repo := parseOwnerAndRepo(ctx) | |||||
if ctx.Written() { | |||||
return | |||||
} | |||||
branch := ctx.Query("branch") | |||||
if len(branch) == 0 { | |||||
ctx.Handle(422, "TriggerTask", errors.New("branch is empty")) | |||||
return | |||||
} | |||||
log.Trace("TriggerTask[%d].(new request): %s", repo.ID, branch) | |||||
go models.HookQueue.Add(repo.ID) | |||||
go models.AddTestPullRequestTask(repo.ID, branch) | |||||
ctx.Status(202) | |||||
} |
}) | }) | ||||
} | } | ||||
func TriggerHook(ctx *middleware.Context) { | |||||
u, err := models.GetUserByName(ctx.Params(":username")) | |||||
func parseOwnerAndRepo(ctx *middleware.Context) (*models.User, *models.Repository) { | |||||
owner, err := models.GetUserByName(ctx.Params(":username")) | |||||
if err != nil { | if err != nil { | ||||
if models.IsErrUserNotExist(err) { | if models.IsErrUserNotExist(err) { | ||||
ctx.Handle(404, "GetUserByName", err) | ctx.Handle(404, "GetUserByName", err) | ||||
} else { | } else { | ||||
ctx.Handle(500, "GetUserByName", err) | ctx.Handle(500, "GetUserByName", err) | ||||
} | } | ||||
return | |||||
return nil, nil | |||||
} | } | ||||
repo, err := models.GetRepositoryByName(u.Id, ctx.Params(":reponame")) | |||||
repo, err := models.GetRepositoryByName(owner.Id, ctx.Params(":reponame")) | |||||
if err != nil { | if err != nil { | ||||
if models.IsErrRepoNotExist(err) { | if models.IsErrRepoNotExist(err) { | ||||
ctx.Handle(404, "GetRepositoryByName", err) | ctx.Handle(404, "GetRepositoryByName", err) | ||||
} else { | } else { | ||||
ctx.Handle(500, "GetRepositoryByName", err) | ctx.Handle(500, "GetRepositoryByName", err) | ||||
} | } | ||||
return | |||||
return nil, nil | |||||
} | } | ||||
models.HookQueue.AddRepoID(repo.ID) | |||||
ctx.Status(200) | |||||
return owner, repo | |||||
} | } | ||||
func GitHooks(ctx *middleware.Context) { | func GitHooks(ctx *middleware.Context) { |
0.6.16.1023 Beta | |||||
0.6.17.1024 Beta |
{{if .Issue.IsPull}} | {{if .Issue.IsPull}} | ||||
<div class="comment merge box"> | <div class="comment merge box"> | ||||
<a class="avatar text {{if .Issue.HasMerged}}purple{{else if .Issue.IsClosed}}grey{{else if and .Issue.CanAutoMerge (not .IsPullReuqestBroken)}}green{{else}}red{{end}}"> | |||||
<a class="avatar text | |||||
{{if .Issue.HasMerged}}purple | |||||
{{else if .Issue.IsClosed}}grey | |||||
{{else if .IsPullReuqestBroken}}red | |||||
{{else if .Issue.IsChecking}}yellow | |||||
{{else if .Issue.CanAutoMerge}}green | |||||
{{else}}red{{end}}"> | |||||
<span class="mega-octicon octicon-git-merge"></span> | <span class="mega-octicon octicon-git-merge"></span> | ||||
</a> | </a> | ||||
<div class="content"> | <div class="content"> | ||||
<span class="octicon octicon-x"></span> | <span class="octicon octicon-x"></span> | ||||
{{$.i18n.Tr "repo.pulls.data_broken"}} | {{$.i18n.Tr "repo.pulls.data_broken"}} | ||||
</div> | </div> | ||||
{{else if .Issue.IsChecking}} | |||||
<div class="item text yellow"> | |||||
<span class="octicon octicon-sync"></span> | |||||
{{$.i18n.Tr "repo.pulls.is_checking"}} | |||||
</div> | |||||
{{else if .Issue.CanAutoMerge}} | {{else if .Issue.CanAutoMerge}} | ||||
<div class="item text green"> | <div class="item text green"> | ||||
<span class="octicon octicon-check"></span> | <span class="octicon octicon-check"></span> |