Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add status indicator on main home screen for each repo #24638

Merged
merged 23 commits into from
May 13, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
45ff2c0
Add status indicator on main home screen for each repo
yardenshoham May 10, 2023
2279f7c
Update modules/structs/repo.go
yardenshoham May 10, 2023
62a589a
prefix `latest`
yardenshoham May 10, 2023
3a2722a
Don't touch the API
yardenshoham May 10, 2023
e414a41
Deduplicate code for svg selection
yardenshoham May 11, 2023
00717f6
Use `O(1)` database calls instead of `O(n)`
yardenshoham May 11, 2023
16643bc
Merge branch 'main' into issues/15620
yardenshoham May 11, 2023
0e7436e
Don't loop over branches, introduce `GetBranch`
yardenshoham May 11, 2023
0041c01
Update models/git/commit_status.go
yardenshoham May 11, 2023
e858179
Address some review comments from delvh
yardenshoham May 11, 2023
d1267e4
Send the entire status
yardenshoham May 11, 2023
e6d1a04
Merge branch 'main' into issues/15620
yardenshoham May 12, 2023
64f494f
Merge branch 'main' into issues/15620
yardenshoham May 12, 2023
fa57458
Merge branch 'main' into issues/15620
yardenshoham May 12, 2023
dc3f20a
Merge branch 'main' into issues/15620
yardenshoham May 12, 2023
927a3af
Parallelize `GetBranch` calls
yardenshoham May 13, 2023
44cc36c
Merge branch 'main' into issues/15620
yardenshoham May 13, 2023
52cd77e
Cut git calls in half
yardenshoham May 13, 2023
d37464b
Cut git calls in half again
yardenshoham May 13, 2023
7a22fcd
Merge branch 'main' into issues/15620
yardenshoham May 13, 2023
433df0b
Merge branch 'main' into issues/15620
GiteaBot May 13, 2023
2349e18
Merge branch 'main' into issues/15620
GiteaBot May 13, 2023
16bceb2
Merge branch 'main' into issues/15620
GiteaBot May 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions models/git/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/builder"
"xorm.io/xorm"
)

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

// GetLatestCommitStatusForPairs returns all statuses with a unique context for a given list of repo-sha pairs
func GetLatestCommitStatusForPairs(ctx context.Context, repoIDsToLatestCommitSHAs map[int64]string, listOptions db.ListOptions) (map[int64][]*CommitStatus, error) {
type result struct {
ID int64
RepoID int64
}

results := make([]result, 0, len(repoIDsToLatestCommitSHAs))

sess := db.GetEngine(ctx).Table(&CommitStatus{})

// Create a disjunction of conditions for each repoID and SHA pair
conds := make([]builder.Cond, 0, len(repoIDsToLatestCommitSHAs))
for repoID, sha := range repoIDsToLatestCommitSHAs {
conds = append(conds, builder.Eq{"repo_id": repoID, "sha": sha})
}
sess = sess.Where(builder.Or(conds...)).
Select("max( id ) as id, repo_id").
GroupBy("context_hash, repo_id").OrderBy("max( id ) desc")

sess = db.SetSessionPagination(sess, &listOptions)

err := sess.Find(&results)
if err != nil {
return nil, err
}

ids := make([]int64, 0, len(results))
repoStatuses := make(map[int64][]*CommitStatus)
for _, result := range results {
ids = append(ids, result.ID)
}

statuses := make([]*CommitStatus, 0, len(ids))
if len(ids) > 0 {
err = db.GetEngine(ctx).In("id", ids).Find(&statuses)
if err != nil {
return nil, err
}

// Group the statuses by repo ID
for _, status := range statuses {
repoStatuses[status.RepoID] = append(repoStatuses[status.RepoID], status)
}
}

return repoStatuses, nil
}

// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
start := timeutil.TimeStampNow().AddDuration(-before)
Expand Down
11 changes: 11 additions & 0 deletions modules/git/repo_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ func GetBranchesByPath(ctx context.Context, path string, skip, limit int) ([]*Br
return gitRepo.GetBranches(skip, limit)
}

// GetBranch returns a branch by its name
func GetBranch(ctx context.Context, path, branch string) (*Branch, error) {
gitRepo, err := OpenRepository(ctx, path)
if err != nil {
return nil, err
}
defer gitRepo.Close()

return gitRepo.GetBranch(branch)
}

