Merge branch 'main' into fix-obj-hash

This commit is contained in:
Giteabot 2024-04-26 16:45:45 +08:00 committed by GitHub
commit 2fad316df7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 125 additions and 95 deletions

View File

@ -74,6 +74,13 @@ func (run *ActionRun) Link() string {
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index)
} }
func (run *ActionRun) WorkflowLink() string {
if run.Repo == nil {
return ""
}
return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID)
}
// RefLink return the url of run's ref // RefLink return the url of run's ref
func (run *ActionRun) RefLink() string { func (run *ActionRun) RefLink() string {
refName := git.RefName(run.Ref) refName := git.RefName(run.Ref)
@ -156,6 +163,10 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err
return nil, fmt.Errorf("event %s is not a pull request event", run.Event) return nil, fmt.Errorf("event %s is not a pull request event", run.Event)
} }
func (run *ActionRun) IsSchedule() bool {
return run.ScheduleID > 0
}
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
_, err := db.GetEngine(ctx).ID(repo.ID). _, err := db.GetEngine(ctx).ID(repo.ID).
SetExpr("num_action_runs", SetExpr("num_action_runs",

View File

@ -0,0 +1,32 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package pipeline
import (
"fmt"
"time"
"code.gitea.io/gitea/modules/git"
)
// LFSResult represents commits found using a provided pointer file hash
type LFSResult struct {
Name string
SHA string
Summary string
When time.Time
ParentHashes []git.ObjectID
BranchName string
FullCommitName string
}
type lfsResultSlice []*LFSResult
func (a lfsResultSlice) Len() int { return len(a) }
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
func lfsError(msg string, err error) error {
return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err)
}

View File

@ -7,12 +7,10 @@ package pipeline
import ( import (
"bufio" "bufio"
"fmt"
"io" "io"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -21,23 +19,6 @@ import (
"github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/object"
) )
// LFSResult represents commits found using a provided pointer file hash
type LFSResult struct {
Name string
SHA string
Summary string
When time.Time
ParentHashes []git.ObjectID
BranchName string
FullCommitName string
}
type lfsResultSlice []*LFSResult
func (a lfsResultSlice) Len() int { return len(a) }
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
// FindLFSFile finds commits that contain a provided pointer file hash // FindLFSFile finds commits that contain a provided pointer file hash
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{} resultsMap := map[string]*LFSResult{}
@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
All: true, All: true,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err) return nil, lfsError("failed to get GoGit CommitsIter", err)
} }
err = commitsIter.ForEach(func(gitCommit *object.Commit) error { err = commitsIter.ForEach(func(gitCommit *object.Commit) error {
@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
return nil return nil
}) })
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err) return nil, lfsError("failure in CommitIter.ForEach", err)
} }
for _, result := range resultsMap { for _, result := range resultsMap {
@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
select { select {
case err, has := <-errChan: case err, has := <-errChan:
if has { if has {
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err) return nil, lfsError("unable to obtain name for LFS files", err)
} }
default: default:
} }

View File

@ -8,33 +8,14 @@ package pipeline
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"fmt"
"io" "io"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
) )
// LFSResult represents commits found using a provided pointer file hash
type LFSResult struct {
Name string
SHA string
Summary string
When time.Time
ParentIDs []git.ObjectID
BranchName string
FullCommitName string
}
type lfsResultSlice []*LFSResult
func (a lfsResultSlice) Len() int { return len(a) }
func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) }
// FindLFSFile finds commits that contain a provided pointer file hash // FindLFSFile finds commits that contain a provided pointer file hash
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) {
resultsMap := map[string]*LFSResult{} resultsMap := map[string]*LFSResult{}
@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
n += int64(count) n += int64(count)
if bytes.Equal(binObjectID, objectID.RawValue()) { if bytes.Equal(binObjectID, objectID.RawValue()) {
result := LFSResult{ result := LFSResult{
Name: curPath + string(fname), Name: curPath + string(fname),
SHA: curCommit.ID.String(), SHA: curCommit.ID.String(),
Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0], Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0],
When: curCommit.Author.When, When: curCommit.Author.When,
ParentIDs: curCommit.Parents, ParentHashes: curCommit.Parents,
} }
resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result
} else if string(mode) == git.EntryModeTree.String() { } else if string(mode) == git.EntryModeTree.String() {
@ -183,7 +164,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
for _, result := range resultsMap { for _, result := range resultsMap {
hasParent := false hasParent := false
for _, parentID := range result.ParentIDs { for _, parentID := range result.ParentHashes {
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent { if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent {
break break
} }
@ -240,7 +221,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
select { select {
case err, has := <-errChan: case err, has := <-errChan:
if has { if has {
return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err) return nil, lfsError("unable to obtain name for LFS files", err)
} }
default: default:
} }

8
package-lock.json generated
View File

@ -28,7 +28,7 @@
"esbuild-loader": "4.1.0", "esbuild-loader": "4.1.0",
"escape-goat": "4.0.0", "escape-goat": "4.0.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"htmx.org": "1.9.11", "htmx.org": "1.9.12",
"idiomorph": "0.3.0", "idiomorph": "0.3.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.10", "katex": "0.16.10",
@ -6728,9 +6728,9 @@
} }
}, },
"node_modules/htmx.org": { "node_modules/htmx.org": {
"version": "1.9.11", "version": "1.9.12",
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz", "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
"integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw==" "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
}, },
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "5.0.0", "version": "5.0.0",

