diff --git a/modules/context/context_response.go b/modules/context/context_response.go index bb3ccf69ce421..9dc6d1fc0ec5f 100644 --- a/modules/context/context_response.go +++ b/modules/context/context_response.go @@ -166,6 +166,7 @@ func (ctx *Context) serverErrorInternal(logMsg string, logErr error) { // NotFoundOrServerError use error check function to determine if the error // is about not found. It responds with 404 status code for not found error, // or error context description for logging purpose of 500 server error. +// TODO: remove the "errCheck" and use util.ErrNotFound to check func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) { if errCheck(logErr) { ctx.notFoundInternal(logMsg, logErr) diff --git a/modules/git/commit.go b/modules/git/commit.go index 729e3b4672a7c..c44882d886171 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -20,7 +20,6 @@ import ( // Commit represents a git commit. type Commit struct { - Branch string // Branch this commit belongs to Tree ID SHA1 // The ID of this commit object Author *Signature @@ -432,31 +431,6 @@ func (c *Commit) GetBranchName() (string, error) { return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil } -// LoadBranchName load branch name for commit -func (c *Commit) LoadBranchName() (err error) { - if len(c.Branch) != 0 { - return nil - } - - c.Branch, err = c.GetBranchName() - return err -} - -// GetTagName gets the current tag name for given commit -func (c *Commit) GetTagName() (string, error) { - data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always").AddDynamicArguments(c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path}) - if err != nil { - // handle special case where there is no tag for this commit - if strings.Contains(err.Error(), "no tag exactly matches") { - return "", nil - } - - return "", err - } - - return strings.TrimSpace(data), nil -} - // CommitFileStatus represents status of files in a commit. type CommitFileStatus struct { Added []string diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 54e424bb832ab..8eaa17cb04182 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -3,7 +3,61 @@ package git +import ( + "context" + "strings" + + "code.gitea.io/gitea/modules/util" +) + // GetRefs returns all references of the repository. func (repo *Repository) GetRefs() ([]*Reference, error) { return repo.GetRefsFiltered("") } + +// ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC +// refType should only be a literal "branch" or "tag" and nothing else +func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { + cmd := NewCommand(ctx) + if refType == "branch" { + cmd.AddArguments("branch") + } else if refType == "tag" { + cmd.AddArguments("tag") + } else { + return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) + } + stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(&RunOpts{Dir: repo.Path}) + if err != nil { + return nil, err + } + + refs := strings.Split(strings.TrimSpace(stdout), "\n") + if refType == "branch" { + return parseBranches(refs), nil + } + return parseTags(refs), nil +} + +func parseBranches(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if strings.HasPrefix(ref, "* ") { // current branch (main branch) + results = append(results, ref[len("* "):]) + } else if strings.HasPrefix(ref, " ") { // all other branches + results = append(results, ref[len(" "):]) + } else if ref != "" { + results = append(results, ref) + } + } + return results +} + +func parseTags(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if ref != "" { + results = append(results, ref) + } + } + return results +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 25fb155435760..dc88c422b5ea1 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1170,6 +1170,9 @@ commit_graph.select = Select branches commit_graph.hide_pr_refs = Hide Pull Requests commit_graph.monochrome = Mono commit_graph.color = Color +commit.contained_in = This commit is contained in: +commit.contained_in_default_branch = This commit is part of the default branch +commit.load_referencing_branches_and_tags = Load branches and tags referencing this commit blame = Blame download_file = Download file normal_view = Normal View diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index e88f1139f8b7e..5b32591b89140 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/gitdiff" + git_service "code.gitea.io/gitea/services/repository" ) const ( @@ -255,6 +256,15 @@ func FileHistory(ctx *context.Context) { 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 @@ -374,11 +384,6 @@ func Diff(ctx *context.Context) { return } - ctx.Data["TagName"], err = commit.GetTagName() - if err != nil { - ctx.ServerError("commit.GetTagName", err) - return - } ctx.HTML(http.StatusOK, tplCommitPage) } diff --git a/routers/web/web.go b/routers/web/web.go index d7ef2fb82f229..0b519614453a7 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1337,6 +1337,7 @@ func registerRoutes(m *web.Route) { m.Group("", func() { m.Get("/graph", repo.Graph) m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) + m.Get("/commit/{sha:([a-f0-9]{7,40})$}/load-branches-and-tags", repo.LoadBranchesAndTags) m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick) }, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader) diff --git a/services/repository/commit.go b/services/repository/commit.go new file mode 100644 index 0000000000000..2497910a838d7 --- /dev/null +++ b/services/repository/commit.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + "fmt" + + gitea_ctx "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/util" +) + +type ContainedLinks struct { // TODO: better name? + Branches []*namedLink `json:"branches"` + Tags []*namedLink `json:"tags"` + DefaultBranch string `json:"default_branch"` +} + +type namedLink struct { // TODO: better name? + Name string `json:"name"` + WebLink string `json:"web_link"` +} + +// LoadBranchesAndTags creates a new repository branch +func LoadBranchesAndTags(ctx context.Context, baseRepo *gitea_ctx.Repository, commitSHA string) (*ContainedLinks, error) { + containedTags, err := baseRepo.GitRepo.ListOccurrences(ctx, "tag", commitSHA) + if err != nil { + return nil, fmt.Errorf("encountered a problem while querying %s: %w", "tags", err) + } + containedBranches, err := baseRepo.GitRepo.ListOccurrences(ctx, "branch", commitSHA) + if err != nil { + return nil, fmt.Errorf("encountered a problem while querying %s: %w", "branches", err) + } + + result := &ContainedLinks{ + DefaultBranch: baseRepo.Repository.DefaultBranch, + Branches: make([]*namedLink, 0, len(containedBranches)), + Tags: make([]*namedLink, 0, len(containedTags)), + } + for _, tag := range containedTags { + // TODO: Use a common method to get the link to a branch/tag instead of hard-coding it here + result.Tags = append(result.Tags, &namedLink{ + Name: tag, + WebLink: fmt.Sprintf("%s/src/tag/%s", baseRepo.RepoLink, util.PathEscapeSegments(tag)), + }) + } + for _, branch := range containedBranches { + result.Branches = append(result.Branches, &namedLink{ + Name: branch, + WebLink: fmt.Sprintf("%s/src/branch/%s", baseRepo.RepoLink, util.PathEscapeSegments(branch)), + }) + } + return result, nil +} diff --git a/templates/repo/commit_load_branches_and_tags.tmpl b/templates/repo/commit_load_branches_and_tags.tmpl new file mode 100644 index 0000000000000..c19aa55c56197 --- /dev/null +++ b/templates/repo/commit_load_branches_and_tags.tmpl @@ -0,0 +1,18 @@ +
+ +
+
+
{{.locale.Tr "repo.commit.contained_in"}}
+
+
{{svg "octicon-git-branch"}}
+
+
+
+
{{svg "octicon-tag"}}
+
+
+
+
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index e4aad30fa7cf1..bd33a304435a1 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -137,12 +137,7 @@ {{if IsMultilineCommitMessage .Commit.Message}}
{{RenderCommitBody $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}
{{end}} - {{if .BranchName}} - {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{.BranchName}} - {{end}} - {{if .TagName}} - {{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}} - {{end}} + {{template "repo/commit_load_branches_and_tags" .}}
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 6768bcb5137f0..ef9d0654566f5 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -70,7 +70,7 @@ {{end}} {{if IsMultilineCommitMessage .Message}} - + {{end}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $}} {{if IsMultilineCommitMessage .Message}} diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl index 6bbc19529f942..57c9fd17ef775 100644 --- a/templates/repo/commits_list_small.tmpl +++ b/templates/repo/commits_list_small.tmpl @@ -40,7 +40,7 @@ {{RenderCommitMessageLinkSubject $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}} {{if IsMultilineCommitMessage .Message}} - + {{end}} {{if IsMultilineCommitMessage .Message}}
{{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}}
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 13b4d3d3d322f..3eabf9f181e57 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -28,7 +28,7 @@ {{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}} {{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}} {{if IsMultilineCommitMessage .LatestCommit.Message}} - +
{{RenderCommitBody $.Context .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}
{{end}}
diff --git a/web_src/js/features/repo-commit.js b/web_src/js/features/repo-commit.js index 7e4e40806b6c1..7240bf398aec6 100644 --- a/web_src/js/features/repo-commit.js +++ b/web_src/js/features/repo-commit.js @@ -5,7 +5,7 @@ import {toggleElem} from '../utils/dom.js'; const {csrfToken} = window.config; export function initRepoEllipsisButton() { - $('.ellipsis-button').on('click', function (e) { + $('.js-toggle-commit-body').on('click', function (e) { e.preventDefault(); const expanded = $(this).attr('aria-expanded') === 'true'; toggleElem($(this).parent().find('.commit-body')); diff --git a/web_src/js/features/repo-diff-commit.js b/web_src/js/features/repo-diff-commit.js new file mode 100644 index 0000000000000..968f318e63d5d --- /dev/null +++ b/web_src/js/features/repo-diff-commit.js @@ -0,0 +1,52 @@ +import {hideElem, showElem, toggleElem} from '../utils/dom.js'; + +async function loadBranchesAndTags(area, loadingButton) { + loadingButton.classList.add('disabled'); + try { + const res = await fetch(loadingButton.getAttribute('data-fetch-url')); + const data = await res.json(); + hideElem(loadingButton); + addTags(area, data.tags); + addBranches(area, data.branches, data.default_branch); + showElem(area.querySelectorAll('.branch-and-tag-detail')); + } finally { + loadingButton.classList.remove('disabled'); + } +} + +function addTags(area, tags) { + const tagArea = area.querySelector('.tag-area'); + toggleElem(tagArea, tags.length > 0); + for (const tag of tags) { + addLink(tagArea, tag.web_link, tag.name); + } +} + +function addBranches(area, branches, defaultBranch) { + const defaultBranchTooltip = area.getAttribute('data-text-default-branch-tooltip'); + const branchArea = area.querySelector('.branch-area'); + toggleElem(branchArea, branches.length > 0); + for (const branch of branches) { + const tooltip = defaultBranch === branch.name ? defaultBranchTooltip : null; + addLink(branchArea, branch.web_link, branch.name, tooltip); + } +} + +function addLink(parent, href, text, tooltip) { + const link = document.createElement('a'); + link.classList.add('muted', 'gt-px-2'); + link.href = href; + link.textContent = text; + if (tooltip) { + link.classList.add('gt-border-secondary', 'gt-rounded'); + link.setAttribute('data-tooltip-content', tooltip); + } + parent.append(link); +} + +export function initRepoDiffCommitBranchesAndTags() { + for (const area of document.querySelectorAll('.branch-and-tag-area')) { + const btn = area.querySelector('.load-branches-and-tags'); + btn.addEventListener('click', () => loadBranchesAndTags(area, btn)); + } +} diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index f23ff45470c10..5991df6322c57 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -459,7 +459,7 @@ async function onEditContent(event) { } export function initRepository() { - if ($('.repository').length === 0) { + if ($('.page-content.repository').length === 0) { return; } diff --git a/web_src/js/index.js b/web_src/js/index.js index 0c786f96fbef2..8bd219bbe1576 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -83,6 +83,7 @@ import {initGiteaFomantic} from './modules/fomantic.js'; import {onDomReady} from './utils/dom.js'; import {initRepoIssueList} from './features/repo-issue-list.js'; import {initCommonIssueListQuickGoto} from './features/common-issue-list.js'; +import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'; // Init Gitea's Fomantic settings initGiteaFomantic(); @@ -141,6 +142,7 @@ onDomReady(() => { initRepoCodeView(); initRepoCommentForm(); initRepoEllipsisButton(); + initRepoDiffCommitBranchesAndTags(); initRepoCommitLastCommitLoader(); initRepoEditor(); initRepoGraphGit();