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