View File

@ -27,7 +27,7 @@
"esbuild-loader": "4.1.0", "esbuild-loader": "4.1.0",
"escape-goat": "4.0.0", "escape-goat": "4.0.0",
"fast-glob": "3.3.2", "fast-glob": "3.3.2",
"htmx.org": "1.9.11", "htmx.org": "1.9.12",
"idiomorph": "0.3.0", "idiomorph": "0.3.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"katex": "0.16.10", "katex": "0.16.10",

View File

@ -67,6 +67,9 @@ type ViewResponse struct {
CanRerun bool `json:"canRerun"` CanRerun bool `json:"canRerun"`
CanDeleteArtifact bool `json:"canDeleteArtifact"` CanDeleteArtifact bool `json:"canDeleteArtifact"`
Done bool `json:"done"` Done bool `json:"done"`
WorkflowID string `json:"workflowID"`
WorkflowLink string `json:"workflowLink"`
IsSchedule bool `json:"isSchedule"`
Jobs []*ViewJob `json:"jobs"` Jobs []*ViewJob `json:"jobs"`
Commit ViewCommit `json:"commit"` Commit ViewCommit `json:"commit"`
} `json:"run"` } `json:"run"`
@ -90,12 +93,10 @@ type ViewJob struct {
} }
type ViewCommit struct { type ViewCommit struct {
LocaleCommit string `json:"localeCommit"` ShortSha string `json:"shortSHA"`
LocalePushedBy string `json:"localePushedBy"` Link string `json:"link"`
ShortSha string `json:"shortSHA"` Pusher ViewUser `json:"pusher"`
Link string `json:"link"` Branch ViewBranch `json:"branch"`
Pusher ViewUser `json:"pusher"`
Branch ViewBranch `json:"branch"`
} }
type ViewUser struct { type ViewUser struct {
@ -151,6 +152,9 @@ func ViewPost(ctx *context_module.Context) {
resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.Done = run.Status.IsDone() resp.State.Run.Done = run.Status.IsDone()
resp.State.Run.WorkflowID = run.WorkflowID
resp.State.Run.WorkflowLink = run.WorkflowLink()
resp.State.Run.IsSchedule = run.IsSchedule()
resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json
resp.State.Run.Status = run.Status.String() resp.State.Run.Status = run.Status.String()
for _, v := range jobs { for _, v := range jobs {
@ -172,12 +176,10 @@ func ViewPost(ctx *context_module.Context) {
Link: run.RefLink(), Link: run.RefLink(),
} }
resp.State.Run.Commit = ViewCommit{ resp.State.Run.Commit = ViewCommit{
LocaleCommit: ctx.Locale.TrString("actions.runs.commit"), ShortSha: base.ShortSha(run.CommitSHA),
LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"), Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA),
ShortSha: base.ShortSha(run.CommitSHA), Pusher: pusher,
Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), Branch: branch,
Pusher: pusher,
Branch: branch,
} }
var task *actions_model.ActionTask var task *actions_model.ActionTask

View File

@ -3149,13 +3149,10 @@ func UpdateCommentContent(ctx *context.Context) {
} }
oldContent := comment.Content oldContent := comment.Content
comment.Content = ctx.FormString("content") newContent := ctx.FormString("content")
if len(comment.Content) == 0 {
ctx.JSON(http.StatusOK, map[string]any{ // allow to save empty content
"content": "", comment.Content = newContent
})
return
}
if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil { if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
if errors.Is(err, user_model.ErrBlockedUser) { if errors.Is(err, user_model.ErrBlockedUser) {
ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user")) ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user"))
@ -3178,21 +3175,27 @@ func UpdateCommentContent(ctx *context.Context) {
} }
} }
content, err := markdown.RenderString(&markup.RenderContext{ var renderedContent template.HTML
Links: markup.Links{ if comment.Content != "" {
Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? renderedContent, err = markdown.RenderString(&markup.RenderContext{
}, Links: markup.Links{
Metas: ctx.Repo.Repository.ComposeMetas(ctx), Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
GitRepo: ctx.Repo.GitRepo, },
Ctx: ctx, Metas: ctx.Repo.Repository.ComposeMetas(ctx),
}, comment.Content) GitRepo: ctx.Repo.GitRepo,
if err != nil { Ctx: ctx,
ctx.ServerError("RenderString", err) }, comment.Content)
return if err != nil {
ctx.ServerError("RenderString", err)
return
}
} else {
contentEmpty := fmt.Sprintf(`<span class="no-content">%s</span>`, ctx.Tr("repo.issues.no_content"))
renderedContent = template.HTML(contentEmpty)
} }
ctx.JSON(http.StatusOK, map[string]any{ ctx.JSON(http.StatusOK, map[string]any{
"content": content, "content": renderedContent,
"attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content), "attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content),
}) })
} }

