detect force push (#856)

This commit is contained in:
Marko Gacesa 2023-12-07 10:25:08 +00:00 committed by Harness
parent 90d8ac7193
commit 1899e70e56
8 changed files with 86 additions and 14 deletions

View File

@ -25,6 +25,7 @@ import (
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/git"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
)
@ -57,6 +58,7 @@ type Controller struct {
principalStore store.PrincipalStore
repoStore store.RepoStore
gitReporter *eventsgit.Reporter
git git.Interface
pullreqStore store.PullReqStore
urlProvider url.Provider
protectionManager *protection.Manager
@ -67,6 +69,7 @@ func NewController(
principalStore store.PrincipalStore,
repoStore store.RepoStore,
gitReporter *eventsgit.Reporter,
git git.Interface,
pullreqStore store.PullReqStore,
urlProvider url.Provider,
protectionManager *protection.Manager,
@ -76,6 +79,7 @@ func NewController(
principalStore: principalStore,
repoStore: repoStore,
gitReporter: gitReporter,
git: git,
pullreqStore: pullreqStore,
urlProvider: urlProvider,
protectionManager: protectionManager,

View File

@ -21,6 +21,7 @@ import (
"github.com/harness/gitness/app/auth"
events "github.com/harness/gitness/app/events/git"
"github.com/harness/gitness/git"
"github.com/harness/gitness/githook"
"github.com/harness/gitness/types"
"github.com/harness/gitness/types/enum"
@ -53,7 +54,7 @@ func (c *Controller) PostReceive(
}
// report ref events (best effort)
c.reportReferenceEvents(ctx, repoID, principalID, in)
c.reportReferenceEvents(ctx, repo, principalID, in)
// create output object and have following messages fill its messages
out := &githook.Output{}
@ -69,16 +70,16 @@ func (c *Controller) PostReceive(
// TODO: in the future we might want to think about propagating errors so user is aware of events not being triggered.
func (c *Controller) reportReferenceEvents(
ctx context.Context,
repoID int64,
repo *types.Repository,
principalID int64,
in githook.PostReceiveInput,
) {
for _, refUpdate := range in.RefUpdates {
switch {
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixBranch):
c.reportBranchEvent(ctx, repoID, principalID, refUpdate)
c.reportBranchEvent(ctx, repo, principalID, refUpdate)
case strings.HasPrefix(refUpdate.Ref, gitReferenceNamePrefixTag):
c.reportTagEvent(ctx, repoID, principalID, refUpdate)
c.reportTagEvent(ctx, repo, principalID, refUpdate)
default:
// Ignore any other references in post-receive
}
@ -87,61 +88,75 @@ func (c *Controller) reportReferenceEvents(
func (c *Controller) reportBranchEvent(
ctx context.Context,
repoID int64,
repo *types.Repository,
principalID int64,
branchUpdate githook.ReferenceUpdate,
) {
switch {
case branchUpdate.Old == types.NilSHA:
c.gitReporter.BranchCreated(ctx, &events.BranchCreatedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
SHA: branchUpdate.New,
})
case branchUpdate.New == types.NilSHA:
c.gitReporter.BranchDeleted(ctx, &events.BranchDeletedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
SHA: branchUpdate.Old,
})
default:
result, err := c.git.IsAncestor(ctx, git.IsAncestorParams{
ReadParams: git.ReadParams{RepoUID: repo.GitUID},
AncestorCommitSHA: branchUpdate.Old,
DescendantCommitSHA: branchUpdate.New,
})
if err != nil {
log.Ctx(ctx).Err(err).
Str("ref", branchUpdate.Ref).
Msg("failed to check ancestor")
}
// In case of an error consider this a forced update. In post-update the branch has already been updated,
// so there's less harm in declaring the update as forced. A force update event might trigger some additional
// operations that aren't required for ordinary updates (force pushes alter the commit history of a branch).
forced := err != nil || !result.Ancestor
c.gitReporter.BranchUpdated(ctx, &events.BranchUpdatedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: branchUpdate.Ref,
OldSHA: branchUpdate.Old,
NewSHA: branchUpdate.New,
Forced: false, // TODO: data not available yet
Forced: forced,
})
}
}
func (c *Controller) reportTagEvent(
ctx context.Context,
repoID int64,
repo *types.Repository,
principalID int64,
tagUpdate githook.ReferenceUpdate,
) {
switch {
case tagUpdate.Old == types.NilSHA:
c.gitReporter.TagCreated(ctx, &events.TagCreatedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
SHA: tagUpdate.New,
})
case tagUpdate.New == types.NilSHA:
c.gitReporter.TagDeleted(ctx, &events.TagDeletedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
SHA: tagUpdate.Old,
})
default:
c.gitReporter.TagUpdated(ctx, &events.TagUpdatedPayload{
RepoID: repoID,
RepoID: repo.ID,
PrincipalID: principalID,
Ref: tagUpdate.Ref,
OldSHA: tagUpdate.Old,

View File

@ -20,6 +20,7 @@ import (
"github.com/harness/gitness/app/services/protection"
"github.com/harness/gitness/app/store"
"github.com/harness/gitness/app/url"
"github.com/harness/gitness/git"
"github.com/google/wire"
)
@ -34,6 +35,7 @@ func ProvideController(
principalStore store.PrincipalStore,
repoStore store.RepoStore,
gitReporter *eventsgit.Reporter,
git git.Interface,
pullreqStore store.PullReqStore,
urlProvider url.Provider,
protectionManager *protection.Manager,
@ -43,6 +45,7 @@ func ProvideController(
principalStore,
repoStore,
gitReporter,
git,
pullreqStore,
urlProvider,
protectionManager)

View File

@ -245,7 +245,7 @@ func initSystem(ctx context.Context, config *types.Config) (*server.System, erro
if err != nil {
return nil, err
}
githookController := githook.ProvideController(authorizer, principalStore, repoStore, reporter2, pullReqStore, provider, protectionManager)
githookController := githook.ProvideController(authorizer, principalStore, repoStore, reporter2, gitInterface, pullReqStore, provider, protectionManager)
serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore)
principalController := principal.ProvideController(principalStore)
checkController := check2.ProvideController(transactor, authorizer, repoStore, checkStore, gitInterface)

View File

@ -74,6 +74,7 @@ type Adapter interface {
Merge(ctx context.Context, pr *types.PullRequest, mergeMethod enum.MergeMethod, baseBranch, trackingBranch string,
tmpBasePath string, mergeMsg string, identity *types.Identity, env ...string) (types.MergeResult, error)
GetMergeBase(ctx context.Context, repoPath, remote, base, head string) (string, string, error)
IsAncestor(ctx context.Context, repoPath, ancestorCommitSHA, descendantCommitSHA string) (bool, error)
Blame(ctx context.Context, repoPath, rev, file string, lineFrom, lineTo int) types.BlameReader
Sync(ctx context.Context, repoPath string, source string, refSpecs []string) error

View File

@ -585,6 +585,28 @@ func (a Adapter) GetMergeBase(
return strings.TrimSpace(stdout), base, nil
}
// IsAncestor returns if the provided commit SHA is ancestor of the other commit SHA.
func (a Adapter) IsAncestor(
ctx context.Context,
repoPath string,
ancestorCommitSHA, descendantCommitSHA string,
) (bool, error) {
if repoPath == "" {
return false, ErrRepositoryPathEmpty
}
_, stderr, runErr := git.NewCommand(ctx, "merge-base", "--is-ancestor", ancestorCommitSHA, descendantCommitSHA).
RunStdString(&git.RunOpts{Dir: repoPath})
if runErr != nil {
if runErr.IsExitCode(1) && stderr == "" {
return false, nil
}
return false, processGiteaErrorf(runErr, "failed to check commit ancestry: %v", stderr)
}
return true, nil
}
// giteaRunStdError is an implementation of the RunStdError interface in the gitea codebase.
// It allows us to process gitea errors even when using cmd.Run() instead of cmd.RunStdString() or run.StdBytes().
type giteaRunStdError struct {

View File

@ -53,6 +53,7 @@ type Interface interface {
GetCommitDivergences(ctx context.Context, params *GetCommitDivergencesParams) (*GetCommitDivergencesOutput, error)
CommitFiles(ctx context.Context, params *CommitFilesParams) (CommitFilesResponse, error)
MergeBase(ctx context.Context, params MergeBaseParams) (MergeBaseOutput, error)
IsAncestor(ctx context.Context, params IsAncestorParams) (IsAncestorOutput, error)
/*
* Git Cli Service

View File

@ -411,3 +411,29 @@ func (s *Service) MergeBase(
MergeBaseSHA: result,
}, nil
}
type IsAncestorParams struct {
ReadParams
AncestorCommitSHA string
DescendantCommitSHA string
}
type IsAncestorOutput struct {
Ancestor bool
}
func (s *Service) IsAncestor(
ctx context.Context,
params IsAncestorParams,
) (IsAncestorOutput, error) {
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
result, err := s.adapter.IsAncestor(ctx, repoPath, params.AncestorCommitSHA, params.DescendantCommitSHA)
if err != nil {
return IsAncestorOutput{}, err
}
return IsAncestorOutput{
Ancestor: result,
}, nil
}