internal/git/objectpool/pool.go (130 lines of code) (raw):

package objectpool import ( "context" "errors" "fmt" "os" "path/filepath" "strings" "gitlab.com/gitlab-org/gitaly/v16/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/v16/internal/git/gitcmd" housekeepingmgr "gitlab.com/gitlab-org/gitaly/v16/internal/git/housekeeping/manager" "gitlab.com/gitlab-org/gitaly/v16/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v16/internal/git/stats" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/storage" "gitlab.com/gitlab-org/gitaly/v16/internal/gitaly/transaction" "gitlab.com/gitlab-org/gitaly/v16/internal/log" "gitlab.com/gitlab-org/gitaly/v16/proto/go/gitalypb" ) var ( // ErrInvalidPoolDir is returned when the object pool relative path is malformed. ErrInvalidPoolDir = errors.New("invalid object pool directory") // ErrInvalidPoolRepository indicates the directory the alternates file points to is not a valid git repository ErrInvalidPoolRepository = errors.New("object pool is not a valid git repository") // ErrAlternateObjectDirNotExist indicates a repository does not have an alternates file ErrAlternateObjectDirNotExist = errors.New("no alternates directory exists") ) // ObjectPool are a way to de-dupe objects between repositories, where the objects // live in a pool in a distinct repository which is used as an alternate object // store for other repositories. type ObjectPool struct { *localrepo.Repo logger log.Logger locator storage.Locator gitCmdFactory gitcmd.CommandFactory txManager transaction.Manager housekeepingManager housekeepingmgr.Manager } // FromProto returns an object pool object from its Protobuf representation. This function verifies // that the object pool exists and is a valid pool repository. func FromProto( ctx context.Context, logger log.Logger, locator storage.Locator, gitCmdFactory gitcmd.CommandFactory, catfileCache catfile.Cache, txManager transaction.Manager, housekeepingManager housekeepingmgr.Manager, proto *gitalypb.ObjectPool, ) (*ObjectPool, error) { poolPath, err := locator.GetRepoPath(ctx, proto.GetRepository(), storage.WithRepositoryVerificationSkipped()) if err != nil { return nil, err } if !storage.IsPoolRepository(proto.GetRepository()) { // When creating repositories in the ObjectPool service we will first create the // repository in a temporary directory. So we need to check whether the path we see // here is in such a temporary directory and let it pass. tempDir, err := locator.TempDir(proto.GetRepository().GetStorageName()) if err != nil { return nil, fmt.Errorf("getting temporary storage directory: %w", err) } if !strings.HasPrefix(poolPath, tempDir) { return nil, ErrInvalidPoolDir } } pool := &ObjectPool{ Repo: localrepo.New(logger, locator, gitCmdFactory, catfileCache, proto.GetRepository()), logger: logger, locator: locator, gitCmdFactory: gitCmdFactory, txManager: txManager, housekeepingManager: housekeepingManager, } if !pool.IsValid(ctx) { return nil, ErrInvalidPoolRepository } return pool, nil } // ToProto returns a new struct that is the protobuf definition of the ObjectPool func (o *ObjectPool) ToProto() *gitalypb.ObjectPool { return &gitalypb.ObjectPool{ Repository: &gitalypb.Repository{ StorageName: o.GetStorageName(), RelativePath: o.GetRelativePath(), }, } } // Exists will return true if the pool path exists and is a directory func (o *ObjectPool) Exists(ctx context.Context) bool { path, err := o.Path(ctx) if err != nil { return false } fi, err := os.Stat(path) if os.IsNotExist(err) || err != nil { return false } return fi.IsDir() } // IsValid checks if a repository exists, and if its valid. func (o *ObjectPool) IsValid(ctx context.Context) bool { return o.locator.ValidateRepository(ctx, o.Repo) == nil } // FromRepo returns an instance of ObjectPool that the repository points to func FromRepo( ctx context.Context, logger log.Logger, locator storage.Locator, gitCmdFactory gitcmd.CommandFactory, catfileCache catfile.Cache, txManager transaction.Manager, housekeepingManager housekeepingmgr.Manager, repo *localrepo.Repo, ) (*ObjectPool, error) { storagePath, err := locator.GetStorageByName(ctx, repo.GetStorageName()) if err != nil { return nil, err } repoPath, err := repo.Path(ctx) if err != nil { return nil, err } altInfo, err := stats.AlternatesInfoForRepository(repoPath) if err != nil { return nil, fmt.Errorf("getting alternates info: %w", err) } if !altInfo.Exists || len(altInfo.ObjectDirectories) == 0 { return nil, ErrAlternateObjectDirNotExist } absolutePoolObjectDirPath := altInfo.AbsoluteObjectDirectories()[0] relativePoolObjectDirPath, err := filepath.Rel(storagePath, absolutePoolObjectDirPath) if err != nil { return nil, err } objectPoolProto := &gitalypb.ObjectPool{ Repository: &gitalypb.Repository{ StorageName: repo.GetStorageName(), RelativePath: filepath.Dir(relativePoolObjectDirPath), }, } if locator.ValidateRepository(ctx, objectPoolProto.GetRepository()) != nil { return nil, ErrInvalidPoolRepository } return FromProto(ctx, logger, locator, gitCmdFactory, catfileCache, txManager, housekeepingManager, objectPoolProto) }