View File

@ -15,7 +15,7 @@
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}} {{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
</a> </a>
<div class="flex-item-body"> <div class="flex-item-body">
<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>: <span><b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:</span>
{{- if .ScheduleID -}} {{- if .ScheduleID -}}
{{ctx.Locale.Tr "actions.runs.scheduled"}} {{ctx.Locale.Tr "actions.runs.scheduled"}}
{{- else -}} {{- else -}}

View File

@ -10,6 +10,9 @@
data-locale-cancel="{{ctx.Locale.Tr "cancel"}}" data-locale-cancel="{{ctx.Locale.Tr "cancel"}}"
data-locale-rerun="{{ctx.Locale.Tr "rerun"}}" data-locale-rerun="{{ctx.Locale.Tr "rerun"}}"
data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}" data-locale-rerun-all="{{ctx.Locale.Tr "rerun_all"}}"
data-locale-runs-scheduled="{{ctx.Locale.Tr "actions.runs.scheduled"}}"
data-locale-runs-commit="{{ctx.Locale.Tr "actions.runs.commit"}}"
data-locale-runs-pushed-by="{{ctx.Locale.Tr "actions.runs.pushed_by"}}"
data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}" data-locale-status-unknown="{{ctx.Locale.Tr "actions.status.unknown"}}"
data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}" data-locale-status-waiting="{{ctx.Locale.Tr "actions.status.waiting"}}"
data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}" data-locale-status-running="{{ctx.Locale.Tr "actions.status.running"}}"

View File

