fix commit adn files changed counters

jobatzil/rename
Enver Bisevac 2023-08-11 13:29:25 +02:00
parent d2c04cf42c
commit 63145686f4
16 changed files with 2358 additions and 2155 deletions

View File

@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"strconv"
"time"
"github.com/harness/gitness/gitrpc/rpc"
@ -97,6 +98,7 @@ type RenameDetails struct {
type ListCommitsOutput struct {
Commits []Commit
RenameDetails []*RenameDetails
TotalCommits int
}
func (c *Client) ListCommits(ctx context.Context, params *ListCommitsParams) (*ListCommitsOutput, error) {
@ -122,6 +124,21 @@ func (c *Client) ListCommits(ctx context.Context, params *ListCommitsParams) (*L
Commits: make([]Commit, 0, 16),
}
// check for list commits header
header, err := stream.Header()
if err != nil {
return nil, processRPCErrorf(err, "failed to read list commits header from stream")
}
values := header.Get("total-commits")
if len(values) > 0 && values[0] != "" {
total, err := strconv.ParseInt(values[0], 10, 32)
if err != nil {
return nil, processRPCErrorf(err, "failed to convert header total-commits")
}
output.TotalCommits = int(total)
}
for {
var next *rpc.ListCommitsResponse
next, err = stream.Recv()

View File

@ -6,12 +6,14 @@ package service
import (
"context"
"strconv"
"github.com/harness/gitness/gitrpc/internal/types"
"github.com/harness/gitness/gitrpc/rpc"
"github.com/rs/zerolog/log"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
@ -65,7 +67,27 @@ func (s RepositoryService) ListCommits(request *rpc.ListCommitsRequest,
return processGitErrorf(err, "failed to get list of commits")
}
// try to get total commits between gitref and After refs
totalCommits := 0
if request.Page == 1 && len(gitCommits) < int(request.Limit) {
totalCommits = len(gitCommits)
} else if request.After != "" && request.GitRef != request.After {
div, err := s.adapter.GetCommitDivergences(ctx, repoPath, []types.CommitDivergenceRequest{
{From: request.GitRef, To: request.After},
}, 0)
if err != nil {
return processGitErrorf(err, "failed to get total commits")
}
if len(div) > 0 {
totalCommits = int(div[0].Ahead)
}
}
log.Ctx(ctx).Trace().Msgf("git adapter returned %d commits", len(gitCommits))
header := metadata.New(map[string]string{"total-commits": strconv.Itoa(totalCommits)})
if err := stream.SendHeader(header); err != nil {
return ErrInternalf("unable to send 'total-commits' header", err)
}
for i := range gitCommits {
var commit *rpc.Commit

View File

@ -20,14 +20,14 @@ import (
* ListCommits lists the commits of a repo.
*/
func (c *Controller) ListCommits(ctx context.Context, session *auth.Session,
repoRef string, gitRef string, filter *types.CommitFilter) ([]types.Commit, []types.RenameDetails, error) {
repoRef string, gitRef string, filter *types.CommitFilter) (types.ListCommitResponse, error) {
repo, err := c.repoStore.FindByRef(ctx, repoRef)
if err != nil {
return nil, nil, err
return types.ListCommitResponse{}, err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return nil, nil, err
return types.ListCommitResponse{}, err
}
// set gitRef to default branch in case an empty reference was provided
@ -47,7 +47,7 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session,
Committer: filter.Committer,
})
if err != nil {
return nil, nil, err
return types.ListCommitResponse{}, err
}
commits := make([]types.Commit, len(rpcOut.Commits))
@ -55,7 +55,7 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session,
var commit *types.Commit
commit, err = controller.MapCommit(&rpcOut.Commits[i])
if err != nil {
return nil, nil, fmt.Errorf("failed to map commit: %w", err)
return types.ListCommitResponse{}, fmt.Errorf("failed to map commit: %w", err)
}
commits[i] = *commit
}
@ -64,9 +64,13 @@ func (c *Controller) ListCommits(ctx context.Context, session *auth.Session,
for i := range rpcOut.RenameDetails {
renameDetails := controller.MapRenameDetails(rpcOut.RenameDetails[i])
if renameDetails == nil {
return nil, nil, fmt.Errorf("rename details was nil")
return types.ListCommitResponse{}, fmt.Errorf("rename details was nil")
}
renameDetailList[i] = *renameDetails
}
return commits, renameDetailList, nil
return types.ListCommitResponse{
Commits: commits,
RenameDetails: renameDetailList,
TotalCommits: rpcOut.TotalCommits,
}, nil
}

View File

@ -43,3 +43,26 @@ func HandleDiff(repoCtrl *repo.Controller) http.HandlerFunc {
render.JSONArrayDynamic(ctx, w, stream)
}
}
// HandleDiffStats how diff statistics of two commits, branches or tags.
func HandleDiffStats(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
path := request.GetOptionalRemainderFromPath(r)
output, err := repoCtrl.DiffStats(ctx, session, repoRef, path)
if err != nil {
render.TranslatedUserError(w, err)
return
}
render.JSON(w, http.StatusOK, output)
}
}

View File

@ -10,7 +10,6 @@ import (
"github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
"github.com/harness/gitness/types"
)
/*
@ -34,20 +33,15 @@ func HandleListCommits(repoCtrl *repo.Controller) http.HandlerFunc {
return
}
commits, renameDetails, err := repoCtrl.ListCommits(ctx, session, repoRef, gitRef, filter)
list, err := repoCtrl.ListCommits(ctx, session, repoRef, gitRef, filter)
if err != nil {
render.TranslatedUserError(w, err)
return
}
commitsResponse := types.ListCommitResponse{
Commits: commits,
RenameDetails: renameDetails,
}
// TODO: get last page indicator explicitly - current check is wrong in case len % limit == 0
isLastPage := len(commits) < filter.Limit
isLastPage := len(list.Commits) < filter.Limit
render.PaginationNoTotal(r, w, filter.Page, filter.Limit, isLastPage)
render.JSON(w, http.StatusOK, commitsResponse)
render.JSON(w, http.StatusOK, list)
}
}

View File

@ -661,13 +661,23 @@ func repoOperations(reflector *openapi3.Reflector) {
opDiff.WithTags("repository")
opDiff.WithMapOfAnything(map[string]interface{}{"operationId": "rawDiff"})
_ = reflector.SetRequest(&opDiff, new(getRawDiffRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opDiff, new(types.DiffStats), http.StatusOK)
_ = reflector.SetStringResponse(&opDiff, http.StatusOK, "text/plain")
_ = reflector.SetJSONResponse(&opDiff, []gitrpc.FileDiff{}, http.StatusOK)
_ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDiff, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/diff/{range}", opDiff)
opDiffStats := openapi3.Operation{}
opDiffStats.WithTags("repository")
opDiffStats.WithMapOfAnything(map[string]interface{}{"operationId": "diffStats"})
_ = reflector.SetRequest(&opDiffStats, new(getRawDiffRequest), http.MethodGet)
_ = reflector.SetJSONResponse(&opDiffStats, new(types.DiffStats), http.StatusOK)
_ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusInternalServerError)
_ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusUnauthorized)
_ = reflector.SetJSONResponse(&opDiffStats, new(usererror.Error), http.StatusForbidden)
_ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/diff-stats/{range}", opDiffStats)
opMergeCheck := openapi3.Operation{}
opMergeCheck.WithTags("repository")
opMergeCheck.WithMapOfAnything(map[string]interface{}{"operationId": "mergeCheck"})

View File

@ -271,6 +271,9 @@ func setupRepos(r chi.Router,
r.Route("/diff", func(r chi.Router) {
r.Get("/*", handlerrepo.HandleDiff(repoCtrl))
})
r.Route("/diff-stats", func(r chi.Router) {
r.Get("/*", handlerrepo.HandleDiffStats(repoCtrl))
})
r.Route("/merge-check", func(r chi.Router) {
r.Post("/*", handlerrepo.HandleMergeCheck(repoCtrl))
})

View File

@ -74,4 +74,5 @@ type RenameDetails struct {
type ListCommitResponse struct {
Commits []Commit `json:"commits"`
RenameDetails []RenameDetails `json:"rename_details"`
TotalCommits int `json:"total_commits,omitempty"`
}

View File

@ -76,11 +76,13 @@ export const Changes: React.FC<ChangesProps> = ({
const [commitRange, setCommitRange] = useState<string[]>(defaultCommitRange || [])
const { routes } = useAppContext()
const { data: prCommits } = useGet<TypesCommit[]>({
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestMetadata?.number}/commits`,
const { data: prCommits } = useGet<{
commits: TypesCommit[]
}>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
git_ref: pullRequestMetadata?.source_branch,
after: pullRequestMetadata?.target_branch
git_ref: sourceRef,
after: targetRef
},
lazy: !pullRequestMetadata?.number
})
@ -118,8 +120,6 @@ export const Changes: React.FC<ChangesProps> = ({
path: `/api/v1/repos/${repoMetadata?.path}/+/diff/${
commitRangePath
? commitRangePath
: pullRequestMetadata
? `${pullRequestMetadata.merge_base_sha}...${pullRequestMetadata.source_sha}`
: `${targetRef}...${sourceRef}`
}`,
requestOptions: {
@ -233,7 +233,7 @@ export const Changes: React.FC<ChangesProps> = ({
<Container flex={{ alignItems: 'center' }}>
<Render when={pullRequestMetadata?.number}>
<CommitRangeDropdown
allCommits={prCommits || []}
allCommits={prCommits?.commits || []}
selectedCommits={commitRange}
setSelectedCommits={setCommitRange}
/>
@ -309,8 +309,8 @@ export const Changes: React.FC<ChangesProps> = ({
repoMetadata={repoMetadata}
pullRequestMetadata={pullRequestMetadata}
onCommentUpdate={onCommentUpdate}
targetRef={pullRequestMetadata ? pullRequestMetadata.merge_base_sha : targetRef}
sourceRef={pullRequestMetadata ? pullRequestMetadata.source_sha : sourceRef}
targetRef={targetRef}
sourceRef={sourceRef}
/>
))}
</Layout.Vertical>

View File

@ -0,0 +1,27 @@
import React from 'react'
import { Container, PageError } from '@harness/uicore'
import { getErrorMessage } from 'utils/Utils'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
interface TabContentWrapperProps {
className?: string
loading?: boolean
error?: Unknown
onRetry: () => void
}
export const TabContentWrapper: React.FC<TabContentWrapperProps> = ({
className,
loading,
error,
onRetry,
children
}) => {
return (
<Container className={className} padding="xlarge">
<LoadingSpinner visible={loading} withBorder={true} />
{error && <PageError message={getErrorMessage(error)} onClick={onRetry} />}
{!error && children}
</Container>
)
}

View File

@ -1,5 +1,5 @@
import { noop } from 'lodash-es'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import React, { useCallback, useState } from 'react'
import {
Container,
PageBody,
@ -20,19 +20,17 @@ import { useAppContext } from 'AppContext'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { useStrings } from 'framework/strings'
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
import { voidFn, getErrorMessage, LIST_FETCHING_LIMIT, showToaster } from 'utils/Utils'
import { getErrorMessage, showToaster } from 'utils/Utils'
import { Images } from 'images'
import { CodeIcon, isRefATag, makeDiffRefs } from 'utils/GitUtils'
import { CommitsView } from 'components/CommitsView/CommitsView'
import { Changes } from 'components/Changes/Changes'
import type { OpenapiCreatePullReqRequest, TypesCommit, TypesPullReq } from 'services/code'
import type { OpenapiCreatePullReqRequest, TypesDiffStats, TypesPullReq, TypesRepository } from 'services/code'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { usePageIndex } from 'hooks/usePageIndex'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { TabTitleWithCount, tabContainerCSS } from 'components/TabTitleWithCount/TabTitleWithCount'
import type { DiffFileEntry } from 'utils/types'
import { MarkdownEditorWithPreview } from 'components/MarkdownEditorWithPreview/MarkdownEditorWithPreview'
import { TabContentWrapper } from 'components/TabContentWrapper/TabContentWrapper'
import { CompareContentHeader, PRCreationType } from './CompareContentHeader/CompareContentHeader'
import { CompareCommits } from './CompareCommits'
import css from './Compare.module.scss'
export default function Compare() {
@ -42,29 +40,16 @@ export default function Compare() {
const { repoMetadata, error, loading, diffRefs } = useGetRepositoryMetadata()
const [sourceGitRef, setSourceGitRef] = useState(diffRefs.sourceGitRef)
const [targetGitRef, setTargetGitRef] = useState(diffRefs.targetGitRef)
const [page, setPage] = usePageIndex()
const limit = LIST_FETCHING_LIMIT
const {
data: commits,
error: commitsError,
refetch,
response
} = useGet<{
commits: TypesCommit[]
}>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
limit,
page,
git_ref: sourceGitRef,
after: targetGitRef
},
lazy: !repoMetadata
})
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [diffs, setDiffs] = useState<DiffFileEntry[]>([])
const { showError } = useToaster()
const {
data,
error: errorStats
} = useGet<TypesDiffStats>({
path: `/api/v1/repos/${repoMetadata?.path}/+/diff-stats/${targetGitRef}...${sourceGitRef}`,
lazy: !repoMetadata
})
const { mutate: createPullRequest, loading: creatingInProgress } = useMutate<TypesPullReq>({
verb: 'POST',
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq`
@ -144,30 +129,6 @@ export default function Compare() {
repoMetadata
]
)
const ChangesTab = useMemo(() => {
if (repoMetadata) {
return (
<Container className={css.changesContainer}>
<Changes
readOnly
repoMetadata={repoMetadata}
targetRef={targetGitRef}
sourceRef={sourceGitRef}
emptyTitle={getString('noChanges')}
emptyMessage={getString('noChangesCompare')}
onCommentUpdate={noop}
onDataReady={setDiffs}
/>
</Container>
)
}
}, [repoMetadata, sourceGitRef, targetGitRef, getString])
useEffect(() => {
if (commits?.commits?.length) {
setTitle(commits.commits[0].title as string)
}
}, [commits?.commits])
return (
<Container className={css.main}>
@ -176,7 +137,7 @@ export default function Compare() {
title={getString('comparingChanges')}
dataTooltipId="comparingChanges"
/>
<PageBody error={getErrorMessage(error || commitsError)} retryOnError={voidFn(refetch)} className={css.pageBody}>
<PageBody error={getErrorMessage(error || errorStats)} className={css.pageBody}>
<LoadingSpinner visible={loading} />
{repoMetadata && (
@ -219,7 +180,6 @@ export default function Compare() {
id="prComparing"
defaultSelectedTabId="general"
large={false}
onChange={() => setPage(1)}
tabList={[
{
id: 'general',
@ -259,8 +219,6 @@ export default function Compare() {
</Layout.Vertical>
</Container>
</Layout.Vertical>
{/** Fake rendering Changes Tab to get changes count - no API has it now */}
<Container style={{ display: 'none' }}>{ChangesTab}</Container>
</Container>
)
},
@ -270,21 +228,17 @@ export default function Compare() {
<TabTitleWithCount
icon={CodeIcon.Commit}
title={getString('commits')}
count={commits?.commits?.length || 0}
count={data?.commits}
padding={{ left: 'medium' }}
/>
),
panel: (
<Container padding="xlarge">
<CommitsView
commits={commits?.commits || []}
repoMetadata={repoMetadata}
emptyTitle={getString('compareEmptyDiffTitle')}
emptyMessage={getString('compareEmptyDiffMessage')}
/>
<ResourceListingPagination response={response} page={page} setPage={setPage} />
</Container>
)
panel:
<CompareCommits
repoMetadata={repoMetadata as TypesRepository}
sourceSha={sourceGitRef}
targetSha={targetGitRef}
handleRefresh={()=>{}} // TODO: when to refresh
/>
},
{
id: 'diff',
@ -292,11 +246,22 @@ export default function Compare() {
<TabTitleWithCount
icon={CodeIcon.File}
title={getString('filesChanged')}
count={diffs?.length || 0}
count={data?.files_changed || 0}
padding={{ left: 'medium' }}
/>
),
panel: ChangesTab
panel:
<TabContentWrapper loading={loading} error={error} onRetry={()=>{}} className={css.changesContainer}>
<Changes
readOnly
repoMetadata={repoMetadata}
targetRef={targetGitRef}
sourceRef={sourceGitRef}
emptyTitle={getString('noChanges')}
emptyMessage={getString('noChangesCompare')}
onCommentUpdate={noop}
/>
</TabContentWrapper>
}
]}
/>

