a5b1c1b0b3
- Add a indication to the file history if the file has been renamed, this indication contains a link to browse the history of the file further. - Added unit testing. - Added integration testing. - Resolves https://codeberg.org/forgejo/forgejo/issues/1279 (cherry picked from commit 72c297521b1830360aab4b50e37efcc7e67e0d5d) (cherry picked from commit 283f9648947f8dd2f315ecca19566ccca2b49c18) Conflicts: options/locale/locale_en-US.ini https://codeberg.org/forgejo/forgejo/pulls/1550 (cherry picked from commit 7c30af7fdee08efd02041c01abca47394a69bb8b) (cherry picked from commit f3be6eb269526a9f4ea7861189f07977f2d4a32f) (cherry picked from commit 78e1755b94c18c043e0c8f8c2849803cc8069feb) (cherry picked from commit 73799479e0fb68534dac10f809ee246dbc809b62) (cherry picked from commit 938359b94120b7ea7bcdfbfda265ada691620da1) (cherry picked from commit b168a9c081f93c10d40319333fc24d68a4f9763c) [GITEA] Detect file rename and show in history (squash) ctx.Locale (cherry picked from commit 40447752ff97aa306295685dcf4ddd3b13f48320) (cherry picked from commit ea23594cdbb12c32dc28638f65bf40e37d344e5f) (cherry picked from commit cdc473850c85abcbe38c799c2d2446966978f2b2) (cherry picked from commit 86e6641c29df213d7db1b85867dafebcafeee1dd) (cherry picked from commit 2757de586b80834513e61033692ac72d25381431) (cherry picked from commit def4ae32ddb4b0b83f6bb9c197e00fdcd784928e) (cherry picked from commit 6dada09329e28840f7ad890bed333ae122838fb2) (cherry picked from commit 5d6d5272513629b126917c30f7bfde421fdcbe27) Conflicts: tests/integration/repo_test.go https://codeberg.org/forgejo/forgejo/pulls/2119 (cherry picked from commit d3c1bce7db31b243a7142b71bf4af36506752e6e) (cherry picked from commit 04bcb22d5c00d1fa8b39e2a3cf2e73f0a8c1204f) (cherry picked from commit e16241fd992c22203d113a4a11e7f57f9ed2ddb3) (cherry picked from commit 8e2beb3ed5da1ac8a58608acdf059f607576ff96)
453 lines
12 KiB
Go
453 lines
12 KiB
Go
// Copyright 2014 The Gogs Authors. All rights reserved.
|
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
|
"code.gitea.io/gitea/models/db"
|
|
git_model "code.gitea.io/gitea/models/git"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/base"
|
|
"code.gitea.io/gitea/modules/charset"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/git"
|
|
"code.gitea.io/gitea/modules/gitgraph"
|
|
"code.gitea.io/gitea/modules/gitrepo"
|
|
"code.gitea.io/gitea/modules/log"
|
|
"code.gitea.io/gitea/modules/markup"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/services/gitdiff"
|
|
git_service "code.gitea.io/gitea/services/repository"
|
|
)
|
|
|
|
const (
|
|
tplCommits base.TplName = "repo/commits"
|
|
tplGraph base.TplName = "repo/graph"
|
|
tplGraphDiv base.TplName = "repo/graph/div"
|
|
tplCommitPage base.TplName = "repo/commit_page"
|
|
)
|
|
|
|
// RefCommits render commits page
|
|
func RefCommits(ctx *context.Context) {
|
|
switch {
|
|
case len(ctx.Repo.TreePath) == 0:
|
|
Commits(ctx)
|
|
case ctx.Repo.TreePath == "search":
|
|
SearchCommits(ctx)
|
|
default:
|
|
FileHistory(ctx)
|
|
}
|
|
}
|
|
|
|
// Commits render branch's commits
|
|
func Commits(ctx *context.Context) {
|
|
ctx.Data["PageIsCommits"] = true
|
|
if ctx.Repo.Commit == nil {
|
|
ctx.NotFound("Commit not found", nil)
|
|
return
|
|
}
|
|
ctx.Data["PageIsViewCode"] = true
|
|
|
|
commitsCount, err := ctx.Repo.GetCommitsCount()
|
|
if err != nil {
|
|
ctx.ServerError("GetCommitsCount", err)
|
|
return
|
|
}
|
|
|
|
page := ctx.FormInt("page")
|
|
if page <= 1 {
|
|
page = 1
|
|
}
|
|
|
|
pageSize := ctx.FormInt("limit")
|
|
if pageSize <= 0 {
|
|
pageSize = setting.Git.CommitsRangeSize
|
|
}
|
|
|
|
// Both `git log branchName` and `git log commitId` work.
|
|
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
|
|
if err != nil {
|
|
ctx.ServerError("CommitsByRange", err)
|
|
return
|
|
}
|
|
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
|
|
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
|
ctx.Data["CommitCount"] = commitsCount
|
|
|
|
pager := context.NewPagination(int(commitsCount), pageSize, 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["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")
|
|
|
|
commitsCount, err := ctx.Repo.GetCommitsCount()
|
|
if err != nil {
|
|
ctx.ServerError("GetCommitsCount", err)
|
|
return
|
|
}
|
|
|
|
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{}
|
|
branches = []string{}
|
|
graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
|
|
if err != nil {
|
|
ctx.ServerError("GetCommitGraphsCount", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
page := ctx.FormInt("page")
|
|
|
|
graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files)
|
|
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
|
|
|
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
|
ctx.Data["CommitCount"] = commitsCount
|
|
|
|
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
|
|
paginator.AddParam(ctx, "mode", "Mode")
|
|
paginator.AddParam(ctx, "hide-pr-refs", "HidePRRefs")
|
|
for _, branch := range branches {
|
|
paginator.AddParamString("branch", branch)
|
|
}
|
|
for _, file := range files {
|
|
paginator.AddParamString("file", file)
|
|
}
|
|
ctx.Data["Page"] = paginator
|
|
if ctx.FormBool("div-only") {
|
|
ctx.HTML(http.StatusOK, tplGraphDiv)
|
|
return
|
|
}
|
|
|
|
ctx.HTML(http.StatusOK, tplGraph)
|
|
}
|
|
|
|
// SearchCommits render commits filtered by keyword
|
|
func SearchCommits(ctx *context.Context) {
|
|
ctx.Data["PageIsCommits"] = true
|
|
ctx.Data["PageIsViewCode"] = true
|
|
|
|
query := ctx.FormTrim("q")
|
|
if len(query) == 0 {
|
|
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchNameSubURL())
|
|
return
|
|
}
|
|
|
|
all := ctx.FormBool("all")
|
|
opts := git.NewSearchCommitsOptions(query, all)
|
|
commits, err := ctx.Repo.Commit.SearchCommits(opts)
|
|
if err != nil {
|
|
ctx.ServerError("SearchCommits", err)
|
|
return
|
|
}
|
|
ctx.Data["CommitCount"] = len(commits)
|
|
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
|
|
|
ctx.Data["Keyword"] = query
|
|
if all {
|
|
ctx.Data["All"] = "checked"
|
|
}
|
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
|
ctx.HTML(http.StatusOK, tplCommits)
|
|
}
|
|
|
|
// FileHistory show a file's reversions
|
|
func FileHistory(ctx *context.Context) {
|
|
ctx.Data["IsRepoToolbarCommits"] = true
|
|
|
|
fileName := ctx.Repo.TreePath
|
|
if len(fileName) == 0 {
|
|
Commits(ctx)
|
|
return
|
|
}
|
|
|
|
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefName, fileName)
|
|
if err != nil {
|
|
ctx.ServerError("FileCommitsCount", err)
|
|
return
|
|
} else if commitsCount == 0 {
|
|
ctx.NotFound("FileCommitsCount", nil)
|
|
return
|
|
}
|
|
|
|
page := ctx.FormInt("page")
|
|
if page <= 1 {
|
|
page = 1
|
|
}
|
|
|
|
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
|
|
git.CommitsByFileAndRangeOptions{
|
|
Revision: ctx.Repo.RefName,
|
|
File: fileName,
|
|
Page: page,
|
|
})
|
|
if err != nil {
|
|
ctx.ServerError("CommitsByFileAndRange", err)
|
|
return
|
|
}
|
|
oldestCommit := commits[len(commits)-1]
|
|
|
|
renamedFiles, err := git.GetCommitFileRenames(ctx, ctx.Repo.GitRepo.Path, oldestCommit.ID.String())
|
|
if err != nil {
|
|
ctx.ServerError("GetCommitFileRenames", err)
|
|
return
|
|
}
|
|
|
|
for _, renames := range renamedFiles {
|
|
if renames[1] == fileName {
|
|
ctx.Data["OldFilename"] = renames[0]
|
|
ctx.Data["OldFilenameHistory"] = fmt.Sprintf("%s/commits/commit/%s/%s", ctx.Repo.RepoLink, oldestCommit.ID.String(), renames[0])
|
|
break
|
|
}
|
|
}
|
|
|
|
ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
|
|
|
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
|
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
|
|
ctx.Data["FileName"] = fileName
|
|
ctx.Data["CommitCount"] = commitsCount
|
|
|
|
pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
|
|
pager.SetDefaultParams(ctx)
|
|
ctx.Data["Page"] = pager
|
|
|
|
ctx.HTML(http.StatusOK, tplCommits)
|
|
}
|
|
|
|
func LoadBranchesAndTags(ctx *context.Context) {
|
|
response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.Params("sha"))
|
|
if err == nil {
|
|
ctx.JSON(http.StatusOK, response)
|
|
return
|
|
}
|
|
ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.Params("sha")), git.IsErrNotExist, err)
|
|
}
|
|
|
|
// Diff show different from current commit to previous commit
|
|
func Diff(ctx *context.Context) {
|
|
ctx.Data["PageIsDiff"] = true
|
|
|
|
userName := ctx.Repo.Owner.Name
|
|
repoName := ctx.Repo.Repository.Name
|
|
commitID := ctx.Params(":sha")
|
|
var (
|
|
gitRepo *git.Repository
|
|
err error
|
|
)
|
|
|
|
if ctx.Data["PageIsWiki"] != nil {
|
|
gitRepo, err = gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
|
if err != nil {
|
|
ctx.ServerError("Repo.GitRepo.GetCommit", err)
|
|
return
|
|
}
|
|
defer gitRepo.Close()
|
|
} else {
|
|
gitRepo = ctx.Repo.GitRepo
|
|
}
|
|
|
|
commit, err := gitRepo.GetCommit(commitID)
|
|
if err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.NotFound("Repo.GitRepo.GetCommit", err)
|
|
} else {
|
|
ctx.ServerError("Repo.GitRepo.GetCommit", err)
|
|
}
|
|
return
|
|
}
|
|
if len(commitID) != commit.ID.Type().FullLength() {
|
|
commitID = commit.ID.String()
|
|
}
|
|
|
|
fileOnly := ctx.FormBool("file-only")
|
|
maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
|
|
files := ctx.FormStrings("files")
|
|
if fileOnly && (len(files) == 2 || len(files) == 1) {
|
|
maxLines, maxFiles = -1, -1
|
|
}
|
|
|
|
diff, err := gitdiff.GetDiff(ctx, gitRepo, &gitdiff.DiffOptions{
|
|
AfterCommitID: commitID,
|
|
SkipTo: ctx.FormString("skip-to"),
|
|
MaxLines: maxLines,
|
|
MaxLineCharacters: setting.Git.MaxGitDiffLineCharacters,
|
|
MaxFiles: maxFiles,
|
|
WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
|
|
}, files...)
|
|
if err != nil {
|
|
ctx.NotFound("GetDiff", err)
|
|
return
|
|
}
|
|
|
|
parents := make([]string, commit.ParentCount())
|
|
for i := 0; i < commit.ParentCount(); i++ {
|
|
sha, err := commit.ParentID(i)
|
|
if err != nil {
|
|
ctx.NotFound("repo.Diff", err)
|
|
return
|
|
}
|
|
parents[i] = sha.String()
|
|
}
|
|
|
|
ctx.Data["CommitID"] = commitID
|
|
ctx.Data["AfterCommitID"] = commitID
|
|
ctx.Data["Username"] = userName
|
|
ctx.Data["Reponame"] = repoName
|
|
|
|
var parentCommit *git.Commit
|
|
if commit.ParentCount() > 0 {
|
|
parentCommit, err = gitRepo.GetCommit(parents[0])
|
|
if err != nil {
|
|
ctx.NotFound("GetParentCommit", err)
|
|
return
|
|
}
|
|
}
|
|
setCompareContext(ctx, parentCommit, commit, userName, repoName)
|
|
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
|
|
ctx.Data["Commit"] = commit
|
|
ctx.Data["Diff"] = diff
|
|
|
|
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{ListAll: true})
|
|
if err != nil {
|
|
log.Error("GetLatestCommitStatus: %v", err)
|
|
}
|
|
|
|
ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
|
|
ctx.Data["CommitStatuses"] = statuses
|
|
|
|
verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
|
|
ctx.Data["Verification"] = verification
|
|
ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit)
|
|
ctx.Data["Parents"] = parents
|
|
ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
|
|
|
|
if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
|
|
return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID)
|
|
}, nil); err != nil {
|
|
ctx.ServerError("CalculateTrustStatus", err)
|
|
return
|
|
}
|
|
|
|
note := &git.Note{}
|
|
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
|
|
if err == nil {
|
|
ctx.Data["NoteCommit"] = note.Commit
|
|
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
|
|
ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
|
|
Links: markup.Links{
|
|
Base: ctx.Repo.RepoLink,
|
|
BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
|
|
},
|
|
Metas: ctx.Repo.Repository.ComposeMetas(ctx),
|
|
GitRepo: ctx.Repo.GitRepo,
|
|
Ctx: ctx,
|
|
}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
|
|
if err != nil {
|
|
ctx.ServerError("RenderCommitMessage", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
ctx.Data["BranchName"], err = commit.GetBranchName()
|
|
if err != nil {
|
|
ctx.ServerError("commit.GetBranchName", err)
|
|
return
|
|
}
|
|
|
|
ctx.HTML(http.StatusOK, tplCommitPage)
|
|
}
|
|
|
|
// 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.OpenWikiRepository(ctx, ctx.Repo.Repository)
|
|
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.Params(":sha"),
|
|
git.RawDiffType(ctx.Params(":ext")),
|
|
ctx.Resp,
|
|
); err != nil {
|
|
if git.IsErrNotExist(err) {
|
|
ctx.NotFound("GetRawDiff",
|
|
errors.New("commit "+ctx.Params(":sha")+" does not exist."))
|
|
return
|
|
}
|
|
ctx.ServerError("GetRawDiff", err)
|
|
return
|
|
}
|
|
}
|