@ -4,11 +4,13 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -46,22 +48,25 @@ func TestPullCompare(t *testing.T) {
testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1")
testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther) testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther)
testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n") testEditFile(t, session, "user1", "repo1", "master1", "README.md", "Hello, World (Edited)\n")
resp = testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title") testPullCreate(t, session, "user1", "repo1", false, "master", "master1", "This is a pull title")
// the max value on issue_index.yml for repo_id=1 is 5 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "repo1"})
req = NewRequest(t, "GET", "/user2/repo1/pulls/6/files") issueIndex := unittest.AssertExistsAndLoadBean(t, &issues_model.IssueIndex{GroupID: repo1.ID}, unittest.OrderBy("group_id ASC"))
prFilesURL := fmt.Sprintf("/user2/repo1/pulls/%d/files", issueIndex.MaxIndex)
req = NewRequest(t, "GET", prFilesURL)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body) doc := NewHTMLParser(t, resp.Body)
editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() editButtonCount := doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()
assert.Greater(t, editButtonCount, 0, "Expected to find a button to edit a file in the PR diff view but there were none") assert.Greater(t, editButtonCount, 0, "Expected to find a button to edit a file in the PR diff view but there were none")
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) repoForked := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
// delete the head repository and revisit the PR diff view // delete the head repository and revisit the PR diff view
err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID) err := repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, repoForked.ID)
assert.NoError(t, err) assert.NoError(t, err)
req = NewRequest(t, "GET", "/user2/repo1/pulls/6/files") req = NewRequest(t, "GET", prFilesURL)
resp = session.MakeRequest(t, req, http.StatusOK) resp = session.MakeRequest(t, req, http.StatusOK)
doc = NewHTMLParser(t, resp.Body) doc = NewHTMLParser(t, resp.Body)
editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length()

View File

@ -44,6 +44,9 @@ const sfc = {
canApprove: false, canApprove: false,
canRerun: false, canRerun: false,
done: false, done: false,
workflowID: '',
workflowLink: '',
isSchedule: false,
jobs: [ jobs: [
// { // {
// id: 0, // id: 0,
@ -338,10 +341,13 @@ export function initRepositoryActionView() {
approve: el.getAttribute('data-locale-approve'), approve: el.getAttribute('data-locale-approve'),
cancel: el.getAttribute('data-locale-cancel'), cancel: el.getAttribute('data-locale-cancel'),
rerun: el.getAttribute('data-locale-rerun'), rerun: el.getAttribute('data-locale-rerun'),
rerun_all: el.getAttribute('data-locale-rerun-all'),
scheduled: el.getAttribute('data-locale-runs-scheduled'),
commit: el.getAttribute('data-locale-runs-commit'),
pushedBy: el.getAttribute('data-locale-runs-pushed-by'),
artifactsTitle: el.getAttribute('data-locale-artifacts-title'), artifactsTitle: el.getAttribute('data-locale-artifacts-title'),
areYouSure: el.getAttribute('data-locale-are-you-sure'), areYouSure: el.getAttribute('data-locale-are-you-sure'),
confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'), confirmDeleteArtifact: el.getAttribute('data-locale-confirm-delete-artifact'),
rerun_all: el.getAttribute('data-locale-rerun-all'),
showTimeStamps: el.getAttribute('data-locale-show-timestamps'), showTimeStamps: el.getAttribute('data-locale-show-timestamps'),
showLogSeconds: el.getAttribute('data-locale-show-log-seconds'), showLogSeconds: el.getAttribute('data-locale-show-log-seconds'),
showFullScreen: el.getAttribute('data-locale-show-full-screen'), showFullScreen: el.getAttribute('data-locale-show-full-screen'),
@ -382,10 +388,16 @@ export function initRepositoryActionView() {
</button> </button>
</div> </div>
<div class="action-commit-summary"> <div class="action-commit-summary">
{{ run.commit.localeCommit }} <span><a class="muted" :href="run.workflowLink"><b>{{ run.workflowID }}</b></a>:</span>
<a class="muted" :href="run.commit.link">{{ run.commit.shortSHA }}</a> <template v-if="run.isSchedule">
{{ run.commit.localePushedBy }} {{ locale.scheduled }}
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a> </template>
<template v-else>
{{ locale.commit }}
<a class="muted" :href="run.commit.link">{{ run.commit.shortSHA }}</a>
{{ locale.pushedBy }}
<a class="muted" :href="run.commit.pusher.link">{{ run.commit.pusher.displayName }}</a>
</template>
<span class="ui label tw-max-w-full" v-if="run.commit.shortSHA"> <span class="ui label tw-max-w-full" v-if="run.commit.shortSHA">
<a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a> <a class="gt-ellipsis" :href="run.commit.branch.link">{{ run.commit.branch.name }}</a>
</span> </span>