0
0
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:
serfersac 2026-05-09 18:51:24 +02:00 committed by GitHub
commit 51f87cf9ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 660 additions and 168 deletions

View File

@ -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
}

View 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
}

View 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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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