github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/graveler/ref/manager.go (about)

     1  package ref
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/go-multierror"
    11  	"github.com/treeverse/lakefs/pkg/batch"
    12  	"github.com/treeverse/lakefs/pkg/cache"
    13  	"github.com/treeverse/lakefs/pkg/graveler"
    14  	"github.com/treeverse/lakefs/pkg/ident"
    15  	"github.com/treeverse/lakefs/pkg/kv"
    16  	"github.com/treeverse/lakefs/pkg/logging"
    17  )
    18  
    19  const (
    20  	// commitIDStringLength string representation length of commit ID - based on hex representation of sha256
    21  	commitIDStringLength = 64
    22  	// ImportExpiryTime Expiry time to remove imports from ref-store
    23  	ImportExpiryTime = 24 * time.Hour
    24  )
    25  
    26  type CacheConfig struct {
    27  	Size   int
    28  	Expiry time.Duration
    29  	Jitter time.Duration
    30  }
    31  
    32  type Manager struct {
    33  	kvStore         kv.Store
    34  	kvStoreLimited  kv.Store
    35  	addressProvider ident.AddressProvider
    36  	batchExecutor   batch.Batcher
    37  	repoCache       cache.Cache
    38  	commitCache     cache.Cache
    39  	maxBatchDelay   time.Duration
    40  }
    41  
    42  func branchFromProto(pb *graveler.BranchData) *graveler.Branch {
    43  	var sealedTokens []graveler.StagingToken
    44  	for _, st := range pb.SealedTokens {
    45  		sealedTokens = append(sealedTokens, graveler.StagingToken(st))
    46  	}
    47  	branch := &graveler.Branch{
    48  		CommitID:     graveler.CommitID(pb.CommitId),
    49  		StagingToken: graveler.StagingToken(pb.StagingToken),
    50  		SealedTokens: sealedTokens,
    51  	}
    52  	return branch
    53  }
    54  
    55  func protoFromBranch(branchID graveler.BranchID, b *graveler.Branch) *graveler.BranchData {
    56  	var sealedTokens []string
    57  	for _, st := range b.SealedTokens {
    58  		sealedTokens = append(sealedTokens, st.String())
    59  	}
    60  	branch := &graveler.BranchData{
    61  		Id:           branchID.String(),
    62  		CommitId:     b.CommitID.String(),
    63  		StagingToken: b.StagingToken.String(),
    64  		SealedTokens: sealedTokens,
    65  	}
    66  	return branch
    67  }
    68  
    69  type ManagerConfig struct {
    70  	Executor              batch.Batcher
    71  	KVStore               kv.Store
    72  	KVStoreLimited        kv.Store
    73  	AddressProvider       ident.AddressProvider
    74  	RepositoryCacheConfig CacheConfig
    75  	CommitCacheConfig     CacheConfig
    76  	MaxBatchDelay         time.Duration
    77  }
    78  
    79  func NewRefManager(cfg ManagerConfig) *Manager {
    80  	return &Manager{
    81  		kvStore:         cfg.KVStore,
    82  		kvStoreLimited:  cfg.KVStoreLimited,
    83  		addressProvider: cfg.AddressProvider,
    84  		batchExecutor:   cfg.Executor,
    85  		repoCache:       newCache(cfg.RepositoryCacheConfig),
    86  		commitCache:     newCache(cfg.CommitCacheConfig),
    87  		maxBatchDelay:   cfg.MaxBatchDelay,
    88  	}
    89  }
    90  
    91  func (m *Manager) getRepository(ctx context.Context, repositoryID graveler.RepositoryID) (*graveler.RepositoryRecord, error) {
    92  	data := graveler.RepositoryData{}
    93  	_, err := kv.GetMsg(ctx, m.kvStore, graveler.RepositoriesPartition(), []byte(graveler.RepoPath(repositoryID)), &data)
    94  	if err != nil {
    95  		if errors.Is(err, kv.ErrNotFound) {
    96  			err = graveler.ErrRepositoryNotFound
    97  		}
    98  		return nil, err
    99  	}
   100  	return graveler.RepoFromProto(&data), nil
   101  }
   102  
   103  func (m *Manager) getRepositoryBatch(ctx context.Context, repositoryID graveler.RepositoryID) (*graveler.RepositoryRecord, error) {
   104  	key := fmt.Sprintf("GetRepository:%s", repositoryID)
   105  	repository, err := m.batchExecutor.BatchFor(ctx, key, m.maxBatchDelay, batch.ExecuterFunc(func() (interface{}, error) {
   106  		return m.getRepository(context.Background(), repositoryID)
   107  	}))
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	return repository.(*graveler.RepositoryRecord), nil
   112  }
   113  
   114  func (m *Manager) GetRepository(ctx context.Context, repositoryID graveler.RepositoryID) (*graveler.RepositoryRecord, error) {
   115  	rec, err := m.repoCache.GetOrSet(repositoryID, func() (interface{}, error) {
   116  		repo, err := m.getRepositoryBatch(ctx, repositoryID)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  
   121  		switch repo.State {
   122  		case graveler.RepositoryState_ACTIVE:
   123  			return repo, nil
   124  		case graveler.RepositoryState_IN_DELETION:
   125  			return nil, graveler.ErrRepositoryInDeletion
   126  		default:
   127  			return nil, fmt.Errorf("invalid repository state (%d) rec: %w", repo.State, graveler.ErrInvalid)
   128  		}
   129  	})
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	return rec.(*graveler.RepositoryRecord), nil
   134  }
   135  
   136  func (m *Manager) createBareRepository(ctx context.Context, repositoryID graveler.RepositoryID, repository graveler.Repository) (*graveler.RepositoryRecord, error) {
   137  	repoRecord := &graveler.RepositoryRecord{
   138  		RepositoryID: repositoryID,
   139  		Repository:   &repository,
   140  	}
   141  	repo := graveler.ProtoFromRepo(repoRecord)
   142  
   143  	err := kv.SetMsgIf(ctx, m.kvStore, graveler.RepositoriesPartition(), []byte(graveler.RepoPath(repositoryID)), repo, nil)
   144  	if err != nil {
   145  		if errors.Is(err, kv.ErrPredicateFailed) {
   146  			err = graveler.ErrNotUnique
   147  		}
   148  		return nil, err
   149  	}
   150  
   151  	return repoRecord, nil
   152  }
   153  
   154  func (m *Manager) CreateRepository(ctx context.Context, repositoryID graveler.RepositoryID, repository graveler.Repository) (*graveler.RepositoryRecord, error) {
   155  	firstCommit := graveler.NewCommit()
   156  	firstCommit.Message = graveler.FirstCommitMsg
   157  	firstCommit.Generation = 1
   158  
   159  	repo := &graveler.RepositoryRecord{
   160  		RepositoryID: repositoryID,
   161  		Repository:   &repository,
   162  	}
   163  	// If branch creation fails - this commit will become dangling. This is a known issue that can be resolved via garbage collection
   164  	commitID, err := m.addCommit(ctx, graveler.RepoPartition(repo), firstCommit)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	branch := graveler.Branch{
   170  		CommitID:     commitID,
   171  		StagingToken: graveler.GenerateStagingToken(repositoryID, repository.DefaultBranchID),
   172  		SealedTokens: nil,
   173  	}
   174  	err = m.createBranch(ctx, graveler.RepoPartition(repo), repository.DefaultBranchID, branch)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	_, err = m.createBareRepository(ctx, repositoryID, repository)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	return repo, nil
   183  }
   184  
   185  func (m *Manager) CreateBareRepository(ctx context.Context, repositoryID graveler.RepositoryID, repository graveler.Repository) (*graveler.RepositoryRecord, error) {
   186  	return m.createBareRepository(ctx, repositoryID, repository)
   187  }
   188  
   189  func (m *Manager) ListRepositories(ctx context.Context) (graveler.RepositoryIterator, error) {
   190  	return NewRepositoryIterator(ctx, m.kvStore)
   191  }
   192  
   193  func (m *Manager) updateRepoState(ctx context.Context, repo *graveler.RepositoryRecord, state graveler.RepositoryState) error {
   194  	repo.State = state
   195  	return kv.SetMsg(ctx, m.kvStore, graveler.RepositoriesPartition(), []byte(graveler.RepoPath(repo.RepositoryID)), graveler.ProtoFromRepo(repo))
   196  }
   197  
   198  func (m *Manager) deleteRepositoryBranches(ctx context.Context, repository *graveler.RepositoryRecord) error {
   199  	itr, err := m.ListBranches(ctx, repository)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	defer itr.Close()
   204  	var wg multierror.Group
   205  	for itr.Next() {
   206  		b := itr.Value()
   207  		wg.Go(func() error {
   208  			return m.DeleteBranch(ctx, repository, b.BranchID)
   209  		})
   210  	}
   211  	return wg.Wait().ErrorOrNil()
   212  }
   213  
   214  func (m *Manager) deleteRepositoryTags(ctx context.Context, repository *graveler.RepositoryRecord) error {
   215  	itr, err := m.ListTags(ctx, repository)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	defer itr.Close()
   220  	var wg multierror.Group
   221  	for itr.Next() {
   222  		tag := itr.Value()
   223  		wg.Go(func() error {
   224  			return m.DeleteTag(ctx, repository, tag.TagID)
   225  		})
   226  	}
   227  	return wg.Wait().ErrorOrNil()
   228  }
   229  
   230  func (m *Manager) deleteRepositoryCommits(ctx context.Context, repository *graveler.RepositoryRecord) error {
   231  	itr, err := m.ListCommits(ctx, repository)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	defer itr.Close()
   236  	var wg multierror.Group
   237  	for itr.Next() {
   238  		commit := itr.Value()
   239  		wg.Go(func() error {
   240  			return m.RemoveCommit(ctx, repository, commit.CommitID)
   241  		})
   242  	}
   243  	return wg.Wait().ErrorOrNil()
   244  }
   245  
   246  func (m *Manager) deleteRepositoryMetadata(ctx context.Context, repository *graveler.RepositoryRecord) error {
   247  	return m.kvStore.Delete(ctx, []byte(graveler.RepoPartition(repository)), []byte(graveler.RepoMetadataPath()))
   248  }
   249  
   250  func (m *Manager) deleteRepository(ctx context.Context, repo *graveler.RepositoryRecord) error {
   251  	// ctx := context.Background() TODO (niro): When running this async create a new context and remove ctx from signature
   252  	var wg multierror.Group
   253  	wg.Go(func() error {
   254  		return m.deleteRepositoryBranches(ctx, repo)
   255  	})
   256  	wg.Go(func() error {
   257  		return m.deleteRepositoryTags(ctx, repo)
   258  	})
   259  	wg.Go(func() error {
   260  		return m.deleteRepositoryCommits(ctx, repo)
   261  	})
   262  	wg.Go(func() error {
   263  		return m.deleteRepositoryMetadata(ctx, repo)
   264  	})
   265  
   266  	if err := wg.Wait().ErrorOrNil(); err != nil {
   267  		return err
   268  	}
   269  
   270  	// Finally delete the repository record itself
   271  	return m.kvStore.Delete(ctx, []byte(graveler.RepositoriesPartition()), []byte(graveler.RepoPath(repo.RepositoryID)))
   272  }
   273  
   274  func (m *Manager) DeleteRepository(ctx context.Context, repositoryID graveler.RepositoryID, opts ...graveler.SetOptionsFunc) error {
   275  	repo, err := m.getRepository(ctx, repositoryID)
   276  	if err != nil {
   277  		return err
   278  	}
   279  
   280  	options := &graveler.SetOptions{}
   281  	for _, opt := range opts {
   282  		opt(options)
   283  	}
   284  	if repo.ReadOnly && !options.Force {
   285  		return graveler.ErrReadOnlyRepository
   286  	}
   287  
   288  	// Set repository state to deleted and then perform background delete.
   289  	if repo.State != graveler.RepositoryState_IN_DELETION {
   290  		err = m.updateRepoState(ctx, repo, graveler.RepositoryState_IN_DELETION)
   291  		if err != nil {
   292  			return err
   293  		}
   294  	}
   295  
   296  	// TODO(niro): This should be a background delete process
   297  	return m.deleteRepository(ctx, repo)
   298  }
   299  
   300  func (m *Manager) getRepositoryMetadata(ctx context.Context, repo *graveler.RepositoryRecord) (graveler.RepositoryMetadata, kv.Predicate, error) {
   301  	data := graveler.RepoMetadata{}
   302  	pred, err := kv.GetMsg(ctx, m.kvStore, graveler.RepoPartition(repo), []byte(graveler.RepoMetadataPath()), &data)
   303  	if err != nil {
   304  		return nil, nil, err
   305  	}
   306  	return graveler.RepoMetadataFromProto(&data), pred, nil
   307  }
   308  
   309  func (m *Manager) GetRepositoryMetadata(ctx context.Context, repositoryID graveler.RepositoryID) (graveler.RepositoryMetadata, error) {
   310  	repo, err := m.getRepository(ctx, repositoryID)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	metadata, _, err := m.getRepositoryMetadata(ctx, repo)
   316  	if errors.Is(err, kv.ErrNotFound) { // Return nil map if not exists
   317  		return nil, nil
   318  	}
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  
   323  	return metadata, nil
   324  }
   325  
   326  func (m *Manager) SetRepositoryMetadata(ctx context.Context, repo *graveler.RepositoryRecord, updateFunc graveler.RepoMetadataUpdateFunc) error {
   327  	metadata, pred, err := m.getRepositoryMetadata(ctx, repo)
   328  	if errors.Is(err, kv.ErrNotFound) { // Create new metadata map and set predicate to nil for setIf not exists
   329  		metadata = graveler.RepositoryMetadata{}
   330  		pred = nil
   331  	} else if err != nil {
   332  		return err
   333  	}
   334  
   335  	newMetadata, err := updateFunc(metadata)
   336  	// return on error or nothing to update
   337  	if err != nil || newMetadata == nil {
   338  		return err
   339  	}
   340  	return kv.SetMsgIf(ctx, m.kvStore, graveler.RepoPartition(repo), []byte(graveler.RepoMetadataPath()), graveler.ProtoFromRepositoryMetadata(newMetadata), pred)
   341  }
   342  
   343  func (m *Manager) ParseRef(ref graveler.Ref) (graveler.RawRef, error) {
   344  	return ParseRef(ref)
   345  }
   346  
   347  func (m *Manager) ResolveRawRef(ctx context.Context, repository *graveler.RepositoryRecord, raw graveler.RawRef) (*graveler.ResolvedRef, error) {
   348  	return ResolveRawRef(ctx, m, m.addressProvider, repository, raw)
   349  }
   350  
   351  func (m *Manager) getBranchWithPredicate(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID) (*graveler.Branch, kv.Predicate, error) {
   352  	key := fmt.Sprintf("GetBranch:%s:%s", repository.RepositoryID, branchID)
   353  	type branchPred struct {
   354  		*graveler.Branch
   355  		kv.Predicate
   356  	}
   357  	result, err := m.batchExecutor.BatchFor(ctx, key, m.maxBatchDelay, batch.ExecuterFunc(func() (interface{}, error) {
   358  		key := graveler.BranchPath(branchID)
   359  		data := graveler.BranchData{}
   360  		pred, err := kv.GetMsg(context.Background(), m.kvStore, graveler.RepoPartition(repository), []byte(key), &data)
   361  		if err != nil {
   362  			return nil, err
   363  		}
   364  		return &branchPred{Branch: branchFromProto(&data), Predicate: pred}, nil
   365  	}))
   366  	if errors.Is(err, kv.ErrNotFound) {
   367  		err = graveler.ErrBranchNotFound
   368  	}
   369  	if err != nil {
   370  		return nil, nil, err
   371  	}
   372  	branchWithPred := result.(*branchPred)
   373  	return branchWithPred.Branch, branchWithPred.Predicate, nil
   374  }
   375  
   376  func (m *Manager) GetBranch(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID) (*graveler.Branch, error) {
   377  	branch, _, err := m.getBranchWithPredicate(ctx, repository, branchID)
   378  	return branch, err
   379  }
   380  
   381  func (m *Manager) createBranch(ctx context.Context, repositoryPartition string, branchID graveler.BranchID, branch graveler.Branch) error {
   382  	err := kv.SetMsgIf(ctx, m.kvStore, repositoryPartition, []byte(graveler.BranchPath(branchID)), protoFromBranch(branchID, &branch), nil)
   383  	if errors.Is(err, kv.ErrPredicateFailed) {
   384  		err = graveler.ErrBranchExists
   385  	}
   386  	return err
   387  }
   388  
   389  func (m *Manager) CreateBranch(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID, branch graveler.Branch) error {
   390  	return m.createBranch(ctx, graveler.RepoPartition(repository), branchID, branch)
   391  }
   392  
   393  func (m *Manager) SetBranch(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID, branch graveler.Branch) error {
   394  	return kv.SetMsg(ctx, m.kvStore, graveler.RepoPartition(repository), []byte(graveler.BranchPath(branchID)), protoFromBranch(branchID, &branch))
   395  }
   396  
   397  func (m *Manager) BranchUpdate(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID, f graveler.BranchUpdateFunc) error {
   398  	b, pred, err := m.getBranchWithPredicate(ctx, repository, branchID)
   399  	if err != nil {
   400  		return err
   401  	}
   402  	newBranch, err := f(b)
   403  	// return on error or nothing to update
   404  	if err != nil || newBranch == nil {
   405  		return err
   406  	}
   407  	return kv.SetMsgIf(ctx, m.kvStore, graveler.RepoPartition(repository), []byte(graveler.BranchPath(branchID)), protoFromBranch(branchID, newBranch), pred)
   408  }
   409  
   410  func (m *Manager) DeleteBranch(ctx context.Context, repository *graveler.RepositoryRecord, branchID graveler.BranchID) error {
   411  	_, err := m.GetBranch(ctx, repository, branchID)
   412  	if err != nil {
   413  		return err
   414  	}
   415  	return m.kvStore.Delete(ctx, []byte(graveler.RepoPartition(repository)), []byte(graveler.BranchPath(branchID)))
   416  }
   417  
   418  func (m *Manager) ListBranches(ctx context.Context, repository *graveler.RepositoryRecord) (graveler.BranchIterator, error) {
   419  	return NewBranchSimpleIterator(ctx, m.kvStore, repository)
   420  }
   421  
   422  func (m *Manager) GCBranchIterator(ctx context.Context, repository *graveler.RepositoryRecord) (graveler.BranchIterator, error) {
   423  	return NewBranchByCommitIterator(ctx, m.kvStore, repository)
   424  }
   425  
   426  func (m *Manager) GetTag(ctx context.Context, repository *graveler.RepositoryRecord, tagID graveler.TagID) (*graveler.CommitID, error) {
   427  	key := fmt.Sprintf("GetTag:%s:%s", repository.RepositoryID, tagID)
   428  	commitID, err := m.batchExecutor.BatchFor(ctx, key, m.maxBatchDelay, batch.ExecuterFunc(func() (interface{}, error) {
   429  		tagKey := graveler.TagPath(tagID)
   430  		t := graveler.TagData{}
   431  		_, err := kv.GetMsg(context.Background(), m.kvStore, graveler.RepoPartition(repository), []byte(tagKey), &t)
   432  		if err != nil {
   433  			return nil, err
   434  		}
   435  		commitID := graveler.CommitID(t.CommitId)
   436  		return &commitID, nil
   437  	}))
   438  	if errors.Is(err, kv.ErrNotFound) {
   439  		err = graveler.ErrTagNotFound
   440  	}
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  	return commitID.(*graveler.CommitID), nil
   445  }
   446  
   447  func (m *Manager) CreateTag(ctx context.Context, repository *graveler.RepositoryRecord, tagID graveler.TagID, commitID graveler.CommitID) error {
   448  	t := &graveler.TagData{
   449  		Id:       tagID.String(),
   450  		CommitId: commitID.String(),
   451  	}
   452  	tagKey := graveler.TagPath(tagID)
   453  	err := kv.SetMsgIf(ctx, m.kvStore, graveler.RepoPartition(repository), []byte(tagKey), t, nil)
   454  	if err != nil {
   455  		if errors.Is(err, kv.ErrPredicateFailed) {
   456  			err = graveler.ErrTagAlreadyExists
   457  		}
   458  		return err
   459  	}
   460  	return nil
   461  }
   462  
   463  func (m *Manager) DeleteTag(ctx context.Context, repository *graveler.RepositoryRecord, tagID graveler.TagID) error {
   464  	tagKey := graveler.TagPath(tagID)
   465  	// TODO (issue 3640) align with delete tag DB - return ErrNotFound when tag does not exist
   466  	return m.kvStore.Delete(ctx, []byte(graveler.RepoPartition(repository)), []byte(tagKey))
   467  }
   468  
   469  func (m *Manager) ListTags(ctx context.Context, repository *graveler.RepositoryRecord) (graveler.TagIterator, error) {
   470  	return NewTagIterator(ctx, m.kvStore, repository)
   471  }
   472  
   473  func (m *Manager) GetCommitByPrefix(ctx context.Context, repository *graveler.RepositoryRecord, prefix graveler.CommitID) (*graveler.Commit, error) {
   474  	// optimize by get if prefix is not a prefix, but a full length commit id
   475  	if len(prefix) == commitIDStringLength {
   476  		return m.GetCommit(ctx, repository, prefix)
   477  	}
   478  	key := fmt.Sprintf("GetCommitByPrefix:%s:%s", repository.RepositoryID, prefix)
   479  	commit, err := m.batchExecutor.BatchFor(ctx, key, m.maxBatchDelay, batch.ExecuterFunc(func() (interface{}, error) {
   480  		it, err := NewOrderedCommitIterator(context.Background(), m.kvStore, repository, false)
   481  		if err != nil {
   482  			return nil, err
   483  		}
   484  		defer it.Close()
   485  		it.SeekGE(prefix)
   486  		var commit *graveler.Commit
   487  		for it.Next() {
   488  			c := it.Value()
   489  			if strings.HasPrefix(string(c.CommitID), string(prefix)) {
   490  				if commit != nil {
   491  					return nil, graveler.ErrCommitNotFound // more than 1 commit starts with the ID prefix
   492  				}
   493  				commit = c.Commit
   494  			} else {
   495  				break
   496  			}
   497  		}
   498  		if err := it.Err(); err != nil {
   499  			return nil, err
   500  		}
   501  		if commit == nil {
   502  			return nil, graveler.ErrCommitNotFound
   503  		}
   504  		return commit, nil
   505  	}))
   506  	if err != nil {
   507  		return nil, err
   508  	}
   509  	return commit.(*graveler.Commit), nil
   510  }
   511  
   512  func (m *Manager) GetCommit(ctx context.Context, repository *graveler.RepositoryRecord, commitID graveler.CommitID) (*graveler.Commit, error) {
   513  	key := fmt.Sprintf("%s:%s", repository.RepositoryID, commitID)
   514  	v, err := m.commitCache.GetOrSet(key, func() (v interface{}, err error) {
   515  		return m.getCommitBatch(ctx, repository, commitID)
   516  	})
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  	return v.(*graveler.Commit), nil
   521  }
   522  
   523  func (m *Manager) getCommitBatch(ctx context.Context, repository *graveler.RepositoryRecord, commitID graveler.CommitID) (*graveler.Commit, error) {
   524  	key := fmt.Sprintf("GetCommit:%s:%s", repository.RepositoryID, commitID)
   525  	commit, err := m.batchExecutor.BatchFor(ctx, key, m.maxBatchDelay, batch.ExecuterFunc(func() (interface{}, error) {
   526  		return m.getCommit(context.Background(), commitID, repository)
   527  	}))
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	return commit.(*graveler.Commit), nil
   532  }
   533  
   534  func (m *Manager) getCommit(ctx context.Context, commitID graveler.CommitID, repository *graveler.RepositoryRecord) (interface{}, error) {
   535  	commitKey := graveler.CommitPath(commitID)
   536  	c := graveler.CommitData{}
   537  	_, err := kv.GetMsg(ctx, m.kvStore, graveler.RepoPartition(repository), []byte(commitKey), &c)
   538  	if errors.Is(err, kv.ErrNotFound) {
   539  		err = graveler.ErrCommitNotFound
   540  	}
   541  	if err != nil {
   542  		return nil, err
   543  	}
   544  	return graveler.CommitFromProto(&c), nil
   545  }
   546  
   547  func (m *Manager) addCommit(ctx context.Context, repoPartition string, commit graveler.Commit) (graveler.CommitID, error) {
   548  	commitID := m.addressProvider.ContentAddress(commit)
   549  	c := graveler.ProtoFromCommit(graveler.CommitID(commitID), &commit)
   550  	commitKey := graveler.CommitPath(graveler.CommitID(commitID))
   551  	err := kv.SetMsgIf(ctx, m.kvStore, repoPartition, []byte(commitKey), c, nil)
   552  	// commits are written based on their content hash, if we insert the same ID again,
   553  	// it will necessarily have the same attributes as the existing one, so if a commit already exists doesn't return an error
   554  	if err != nil && !errors.Is(err, kv.ErrPredicateFailed) {
   555  		return "", err
   556  	}
   557  	return graveler.CommitID(commitID), nil
   558  }
   559  
   560  func (m *Manager) AddCommit(ctx context.Context, repository *graveler.RepositoryRecord, commit graveler.Commit) (graveler.CommitID, error) {
   561  	return m.addCommit(ctx, graveler.RepoPartition(repository), commit)
   562  }
   563  
   564  func (m *Manager) CreateCommitRecord(ctx context.Context, repository *graveler.RepositoryRecord, commitID graveler.CommitID, commit graveler.Commit) error {
   565  	if m.addressProvider.ContentAddress(commit) != commitID.String() {
   566  		return graveler.ErrInvalidCommitID
   567  	}
   568  	c := graveler.ProtoFromCommit(commitID, &commit)
   569  	commitKey := graveler.CommitPath(commitID)
   570  	err := kv.SetMsgIf(ctx, m.kvStore, graveler.RepoPartition(repository), []byte(commitKey), c, nil)
   571  	if errors.Is(err, kv.ErrPredicateFailed) {
   572  		return graveler.ErrCommitAlreadyExists
   573  	} else if err != nil {
   574  		return err
   575  	}
   576  	return nil
   577  }
   578  
   579  func (m *Manager) RemoveCommit(ctx context.Context, repository *graveler.RepositoryRecord, commitID graveler.CommitID) error {
   580  	commitKey := graveler.CommitPath(commitID)
   581  	return m.kvStore.Delete(ctx, []byte(graveler.RepoPartition(repository)), []byte(commitKey))
   582  }
   583  
   584  func (m *Manager) FindMergeBase(ctx context.Context, repository *graveler.RepositoryRecord, commitIDs ...graveler.CommitID) (*graveler.Commit, error) {
   585  	const allowedCommitsToCompare = 2
   586  	if len(commitIDs) != allowedCommitsToCompare {
   587  		return nil, graveler.ErrInvalidMergeBase
   588  	}
   589  	return FindMergeBase(ctx, m, repository, commitIDs[0], commitIDs[1])
   590  }
   591  
   592  func (m *Manager) Log(ctx context.Context, repository *graveler.RepositoryRecord, from graveler.CommitID, firstParent bool, since *time.Time) (graveler.CommitIterator, error) {
   593  	return NewCommitIterator(ctx, &CommitIteratorConfig{
   594  		repository:  repository,
   595  		start:       from,
   596  		firstParent: firstParent,
   597  		since:       since,
   598  		manager:     m,
   599  	}), nil
   600  }
   601  
   602  func (m *Manager) ListCommits(ctx context.Context, repository *graveler.RepositoryRecord) (graveler.CommitIterator, error) {
   603  	return NewOrderedCommitIterator(ctx, m.kvStore, repository, false)
   604  }
   605  
   606  func (m *Manager) GCCommitIterator(ctx context.Context, repository *graveler.RepositoryRecord) (graveler.CommitIterator, error) {
   607  	return NewOrderedCommitIterator(ctx, m.kvStore, repository, true)
   608  }
   609  
   610  func newCache(cfg CacheConfig) cache.Cache {
   611  	if cfg.Size == 0 {
   612  		return cache.NoCache
   613  	}
   614  	return cache.NewCache(cfg.Size, cfg.Expiry, cache.NewJitterFn(cfg.Jitter))
   615  }
   616  
   617  func (m *Manager) DeleteExpiredImports(ctx context.Context, repository *graveler.RepositoryRecord) error {
   618  	expiry := time.Now().Add(-ImportExpiryTime)
   619  	repoPartition := graveler.RepoPartition(repository)
   620  	key := []byte(graveler.ImportsPath(""))
   621  	options := kv.IteratorOptionsFrom([]byte(""))
   622  	itr, err := kv.NewPrimaryIterator(ctx, m.kvStoreLimited, (&graveler.ImportStatusData{}).ProtoReflect().Type(), repoPartition, key, options)
   623  	if err != nil {
   624  		return fmt.Errorf("failed to get imports iterator from store: %w", err)
   625  	}
   626  	defer itr.Close()
   627  
   628  	var errs multierror.Error
   629  	for itr.Next() {
   630  		entry := itr.Entry()
   631  		status, ok := entry.Value.(*graveler.ImportStatusData)
   632  		if !ok {
   633  			return fmt.Errorf("invalid protobuf type %s: %w", entry.Value.ProtoReflect().Type().Descriptor().FullName(), graveler.ErrReadingFromStore)
   634  		}
   635  		if status.UpdatedAt.AsTime().Before(expiry) {
   636  			if !status.Completed && status.Error == "" {
   637  				logging.FromContext(ctx).WithFields(logging.Fields{"import_id": status.Id}).Warning("removing stale import")
   638  			}
   639  			err = m.kvStoreLimited.Delete(ctx, []byte(repoPartition), entry.Key)
   640  			if err != nil {
   641  				errs.Errors = append(errs.Errors, fmt.Errorf("delete failed for import ID %s: %w", status.Id, err))
   642  			}
   643  		}
   644  	}
   645  	return errs.ErrorOrNil()
   646  }