Skip to content

Commit 4810fe5

Browse files
yardenshohamdelvhGiteaBot
authored
Add status indicator on main home screen for each repo (#24638)
It will show the calculated commit status state of the latest commit on the default branch for each repository in the dashboard repo list - Closes #15620 # Before ![image](https://github.com/go-gitea/gitea/assets/20454870/aa1326c7-43c0-458a-a798-3102c766bcf9) # After ![image](https://github.com/go-gitea/gitea/assets/20454870/8658cc03-2224-442a-b1c8-bf64126e4575) --------- Signed-off-by: Yarden Shoham <[email protected]> Co-authored-by: delvh <[email protected]> Co-authored-by: Giteabot <[email protected]>
1 parent 68081c4 commit 4810fe5

File tree

10 files changed

+152
-20
lines changed

10 files changed

+152
-20
lines changed

models/git/commit_status.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
api "code.gitea.io/gitea/modules/structs"
2424
"code.gitea.io/gitea/modules/timeutil"
2525

26+
"xorm.io/builder"
2627
"xorm.io/xorm"
2728
)
2829

@@ -240,6 +241,55 @@ func GetLatestCommitStatus(ctx context.Context, repoID int64, sha string, listOp
240241
return statuses, count, db.GetEngine(ctx).In("id", ids).Find(&statuses)
241242
}
242243

244+
// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
245+
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
246+
type result struct {
247+
ID int64
248+
RepoID int64
249+
}
250+
251+
results := make([]result, 0, len(repoIDsToLatestCommitSHAs))
252+
253+
sess := db.GetEngine(ctx).Table(&CommitStatus{})
254+
255+
// Create a disjunction of conditions for each repoID and SHA pair
256+
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
257+
for repoID, sha := range repoIDsToLatestCommitSHAs {
258+
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
259+
}
260+
sess = sess.Where(builder.Or(conds...)).
261+
Select("max( id ) as id, repo_id").
262+
GroupBy("context_hash, repo_id").OrderBy("max( id ) desc")
263+
264+
sess = db.SetSessionPagination(sess, &listOptions)
265+
266+
err := sess.Find(&results)
267+
if err != nil {
268+
return nil, err
269+
}
270+
271+
ids := make([]int64, 0, len(results))
272+
repoStatuses := make(map[int64][]*CommitStatus)
273+
for _, result := range results {
274+
ids = append(ids, result.ID)
275+
}
276+
277+
statuses := make([]*CommitStatus, 0, len(ids))
278+
if len(ids) > 0 {
279+
err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
280+
if err != nil {
281+
return nil, err
282+
}
283+
284+
// Group the statuses by repo ID
285+
for _, status := range statuses {
286+
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
287+
}
288+
}
289+
290+
return repoStatuses, nil
291+
}
292+
243293
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
244294
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
245295
start := timeutil.TimeStampNow().AddDuration(-before)

modules/git/repo_branch.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ func GetBranchesByPath(ctx context.Context, path string, skip, limit int) ([]*Br
106106
return gitRepo.GetBranches(skip, limit)
107107
}
108108

109+
// GetBranchCommitID returns a branch commit ID by its name
110+
func GetBranchCommitID(ctx context.Context, path, branch string) (string, error) {
111+
gitRepo, err := OpenRepository(ctx, path)
112+
if err != nil {
113+
return "", err
114+
}
115+
defer gitRepo.Close()
116+
117+
return gitRepo.GetBranchCommitID(branch)
118+
}
119+
109120
// GetBranches returns a slice of *git.Branch
110121
func (repo *Repository) GetBranches(skip, limit int) ([]*Branch, int, error) {
111122
brs, countAll, err := repo.GetBranchNames(skip, limit)

routers/web/repo/repo.go

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import (
99
"fmt"
1010
"net/http"
1111
"strings"
12+
"sync"
1213

1314
"code.gitea.io/gitea/models"
1415
"code.gitea.io/gitea/models/db"
16+
git_model "code.gitea.io/gitea/models/git"
1517
"code.gitea.io/gitea/models/organization"
1618
access_model "code.gitea.io/gitea/models/perm/access"
1719
repo_model "code.gitea.io/gitea/models/repo"
@@ -576,23 +578,49 @@ func SearchRepo(ctx *context.Context) {
576578
return
577579
}
578580

579-
results := make([]*api.Repository, len(repos))
581+
// collect the latest commit of each repo
582+
repoIDsToLatestCommitSHAs := make(map[int64]string)
583+
wg := sync.WaitGroup{}
584+
wg.Add(len(repos))
585+
for _, repo := range repos {
586+
go func(repo *repo_model.Repository) {
587+
defer wg.Done()
588+
commitID, err := repo_service.GetBranchCommitID(ctx, repo, repo.DefaultBranch)
589+
if err != nil {
590+
return
591+
}
592+
repoIDsToLatestCommitSHAs[repo.ID] = commitID
593+
}(repo)
594+
}
595+
wg.Wait()
596+
597+
// call the database O(1) times to get the commit statuses for all repos
598+
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{})
599+
if err != nil {
600+
log.Error("GetLatestCommitStatusForPairs: %v", err)
601+
return
602+
}
603+
604+
results := make([]*repo_service.WebSearchRepository, len(repos))
580605
for i, repo := range repos {
581-
results[i] = &api.Repository{
582-
ID: repo.ID,
583-
FullName: repo.FullName(),
584-
Fork: repo.IsFork,
585-
Private: repo.IsPrivate,
586-
Template: repo.IsTemplate,
587-
Mirror: repo.IsMirror,
588-
Stars: repo.NumStars,
589-
HTMLURL: repo.HTMLURL(),
590-
Link: repo.Link(),
591-
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
606+
results[i] = &repo_service.WebSearchRepository{
607+
Repository: &api.Repository{
608+
ID: repo.ID,
609+
FullName: repo.FullName(),
610+
Fork: repo.IsFork,
611+
Private: repo.IsPrivate,
612+
Template: repo.IsTemplate,
613+
Mirror: repo.IsMirror,
614+
Stars: repo.NumStars,
615+
HTMLURL: repo.HTMLURL(),
616+
Link: repo.Link(),
617+
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
618+
},
619+
LatestCommitStatus: git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]),
592620
}
593621
}
594622