View File

@ -0,0 +1,58 @@
import React from 'react'
import { useGet } from 'restful-react'
import type { TypesCommit } from 'services/code'
import type { GitInfoProps } from 'utils/GitUtils'
import { voidFn, LIST_FETCHING_LIMIT } from 'utils/Utils'
import { usePageIndex } from 'hooks/usePageIndex'
import { useStrings } from 'framework/strings'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { CommitsView } from 'components/CommitsView/CommitsView'
import { TabContentWrapper } from 'components/TabContentWrapper/TabContentWrapper'
interface CommitProps extends Pick<GitInfoProps, 'repoMetadata'> {
sourceSha? :string
targetSha? :string
handleRefresh: () => void
}
export const CompareCommits: React.FC<CommitProps> = ({
repoMetadata,
sourceSha,
targetSha,
handleRefresh
}) => {
const limit = LIST_FETCHING_LIMIT
const [page, setPage] = usePageIndex()
const { getString } = useStrings()
const {
data,
error,
loading,
refetch,
response
} = useGet<{
commits: TypesCommit[]
}>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
limit,
page,
git_ref: sourceSha,
after: targetSha
},
lazy: !repoMetadata
})
return (
<TabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}>
<CommitsView
commits={data?.commits || []}
repoMetadata={repoMetadata}
emptyTitle={getString('compareEmptyDiffTitle')}
emptyMessage={getString('compareEmptyDiffMessage')}
handleRefresh={voidFn(handleRefresh)}
/>
<ResourceListingPagination response={response} page={page} setPage={setPage} />
</TabContentWrapper>
)
}