// GetBranches returns a slice of *git.Branch
func (repo *Repository) GetBranches(skip, limit int) ([]*Branch, int, error) {
brs, countAll, err := repo.GetBranchNames(skip, limit)
Expand Down
52 changes: 39 additions & 13 deletions routers/web/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
Expand Down Expand Up @@ -576,23 +577,48 @@ func SearchRepo(ctx *context.Context) {
return
}

results := make([]*api.Repository, len(repos))
// collect the latest commit of each repo
repoIDsToLatestCommitSHAs := make(map[int64]string)
for _, repo := range repos {
branch, err := repo_service.GetBranch(ctx, repo, repo.DefaultBranch)
if err != nil {
continue
}
commit, err := branch.GetCommit()
if err != nil {
log.Error("GetCommit: %v", err)
continue
}
repoIDsToLatestCommitSHAs[repo.ID] = commit.ID.String()
}

// call the database O(1) times to get the commit statuses for all repos
repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoIDsToLatestCommitSHAs, db.ListOptions{})
if err != nil {
log.Error("GetLatestCommitStatusForPairs: %v", err)
return
}

results := make([]*repo_service.WebSearchRepository, len(repos))
for i, repo := range repos {
results[i] = &api.Repository{
ID: repo.ID,
FullName: repo.FullName(),
Fork: repo.IsFork,
Private: repo.IsPrivate,
Template: repo.IsTemplate,
Mirror: repo.IsMirror,
Stars: repo.NumStars,
HTMLURL: repo.HTMLURL(),
Link: repo.Link(),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
results[i] = &repo_service.WebSearchRepository{
Repository: &api.Repository{
ID: repo.ID,
FullName: repo.FullName(),
Fork: repo.IsFork,
Private: repo.IsPrivate,
Template: repo.IsTemplate,
Mirror: repo.IsMirror,
Stars: repo.NumStars,
HTMLURL: repo.HTMLURL(),
Link: repo.Link(),
Internal: !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePrivate,
},
LatestCommitStatus: git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]),
}
}

