[gitstore] Bring back old implementation under go/gitstore_deprecated
Bug: skia:9084
Change-Id: Iee71e00dbbda0d4adb69ba29e6af428627a2245b
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/236345
Reviewed-by: Kevin Lubick <kjlubick@google.com>
Commit-Queue: Eric Boren <borenet@google.com>
diff --git a/datahopper/go/datahopper/main.go b/datahopper/go/datahopper/main.go
index 515bee7..87f3755 100644
--- a/datahopper/go/datahopper/main.go
+++ b/datahopper/go/datahopper/main.go
@@ -22,7 +22,7 @@
"go.skia.org/infra/go/gcs/gcsclient"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/gitauth"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
diff --git a/go/gitstore/bt_gitstore/bt_gitstore.go b/go/gitstore/bt_gitstore/bt_gitstore.go
index 35acd3b..cec4777 100644
--- a/go/gitstore/bt_gitstore/bt_gitstore.go
+++ b/go/gitstore/bt_gitstore/bt_gitstore.go
@@ -18,6 +18,7 @@
"cloud.google.com/go/bigtable"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/gitstore"
+ deprecated "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
@@ -38,6 +39,9 @@
// The given repoURL serves to identify the repository. Internally it is stored normalized
// via a call to git.NormalizeURL.
func New(ctx context.Context, config *BTConfig, repoURL string) (*BigTableGitStore, error) {
+ if config.TableID == deprecated.DEPRECATED_TABLE_ID {
+ return nil, skerr.Fmt("This implementation of BigTableGitStore cannot be used with deprecated table %q", deprecated.DEPRECATED_TABLE_ID)
+ }
// Create the client.
client, err := bigtable.NewClient(ctx, config.ProjectID, config.InstanceID)
if err != nil {
diff --git a/go/gitstore_deprecated/bt_gitstore/bt_gitstore.go b/go/gitstore_deprecated/bt_gitstore/bt_gitstore.go
new file mode 100644
index 0000000..0637eae
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/bt_gitstore.go
@@ -0,0 +1,750 @@
+package bt_gitstore
+
+// The bt_gitstore package implements a way to store Git metadata in BigTable for faster retrieval
+// than requiring a local checkout.
+
+import (
+ "bytes"
+ "context"
+ "encoding/binary"
+ "fmt"
+ "hash/crc32"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "cloud.google.com/go/bigtable"
+ "go.skia.org/infra/go/git"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/util"
+ "go.skia.org/infra/go/vcsinfo"
+ "golang.org/x/sync/errgroup"
+)
+
+const (
+ DEPRECATED_TABLE_ID = "git-repos"
+ TESTING_TABLE_ID = "test-git-repos"
+)
+
+// BigTableGitStore implements the GitStore interface based on BigTable.
+type BigTableGitStore struct {
+ RepoID int64
+ RepoURL string
+ shards uint32
+ table *bigtable.Table
+}
+
+// New returns an instance of GitStore that uses BigTable as its backend storage.
+// The given repoURL serves to identify the repository. Internally it is stored normalized
+// via a call to git.NormalizeURL.
+func New(ctx context.Context, config *BTConfig, repoURL string) (*BigTableGitStore, error) {
+ if config.TableID != DEPRECATED_TABLE_ID && config.TableID != TESTING_TABLE_ID {
+ return nil, skerr.Fmt("This deprecated implementation of BigTableGitStore only works with table %q, not %q", DEPRECATED_TABLE_ID, config.TableID)
+ }
+ // Create the client.
+ client, err := bigtable.NewClient(ctx, config.ProjectID, config.InstanceID)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "creating bigtable client (project: %s; instance: %s)", config.ProjectID, config.InstanceID)
+ }
+
+ repoURL, err = git.NormalizeURL(repoURL)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "normalizing URL %q", repoURL)
+ }
+
+ shards := config.Shards
+ if shards <= 0 {
+ shards = DefaultShards
+ }
+
+ ret := &BigTableGitStore{
+ table: client.Open(config.TableID),
+ shards: uint32(shards),
+ RepoURL: repoURL,
+ }
+
+ repoInfo, err := ret.loadRepoInfo(ctx, true)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "getting initial repo info for %s (project %s; instance: %s)", repoURL, config.ProjectID, config.InstanceID)
+ }
+ ret.RepoID = repoInfo.ID
+ return ret, nil
+}
+
+const (
+ // Column families.
+ cfBranches = "B"
+ cfCommit = "C"
+ cfTsCommit = "T"
+ cfMeta = "M"
+
+ // meta data columns.
+ colMetaID = "metaID"
+ colMetaIDCounter = "metaIDCounter"
+
+ // Keys of meta data rows.
+ metaVarRepo = "repo"
+ metaVarIDCounter = "idcounter"
+
+ // index commit
+ colHash = "h"
+ colTimestamp = "t"
+
+ // shortcommit
+ colAuthor = "a"
+ colSubject = "s"
+ colParents = "p"
+ colBody = "b"
+ colBranches = "br"
+
+ // Define the row types.
+ typIndex = "i"
+ typTimeStamp = "z"
+ typCommit = "k"
+ typMeta = "!"
+
+ // allCommitsBranch is a pseudo branch name to index all commits in a repo.
+ allCommitsBranch = "@all-commits"
+
+ // getBatchSize is the batchsize for the Get operation. Each call to bigtable is made with maximally
+ // this number of git hashes. This is a conservative number to stay within the 1M request
+ // size limit. Since requests are sharded this will not limit throughput in practice.
+ getBatchSize = 5000
+
+ // writeBatchSize is the number of mutations to write at once. This could be fine tuned for different
+ // row types.
+ writeBatchSize = 1000
+)
+
+var (
+ // Default number of shards used, if not shards provided in BTConfig.
+ DefaultShards = 32
+)
+
+// Put implements the GitStore interface.
+func (b *BigTableGitStore) Put(ctx context.Context, commits []*vcsinfo.LongCommit) error {
+ branch := ""
+
+ if err := b.writeLongCommits(ctx, commits); err != nil {
+ return skerr.Fmt("Error writing long commits: %s", err)
+ }
+
+ // Retrieve the commits in time chronological order and set the index.
+ indexCommits, err := b.RangeByTime(ctx, vcsinfo.MinTime, vcsinfo.MaxTime, branch)
+ if err != err {
+ return skerr.Fmt("Error retrieving commits in order: %s", err)
+ }
+
+ for idx, idxCommit := range indexCommits {
+ idxCommit.Index = idx
+ }
+ return b.writeIndexCommits(ctx, indexCommits, branch)
+}
+
+// Get implements the GitStore interface.
+func (b *BigTableGitStore) Get(ctx context.Context, hashes []string) ([]*vcsinfo.LongCommit, error) {
+ rowNames := make(bigtable.RowList, len(hashes))
+ hashOrder := make(map[string]int, len(hashes))
+ for idx, h := range hashes {
+ rowNames[idx] = b.rowName("", typCommit, h)
+ hashOrder[h] = idx
+ }
+
+ var egroup errgroup.Group
+ tempRet := make([]*vcsinfo.LongCommit, len(hashes))
+ prefix := cfCommit + ":"
+
+ for batchStart := 0; batchStart < len(rowNames); batchStart += getBatchSize {
+ func(bStart, bEnd int) {
+ egroup.Go(func() error {
+ bRowNames := rowNames[bStart:bEnd]
+ batchIdx := int64(bStart - 1)
+
+ err := b.table.ReadRows(ctx, bRowNames, func(row bigtable.Row) bool {
+ longCommit := vcsinfo.NewLongCommit()
+ longCommit.Hash = keyFromRowName(row.Key())
+
+ for _, col := range row[cfCommit] {
+ switch strings.TrimPrefix(col.Column, prefix) {
+ case colHash:
+ longCommit.Timestamp = col.Timestamp.Time().UTC()
+ case colAuthor:
+ longCommit.Author = string(col.Value)
+ case colSubject:
+ longCommit.Subject = string(col.Value)
+ case colParents:
+ if len(col.Value) > 0 {
+ longCommit.Parents = strings.Split(string(col.Value), ":")
+ }
+ case colBody:
+ longCommit.Body = string(col.Value)
+ }
+ }
+ targetIdx := atomic.AddInt64(&batchIdx, 1)
+ tempRet[targetIdx] = longCommit
+ return true
+ })
+ if err != nil {
+ return skerr.Fmt("Error running ReadRows: %s", err)
+ }
+ return nil
+ })
+ }(batchStart, util.MinInt(batchStart+getBatchSize, len(rowNames)))
+ }
+
+ if err := egroup.Wait(); err != nil {
+ return nil, err
+ }
+
+ // Put the results into their places based of the order of the input hashes.
+ ret := make([]*vcsinfo.LongCommit, len(hashes))
+ for _, commit := range tempRet {
+ if commit != nil {
+ targetIdx := hashOrder[commit.Hash]
+ ret[targetIdx] = commit
+ }
+ }
+ return ret, nil
+}
+
+// PutBranches implements the GitStore interface.
+func (b *BigTableGitStore) PutBranches(ctx context.Context, branches map[string]string) error {
+ repoInfo, err := b.loadRepoInfo(ctx, false)
+ if err != nil {
+ return err
+ }
+
+ // Load the commit graph.
+ graph, err := b.GetGraph(ctx)
+ if err != nil {
+ return skerr.Fmt("Error loading graph: %s", err)
+ }
+
+ // updateFromm maps branchName -> branch_pointer_to_old_head to capture the branches we need to update
+ // and whether the branch existed before this update (the value of the map is not nil).
+ updateFrom := make(map[string]*gitstore_deprecated.BranchPointer, len(branches))
+ for branchName, head := range branches {
+ // Assume we start out with a completely fresh branch
+ var oldHeadPtr *gitstore_deprecated.BranchPointer = nil
+ if foundHeadPtr, ok := repoInfo.Branches[branchName]; ok {
+ // We are already done and do not need to update this branch.
+ if foundHeadPtr.Head == head {
+ continue
+ }
+
+ oldHeadNode := graph.GetNode(foundHeadPtr.Head)
+ if oldHeadNode == nil {
+ return skerr.Fmt("Unable to find previous head commit %s in graph", foundHeadPtr.Head)
+ }
+ oldHeadPtr = foundHeadPtr
+ }
+ updateFrom[branchName] = oldHeadPtr
+ }
+
+ var egroup errgroup.Group
+ for branchName, oldHeadPtr := range updateFrom {
+ func(branchName string, oldHeadPtr *gitstore_deprecated.BranchPointer) {
+ egroup.Go(func() error {
+ if branches[branchName] == gitstore_deprecated.DELETE_BRANCH {
+ return b.deleteBranchPointer(ctx, branchName)
+ } else {
+ return b.updateBranch(ctx, branchName, branches[branchName], oldHeadPtr, graph)
+ }
+ })
+ }(branchName, oldHeadPtr)
+ }
+ if err := egroup.Wait(); err != nil {
+ return skerr.Fmt("Error updating branches: %s", err)
+ }
+ return nil
+}
+
+// GetBranches implements the GitStore interface.
+func (b *BigTableGitStore) GetBranches(ctx context.Context) (map[string]*gitstore_deprecated.BranchPointer, error) {
+ repoInfo, err := b.loadRepoInfo(ctx, false)
+ if err != nil {
+ return nil, err
+ }
+
+ // Replace the pseudo branch for all commits with an empty branch name.
+ if found, ok := repoInfo.Branches[allCommitsBranch]; ok {
+ repoInfo.Branches[""] = found
+ delete(repoInfo.Branches, allCommitsBranch)
+ }
+
+ return repoInfo.Branches, nil
+}
+
+// RangeByTime implements the GitStore interface.
+func (b *BigTableGitStore) RangeByTime(ctx context.Context, start, end time.Time, branch string) ([]*vcsinfo.IndexCommit, error) {
+ startTS := sortableTimestamp(start)
+ endTS := sortableTimestamp(end)
+
+ result := newSRTimestampCommits(b.shards)
+ filters := []bigtable.Filter{bigtable.FamilyFilter(cfTsCommit)}
+ err := b.iterShardedRange(ctx, branch, typTimeStamp, startTS, endTS, filters, result)
+ if err != nil {
+ return nil, err
+ }
+
+ return result.Sorted(), nil
+}
+
+// RangeByTime implements the GitStore interface.
+func (b *BigTableGitStore) RangeN(ctx context.Context, startIndex, endIndex int, branch string) ([]*vcsinfo.IndexCommit, error) {
+ startIdx := sortableIndex(startIndex)
+ endIdx := sortableIndex(endIndex)
+
+ result := newSRIndexCommits(b.shards)
+ filters := []bigtable.Filter{bigtable.FamilyFilter(cfCommit)}
+ err := b.iterShardedRange(ctx, branch, typIndex, startIdx, endIdx, filters, result)
+ if err != nil {
+ return nil, err
+ }
+ return result.Sorted(), nil
+}
+
+func (b *BigTableGitStore) loadRepoInfo(ctx context.Context, create bool) (*gitstore_deprecated.RepoInfo, error) {
+ // load repo info
+ rowName := getRepoInfoRowName(b.RepoURL)
+ row, err := b.table.ReadRow(ctx, rowName, bigtable.RowFilter(bigtable.LatestNFilter(1)))
+ if err != nil {
+ return nil, err
+ }
+
+ if row != nil {
+ return extractRepoInfo(row)
+ }
+
+ // If we are not create new repo information, return an error.
+ if !create {
+ return nil, skerr.Fmt("Repo information for %s not found.", b.RepoURL)
+ }
+
+ // Get a new ID from the DB
+ rmw := bigtable.NewReadModifyWrite()
+ rmw.Increment(cfMeta, colMetaIDCounter, 1)
+ row, err = b.table.ApplyReadModifyWrite(ctx, unshardedRowName(typMeta, metaVarIDCounter), rmw)
+ if err != nil {
+ return nil, err
+ }
+
+ // encID contains the big-endian encoded ID
+ encID := []byte(rowMap(row).GetStr(cfMeta, colMetaIDCounter))
+ id := int64(binary.BigEndian.Uint64(encID))
+ mut := bigtable.NewMutation()
+ mut.Set(cfMeta, colMetaID, bigtable.ServerTime, encID)
+ if err := b.table.Apply(ctx, rowName, mut); err != nil {
+ return nil, err
+ }
+
+ b.RepoID = id
+ return &gitstore_deprecated.RepoInfo{
+ RepoURL: b.RepoURL,
+ ID: id,
+ Branches: map[string]*gitstore_deprecated.BranchPointer{},
+ }, nil
+}
+
+// graphColFilter defines a filter (regex) that only keeps columns we need to build the commit graph.
+// Used by GetGraph.
+var graphColFilter = fmt.Sprintf("(%s)", strings.Join([]string{colHash, colParents}, "|"))
+
+// GetGraph implements the GitStore interface.
+func (b *BigTableGitStore) GetGraph(ctx context.Context) (*gitstore_deprecated.CommitGraph, error) {
+ result := newRawNodesResult(b.shards)
+ filters := []bigtable.Filter{
+ bigtable.FamilyFilter(cfCommit),
+ bigtable.ColumnFilter(graphColFilter),
+ }
+ if err := b.iterShardedRange(ctx, "", typCommit, "", "", filters, result); err != nil {
+ return nil, skerr.Fmt("Error getting sharded commits: %s", err)
+ }
+ rawGraph, timeStamps := result.Merge()
+ return gitstore_deprecated.BuildGraph(rawGraph, timeStamps), nil
+}
+
+func (b *BigTableGitStore) getAsIndexCommits(ctx context.Context, ancestors []*gitstore_deprecated.Node, startIdx int) ([]*vcsinfo.IndexCommit, error) {
+ ret := make([]*vcsinfo.IndexCommit, len(ancestors))
+ for idx, commitNode := range ancestors {
+ ret[idx] = &vcsinfo.IndexCommit{
+ Index: startIdx + idx,
+ Hash: commitNode.Hash,
+ Timestamp: commitNode.Timestamp,
+ }
+ }
+ return ret, nil
+}
+
+// updateBranch updates the indices for the named branch and stores the branch pointer. It
+// calculates the branch based on the given commit graph.
+// If there is no previous branch then oldBranchPtr should be nil.
+func (b *BigTableGitStore) updateBranch(ctx context.Context, branchName, newBranchHead string, oldBranchPtr *gitstore_deprecated.BranchPointer, graph *gitstore_deprecated.CommitGraph) error {
+ // Make sure the new head node is in branch.
+ headNode := graph.GetNode(newBranchHead)
+ if headNode == nil {
+ return skerr.Fmt("Head commit %s not found in commit graph", newBranchHead)
+ }
+
+ // If we have not previous branch we set the corresponding values so the logic below still works.
+ if oldBranchPtr == nil {
+ oldBranchPtr = &gitstore_deprecated.BranchPointer{Head: "", Index: 0}
+ }
+
+ branchNodes := graph.DecendantChain(oldBranchPtr.Head, newBranchHead)
+ startIndex := 0
+
+ // If the hash of the first Node matches the hash of the old branchpointer we need to adjust
+ // the initial value of index.
+ if branchNodes[0].Hash == oldBranchPtr.Head {
+ startIndex = oldBranchPtr.Index
+ }
+ indexCommits, err := b.getAsIndexCommits(ctx, branchNodes, startIndex)
+ if err != nil {
+ return skerr.Fmt("Error getting index commits for branch %s: %s", branchName, err)
+ }
+
+ // Write the index commits.
+ if err := b.writeIndexCommits(ctx, indexCommits, branchName); err != nil {
+ return err
+ }
+
+ // Write the index commits of the branch sorted by timestamps.
+ return b.writeTimestampIndex(ctx, indexCommits, branchName)
+}
+
+// putBranchPointer writes the branch pointer (the HEAD of a branch) to the row that stores
+// the repo information. idxCommit is the index commit of the HEAD of the branch.
+func (b *BigTableGitStore) putBranchPointer(ctx context.Context, repoInfoRowName, branchName string, idxCommit *vcsinfo.IndexCommit) error {
+ if branchName == "" {
+ branchName = allCommitsBranch
+ }
+
+ mut := bigtable.NewMutation()
+ now := bigtable.Now()
+ mut.Set(cfBranches, branchName, now, encBranchPointer(idxCommit.Hash, idxCommit.Index))
+ mut.DeleteTimestampRange(cfBranches, branchName, 0, now)
+ return b.table.Apply(ctx, repoInfoRowName, mut)
+}
+
+// deleteBranchPointer deletes the row containing the branch pointer.
+func (b *BigTableGitStore) deleteBranchPointer(ctx context.Context, branchName string) error {
+ mut := bigtable.NewMutation()
+ mut.DeleteCellsInColumn(cfBranches, branchName)
+ return b.table.Apply(ctx, getRepoInfoRowName(b.RepoURL), mut)
+}
+
+// writeLongCommits writes the LongCommits to the store idempotently.
+func (b *BigTableGitStore) writeLongCommits(ctx context.Context, commits []*vcsinfo.LongCommit) error {
+ branch := ""
+
+ // Assemble the mutations.
+ nMutations := len(commits)
+ rowNames := make([]string, 0, nMutations)
+ mutations := make([]*bigtable.Mutation, 0, nMutations)
+
+ // Assemble the records for the Timestamp index.
+ tsIdxCommits := make([]*vcsinfo.IndexCommit, 0, nMutations)
+
+ for _, commit := range commits {
+ // Add the long commits
+ rowNames = append(rowNames, b.rowName(branch, typCommit, commit.Hash))
+ mutations = append(mutations, b.getCommitMutation(commit))
+
+ tsIdxCommits = append(tsIdxCommits, &vcsinfo.IndexCommit{
+ Hash: commit.Hash,
+ Timestamp: commit.Timestamp,
+ })
+ }
+
+ if err := b.applyBulkBatched(ctx, rowNames, mutations, writeBatchSize); err != nil {
+ return skerr.Fmt("Error writing commits: %s", err)
+ }
+ return b.writeTimestampIndex(ctx, tsIdxCommits, branch)
+}
+
+// applyBulkBatched writes the given rowNames/mutation pairs to bigtable in batches that are
+// maximally of size 'batchSize'. The batches are written in parallel.
+func (b *BigTableGitStore) applyBulkBatched(ctx context.Context, rowNames []string, mutations []*bigtable.Mutation, batchSize int) error {
+ var egroup errgroup.Group
+ err := util.ChunkIter(len(rowNames), batchSize, func(chunkStart, chunkEnd int) error {
+ egroup.Go(func() error {
+ rowNames := rowNames[chunkStart:chunkEnd]
+ mutations := mutations[chunkStart:chunkEnd]
+ errs, err := b.table.ApplyBulk(ctx, rowNames, mutations)
+ if err != nil {
+ return skerr.Fmt("Error writing batch [%d:%d]: %s", chunkStart, chunkEnd, err)
+ }
+ if errs != nil {
+ return skerr.Fmt("Error writing some portions of batch [%d:%d]: %s", chunkStart, chunkEnd, errs)
+ }
+ return nil
+ })
+ return nil
+ })
+ if err != nil {
+ return skerr.Fmt("Error running ChunkIter: %s", err)
+ }
+ return egroup.Wait()
+}
+
+// writeIndexCommits writes the given index commits keyed by their indices for the given branch.
+func (b *BigTableGitStore) writeIndexCommits(ctx context.Context, indexCommits []*vcsinfo.IndexCommit, branch string) error {
+ idxRowNames := make([]string, 0, len(indexCommits))
+ idxMutations := make([]*bigtable.Mutation, 0, len(indexCommits))
+
+ for idx, commit := range indexCommits {
+ sIndex := sortableIndex(indexCommits[idx].Index)
+ idxRowNames = append(idxRowNames, b.rowName(branch, typIndex, sIndex))
+ idxMutations = append(idxMutations, b.simpleMutation(cfCommit, commit.Timestamp, [2]string{colHash, commit.Hash}))
+ }
+
+ if err := b.applyBulkBatched(ctx, idxRowNames, idxMutations, writeBatchSize); err != nil {
+ return skerr.Fmt("Error writing indices: %s", err)
+ }
+ return b.putBranchPointer(ctx, getRepoInfoRowName(b.RepoURL), branch, indexCommits[len(indexCommits)-1])
+}
+
+// writeTimestampIndexCommits writes the given index commits keyed by their timestamp for the
+// given branch.
+func (b *BigTableGitStore) writeTimestampIndex(ctx context.Context, indexCommits []*vcsinfo.IndexCommit, branch string) error {
+ nMutations := len(indexCommits)
+ tsRowNames := make([]string, 0, nMutations)
+ tsMutations := make([]*bigtable.Mutation, 0, nMutations)
+
+ for _, commit := range indexCommits {
+ tsRowName := b.rowName(branch, typTimeStamp, sortableTimestamp(commit.Timestamp))
+ tsRowNames = append(tsRowNames, tsRowName)
+ tsMutations = append(tsMutations, b.simpleMutation(cfTsCommit, commit.Timestamp, [][2]string{
+ {commit.Hash, sortableIndex(commit.Index)},
+ }...))
+ }
+
+ // Write the timestamped index.
+ if err := b.applyBulkBatched(ctx, tsRowNames, tsMutations, writeBatchSize); err != nil {
+ return skerr.Fmt("Error writing timestamps: %s", err)
+ }
+ return nil
+}
+
+// iterShardedRange iterates the keys in the half open interval [startKey, endKey) across all
+// shards triggering as many queries as there are shards. If endKey is empty, then startKey is
+// used to generate a prefix and a Prefix scan is performed.
+// The results of the query are added to the instance of shardedResults.
+func (b *BigTableGitStore) iterShardedRange(ctx context.Context, branch, rowType, startKey, endKey string, filters []bigtable.Filter, result shardedResults) error {
+ var egroup errgroup.Group
+
+ // Query all shards in parallel.
+ for shard := uint32(0); shard < b.shards; shard++ {
+ func(shard uint32) {
+ egroup.Go(func() error {
+ defer result.Finish(shard)
+
+ var rr bigtable.RowRange
+ // Treat the startKey as part of a prefix and do a prefix scan.
+ if endKey == "" {
+ rowPrefix := b.shardedRowName(shard, branch, rowType, startKey)
+ rr = bigtable.PrefixRange(rowPrefix)
+ } else {
+ // Derive the start and end row names.
+ rStart := b.shardedRowName(shard, branch, rowType, startKey)
+ rEnd := b.shardedRowName(shard, branch, rowType, endKey)
+ rr = bigtable.NewRange(rStart, rEnd)
+ }
+
+ var addErr error
+ err := b.table.ReadRows(ctx, rr, func(row bigtable.Row) bool {
+ addErr = result.Add(shard, row)
+ return addErr == nil
+ }, filtersToReadOptions(filters)...)
+ if err != nil {
+ return err
+ }
+ return addErr
+ })
+ }(shard)
+ }
+
+ if err := egroup.Wait(); err != nil {
+ return err
+ }
+ return nil
+}
+
+// simpleMutation assembles a simple mutation consisting of a column family, a timestamp and a
+// set of column/value pairs. The timestamp is applied to all column/pairs.
+func (b *BigTableGitStore) simpleMutation(cfFam string, timeStamp time.Time, colValPairs ...[2]string) *bigtable.Mutation {
+ ts := bigtable.Time(timeStamp.UTC())
+ ret := bigtable.NewMutation()
+ for _, pair := range colValPairs {
+ ret.Set(cfFam, pair[0], ts, []byte(pair[1]))
+ }
+ return ret
+}
+
+// getCommitMutation gets the mutation to write a long commit. Since the timestamp is set to the
+// timestamp of the commit this is idempotent.
+func (b *BigTableGitStore) getCommitMutation(commit *vcsinfo.LongCommit) *bigtable.Mutation {
+ ts := bigtable.Time(commit.Timestamp.UTC())
+ ret := bigtable.NewMutation()
+ ret.Set(cfCommit, colHash, ts, []byte(commit.Hash))
+ ret.Set(cfCommit, colAuthor, ts, []byte(commit.Author))
+ ret.Set(cfCommit, colSubject, ts, []byte(commit.Subject))
+ ret.Set(cfCommit, colParents, ts, []byte(strings.Join(commit.Parents, ":")))
+ ret.Set(cfCommit, colBody, ts, []byte(commit.Body))
+ return ret
+}
+
+// rowName returns that BT rowName based on the tuple: (branch,rowType,Key).
+// It also derives a unique shard for the given tuple and generates the complete rowName.
+func (b *BigTableGitStore) rowName(branch string, rowType string, key string) string {
+ return b.shardedRowName(crc32.ChecksumIEEE([]byte(key))%b.shards, branch, rowType, key)
+}
+
+// shardedRowName returns the row name from (shard, branch, rowType, key) this is useful
+// when we want to generate a specific row name with a defined shard.
+func (b *BigTableGitStore) shardedRowName(shard uint32, branch, rowType, key string) string {
+ return fmt.Sprintf("%02d:%04d:%s:%s:%s", shard, b.RepoID, branch, rowType, key)
+}
+
+// unshardedRowName concatenates parts without prefixing any sharding information. This is for
+// row types that are not sharded.
+func unshardedRowName(parts ...string) string {
+ return strings.Join(parts, ":")
+}
+
+// getRepoInfoRowName returns the name of the row where the repo meta data is stored based on the repoURL.
+func getRepoInfoRowName(repoURL string) string {
+ return unshardedRowName(typMeta, metaVarRepo, repoURL)
+}
+
+// getRepoInfoRowNamePrefix returns the row Name prefix to scan all rows containing repo meta data.
+func getRepoInfoRowNamePrefix() string {
+ return unshardedRowName(typMeta, metaVarRepo)
+}
+
+// extractRepoInfo extract the repo meta data information from a read row.
+func extractRepoInfo(row bigtable.Row) (*gitstore_deprecated.RepoInfo, error) {
+ rm := rowMap(row)
+
+ // Extract the branch info.
+ branchInfo := rm.GetStrMap(cfBranches)
+ branches := make(map[string]*gitstore_deprecated.BranchPointer, len(branchInfo))
+ var err error
+ for name, b := range branchInfo {
+ branches[name], err = decBranchPointer([]byte(b))
+ if err != nil {
+ return nil, skerr.Fmt("Error decoding branch pointer: %s", err)
+ }
+ }
+
+ // Extract the repo ID.
+ idBytes := []byte(rm.GetStr(cfMeta, colMetaID))
+ if len(idBytes) != 8 {
+ return nil, skerr.Fmt("Error: Got id that's not exactly 8 bytes: '%x': %s", idBytes, err)
+ }
+
+ ret := &gitstore_deprecated.RepoInfo{
+ RepoURL: keyFromRowName(row.Key()),
+ ID: int64(binary.BigEndian.Uint64(idBytes)),
+ Branches: branches,
+ }
+ return ret, nil
+}
+
+// filtersToReadOptions converts a list of filters to []bigtable.ReadOption. It will inject
+// a ChainFilters(...) instance if multiple filters are defined. By returning a slice we are
+// able to pass 0 - n filter to a ReadRows call.
+func filtersToReadOptions(filters []bigtable.Filter) []bigtable.ReadOption {
+ if len(filters) == 0 {
+ return []bigtable.ReadOption{}
+ }
+
+ // If there is more than one filter then chain them.
+ if len(filters) > 1 {
+ filters = []bigtable.Filter{bigtable.ChainFilters(filters...)}
+ }
+
+ return []bigtable.ReadOption{bigtable.RowFilter(filters[0])}
+}
+
+// keyFromRowName assumes that key segments are separated by ':' and the last segment is the
+// actual key we are interested in.
+func keyFromRowName(rowName string) string {
+ parts := strings.Split(rowName, ":")
+ return parts[len(parts)-1]
+}
+
+// sortableTimestamp returns a timestamp as a string (in seconds) that can used as a key in BT.
+func sortableTimestamp(ts time.Time) string {
+ // Convert the timestamp in seconds to a string that is sortable and limit it to 10 digits.
+ // That is the equivalent of valid timestamps up to November 2286.
+ return fmt.Sprintf("%010d", util.MinInt64(9999999999, ts.Unix()))
+}
+
+// sortableIndex returns an index as a string that can be used as a key in BT.
+func sortableIndex(index int) string {
+ return fmt.Sprintf("%08d", util.MinInt(99999999, index))
+}
+
+// parseIndex parses an index previously generated with sortableIndex.
+func parseIndex(indexStr string) int {
+ ret, err := strconv.ParseInt(indexStr, 10, 64)
+ if err != nil {
+ return -1
+ }
+ return int(ret)
+}
+
+// encBranchPointer converts a hash and an index into a string where the parts are separated by ':'
+func encBranchPointer(hash string, index int) []byte {
+ idxBuf := make([]byte, 8)
+ binary.BigEndian.PutUint64(idxBuf, uint64(index))
+ return []byte(hash + ":" + string(idxBuf))
+}
+
+// decBranchPointer a string previously generated by encBranchPointer into a BranchPointer,
+// containing a head and index.
+func decBranchPointer(encPointer []byte) (*gitstore_deprecated.BranchPointer, error) {
+ parts := bytes.SplitN(encPointer, []byte(":"), 2)
+ if len(parts) != 2 || len(parts[1]) != 8 {
+ return nil, skerr.Fmt("Received wrong branch pointer. Expected format <commit>:<big_endian_64_bit>")
+ }
+ return &gitstore_deprecated.BranchPointer{
+ Head: string(parts[0]),
+ Index: int(binary.BigEndian.Uint64([]byte(parts[1]))),
+ }, nil
+}
+
+// rowMap is a helper type that wraps around a bigtable.Row and allows to extract columns and their
+// values.
+type rowMap bigtable.Row
+
+// GetStr extracts that value of colFam:colName as a string from the row. If it doesn't exist it
+// returns ""
+func (r rowMap) GetStr(colFamName, colName string) string {
+ prefix := colFamName + ":"
+ for _, col := range r[colFamName] {
+ if strings.TrimPrefix(col.Column, prefix) == colName {
+ return string(col.Value)
+ }
+ }
+ return ""
+}
+
+// GetStrMap extracts a map[string]string from the row that maps columns -> values for the given
+// column family.
+func (r rowMap) GetStrMap(colFamName string) map[string]string {
+ prefix := colFamName + ":"
+ ret := make(map[string]string, len(r[colFamName]))
+ for _, col := range r[colFamName] {
+ trimmed := strings.TrimPrefix(col.Column, prefix)
+ ret[trimmed] = string(col.Value)
+ }
+ return ret
+}
+
+// Make sure BigTableGitStore fulfills the GitStore interface
+var _ gitstore_deprecated.GitStore = (*BigTableGitStore)(nil)
diff --git a/go/gitstore_deprecated/bt_gitstore/bt_gitstore_test.go b/go/gitstore_deprecated/bt_gitstore/bt_gitstore_test.go
new file mode 100644
index 0000000..1ffd438
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/bt_gitstore_test.go
@@ -0,0 +1,144 @@
+package bt_gitstore_test
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "testing"
+
+ assert "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/common"
+ "go.skia.org/infra/go/git"
+ "go.skia.org/infra/go/git/gitinfo"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ gitstore_testutils "go.skia.org/infra/go/gitstore_deprecated/testutils"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/go/timer"
+ "go.skia.org/infra/go/vcsinfo"
+ vcs_testutils "go.skia.org/infra/go/vcsinfo/testutils"
+)
+
+const (
+ localRepoURL = "https://example.com/local.git"
+)
+
+// This test uses a checkout of a repo (can be really any repo) in /tmp/skia.
+// If not present, syncs skia.git to /tmp/skia first.
+func TestGitStoreSkiaRepo(t *testing.T) {
+ unittest.ManualTest(t)
+ t.Skip()
+ skiaRepoDir := filepath.Join(os.TempDir(), "skia")
+ if _, err := os.Stat(skiaRepoDir); os.IsNotExist(err) {
+ _, err = git.NewRepo(context.Background(), common.REPO_SKIA, os.TempDir())
+ assert.NoError(t, err)
+ } else if err != nil {
+ assert.FailNow(t, err.Error())
+ }
+ testGitStore(t, common.REPO_SKIA, skiaRepoDir, true)
+}
+
+func TestGitStoreLocalRepo(t *testing.T) {
+ unittest.LargeTest(t)
+
+ repoDir, cleanup := vcs_testutils.InitTempRepo()
+ defer cleanup()
+ testGitStore(t, localRepoURL, repoDir, true)
+}
+
+func testGitStore(t *testing.T, repoURL, repoDir string, freshLoad bool) {
+ // Get all commits that have been added to the gitstore.
+ _, longCommits, gitStore := gitstore_testutils.SetupAndLoadBTGitStore(t, repoURL, repoDir, freshLoad)
+
+ // Sort long commits they way they are sorted by BigTable (by timestamp/hash)
+ sort.Slice(longCommits, func(i, j int) bool {
+ tsI := longCommits[i].Timestamp.Unix()
+ tsJ := longCommits[j].Timestamp.Unix()
+ return (tsI < tsJ) || ((tsI == tsJ) && (longCommits[i].Hash < longCommits[j].Hash))
+ })
+ indexCommits := make([]*vcsinfo.IndexCommit, len(longCommits))
+ for idx, commit := range longCommits {
+ indexCommits[idx] = &vcsinfo.IndexCommit{
+ Index: idx,
+ Hash: commit.Hash,
+ Timestamp: commit.Timestamp,
+ }
+ }
+
+ // Find all the commits in the repository independent of branches.
+ foundIndexCommits, foundLongCommits := getFromRange(t, gitStore, 0, len(longCommits), "")
+ assert.Equal(t, len(indexCommits), len(foundIndexCommits))
+ assert.Equal(t, len(longCommits), len(foundLongCommits))
+
+ // Make sure they match what we found.
+ for idx, expected := range longCommits {
+ foundLongCommits[idx].Branches = expected.Branches
+ assert.Equal(t, expected, foundLongCommits[idx])
+ }
+
+ // Verify that the branches from the GitStore match what's in the checkout.
+ branchNames, branchCommits := getBranchCommits(t, repoDir)
+ for branchIdx, branchName := range branchNames {
+ expHashes := branchCommits[branchIdx]
+ foundIndexCommits, foundLongCommits := getFromRange(t, gitStore, 0, len(longCommits), branchName)
+ assert.Equal(t, len(expHashes), len(foundIndexCommits))
+ assert.Equal(t, len(expHashes), len(foundLongCommits))
+ expIdx := len(expHashes) - 1
+ for idx := len(foundIndexCommits) - 1; idx >= 0; idx-- {
+ expHash := expHashes[expIdx]
+ assert.Equal(t, foundIndexCommits[idx].Hash, foundLongCommits[idx].Hash)
+ assert.Equal(t, expHash, foundIndexCommits[idx].Hash)
+ expIdx--
+ }
+ }
+}
+
+func getBranchCommits(t *testing.T, repoDir string) ([]string, [][]string) {
+ ctx := context.TODO()
+ vcs, err := gitinfo.NewGitInfo(ctx, repoDir, false, true)
+ assert.NoError(t, err)
+
+ branches, err := vcs.GetBranches(ctx)
+ assert.NoError(t, err)
+
+ branchNames := make([]string, 0, len(branches))
+ branchCommits := make([][]string, 0, len(branches))
+ for _, branch := range branches {
+ // if strings.Contains(branch.Name, "m62") {
+ // continue
+ // }
+ branchNames = append(branchNames, branch.Name)
+ indexCommits, err := gitinfo.GetBranchCommits(ctx, repoDir, branch.Name)
+ assert.NoError(t, err)
+ commitHashes := make([]string, len(indexCommits))
+ for idx, idxCommit := range indexCommits {
+ commitHashes[idx] = idxCommit.Hash
+ }
+ branchCommits = append(branchCommits, commitHashes)
+ }
+
+ return branchNames, branchCommits
+}
+
+func getFromRange(t *testing.T, gitStore gitstore_deprecated.GitStore, startIdx, endIdx int, branchName string) ([]*vcsinfo.IndexCommit, []*vcsinfo.LongCommit) {
+ ctx := context.TODO()
+
+ tQuery := timer.New(fmt.Sprintf("RangeN %d - %d commits from branch %q", startIdx, endIdx, branchName))
+ foundIndexCommits, err := gitStore.RangeN(ctx, startIdx, endIdx, branchName)
+ assert.NoError(t, err)
+ tQuery.Stop()
+
+ hashes := make([]string, 0, len(foundIndexCommits))
+ for _, commit := range foundIndexCommits {
+ hashes = append(hashes, commit.Hash)
+ }
+
+ tLongCommits := timer.New(fmt.Sprintf("Get %d LongCommits from branch %q", len(hashes), branchName))
+ foundLongCommits, err := gitStore.Get(ctx, hashes)
+ assert.NoError(t, err)
+ assert.Equal(t, len(foundIndexCommits), len(foundLongCommits))
+ tLongCommits.Stop()
+
+ return foundIndexCommits, foundLongCommits
+}
diff --git a/go/gitstore_deprecated/bt_gitstore/helpers.go b/go/gitstore_deprecated/bt_gitstore/helpers.go
new file mode 100644
index 0000000..6166650
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/helpers.go
@@ -0,0 +1,104 @@
+package bt_gitstore
+
+// This file has some helper functions for working with BigTable.
+
+import (
+ "context"
+ "strconv"
+
+ "cloud.google.com/go/bigtable"
+ "go.skia.org/infra/go/bt"
+ "go.skia.org/infra/go/git/repograph"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/skerr"
+)
+
+// InitBT initializes the BT instance for the given configuration. It uses the default way
+// to get auth information from the environment and must be called with an account that has
+// admin rights.
+func InitBT(conf *BTConfig) error {
+ return bt.InitBigtable(conf.ProjectID, conf.InstanceID, conf.TableID, []string{
+ cfCommit,
+ cfMeta,
+ cfBranches,
+ cfTsCommit,
+ })
+}
+
+// AllRepos returns a map of all repos contained in given BigTable project/instance/table.
+// It returns map[normalized_URL]RepoInfo.
+func AllRepos(ctx context.Context, conf *BTConfig) (map[string]*gitstore_deprecated.RepoInfo, error) {
+ // Create the client.
+ client, err := bigtable.NewClient(ctx, conf.ProjectID, conf.InstanceID)
+ if err != nil {
+ return nil, skerr.Fmt("Error creating bigtable client: %s", err)
+ }
+
+ table := client.Open(conf.TableID)
+ rowNamesPrefix := getRepoInfoRowNamePrefix()
+ ret := map[string]*gitstore_deprecated.RepoInfo{}
+ var readRowErr error = nil
+ err = table.ReadRows(ctx, bigtable.PrefixRange(rowNamesPrefix), func(row bigtable.Row) bool {
+ if readRowErr != nil {
+ return false
+ }
+
+ var repoInfo *gitstore_deprecated.RepoInfo
+ repoInfo, readRowErr = extractRepoInfo(row)
+ if readRowErr != nil {
+ return false
+ }
+ // save the repo info and set the all-commits branch.
+ ret[repoInfo.RepoURL] = repoInfo
+ if found, ok := repoInfo.Branches[allCommitsBranch]; ok {
+ repoInfo.Branches[""] = found
+ delete(repoInfo.Branches, allCommitsBranch)
+ }
+
+ return true
+ }, bigtable.RowFilter(bigtable.LatestNFilter(1)))
+
+ if err != nil {
+ return nil, skerr.Fmt("Error reading repo info: %s", err)
+ }
+ return ret, nil
+}
+
+// RepoURLFromID retrieves the URL of a repository by its corresponding numeric ID.
+// If a repository with the given ID can be found it will be returned and the second return value
+// is true. In any other case "" and false will be returned.
+func RepoURLFromID(ctx context.Context, conf *BTConfig, repoIDStr string) (string, bool) {
+ id, err := strconv.ParseInt(repoIDStr, 10, 64)
+ if err != nil {
+ return "", false
+ }
+
+ repoInfos, err := AllRepos(ctx, conf)
+ if err != nil {
+ return "", false
+ }
+
+ for repoURL, info := range repoInfos {
+ if info.ID == id {
+ return repoURL, false
+ }
+ }
+ return "", false
+}
+
+// NewGitStoreMap returns a Map instance with Graphs for the given GitStores.
+func NewBTGitStoreMap(ctx context.Context, repoUrls []string, btConf *BTConfig) (repograph.Map, error) {
+ rv := make(map[string]*repograph.Graph, len(repoUrls))
+ for _, repoUrl := range repoUrls {
+ gs, err := New(ctx, btConf, repoUrl)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "Failed to create GitStore for %s", repoUrl)
+ }
+ graph, err := gitstore_deprecated.GetRepoGraph(ctx, gs)
+ if err != nil {
+ return nil, skerr.Wrapf(err, "Failed to create Graph from GitStore for %s", repoUrl)
+ }
+ rv[repoUrl] = graph
+ }
+ return rv, nil
+}
diff --git a/go/gitstore_deprecated/bt_gitstore/repo_impl_test.go b/go/gitstore_deprecated/bt_gitstore/repo_impl_test.go
new file mode 100644
index 0000000..cd060fd
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/repo_impl_test.go
@@ -0,0 +1,155 @@
+package bt_gitstore
+
+import (
+ "context"
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/google/uuid"
+ assert "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/git"
+ "go.skia.org/infra/go/git/repograph"
+ repograph_shared_tests "go.skia.org/infra/go/git/repograph/shared_tests"
+ git_testutils "go.skia.org/infra/go/git/testutils"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+// gitstoreRefresher is an implementation of repograph_shared_tests.RepoImplRefresher
+// used for testing a GitStore.
+type gitstoreRefresher struct {
+ gs gitstore_deprecated.GitStore
+ repo git.GitDir
+ t *testing.T
+}
+
+func newGitstoreUpdater(t *testing.T, gs gitstore_deprecated.GitStore, gb *git_testutils.GitBuilder) repograph_shared_tests.RepoImplRefresher {
+ return &gitstoreRefresher{
+ gs: gs,
+ repo: git.GitDir(gb.Dir()),
+ t: t,
+ }
+}
+
+func (u *gitstoreRefresher) Refresh(commits ...*vcsinfo.LongCommit) {
+ ctx := context.Background()
+ // Add the commits.
+ assert.NoError(u.t, u.gs.Put(ctx, commits))
+ branches, err := u.repo.Branches(ctx)
+ assert.NoError(u.t, err)
+ putBranches := make(map[string]string, len(branches))
+ for _, branch := range branches {
+ putBranches[branch.Name] = branch.Head
+ }
+ oldBranches, err := u.gs.GetBranches(ctx)
+ assert.NoError(u.t, err)
+ for name := range oldBranches {
+ if name == "" {
+ continue
+ }
+ if _, ok := putBranches[name]; !ok {
+ putBranches[name] = gitstore_deprecated.DELETE_BRANCH
+ }
+ }
+ assert.NoError(u.t, u.gs.PutBranches(ctx, putBranches))
+
+ // Wait for GitStore to be up to date.
+ for {
+ time.Sleep(10 * time.Millisecond)
+ actual, err := u.gs.GetBranches(ctx)
+ assert.NoError(u.t, err)
+ allMatch := true
+ for _, expectBranch := range branches {
+ actualBranch, ok := actual[expectBranch.Name]
+ if !ok || actualBranch.Head != expectBranch.Head {
+ allMatch = false
+ break
+ }
+ }
+ for name := range actual {
+ if _, ok := putBranches[name]; name != "" && !ok {
+ allMatch = false
+ break
+ }
+ }
+ if allMatch {
+ break
+ }
+ }
+}
+
+// setupGitStore performs common setup for GitStore based Graphs.
+func setupGitStore(t *testing.T) (context.Context, *git_testutils.GitBuilder, *repograph.Graph, repograph_shared_tests.RepoImplRefresher, func()) {
+ ctx, g, cleanup := repograph_shared_tests.CommonSetup(t)
+
+ btConf := &BTConfig{
+ ProjectID: "fake-project",
+ InstanceID: fmt.Sprintf("fake-instance-%s", uuid.New()),
+ TableID: TESTING_TABLE_ID,
+ }
+ assert.NoError(t, InitBT(btConf))
+ gs, err := New(context.Background(), btConf, g.RepoUrl())
+ assert.NoError(t, err)
+ ud := newGitstoreUpdater(t, gs, g)
+ repo, err := gitstore_deprecated.GetRepoGraph(ctx, gs)
+ assert.NoError(t, err)
+ return ctx, g, repo, ud, cleanup
+}
+
+func TestGraphWellFormedBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestGraphWellFormed(t, ctx, g, repo, ud)
+}
+
+func TestRecurseBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRecurse(t, ctx, g, repo, ud)
+}
+
+func TestRecurseAllBranchesBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRecurseAllBranches(t, ctx, g, repo, ud)
+}
+
+func TestLogLinearBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestLogLinear(t, ctx, g, repo, ud)
+}
+
+func TestUpdateHistoryChangedBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestUpdateHistoryChanged(t, ctx, g, repo, ud)
+}
+
+func TestUpdateAndReturnCommitDiffsBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestUpdateAndReturnCommitDiffs(t, ctx, g, repo, ud)
+}
+
+func TestRevListBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRevList(t, ctx, g, repo, ud)
+}
+
+func TestBranchMembershipBTGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestBranchMembership(t, ctx, g, repo, ud)
+}
diff --git a/go/gitstore_deprecated/bt_gitstore/sharded_result.go b/go/gitstore_deprecated/bt_gitstore/sharded_result.go
new file mode 100644
index 0000000..e7acbe5
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/sharded_result.go
@@ -0,0 +1,183 @@
+package bt_gitstore
+
+import (
+ "sort"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "cloud.google.com/go/bigtable"
+ multierror "github.com/hashicorp/go-multierror"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+// shardedResults collects the results of call to iterShardedRange.
+type shardedResults interface {
+ // Add adds the result of a shard.
+ Add(shard uint32, row bigtable.Row) error
+
+ // Finish indicates that the shard is done processing its results.
+ Finish(shard uint32)
+}
+
+// srIndexCommits implements the shardedResults interface for collecting results that are vcsinfo.IndexCommits
+type srIndexCommits struct {
+ errs []*multierror.Error
+ results [][]*vcsinfo.IndexCommit
+ retSize int64
+}
+
+// create a new instance to collect IndexCommits.
+func newSRIndexCommits(shards uint32) *srIndexCommits {
+ return &srIndexCommits{
+ results: make([][]*vcsinfo.IndexCommit, shards),
+ errs: make([]*multierror.Error, shards),
+ }
+}
+
+// Add implements the shardedResults interface.
+func (s *srIndexCommits) Add(shard uint32, row bigtable.Row) error {
+ idx := parseIndex(keyFromRowName(row.Key()))
+ if idx < 0 {
+ return skerr.Fmt("Unable to parse index key %q. Invalid index", row.Key())
+ }
+
+ var hash string
+ var timeStamp bigtable.Timestamp
+ prefix := cfCommit + ":"
+ for _, col := range row[cfCommit] {
+ if strings.TrimPrefix(col.Column, prefix) == colHash {
+ hash = string(col.Value)
+ timeStamp = col.Timestamp
+ }
+ }
+
+ s.results[shard] = append(s.results[shard], &vcsinfo.IndexCommit{
+ Index: idx,
+ Hash: hash,
+ Timestamp: timeStamp.Time().UTC(),
+ })
+ return nil
+}
+
+// Finish implements the shardedResults interface.
+func (s *srIndexCommits) Finish(shard uint32) {
+ atomic.AddInt64(&s.retSize, int64(len(s.results)))
+}
+
+// Sorted returns the resulting IndexCommits by Index->TimeStamp->Hash.
+// Using the hash ensures results with identical timestamps are sorted stably.
+func (s *srIndexCommits) Sorted() []*vcsinfo.IndexCommit {
+ // Concatenate the shard results into a single output and sort it.
+ ret := make([]*vcsinfo.IndexCommit, 0, s.retSize)
+ for _, sr := range s.results {
+ ret = append(ret, sr...)
+ }
+ sort.Slice(ret, func(i, j int) bool {
+ return ret[i].Index < ret[j].Index ||
+ ((ret[i].Index == ret[j].Index) && ret[i].Timestamp.Before(ret[j].Timestamp)) ||
+ ((ret[i].Index == ret[j].Index) && ret[i].Timestamp.Equal(ret[j].Timestamp) &&
+ (ret[i].Hash < ret[j].Hash))
+ })
+ return ret
+}
+
+// srTimestampCommits is an adaptation of srIndexCommits that extracts a different
+// column family from a timestamp-based index. Otherwise it is identical.
+type srTimestampCommits struct {
+ *srIndexCommits
+}
+
+// newSRTimestampCommits creates a new instance to receive timestamp based IndexCommits.
+func newSRTimestampCommits(shards uint32) *srTimestampCommits {
+ return &srTimestampCommits{
+ srIndexCommits: newSRIndexCommits(shards),
+ }
+}
+
+// Add implements the shardedResults interface and overrides the Add function in srIndexCommits.
+func (s *srTimestampCommits) Add(shard uint32, row bigtable.Row) error {
+ prefix := cfTsCommit + ":"
+ for _, col := range row[cfTsCommit] {
+ hash := strings.TrimPrefix(col.Column, prefix)
+ timeStamp := col.Timestamp
+
+ // Parse the index
+ idxStr := string(col.Value)
+ idx := parseIndex(idxStr)
+ if idx < 0 {
+ return skerr.Fmt("Unable to parse index key %q. Invalid index", idxStr)
+ }
+
+ s.results[shard] = append(s.results[shard], &vcsinfo.IndexCommit{
+ Hash: hash,
+ Timestamp: timeStamp.Time().UTC(),
+ Index: idx,
+ })
+ }
+ return nil
+}
+
+// rawNodesResults implements the shardedResults interface for rows necessary to build the
+// commit graph. It collects slices of strings, where the first string is the commit hash and
+// all subsequent strings are its parents.
+type rawNodesResult struct {
+ errs []*multierror.Error
+ results [][][]string
+ timeStamps [][]time.Time
+ retSize int64
+}
+
+// newRawNodesResult creates a new
+func newRawNodesResult(shards uint32) *rawNodesResult {
+ return &rawNodesResult{
+ results: make([][][]string, shards),
+ timeStamps: make([][]time.Time, shards),
+ errs: make([]*multierror.Error, shards),
+ }
+}
+
+// rawNodeColPrefix is the prefix of column names.
+const rawNodeColPrefix = cfCommit + ":"
+
+// Add implements the shardedResults interface.
+func (r *rawNodesResult) Add(shard uint32, row bigtable.Row) error {
+ var commitHash string
+ var parents []string
+ var timeStamp bigtable.Timestamp
+ for _, col := range row[cfCommit] {
+ switch strings.TrimPrefix(col.Column, rawNodeColPrefix) {
+ case colHash:
+ commitHash = string(col.Value)
+ timeStamp = col.Timestamp
+ case colParents:
+ if len(col.Value) > 0 {
+ parents = strings.Split(string(col.Value), ":")
+ }
+ }
+ }
+ hp := make([]string, 0, 1+len(parents))
+ hp = append(hp, commitHash)
+ hp = append(hp, parents...)
+ r.results[shard] = append(r.results[shard], hp)
+ r.timeStamps[shard] = append(r.timeStamps[shard], timeStamp.Time())
+ return nil
+}
+
+// Add implements the shardedResults interface.
+func (r *rawNodesResult) Finish(shard uint32) {
+ atomic.AddInt64(&r.retSize, int64(len(r.results)))
+}
+
+// Merge merges the results of all shards into one slice of string slices. These are unordered
+// since structure will be imposed by building a CommitGraph from it.
+func (r *rawNodesResult) Merge() ([][]string, []time.Time) {
+ ret := make([][]string, 0, r.retSize)
+ timeStamps := make([]time.Time, 0, r.retSize)
+ for idx, shardResults := range r.results {
+ ret = append(ret, shardResults...)
+ timeStamps = append(timeStamps, r.timeStamps[idx]...)
+ }
+ return ret, timeStamps
+}
diff --git a/go/gitstore_deprecated/bt_gitstore/types.go b/go/gitstore_deprecated/bt_gitstore/types.go
new file mode 100644
index 0000000..0acdb28
--- /dev/null
+++ b/go/gitstore_deprecated/bt_gitstore/types.go
@@ -0,0 +1,9 @@
+package bt_gitstore
+
+// BTConfig contains the BigTable configuration to define where the repo should be stored.
+type BTConfig struct {
+ ProjectID string
+ InstanceID string
+ TableID string
+ Shards int
+}
diff --git a/go/gitstore_deprecated/graph.go b/go/gitstore_deprecated/graph.go
new file mode 100644
index 0000000..71d1518
--- /dev/null
+++ b/go/gitstore_deprecated/graph.go
@@ -0,0 +1,85 @@
+package gitstore_deprecated
+
+import (
+ "time"
+)
+
+const (
+ // initialGraphSize is the assumed starting number of commits in a repository. Just so we
+ // don't start with an empty data structure when we building or traversing the graph.
+ initialGraphSize = 100000
+)
+
+// BuildGraph takes a rawGraph (a slice where each element contains a commit hash followed by its
+// parents) and returns an instance of CommitGraph.
+// TODO(kjlubick,borenet): can this be replaced by go/git/repograph.Graph ?
+func BuildGraph(rawGraph [][]string, timeStamps []time.Time) *CommitGraph {
+ nodeMap := make(map[string]*Node, len(rawGraph))
+ for idx, rawNode := range rawGraph {
+ hash := rawNode[0]
+ nodeMap[hash] = &Node{
+ Hash: hash,
+ Parents: make([]*Node, len(rawNode)-1),
+ Timestamp: timeStamps[idx],
+ }
+ }
+
+ for _, rawNode := range rawGraph {
+ for idx, p := range rawNode[1:] {
+ nodeMap[rawNode[0]].Parents[idx] = nodeMap[p]
+ }
+ }
+
+ return &CommitGraph{
+ Nodes: nodeMap,
+ }
+}
+
+// CommitGraph contains commits as Nodes that are connected and thus can be traversed.
+// Given a graph a client can retrieve a specific node and traverse the graph like this:
+// // First-parent traversal
+// node := graph.GetNode(someHash)
+// for node != nil {
+// // so something with the commit
+// node = node.Parents[0]
+// }
+//
+type CommitGraph struct {
+ Nodes map[string]*Node
+}
+
+// GetNode returns the node in the graph that corresponds to the given hash or nil
+func (c *CommitGraph) GetNode(hash string) *Node {
+ return c.Nodes[hash]
+}
+
+// Node is a node in the commit graph that contains the commit hash, a timestamp and pointers to
+// its parent nodes. The first parent is the immediate parent in the same branch (like in Git).
+type Node struct {
+ Hash string
+ Timestamp time.Time
+ Parents []*Node
+}
+
+// DescendantChain returns all nodes in the commit graph in the range of
+// (firstAncestor, lastDescendant) where the parameters are both commit hashes.
+// 'firstAncestor' can be "" in which case it will return all ancestors of 'lastDescendant'.
+// 'lastDescendant' must not be empty and must exist in graph or this will panic.
+func (g *CommitGraph) DecendantChain(firstAncestor, lastDescendant string) []*Node {
+ curr := g.Nodes[lastDescendant]
+ ret := make([]*Node, 0, len(g.Nodes))
+ for curr != nil {
+ ret = append(ret, curr)
+ if (len(curr.Parents) == 0) || (curr.Hash == firstAncestor) {
+ break
+ }
+ curr = curr.Parents[0]
+ }
+
+ // Reverse the result
+ for idx := 0; idx < len(ret)/2; idx++ {
+ rightIdx := len(ret) - 1 - idx
+ ret[idx], ret[rightIdx] = ret[rightIdx], ret[idx]
+ }
+ return ret
+}
diff --git a/go/gitstore_deprecated/mocks/GitStore.go b/go/gitstore_deprecated/mocks/GitStore.go
new file mode 100644
index 0000000..c3998bd
--- /dev/null
+++ b/go/gitstore_deprecated/mocks/GitStore.go
@@ -0,0 +1,162 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+ gitstore_deprecated "go.skia.org/infra/go/gitstore_deprecated"
+
+ time "time"
+
+ vcsinfo "go.skia.org/infra/go/vcsinfo"
+)
+
+// GitStore is an autogenerated mock type for the GitStore type
+type GitStore struct {
+ mock.Mock
+}
+
+// Get provides a mock function with given fields: ctx, hashes
+func (_m *GitStore) Get(ctx context.Context, hashes []string) ([]*vcsinfo.LongCommit, error) {
+ ret := _m.Called(ctx, hashes)
+
+ var r0 []*vcsinfo.LongCommit
+ if rf, ok := ret.Get(0).(func(context.Context, []string) []*vcsinfo.LongCommit); ok {
+ r0 = rf(ctx, hashes)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*vcsinfo.LongCommit)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context, []string) error); ok {
+ r1 = rf(ctx, hashes)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// GetBranches provides a mock function with given fields: ctx
+func (_m *GitStore) GetBranches(ctx context.Context) (map[string]*gitstore_deprecated.BranchPointer, error) {
+ ret := _m.Called(ctx)
+
+ var r0 map[string]*gitstore_deprecated.BranchPointer
+ if rf, ok := ret.Get(0).(func(context.Context) map[string]*gitstore_deprecated.BranchPointer); ok {
+ r0 = rf(ctx)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(map[string]*gitstore_deprecated.BranchPointer)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+ r1 = rf(ctx)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// GetGraph provides a mock function with given fields: ctx
+func (_m *GitStore) GetGraph(ctx context.Context) (*gitstore_deprecated.CommitGraph, error) {
+ ret := _m.Called(ctx)
+
+ var r0 *gitstore_deprecated.CommitGraph
+ if rf, ok := ret.Get(0).(func(context.Context) *gitstore_deprecated.CommitGraph); ok {
+ r0 = rf(ctx)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*gitstore_deprecated.CommitGraph)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+ r1 = rf(ctx)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// Put provides a mock function with given fields: ctx, commits
+func (_m *GitStore) Put(ctx context.Context, commits []*vcsinfo.LongCommit) error {
+ ret := _m.Called(ctx, commits)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context, []*vcsinfo.LongCommit) error); ok {
+ r0 = rf(ctx, commits)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// PutBranches provides a mock function with given fields: ctx, branches
+func (_m *GitStore) PutBranches(ctx context.Context, branches map[string]string) error {
+ ret := _m.Called(ctx, branches)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context, map[string]string) error); ok {
+ r0 = rf(ctx, branches)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// RangeByTime provides a mock function with given fields: ctx, start, end, branch
+func (_m *GitStore) RangeByTime(ctx context.Context, start time.Time, end time.Time, branch string) ([]*vcsinfo.IndexCommit, error) {
+ ret := _m.Called(ctx, start, end, branch)
+
+ var r0 []*vcsinfo.IndexCommit
+ if rf, ok := ret.Get(0).(func(context.Context, time.Time, time.Time, string) []*vcsinfo.IndexCommit); ok {
+ r0 = rf(ctx, start, end, branch)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*vcsinfo.IndexCommit)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context, time.Time, time.Time, string) error); ok {
+ r1 = rf(ctx, start, end, branch)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// RangeN provides a mock function with given fields: ctx, startIndex, endIndex, branch
+func (_m *GitStore) RangeN(ctx context.Context, startIndex int, endIndex int, branch string) ([]*vcsinfo.IndexCommit, error) {
+ ret := _m.Called(ctx, startIndex, endIndex, branch)
+
+ var r0 []*vcsinfo.IndexCommit
+ if rf, ok := ret.Get(0).(func(context.Context, int, int, string) []*vcsinfo.IndexCommit); ok {
+ r0 = rf(ctx, startIndex, endIndex, branch)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).([]*vcsinfo.IndexCommit)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context, int, int, string) error); ok {
+ r1 = rf(ctx, startIndex, endIndex, branch)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
diff --git a/go/gitstore_deprecated/mocks/generate.go b/go/gitstore_deprecated/mocks/generate.go
new file mode 100644
index 0000000..38a73f2
--- /dev/null
+++ b/go/gitstore_deprecated/mocks/generate.go
@@ -0,0 +1,3 @@
+package mocks
+
+//go:generate mockery -name GitStore -dir ../ -output .
diff --git a/go/gitstore_deprecated/repo_impl.go b/go/gitstore_deprecated/repo_impl.go
new file mode 100644
index 0000000..6ed33e6
--- /dev/null
+++ b/go/gitstore_deprecated/repo_impl.go
@@ -0,0 +1,107 @@
+package gitstore_deprecated
+
+import (
+ "context"
+ "time"
+
+ "go.skia.org/infra/go/git"
+ "go.skia.org/infra/go/git/repograph"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+// GetRepoGraph returns *repograph.Graph backed by the given GitStore.
+func GetRepoGraph(ctx context.Context, gs GitStore) (*repograph.Graph, error) {
+ ri, err := NewGitStoreRepoImpl(ctx, gs)
+ if err != nil {
+ return nil, err
+ }
+ return repograph.NewWithRepoImpl(ctx, ri)
+}
+
+// gitStoreRepoImpl is an implementation of the repograph.RepoImpl interface
+// which uses a GitStore to interact with a git repo.
+type gitStoreRepoImpl struct {
+ *repograph.MemCacheRepoImpl
+ gs GitStore
+ lastUpdate time.Time
+}
+
+// NewGitStoreRepoImpl returns a repograph.RepoImpl instance which uses the
+// given GitStore.
+func NewGitStoreRepoImpl(ctx context.Context, gs GitStore) (repograph.RepoImpl, error) {
+ rv := &gitStoreRepoImpl{
+ MemCacheRepoImpl: repograph.NewMemCacheRepoImpl(nil, nil),
+ gs: gs,
+ }
+ if err := rv.Update(ctx); err != nil {
+ return nil, err
+ }
+ return rv, nil
+}
+
+// See documentation for repograph.RepoImpl interface.
+func (g *gitStoreRepoImpl) Update(ctx context.Context) error {
+ branchPtrs, err := g.gs.GetBranches(ctx)
+ if err != nil {
+ return skerr.Wrapf(err, "Failed to read branches from GitStore")
+ }
+ branches := make([]*git.Branch, 0, len(branchPtrs))
+ for name, ptr := range branchPtrs {
+ if name != "" {
+ branches = append(branches, &git.Branch{
+ Name: name,
+ Head: ptr.Head,
+ })
+ }
+ }
+
+ from := g.lastUpdate.Add(-10 * time.Minute)
+ now := time.Now()
+ to := now.Add(time.Second)
+ indexCommits, err := g.gs.RangeByTime(ctx, from, to, "")
+ if err != nil {
+ return skerr.Wrapf(err, "Failed to read IndexCommits from GitStore")
+ }
+ hashes := make([]string, 0, len(indexCommits))
+ for _, c := range indexCommits {
+ hashes = append(hashes, c.Hash)
+ }
+ commits, err := g.gs.Get(ctx, hashes)
+ if err != nil {
+ return skerr.Wrapf(err, "Failed to read LongCommits from GitStore")
+ }
+ commitsMap := make(map[string]*vcsinfo.LongCommit, len(commits))
+ for _, c := range commits {
+ commitsMap[c.Hash] = c
+ }
+
+ g.lastUpdate = now
+ g.BranchList = branches
+ g.Commits = commitsMap
+ return nil
+}
+
+// See documentation for repograph.RepoImpl interface.
+func (g *gitStoreRepoImpl) Details(ctx context.Context, hash string) (*vcsinfo.LongCommit, error) {
+ d, err := g.MemCacheRepoImpl.Details(ctx, hash)
+ if err == nil {
+ return d, nil
+ }
+ // Update() should have pre-fetched all of the commits for us, so we
+ // shouldn't have hit this code. Log a warning and fall back to
+ // retrieving the commit from GitStore.
+ sklog.Warningf("Commit %q not found in results; performing explicit lookup.", hash)
+ got, err := g.gs.Get(ctx, []string{hash})
+ if err != nil {
+ return nil, skerr.Wrapf(err, "Failed to read commit %s from GitStore", hash)
+ }
+ for _, c := range got {
+ if c == nil {
+ return nil, skerr.Fmt("Commit %s not present in GitStore.", hash)
+ }
+ g.Commits[c.Hash] = c
+ }
+ return got[0], nil
+}
diff --git a/go/gitstore_deprecated/repo_impl_test.go b/go/gitstore_deprecated/repo_impl_test.go
new file mode 100644
index 0000000..28fad31
--- /dev/null
+++ b/go/gitstore_deprecated/repo_impl_test.go
@@ -0,0 +1,202 @@
+package gitstore_deprecated
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ assert "github.com/stretchr/testify/require"
+ "go.skia.org/infra/go/git"
+ "go.skia.org/infra/go/git/repograph"
+ repograph_shared_tests "go.skia.org/infra/go/git/repograph/shared_tests"
+ git_testutils "go.skia.org/infra/go/git/testutils"
+ "go.skia.org/infra/go/skerr"
+ "go.skia.org/infra/go/testutils/unittest"
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+// testGitStore is an in-memory GitStore implementation used for testing
+// gitStoreRepoImpl. It is incomplete and does not support indexed commits
+// at all.
+type testGitStore struct {
+ // commits maps commit hashes to LongCommits.
+ commits map[string]*vcsinfo.LongCommit
+ // branches maps branch names to the commit hashes of their HEADs.
+ branches map[string]string
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) Put(ctx context.Context, commits []*vcsinfo.LongCommit) error {
+ for _, c := range commits {
+ gs.commits[c.Hash] = c
+ }
+ return nil
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) Get(ctx context.Context, hashes []string) ([]*vcsinfo.LongCommit, error) {
+ rv := make([]*vcsinfo.LongCommit, len(hashes))
+ for idx, h := range hashes {
+ rv[idx] = gs.commits[h]
+ }
+ return rv, nil
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) PutBranches(ctx context.Context, branches map[string]string) error {
+ for name, hash := range branches {
+ if hash == DELETE_BRANCH {
+ delete(gs.branches, name)
+ } else {
+ gs.branches[name] = hash
+ }
+ }
+ return nil
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) GetBranches(ctx context.Context) (map[string]*BranchPointer, error) {
+ rv := make(map[string]*BranchPointer, len(gs.branches))
+ for name, hash := range gs.branches {
+ rv[name] = &BranchPointer{
+ Head: hash,
+ }
+ }
+ return rv, nil
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) RangeN(ctx context.Context, startIndex, endIndex int, branch string) ([]*vcsinfo.IndexCommit, error) {
+ return nil, skerr.Fmt("RangeN not implemented for testGitStore.")
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) RangeByTime(ctx context.Context, start, end time.Time, branch string) ([]*vcsinfo.IndexCommit, error) {
+ if branch != "" {
+ return nil, skerr.Fmt("RangeByTime not implemented for single branches in testGitStore.")
+ }
+ rv := make([]*vcsinfo.IndexCommit, 0, len(gs.commits))
+ for _, c := range gs.commits {
+ if c.Timestamp.Before(end) && !c.Timestamp.Before(start) {
+ rv = append(rv, &vcsinfo.IndexCommit{
+ Hash: c.Hash,
+ Timestamp: c.Timestamp,
+ })
+ }
+ }
+ return rv, nil
+}
+
+// See documentation for GitStore interface.
+func (gs *testGitStore) GetGraph(ctx context.Context) (*CommitGraph, error) {
+ return nil, skerr.Fmt("GetGraph not implemented for testGitStore.")
+}
+
+// gitstoreRefresher is an implementation of repograph_shared_tests.RepoImplRefresher
+// used for testing a GitStore.
+type gitstoreRefresher struct {
+ gs GitStore
+ repo git.GitDir
+ t *testing.T
+}
+
+func newGitstoreUpdater(t *testing.T, gs GitStore, gb *git_testutils.GitBuilder) repograph_shared_tests.RepoImplRefresher {
+ return &gitstoreRefresher{
+ gs: gs,
+ repo: git.GitDir(gb.Dir()),
+ t: t,
+ }
+}
+
+func (u *gitstoreRefresher) Refresh(commits ...*vcsinfo.LongCommit) {
+ ctx := context.Background()
+ // Add the commits.
+ assert.NoError(u.t, u.gs.Put(ctx, commits))
+ branches, err := u.repo.Branches(ctx)
+ assert.NoError(u.t, err)
+ putBranches := make(map[string]string, len(branches))
+ for _, branch := range branches {
+ putBranches[branch.Name] = branch.Head
+ }
+ oldBranches, err := u.gs.GetBranches(ctx)
+ assert.NoError(u.t, err)
+ for name := range oldBranches {
+ if name == "" {
+ continue
+ }
+ if _, ok := putBranches[name]; !ok {
+ putBranches[name] = DELETE_BRANCH
+ }
+ }
+ assert.NoError(u.t, u.gs.PutBranches(ctx, putBranches))
+}
+
+// setupGitStore performs common setup for GitStore based Graphs.
+func setupGitStore(t *testing.T) (context.Context, *git_testutils.GitBuilder, *repograph.Graph, repograph_shared_tests.RepoImplRefresher, func()) {
+ ctx, g, cleanup := repograph_shared_tests.CommonSetup(t)
+
+ gs := &testGitStore{
+ commits: map[string]*vcsinfo.LongCommit{},
+ branches: map[string]string{},
+ }
+ ud := newGitstoreUpdater(t, gs, g)
+ repo, err := GetRepoGraph(ctx, gs)
+ assert.NoError(t, err)
+ return ctx, g, repo, ud, cleanup
+}
+
+func TestGraphWellFormedGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestGraphWellFormed(t, ctx, g, repo, ud)
+}
+
+func TestRecurseGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRecurse(t, ctx, g, repo, ud)
+}
+
+func TestRecurseAllBranchesGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRecurseAllBranches(t, ctx, g, repo, ud)
+}
+
+func TestLogLinearGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestLogLinear(t, ctx, g, repo, ud)
+}
+
+func TestUpdateHistoryChangedGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestUpdateHistoryChanged(t, ctx, g, repo, ud)
+}
+
+func TestUpdateAndReturnCommitDiffsGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestUpdateAndReturnCommitDiffs(t, ctx, g, repo, ud)
+}
+
+func TestRevListGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestRevList(t, ctx, g, repo, ud)
+}
+
+func TestBranchMembershipGitStore(t *testing.T) {
+ unittest.LargeTest(t)
+ ctx, g, repo, ud, cleanup := setupGitStore(t)
+ defer cleanup()
+ repograph_shared_tests.TestBranchMembership(t, ctx, g, repo, ud)
+}
diff --git a/go/gitstore_deprecated/testutils/bt_testutils.go b/go/gitstore_deprecated/testutils/bt_testutils.go
new file mode 100644
index 0000000..5fe5496
--- /dev/null
+++ b/go/gitstore_deprecated/testutils/bt_testutils.go
@@ -0,0 +1,141 @@
+package testutils
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "go.skia.org/infra/go/bt"
+ "go.skia.org/infra/go/git/gitinfo"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
+ "go.skia.org/infra/go/sklog"
+ "go.skia.org/infra/go/sktest"
+ "go.skia.org/infra/go/timer"
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+const (
+ concurrentWrites = 1000
+)
+
+var (
+ btConf = &bt_gitstore.BTConfig{
+ ProjectID: "skia-public",
+ InstanceID: "staging",
+ TableID: bt_gitstore.TESTING_TABLE_ID,
+ }
+)
+
+// SetupAndLoadBTGitStore loads the Git repo in repoDir into the Gitstore. It assumes that the
+// repo is checked out. repoURL is only used for creating the GitStore.
+func SetupAndLoadBTGitStore(t sktest.TestingT, repoURL, repoDir string, load bool) ([]*vcsinfo.IndexCommit, []*vcsinfo.LongCommit, *bt_gitstore.BigTableGitStore) {
+ if load {
+ // Delete the tables.
+ assert.NoError(t, bt.DeleteTables(btConf.ProjectID, btConf.InstanceID, btConf.TableID))
+ assert.NoError(t, bt_gitstore.InitBT(btConf))
+ }
+
+ // Get a new gitstore.
+ gitStore, err := bt_gitstore.New(context.TODO(), btConf, repoURL)
+ assert.NoError(t, err)
+
+ // Get the commits of the last ~20 years and load them into the GitStore
+ timeDelta := time.Hour * 24 * 365 * 20
+ tLoad := timer.New("Loading all commits")
+ indexCommits, longCommits := loadGitRepo(t, repoDir, gitStore, timeDelta, load)
+ tLoad.Stop()
+
+ return indexCommits, longCommits, gitStore
+}
+
+type commitInfo struct {
+ commits []*vcsinfo.LongCommit
+ indices []int
+}
+
+func loadGitRepo(t sktest.TestingT, repoDir string, gitStore gitstore_deprecated.GitStore, timeDelta time.Duration, load bool) ([]*vcsinfo.IndexCommit, []*vcsinfo.LongCommit) {
+ ctx := context.TODO()
+ commitCh := make(chan *commitInfo, 10)
+ indexCommits, branches := iterateCommits(t, repoDir, concurrentWrites, commitCh, timeDelta)
+ longCommits := make([]*vcsinfo.LongCommit, 0, len(indexCommits))
+
+ for ci := range commitCh {
+ assert.True(t, len(ci.commits) > 0)
+ longCommits = append(longCommits, ci.commits...)
+ if load {
+ // Add the commits.
+ putT := timer.New(fmt.Sprintf("Put %d commits.", len(ci.commits)))
+ assert.NoError(t, gitStore.Put(ctx, ci.commits))
+ putT.Stop()
+ }
+ }
+
+ for name, head := range branches {
+ details, err := gitStore.Get(ctx, []string{head})
+ assert.NoError(t, err)
+ if details[0] == nil {
+ delete(branches, name)
+ } else {
+ sklog.Infof("Found branches: %40s : %s", name, head)
+ }
+ }
+
+ if load {
+ assert.NoError(t, gitStore.PutBranches(ctx, branches))
+ }
+ return indexCommits, longCommits
+}
+
+// iterateCommits returns batches of commits via a channel. It returns all IndexCommits within
+// the given timeDelta.
+func iterateCommits(t sktest.TestingT, repoDir string, maxCount int, targetCh chan<- *commitInfo, timeDelta time.Duration) ([]*vcsinfo.IndexCommit, map[string]string) {
+ gitInfo, err := gitinfo.NewGitInfo(context.TODO(), repoDir, false, true)
+ assert.NoError(t, err)
+
+ start := time.Now().Add(-timeDelta)
+ indexCommits := gitInfo.Range(start, time.Now().Add(time.Hour))
+ sklog.Infof("Index commits: %d", len(indexCommits))
+
+ gitBranches, err := gitInfo.GetBranches(context.TODO())
+ assert.NoError(t, err)
+
+ // Keep track of the branches.
+ branches := map[string]string{}
+ for _, gb := range gitBranches {
+ branches[gb.Name] = gb.Head
+ }
+
+ go func() {
+ ctx := context.TODO()
+ longCommits := make([]*vcsinfo.LongCommit, 0, maxCount)
+ indices := make([]int, 0, maxCount)
+ retIdx := 0
+ batchTimer := timer.New("Fetching commits starting with 0")
+ for idx, indexCommit := range indexCommits {
+ commitDetails, err := gitInfo.Details(ctx, indexCommit.Hash, false)
+ if err != nil {
+ sklog.Fatalf("Error fetching commits: %s", err)
+ }
+ longCommits = append(longCommits, commitDetails)
+ indices = append(indices, indexCommit.Index)
+ if len(longCommits) >= maxCount || idx == (len(indexCommits)-1) {
+ batchTimer.Stop()
+ targetCh <- &commitInfo{
+ commits: longCommits,
+ indices: indices,
+ }
+ longCommits = make([]*vcsinfo.LongCommit, 0, maxCount)
+ indices = make([]int, 0, maxCount)
+ retIdx = 0
+ batchTimer = timer.New(fmt.Sprintf("Fetching commits starting with %d", idx))
+ } else {
+ retIdx++
+ }
+ }
+ batchTimer.Stop()
+ close(targetCh)
+ }()
+ return indexCommits, branches
+}
diff --git a/go/gitstore_deprecated/types.go b/go/gitstore_deprecated/types.go
new file mode 100644
index 0000000..9edb020
--- /dev/null
+++ b/go/gitstore_deprecated/types.go
@@ -0,0 +1,93 @@
+package gitstore_deprecated
+
+import (
+ "context"
+ "time"
+
+ "go.skia.org/infra/go/vcsinfo"
+)
+
+const (
+ // DELETE_BRANCH is a placeholder which can be used as a value in the
+ // branch map passed to GitStore.PutBranches to signify that the branch
+ // should be deleted.
+ DELETE_BRANCH = "@DELETE"
+
+ ALL_BRANCHES = ""
+)
+
+// GitStore defines the functions of a data store for Git metadata (aka vcsinfo.LongCommit)
+// Each GitStore instance relates to one repository that is defined in the constructor of the
+// implementation.
+type GitStore interface {
+ // Put stores the given commits. They can be retrieved in order of timestamps by using
+ // RangeByTime or RangeN (no topological ordering).
+ Put(ctx context.Context, commits []*vcsinfo.LongCommit) error
+
+ // Get retrieves the commits identified by 'hashes'. The return value will always have the
+ // length of the input value and the results will line up by index. If a commit does not exist
+ // the corresponding entry in the result is nil.
+ // The function will only return an error if the retrieval operation (the I/O) fails, not
+ // if the given hashes do not exist or are invalid.
+ Get(ctx context.Context, hashes []string) ([]*vcsinfo.LongCommit, error)
+
+ // PutBranches updates branches in the repository. It writes indices for the branches so they
+ // can be retrieved via RangeN and RangeByTime. These are ordered in toplogical order with only
+ // first-parents included.
+ // 'branches' maps branchName -> commit_hash to indicate the head of a branch. The store then
+ // calculates the commits of the branch and updates the indices accordingly. Branches which
+ // already exist in the GitStore are not removed if not present in 'branches'; if DELETE_BRANCH
+ // string is used as the head instead of a commit hash, then the branch is removed.
+ PutBranches(ctx context.Context, branches map[string]string) error
+
+ // GetBranches returns the current branches in the store. It maps[branchName]->BranchPointer.
+ // A BranchPointer contains the HEAD commit and also the Index of the HEAD commit, which is
+ // usually the total number of commits in the branch minus 1.
+ GetBranches(ctx context.Context) (map[string]*BranchPointer, error)
+
+ // RangeN returns all commits in the half open index range [startIndex, endIndex).
+ // Thus not including endIndex. It returns the commits in the given branch sorted in ascending
+ // order by Index and the commits are topologically sorted only including first-parent commits.
+ RangeN(ctx context.Context, startIndex, endIndex int, branch string) ([]*vcsinfo.IndexCommit, error)
+
+ // RangeByTime returns all commits in the half open time range [start, end). Thus not
+ // including commits at 'end' time.
+ // Caveat: The returned results will match the requested range, but will be sorted by Index.
+ // So if the timestamps within a commit are not in order they will be unordered in the results.
+ RangeByTime(ctx context.Context, start, end time.Time, branch string) ([]*vcsinfo.IndexCommit, error)
+
+ // GetGraph returns the commit graph of the entire repository.
+ GetGraph(ctx context.Context) (*CommitGraph, error)
+}
+
+// GitStoreBased is an interface that tags an object as being based on GitStore and the
+// underlying GitStore instance can be retrieved. e.g.
+//
+// if gsb, ok := someInstance.(GitStoreBased); ok {
+// gitStore := gsb.GetGitStore()
+// ... do something with gitStore
+// }
+//
+type GitStoreBased interface {
+ // GetGitStore returns the underlying GitStore instances.
+ GetGitStore() GitStore
+}
+
+// BranchPointer captures the HEAD of a branch and the index of that commit.
+type BranchPointer struct {
+ Head string
+ Index int
+}
+
+// RepoInfo contains information about one repo in the GitStore.
+type RepoInfo struct {
+ // Numeric id of the repo. This is unique within all repos in a BT table. This ID is uniquely
+ // assigned whenever a new repo is added.
+ ID int64
+
+ // RepoURL contains the URL of the repo as returned by git.NormalizeURL(...).
+ RepoURL string
+
+ // Branches contain all the branches in the repo, mapping branch_name -> branch_pointer.
+ Branches map[string]*BranchPointer
+}
diff --git a/go/ingestion/helpers.go b/go/ingestion/helpers.go
index 15d77cf..e0ecf8e 100644
--- a/go/ingestion/helpers.go
+++ b/go/ingestion/helpers.go
@@ -20,7 +20,7 @@
"go.skia.org/infra/go/gcs"
"go.skia.org/infra/go/git/gitinfo"
"go.skia.org/infra/go/gitiles"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/sharedconfig"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
diff --git a/go/vcsinfo/bt_vcs/bt_vcs.go b/go/vcsinfo/bt_vcs/bt_vcs.go
index e0a014d..73aac6e 100644
--- a/go/vcsinfo/bt_vcs/bt_vcs.go
+++ b/go/vcsinfo/bt_vcs/bt_vcs.go
@@ -12,8 +12,8 @@
"go.skia.org/infra/go/depot_tools"
"go.skia.org/infra/go/eventbus"
"go.skia.org/infra/go/gitiles"
- "go.skia.org/infra/go/gitstore"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/util"
@@ -38,13 +38,13 @@
// BigTableVCS implements the vcsinfo.VCS interface based on a BT-backed GitStore.
type BigTableVCS struct {
- gitStore gitstore.GitStore
+ gitStore gitstore_deprecated.GitStore
repo *gitiles.Repo
defaultBranch string
secondaryVCS vcsinfo.VCS
secondaryExtractor depot_tools.DEPSExtractor
- branchInfo *gitstore.BranchPointer
+ branchInfo *gitstore_deprecated.BranchPointer
// This mutex protects detailsCache and indexCommits
mutex sync.RWMutex
@@ -57,7 +57,7 @@
// gittiles.Repo to retrieve files. Each instance provides an interface to one branch.
// If defaultBranch is gitstore.ALL_BRANCHES all commits in the repository are considered.
// The instances of gitiles.Repo is only used to fetch files.
-func New(ctx context.Context, gitStore gitstore.GitStore, defaultBranch string, repo *gitiles.Repo) (*BigTableVCS, error) {
+func New(ctx context.Context, gitStore gitstore_deprecated.GitStore, defaultBranch string, repo *gitiles.Repo) (*BigTableVCS, error) {
if gitStore == nil {
return nil, errors.New("Cannot have nil gitStore")
}
@@ -91,7 +91,7 @@
// Check if we need to pull across all branches.
targetBranch := b.defaultBranch
if allBranches {
- targetBranch = gitstore.ALL_BRANCHES
+ targetBranch = gitstore_deprecated.ALL_BRANCHES
}
// Simulate a pull by fetching the latest head of the target branch.
@@ -283,12 +283,12 @@
// getBranchInfo determines which branches contain the given commit 'c'.
// This function can potentially spawn a huge number of goroutines (one per branch).
-func (b *BigTableVCS) getBranchInfo(ctx context.Context, c *vcsinfo.LongCommit, allBranches map[string]*gitstore.BranchPointer) (map[string]bool, error) {
+func (b *BigTableVCS) getBranchInfo(ctx context.Context, c *vcsinfo.LongCommit, allBranches map[string]*gitstore_deprecated.BranchPointer) (map[string]bool, error) {
ret := make(map[string]bool, len(allBranches))
var mutex sync.Mutex
var egroup errgroup.Group
for branchName := range allBranches {
- if branchName != gitstore.ALL_BRANCHES {
+ if branchName != gitstore_deprecated.ALL_BRANCHES {
func(branchName string) {
egroup.Go(func() error {
// Since we cannot look up a commit in a branch directly we query for all commits that
@@ -401,8 +401,8 @@
return foundCommit, nil
}
-// GetGitStore implements the gitstore.GitStoreBased interface
-func (b *BigTableVCS) GetGitStore() gitstore.GitStore {
+// GetGitStore implements the gitstore_deprecated.GitStoreBased interface
+func (b *BigTableVCS) GetGitStore() gitstore_deprecated.GitStore {
return b.gitStore
}
diff --git a/go/vcsinfo/bt_vcs/bt_vcs_test.go b/go/vcsinfo/bt_vcs/bt_vcs_test.go
index a3018d4..cc5c4a7 100644
--- a/go/vcsinfo/bt_vcs/bt_vcs_test.go
+++ b/go/vcsinfo/bt_vcs/bt_vcs_test.go
@@ -2,18 +2,16 @@
import (
"context"
- "io/ioutil"
"testing"
"time"
"github.com/stretchr/testify/mock"
assert "github.com/stretchr/testify/require"
"go.skia.org/infra/go/gitiles"
- "go.skia.org/infra/go/gitstore"
- "go.skia.org/infra/go/gitstore/mocks"
- gs_testutils "go.skia.org/infra/go/gitstore/testutils"
+ "go.skia.org/infra/go/gitstore_deprecated"
+ "go.skia.org/infra/go/gitstore_deprecated/mocks"
+ gs_testutils "go.skia.org/infra/go/gitstore_deprecated/testutils"
"go.skia.org/infra/go/testutils/unittest"
- "go.skia.org/infra/go/util"
"go.skia.org/infra/go/vcsinfo"
vcs_testutils "go.skia.org/infra/go/vcsinfo/testutils"
)
@@ -39,14 +37,14 @@
func TestBranchInfo(t *testing.T) {
unittest.LargeTest(t)
- vcs, gitStore, cleanup := setupVCSLocalRepo(t, gitstore.ALL_BRANCHES)
+ vcs, gitStore, cleanup := setupVCSLocalRepo(t, gitstore_deprecated.ALL_BRANCHES)
defer cleanup()
branchPointers, err := gitStore.GetBranches(context.Background())
assert.NoError(t, err)
branches := []string{}
for branchName := range branchPointers {
- if branchName != gitstore.ALL_BRANCHES {
+ if branchName != gitstore_deprecated.ALL_BRANCHES {
branches = append(branches, branchName)
}
}
@@ -260,18 +258,12 @@
}
// setupVCSLocalRepo loads the test repo into a new GitStore and returns an instance of vcsinfo.VCS.
-func setupVCSLocalRepo(t *testing.T, branch string) (vcsinfo.VCS, gitstore.GitStore, func()) {
+func setupVCSLocalRepo(t *testing.T, branch string) (vcsinfo.VCS, gitstore_deprecated.GitStore, func()) {
repoDir, cleanup := vcs_testutils.InitTempRepo()
- wd, err := ioutil.TempDir("", "")
+ _, _, btgs := gs_testutils.SetupAndLoadBTGitStore(t, localRepoURL, repoDir, true)
+ vcs, err := New(context.Background(), btgs, branch, nil)
assert.NoError(t, err)
- ctx := context.Background()
- _, _, btgs := gs_testutils.SetupAndLoadBTGitStore(t, ctx, wd, "file://"+repoDir, true)
- vcs, err := New(ctx, btgs, branch, nil)
- assert.NoError(t, err)
- return vcs, btgs, func() {
- util.RemoveAll(wd)
- cleanup()
- }
+ return vcs, btgs, cleanup
}
func startWithEmptyCache(mg *mocks.GitStore) {
@@ -363,8 +355,8 @@
}
}
-func makeTestBranchPointerMap() map[string]*gitstore.BranchPointer {
- return map[string]*gitstore.BranchPointer{
+func makeTestBranchPointerMap() map[string]*gitstore_deprecated.BranchPointer {
+ return map[string]*gitstore_deprecated.BranchPointer{
"master": {
Head: "master",
Index: 3,
diff --git a/golden/cmd/gold_ingestion/main.go b/golden/cmd/gold_ingestion/main.go
index 635324f..012a967 100644
--- a/golden/cmd/gold_ingestion/main.go
+++ b/golden/cmd/gold_ingestion/main.go
@@ -22,7 +22,7 @@
"go.skia.org/infra/go/eventbus"
"go.skia.org/infra/go/firestore"
"go.skia.org/infra/go/gevent"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/ingestion"
"go.skia.org/infra/go/ingestion/fs_ingestionstore"
diff --git a/golden/cmd/skiacorrectness/main.go b/golden/cmd/skiacorrectness/main.go
index 547c2bd..dd921ec 100644
--- a/golden/cmd/skiacorrectness/main.go
+++ b/golden/cmd/skiacorrectness/main.go
@@ -33,7 +33,7 @@
"go.skia.org/infra/go/gevent"
"go.skia.org/infra/go/git/gitinfo"
"go.skia.org/infra/go/gitiles"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/issues"
"go.skia.org/infra/go/login"
diff --git a/status/go/status/main.go b/status/go/status/main.go
index 8b3ad85..2f5d608 100644
--- a/status/go/status/main.go
+++ b/status/go/status/main.go
@@ -32,7 +32,7 @@
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/git/repograph"
"go.skia.org/infra/go/gitauth"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/login"
"go.skia.org/infra/go/metrics2"
diff --git a/task_scheduler/go/task_scheduler/main.go b/task_scheduler/go/task_scheduler/main.go
index eabe3d8..dc810b8 100644
--- a/task_scheduler/go/task_scheduler/main.go
+++ b/task_scheduler/go/task_scheduler/main.go
@@ -28,7 +28,7 @@
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/git/repograph"
"go.skia.org/infra/go/gitauth"
- "go.skia.org/infra/go/gitstore/bt_gitstore"
+ "go.skia.org/infra/go/gitstore_deprecated/bt_gitstore"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/human"
"go.skia.org/infra/go/isolate"