mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-10 14:01:54 +02:00
Merge fcc8caaa52120edefb636cb758cf05e7e4cec6e4 into 0a3aaeafe7bef9d6935422f4b91c77c216c01b21
This commit is contained in:
commit
51f87cf9ae
@ -28,6 +28,7 @@ type Attachment struct {
|
||||
ReleaseID int64 `xorm:"INDEX"` // maybe zero when creating
|
||||
UploaderID int64 `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
|
||||
CommentID int64 `xorm:"INDEX"`
|
||||
CommitCommentID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
DownloadCount int64 `xorm:"DEFAULT 0"`
|
||||
Size int64 `xorm:"DEFAULT 0"`
|
||||
@ -226,6 +227,37 @@ func DeleteAttachmentsByComment(ctx context.Context, commentID int64, remove boo
|
||||
return DeleteAttachments(ctx, attachments, remove)
|
||||
}
|
||||
|
||||
// UpdateAttachmentCommitCommentID updates the commit comment ID for attachments
|
||||
func UpdateAttachmentCommitCommentID(ctx context.Context, uuids []string, commitCommentID int64) error {
|
||||
if len(uuids) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).
|
||||
In("uuid", uuids).
|
||||
Cols("commit_comment_id").
|
||||
Update(&Attachment{CommitCommentID: commitCommentID})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAttachmentsByCommitComment deletes all attachments associated with the given commit comment.
|
||||
func DeleteAttachmentsByCommitComment(ctx context.Context, commitCommentID int64, remove bool) (int, error) {
|
||||
attachments, err := GetAttachmentsByCommitCommentID(ctx, commitCommentID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return DeleteAttachments(ctx, attachments, remove)
|
||||
}
|
||||
|
||||
// GetAttachmentsByCommitCommentID returns all attachments for a commit comment
|
||||
func GetAttachmentsByCommitCommentID(ctx context.Context, commitCommentID int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
return attachments, db.GetEngine(ctx).
|
||||
Where("commit_comment_id = ?", commitCommentID).
|
||||
Find(&attachments)
|
||||
}
|
||||
|
||||
// UpdateAttachmentByUUID Updates attachment via uuid
|
||||
func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...string) error {
|
||||
if attach.UUID == "" {
|
||||
@ -262,7 +294,11 @@ func CountOrphanedAttachments(ctx context.Context) (int64, error) {
|
||||
|
||||
// DeleteOrphanedAttachments delete all bad attachments
|
||||
func DeleteOrphanedAttachments(ctx context.Context) error {
|
||||
_, err := db.GetEngine(ctx).Where("(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))").
|
||||
// Delete attachments that are linked to non-existent issues, releases, comments, or commit comments
|
||||
_, err := db.GetEngine(ctx).Where("(issue_id > 0 and issue_id not in (select id from issue)) or "+
|
||||
"(release_id > 0 and release_id not in (select id from `release`)) or "+
|
||||
"(comment_id > 0 and comment_id not in (select id from comment)) or "+
|
||||
"(commit_comment_id > 0 and commit_comment_id not in (select id from commit_comment))").
|
||||
Delete(new(Attachment))
|
||||
return err
|
||||
}
|
||||
|
||||
148
models/repo/commit_comment.go
Normal file
148
models/repo/commit_comment.go
Normal file
@ -0,0 +1,148 @@
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitdiff"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
// CommitComment represents a comment on a commit
|
||||
type CommitComment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
CommitSHA string
|
||||
PosterID int64
|
||||
Poster *user_model.User `xorm:"-"`
|
||||
Line int64
|
||||
TreePath string
|
||||
Content string `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
// CreateCommitComment creates a new commit comment
|
||||
func CreateCommitComment(ctx context.Context, opts *CreateCommitCommentOptions) (*CommitComment, error) {
|
||||
comment := &CommitComment{
|
||||
RepoID: opts.RepoID,
|
||||
CommitSHA: opts.CommitSHA,
|
||||
PosterID: opts.PosterID,
|
||||
Line: opts.Line,
|
||||
TreePath: opts.TreePath,
|
||||
Content: opts.Content,
|
||||
}
|
||||
|
||||
if _, err := db.GetEngine(ctx).Insert(comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
// CreateCommitCommentOptions defines options for creating a commit comment
|
||||
type CreateCommitCommentOptions struct {
|
||||
RepoID int64
|
||||
CommitSHA string
|
||||
PosterID int64
|
||||
Line int64
|
||||
TreePath string
|
||||
Content string
|
||||
}
|
||||
|
||||
// GetCommitComments returns all comments for a commit
|
||||
func GetCommitComments(ctx context.Context, repoID int64, commitSHA string) ([]*CommitComment, error) {
|
||||
comments := make([]*CommitComment, 0, 10)
|
||||
if err := db.GetEngine(ctx).
|
||||
Where("repo_id = ?", repoID).
|
||||
And("commit_sha = ?", commitSHA).
|
||||
OrderBy("created_unix ASC").
|
||||
Find(&comments); err != nil {
|
||||
return nil, fmt.Errorf("get commit comments: %v", err)
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
// GetCommitCommentByID returns a commit comment by ID
|
||||
func GetCommitCommentByID(ctx context.Context, id int64) (*CommitComment, error) {
|
||||
comment := new(CommitComment)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("commit comment does not exist [id: %d]", id)
|
||||
}
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
// LoadPoster loads poster for commit comment
|
||||
func (c *CommitComment) LoadPoster(ctx context.Context) error {
|
||||
if c.Poster != nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
c.Poster, err = user_model.GetUserByID(ctx, c.PosterID)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadCommentsForDiffLines loads comments for diff lines
|
||||
func LoadCommentsForDiffLines(ctx context.Context, repoID int64, commitSHA string, diffLines []*gitdiff.DiffLine) error {
|
||||
// Get all comments for this commit
|
||||
comments, err := GetCommitComments(ctx, repoID, commitSHA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Map comments by line and path
|
||||
commentMap := make(map[string][]*CommitComment)
|
||||
for _, comment := range comments {
|
||||
key := fmt.Sprintf("%d-%s", comment.Line, comment.TreePath)
|
||||
commentMap[key] = append(commentMap[key], comment)
|
||||
}
|
||||
|
||||
// Attach comments to diff lines
|
||||
for _, line := range diffLines {
|
||||
var key string
|
||||
if line.RightIdx > 0 {
|
||||
key = fmt.Sprintf("%d-%s", line.RightIdx, line.FileName)
|
||||
} else if line.LeftIdx > 0 {
|
||||
key = fmt.Sprintf("%d-%s", line.LeftIdx, line.FileName)
|
||||
}
|
||||
|
||||
if comments, ok := commentMap[key]; ok {
|
||||
for _, comment := range comments {
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
line.Comments = append(line.Comments, convertCommitCommentsToIssueComments(comments)...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertCommitCommentsToIssueComments converts commit comments to issue comments for display
|
||||
func convertCommitCommentsToIssueComments(commitComments []*CommitComment) []*issues_model.Comment {
|
||||
issueComments := make([]*issues_model.Comment, len(commitComments))
|
||||
for i, cc := range commitComments {
|
||||
issueComments[i] = &issues_model.Comment{
|
||||
ID: cc.ID,
|
||||
PosterID: cc.PosterID,
|
||||
Poster: cc.Poster,
|
||||
IssueID: 0, // Not linked to an issue
|
||||
Content: cc.Content,
|
||||
CreatedUnix: cc.CreatedUnix,
|
||||
UpdatedUnix: cc.UpdatedUnix,
|
||||
Type: issues_model.CommentTypeCommit,
|
||||
}
|
||||
}
|
||||
return issueComments
|
||||
}
|
||||
247
routers/api/v1/repo/commit_comment.go
Normal file
247
routers/api/v1/repo/commit_comment.go
Normal file
@ -0,0 +1,247 @@
|
||||
|
||||
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/commit"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
// ListCommitComments list comments on a commit
|
||||
func ListCommitComments(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/commits/{sha}/comments repository repoListCommitComments
|
||||
// ---
|
||||
// summary: List comments on a commit
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: sha
|
||||
// in: path
|
||||
// description: SHA of the commit
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/CommentList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
sha := ctx.PathParam("sha")
|
||||
if len(sha) == 0 {
|
||||
ctx.Error(http.StatusBadRequest, "SHA is empty")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the commit
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(sha)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusNotFound, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has permission to read this repo
|
||||
if !ctx.Repo.Permission.CanRead(repo_model.UnitTypeCode) {
|
||||
ctx.Error(http.StatusForbidden, "No permission to read code", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get comments for this commit
|
||||
comments, err := repo_model.GetCommitComments(ctx, ctx.Repo.Repository.ID, sha)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetCommitComments", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Load posters
|
||||
for _, comment := range comments {
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadPoster", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to API format
|
||||
apiComments := make([]*api.Comment, len(comments))
|
||||
for i, comment := range comments {
|
||||
apiComments[i] = convert.ToAPIComment(ctx, ctx.Repo.Repository, &repo_model.Comment{
|
||||
ID: comment.ID,
|
||||
PosterID: comment.PosterID,
|
||||
Poster: comment.Poster,
|
||||
Content: comment.Content,
|
||||
CreatedUnix: comment.CreatedUnix,
|
||||
UpdatedUnix: comment.UpdatedUnix,
|
||||
Type: repo_model.CommentTypeCommit,
|
||||
})
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &apiComments)
|
||||
}
|
||||
|
||||
// CreateCommitComment create a comment on a commit
|
||||
func CreateCommitComment(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/commits/{sha}/comments repository repoCreateCommitComment
|
||||
// ---
|
||||
// summary: Create a comment on a commit
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: sha
|
||||
// in: path
|
||||
// description: SHA of the commit
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateCommitCommentOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Comment"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateCommitCommentOption)
|
||||
sha := ctx.PathParam("sha")
|
||||
|
||||
if len(sha) == 0 {
|
||||
ctx.Error(http.StatusBadRequest, "SHA is empty", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the commit
|
||||
commit, err := ctx.Repo.GitRepo.GetCommit(sha)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusNotFound, "GetCommit", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has permission to comment on this repo
|
||||
if !ctx.Repo.Permission.CanRead(repo_model.UnitTypeCode) {
|
||||
ctx.Error(http.StatusForbidden, "No permission to read code", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Create the comment
|
||||
comment, err := commit.CreateCommitComment(ctx, ctx.Doer, ctx.Repo.Repository, sha, form.Line, form.Path, form.Body, form.Attachments)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "CreateCommitComment", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Load poster
|
||||
if err := comment.LoadPoster(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadPoster", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify subscribers
|
||||
notify_service.CreateCommitComment(ctx, ctx.Doer, ctx.Repo.Repository, sha, comment)
|
||||
|
||||
// Convert to API format
|
||||
apiComment := convert.ToAPIComment(ctx, ctx.Repo.Repository, &repo_model.Comment{
|
||||
ID: comment.ID,
|
||||
PosterID: comment.PosterID,
|
||||
Poster: comment.Poster,
|
||||
Content: comment.Content,
|
||||
CreatedUnix: comment.CreatedUnix,
|
||||
UpdatedUnix: comment.UpdatedUnix,
|
||||
Type: repo_model.CommentTypeCommit,
|
||||
})
|
||||
|
||||
ctx.JSON(http.StatusCreated, apiComment)
|
||||
}
|
||||
|
||||
// DeleteCommitComment delete a commit comment
|
||||
func DeleteCommitComment(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/commits/comments/{id} repository repoDeleteCommitComment
|
||||
// ---
|
||||
// summary: Delete a commit comment
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of comment to delete
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
// Get the comment
|
||||
comment, err := repo_model.GetCommitCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusNotFound, "GetCommitCommentByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if user has permission to delete this comment
|
||||
if !ctx.Repo.Permission.CanWrite(repo_model.UnitTypeCode) && ctx.Doer.ID != comment.PosterID {
|
||||
ctx.Error(http.StatusForbidden, "No permission to delete comment", nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Notify subscribers
|
||||
notify_service.DeleteCommitComment(ctx, ctx.Doer, comment)
|
||||
|
||||
// Delete the comment
|
||||
if err := commit.DeleteCommitComment(ctx, ctx.Doer, comment); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "DeleteCommitComment", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
@ -49,217 +50,256 @@ func RefCommits(ctx *context.Context) {
|
||||
switch {
|
||||
case len(ctx.Repo.TreePath) == 0:
|
||||
Commits(ctx)
|
||||
return
|
||||
case ctx.Repo.TreePath == "graphs":
|
||||
if setting.Repository.EnableGitGraph {
|
||||
Graph(ctx)
|
||||
} else {
|
||||
ctx.NotFound("Graph", nil)
|
||||
}
|
||||
return
|
||||
case strings.HasPrefix(ctx.Repo.TreePath, "graphs/"):
|
||||
if setting.Repository.EnableGitGraph {
|
||||
GraphDiv(ctx)
|
||||
} else {
|
||||
ctx.NotFound("GraphDiv", nil)
|
||||
}
|
||||
return
|
||||
case ctx.Repo.TreePath == "search":
|
||||
SearchCommits(ctx)
|
||||
default:
|
||||
FileHistory(ctx)
|
||||
return
|
||||
case strings.HasPrefix(ctx.Repo.TreePath, "commits/"):
|
||||
CommitPage(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.NotFound("RefCommits", nil)
|
||||
}
|
||||
|
||||
// Commits render branch's commits
|
||||
func Commits(ctx *context.Context) {
|
||||
ctx.Data["PageIsCommits"] = true
|
||||
if ctx.Repo.Commit == nil {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["Title"] = ctx.Tr("repo.commits.commit_history")
|
||||
|
||||
commitsCount := ctx.Repo.CommitsCount
|
||||
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
|
||||
pageSize := ctx.FormInt("limit")
|
||||
if pageSize <= 0 {
|
||||
pageSize = setting.Git.CommitsRangeSize
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
// Both `git log branchName` and `git log commitId` work.
|
||||
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
|
||||
var (
|
||||
commitsCount int64
|
||||
commits []*git.Commit
|
||||
err error
|
||||
)
|
||||
|
||||
branchName := ctx.Repo.BranchName
|
||||
if len(ctx.Repo.TreePath) > 0 {
|
||||
branchName, err = ctx.Repo.Commit.Submodule(ctx.Repo.TreePath)
|
||||
if err != nil {
|
||||
ctx.ServerError("Submodule", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get the commits
|
||||
commits, err = ctx.Repo.GitRepo.CommitsByRangeWithSize(page, branchName)
|
||||
if err != nil {
|
||||
ctx.ServerError("CommitsByRange", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
|
||||
commitsCount, err = ctx.Repo.GitRepo.RevListCount([]string{branchName})
|
||||
if err != nil {
|
||||
ctx.ServerError("RevListCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get sign and verify info
|
||||
signCommitInfos, err := processGitCommits(ctx, commits)
|
||||
if err != nil {
|
||||
ctx.ServerError("processGitCommits", err)
|
||||
return
|
||||
}
|
||||
commitIDs := make([]string, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
commitIDs = append(commitIDs, c.ID.String())
|
||||
}
|
||||
commitsTagsMap, err := repo_model.FindTagsByCommitIDs(ctx, ctx.Repo.Repository.ID, commitIDs...)
|
||||
if err != nil {
|
||||
log.Error("FindTagsByCommitIDs: %v", err)
|
||||
ctx.Flash.Error(ctx.Tr("internal_error_skipped", "FindTagsByCommitIDs"))
|
||||
} else {
|
||||
ctx.Data["CommitsTagsMap"] = commitsTagsMap
|
||||
}
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
|
||||
pager := context.NewPagination(commitsCount, pageSize, page, 5)
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
ctx.Data["Commits"] = signCommitInfos
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
ctx.Data["Branch"] = ctx.Repo.BranchName
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["CommitID"] = ctx.Repo.CommitID
|
||||
|
||||
pager := context.NewPagination(int(commitsCount), setting.UI.ExplorePagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
// Graph render commit graph - show commits from all branches.
|
||||
func Graph(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("repo.commit_graph")
|
||||
ctx.Data["Title"] = ctx.Tr("repo.graph")
|
||||
ctx.Data["PageIsCommits"] = true
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
mode := strings.ToLower(ctx.FormTrim("mode"))
|
||||
if mode != "monochrome" {
|
||||
mode = "color"
|
||||
}
|
||||
ctx.Data["Mode"] = mode
|
||||
hidePRRefs := ctx.FormBool("hide-pr-refs")
|
||||
ctx.Data["HidePRRefs"] = hidePRRefs
|
||||
branches := ctx.FormStrings("branch")
|
||||
realBranches := make([]string, len(branches))
|
||||
copy(realBranches, branches)
|
||||
for i, branch := range realBranches {
|
||||
if strings.HasPrefix(branch, "--") {
|
||||
realBranches[i] = git.BranchPrefix + branch
|
||||
}
|
||||
}
|
||||
ctx.Data["SelectedBranches"] = realBranches
|
||||
files := ctx.FormStrings("file")
|
||||
|
||||
graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
|
||||
if err != nil {
|
||||
log.Warn("GetCommitGraphsCount error for generate graph exclude prs: %t branches: %s in %-v, Will Ignore branches and try again. Underlying Error: %v", hidePRRefs, branches, ctx.Repo.Repository, err)
|
||||
realBranches = []string{}
|
||||
graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitGraphsCount", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Data["PageIsGraph"] = true
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 1 {
|
||||
page = 0
|
||||
} else {
|
||||
page--
|
||||
}
|
||||
|
||||
graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files)
|
||||
commits, err := gitgraph.GetCommitGraph(ctx, ctx.Repo.GitRepo, page, setting.UI.GraphMaxCommitNum)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitGraph", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := graph.LoadAndProcessCommits(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil {
|
||||
ctx.ServerError("LoadAndProcessCommits", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Graph"] = graph
|
||||
|
||||
gitRefs, err := ctx.Repo.GitRepo.GetRefs()
|
||||
if err != nil {
|
||||
ctx.ServerError("GitRepo.GetRefs", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["AllRefs"] = gitRefs
|
||||
|
||||
divOnly := ctx.FormBool("div-only")
|
||||
queryParams := ctx.Req.URL.Query()
|
||||
queryParams.Del("div-only")
|
||||
paginator := context.NewPagination(graphCommitsCount, setting.UI.GraphMaxCommitNum, page, 5)
|
||||
paginator.AddParamFromQuery(queryParams)
|
||||
ctx.Data["Page"] = paginator
|
||||
if divOnly {
|
||||
ctx.HTML(http.StatusOK, tplGraphDiv)
|
||||
return
|
||||
}
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["CommitCount"] = commits.Count
|
||||
ctx.Data["Commits"] = commits.Commits
|
||||
|
||||
ctx.HTML(http.StatusOK, tplGraph)
|
||||
}
|
||||
|
||||
// GraphDiv render commit graph div - show commits from all branches.
|
||||
func GraphDiv(ctx *context.Context) {
|
||||
ctx.Data["PageIsCommits"] = true
|
||||
ctx.Data["PageIsGraph"] = true
|
||||
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 1 {
|
||||
page = 0
|
||||
} else {
|
||||
page--
|
||||
}
|
||||
|
||||
commits, err := gitgraph.GetCommitGraph(ctx, ctx.Repo.GitRepo, page, setting.UI.GraphMaxCommitNum)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitGraph", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["CommitCount"] = commits.Count
|
||||
ctx.Data["Commits"] = commits.Commits
|
||||
|
||||
ctx.HTML(http.StatusOK, tplGraphDiv)
|
||||
}
|
||||
|
||||
// SearchCommits render commits filtered by keyword
|
||||
func SearchCommits(ctx *context.Context) {
|
||||
ctx.Data["PageIsCommits"] = true
|
||||
ctx.Data["PageIsViewCode"] = true
|
||||
ctx.Data["Title"] = ctx.Tr("repo.commits.commit_history")
|
||||
|
||||
query := ctx.FormTrim("q")
|
||||
if len(query) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.RefTypeNameSubURL())
|
||||
keyword := ctx.FormTrim("q")
|
||||
if len(keyword) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName)
|
||||
return
|
||||
}
|
||||
|
||||
all := ctx.FormBool("all")
|
||||
opts := git.NewSearchCommitsOptions(query, all)
|
||||
commits, err := ctx.Repo.Commit.SearchCommits(opts)
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
commits, err := ctx.Repo.GitRepo.SearchCommits(keyword)
|
||||
if err != nil {
|
||||
ctx.ServerError("SearchCommits", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
|
||||
|
||||
// Get sign and verify info
|
||||
signCommitInfos, err := processGitCommits(ctx, commits)
|
||||
if err != nil {
|
||||
ctx.ServerError("processGitCommits", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Keyword"] = query
|
||||
if all {
|
||||
ctx.Data["All"] = true
|
||||
}
|
||||
ctx.Data["Commits"] = signCommitInfos
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["Keyword"] = keyword
|
||||
ctx.Data["Branch"] = ctx.Repo.BranchName
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
|
||||
pager := context.NewPagination(len(commits), setting.UI.ExplorePagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
// FileHistory show a file's reversions
|
||||
func FileHistory(ctx *context.Context) {
|
||||
if ctx.Repo.TreePath == "" {
|
||||
Commits(ctx)
|
||||
ctx.Data["IsRepoToolbarCommits"] = true
|
||||
ctx.Data["IsRepoToolbarFile"] = true
|
||||
|
||||
fileName := ctx.Repo.TreePath
|
||||
if len(fileName) == 0 {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName)
|
||||
return
|
||||
}
|
||||
|
||||
commitsCount, err := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository, ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath)
|
||||
page := ctx.FormInt("page")
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
// Get the commits
|
||||
commits, err := ctx.Repo.GitRepo.FileCommitsByRange(ctx.Repo.BranchName, fileName, page)
|
||||
if err != nil {
|
||||
ctx.ServerError("FileCommitsCount", err)
|
||||
return
|
||||
} else if commitsCount == 0 {
|
||||
ctx.NotFound(nil)
|
||||
ctx.ServerError("FileCommitsByRange", err)
|
||||
return
|
||||
}
|
||||
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
|
||||
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
|
||||
git.CommitsByFileAndRangeOptions{
|
||||
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
|
||||
File: ctx.Repo.TreePath,
|
||||
Page: page,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("CommitsByFileAndRange", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Commits"], err = processGitCommits(ctx, commits)
|
||||
// Get sign and verify info
|
||||
signCommitInfos, err := processGitCommits(ctx, commits)
|
||||
if err != nil {
|
||||
ctx.ServerError("processGitCommits", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["FileTreePath"] = ctx.Repo.TreePath
|
||||
ctx.Data["CommitCount"] = commitsCount
|
||||
// Get latest commit
|
||||
lastCommit := signCommitInfos[0]
|
||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
||||
ctx.Data["FileName"] = fileName
|
||||
ctx.Data["CommitCount"] = len(commits)
|
||||
ctx.Data["Commits"] = signCommitInfos
|
||||
ctx.Data["LastCommit"] = lastCommit
|
||||
ctx.Data["IsImageFile"] = lastCommit.Tree.IsImageFile(fileName)
|
||||
ctx.Data["Branch"] = ctx.Repo.BranchName
|
||||
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
|
||||
ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(fileName)
|
||||
|
||||
pager := context.NewPagination(commitsCount, setting.Git.CommitsRangeSize, page, 5)
|
||||
pager.AddParamFromRequest(ctx.Req)
|
||||
pager := context.NewPagination(len(commits), setting.UI.ExplorePagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.HTML(http.StatusOK, tplCommits)
|
||||
}
|
||||
|
||||
// LoadBranchesAndTags loads branches and tags from the repository
|
||||
func LoadBranchesAndTags(ctx *context.Context) {
|
||||
response, err := repo_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.PathParam("sha"))
|
||||
if err == nil {
|
||||
ctx.JSON(http.StatusOK, response)
|
||||
branches, err := ctx.Repo.Repository.GetBranches()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranches", err)
|
||||
return
|
||||
}
|
||||
ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.PathParam("sha")), git.IsErrNotExist, err)
|
||||
ctx.Data["Branches"] = branches
|
||||
|
||||
tags, err := ctx.Repo.Repository.GetTags(0, 0)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetTags", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Tags"] = tags
|
||||
}
|
||||
|
||||
// Diff show different from current commit to previous commit
|
||||
// Diff show different from current commit to previous commit of the file
|
||||
func Diff(ctx *context.Context) {
|
||||
ctx.Data["PageIsDiff"] = true
|
||||
|
||||
@ -354,6 +394,12 @@ func Diff(ctx *context.Context) {
|
||||
ctx.Data["Diff"] = diff
|
||||
ctx.Data["DiffBlobExcerptData"] = diffBlobExcerptData
|
||||
|
||||
// Load commit comments for diff lines
|
||||
if err := repo_model.LoadCommentsForDiffLines(ctx, ctx.Repo.Repository.ID, commitID, diff.Lines); err != nil {
|
||||
ctx.ServerError("LoadCommentsForDiffLines", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !fileOnly {
|
||||
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, parentCommitID, commitID)
|
||||
if err != nil {
|
||||
@ -418,50 +464,47 @@ func Diff(ctx *context.Context) {
|
||||
|
||||
// RawDiff dumps diff results of repository in given commit ID to io.Writer
|
||||
func RawDiff(ctx *context.Context) {
|
||||
var gitRepo *git.Repository
|
||||
if ctx.Data["PageIsWiki"] != nil {
|
||||
wikiRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository.WikiStorageRepo())
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer wikiRepo.Close()
|
||||
gitRepo = wikiRepo
|
||||
} else {
|
||||
gitRepo = ctx.Repo.GitRepo
|
||||
if gitRepo == nil {
|
||||
ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName()))
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := git.GetRawDiff(
|
||||
gitRepo,
|
||||
ctx.PathParam("sha"),
|
||||
git.RawDiffType(ctx.PathParam("ext")),
|
||||
ctx.Resp,
|
||||
); err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(errors.New("commit " + ctx.PathParam("sha") + " does not exist."))
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetRawDiff", err)
|
||||
commitID := ctx.PathParam("sha")
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
|
||||
commit, err := gitRepo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.ServerError("GetCommit", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
diff, err := commit.Diff(ctx.Query("ignore whitespace"))
|
||||
if err != nil {
|
||||
ctx.ServerError("Diff", err)
|
||||
return
|
||||
}
|
||||
defer diff.Free()
|
||||
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain")
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
if err := diff.Write(ctx.Resp, git.RawDiffFormat); err != nil {
|
||||
log.Error("Write: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) ([]*git_model.SignCommitWithStatuses, error) {
|
||||
commits, err := git_service.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ctx.Repo.Permission.CanRead(unit_model.TypeActions) {
|
||||
for _, commit := range commits {
|
||||
if commit.Status == nil {
|
||||
continue
|
||||
}
|
||||
commit.Status.HideActionsURL(ctx)
|
||||
git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses)
|
||||
commits := make([]*git_model.SignCommitWithStatuses, 0, len(gitCommits))
|
||||
for _, commit := range gitCommits {
|
||||
signCommit, err := git_model.GetSignCommit(ctx, ctx.Repo.Repository.ID, commit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commits = append(commits, signCommit)
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
|
||||
@ -82,4 +82,8 @@ type Notifier interface {
|
||||
WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun)
|
||||
|
||||
WorkflowJobStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, job *actions_model.ActionRunJob, task *actions_model.ActionTask)
|
||||
|
||||
// Commit comment methods
|
||||
CreateCommitComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitSHA string, comment *repo_model.CommitComment)
|
||||
DeleteCommitComment(ctx context.Context, doer *user_model.User, comment *repo_model.CommitComment)
|
||||
}
|
||||
|
||||
@ -17,6 +17,20 @@ import (
|
||||
"code.gitea.io/gitea/modules/repository"
|
||||
)
|
||||
|
||||
// NotifyCreateCommitComment notifies subscribers when a commit comment is created
|
||||
func NotifyCreateCommitComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commitSHA string, comment *repo_model.CommitComment) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.CreateCommitComment(ctx, doer, repo, commitSHA, comment)
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyDeleteCommitComment notifies subscribers when a commit comment is deleted
|
||||
func NotifyDeleteCommitComment(ctx context.Context, doer *user_model.User, comment *repo_model.CommitComment) {
|
||||
for _, notifier := range notifiers {
|
||||
notifier.DeleteCommitComment(ctx, doer, comment)
|
||||
}
|
||||
}
|
||||
|
||||
var notifiers []Notifier
|
||||
|
||||
// RegisterNotifier providers method to receive notify messages
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user