mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 02:46:04 +01:00 
			
		
		
		
	Add push commits history comment on PR time-line (#11167)
* Add push commits history comment on PR time-line * Add notify by email and ui of this comment type also Signed-off-by: a1012112796 <1012112796@qq.com> * Add migrations for IsForcePush * fix wrong force-push judgement * Apply suggestions from code review * Remove commit number check * add own notify fun * fix some typo Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> * fix lint * fix style again, I forgot something before * Change email notify way * fix api * add number check if It's force-push * Add repo commit link fuction remove unnecessary check skip show push commits comment which not have commits alive * Update issue_comment.go * Apply suggestions from code review Co-authored-by: mrsdizzie <info@mrsdizzie.com> * Apply suggestions from code review * fix ui view Co-authored-by: silverwind <me@silverwind.io> * fix height * remove unnecessary style define * simplify GetBranchName * Apply suggestions from code review * save commit ids and isForce push by json * simplify GetBranchName * fix bug Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: mrsdizzie <info@mrsdizzie.com> Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		| @@ -7,6 +7,8 @@ | |||||||
| package models | package models | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"container/list" | ||||||
|  | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| @@ -90,6 +92,8 @@ const ( | |||||||
| 	CommentTypeReviewRequest | 	CommentTypeReviewRequest | ||||||
| 	// merge pull request | 	// merge pull request | ||||||
| 	CommentTypeMergePull | 	CommentTypeMergePull | ||||||
|  | 	// push to PR head branch | ||||||
|  | 	CommentTypePullPush | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // CommentTag defines comment tag type | // CommentTag defines comment tag type | ||||||
| @@ -167,6 +171,18 @@ type Comment struct { | |||||||
| 	RefRepo    *Repository `xorm:"-"` | 	RefRepo    *Repository `xorm:"-"` | ||||||
| 	RefIssue   *Issue      `xorm:"-"` | 	RefIssue   *Issue      `xorm:"-"` | ||||||
| 	RefComment *Comment    `xorm:"-"` | 	RefComment *Comment    `xorm:"-"` | ||||||
|  |  | ||||||
|  | 	Commits     *list.List `xorm:"-"` | ||||||
|  | 	OldCommit   string     `xorm:"-"` | ||||||
|  | 	NewCommit   string     `xorm:"-"` | ||||||
|  | 	CommitsNum  int64      `xorm:"-"` | ||||||
|  | 	IsForcePush bool       `xorm:"-"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // PushActionContent is content of push pull comment | ||||||
|  | type PushActionContent struct { | ||||||
|  | 	IsForcePush bool     `json:"is_force_push"` | ||||||
|  | 	CommitIDs   []string `json:"commit_ids"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // LoadIssue loads issue from database | // LoadIssue loads issue from database | ||||||
| @@ -543,6 +559,47 @@ func (c *Comment) CodeCommentURL() string { | |||||||
| 	return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag()) | 	return fmt.Sprintf("%s/files#%s", c.Issue.HTMLURL(), c.HashTag()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // LoadPushCommits Load push commits | ||||||
|  | func (c *Comment) LoadPushCommits() (err error) { | ||||||
|  | 	if c.Content == "" || c.Commits != nil || c.Type != CommentTypePullPush { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var data PushActionContent | ||||||
|  |  | ||||||
|  | 	err = json.Unmarshal([]byte(c.Content), &data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	c.IsForcePush = data.IsForcePush | ||||||
|  |  | ||||||
|  | 	if c.IsForcePush { | ||||||
|  | 		if len(data.CommitIDs) != 2 { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		c.OldCommit = data.CommitIDs[0] | ||||||
|  | 		c.NewCommit = data.CommitIDs[1] | ||||||
|  | 	} else { | ||||||
|  | 		repoPath := c.Issue.Repo.RepoPath() | ||||||
|  | 		gitRepo, err := git.OpenRepository(repoPath) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		defer gitRepo.Close() | ||||||
|  |  | ||||||
|  | 		c.Commits = gitRepo.GetCommitsFromIDs(data.CommitIDs) | ||||||
|  | 		c.CommitsNum = int64(c.Commits.Len()) | ||||||
|  | 		if c.CommitsNum > 0 { | ||||||
|  | 			c.Commits = ValidateCommitsWithEmails(c.Commits) | ||||||
|  | 			c.Commits = ParseCommitsWithSignature(c.Commits, c.Issue.Repo) | ||||||
|  | 			c.Commits = ParseCommitsWithStatus(c.Commits, c.Issue.Repo) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
| func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { | func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) { | ||||||
| 	var LabelID int64 | 	var LabelID int64 | ||||||
| 	if opts.Label != nil { | 	if opts.Label != nil { | ||||||
| @@ -576,6 +633,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err | |||||||
| 		RefCommentID:     opts.RefCommentID, | 		RefCommentID:     opts.RefCommentID, | ||||||
| 		RefAction:        opts.RefAction, | 		RefAction:        opts.RefAction, | ||||||
| 		RefIsPull:        opts.RefIsPull, | 		RefIsPull:        opts.RefIsPull, | ||||||
|  | 		IsForcePush:      opts.IsForcePush, | ||||||
| 	} | 	} | ||||||
| 	if _, err = e.Insert(comment); err != nil { | 	if _, err = e.Insert(comment); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -738,6 +796,7 @@ type CreateCommentOptions struct { | |||||||
| 	RefCommentID     int64 | 	RefCommentID     int64 | ||||||
| 	RefAction        references.XRefAction | 	RefAction        references.XRefAction | ||||||
| 	RefIsPull        bool | 	RefIsPull        bool | ||||||
|  | 	IsForcePush      bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // CreateComment creates comment of issue or commit. | // CreateComment creates comment of issue or commit. | ||||||
| @@ -1016,3 +1075,92 @@ func UpdateCommentsMigrationsByType(tp structs.GitServiceType, originalAuthorID | |||||||
| 		}) | 		}) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CreatePushPullComment create push code to pull base commend | ||||||
|  | func CreatePushPullComment(pusher *User, pr *PullRequest, oldCommitID, newCommitID string) (comment *Comment, err error) { | ||||||
|  | 	if pr.HasMerged || oldCommitID == "" || newCommitID == "" { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ops := &CreateCommentOptions{ | ||||||
|  | 		Type: CommentTypePullPush, | ||||||
|  | 		Doer: pusher, | ||||||
|  | 		Repo: pr.BaseRepo, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var data PushActionContent | ||||||
|  |  | ||||||
|  | 	data.CommitIDs, data.IsForcePush, err = getCommitIDsFromRepo(pr.BaseRepo, oldCommitID, newCommitID, pr.BaseBranch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ops.Issue = pr.Issue | ||||||
|  | 	dataJSON, err := json.Marshal(data) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ops.Content = string(dataJSON) | ||||||
|  |  | ||||||
|  | 	comment, err = CreateComment(ops) | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getCommitsFromRepo get commit IDs from repo in betwern oldCommitID and newCommitID | ||||||
|  | // isForcePush will be true if oldCommit isn't on the branch | ||||||
|  | // Commit on baseBranch will skip | ||||||
|  | func getCommitIDsFromRepo(repo *Repository, oldCommitID, newCommitID, baseBranch string) (commitIDs []string, isForcePush bool, err error) { | ||||||
|  | 	repoPath := repo.RepoPath() | ||||||
|  | 	gitRepo, err := git.OpenRepository(repoPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  | 	defer gitRepo.Close() | ||||||
|  |  | ||||||
|  | 	oldCommit, err := gitRepo.GetCommit(oldCommitID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	oldCommitBranch, err := oldCommit.GetBranchName() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if oldCommitBranch == "undefined" { | ||||||
|  | 		commitIDs = make([]string, 2) | ||||||
|  | 		commitIDs[0] = oldCommitID | ||||||
|  | 		commitIDs[1] = newCommitID | ||||||
|  |  | ||||||
|  | 		return commitIDs, true, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	newCommit, err := gitRepo.GetCommit(newCommitID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var commits *list.List | ||||||
|  | 	commits, err = newCommit.CommitsBeforeUntil(oldCommitID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, false, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commitIDs = make([]string, 0, commits.Len()) | ||||||
|  |  | ||||||
|  | 	for e := commits.Back(); e != nil; e = e.Prev() { | ||||||
|  | 		commit := e.Value.(*git.Commit) | ||||||
|  | 		commitBranch, err := commit.GetBranchName() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, false, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if commitBranch != baseBranch { | ||||||
|  | 			commitIDs = append(commitIDs, commit.ID.String()) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return | ||||||
|  | } | ||||||
|   | |||||||
| @@ -264,6 +264,17 @@ func (repo *Repository) HTMLURL() string { | |||||||
| 	return setting.AppURL + repo.FullName() | 	return setting.AppURL + repo.FullName() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // CommitLink make link to by commit full ID | ||||||
|  | // note: won't check whether it's an right id | ||||||
|  | func (repo *Repository) CommitLink(commitID string) (result string) { | ||||||
|  | 	if commitID == "" || commitID == "0000000000000000000000000000000000000000" { | ||||||
|  | 		result = "" | ||||||
|  | 	} else { | ||||||
|  | 		result = repo.HTMLURL() + "/commit/" + commitID | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
| // APIURL returns the repository API URL | // APIURL returns the repository API URL | ||||||
| func (repo *Repository) APIURL() string { | func (repo *Repository) APIURL() string { | ||||||
| 	return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) | 	return setting.AppURL + path.Join("api/v1/repos", repo.FullName()) | ||||||
|   | |||||||
| @@ -466,15 +466,15 @@ func (c *Commit) GetSubModule(entryname string) (*SubModule, error) { | |||||||
| 	return nil, nil | 	return nil, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // GetBranchName gets the closes branch name (as returned by 'git name-rev') | // GetBranchName gets the closes branch name (as returned by 'git name-rev --name-only') | ||||||
| func (c *Commit) GetBranchName() (string, error) { | func (c *Commit) GetBranchName() (string, error) { | ||||||
| 	data, err := NewCommand("name-rev", c.ID.String()).RunInDirBytes(c.repo.Path) | 	data, err := NewCommand("name-rev", "--name-only", c.ID.String()).RunInDir(c.repo.Path) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// name-rev commitID output will be "COMMIT_ID master" or "COMMIT_ID master~12" | 	// name-rev commitID output will be "master" or "master~12" | ||||||
| 	return strings.Split(strings.Split(string(data), " ")[1], "~")[0], nil | 	return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // CommitFileStatus represents status of files in a commit. | // CommitFileStatus represents status of files in a commit. | ||||||
|   | |||||||
| @@ -454,3 +454,21 @@ func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) | |||||||
| 	} | 	} | ||||||
| 	return branches, nil | 	return branches, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetCommitsFromIDs get commits from commit IDs | ||||||
|  | func (repo *Repository) GetCommitsFromIDs(commitIDs []string) (commits *list.List) { | ||||||
|  | 	if len(commitIDs) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	commits = list.New() | ||||||
|  |  | ||||||
|  | 	for _, commitID := range commitIDs { | ||||||
|  | 		commit, err := repo.GetCommit(commitID) | ||||||
|  | 		if err == nil && commit != nil { | ||||||
|  | 			commits.PushBack(commit) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return commits | ||||||
|  | } | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ type Notifier interface { | |||||||
| 	NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) | 	NotifyPullRequestSynchronized(doer *models.User, pr *models.PullRequest) | ||||||
| 	NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment) | 	NotifyPullRequestReview(*models.PullRequest, *models.Review, *models.Comment) | ||||||
| 	NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) | 	NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) | ||||||
|  | 	NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) | ||||||
|  |  | ||||||
| 	NotifyCreateIssueComment(*models.User, *models.Repository, | 	NotifyCreateIssueComment(*models.User, *models.Repository, | ||||||
| 		*models.Issue, *models.Comment) | 		*models.Issue, *models.Comment) | ||||||
|   | |||||||
| @@ -54,6 +54,10 @@ func (*NullNotifier) NotifyPullRequestSynchronized(doer *models.User, pr *models | |||||||
| func (*NullNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) { | func (*NullNotifier) NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullRequest, oldBranch string) { | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NotifyPullRequestPushCommits notifies when push commits to pull request's head branch | ||||||
|  | func (*NullNotifier) NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) { | ||||||
|  | } | ||||||
|  |  | ||||||
| // NotifyUpdateComment places a place holder function | // NotifyUpdateComment places a place holder function | ||||||
| func (*NullNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) { | func (*NullNotifier) NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) { | ||||||
| } | } | ||||||
|   | |||||||
| @@ -37,6 +37,8 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models. | |||||||
| 		act = models.ActionCommentIssue | 		act = models.ActionCommentIssue | ||||||
| 	} else if comment.Type == models.CommentTypeCode { | 	} else if comment.Type == models.CommentTypeCode { | ||||||
| 		act = models.ActionCommentIssue | 		act = models.ActionCommentIssue | ||||||
|  | 	} else if comment.Type == models.CommentTypePullPush { | ||||||
|  | 		act = 0 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := mailer.MailParticipantsComment(comment, act, issue); err != nil { | 	if err := mailer.MailParticipantsComment(comment, act, issue); err != nil { | ||||||
| @@ -117,3 +119,29 @@ func (m *mailNotifier) NotifyMergePullRequest(pr *models.PullRequest, doer *mode | |||||||
| 		log.Error("MailParticipants: %v", err) | 		log.Error("MailParticipants: %v", err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (m *mailNotifier) NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) { | ||||||
|  | 	var err error | ||||||
|  | 	if err = comment.LoadIssue(); err != nil { | ||||||
|  | 		log.Error("comment.LoadIssue: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err = comment.Issue.LoadRepo(); err != nil { | ||||||
|  | 		log.Error("comment.Issue.LoadRepo: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err = comment.Issue.LoadPullRequest(); err != nil { | ||||||
|  | 		log.Error("comment.Issue.LoadPullRequest: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err = comment.Issue.PullRequest.LoadBaseRepo(); err != nil { | ||||||
|  | 		log.Error("comment.Issue.PullRequest.LoadBaseRepo: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	if err := comment.LoadPushCommits(); err != nil { | ||||||
|  | 		log.Error("comment.LoadPushCommits: %v", err) | ||||||
|  | 	} | ||||||
|  | 	comment.Content = "" | ||||||
|  |  | ||||||
|  | 	m.NotifyCreateIssueComment(doer, comment.Issue.Repo, comment.Issue, comment) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -94,6 +94,13 @@ func NotifyPullRequestChangeTargetBranch(doer *models.User, pr *models.PullReque | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NotifyPullRequestPushCommits notifies when push commits to pull request's head branch | ||||||
|  | func NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) { | ||||||
|  | 	for _, notifier := range notifiers { | ||||||
|  | 		notifier.NotifyPullRequestPushCommits(doer, pr, comment) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // NotifyUpdateComment notifies update comment to notifiers | // NotifyUpdateComment notifies update comment to notifiers | ||||||
| func NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) { | func NotifyUpdateComment(doer *models.User, c *models.Comment, oldContent string) { | ||||||
| 	for _, notifier := range notifiers { | 	for _, notifier := range notifiers { | ||||||
|   | |||||||
| @@ -105,6 +105,15 @@ func (ns *notificationService) NotifyPullRequestReview(pr *models.PullRequest, r | |||||||
| 	_ = ns.issueQueue.Push(opts) | 	_ = ns.issueQueue.Push(opts) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (ns *notificationService) NotifyPullRequestPushCommits(doer *models.User, pr *models.PullRequest, comment *models.Comment) { | ||||||
|  | 	var opts = issueNotificationOpts{ | ||||||
|  | 		IssueID:              pr.IssueID, | ||||||
|  | 		NotificationAuthorID: doer.ID, | ||||||
|  | 		CommentID:            comment.ID, | ||||||
|  | 	} | ||||||
|  | 	_ = ns.issueQueue.Push(opts) | ||||||
|  | } | ||||||
|  |  | ||||||
| func (ns *notificationService) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) { | func (ns *notificationService) NotifyIssueChangeAssignee(doer *models.User, issue *models.Issue, assignee *models.User, removed bool, comment *models.Comment) { | ||||||
| 	if !removed { | 	if !removed { | ||||||
| 		var opts = issueNotificationOpts{ | 		var opts = issueNotificationOpts{ | ||||||
|   | |||||||
| @@ -1029,6 +1029,9 @@ issues.due_date = Due Date | |||||||
| issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." | issues.invalid_due_date_format = "Due date format must be 'yyyy-mm-dd'." | ||||||
| issues.error_modifying_due_date = "Failed to modify the due date." | issues.error_modifying_due_date = "Failed to modify the due date." | ||||||
| issues.error_removing_due_date = "Failed to remove the due date." | issues.error_removing_due_date = "Failed to remove the due date." | ||||||
|  | issues.push_commit_1 = "added %d commit %s" | ||||||
|  | issues.push_commits_n = "added %d commits %s" | ||||||
|  | issues.force_push_codes =  `force-pushed the %[1]s branch from <a href="%[3]s">%[2]s</a> to <a href="%[5]s">%[4]s</a>.  %[6]s` | ||||||
| issues.due_date_form = "yyyy-mm-dd" | issues.due_date_form = "yyyy-mm-dd" | ||||||
| issues.due_date_form_add = "Add due date" | issues.due_date_form_add = "Add due date" | ||||||
| issues.due_date_form_edit = "Edit" | issues.due_date_form_edit = "Edit" | ||||||
|   | |||||||
| @@ -999,6 +999,12 @@ func ViewIssue(ctx *context.Context) { | |||||||
| 				ctx.ServerError("LoadResolveDoer", err) | 				ctx.ServerError("LoadResolveDoer", err) | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|  | 		} else if comment.Type == models.CommentTypePullPush { | ||||||
|  | 			participants = addParticipant(comment.Poster, participants) | ||||||
|  | 			if err = comment.LoadPushCommits(); err != nil { | ||||||
|  | 				ctx.ServerError("LoadPushCommits", err) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -319,6 +319,8 @@ func actionToTemplate(issue *models.Issue, actionType models.ActionType, | |||||||
| 			name = "code" | 			name = "code" | ||||||
| 		case models.CommentTypeAssignees: | 		case models.CommentTypeAssignees: | ||||||
| 			name = "assigned" | 			name = "assigned" | ||||||
|  | 		case models.CommentTypePullPush: | ||||||
|  | 			name = "push" | ||||||
| 		default: | 		default: | ||||||
| 			name = "default" | 			name = "default" | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import ( | |||||||
| 	"bufio" | 	"bufio" | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| @@ -57,6 +58,43 @@ func NewPullRequest(repo *models.Repository, pull *models.Issue, labelIDs []int6 | |||||||
|  |  | ||||||
| 	notification.NotifyNewPullRequest(pr) | 	notification.NotifyNewPullRequest(pr) | ||||||
|  |  | ||||||
|  | 	// add first push codes comment | ||||||
|  | 	baseGitRepo, err := git.OpenRepository(pr.BaseRepo.RepoPath()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	defer baseGitRepo.Close() | ||||||
|  |  | ||||||
|  | 	compareInfo, err := baseGitRepo.GetCompareInfo(pr.BaseRepo.RepoPath(), | ||||||
|  | 		pr.BaseBranch, pr.GetGitRefName()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if compareInfo.Commits.Len() > 0 { | ||||||
|  | 		data := models.PushActionContent{IsForcePush: false} | ||||||
|  | 		data.CommitIDs = make([]string, 0, compareInfo.Commits.Len()) | ||||||
|  | 		for e := compareInfo.Commits.Back(); e != nil; e = e.Prev() { | ||||||
|  | 			data.CommitIDs = append(data.CommitIDs, e.Value.(*git.Commit).ID.String()) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		dataJSON, err := json.Marshal(data) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ops := &models.CreateCommentOptions{ | ||||||
|  | 			Type:        models.CommentTypePullPush, | ||||||
|  | 			Doer:        pull.Poster, | ||||||
|  | 			Repo:        repo, | ||||||
|  | 			Issue:       pr.Issue, | ||||||
|  | 			IsForcePush: false, | ||||||
|  | 			Content:     string(dataJSON), | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		_, _ = models.CreateComment(ops) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -237,6 +275,12 @@ func AddTestPullRequestTask(doer *models.User, repoID int64, branch string, isSy | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		addHeadRepoTasks(prs) | 		addHeadRepoTasks(prs) | ||||||
|  | 		for _, pr := range prs { | ||||||
|  | 			comment, err := models.CreatePushPullComment(doer, pr, oldCommitID, newCommitID) | ||||||
|  | 			if err == nil && comment != nil { | ||||||
|  | 				notification.NotifyPullRequestPushCommits(doer, pr, comment) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch) | 		log.Trace("AddTestPullRequestTask [base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch) | ||||||
| 		prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch) | 		prs, err = models.GetUnmergedPullRequestsByBaseInfo(repoID, branch) | ||||||
|   | |||||||
| @@ -17,6 +17,25 @@ | |||||||
|  |  | ||||||
| <body> | <body> | ||||||
| 	{{if .IsMention}}<p><b>@{{.Doer.Name}}</b> mentioned you:</p>{{end}} | 	{{if .IsMention}}<p><b>@{{.Doer.Name}}</b> mentioned you:</p>{{end}} | ||||||
|  | 	{{if eq .Comment.Type 29}} | ||||||
|  | 		<p> | ||||||
|  | 			<b>{{.Doer.Name}}</b>   | ||||||
|  | 			{{if .Comment.IsForcePush}} | ||||||
|  | 				{{ $oldCommitLink:= printf "%s%s/%s/commit/%s" AppUrl  .Comment.Issue.PullRequest.BaseRepo.OwnerName .Comment.Issue.PullRequest.BaseRepo.Name .Comment.OldCommit}} | ||||||
|  | 				{{ $newCommitLink:= printf "%s%s/%s/commit/%s" AppUrl  .Comment.Issue.PullRequest.BaseRepo.OwnerName .Comment.Issue.PullRequest.BaseRepo.Name .Comment.NewCommit}} | ||||||
|  | 				force-pushed the <b>{{.Comment.Issue.PullRequest.HeadBranch}}</b> from | ||||||
|  | 				<a href="{{$oldCommitLink}}"><b>{{ShortSha .Comment.OldCommit}}</b></a> | ||||||
|  | 				to | ||||||
|  | 				<a href="{{$newCommitLink}}"><b>{{ShortSha .Comment.NewCommit}}</b></a>. | ||||||
|  | 			{{else}} | ||||||
|  | 				{{if eq .Comment.Commits.Len 1}} | ||||||
|  | 					{{printf "pushed 1 commit to %s:" .Comment.Issue.PullRequest.HeadBranch}} | ||||||
|  | 				{{else}} | ||||||
|  | 					{{printf "pushed %d commits to %s:" .Comment.Commits.Len .Comment.Issue.PullRequest.HeadBranch}} | ||||||
|  | 				{{end}} | ||||||
|  | 			{{end}} | ||||||
|  | 		</p> | ||||||
|  | 	{{end}} | ||||||
| 	<p> | 	<p> | ||||||
| 		{{if eq .ActionName "close"}} | 		{{if eq .ActionName "close"}} | ||||||
| 			Closed #{{.Issue.Index}}. | 			Closed #{{.Issue.Index}}. | ||||||
| @@ -47,6 +66,18 @@ | |||||||
| 				<div>{{.RenderedContent | Safe}}</div> | 				<div>{{.RenderedContent | Safe}}</div> | ||||||
| 			</div> | 			</div> | ||||||
| 		{{end -}} | 		{{end -}} | ||||||
|  | 		{{if eq .Comment.Type 29}} | ||||||
|  | 			{{ $r:= List .Comment.Commits}} | ||||||
|  | 			<ul> | ||||||
|  | 			{{range $r}} | ||||||
|  | 				<li> | ||||||
|  | 					<a href="{{AppUrl}}{{$.Comment.Issue.PullRequest.BaseRepo.OwnerName}}/{{$.Comment.Issue.PullRequest.BaseRepo.Name}}/commit/{{.ID}}"> | ||||||
|  | 						{{ShortSha .ID.String}} | ||||||
|  | 					</a>  -  {{.Summary}} | ||||||
|  | 				</li> | ||||||
|  | 			{{end}} | ||||||
|  | 			</ul> | ||||||
|  | 		{{end}} | ||||||
| 	</p> | 	</p> | ||||||
| 	<div class="footer"> | 	<div class="footer"> | ||||||
| 	<p> | 	<p> | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								templates/repo/commits_list_small.tmpl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								templates/repo/commits_list_small.tmpl
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | {{ $r:= List .Commits}} | ||||||
|  | {{ $index := 0}} | ||||||
|  | {{range $r}} | ||||||
|  | 	{{ $tag := printf "%s-%d" $.HashTag $index }} | ||||||
|  | 	{{ $index = Add $index 1}} | ||||||
|  | 	<div class="timeline-item event small-line-spacing" id="{{$tag}}"> | ||||||
|  | 		<span class="badge badge-commit">{{svg "octicon-git-commit" 16}}</span> | ||||||
|  | 		{{if .User}} | ||||||
|  | 			<a class="ui avatar image" href="{{AppSubUrl}}/{{.User.Name}}"><img src="{{.User.RelAvatarLink}}" alt=""/></a> | ||||||
|  | 		{{else}} | ||||||
|  | 			<img class="ui avatar image" src="{{AvatarLink .Author.Email}}" alt=""/> | ||||||
|  | 		{{end}} | ||||||
|  |  | ||||||
|  | 		<span class="ui float right shabox"> | ||||||
|  | 			{{$class := "ui sha label"}} | ||||||
|  | 			{{if .Signature}} | ||||||
|  | 				{{$class = (printf "%s%s" $class " isSigned")}} | ||||||
|  | 				{{if .Verification.Verified}} | ||||||
|  | 					{{if eq .Verification.TrustStatus "trusted"}} | ||||||
|  | 						{{$class = (printf "%s%s" $class " isVerified")}} | ||||||
|  | 					{{else if eq .Verification.TrustStatus "untrusted"}} | ||||||
|  | 						{{$class = (printf "%s%s" $class " isVerifiedUntrusted")}} | ||||||
|  | 					{{else}} | ||||||
|  | 						{{$class = (printf "%s%s" $class " isVerifiedUnmatched")}} | ||||||
|  | 					{{end}} | ||||||
|  | 				{{else if .Verification.Warning}} | ||||||
|  | 					{{$class = (printf "%s%s" $class " isWarning")}} | ||||||
|  | 				{{end}} | ||||||
|  | 			{{end}} | ||||||
|  | 			{{if $.Issue.PullRequest.BaseRepo.Name}} | ||||||
|  | 				<a href="{{AppSubUrl}}/{{$.Issue.PullRequest.BaseRepo.OwnerName}}/{{$.Issue.PullRequest.BaseRepo.Name}}/commit/{{.ID}}" rel="nofollow" class="{{$class}}"> | ||||||
|  | 			{{else}} | ||||||
|  | 				<span class="{{$class}}"> | ||||||
|  | 			{{end}} | ||||||
|  | 					<span class="shortsha">{{ShortSha .ID.String}}</span> | ||||||
|  | 			{{if $.Issue.PullRequest.BaseRepo.Name}} | ||||||
|  | 				</a> | ||||||
|  | 			{{else}} | ||||||
|  | 				</span> | ||||||
|  | 			{{end}} | ||||||
|  | 		</span> | ||||||
|  |  | ||||||
|  | 		<span class="message-wrapper"> | ||||||
|  | 			{{ $commitLink:= printf "%s/%s/%s/commit/%s" AppSubUrl  $.Issue.PullRequest.BaseRepo.OwnerName $.Issue.PullRequest.BaseRepo.Name .ID }} | ||||||
|  | 			<span class="commit-summary has-emoji{{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message ($.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink $.Issue.PullRequest.BaseRepo.ComposeMetas}}</span> | ||||||
|  | 		</span> | ||||||
|  | 		{{if IsMultilineCommitMessage .Message}} | ||||||
|  | 			<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> | ||||||
|  | 		{{end}} | ||||||
|  | 		{{if eq (CommitType .) "SignCommitWithStatuses"}} | ||||||
|  | 			{{template "repo/commit_status" .Status}} | ||||||
|  | 		{{end}} | ||||||
|  | 		{{if IsMultilineCommitMessage .Message}} | ||||||
|  | 			<pre class="commit-body" style="display: none;">{{RenderCommitBody .Message ($.Issue.PullRequest.BaseRepo.Link|Escape) $.Issue.PullRequest.BaseRepo.ComposeMetas}}</pre> | ||||||
|  | 		{{end}} | ||||||
|  | 	</div> | ||||||
|  | {{end}} | ||||||
| @@ -7,7 +7,8 @@ | |||||||
| 	 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, | 	 13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, | ||||||
| 	 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE, | 	 18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE, | ||||||
| 	 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED, 25 = TARGET_BRANCH_CHANGED, | 	 22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED, 25 = TARGET_BRANCH_CHANGED, | ||||||
| 	 26 = DELETE_TIME_MANUAL, 27 = REVIEW_REQUEST, 28 = MERGE_PULL_REQUEST --> | 	 26 = DELETE_TIME_MANUAL, 27 = REVIEW_REQUEST, 28 = MERGE_PULL_REQUEST, | ||||||
|  | 	 29 = PULL_PUSH_EVENT --> | ||||||
| 	{{if eq .Type 0}} | 	{{if eq .Type 0}} | ||||||
| 		<div class="timeline-item comment" id="{{.HashTag}}"> | 		<div class="timeline-item comment" id="{{.HashTag}}"> | ||||||
| 		{{if .OriginalAuthor }} | 		{{if .OriginalAuthor }} | ||||||
| @@ -594,5 +595,23 @@ | |||||||
| 				{{end}} | 				{{end}} | ||||||
| 			</span> | 			</span> | ||||||
| 		</div> | 		</div> | ||||||
|  | 	{{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}} | ||||||
|  | 		<div class="timeline-item event" id="{{.HashTag}}"> | ||||||
|  | 			<span class="badge">{{svg "octicon-repo-force-push" 16}}</span> | ||||||
|  | 			<a class="ui avatar image" href="{{.Poster.HomeLink}}"> | ||||||
|  | 				<img src="{{.Poster.RelAvatarLink}}"> | ||||||
|  | 			</a> | ||||||
|  | 			<span class="text grey"> | ||||||
|  | 				<a href="{{.Poster.HomeLink}}">{{.Poster.GetDisplayName}}</a> | ||||||
|  | 				{{ if .IsForcePush }} | ||||||
|  | 					{{$.i18n.Tr "repo.issues.force_push_codes" $.Issue.PullRequest.HeadBranch (ShortSha .OldCommit) ($.Issue.Repo.CommitLink .OldCommit)  (ShortSha .NewCommit) ($.Issue.Repo.CommitLink .NewCommit) $createdStr | Safe}} | ||||||
|  | 				{{else}} | ||||||
|  | 					{{$.i18n.Tr (TrN $.i18n.Lang .Commits.Len "repo.issues.push_commit_1" "repo.issues.push_commits_n") .Commits.Len $createdStr | Safe}} | ||||||
|  | 				{{end}} | ||||||
|  | 			</span> | ||||||
|  | 		</div> | ||||||
|  | 		{{if not .IsForcePush}} | ||||||
|  | 			{{template "repo/commits_list_small" .}} | ||||||
|  | 		{{end}} | ||||||
| 	{{end}} | 	{{end}} | ||||||
| {{end}} | {{end}} | ||||||
|   | |||||||
| @@ -762,6 +762,12 @@ | |||||||
|                     padding-bottom: 0 !important; |                     padding-bottom: 0 !important; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 .badge.badge-commit { | ||||||
|  |                     border-color: transparent; | ||||||
|  |                     background: radial-gradient(white 60%, transparent 60%) no-repeat; | ||||||
|  |                     height: 0 !important; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 .badge { |                 .badge { | ||||||
|                     width: 32px; |                     width: 32px; | ||||||
|                     height: 32px; |                     height: 32px; | ||||||
| @@ -804,6 +810,16 @@ | |||||||
|                     line-height: 30px; |                     line-height: 30px; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 &.event.small-line-spacing { | ||||||
|  |                     padding: 2px 0 0 15px; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 &.event > .commit-status-link { | ||||||
|  |                     float: right; | ||||||
|  |                     margin-right: 8px; | ||||||
|  |                     margin-top: 4px; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 .author { |                 .author { | ||||||
|                     font-weight: 700; |                     font-weight: 700; | ||||||
|                 } |                 } | ||||||
| @@ -1040,6 +1056,69 @@ | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 & > .shabox { | ||||||
|  |                     .sha.label { | ||||||
|  |                         margin: 0; | ||||||
|  |                         border: 1px solid #bbbbbb; | ||||||
|  |  | ||||||
|  |                         &.isSigned.isWarning { | ||||||
|  |                             border: 1px solid #db2828; | ||||||
|  |                             background: fade(#db2828, 10%); | ||||||
|  |  | ||||||
|  |                             .shortsha { | ||||||
|  |                                 display: inline-block; | ||||||
|  |                                 padding-top: 1px; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             &:hover { | ||||||
|  |                                 background: fade(#db2828, 30%) !important; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         &.isSigned.isVerified { | ||||||
|  |                             border: 1px solid #21ba45; | ||||||
|  |                             background: fade(#21ba45, 10%); | ||||||
|  |  | ||||||
|  |                             .shortsha { | ||||||
|  |                                 display: inline-block; | ||||||
|  |                                 padding-top: 1px; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             &:hover { | ||||||
|  |                                 background: fade(#21ba45, 30%) !important; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         &.isSigned.isVerifiedUntrusted { | ||||||
|  |                             border: 1px solid #fbbd08; | ||||||
|  |                             background: fade(#fbbd08, 10%); | ||||||
|  |  | ||||||
|  |                             .shortsha { | ||||||
|  |                                 display: inline-block; | ||||||
|  |                                 padding-top: 1px; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             &:hover { | ||||||
|  |                                 background: fade(#fbbd08, 30%) !important; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         &.isSigned.isVerifiedUnmatched { | ||||||
|  |                             border: 1px solid #f2711c; | ||||||
|  |                             background: fade(#f2711c, 10%); | ||||||
|  |  | ||||||
|  |                             .shortsha { | ||||||
|  |                                 display: inline-block; | ||||||
|  |                                 padding-top: 1px; | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             &:hover { | ||||||
|  |                                 background: fade(#f2711c, 30%) !important; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 .detail { |                 .detail { | ||||||
|                     font-size: .9rem; |                     font-size: .9rem; | ||||||
|                     margin-top: 5px; |                     margin-top: 5px; | ||||||
|   | |||||||
| @@ -704,6 +704,11 @@ a.ui.basic.green.label:hover { | |||||||
|     color: #9e9e9e; |     color: #9e9e9e; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .repository.view.issue .comment-list .timeline-item .badge.badge-commit { | ||||||
|  |     background: radial-gradient(#383c4a 60%, transparent 60%) no-repeat; | ||||||
|  |     height: 0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .repository .comment.form .content .form:after { | .repository .comment.form .content .form:after { | ||||||
|     border-right-color: #313c47; |     border-right-color: #313c47; | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user