code.gitea.io/gitea@v1.21.7/routers/web/repo/commit.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2019 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"html/template"
    11  	"net/http"
    12  	"path"
    13  	"strings"
    14  
    15  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    16  	"code.gitea.io/gitea/models/db"
    17  	git_model "code.gitea.io/gitea/models/git"
    18  	repo_model "code.gitea.io/gitea/models/repo"
    19  	user_model "code.gitea.io/gitea/models/user"
    20  	"code.gitea.io/gitea/modules/base"
    21  	"code.gitea.io/gitea/modules/charset"
    22  	"code.gitea.io/gitea/modules/context"
    23  	"code.gitea.io/gitea/modules/git"
    24  	"code.gitea.io/gitea/modules/gitgraph"
    25  	"code.gitea.io/gitea/modules/log"
    26  	"code.gitea.io/gitea/modules/markup"
    27  	"code.gitea.io/gitea/modules/setting"
    28  	"code.gitea.io/gitea/modules/util"
    29  	"code.gitea.io/gitea/services/gitdiff"
    30  	git_service "code.gitea.io/gitea/services/repository"
    31  )
    32  
    33  const (
    34  	tplCommits    base.TplName = "repo/commits"
    35  	tplGraph      base.TplName = "repo/graph"
    36  	tplGraphDiv   base.TplName = "repo/graph/div"
    37  	tplCommitPage base.TplName = "repo/commit_page"
    38  )
    39  
    40  // RefCommits render commits page
    41  func RefCommits(ctx *context.Context) {
    42  	switch {
    43  	case len(ctx.Repo.TreePath) == 0:
    44  		Commits(ctx)
    45  	case ctx.Repo.TreePath == "search":
    46  		SearchCommits(ctx)
    47  	default:
    48  		FileHistory(ctx)
    49  	}
    50  }
    51  
    52  // Commits render branch's commits
    53  func Commits(ctx *context.Context) {
    54  	ctx.Data["PageIsCommits"] = true
    55  	if ctx.Repo.Commit == nil {
    56  		ctx.NotFound("Commit not found", nil)
    57  		return
    58  	}
    59  	ctx.Data["PageIsViewCode"] = true
    60  
    61  	commitsCount, err := ctx.Repo.GetCommitsCount()
    62  	if err != nil {
    63  		ctx.ServerError("GetCommitsCount", err)
    64  		return
    65  	}
    66  
    67  	page := ctx.FormInt("page")
    68  	if page <= 1 {
    69  		page = 1
    70  	}
    71  
    72  	pageSize := ctx.FormInt("limit")
    73  	if pageSize <= 0 {
    74  		pageSize = setting.Git.CommitsRangeSize
    75  	}
    76  
    77  	// Both `git log branchName` and `git log commitId` work.
    78  	commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
    79  	if err != nil {
    80  		ctx.ServerError("CommitsByRange", err)
    81  		return
    82  	}
    83  	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
    84  
    85  	ctx.Data["Username"] = ctx.Repo.Owner.Name
    86  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
    87  	ctx.Data["CommitCount"] = commitsCount
    88  
    89  	pager := context.NewPagination(int(commitsCount), pageSize, page, 5)
    90  	pager.SetDefaultParams(ctx)
    91  	ctx.Data["Page"] = pager
    92  
    93  	ctx.HTML(http.StatusOK, tplCommits)
    94  }
    95  
    96  // Graph render commit graph - show commits from all branches.
    97  func Graph(ctx *context.Context) {
    98  	ctx.Data["Title"] = ctx.Tr("repo.commit_graph")
    99  	ctx.Data["PageIsCommits"] = true
   100  	ctx.Data["PageIsViewCode"] = true
   101  	mode := strings.ToLower(ctx.FormTrim("mode"))
   102  	if mode != "monochrome" {
   103  		mode = "color"
   104  	}
   105  	ctx.Data["Mode"] = mode
   106  	hidePRRefs := ctx.FormBool("hide-pr-refs")
   107  	ctx.Data["HidePRRefs"] = hidePRRefs
   108  	branches := ctx.FormStrings("branch")
   109  	realBranches := make([]string, len(branches))
   110  	copy(realBranches, branches)
   111  	for i, branch := range realBranches {
   112  		if strings.HasPrefix(branch, "--") {
   113  			realBranches[i] = git.BranchPrefix + branch
   114  		}
   115  	}
   116  	ctx.Data["SelectedBranches"] = realBranches
   117  	files := ctx.FormStrings("file")
   118  
   119  	commitsCount, err := ctx.Repo.GetCommitsCount()
   120  	if err != nil {
   121  		ctx.ServerError("GetCommitsCount", err)
   122  		return
   123  	}
   124  
   125  	graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
   126  	if err != nil {
   127  		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)
   128  		realBranches = []string{}
   129  		branches = []string{}
   130  		graphCommitsCount, err = ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
   131  		if err != nil {
   132  			ctx.ServerError("GetCommitGraphsCount", err)
   133  			return
   134  		}
   135  	}
   136  
   137  	page := ctx.FormInt("page")
   138  
   139  	graph, err := gitgraph.GetCommitGraph(ctx.Repo.GitRepo, page, 0, hidePRRefs, realBranches, files)
   140  	if err != nil {
   141  		ctx.ServerError("GetCommitGraph", err)
   142  		return
   143  	}
   144  
   145  	if err := graph.LoadAndProcessCommits(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo); err != nil {
   146  		ctx.ServerError("LoadAndProcessCommits", err)
   147  		return
   148  	}
   149  
   150  	ctx.Data["Graph"] = graph
   151  
   152  	gitRefs, err := ctx.Repo.GitRepo.GetRefs()
   153  	if err != nil {
   154  		ctx.ServerError("GitRepo.GetRefs", err)
   155  		return
   156  	}
   157  
   158  	ctx.Data["AllRefs"] = gitRefs
   159  
   160  	ctx.Data["Username"] = ctx.Repo.Owner.Name
   161  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
   162  	ctx.Data["CommitCount"] = commitsCount
   163  
   164  	paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
   165  	paginator.AddParam(ctx, "mode", "Mode")
   166  	paginator.AddParam(ctx, "hide-pr-refs", "HidePRRefs")
   167  	for _, branch := range branches {
   168  		paginator.AddParamString("branch", branch)
   169  	}
   170  	for _, file := range files {
   171  		paginator.AddParamString("file", file)
   172  	}
   173  	ctx.Data["Page"] = paginator
   174  	if ctx.FormBool("div-only") {
   175  		ctx.HTML(http.StatusOK, tplGraphDiv)
   176  		return
   177  	}
   178  
   179  	ctx.HTML(http.StatusOK, tplGraph)
   180  }
   181  
   182  // SearchCommits render commits filtered by keyword
   183  func SearchCommits(ctx *context.Context) {
   184  	ctx.Data["PageIsCommits"] = true
   185  	ctx.Data["PageIsViewCode"] = true
   186  
   187  	query := ctx.FormTrim("q")
   188  	if len(query) == 0 {
   189  		ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchNameSubURL())
   190  		return
   191  	}
   192  
   193  	all := ctx.FormBool("all")
   194  	opts := git.NewSearchCommitsOptions(query, all)
   195  	commits, err := ctx.Repo.Commit.SearchCommits(opts)
   196  	if err != nil {
   197  		ctx.ServerError("SearchCommits", err)
   198  		return
   199  	}
   200  	ctx.Data["CommitCount"] = len(commits)
   201  	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
   202  
   203  	ctx.Data["Keyword"] = query
   204  	if all {
   205  		ctx.Data["All"] = "checked"
   206  	}
   207  	ctx.Data["Username"] = ctx.Repo.Owner.Name
   208  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
   209  	ctx.HTML(http.StatusOK, tplCommits)
   210  }
   211  
   212  // FileHistory show a file's reversions
   213  func FileHistory(ctx *context.Context) {
   214  	ctx.Data["IsRepoToolbarCommits"] = true
   215  
   216  	fileName := ctx.Repo.TreePath
   217  	if len(fileName) == 0 {
   218  		Commits(ctx)
   219  		return
   220  	}
   221  
   222  	commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefName, fileName)
   223  	if err != nil {
   224  		ctx.ServerError("FileCommitsCount", err)
   225  		return
   226  	} else if commitsCount == 0 {
   227  		ctx.NotFound("FileCommitsCount", nil)
   228  		return
   229  	}
   230  
   231  	page := ctx.FormInt("page")
   232  	if page <= 1 {
   233  		page = 1
   234  	}
   235  
   236  	commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
   237  		git.CommitsByFileAndRangeOptions{
   238  			Revision: ctx.Repo.RefName,
   239  			File:     fileName,
   240  			Page:     page,
   241  		})
   242  	if err != nil {
   243  		ctx.ServerError("CommitsByFileAndRange", err)
   244  		return
   245  	}
   246  	ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commits, ctx.Repo.Repository)
   247  
   248  	ctx.Data["Username"] = ctx.Repo.Owner.Name
   249  	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
   250  	ctx.Data["FileName"] = fileName
   251  	ctx.Data["CommitCount"] = commitsCount
   252  
   253  	pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
   254  	pager.SetDefaultParams(ctx)
   255  	ctx.Data["Page"] = pager
   256  
   257  	ctx.HTML(http.StatusOK, tplCommits)
   258  }
   259  
   260  func LoadBranchesAndTags(ctx *context.Context) {
   261  	response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.Params("sha"))
   262  	if err == nil {
   263  		ctx.JSON(http.StatusOK, response)
   264  		return
   265  	}
   266  	ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.Params("sha")), git.IsErrNotExist, err)
   267  }
   268  
   269  // Diff show different from current commit to previous commit
   270  func Diff(ctx *context.Context) {
   271  	ctx.Data["PageIsDiff"] = true
   272  
   273  	userName := ctx.Repo.Owner.Name
   274  	repoName := ctx.Repo.Repository.Name
   275  	commitID := ctx.Params(":sha")
   276  	var (
   277  		gitRepo *git.Repository
   278  		err     error
   279  	)
   280  
   281  	if ctx.Data["PageIsWiki"] != nil {
   282  		gitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
   283  		if err != nil {
   284  			ctx.ServerError("Repo.GitRepo.GetCommit", err)
   285  			return
   286  		}
   287  		defer gitRepo.Close()
   288  	} else {
   289  		gitRepo = ctx.Repo.GitRepo
   290  	}
   291  
   292  	commit, err := gitRepo.GetCommit(commitID)
   293  	if err != nil {
   294  		if git.IsErrNotExist(err) {
   295  			ctx.NotFound("Repo.GitRepo.GetCommit", err)
   296  		} else {
   297  			ctx.ServerError("Repo.GitRepo.GetCommit", err)
   298  		}
   299  		return
   300  	}
   301  	if len(commitID) != git.SHAFullLength {
   302  		commitID = commit.ID.String()
   303  	}
   304  
   305  	fileOnly := ctx.FormBool("file-only")
   306  	maxLines, maxFiles := setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffFiles
   307  	files := ctx.FormStrings("files")
   308  	if fileOnly && (len(files) == 2 || len(files) == 1) {
   309  		maxLines, maxFiles = -1, -1
   310  	}
   311  
   312  	diff, err := gitdiff.GetDiff(gitRepo, &gitdiff.DiffOptions{
   313  		AfterCommitID:      commitID,
   314  		SkipTo:             ctx.FormString("skip-to"),
   315  		MaxLines:           maxLines,
   316  		MaxLineCharacters:  setting.Git.MaxGitDiffLineCharacters,
   317  		MaxFiles:           maxFiles,
   318  		WhitespaceBehavior: gitdiff.GetWhitespaceFlag(ctx.Data["WhitespaceBehavior"].(string)),
   319  	}, files...)
   320  	if err != nil {
   321  		ctx.NotFound("GetDiff", err)
   322  		return
   323  	}
   324  
   325  	parents := make([]string, commit.ParentCount())
   326  	for i := 0; i < commit.ParentCount(); i++ {
   327  		sha, err := commit.ParentID(i)
   328  		if err != nil {
   329  			ctx.NotFound("repo.Diff", err)
   330  			return
   331  		}
   332  		parents[i] = sha.String()
   333  	}
   334  
   335  	ctx.Data["CommitID"] = commitID
   336  	ctx.Data["AfterCommitID"] = commitID
   337  	ctx.Data["Username"] = userName
   338  	ctx.Data["Reponame"] = repoName
   339  
   340  	var parentCommit *git.Commit
   341  	if commit.ParentCount() > 0 {
   342  		parentCommit, err = gitRepo.GetCommit(parents[0])
   343  		if err != nil {
   344  			ctx.NotFound("GetParentCommit", err)
   345  			return
   346  		}
   347  	}
   348  	setCompareContext(ctx, parentCommit, commit, userName, repoName)
   349  	ctx.Data["Title"] = commit.Summary() + " ยท " + base.ShortSha(commitID)
   350  	ctx.Data["Commit"] = commit
   351  	ctx.Data["Diff"] = diff
   352  
   353  	statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptions{ListAll: true})
   354  	if err != nil {
   355  		log.Error("GetLatestCommitStatus: %v", err)
   356  	}
   357  
   358  	ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses)
   359  	ctx.Data["CommitStatuses"] = statuses
   360  
   361  	verification := asymkey_model.ParseCommitWithSignature(ctx, commit)
   362  	ctx.Data["Verification"] = verification
   363  	ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, commit)
   364  	ctx.Data["Parents"] = parents
   365  	ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
   366  
   367  	if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) {
   368  		return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID)
   369  	}, nil); err != nil {
   370  		ctx.ServerError("CalculateTrustStatus", err)
   371  		return
   372  	}
   373  
   374  	note := &git.Note{}
   375  	err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
   376  	if err == nil {
   377  		ctx.Data["NoteCommit"] = note.Commit
   378  		ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
   379  		ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
   380  			Links: markup.Links{
   381  				Base:       ctx.Repo.RepoLink,
   382  				BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
   383  			},
   384  			Metas:   ctx.Repo.Repository.ComposeMetas(),
   385  			GitRepo: ctx.Repo.GitRepo,
   386  			Ctx:     ctx,
   387  		}, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
   388  		if err != nil {
   389  			ctx.ServerError("RenderCommitMessage", err)
   390  			return
   391  		}
   392  	}
   393  
   394  	ctx.Data["BranchName"], err = commit.GetBranchName()
   395  	if err != nil {
   396  		ctx.ServerError("commit.GetBranchName", err)
   397  		return
   398  	}
   399  
   400  	ctx.HTML(http.StatusOK, tplCommitPage)
   401  }
   402  
   403  // RawDiff dumps diff results of repository in given commit ID to io.Writer
   404  func RawDiff(ctx *context.Context) {
   405  	var gitRepo *git.Repository
   406  	if ctx.Data["PageIsWiki"] != nil {
   407  		wikiRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.WikiPath())
   408  		if err != nil {
   409  			ctx.ServerError("OpenRepository", err)
   410  			return
   411  		}
   412  		defer wikiRepo.Close()
   413  		gitRepo = wikiRepo
   414  	} else {
   415  		gitRepo = ctx.Repo.GitRepo
   416  		if gitRepo == nil {
   417  			ctx.ServerError("GitRepo not open", fmt.Errorf("no open git repo for '%s'", ctx.Repo.Repository.FullName()))
   418  			return
   419  		}
   420  	}
   421  	if err := git.GetRawDiff(
   422  		gitRepo,
   423  		ctx.Params(":sha"),
   424  		git.RawDiffType(ctx.Params(":ext")),
   425  		ctx.Resp,
   426  	); err != nil {
   427  		if git.IsErrNotExist(err) {
   428  			ctx.NotFound("GetRawDiff",
   429  				errors.New("commit "+ctx.Params(":sha")+" does not exist."))
   430  			return
   431  		}
   432  		ctx.ServerError("GetRawDiff", err)
   433  		return
   434  	}
   435  }