595-
ctx.JSON(http.StatusOK, api.SearchResults{
623+
ctx.JSON(http.StatusOK, repo_service.WebSearchResults{
596624
OK: true,
597625
Data: results,
598626
})

services/repository/branch.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit i
5353
return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit)
5454
}
5555

56+
func GetBranchCommitID(ctx context.Context, repo *repo_model.Repository, branch string) (string, error) {
57+
return git.GetBranchCommitID(ctx, repo.RepoPath(), branch)
58+
}
59+
5660
// checkBranchName validates branch name with existing repository branches
5761
func checkBranchName(ctx context.Context, repo *repo_model.Repository, name string) error {
5862
_, err := git.WalkReferences(ctx, repo.RepoPath(), func(_, refName string) error {

services/repository/repository.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"code.gitea.io/gitea/models"
1111
"code.gitea.io/gitea/models/db"
12+
"code.gitea.io/gitea/models/git"
1213
issues_model "code.gitea.io/gitea/models/issues"
1314
"code.gitea.io/gitea/models/organization"
1415
packages_model "code.gitea.io/gitea/models/packages"
@@ -20,9 +21,22 @@ import (
2021
"code.gitea.io/gitea/modules/notification"
2122
repo_module "code.gitea.io/gitea/modules/repository"
2223
"code.gitea.io/gitea/modules/setting"
24+
"code.gitea.io/gitea/modules/structs"
2325
pull_service "code.gitea.io/gitea/services/pull"
2426
)
2527

28+
// WebSearchRepository represents a repository returned by web search
29+
type WebSearchRepository struct {
30+
Repository *structs.Repository `json:"repository"`
31+
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
32+
}
33+
34+
// WebSearchResults results of a successful web search
35+
type WebSearchResults struct {
36+
OK bool `json:"ok"`
37+
Data []*WebSearchRepository `json:"data"`
38+
}
39+
2640
// CreateRepository creates a repository for the user/organization.
2741
func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) {
2842
repo, err := repo_module.CreateRepository(doer, owner, opts)

web_src/js/components/DashboardRepoList.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@
7979
<svg-icon name="octicon-archive" :size="16" class-name="gt-ml-2"/>
8080
</span>
8181
</div>
82+
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
83+
<svg-icon v-if="repo.latest_commit_status_state" :name="statusIcon(repo.latest_commit_status_state)" :class-name="'commit-status icon text ' + statusColor(repo.latest_commit_status_state)" :size="16"/>
8284
</a>
8385
</li>
8486
</ul>
@@ -154,6 +156,15 @@ import {SvgIcon} from '../svg.js';
154156
155157
const {appSubUrl, assetUrlPrefix, pageData} = window.config;
156158
159+
const commitStatus = {
160+
pending: {name: 'octicon-dot-fill', color: 'grey'},
161+
running: {name: 'octicon-dot-fill', color: 'yellow'},
162+
success: {name: 'octicon-check', color: 'green'},
163+
error: {name: 'gitea-exclamation', color: 'red'},
164+
failure: {name: 'octicon-x', color: 'red'},
165+
warning: {name: 'gitea-exclamation', color: 'yellow'},
166+
};
167+
157168
const sfc = {
158169
components: {SvgIcon},
159170
data() {
@@ -387,7 +398,7 @@ const sfc = {
387398
}
388399
389400
if (searchedURL === this.searchURL) {
390-
this.repos = json.data;
401+
this.repos = json.data.map((webSearchRepo) => {return {...webSearchRepo.repository, latest_commit_status_state: webSearchRepo.latest_commit_status.State}});
391402
const count = response.headers.get('X-Total-Count');
392403
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
393404
this.reposTotalCount = count;
@@ -412,6 +423,14 @@ const sfc = {
412423
return 'octicon-repo';
413424
}
414425
return 'octicon-repo';
426+
},
427+
428+
statusIcon(status) {
429+
return commitStatus[status].name;
430+
},
431+
432+
statusColor(status) {
433+
return commitStatus[status].color;
415434
}
416435
},
417436
};

web_src/js/features/org-team.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export function initOrgTeamSearchRepoBox() {
2626
const items = [];
2727
$.each(response.data, (_i, item) => {
2828
items.push({
29-
title: item.full_name.split('/')[1],
30-
description: item.full_name
29+
title: item.repository.full_name.split('/')[1],
30+
description: item.repository.full_name
3131
});
3232
});
3333

web_src/js/features/repo-issue.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,8 @@ export function initRepoIssueReferenceRepositorySearch() {
291291
const filteredResponse = {success: true, results: []};
292292
$.each(response.data, (_r, repo) => {
293293
filteredResponse.results.push({
294-
name: htmlEscape(repo.full_name),
295-
value: repo.full_name
294+
name: htmlEscape(repo.repository.full_name),
295+
value: repo.repository.full_name
296296
});
297297
});
298298
return filteredResponse;

web_src/js/features/repo-template.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export function initRepoTemplateSearch() {
3434
// Parse the response from the api to work with our dropdown
3535
$.each(response.data, (_r, repo) => {
3636
filteredResponse.results.push({
37-
name: htmlEscape(repo.full_name),
38-
value: repo.id
37+
name: htmlEscape(repo.repository.full_name),
38+
value: repo.repository.id
3939
});
4040
});
4141
return filteredResponse;

web_src/js/svg.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import {h} from 'vue';
22
import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-left.svg';
33
import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg';
44
import giteaEmptyCheckbox from '../../public/img/svg/gitea-empty-checkbox.svg';
5+
import giteaExclamation from '../../public/img/svg/gitea-exclamation.svg';
56
import octiconArchive from '../../public/img/svg/octicon-archive.svg';
67
import octiconArrowSwitch from '../../public/img/svg/octicon-arrow-switch.svg';
78
import octiconBlocked from '../../public/img/svg/octicon-blocked.svg';
89
import octiconBold from '../../public/img/svg/octicon-bold.svg';
10+
import octiconCheck from '../../public/img/svg/octicon-check.svg';
911
import octiconCheckbox from '../../public/img/svg/octicon-checkbox.svg';
1012
import octiconCheckCircleFill from '../../public/img/svg/octicon-check-circle-fill.svg';
1113
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
@@ -19,6 +21,7 @@ import octiconDiffAdded from '../../public/img/svg/octicon-diff-added.svg';
1921
import octiconDiffModified from '../../public/img/svg/octicon-diff-modified.svg';
2022
import octiconDiffRemoved from '../../public/img/svg/octicon-diff-removed.svg';
2123
import octiconDiffRenamed from '../../public/img/svg/octicon-diff-renamed.svg';
24+
import octiconDotFill from '../../public/img/svg/octicon-dot-fill.svg';
2225
import octiconEye from '../../public/img/svg/octicon-eye.svg';
2326
import octiconFile from '../../public/img/svg/octicon-file.svg';
2427
import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg';
@@ -67,10 +70,12 @@ const svgs = {
6770
'gitea-double-chevron-left': giteaDoubleChevronLeft,
6871
'gitea-double-chevron-right': giteaDoubleChevronRight,
6972
'gitea-empty-checkbox': giteaEmptyCheckbox,
73+
'gitea-exclamation': giteaExclamation,
7074
'octicon-archive': octiconArchive,
7175
'octicon-arrow-switch': octiconArrowSwitch,
7276
'octicon-blocked': octiconBlocked,
7377
'octicon-bold': octiconBold,
78+
'octicon-check': octiconCheck,
7479
'octicon-check-circle-fill': octiconCheckCircleFill,
7580
'octicon-checkbox': octiconCheckbox,
7681
'octicon-chevron-down': octiconChevronDown,
@@ -84,6 +89,7 @@ const svgs = {
8489
'octicon-diff-modified': octiconDiffModified,
8590
'octicon-diff-removed': octiconDiffRemoved,
8691
'octicon-diff-renamed': octiconDiffRenamed,
92+
'octicon-dot-fill': octiconDotFill,
8793
'octicon-eye': octiconEye,
8894
'octicon-file': octiconFile,
8995
'octicon-file-directory-fill': octiconFileDirectoryFill,

0 commit comments

Comments
 (0)