mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-30 19:08:37 +00:00 
			
		
		
		
	Show commit status for releases (#29149)
Fixes #29082 
This commit is contained in:
		| @@ -12,6 +12,7 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models" | ||||
| 	"code.gitea.io/gitea/models/db" | ||||
| 	git_model "code.gitea.io/gitea/models/git" | ||||
| 	repo_model "code.gitea.io/gitea/models/repo" | ||||
| 	"code.gitea.io/gitea/models/unit" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| @@ -67,6 +68,88 @@ func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *repo_model | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| type ReleaseInfo struct { | ||||
| 	Release        *repo_model.Release | ||||
| 	CommitStatus   *git_model.CommitStatus | ||||
| 	CommitStatuses []*git_model.CommitStatus | ||||
| } | ||||
|  | ||||
| func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) ([]*ReleaseInfo, error) { | ||||
| 	releases, err := db.Find[repo_model.Release](ctx, opts) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, release := range releases { | ||||
| 		release.Repo = ctx.Repo.Repository | ||||
| 	} | ||||
|  | ||||
| 	if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Temporary cache commits count of used branches to speed up. | ||||
| 	countCache := make(map[string]int64) | ||||
| 	cacheUsers := make(map[int64]*user_model.User) | ||||
| 	if ctx.Doer != nil { | ||||
| 		cacheUsers[ctx.Doer.ID] = ctx.Doer | ||||
| 	} | ||||
| 	var ok bool | ||||
|  | ||||
| 	canReadActions := ctx.Repo.CanRead(unit.TypeActions) | ||||
|  | ||||
| 	releaseInfos := make([]*ReleaseInfo, 0, len(releases)) | ||||
| 	for _, r := range releases { | ||||
| 		if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { | ||||
| 			r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) | ||||
| 			if err != nil { | ||||
| 				if user_model.IsErrUserNotExist(err) { | ||||
| 					r.Publisher = user_model.NewGhostUser() | ||||
| 				} else { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 			} | ||||
| 			cacheUsers[r.PublisherID] = r.Publisher | ||||
| 		} | ||||
|  | ||||
| 		r.Note, err = markdown.RenderString(&markup.RenderContext{ | ||||
| 			Links: markup.Links{ | ||||
| 				Base: ctx.Repo.RepoLink, | ||||
| 			}, | ||||
| 			Metas:   ctx.Repo.Repository.ComposeMetas(ctx), | ||||
| 			GitRepo: ctx.Repo.GitRepo, | ||||
| 			Ctx:     ctx, | ||||
| 		}, r.Note) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		if !r.IsDraft { | ||||
| 			if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		info := &ReleaseInfo{ | ||||
| 			Release: r, | ||||
| 		} | ||||
|  | ||||
| 		if canReadActions { | ||||
| 			statuses, _, err := git_model.GetLatestCommitStatus(ctx, r.Repo.ID, r.Sha1, db.ListOptions{ListAll: true}) | ||||
| 			if err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
|  | ||||
| 			info.CommitStatus = git_model.CalcCommitStatus(statuses) | ||||
| 			info.CommitStatuses = statuses | ||||
| 		} | ||||
|  | ||||
| 		releaseInfos = append(releaseInfos, info) | ||||
| 	} | ||||
|  | ||||
| 	return releaseInfos, nil | ||||
| } | ||||
|  | ||||
| // Releases render releases list page | ||||
| func Releases(ctx *context.Context) { | ||||
| 	ctx.Data["PageIsReleaseList"] = true | ||||
| @@ -91,77 +174,21 @@ func Releases(ctx *context.Context) { | ||||
| 	writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) | ||||
| 	ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived | ||||
|  | ||||
| 	opts := repo_model.FindReleasesOptions{ | ||||
| 	releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{ | ||||
| 		ListOptions: listOptions, | ||||
| 		// only show draft releases for users who can write, read-only users shouldn't see draft releases. | ||||
| 		IncludeDrafts: writeAccess, | ||||
| 		RepoID:        ctx.Repo.Repository.ID, | ||||
| 	} | ||||
|  | ||||
| 	releases, err := db.Find[repo_model.Release](ctx, opts) | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetReleasesByRepoID", err) | ||||
| 		ctx.ServerError("getReleaseInfos", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	for _, release := range releases { | ||||
| 		release.Repo = ctx.Repo.Repository | ||||
| 	} | ||||
|  | ||||
| 	if err = repo_model.GetReleaseAttachments(ctx, releases...); err != nil { | ||||
| 		ctx.ServerError("GetReleaseAttachments", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Temporary cache commits count of used branches to speed up. | ||||
| 	countCache := make(map[string]int64) | ||||
| 	cacheUsers := make(map[int64]*user_model.User) | ||||
| 	if ctx.Doer != nil { | ||||
| 		cacheUsers[ctx.Doer.ID] = ctx.Doer | ||||
| 	} | ||||
| 	var ok bool | ||||
|  | ||||
| 	for _, r := range releases { | ||||
| 		if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { | ||||
| 			r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) | ||||
| 			if err != nil { | ||||
| 				if user_model.IsErrUserNotExist(err) { | ||||
| 					r.Publisher = user_model.NewGhostUser() | ||||
| 				} else { | ||||
| 					ctx.ServerError("GetUserByID", err) | ||||
| 					return | ||||
| 				} | ||||
| 			} | ||||
| 			cacheUsers[r.PublisherID] = r.Publisher | ||||
| 		} | ||||
|  | ||||
| 		r.Note, err = markdown.RenderString(&markup.RenderContext{ | ||||
| 			Links: markup.Links{ | ||||
| 				Base: ctx.Repo.RepoLink, | ||||
| 			}, | ||||
| 			Metas:   ctx.Repo.Repository.ComposeMetas(ctx), | ||||
| 			GitRepo: ctx.Repo.GitRepo, | ||||
| 			Ctx:     ctx, | ||||
| 		}, r.Note) | ||||
| 		if err != nil { | ||||
| 			ctx.ServerError("RenderString", err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if r.IsDraft { | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if err := calReleaseNumCommitsBehind(ctx.Repo, r, countCache); err != nil { | ||||
| 			ctx.ServerError("calReleaseNumCommitsBehind", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["Releases"] = releases | ||||
|  | ||||
| 	numReleases := ctx.Data["NumReleases"].(int64) | ||||
| 	pager := context.NewPagination(int(numReleases), opts.PageSize, opts.Page, 5) | ||||
| 	pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5) | ||||
| 	pager.SetDefaultParams(ctx) | ||||
| 	ctx.Data["Page"] = pager | ||||
|  | ||||
| @@ -249,15 +276,24 @@ func SingleRelease(ctx *context.Context) { | ||||
| 	writeAccess := ctx.Repo.CanWrite(unit.TypeReleases) | ||||
| 	ctx.Data["CanCreateRelease"] = writeAccess && !ctx.Repo.Repository.IsArchived | ||||
|  | ||||
| 	release, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, ctx.Params("*")) | ||||
| 	releases, err := getReleaseInfos(ctx, &repo_model.FindReleasesOptions{ | ||||
| 		ListOptions: db.ListOptions{Page: 1, PageSize: 1}, | ||||
| 		RepoID:      ctx.Repo.Repository.ID, | ||||
| 		TagNames:    []string{ctx.Params("*")}, | ||||
| 		// only show draft releases for users who can write, read-only users shouldn't see draft releases. | ||||
| 		IncludeDrafts: writeAccess, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if repo_model.IsErrReleaseNotExist(err) { | ||||
| 			ctx.NotFound("GetRelease", err) | ||||
| 		ctx.ServerError("getReleaseInfos", err) | ||||
| 		return | ||||
| 	} | ||||
| 		ctx.ServerError("GetReleasesByRepoID", err) | ||||
| 	if len(releases) != 1 { | ||||
| 		ctx.NotFound("SingleRelease", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	release := releases[0].Release | ||||
|  | ||||
| 	ctx.Data["PageIsSingleTag"] = release.IsTag | ||||
| 	if release.IsTag { | ||||
| 		ctx.Data["Title"] = release.TagName | ||||
| @@ -265,43 +301,7 @@ func SingleRelease(ctx *context.Context) { | ||||
| 		ctx.Data["Title"] = release.Title | ||||
| 	} | ||||
|  | ||||
| 	release.Repo = ctx.Repo.Repository | ||||
|  | ||||
| 	err = repo_model.GetReleaseAttachments(ctx, release) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("GetReleaseAttachments", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	release.Publisher, err = user_model.GetUserByID(ctx, release.PublisherID) | ||||
| 	if err != nil { | ||||
| 		if user_model.IsErrUserNotExist(err) { | ||||
| 			release.Publisher = user_model.NewGhostUser() | ||||
| 		} else { | ||||
| 			ctx.ServerError("GetUserByID", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if !release.IsDraft { | ||||
| 		if err := calReleaseNumCommitsBehind(ctx.Repo, release, make(map[string]int64)); err != nil { | ||||
| 			ctx.ServerError("calReleaseNumCommitsBehind", err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	release.Note, err = markdown.RenderString(&markup.RenderContext{ | ||||
| 		Links: markup.Links{ | ||||
| 			Base: ctx.Repo.RepoLink, | ||||
| 		}, | ||||
| 		Metas:   ctx.Repo.Repository.ComposeMetas(ctx), | ||||
| 		GitRepo: ctx.Repo.GitRepo, | ||||
| 		Ctx:     ctx, | ||||
| 	}, release.Note) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("RenderString", err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	ctx.Data["Releases"] = []*repo_model.Release{release} | ||||
| 	ctx.Data["Releases"] = releases | ||||
| 	ctx.HTML(http.StatusOK, tplReleasesList) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -64,6 +64,9 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er | ||||
| 			return fmt.Errorf("head of pull request is missing in event payload") | ||||
| 		} | ||||
| 		sha = payload.PullRequest.Head.Sha | ||||
| 	case webhook_module.HookEventRelease: | ||||
| 		event = string(run.Event) | ||||
| 		sha = run.CommitSHA | ||||
| 	default: | ||||
| 		return nil | ||||
| 	} | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| {{if .Statuses}} | ||||
| 	{{if and (eq (len .Statuses) 1) .Status.TargetURL}} | ||||
| 		<a class="gt-vm gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> | ||||
| 		<a class="gt-vm {{.AdditionalClasses}} gt-no-underline" data-tippy="commit-statuses" href="{{.Status.TargetURL}}"> | ||||
| 			{{template "repo/commit_status" .Status}} | ||||
| 		</a> | ||||
| 	{{else}} | ||||
| 		<span class="gt-vm" data-tippy="commit-statuses" tabindex="0"> | ||||
| 		<span class="gt-vm {{.AdditionalClasses}}" data-tippy="commit-statuses" tabindex="0"> | ||||
| 			{{template "repo/commit_status" .Status}} | ||||
| 		</span> | ||||
| 	{{end}} | ||||
|   | ||||
| @@ -5,22 +5,24 @@ | ||||
| 		{{template "base/alert" .}} | ||||
| 		{{template "repo/release_tag_header" .}} | ||||
| 		<ul id="release-list"> | ||||
| 			{{range $idx, $release := .Releases}} | ||||
| 			{{range $idx, $info := .Releases}} | ||||
| 				{{$release := $info.Release}} | ||||
| 				<li class="ui grid"> | ||||
| 					<div class="ui four wide column meta"> | ||||
| 							<a class="muted" href="{{if not (and .Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}}</a> | ||||
| 							{{if and .Sha1 ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 								<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha .Sha1}}</a> | ||||
| 								{{template "repo/branch_dropdown" dict "root" $ "release" .}} | ||||
| 						<a class="muted" href="{{if not (and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode))}}#{{else}}{{$.RepoLink}}/src/tag/{{$release.TagName | PathEscapeSegments}}{{end}}" rel="nofollow">{{svg "octicon-tag" 16 "gt-mr-2"}}{{$release.TagName}}</a> | ||||
| 						{{if and $release.Sha1 ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 							<a class="muted gt-mono" href="{{$.RepoLink}}/src/commit/{{$release.Sha1}}" rel="nofollow">{{svg "octicon-git-commit" 16 "gt-mr-2"}}{{ShortSha $release.Sha1}}</a> | ||||
| 							{{template "repo/branch_dropdown" dict "root" $ "release" $release}} | ||||
| 						{{end}} | ||||
| 					</div> | ||||
| 					<div class="ui twelve wide column detail"> | ||||
| 						<div class="gt-df gt-ac gt-sb gt-fw gt-mb-3"> | ||||
| 							<h4 class="release-list-title gt-word-break"> | ||||
| 									<a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a> | ||||
| 									{{if .IsDraft}} | ||||
| 								<a href="{{$.RepoLink}}/releases/tag/{{$release.TagName | PathEscapeSegments}}">{{$release.Title}}</a> | ||||
| 								{{template "repo/commit_statuses" dict "Status" $info.CommitStatus "Statuses" $info.CommitStatuses "AdditionalClasses" "gt-df"}} | ||||
| 								{{if $release.IsDraft}} | ||||
| 									<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> | ||||
| 									{{else if .IsPrerelease}} | ||||
| 								{{else if $release.IsPrerelease}} | ||||
| 									<span class="ui orange label">{{ctx.Locale.Tr "repo.release.prerelease"}}</span> | ||||
| 								{{else}} | ||||
| 									<span class="ui green label">{{ctx.Locale.Tr "repo.release.stable"}}</span> | ||||
| @@ -28,7 +30,7 @@ | ||||
| 							</h4> | ||||
| 							<div> | ||||
| 								{{if $.CanCreateRelease}} | ||||
| 										<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{.TagName | PathEscapeSegments}}" rel="nofollow"> | ||||
| 									<a class="muted" data-tooltip-content="{{ctx.Locale.Tr "repo.release.edit"}}" href="{{$.RepoLink}}/releases/edit/{{$release.TagName | PathEscapeSegments}}" rel="nofollow"> | ||||
| 										{{svg "octicon-pencil"}} | ||||
| 									</a> | ||||
| 								{{end}} | ||||
| @@ -36,11 +38,11 @@ | ||||
| 						</div> | ||||
| 						<p class="text grey"> | ||||
| 							<span class="author"> | ||||
| 								{{if .OriginalAuthor}} | ||||
| 									{{svg (MigrationIcon .Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{.OriginalAuthor}} | ||||
| 								{{else if .Publisher}} | ||||
| 									{{ctx.AvatarUtils.Avatar .Publisher 20 "gt-mr-2"}} | ||||
| 									<a href="{{.Publisher.HomeLink}}">{{.Publisher.GetDisplayName}}</a> | ||||
| 							{{if $release.OriginalAuthor}} | ||||
| 								{{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "gt-mr-2"}}{{$release.OriginalAuthor}} | ||||
| 							{{else if $release.Publisher}} | ||||
| 								{{ctx.AvatarUtils.Avatar $release.Publisher 20 "gt-mr-2"}} | ||||
| 								<a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a> | ||||
| 							{{else}} | ||||
| 								Ghost | ||||
| 							{{end}} | ||||
| @@ -48,15 +50,15 @@ | ||||
| 							<span class="released"> | ||||
| 								{{ctx.Locale.Tr "repo.released_this"}} | ||||
| 							</span> | ||||
| 								{{if .CreatedUnix}} | ||||
| 									<span class="time">{{TimeSinceUnix .CreatedUnix ctx.Locale}}</span> | ||||
| 							{{if $release.CreatedUnix}} | ||||
| 								<span class="time">{{TimeSinceUnix $release.CreatedUnix ctx.Locale}}</span> | ||||
| 							{{end}} | ||||
| 								{{if and (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 									| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{.TagName | PathEscapeSegments}}...{{.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" .NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" .TargetBehind}}</span> | ||||
| 							{{if and (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 								| <span class="ahead"><a href="{{$.RepoLink}}/compare/{{$release.TagName | PathEscapeSegments}}...{{$release.TargetBehind | PathEscapeSegments}}">{{ctx.Locale.Tr "repo.release.ahead.commits" $release.NumCommitsBehind | Str2html}}</a> {{ctx.Locale.Tr "repo.release.ahead.target" $release.TargetBehind}}</span> | ||||
| 							{{end}} | ||||
| 						</p> | ||||
| 						<div class="markup desc"> | ||||
| 								{{Str2html .Note}} | ||||
| 							{{Str2html $release.Note}} | ||||
| 						</div> | ||||
| 						<div class="divider"></div> | ||||
| 						<details class="download" {{if eq $idx 0}}open{{end}}> | ||||
| @@ -64,16 +66,15 @@ | ||||
| 								{{ctx.Locale.Tr "repo.release.downloads"}} | ||||
| 							</summary> | ||||
| 							<ul class="list"> | ||||
| 									{{if and (not $.DisableDownloadSourceArchives) (not .IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 								{{if and (not $.DisableDownloadSourceArchives) (not $release.IsDraft) ($.Permission.CanRead $.UnitTypeCode)}} | ||||
| 									<li> | ||||
| 											<a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a> | ||||
| 										<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (ZIP)</strong></a> | ||||
| 									</li> | ||||
| 									<li> | ||||
| 											<a class="archive-link" href="{{$.RepoLink}}/archive/{{.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a> | ||||
| 										<a class="archive-link" href="{{$.RepoLink}}/archive/{{$release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{svg "octicon-file-zip" 16 "gt-mr-2"}}{{ctx.Locale.Tr "repo.release.source_code"}} (TAR.GZ)</strong></a> | ||||
| 									</li> | ||||
| 								{{end}} | ||||
| 									{{if .Attachments}} | ||||
| 										{{range .Attachments}} | ||||
| 								{{range $release.Attachments}} | ||||
| 									<li> | ||||
| 										<a target="_blank" rel="nofollow" href="{{.DownloadURL}}" download> | ||||
| 											<strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong> | ||||
| @@ -86,7 +87,6 @@ | ||||
| 										</div> | ||||
| 									</li> | ||||
| 								{{end}} | ||||
| 									{{end}} | ||||
| 							</ul> | ||||
| 						</details> | ||||
| 						<div class="dot"></div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user