View File

@ -226,8 +226,8 @@ export default function PullRequest() {
repoMetadata={repoMetadata as TypesRepository}
pullRequestMetadata={prData as TypesPullReq}
defaultCommitRange={compact(commitSHA?.split(/~1\.\.\.|\.\.\./g))}
targetRef={prData?.target_branch}
sourceRef={prData?.source_branch}
targetRef={prData?.merge_base_sha}
sourceRef={prData?.source_sha}
emptyTitle={getString('noChanges')}
emptyMessage={getString('noChangesPR')}
onCommentUpdate={voidFn(refetchPullRequest)}

View File

@ -24,18 +24,20 @@ export const PullRequestCommits: React.FC<CommitProps> = ({
const [page, setPage] = usePageIndex()
const { getString } = useStrings()
const {
data: commits,
data,
error,
loading,
refetch,
response
} = useGet<TypesCommit[]>({
path: `/api/v1/repos/${repoMetadata?.path}/+/pullreq/${pullRequestMetadata.number}/commits`,
} = useGet<{
commits: TypesCommit[]
}>({
path: `/api/v1/repos/${repoMetadata?.path}/+/commits`,
queryParams: {
limit,
page,
git_ref: pullRequestMetadata.source_branch,
after: pullRequestMetadata.target_branch
git_ref: pullRequestMetadata.source_sha,
after: pullRequestMetadata.merge_base_sha
},
lazy: !repoMetadata
})
@ -43,7 +45,7 @@ export const PullRequestCommits: React.FC<CommitProps> = ({
return (
<PullRequestTabContentWrapper loading={loading} error={error} onRetry={voidFn(refetch)}>
<CommitsView
commits={commits}
commits={data?.commits || []}
repoMetadata={repoMetadata}
emptyTitle={getString('noCommits')}
emptyMessage={getString('noCommitsPR')}

View File

@ -530,6 +530,7 @@ export interface TypesIdentity {
export interface TypesListCommitResponse {
commits?: TypesCommit[] | null
rename_details?: TypesRenameDetails[] | null
total_commits?: number
}
export interface TypesMembershipSpace {

File diff suppressed because it is too large Load Diff