fix: [CODE-2717]: Modify Sync to take DefaultBranch as input (#2962)

This commit is contained in:
Johannes Batzill 2024-11-11 23:18:28 +00:00 committed by Harness
parent 4d4917eddc
commit ccce5ad646
5 changed files with 102 additions and 45 deletions

View File

@ -314,16 +314,12 @@ func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo
log.Info().Msg("sync repository") log.Info().Msg("sync repository")
defaultBranch, err := r.syncGitRepository(ctx, &systemPrincipal, repo, cloneURLWithAuth) err = r.syncGitRepository(ctx, &systemPrincipal, repo, cloneURLWithAuth)
if err != nil { if err != nil {
return fmt.Errorf("failed to sync git repository from '%s': %w", input.CloneURL, err) return fmt.Errorf("failed to sync git repository from '%s': %w", input.CloneURL, err)
} }
log.Info().Msgf("successfully synced repository (returned default branch: '%s')", defaultBranch) log.Info().Msgf("successfully synced repository (with default branch: %q)", repo.DefaultBranch)
if defaultBranch == "" {
defaultBranch = r.defaultBranch
}
log.Info().Msg("update repo in DB") log.Info().Msg("update repo in DB")
@ -333,7 +329,6 @@ func (r *Repository) Handle(ctx context.Context, data string, _ job.ProgressRepo
} }
repo.GitUID = gitUID repo.GitUID = gitUID
repo.DefaultBranch = defaultBranch
repo.State = enum.RepoStateActive repo.State = enum.RepoStateActive
return nil return nil
@ -463,23 +458,24 @@ func (r *Repository) syncGitRepository(ctx context.Context,
principal *types.Principal, principal *types.Principal,
repo *types.Repository, repo *types.Repository,
sourceCloneURL string, sourceCloneURL string,
) (string, error) { ) error {
writeParams, err := r.createRPCWriteParams(ctx, principal, repo) writeParams, err := r.createRPCWriteParams(ctx, principal, repo)
if err != nil { if err != nil {
return "", err return err
} }
syncOut, err := r.git.SyncRepository(ctx, &git.SyncRepositoryParams{ _, err = r.git.SyncRepository(ctx, &git.SyncRepositoryParams{
WriteParams: writeParams, WriteParams: writeParams,
Source: sourceCloneURL, Source: sourceCloneURL,
CreateIfNotExists: false, CreateIfNotExists: false,
RefSpecs: []string{"refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"}, RefSpecs: []string{"refs/heads/*:refs/heads/*", "refs/tags/*:refs/tags/*"},
DefaultBranch: repo.DefaultBranch,
}) })
if err != nil { if err != nil {
return "", fmt.Errorf("failed to sync repository: %w", err) return fmt.Errorf("failed to sync repository: %w", err)
} }
return syncOut.DefaultBranch, nil return nil
} }
func (r *Repository) deleteGitRepository(ctx context.Context, func (r *Repository) deleteGitRepository(ctx context.Context,

View File

@ -96,14 +96,22 @@ func (g *Git) HasBranches(
func (g *Git) IsBranchExist(ctx context.Context, repoPath, name string) (bool, error) { func (g *Git) IsBranchExist(ctx context.Context, repoPath, name string) (bool, error) {
cmd := command.New("show-ref", cmd := command.New("show-ref",
command.WithFlag("--verify", BranchPrefix+name), command.WithFlag("--exists", BranchPrefix+name),
) )
err := cmd.Run(ctx, err := cmd.Run(ctx,
command.WithDir(repoPath), command.WithDir(repoPath),
) )
if err != nil { if cmdERR := command.AsError(err); cmdERR != nil && cmdERR.IsExitCode(2) {
return false, fmt.Errorf("failed to check if branch '%s' exist: %w", name, err) // git returns exit code 2 in case the ref doesn't exist.
// On success it would be 0 and no error would be returned in the first place.
// Any other exit code we fall through to default error handling.
// https://git-scm.com/docs/git-show-ref#Documentation/git-show-ref.txt---exists
return false, nil
} }
if err != nil {
return false, processGitErrorf(err, "failed to check if branch %q exist", name)
}
return true, nil return true, nil
} }

View File

@ -24,6 +24,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git/command" "github.com/harness/gitness/git/command"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -98,27 +99,28 @@ func (g *Git) SetDefaultBranch(
ctx context.Context, ctx context.Context,
repoPath string, repoPath string,
defaultBranch string, defaultBranch string,
allowEmpty bool, ignoreBranchExistance bool,
) error { ) error {
if repoPath == "" { if repoPath == "" {
return ErrRepositoryPathEmpty return ErrRepositoryPathEmpty
} }
// if requested, error out if branch doesn't exist. Otherwise, blindly set it. if !ignoreBranchExistance {
// best effort try to check for existence - technically someone else could delete it in the meanwhile.
exist, err := g.IsBranchExist(ctx, repoPath, defaultBranch) exist, err := g.IsBranchExist(ctx, repoPath, defaultBranch)
if err != nil { if err != nil {
log.Ctx(ctx).Err(err).Msgf("failed to set default branch") return fmt.Errorf("failed to check if branch exists: %w", err)
}
if !exist {
return errors.NotFound("branch %q does not exist", defaultBranch)
} }
if !allowEmpty && !exist {
// TODO: ensure this returns not found error to caller
return fmt.Errorf("branch '%s' does not exist", defaultBranch)
} }
// change default branch // change default branch
cmd := command.New("symbolic-ref", cmd := command.New("symbolic-ref",
command.WithArg("HEAD", gitReferenceNamePrefixBranch+defaultBranch), command.WithArg("HEAD", gitReferenceNamePrefixBranch+defaultBranch),
) )
err = cmd.Run(ctx, command.WithDir(repoPath)) err := cmd.Run(ctx, command.WithDir(repoPath))
if err != nil { if err != nil {
return processGitErrorf(err, "failed to set new default branch") return processGitErrorf(err, "failed to set new default branch")
} }
@ -130,6 +132,26 @@ func (g *Git) SetDefaultBranch(
func (g *Git) GetDefaultBranch( func (g *Git) GetDefaultBranch(
ctx context.Context, ctx context.Context,
repoPath string, repoPath string,
) (string, error) {
rawBranchRef, err := g.GetSymbolicRefHeadRaw(ctx, repoPath)
if err != nil {
return "", fmt.Errorf("failed to get raw symbolic ref HEAD: %w", err)
}
branchName := strings.TrimPrefix(
strings.TrimSpace(
rawBranchRef,
),
BranchPrefix,
)
return branchName, nil
}
// GetSymbolicRefHeadRaw returns the raw output of the symolic-ref command for HEAD.
func (g *Git) GetSymbolicRefHeadRaw(
ctx context.Context,
repoPath string,
) (string, error) { ) (string, error) {
if repoPath == "" { if repoPath == "" {
return "", ErrRepositoryPathEmpty return "", ErrRepositoryPathEmpty
@ -144,7 +166,7 @@ func (g *Git) GetDefaultBranch(
command.WithDir(repoPath), command.WithDir(repoPath),
command.WithStdout(output)) command.WithStdout(output))
if err != nil { if err != nil {
return "", processGitErrorf(err, "failed to get default branch") return "", processGitErrorf(err, "failed to get value of symbolic ref HEAD from git")
} }
return output.String(), nil return output.String(), nil

View File

@ -109,6 +109,10 @@ type SyncRepositoryParams struct {
// RefSpecs [OPTIONAL] allows to override the refspecs that are being synced from the remote repository. // RefSpecs [OPTIONAL] allows to override the refspecs that are being synced from the remote repository.
// By default all references present on the remote repository will be fetched (including scm internal ones). // By default all references present on the remote repository will be fetched (including scm internal ones).
RefSpecs []string RefSpecs []string
// DefaultBranch [OPTIONAL] allows to override the default branch of the repository.
// If empty, the default branch will be set to match the remote repository's default branch.
// WARNING: If the remote repo is empty and no value is provided, an api.ErrNoDefaultBranch error is returned.
DefaultBranch string
} }
type SyncRepositoryOutput struct { type SyncRepositoryOutput struct {
@ -130,7 +134,15 @@ type HashRepositoryOutput struct {
} }
type UpdateDefaultBranchParams struct { type UpdateDefaultBranchParams struct {
WriteParams WriteParams
// BranchName is the name of the branch // BranchName is the name of the branch (not the full reference).
BranchName string
}
type GetDefaultBranchParams struct {
ReadParams
}
type GetDefaultBranchOutput struct {
// BranchName is the name of the branch (not the full reference).
BranchName string BranchName string
} }
@ -259,7 +271,7 @@ func (s *Service) SyncRepository(
} }
// the default branch doesn't matter for a sync, // the default branch doesn't matter for a sync,
// we create an empty repo and the head will by updated as part of the Sync. // we create an empty repo and the head will by updated later.
const syncDefaultBranch = "main" const syncDefaultBranch = "main"
if err = s.createRepositoryInternal( if err = s.createRepositoryInternal(
ctx, ctx,
@ -278,24 +290,22 @@ func (s *Service) SyncRepository(
// sync repo content // sync repo content
err = s.git.Sync(ctx, repoPath, params.Source, params.RefSpecs) err = s.git.Sync(ctx, repoPath, params.Source, params.RefSpecs)
if err != nil { if err != nil {
return nil, fmt.Errorf("SyncRepository: failed to sync git repo: %w", err) return nil, fmt.Errorf("failed to sync from source repo: %w", err)
} }
// get remote default branch defaultBranch := params.DefaultBranch
defaultBranch, err := s.git.GetRemoteDefaultBranch(ctx, params.Source) if defaultBranch == "" {
if errors.Is(err, api.ErrNoDefaultBranch) { // get default branch from remote repo (returns api.ErrNoDefaultBranch if repo is empty!)
return &SyncRepositoryOutput{ defaultBranch, err = s.git.GetRemoteDefaultBranch(ctx, params.Source)
DefaultBranch: "",
}, nil
}
if err != nil { if err != nil {
return nil, fmt.Errorf("SyncRepository: failed to get default branch from repo: %w", err) return nil, fmt.Errorf("failed to get default branch from source repo: %w", err)
}
} }
// set default branch // set default branch
err = s.git.SetDefaultBranch(ctx, repoPath, defaultBranch, true) err = s.git.SetDefaultBranch(ctx, repoPath, defaultBranch, true)
if err != nil { if err != nil {
return nil, fmt.Errorf("SyncRepository: failed to set default branch of repo: %w", err) return nil, fmt.Errorf("failed to set default branch of repo: %w", err)
} }
return &SyncRepositoryOutput{ return &SyncRepositoryOutput{
@ -329,7 +339,8 @@ func (s *Service) HashRepository(ctx context.Context, params *HashRepositoryPara
}() }()
// add default branch to hash // add default branch to hash
defaultBranch, err := s.git.GetDefaultBranch(goCtx, repoPath) // IMPORTANT: Has to stay as is to ensure hash consistency! (e.g. "refs/heads/main/n")
defaultBranchRef, err := s.git.GetSymbolicRefHeadRaw(goCtx, repoPath)
if err != nil { if err != nil {
hashChan <- hash.SourceNext{ hashChan <- hash.SourceNext{
Err: fmt.Errorf("HashRepository: failed to get default branch: %w", err), Err: fmt.Errorf("HashRepository: failed to get default branch: %w", err),
@ -338,7 +349,7 @@ func (s *Service) HashRepository(ctx context.Context, params *HashRepositoryPara
} }
hashChan <- hash.SourceNext{ hashChan <- hash.SourceNext{
Data: hash.SerializeHead(defaultBranch), Data: hash.SerializeHead(defaultBranchRef),
} }
err = s.git.WalkReferences(goCtx, repoPath, func(wre api.WalkReferencesEntry) error { err = s.git.WalkReferences(goCtx, repoPath, func(wre api.WalkReferencesEntry) error {
@ -515,6 +526,26 @@ func (s *Service) GetRepositorySize(
}, nil }, nil
} }
// GetDefaultBranch returns the default branch of the repo.
func (s *Service) GetDefaultBranch(
ctx context.Context,
params *GetDefaultBranchParams,
) (*GetDefaultBranchOutput, error) {
if err := params.Validate(); err != nil {
return nil, err
}
repoPath := getFullPathForRepo(s.reposRoot, params.RepoUID)
dfltBranch, err := s.git.GetDefaultBranch(ctx, repoPath)
if err != nil {
return nil, fmt.Errorf("failed to get repo default branch: %w", err)
}
return &GetDefaultBranchOutput{
BranchName: dfltBranch,
}, nil
}
// UpdateDefaultBranch updates the default branch of the repo. // UpdateDefaultBranch updates the default branch of the repo.
func (s *Service) UpdateDefaultBranch( func (s *Service) UpdateDefaultBranch(
ctx context.Context, ctx context.Context,
@ -531,7 +562,7 @@ func (s *Service) UpdateDefaultBranch(
err := s.git.SetDefaultBranch(ctx, repoPath, params.BranchName, false) err := s.git.SetDefaultBranch(ctx, repoPath, params.BranchName, false)
if err != nil { if err != nil {
return fmt.Errorf("UpdateDefaultBranch: failed to update repo default branch %q: %w", return fmt.Errorf("failed to update repo default branch %q: %w",
params.BranchName, err) params.BranchName, err)
} }
return nil return nil

View File

@ -17,8 +17,8 @@ package git
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/harness/gitness/git/api"
"github.com/harness/gitness/git/merge" "github.com/harness/gitness/git/merge"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -44,7 +44,7 @@ func (s *Service) Summary(
if err != nil { if err != nil {
return SummaryOutput{}, err return SummaryOutput{}, err
} }
defaultBranch = strings.TrimSpace(defaultBranch) defaultBranchRef := api.GetReferenceFromBranchName(defaultBranch)
g, ctx := errgroup.WithContext(ctx) g, ctx := errgroup.WithContext(ctx)
@ -52,7 +52,7 @@ func (s *Service) Summary(
g.Go(func() error { g.Go(func() error {
var err error var err error
commitCount, err = merge.CommitCount(ctx, repoPath, "", defaultBranch) commitCount, err = merge.CommitCount(ctx, repoPath, "", defaultBranchRef)
return err return err
}) })