chore: extract commit header template (#8061)
Some checks are pending
/ release (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions

Part of #7155

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [x] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8061
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Reviewed-by: Beowulf <beowulf@beocode.eu>
Co-authored-by: Lucas Schwiderski <lucas@lschwiderski.de>
Co-committed-by: Lucas Schwiderski <lucas@lschwiderski.de>
This commit is contained in:
Lucas Schwiderski 2025-06-05 11:35:08 +02:00 committed by Earl Warren
parent 7df94ff7b2
commit 313504739f
3 changed files with 424 additions and 338 deletions

View file

@ -0,0 +1,339 @@
{{$class := ""}}
{{if .Commit.Signature}}
{{$class = (print $class " isSigned")}}
{{if .Verification.Verified}}
{{if eq .Verification.TrustStatus "trusted"}}
{{$class = (print $class " isVerified")}}
{{else if eq .Verification.TrustStatus "untrusted"}}
{{$class = (print $class " isVerifiedUntrusted")}}
{{else}}
{{$class = (print $class " isVerifiedUnmatched")}}
{{end}}
{{else if .Verification.Warning}}
{{$class = (print $class " isWarning")}}
{{end}}
{{end}}
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
<div class="tw-flex tw-mb-4 tw-gap-1">
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}}
<div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}">
{{ctx.Locale.Tr "repo.diff.browse_source"}}
</a>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}{{- /* */ -}}
<div class="ui dropdown primary tiny button">
{{ctx.Locale.Tr "repo.commit.operations"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="ui header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
<div class="divider"></div>
<div class="item show-create-branch-modal"
data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}
data-branch-from="{{ShortSha .CommitID}}"
data-branch-from-urlcomponent="{{.CommitID}}"
data-modal="#create-branch-modal">
{{ctx.Locale.Tr "repo.branch.create_branch_operation"}}
</div>
<div class="item show-create-branch-modal"
data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}
data-branch-from="{{ShortSha .CommitID}}"
data-branch-from-urlcomponent="{{.CommitID}}"
data-modal="#create-tag-modal"
data-modal-from-span="#modal-create-tag-from-span"
data-modal-form="#create-tag-form">
{{ctx.Locale.Tr "repo.tag.create_tag_operation"}}
</div>
<div class="item show-modal revert-button"
data-modal="#cherry-pick-modal"
data-modal-cherry-pick-type="revert"
data-modal-cherry-pick-header="{{ctx.Locale.Tr "repo.commit.revert-header" (ShortSha .CommitID)}}"
data-modal-cherry-pick-content="{{ctx.Locale.Tr "repo.commit.revert-content"}}"
data-modal-cherry-pick-submit="{{ctx.Locale.Tr "repo.commit.revert"}}">{{ctx.Locale.Tr "repo.commit.revert"}}</div>
<div class="item cherry-pick-button show-modal"
data-modal="#cherry-pick-modal"
data-modal-cherry-pick-type="cherry-pick"
data-modal-cherry-pick-header="{{ctx.Locale.Tr "repo.commit.cherry-pick-header" (ShortSha .CommitID)}}"
data-modal-cherry-pick-content="{{ctx.Locale.Tr "repo.commit.cherry-pick-content"}}"
data-modal-cherry-pick-submit="{{ctx.Locale.Tr "repo.commit.cherry-pick"}}">{{ctx.Locale.Tr "repo.commit.cherry-pick"}}</div>
<div class="ui g-modal-confirm modal" id="cherry-pick-modal">
<div class="header">
<span id="cherry-pick-header"></span>
</div>
<div class="content">
<p id="cherry-pick-content" class="branch-dropdown"></p>
{{template "repo/branch_dropdown" dict "root" .
"noTag" true "disableCreateBranch" true
"branchForm" "branch-dropdown-form"
"branchURLPrefix" (printf "%s/_cherrypick/%s/" $.RepoLink .CommitID) "branchURLSuffix" ""
"setAction" true "submitForm" true}}
<form method="get" action="{{$.RepoLink}}/_cherrypick/{{.CommitID}}/{{PathEscapeSegments $.Repository.DefaultBranch}}" id="branch-dropdown-form">
<input type="hidden" name="ref" value="{{$.Repository.DefaultBranch}}">
<input type="hidden" name="refType" value="branch">
<input type="hidden" id="cherry-pick-type" name="cherry-pick-type"><br>
<button type="submit" id="cherry-pick-submit" class="ui primary button"></button>
</form>
</div>
</div>
<div class="ui small modal" id="create-branch-modal">
<div class="header">
{{ctx.Locale.Tr "repo.branch.new_branch"}}
</div>
<div class="content">
<form class="ui form" id="create-branch-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>
{{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|SafeHTML)}}
</label>
</div>
<div class="required field">
<label for="new_branch_name">{{ctx.Locale.Tr "repo.branch.name"}}</label>
<input id="new_branch_name" name="new_branch_name" required>
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui primary button">{{ctx.Locale.Tr "repo.branch.confirm_create_branch"}}</button>
</div>
</form>
</div>
</div>
<div class="ui small modal" id="create-tag-modal">
<div class="header">
{{ctx.Locale.Tr "repo.tag.create_tag_operation"}}
</div>
<div class="content">
<form class="ui form" id="create-tag-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="create_tag" value="true">
<div class="field">
<label>
{{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|SafeHTML)}}
</label>
</div>
<div class="required field">
<label for="new_branch_name">{{ctx.Locale.Tr "repo.release.tag_name"}}</label>
<input id="new_branch_name" name="new_branch_name" required>
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui primary button">{{ctx.Locale.Tr "repo.tag.confirm_create_tag"}}</button>
</div>
</form>
</div>
</div>
{{if not .NoteRendered}}
<div id="commit-notes-add-button" class="item">
{{ctx.Locale.Tr "repo.diff.git-notes.add"}}
</div>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
</div>
{{if IsMultilineCommitMessage .Commit.Message}}
<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</pre>
{{end}}
{{template "repo/commit_load_branches_and_tags" .}}
</div>
<div class="ui attached segment tw-flex tw-items-center tw-justify-between tw-py-1 commit-header-row tw-flex-wrap {{$class}}">
<div class="tw-flex tw-items-center author">
{{if .Author}}
{{ctx.AvatarUtils.Avatar .Author 28 "tw-mr-2"}}
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a>
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a>
{{end}}
{{else}}
{{ctx.AvatarUtils.AvatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "tw-mr-2"}}
<strong>{{.Commit.Author.Name}}</strong>
{{end}}
<span class="text grey tw-ml-2" id="authored-time">{{DateUtils.TimeSince .Commit.Author.When}}</span>
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}}
<span class="tw-ml-2"></span>
<span class="text grey tw-mx-2">{{ctx.Locale.Tr "repo.diff.committed_by"}}</span>
{{if ne .Verification.CommittingUser.ID 0}}
{{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "tw-mr-2"}}
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a>
{{else}}
{{ctx.AvatarUtils.AvatarByEmail .Commit.Committer.Email .Commit.Committer.Name 28 "tw-mr-2"}}
<strong>{{.Commit.Committer.Name}}</strong>
{{end}}
{{end}}
</div>
<div class="tw-flex tw-items-center">
{{if .Parents}}
<div>
<span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
{{range .Parents}}
{{if $.PageIsWiki}}
<a class="ui primary sha label" href="{{$.RepoLink}}/wiki/commit/{{PathEscape .}}">
<span class="shortsha">{{ShortSha .}}</span>
</a>
{{else}}
<a class="ui primary sha label" href="{{$.RepoLink}}/commit/{{PathEscape .}}">
<span class="shortsha">{{ShortSha .}}</span>
</a>
{{end}}
{{end}}
</div>
{{end}}
<div class="item">
<span>{{ctx.Locale.Tr "repo.diff.commit"}}</span>
<span class="ui primary sha label">
<span class="shortsha">{{ShortSha .CommitID}}</span>
</span>
</div>
</div>
</div>
{{if .Commit.Signature}}
<div class="ui bottom attached message tw-text-left tw-flex tw-items-center tw-justify-between commit-header-row tw-flex-wrap tw-mb-0 {{$class}}">
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{if ne .Verification.SigningUser.ID 0}}
{{svg "gitea-lock" 16 "tw-mr-2"}}
{{if eq .Verification.TrustStatus "trusted"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
{{else if eq .Verification.TrustStatus "untrusted"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user"}}:</span>
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
{{end}}
{{ctx.AvatarUtils.Avatar .Verification.SigningUser 28 "tw-mr-2"}}
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.GetDisplayName}}</strong></a>
{{else}}
<span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog" 16 "tw-mr-2"}}</span>
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
{{ctx.AvatarUtils.AvatarByEmail .Verification.SigningEmail "" 28 "tw-mr-2"}}
<strong>{{.Verification.SigningUser.GetDisplayName}}</strong>
{{end}}
{{else}}
{{svg "gitea-unlock" 16 "tw-mr-2"}}
<span class="ui text">{{ctx.Locale.Tr .Verification.Reason}}</span>
{{end}}
</div>
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
{{if ne .Verification.SigningUser.ID 0}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{else if .Verification.Warning}}
{{svg "octicon-unverified" 16 "tw-mr-2"}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
{{if .Verification.SigningKey}}
{{if ne .Verification.SigningKey.KeyID ""}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{if .Verification.SigningSSHKey}}
{{if ne .Verification.SigningSSHKey.Fingerprint ""}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{end}}
{{end}}
{{end}}
</div>
</div>
{{end}}
{{if .NoteRendered}}
<div class="ui top attached header segment git-notes tw-flex tw-gap-1 tw-flex-wrap">
{{svg "octicon-note" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.diff.git-notes"}}:
{{if .NoteAuthor}}
<a href="{{.NoteAuthor.HomeLink}}">
{{if .NoteAuthor.FullName}}
<strong>{{.NoteAuthor.FullName}}</strong>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
</a>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
<span class="text grey tw-flex-1" id="note-authored-time">{{DateUtils.TimeSince .NoteCommit.Author.When}}</span>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div class="ui tw-flex tw-items-center">
<button id="commit-notes-edit-button" class="ui tiny primary button tw-py-[6px] tw-px-[10px]">{{ctx.Locale.Tr "edit"}}</button>
<button class="ui tiny button red show-modal tw-py-[6px] tw-px-[10px]" data-modal="#delete-note-modal">{{ctx.Locale.Tr "remove"}}</button>
</div>
<div class="ui small modal" id="delete-note-modal">
<div class="header">
{{ctx.Locale.Tr "repo.diff.git-notes.remove-header"}}
</div>
<div class="content">
<p>{{ctx.Locale.Tr "repo.diff.git-notes.remove-body"}}</p>
<div class="text right actions">
<form action="{{.Link}}/notes/remove" method="post">
{{.CsrfTokenHtml}}
<button type="button" class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button type="submit" class="ui red button" href="{{.Link}}/notes/remove">{{ctx.Locale.Tr "remove"}}</button>
</form>
</div>
</div>
</div>
{{end}}
</div>
<div id="commit-notes-display-area" class="ui bottom attached info segment git-notes">
<pre class="commit-body">{{.NoteRendered | SanitizeHTML}}</pre>
</div>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div id="commit-notes-edit-area" class="ui bottom attached info segment git-notes tw-hidden">
<form class="ui form" action="{{.Link}}/notes" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<textarea name="notes">{{.NoteRendered | SanitizeHTML}}</textarea>
</div>
<div class="field">
<button id="notes-save-button" class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
{{end}}
{{else if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div id="commit-notes-add-area" class="ui tw-mt-3 segment tw-hidden">
<form class="ui form" action="{{.Link}}/notes" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<textarea name="notes"></textarea>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "add"}}</button>
</div>
</form>
</div>
{{end}}

View file

@ -2,344 +2,7 @@
<div role="main" aria-label="{{.Title}}" class="page-content repository diff"> <div role="main" aria-label="{{.Title}}" class="page-content repository diff">
{{template "repo/header" .}} {{template "repo/header" .}}
<div class="ui container fluid padded"> <div class="ui container fluid padded">
{{$class := ""}} {{template "repo/commit_header" .}}
{{if .Commit.Signature}}
{{$class = (print $class " isSigned")}}
{{if .Verification.Verified}}
{{if eq .Verification.TrustStatus "trusted"}}
{{$class = (print $class " isVerified")}}
{{else if eq .Verification.TrustStatus "untrusted"}}
{{$class = (print $class " isVerifiedUntrusted")}}
{{else}}
{{$class = (print $class " isVerifiedUnmatched")}}
{{end}}
{{else if .Verification.Warning}}
{{$class = (print $class " isWarning")}}
{{end}}
{{end}}
<div class="ui top attached header clearing segment tw-relative commit-header {{$class}}">
<div class="tw-flex tw-mb-4 tw-gap-1">
<h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}}
<div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}">
{{ctx.Locale.Tr "repo.diff.browse_source"}}
</a>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}{{- /* */ -}}
<div class="ui dropdown primary tiny button">
{{ctx.Locale.Tr "repo.commit.operations"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
<div class="ui header">{{ctx.Locale.Tr "repo.commit.operations"}}</div>
<div class="divider"></div>
<div class="item show-create-branch-modal"
data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}
data-branch-from="{{ShortSha .CommitID}}"
data-branch-from-urlcomponent="{{.CommitID}}"
data-modal="#create-branch-modal">
{{ctx.Locale.Tr "repo.branch.create_branch_operation"}}
</div>
<div class="item show-create-branch-modal"
data-content="{{ctx.Locale.Tr "repo.branch.new_branch_from" (.CommitID)}}" {{/* used by the form */}}
data-branch-from="{{ShortSha .CommitID}}"
data-branch-from-urlcomponent="{{.CommitID}}"
data-modal="#create-tag-modal"
data-modal-from-span="#modal-create-tag-from-span"
data-modal-form="#create-tag-form">
{{ctx.Locale.Tr "repo.tag.create_tag_operation"}}
</div>
<div class="item show-modal revert-button"
data-modal="#cherry-pick-modal"
data-modal-cherry-pick-type="revert"
data-modal-cherry-pick-header="{{ctx.Locale.Tr "repo.commit.revert-header" (ShortSha .CommitID)}}"
data-modal-cherry-pick-content="{{ctx.Locale.Tr "repo.commit.revert-content"}}"
data-modal-cherry-pick-submit="{{ctx.Locale.Tr "repo.commit.revert"}}">{{ctx.Locale.Tr "repo.commit.revert"}}</div>
<div class="item cherry-pick-button show-modal"
data-modal="#cherry-pick-modal"
data-modal-cherry-pick-type="cherry-pick"
data-modal-cherry-pick-header="{{ctx.Locale.Tr "repo.commit.cherry-pick-header" (ShortSha .CommitID)}}"
data-modal-cherry-pick-content="{{ctx.Locale.Tr "repo.commit.cherry-pick-content"}}"
data-modal-cherry-pick-submit="{{ctx.Locale.Tr "repo.commit.cherry-pick"}}">{{ctx.Locale.Tr "repo.commit.cherry-pick"}}</div>
<div class="ui g-modal-confirm modal" id="cherry-pick-modal">
<div class="header">
<span id="cherry-pick-header"></span>
</div>
<div class="content">
<p id="cherry-pick-content" class="branch-dropdown"></p>
{{template "repo/branch_dropdown" dict "root" .
"noTag" true "disableCreateBranch" true
"branchForm" "branch-dropdown-form"
"branchURLPrefix" (printf "%s/_cherrypick/%s/" $.RepoLink .CommitID) "branchURLSuffix" ""
"setAction" true "submitForm" true}}
<form method="get" action="{{$.RepoLink}}/_cherrypick/{{.CommitID}}/{{PathEscapeSegments $.Repository.DefaultBranch}}" id="branch-dropdown-form">
<input type="hidden" name="ref" value="{{$.Repository.DefaultBranch}}">
<input type="hidden" name="refType" value="branch">
<input type="hidden" id="cherry-pick-type" name="cherry-pick-type"><br>
<button type="submit" id="cherry-pick-submit" class="ui primary button"></button>
</form>
</div>
</div>
<div class="ui small modal" id="create-branch-modal">
<div class="header">
{{ctx.Locale.Tr "repo.branch.new_branch"}}
</div>
<div class="content">
<form class="ui form" id="create-branch-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<label>
{{ctx.Locale.Tr "repo.branch.new_branch_from" (`<span class="text" id="modal-create-branch-from-span"></span>`|SafeHTML)}}
</label>
</div>
<div class="required field">
<label for="new_branch_name">{{ctx.Locale.Tr "repo.branch.name"}}</label>
<input id="new_branch_name" name="new_branch_name" required>
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui primary button">{{ctx.Locale.Tr "repo.branch.confirm_create_branch"}}</button>
</div>
</form>
</div>
</div>
<div class="ui small modal" id="create-tag-modal">
<div class="header">
{{ctx.Locale.Tr "repo.tag.create_tag_operation"}}
</div>
<div class="content">
<form class="ui form" id="create-tag-form" action="" data-base-action="{{.RepoLink}}/branches/_new/commit/" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="create_tag" value="true">
<div class="field">
<label>
{{ctx.Locale.Tr "repo.tag.create_tag_from" (`<span class="text" id="modal-create-tag-from-span"></span>`|SafeHTML)}}
</label>
</div>
<div class="required field">
<label for="new_branch_name">{{ctx.Locale.Tr "repo.release.tag_name"}}</label>
<input id="new_branch_name" name="new_branch_name" required>
</div>
<div class="text right actions">
<button class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button class="ui primary button">{{ctx.Locale.Tr "repo.tag.confirm_create_tag"}}</button>
</div>
</form>
</div>
</div>
{{if not .NoteRendered}}
<div id="commit-notes-add-button" class="item">
{{ctx.Locale.Tr "repo.diff.git-notes.add"}}
</div>
{{end}}
</div>
</div>
{{end}}
</div>
{{end}}
</div>
{{if IsMultilineCommitMessage .Commit.Message}}
<pre class="commit-body">{{RenderCommitBody $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}</pre>
{{end}}
{{template "repo/commit_load_branches_and_tags" .}}
</div>
<div class="ui attached segment tw-flex tw-items-center tw-justify-between tw-py-1 commit-header-row tw-flex-wrap {{$class}}">
<div class="tw-flex tw-items-center author">
{{if .Author}}
{{ctx.AvatarUtils.Avatar .Author 28 "tw-mr-2"}}
{{if .Author.FullName}}
<a href="{{.Author.HomeLink}}"><strong>{{.Author.FullName}}</strong></a>
{{else}}
<a href="{{.Author.HomeLink}}"><strong>{{.Commit.Author.Name}}</strong></a>
{{end}}
{{else}}
{{ctx.AvatarUtils.AvatarByEmail .Commit.Author.Email .Commit.Author.Email 28 "tw-mr-2"}}
<strong>{{.Commit.Author.Name}}</strong>
{{end}}
<span class="text grey tw-ml-2" id="authored-time">{{DateUtils.TimeSince .Commit.Author.When}}</span>
{{if or (ne .Commit.Committer.Name .Commit.Author.Name) (ne .Commit.Committer.Email .Commit.Author.Email)}}
<span class="tw-ml-2"></span>
<span class="text grey tw-mx-2">{{ctx.Locale.Tr "repo.diff.committed_by"}}</span>
{{if ne .Verification.CommittingUser.ID 0}}
{{ctx.AvatarUtils.Avatar .Verification.CommittingUser 28 "tw-mr-2"}}
<a href="{{.Verification.CommittingUser.HomeLink}}"><strong>{{.Commit.Committer.Name}}</strong></a>
{{else}}
{{ctx.AvatarUtils.AvatarByEmail .Commit.Committer.Email .Commit.Committer.Name 28 "tw-mr-2"}}
<strong>{{.Commit.Committer.Name}}</strong>
{{end}}
{{end}}
</div>
<div class="tw-flex tw-items-center">
{{if .Parents}}
<div>
<span>{{ctx.Locale.Tr "repo.diff.parent"}}</span>
{{range .Parents}}
{{if $.PageIsWiki}}
<a class="ui primary sha label" href="{{$.RepoLink}}/wiki/commit/{{PathEscape .}}">
<span class="shortsha">{{ShortSha .}}</span>
</a>
{{else}}
<a class="ui primary sha label" href="{{$.RepoLink}}/commit/{{PathEscape .}}">
<span class="shortsha">{{ShortSha .}}</span>
</a>
{{end}}
{{end}}
</div>
{{end}}
<div class="item">
<span>{{ctx.Locale.Tr "repo.diff.commit"}}</span>
<span class="ui primary sha label">
<span class="shortsha">{{ShortSha .CommitID}}</span>
</span>
</div>
</div>
</div>
{{if .Commit.Signature}}
<div class="ui bottom attached message tw-text-left tw-flex tw-items-center tw-justify-between commit-header-row tw-flex-wrap tw-mb-0 {{$class}}">
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{if ne .Verification.SigningUser.ID 0}}
{{svg "gitea-lock" 16 "tw-mr-2"}}
{{if eq .Verification.TrustStatus "trusted"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
{{else if eq .Verification.TrustStatus "untrusted"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user"}}:</span>
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by_untrusted_user_unmatched"}}:</span>
{{end}}
{{ctx.AvatarUtils.Avatar .Verification.SigningUser 28 "tw-mr-2"}}
<a href="{{.Verification.SigningUser.HomeLink}}"><strong>{{.Verification.SigningUser.GetDisplayName}}</strong></a>
{{else}}
<span title="{{ctx.Locale.Tr "gpg.default_key"}}">{{svg "gitea-lock-cog" 16 "tw-mr-2"}}</span>
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.signed_by"}}:</span>
{{ctx.AvatarUtils.AvatarByEmail .Verification.SigningEmail "" 28 "tw-mr-2"}}
<strong>{{.Verification.SigningUser.GetDisplayName}}</strong>
{{end}}
{{else}}
{{svg "gitea-unlock" 16 "tw-mr-2"}}
<span class="ui text">{{ctx.Locale.Tr .Verification.Reason}}</span>
{{end}}
</div>
<div class="tw-flex tw-items-center">
{{if .Verification.Verified}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
{{if ne .Verification.SigningUser.ID 0}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2" data-tooltip-content="{{ctx.Locale.Tr "gpg.default_key"}}">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{else if .Verification.Warning}}
{{svg "octicon-unverified" 16 "tw-mr-2"}}
{{if .Verification.SigningSSHKey}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{else}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{else}}
{{if .Verification.SigningKey}}
{{if ne .Verification.SigningKey.KeyID ""}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.gpg_key_id"}}:</span>
{{.Verification.SigningKey.PaddedKeyID}}
{{end}}
{{end}}
{{if .Verification.SigningSSHKey}}
{{if ne .Verification.SigningSSHKey.Fingerprint ""}}
{{svg "octicon-verified" 16 "tw-mr-2"}}
<span class="ui text tw-mr-2">{{ctx.Locale.Tr "repo.commits.ssh_key_fingerprint"}}:</span>
{{.Verification.SigningSSHKey.Fingerprint}}
{{end}}
{{end}}
{{end}}
</div>
</div>
{{end}}
{{if .NoteRendered}}
<div class="ui top attached header segment git-notes tw-flex tw-gap-1 tw-flex-wrap">
{{svg "octicon-note" 16 "tw-mr-2"}}
{{ctx.Locale.Tr "repo.diff.git-notes"}}:
{{if .NoteAuthor}}
<a href="{{.NoteAuthor.HomeLink}}">
{{if .NoteAuthor.FullName}}
<strong>{{.NoteAuthor.FullName}}</strong>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
</a>
{{else}}
<strong>{{.NoteCommit.Author.Name}}</strong>
{{end}}
<span class="text grey tw-flex-1" id="note-authored-time">{{DateUtils.TimeSince .NoteCommit.Author.When}}</span>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div class="ui tw-flex tw-items-center">
<button id="commit-notes-edit-button" class="ui tiny primary button tw-py-[6px] tw-px-[10px]">{{ctx.Locale.Tr "edit"}}</button>
<button class="ui tiny button red show-modal tw-py-[6px] tw-px-[10px]" data-modal="#delete-note-modal">{{ctx.Locale.Tr "remove"}}</button>
</div>
<div class="ui small modal" id="delete-note-modal">
<div class="header">
{{ctx.Locale.Tr "repo.diff.git-notes.remove-header"}}
</div>
<div class="content">
<p>{{ctx.Locale.Tr "repo.diff.git-notes.remove-body"}}</p>
<div class="text right actions">
<form action="{{.Link}}/notes/remove" method="post">
{{.CsrfTokenHtml}}
<button type="button" class="ui cancel button">{{ctx.Locale.Tr "settings.cancel"}}</button>
<button type="submit" class="ui red button" href="{{.Link}}/notes/remove">{{ctx.Locale.Tr "remove"}}</button>
</form>
</div>
</div>
</div>
{{end}}
</div>
<div id="commit-notes-display-area" class="ui bottom attached info segment git-notes">
<pre class="commit-body">{{.NoteRendered | SanitizeHTML}}</pre>
</div>
{{if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div id="commit-notes-edit-area" class="ui bottom attached info segment git-notes tw-hidden">
<form class="ui form" action="{{.Link}}/notes" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<textarea name="notes">{{.NoteRendered | SanitizeHTML}}</textarea>
</div>
<div class="field">
<button id="notes-save-button" class="ui primary button">{{ctx.Locale.Tr "save"}}</button>
</div>
</form>
</div>
{{end}}
{{else if and ($.Permission.CanWrite $.UnitTypeCode) (not $.Repository.IsArchived) (not .IsDeleted)}}
<div id="commit-notes-add-area" class="ui tw-mt-3 segment tw-hidden">
<form class="ui form" action="{{.Link}}/notes" method="post">
{{.CsrfTokenHtml}}
<div class="field">
<textarea name="notes"></textarea>
</div>
<div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "add"}}</button>
</div>
</form>
</div>
{{end}}
{{template "repo/diff/box" .}} {{template "repo/diff/box" .}}
</div> </div>
</div> </div>

View file

@ -0,0 +1,84 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"strings"
"testing"
"time"
repo_model "forgejo.org/models/repo"
"forgejo.org/models/unittest"
"forgejo.org/modules/git"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRepoCommitHeader(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
t.Run("Verify commit info", func(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath())
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("65f1bf27bc3bf70f64657658635e66094edbcb4d")
require.NoError(t, err)
req := NewRequest(t, "GET", "/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
summary := htmlDoc.Find(".commit-header h3")
assert.Equal(t, commit.Summary(), strings.TrimSpace(summary.Text()))
author := htmlDoc.Find(".commit-header-row .author strong").First()
assert.Equal(t, commit.Author.Name, author.Text())
committer := htmlDoc.Find(".commit-header-row .author strong").Last()
assert.Equal(t, commit.Committer.Name, committer.Text())
date, _ := htmlDoc.Find(".commit-header-row #authored-time relative-time").Attr("datetime")
assert.Equal(t, commit.Author.When.Format(time.RFC3339), date)
sha := htmlDoc.Find(".commit-header-row .sha.label")
assert.Equal(t, commit.ID.String()[:10], sha.Find(".shortsha").Text())
})
t.Run("Verify parent commit ID", func(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath())
require.NoError(t, err)
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("205ac761f3326a7ebe416e8673760016450b5cec")
require.NoError(t, err)
req := NewRequest(t, "GET", "/user2/repo2/commit/205ac761f3326a7ebe416e8673760016450b5cec")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
shas := htmlDoc.Find(".commit-header-row .sha.label")
assert.Equal(t, 2, shas.Length())
parentSha := shas.First()
parentHref, _ := parentSha.Attr("href")
assert.Equal(t, "/user2/repo2/commit/2c54faec6c45d31c1abfaecdab471eac6633738a", parentHref)
parentID, err := commit.ParentID(0)
require.NoError(t, err)
assert.Equal(t, parentID.String()[:10], parentSha.Find(".shortsha").Text())
sha := shas.Last()
assert.Equal(t, commit.ID.String()[:10], sha.Find(".shortsha").Text())
})
}