diff --git a/models/action.go b/models/action.go index ead34dbac2d81..4f6d7d91fedc5 100644 --- a/models/action.go +++ b/models/action.go @@ -14,15 +14,14 @@ import ( "time" "unicode" - "github.com/Unknwon/com" - "github.com/go-xorm/builder" - "code.gitea.io/git" - api "code.gitea.io/sdk/gitea" - "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/sdk/gitea" + + "github.com/Unknwon/com" + "github.com/go-xorm/builder" ) // ActionType represents the type of an action. @@ -549,6 +548,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { isNewBranch := false opType := ActionCommitRepo + // Check it's tag push or branch. if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { opType = ActionPushTag @@ -572,6 +572,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { } } + var commentCommits = opts.Commits.Commits if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] } @@ -629,6 +630,49 @@ func CommitRepoAction(opts CommitRepoActionOptions) error { } } + // if there is a pull request from this branch, then update pull request + prs, err := GetUnmergedPullRequestsByHeadInfo(repo.ID, refName) + if err != nil { + return fmt.Errorf("GetUnmergedPullRequestsByHeadInfo: %v", err) + } + + if len(prs) > 0 { + var isForcePush bool + + // detect force push + if !isNewBranch && git.EmptySHA != opts.OldCommitID { + output, err := git.NewCommand("rev-list", opts.OldCommitID, "^"+opts.NewCommitID).RunInDir(repo.RepoPath()) + if err != nil { + return fmt.Errorf("rev-list: %v", err) + } + isForcePush = len(output) > 0 + } + + if err := PullRequestList(prs).LoadAttributes(); err != nil { + return fmt.Errorf("PullRequestList.LoadAttributes: %v", err) + } + + issues := make([]*Issue, 0, len(prs)) + lastCommitID := commentCommits[len(commentCommits)-1].Sha1 + for _, pr := range prs { + pr.LastCommitID = lastCommitID + if err := pr.UpdateCols("last_commit_id"); err != nil { + return fmt.Errorf("UpdateCols: %v", err) + } + + pr.Issue.PullRequest = pr + issues = append(issues, pr.Issue) + } + + if _, err := IssueList(issues).LoadRepositories(); err != nil { + return fmt.Errorf("IssueList.LoadRepositories: %v", err) + } + + if err := CreatePullPushComments(pusher, issues, commentCommits, isForcePush); err != nil { + return fmt.Errorf("CreatePullPushComments: %v", err) + } + } + case ActionDeleteBranch: // Delete Branch isHookEventPush = true diff --git a/models/issue_comment.go b/models/issue_comment.go index 34c0ecdce5565..f015d0906a81e 100644 --- a/models/issue_comment.go +++ b/models/issue_comment.go @@ -9,14 +9,15 @@ import ( "strings" "time" + "code.gitea.io/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/sdk/gitea" + "github.com/Unknwon/com" "github.com/go-xorm/builder" "github.com/go-xorm/xorm" - - api "code.gitea.io/sdk/gitea" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup" ) // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference. @@ -30,36 +31,38 @@ const ( // Enumerate all the comment types const ( // Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0) - CommentTypeComment CommentType = iota - CommentTypeReopen - CommentTypeClose + CommentTypeComment CommentType = iota // 0 + CommentTypeReopen // 1 + CommentTypeClose // 2 // References. - CommentTypeIssueRef + CommentTypeIssueRef // 3 // Reference from a commit (not part of a pull request) - CommentTypeCommitRef + CommentTypeCommitRef // 4 // Reference from a comment - CommentTypeCommentRef + CommentTypeCommentRef // 5 // Reference from a pull request - CommentTypePullRef + CommentTypePullRef // 6 // Labels changed - CommentTypeLabel + CommentTypeLabel // 7 // Milestone changed - CommentTypeMilestone + CommentTypeMilestone // 8 // Assignees changed - CommentTypeAssignees + CommentTypeAssignees // 9 // Change Title - CommentTypeChangeTitle + CommentTypeChangeTitle // 10 // Delete Branch - CommentTypeDeleteBranch + CommentTypeDeleteBranch // 11 // Start a stopwatch for time tracking - CommentTypeStartTracking + CommentTypeStartTracking // 12 // Stop a stopwatch for time tracking - CommentTypeStopTracking + CommentTypeStopTracking // 13 // Add time manual for time tracking - CommentTypeAddTimeManual + CommentTypeAddTimeManual // 14 // Cancel a stopwatch for time tracking - CommentTypeCancelTracking + CommentTypeCancelTracking // 15 + // Push commit to a pull request + CommentTypePullPushCommit // 16 ) // CommentTag defines comment tag type @@ -77,9 +80,10 @@ const ( type Comment struct { ID int64 `xorm:"pk autoincr"` Type CommentType - PosterID int64 `xorm:"INDEX"` - Poster *User `xorm:"-"` - IssueID int64 `xorm:"INDEX"` + PosterID int64 `xorm:"INDEX"` + Poster *User `xorm:"-"` + IssueID int64 `xorm:"INDEX"` + Issue *Issue `xorm:"-"` LabelID int64 Label *Label `xorm:"-"` OldMilestoneID int64 @@ -104,7 +108,8 @@ type Comment struct { UpdatedUnix int64 `xorm:"INDEX updated"` // Reference issue in commit message - CommitSHA string `xorm:"VARCHAR(40)"` + CommitSHA string `xorm:"VARCHAR(40)"` + CommitStatus *CommitStatus `xorm:"-"` Attachments []*Attachment `xorm:"-"` @@ -181,6 +186,14 @@ func (c *Comment) PRURL() string { return issue.HTMLURL() } +// TargetURL returns the main link on this comment +func (c *Comment) TargetURL() string { + if c.Type == CommentTypePullPushCommit { + return fmt.Sprintf("%s/%s/commit/%s", setting.AppSubURL, c.Issue.Repo.FullName(), c.CommitSHA) + } + return "" +} + // APIFormat converts a Comment to the api.Comment format func (c *Comment) APIFormat() *api.Comment { return &api.Comment{ @@ -264,6 +277,25 @@ func (c *Comment) LoadAssignees() error { return nil } +// LoadCommitStatus if comment.Type is CommentTypePullPushCommit, then load commit status +func (c *Comment) LoadCommitStatus() error { + if c.CommitStatus != nil { + return nil + } + + issue, err := getIssueByID(x, c.IssueID) + if err != nil { + return err + } + commitStatuses, err := GetLatestCommitStatus(issue.RepoID, c.CommitSHA, 0) + if err != nil { + return err + } + + c.CommitStatus = CalcCommitStatus(commitStatuses) + return nil +} + // MailParticipants sends new comment emails to repository watchers // and mentioned people. func (c *Comment) MailParticipants(e Engine, opType ActionType, issue *Issue) (err error) { @@ -566,6 +598,141 @@ func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commi return err } +// ClearPullPushComment clear all the push commit on issue since fore push +func ClearPullPushComment(issue *Issue) error { + _, err := x.Delete(&Comment{ + Type: CommentTypePullPushCommit, + IssueID: issue.ID, + }) + return err +} + +func createPullPushCommentsNonForcePush(doer *User, issues []*Issue, commits []*PushCommit) error { + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + for _, issue := range issues { + for i := len(commits) - 1; i >= 0; i-- { + _, err := createComment(sess, &CreateCommentOptions{ + Type: CommentTypePullPushCommit, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + CommitSHA: commits[i].Sha1, + Content: commits[i].Message, + }) + if err != nil { + return err + } + } + } + + return sess.Commit() +} + +// CreatePullPushComments creates commit comments when push commits to a pull request. +func CreatePullPushComments(doer *User, issues []*Issue, commits []*PushCommit, isForcePush bool) error { + if !isForcePush { + return createPullPushCommentsNonForcePush(doer, issues, commits) + } + + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + var commitIDCache = make(map[string]string) + var ok bool + for _, issue := range issues { + var commitID string + var key = fmt.Sprintf("%s/%s", issue.Repo.RepoPath(), issue.PullRequest.BaseBranch) + if commitID, ok = commitIDCache[key]; !ok { + gitRepo, err := git.OpenRepository(issue.Repo.RepoPath()) + if err != nil { + return fmt.Errorf("OpenRepository: %v", err) + } + commit, err := gitRepo.GetBranchCommit(issue.PullRequest.BaseBranch) + if err != nil { + return fmt.Errorf("GetBranchCommit: %v", err) + } + commitID = commit.ID.String() + commitIDCache[key] = commitID + } + + var startIdx = -1 + for i := len(commits) - 1; i >= 0; i-- { + if commitID == commits[i].Sha1 { + startIdx = i + break + } + } + if startIdx > -1 { + commits = commits[:startIdx] + } + if len(commits) <= 0 { + continue + } + + var alreadyComments []*Comment + err := sess.Find(&alreadyComments, &Comment{ + Type: CommentTypePullPushCommit, + IssueID: issue.ID, + }) + if err != nil { + return fmt.Errorf("check pull push comment: %v", err) + } + + var deleteCommitIDs = make([]int64, 0, len(alreadyComments)) + var keepCommentSha1s = make(map[string]struct{}) + for _, comment := range alreadyComments { + var find bool + for _, commit := range commits { + if commit.Sha1 == comment.CommitSHA { + find = true + break + } + } + if !find { + deleteCommitIDs = append(deleteCommitIDs, comment.ID) + } else { + keepCommentSha1s[comment.CommitSHA] = struct{}{} + } + } + + if len(deleteCommitIDs) > 0 { + if _, err := sess.In("id", deleteCommitIDs).Delete(new(Comment)); err != nil { + return fmt.Errorf("Delete unused commit comment: %v", err) + } + } + + for i := len(commits) - 1; i >= 0; i-- { + if _, ok := keepCommentSha1s[commits[i].Sha1]; ok { + continue + } + + _, err := createComment(sess, &CreateCommentOptions{ + Type: CommentTypePullPushCommit, + Doer: doer, + Repo: issue.Repo, + Issue: issue, + CommitSHA: commits[i].Sha1, + Content: commits[i].Message, + }) + if err != nil { + return err + } + } + } + + return sess.Commit() +} + // GetCommentByID returns the comment by given ID. func GetCommentByID(id int64) (*Comment, error) { c := new(Comment) diff --git a/models/pull.go b/models/pull.go index 4b928c8d9b9cb..c924abf04b1d9 100644 --- a/models/pull.go +++ b/models/pull.go @@ -66,6 +66,7 @@ type PullRequest struct { HeadBranch string BaseBranch string MergeBase string `xorm:"VARCHAR(40)"` + LastCommitID string `xorm:"VARCHAR(40)"` HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` diff --git a/models/repo.go b/models/repo.go index 8d57ae51a5307..15fc4e1534087 100644 --- a/models/repo.go +++ b/models/repo.go @@ -1567,7 +1567,8 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error } // ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) { +func ChangeRepositoryName(repo *Repository, oldRepoName, newRepoName string) (err error) { + u := repo.Owner oldRepoName = strings.ToLower(oldRepoName) newRepoName = strings.ToLower(newRepoName) if err = IsUsableRepoName(newRepoName); err != nil { @@ -1581,11 +1582,6 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error) return ErrRepoAlreadyExist{u.Name, newRepoName} } - repo, err := GetRepositoryByName(u.ID, oldRepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName: %v", err) - } - // Change repository directory name. if err = os.Rename(repo.RepoPath(), RepoPath(u.Name, newRepoName)); err != nil { return fmt.Errorf("rename repository directory: %v", err) diff --git a/models/status.go b/models/status.go index e2e8adb77b652..53d9f8809408b 100644 --- a/models/status.go +++ b/models/status.go @@ -149,11 +149,11 @@ func GetCommitStatuses(repo *Repository, sha string, page int) ([]*CommitStatus, } // GetLatestCommitStatus returns all statuses with a unique context for a given commit. -func GetLatestCommitStatus(repo *Repository, sha string, page int) ([]*CommitStatus, error) { +func GetLatestCommitStatus(repoID int64, sha string, page int) ([]*CommitStatus, error) { ids := make([]int64, 0, 10) err := x.Limit(10, page*10). Table(&CommitStatus{}). - Where("repo_id = ?", repo.ID).And("sha = ?", sha). + Where("repo_id = ?", repoID).And("sha = ?", sha). Select("max( id ) as id"). GroupBy("context").OrderBy("max( id ) desc").Find(&ids) if err != nil { @@ -287,7 +287,7 @@ func ParseCommitsWithStatus(oldCommits *list.List, repo *Repository) *list.List commit := SignCommitWithStatuses{ SignCommit: &c, } - statuses, err := GetLatestCommitStatus(repo, commit.ID.String(), 0) + statuses, err := GetLatestCommitStatus(repo.ID, commit.ID.String(), 0) if err != nil { log.Error(3, "GetLatestCommitStatus: %v", err) } else { diff --git a/routers/api/v1/repo/status.go b/routers/api/v1/repo/status.go index e4cc20a50b88a..a839c271af160 100644 --- a/routers/api/v1/repo/status.go +++ b/routers/api/v1/repo/status.go @@ -92,7 +92,7 @@ func GetCombinedCommitStatus(ctx *context.APIContext) { page := ctx.ParamsInt("page") - statuses, err := models.GetLatestCommitStatus(repo, sha, page) + statuses, err := models.GetLatestCommitStatus(repo.ID, sha, page) if err != nil { ctx.Error(500, "GetLatestCommitStatus", fmt.Errorf("GetLatestCommitStatus[%s, %s, %d]: %v", repo.FullName(), sha, page, err)) return diff --git a/routers/repo/commit.go b/routers/repo/commit.go index afd6b113c4190..34c62b14fb656 100644 --- a/routers/repo/commit.go +++ b/routers/repo/commit.go @@ -214,7 +214,7 @@ func Diff(ctx *context.Context) { commitID = commit.ID.String() } - statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), 0) + statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), 0) if err != nil { log.Error(3, "GetLatestCommitStatus: %v", err) } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index c24a4e4360d41..224fdac220588 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -626,7 +626,9 @@ func ViewIssue(ctx *context.Context) { // Render comments and and fetch participants. participants[0] = issue.Poster for _, comment = range issue.Comments { - if comment.Type == models.CommentTypeComment { + comment.Issue = issue + switch comment.Type { + case models.CommentTypeComment: comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())) @@ -658,12 +660,12 @@ func ViewIssue(ctx *context.Context) { if !isAdded && !issue.IsPoster(comment.Poster.ID) { participants = append(participants, comment.Poster) } - } else if comment.Type == models.CommentTypeLabel { + case models.CommentTypeLabel: if err = comment.LoadLabel(); err != nil { ctx.Handle(500, "LoadLabel", err) return } - } else if comment.Type == models.CommentTypeMilestone { + case models.CommentTypeMilestone: if err = comment.LoadMilestone(); err != nil { ctx.Handle(500, "LoadMilestone", err) return @@ -678,11 +680,16 @@ func ViewIssue(ctx *context.Context) { if comment.MilestoneID > 0 && comment.Milestone == nil { comment.Milestone = ghostMilestone } - } else if comment.Type == models.CommentTypeAssignees { + case models.CommentTypeAssignees: if err = comment.LoadAssignees(); err != nil { ctx.Handle(500, "LoadAssignees", err) return } + case models.CommentTypePullPushCommit: + if err = comment.LoadCommitStatus(); err != nil { + ctx.Handle(500, "LoadCommitStatus", err) + return + } } } diff --git a/routers/repo/setting.go b/routers/repo/setting.go index ebe1c7cd9e029..823fb826fb3dd 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -55,7 +55,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { // Check if repository name has been changed. if repo.LowerName != strings.ToLower(newRepoName) { isNameChanged = true - if err := models.ChangeRepositoryName(ctx.Repo.Owner, repo.Name, newRepoName); err != nil { + if err := models.ChangeRepositoryName(ctx.Repo.Repository, repo.Name, newRepoName); err != nil { ctx.Data["Err_RepoName"] = true switch { case models.IsErrRepoAlreadyExist(err): diff --git a/routers/repo/view.go b/routers/repo/view.go index d43b4d7f78b71..d08c1bf57493e 100644 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -117,7 +117,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { ctx.Data["LatestCommitVerification"] = models.ParseCommitWithSignature(latestCommit) ctx.Data["LatestCommitUser"] = models.ValidateCommitWithEmail(latestCommit) - statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository, ctx.Repo.Commit.ID.String(), 0) + statuses, err := models.GetLatestCommitStatus(ctx.Repo.Repository.ID, ctx.Repo.Commit.ID.String(), 0) if err != nil { log.Error(3, "GetLatestCommitStatus: %v", err) } diff --git a/templates/repo/commit_status.tmpl b/templates/repo/commit_status.tmpl index 752fd762e3d60..3c7226a681b89 100644 --- a/templates/repo/commit_status.tmpl +++ b/templates/repo/commit_status.tmpl @@ -1,15 +1,17 @@ -{{if eq .State "pending"}} - -{{end}} -{{if eq .State "success"}} - -{{end}} -{{if eq .State "error"}} - -{{end}} -{{if eq .State "failure"}} - -{{end}} -{{if eq .State "warning"}} - +{{if .}} + {{if eq .State "pending"}} + + {{end}} + {{if eq .State "success"}} + + {{end}} + {{if eq .State "error"}} + + {{end}} + {{if eq .State "failure"}} + + {{end}} + {{if eq .State "warning"}} + + {{end}} {{end}} \ No newline at end of file diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl index 34609ceb31734..116a0662982d4 100644 --- a/templates/repo/issue/view_content/comments.tmpl +++ b/templates/repo/issue/view_content/comments.tmpl @@ -1,7 +1,7 @@ {{range .Issue.Comments}} {{ $createdStr:= TimeSince .Created $.Lang }} - + {{if eq .Type 0}}
@@ -182,5 +182,17 @@ {{.Poster.Name}} {{$.i18n.Tr "repo.issues.cancel_tracking_history" $createdStr | Safe}}
+ {{else if eq .Type 16}} +
+ + + + + {{.Content | Str2html}} +
+ {{template "repo/commit_status" .CommitStatus}} + {{ShortSha .CommitSHA}} +
+
{{end}} {{end}}