ctx.JSON(http.StatusOK, api.SearchResults{
ctx.JSON(http.StatusOK, repo_service.WebSearchResults{
OK: true,
Data: results,
})
Expand Down
4 changes: 4 additions & 0 deletions services/repository/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ func GetBranches(ctx context.Context, repo *repo_model.Repository, skip, limit i
return git.GetBranchesByPath(ctx, repo.RepoPath(), skip, limit)
}

func GetBranch(ctx context.Context, repo *repo_model.Repository, branch string) (*git.Branch, error) {
return git.GetBranch(ctx, repo.RepoPath(), branch)
}

// checkBranchName validates branch name with existing repository branches
func checkBranchName(ctx context.Context, repo *repo_model.Repository, name string) error {
_, err := git.WalkReferences(ctx, repo.RepoPath(), func(_, refName string) error {
Expand Down
14 changes: 14 additions & 0 deletions services/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
Expand All @@ -20,9 +21,22 @@ import (
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
pull_service "code.gitea.io/gitea/services/pull"
)

// WebSearchRepository represents a repository returned by web search
type WebSearchRepository struct {
Repository *structs.Repository `json:"repository"`
LatestCommitStatus *git.CommitStatus `json:"latest_commit_status"`
}

// WebSearchResults results of a successful web search
type WebSearchResults struct {
OK bool `json:"ok"`
Data []*WebSearchRepository `json:"data"`
}

// CreateRepository creates a repository for the user/organization.
func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) {
repo, err := repo_module.CreateRepository(doer, owner, opts)
Expand Down
21 changes: 20 additions & 1 deletion web_src/js/components/DashboardRepoList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
<svg-icon name="octicon-archive" :size="16" class-name="gt-ml-2"/>
</span>
</div>
<!-- the commit status icon logic is taken from templates/repo/commit_status.tmpl -->
<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"/>
</a>
</li>
</ul>
Expand Down Expand Up @@ -154,6 +156,15 @@ import {SvgIcon} from '../svg.js';

const {appSubUrl, assetUrlPrefix, pageData} = window.config;

const commitStatus = {
pending: {name: 'octicon-dot-fill', color: 'grey'},
running: {name: 'octicon-dot-fill', color: 'yellow'},
success: {name: 'octicon-check', color: 'green'},
error: {name: 'gitea-exclamation', color: 'red'},
failure: {name: 'octicon-x', color: 'red'},
warning: {name: 'gitea-exclamation', color: 'yellow'},
};

const sfc = {
components: {SvgIcon},
data() {
Expand Down Expand Up @@ -387,7 +398,7 @@ const sfc = {
}

if (searchedURL === this.searchURL) {
this.repos = json.data;
this.repos = json.data.map((webSearchRepo) => {return {...webSearchRepo.repository, latest_commit_status_state: webSearchRepo.latest_commit_status.State}});
const count = response.headers.get('X-Total-Count');
if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
this.reposTotalCount = count;
Expand All @@ -412,6 +423,14 @@ const sfc = {
return 'octicon-repo';
}
return 'octicon-repo';
},

statusIcon(status) {
return commitStatus[status].name;
},

statusColor(status) {
return commitStatus[status].color;
}
},
};
Expand Down
4 changes: 2 additions & 2 deletions web_src/js/features/org-team.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ export function initOrgTeamSearchRepoBox() {
const items = [];
$.each(response.data, (_i, item) => {
items.push({
title: item.full_name.split('/')[1],
description: item.full_name
title: item.repository.full_name.split('/')[1],
description: item.repository.full_name
});
});

Expand Down
4 changes: 2 additions & 2 deletions web_src/js/features/repo-issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ export function initRepoIssueReferenceRepositorySearch() {
const filteredResponse = {success: true, results: []};
$.each(response.data, (_r, repo) => {
filteredResponse.results.push({
name: htmlEscape(repo.full_name),
value: repo.full_name
name: htmlEscape(repo.repository.full_name),
value: repo.repository.full_name
});
});
return filteredResponse;
Expand Down
4 changes: 2 additions & 2 deletions web_src/js/features/repo-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ export function initRepoTemplateSearch() {
// Parse the response from the api to work with our dropdown
$.each(response.data, (_r, repo) => {
filteredResponse.results.push({
name: htmlEscape(repo.full_name),
value: repo.id
name: htmlEscape(repo.repository.full_name),
value: repo.repository.id
});
});
return filteredResponse;
Expand Down
6 changes: 6 additions & 0 deletions web_src/js/svg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import {h} from 'vue';
import giteaDoubleChevronLeft from '../../public/img/svg/gitea-double-chevron-left.svg';
import giteaDoubleChevronRight from '../../public/img/svg/gitea-double-chevron-right.svg';
import giteaEmptyCheckbox from '../../public/img/svg/gitea-empty-checkbox.svg';
import giteaExclamation from '../../public/img/svg/gitea-exclamation.svg';
import octiconArchive from '../../public/img/svg/octicon-archive.svg';
import octiconArrowSwitch from '../../public/img/svg/octicon-arrow-switch.svg';
import octiconBlocked from '../../public/img/svg/octicon-blocked.svg';
import octiconBold from '../../public/img/svg/octicon-bold.svg';
import octiconCheck from '../../public/img/svg/octicon-check.svg';
import octiconCheckbox from '../../public/img/svg/octicon-checkbox.svg';
import octiconCheckCircleFill from '../../public/img/svg/octicon-check-circle-fill.svg';
import octiconChevronDown from '../../public/img/svg/octicon-chevron-down.svg';
Expand All @@ -19,6 +21,7 @@ import octiconDiffAdded from '../../public/img/svg/octicon-diff-added.svg';
import octiconDiffModified from '../../public/img/svg/octicon-diff-modified.svg';
import octiconDiffRemoved from '../../public/img/svg/octicon-diff-removed.svg';
import octiconDiffRenamed from '../../public/img/svg/octicon-diff-renamed.svg';
import octiconDotFill from '../../public/img/svg/octicon-dot-fill.svg';
import octiconEye from '../../public/img/svg/octicon-eye.svg';
import octiconFile from '../../public/img/svg/octicon-file.svg';
import octiconFileDirectoryFill from '../../public/img/svg/octicon-file-directory-fill.svg';
Expand Down Expand Up @@ -67,10 +70,12 @@ const svgs = {
'gitea-double-chevron-left': giteaDoubleChevronLeft,
'gitea-double-chevron-right': giteaDoubleChevronRight,
'gitea-empty-checkbox': giteaEmptyCheckbox,
'gitea-exclamation': giteaExclamation,
'octicon-archive': octiconArchive,
'octicon-arrow-switch': octiconArrowSwitch,
'octicon-blocked': octiconBlocked,
'octicon-bold': octiconBold,
'octicon-check': octiconCheck,
'octicon-check-circle-fill': octiconCheckCircleFill,
'octicon-checkbox': octiconCheckbox,
'octicon-chevron-down': octiconChevronDown,
Expand All @@ -84,6 +89,7 @@ const svgs = {
'octicon-diff-modified': octiconDiffModified,
'octicon-diff-removed': octiconDiffRemoved,
'octicon-diff-renamed': octiconDiffRenamed,
'octicon-dot-fill': octiconDotFill,
'octicon-eye': octiconEye,
'octicon-file': octiconFile,
'octicon-file-directory-fill': octiconFileDirectoryFill,
Expand Down