diff options
author | Toon Claes <toon@gitlab.com> | 2022-08-16 18:30:33 +0300 |
---|---|---|
committer | Toon Claes <toon@gitlab.com> | 2022-08-16 18:30:33 +0300 |
commit | 8263b2f7f00bb5197a1b97f2a420c5662af8e40c (patch) | |
tree | 3bdad03b19eb24a475e17d92f745fd93e1ee0ee6 /internal | |
parent | 4128e23ac5533b0f3548d4a627e8a3e19718be5f (diff) | |
parent | cffaa1610ff5f2ac8d2b78a5f74d2f4e4a90a5bb (diff) |
Merge branch 'pks-gitpipe-sha256' into 'master'
gitpipe: Support SHA256 object hash
See merge request gitlab-org/gitaly!4798
Diffstat (limited to 'internal')
-rw-r--r-- | internal/git/gitpipe/catfile_info.go | 10 | ||||
-rw-r--r-- | internal/git/gitpipe/catfile_info_test.go | 98 | ||||
-rw-r--r-- | internal/git/gitpipe/catfile_object_test.go | 59 | ||||
-rw-r--r-- | internal/git/gitpipe/diff_tree.go | 2 | ||||
-rw-r--r-- | internal/git/gitpipe/diff_tree_test.go | 240 | ||||
-rw-r--r-- | internal/git/gitpipe/ls_tree.go | 10 | ||||
-rw-r--r-- | internal/git/gitpipe/ls_tree_test.go | 217 | ||||
-rw-r--r-- | internal/git/gitpipe/pipeline_test.go | 156 | ||||
-rw-r--r-- | internal/git/gitpipe/revision_test.go | 397 | ||||
-rw-r--r-- | internal/git/gitpipe/testhelper_test.go | 10 |
10 files changed, 694 insertions, 505 deletions
diff --git a/internal/git/gitpipe/catfile_info.go b/internal/git/gitpipe/catfile_info.go index 8b4792378..3aec77cca 100644 --- a/internal/git/gitpipe/catfile_info.go +++ b/internal/git/gitpipe/catfile_info.go @@ -185,6 +185,14 @@ func CatfileInfoAllObjects( go func() { defer close(resultChan) + objectHash, err := repo.ObjectHash(ctx) + if err != nil { + sendCatfileInfoResult(ctx, resultChan, CatfileInfoResult{ + err: fmt.Errorf("detecting object hash: %w", err), + }) + return + } + var stderr bytes.Buffer cmd, err := repo.Exec(ctx, git.SubCmd{ Name: "cat-file", @@ -204,7 +212,7 @@ func CatfileInfoAllObjects( reader := bufio.NewReader(cmd) for { - objectInfo, err := catfile.ParseObjectInfo(git.ObjectHashSHA1, reader) + objectInfo, err := catfile.ParseObjectInfo(objectHash, reader) if err != nil { if errors.Is(err, io.EOF) { break diff --git a/internal/git/gitpipe/catfile_info_test.go b/internal/git/gitpipe/catfile_info_test.go index e9af6cfd7..94cf6a78d 100644 --- a/internal/git/gitpipe/catfile_info_test.go +++ b/internal/git/gitpipe/catfile_info_test.go @@ -1,15 +1,13 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( + "bytes" "context" "errors" "fmt" "testing" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" @@ -18,23 +16,27 @@ import ( "google.golang.org/grpc/metadata" ) -const ( - lfsPointer1 = "0c304a93cb8430108629bbbcaa27db3343299bc0" - lfsPointer2 = "f78df813119a79bfbe0442ab92540a61d3ab7ff3" - lfsPointer3 = "bab31d249f78fba464d1b75799aad496cc07fa3b" - lfsPointer4 = "125fcc9f6e33175cb278b9b2809154d2535fe19f" -) - func TestCatfileInfo(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, }) repo := localrepo.NewTestRepo(t, cfg, repoProto) + blobA := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("a"), 133)) + blobB := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("b"), 127)) + blobC := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("c"), 127)) + blobD := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("d"), 129)) + + blobID := gittest.WriteBlob(t, cfg, repoPath, []byte("contents")) + treeID := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "branch-test.txt", Mode: "100644", OID: blobID}, + }) + for _, tc := range []struct { desc string revlistInputs []RevisionResult @@ -45,36 +47,36 @@ func TestCatfileInfo(t *testing.T) { { desc: "single blob", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, }, }, { desc: "multiple blobs", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer2}, - {OID: lfsPointer3}, - {OID: lfsPointer4}, + {OID: blobA}, + {OID: blobB}, + {OID: blobC}, + {OID: blobD}, }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer3, Type: "blob", Size: 127}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer4, Type: "blob", Size: 129}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobC, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobD, Type: "blob", Size: 129}}, }, }, { desc: "object name", revlistInputs: []RevisionResult{ - {OID: "b95c0fad32f4361845f91d9ce4c1721b52b82793"}, - {OID: "93e123ac8a3e6a0b600953d7598af629dec7b735", ObjectName: []byte("branch-test.txt")}, + {OID: treeID}, + {OID: blobID, ObjectName: []byte("branch-test.txt")}, }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: "b95c0fad32f4361845f91d9ce4c1721b52b82793", Type: "tree", Size: 43}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: "93e123ac8a3e6a0b600953d7598af629dec7b735", Type: "blob", Size: 59}, ObjectName: []byte("branch-test.txt")}, + {ObjectInfo: &catfile.ObjectInfo{Oid: treeID, Type: "tree", Size: hashDependentObjectSize(43, 55)}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobID, Type: "blob", Size: 8}, ObjectName: []byte("branch-test.txt")}, }, }, { @@ -87,20 +89,20 @@ func TestCatfileInfo(t *testing.T) { { desc: "mixed valid and invalid revision", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, {OID: "invalidobjectid"}, - {OID: lfsPointer2}, + {OID: blobB}, }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, }, expectedErr: errors.New("retrieving object info for \"invalidobjectid\": object not found"), }, { desc: "skip everything", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer2}, + {OID: blobA}, + {OID: blobB}, }, opts: []CatfileInfoOption{ WithSkipCatfileInfoResult(func(*catfile.ObjectInfo) bool { return true }), @@ -109,30 +111,30 @@ func TestCatfileInfo(t *testing.T) { { desc: "skip one", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer2}, + {OID: blobA}, + {OID: blobB}, }, opts: []CatfileInfoOption{ WithSkipCatfileInfoResult(func(objectInfo *catfile.ObjectInfo) bool { - return objectInfo.Oid == lfsPointer1 + return objectInfo.Oid == blobA }), }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}, }, }, { desc: "skip nothing", revlistInputs: []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer2}, + {OID: blobA}, + {OID: blobB}, }, opts: []CatfileInfoOption{ WithSkipCatfileInfoResult(func(*catfile.ObjectInfo) bool { return false }), }, expectedResults: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}, }, }, } { @@ -175,15 +177,15 @@ func TestCatfileInfo(t *testing.T) { defer objectInfoReaderCancel() it, err := CatfileInfo(ctx, objectInfoReader, NewRevisionIterator(ctx, []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer1}, + {OID: blobA}, + {OID: blobA}, })) require.NoError(t, err) require.True(t, it.Next()) require.NoError(t, it.Err()) require.Equal(t, CatfileInfoResult{ - ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}, + ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}, }, it.Result()) cancel() @@ -217,7 +219,7 @@ func TestCatfileInfo(t *testing.T) { // not flushed after every object this means that the request is currently // outstanding. <-nextCh - inputCh <- git.ObjectID(lfsPointer1) + inputCh <- blobA // Wait for the pipeline to request the next object. <-nextCh @@ -233,7 +235,7 @@ func TestCatfileInfo(t *testing.T) { cancel() // Now we queue another request that should cause the pipeline to fail. - inputCh <- git.ObjectID(lfsPointer1) + inputCh <- blobA // Verify whether we can receive any more objects via the iterator. This should // fail because the context got cancelled, but in any case it shouldn't block. Note @@ -258,7 +260,7 @@ func TestCatfileInfo(t *testing.T) { defer cancel() input := []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, } it, err := CatfileInfo(ctx, objectInfoReader, NewRevisionIterator(ctx, input)) @@ -279,6 +281,8 @@ func TestCatfileInfo(t *testing.T) { } func TestCatfileInfoAllObjects(t *testing.T) { + t.Parallel() + cfg := testcfg.Build(t) ctx := testhelper.Context(t) @@ -297,8 +301,8 @@ func TestCatfileInfoAllObjects(t *testing.T) { actualObjects := []CatfileInfoResult{ {ObjectInfo: &catfile.ObjectInfo{Oid: blob1, Type: "blob", Size: 6}}, {ObjectInfo: &catfile.ObjectInfo{Oid: blob2, Type: "blob", Size: 6}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: tree, Type: "tree", Size: 34}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: commit, Type: "commit", Size: 177}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: tree, Type: "tree", Size: hashDependentObjectSize(34, 46)}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: commit, Type: "commit", Size: hashDependentObjectSize(177, 201)}}, } t.Run("successful", func(t *testing.T) { diff --git a/internal/git/gitpipe/catfile_object_test.go b/internal/git/gitpipe/catfile_object_test.go index 72c47c1f5..fe4f5d319 100644 --- a/internal/git/gitpipe/catfile_object_test.go +++ b/internal/git/gitpipe/catfile_object_test.go @@ -1,8 +1,7 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( + "bytes" "context" "errors" "fmt" @@ -10,7 +9,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/catfile" "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" @@ -20,15 +18,26 @@ import ( ) func TestCatfileObject(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, }) repo := localrepo.NewTestRepo(t, cfg, repoProto) + blobA := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("a"), 133)) + blobB := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("b"), 127)) + blobC := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("c"), 127)) + blobD := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("d"), 129)) + + blobID := gittest.WriteBlob(t, cfg, repoPath, []byte("contents")) + treeID := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "branch-test.txt", Mode: "100644", OID: blobID}, + }) + for _, tc := range []struct { desc string catfileInfoInputs []CatfileInfoResult @@ -38,36 +47,36 @@ func TestCatfileObject(t *testing.T) { { desc: "single blob", catfileInfoInputs: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}}, }, }, { desc: "multiple blobs", catfileInfoInputs: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer3, Type: "blob", Size: 127}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer4, Type: "blob", Size: 129}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobC, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobD, Type: "blob", Size: 129}}, }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer3, Type: "blob", Size: 127}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer4, Type: "blob", Size: 129}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobC, Type: "blob", Size: 127}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobD, Type: "blob", Size: 129}}}, }, }, { desc: "revlist result with object names", catfileInfoInputs: []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: "b95c0fad32f4361845f91d9ce4c1721b52b82793", Type: "tree", Size: 43}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: "93e123ac8a3e6a0b600953d7598af629dec7b735", Type: "blob", Size: 59}, ObjectName: []byte("branch-test.txt")}, + {ObjectInfo: &catfile.ObjectInfo{Oid: treeID, Type: "tree", Size: hashDependentObjectSize(43, 55)}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobID, Type: "blob", Size: 59}, ObjectName: []byte("branch-test.txt")}, }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "b95c0fad32f4361845f91d9ce4c1721b52b82793", Type: "tree", Size: 43}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "93e123ac8a3e6a0b600953d7598af629dec7b735", Type: "blob", Size: 59}}, ObjectName: []byte("branch-test.txt")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: treeID, Type: "tree", Size: hashDependentObjectSize(43, 55)}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobID, Type: "blob", Size: 8}}, ObjectName: []byte("branch-test.txt")}, }, }, { @@ -135,14 +144,14 @@ func TestCatfileObject(t *testing.T) { defer objectReaderCancel() it, err := CatfileObject(ctx, objectReader, NewCatfileInfoIterator(ctx, []CatfileInfoResult{ - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, - {ObjectInfo: &catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 133}}, + {ObjectInfo: &catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 127}}, })) require.NoError(t, err) require.True(t, it.Next()) require.NoError(t, it.Err()) - require.Equal(t, git.ObjectID(lfsPointer1), it.Result().ObjectID()) + require.Equal(t, blobA, it.Result().ObjectID()) _, err = io.Copy(io.Discard, it.Result()) require.NoError(t, err) @@ -178,7 +187,7 @@ func TestCatfileObject(t *testing.T) { // not flushed after every object this means that the request is currently // outstanding. <-nextCh - inputCh <- git.ObjectID(lfsPointer1) + inputCh <- blobA // Wait for the pipeline to request the next object. <-nextCh @@ -194,7 +203,7 @@ func TestCatfileObject(t *testing.T) { cancel() // Now we queue another request that should cause the pipeline to fail. - inputCh <- git.ObjectID(lfsPointer1) + inputCh <- blobA // Verify whether we can receive any more objects via the iterator. This should // fail because the context got cancelled, but in any case it shouldn't block. Note @@ -219,7 +228,7 @@ func TestCatfileObject(t *testing.T) { defer cancel() input := []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, } it, err := CatfileObject(ctx, objectReader, NewRevisionIterator(ctx, input)) diff --git a/internal/git/gitpipe/diff_tree.go b/internal/git/gitpipe/diff_tree.go index 5caecf55f..cf81a1ba3 100644 --- a/internal/git/gitpipe/diff_tree.go +++ b/internal/git/gitpipe/diff_tree.go @@ -46,7 +46,7 @@ func DiffTreeWithSkip(skipResult func(*RevisionResult) bool) DiffTreeOption { // DiffTree runs git-diff-tree(1) between the two given revisions. The returned // channel will contain the new object IDs listed by this command. For deleted -// files this would be git.ObjectHashSHA1.ZeroOID. Cancelling the context will cause the +// files this would be the all-zeroes object ID. Cancelling the context will cause the // pipeline to be cancelled, too. By default, it will not recurse into subtrees. func DiffTree( ctx context.Context, diff --git a/internal/git/gitpipe/diff_tree_test.go b/internal/git/gitpipe/diff_tree_test.go index f2cac6a38..ca29d509a 100644 --- a/internal/git/gitpipe/diff_tree_test.go +++ b/internal/git/gitpipe/diff_tree_test.go @@ -1,12 +1,12 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( + "bytes" "errors" "testing" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" @@ -14,108 +14,191 @@ import ( ) func TestDiffTree(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ - SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, - }) - repo := localrepo.NewTestRepo(t, cfg, repoProto) - for _, tc := range []struct { - desc string - leftRevision string - rightRevision string - options []DiffTreeOption - expectedResults []RevisionResult - expectedErr error + desc string + setup func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) + options []DiffTreeOption + expectedErr error }{ { - desc: "single file", - leftRevision: "b83d6e391c22777fca1ed3012fce84f633d7fed0", - rightRevision: "4a24d82dbca5c11c61556f3b35ca472b7463187e", - expectedResults: []RevisionResult{ - { - OID: "c60514b6d3d6bf4bec1030f70026e34dfbd69ad5", - ObjectName: []byte("README.md"), - }, + desc: "single file", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "unchanged", Mode: "100644", Content: "unchanged"}, + {Path: "changed", Mode: "100644", Content: "a"}, + }) + + changedBlob := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "unchanged", Mode: "100644", Content: "unchanged"}, + {Path: "changed", Mode: "100644", OID: changedBlob}, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedBlob, ObjectName: []byte("changed")}, + } }, }, { - desc: "single file in subtree without recursive", - leftRevision: "7975be0116940bf2ad4321f79d02a55c5f7779aa", - rightRevision: "1e292f8fedd741b75372e19097c76d327140c312", - expectedResults: []RevisionResult{ - { - OID: "ceb102b8d3f9a95c2eb979213e49f7cc1b23d56e", - ObjectName: []byte("files"), - }, + desc: "single file in subtree without recursive", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "unchanged", + Mode: "100644", + Content: "unchanged", + }, + { + Path: "subtree", + Mode: "040000", + OID: gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "changed", Mode: "100644", Content: "a"}, + }), + }, + }) + + changedSubtree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "changed", Mode: "100644", Content: "b"}, + }) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "unchanged", + Mode: "100644", + Content: "unchanged", + }, + { + Path: "subtree", + Mode: "040000", + OID: changedSubtree, + }, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedSubtree, ObjectName: []byte("subtree")}, + } }, }, { - desc: "single file in subtree with recursive", - leftRevision: "7975be0116940bf2ad4321f79d02a55c5f7779aa", - rightRevision: "1e292f8fedd741b75372e19097c76d327140c312", + desc: "single file in subtree with recursive", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "unchanged", + Mode: "100644", + Content: "unchanged", + }, + { + Path: "subtree", + Mode: "040000", + OID: gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "changed", Mode: "100644", Content: "a"}, + }), + }, + }) + + changedBlob := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "unchanged", + Mode: "100644", + Content: "unchanged", + }, + { + Path: "subtree", + Mode: "040000", + OID: gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "changed", Mode: "100644", OID: changedBlob}, + }), + }, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedBlob, ObjectName: []byte("subtree/changed")}, + } + }, options: []DiffTreeOption{ DiffTreeWithRecursive(), }, - expectedResults: []RevisionResult{ - { - OID: "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - ObjectName: []byte("files/flat/path/correct/content.txt"), - }, - }, }, { - desc: "with submodules", - leftRevision: "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - rightRevision: "5937ac0a7beb003549fc5fd26fc247adbce4a52e", - expectedResults: []RevisionResult{ - { - OID: "efd587ccb47caf5f31fc954edb21f0a713d9ecc3", - ObjectName: []byte(".gitmodules"), - }, - { - OID: "645f6c4c82fd3f5e06f67134450a570b795e55a6", - ObjectName: []byte("gitlab-grack"), - }, + desc: "with submodules", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + submodule := gittest.WriteCommit(t, cfg, repoPath) + + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: ".gitmodules", Mode: "100644", Content: "a"}, + }) + + changedGitmodules := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: ".gitmodules", Mode: "100644", OID: changedGitmodules}, + {Path: "submodule", Mode: "160000", OID: submodule}, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedGitmodules, ObjectName: []byte(".gitmodules")}, + {OID: submodule, ObjectName: []byte("submodule")}, + } }, }, { - desc: "without submodules", - leftRevision: "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - rightRevision: "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + desc: "without submodules", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + submodule := gittest.WriteCommit(t, cfg, repoPath) + + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: ".gitmodules", Mode: "100644", Content: "a"}, + }) + + changedGitmodules := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: ".gitmodules", Mode: "100644", OID: changedGitmodules}, + {Path: "submodule", Mode: "160000", OID: submodule}, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedGitmodules, ObjectName: []byte(".gitmodules")}, + } + }, options: []DiffTreeOption{ DiffTreeWithIgnoreSubmodules(), }, - expectedResults: []RevisionResult{ - { - OID: "efd587ccb47caf5f31fc954edb21f0a713d9ecc3", - ObjectName: []byte(".gitmodules"), - }, - }, }, { - desc: "with skip function", - leftRevision: "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - rightRevision: "5937ac0a7beb003549fc5fd26fc247adbce4a52e", + desc: "with skip function", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "a", Mode: "100644", Content: "1"}, + {Path: "b", Mode: "100644", Content: "2"}, + }) + + changedBlobA := gittest.WriteBlob(t, cfg, repoPath, []byte("x")) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "a", Mode: "100644", OID: changedBlobA}, + {Path: "b", Mode: "100644", Content: "y"}, + }) + + return treeA.Revision(), treeB.Revision(), []RevisionResult{ + {OID: changedBlobA, ObjectName: []byte("a")}, + } + }, options: []DiffTreeOption{ DiffTreeWithSkip(func(r *RevisionResult) bool { - return r.OID == "efd587ccb47caf5f31fc954edb21f0a713d9ecc3" + return bytes.Equal(r.ObjectName, []byte("b")) }), }, - expectedResults: []RevisionResult{ - { - OID: "645f6c4c82fd3f5e06f67134450a570b795e55a6", - ObjectName: []byte("gitlab-grack"), - }, - }, }, { - desc: "invalid revision", - leftRevision: "refs/heads/master", - rightRevision: "refs/heads/does-not-exist", + desc: "invalid revision", + setup: func(t *testing.T, repoPath string) (git.Revision, git.Revision, []RevisionResult) { + gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main")) + return "refs/heads/main", "refs/heads/does-not-exist", nil + }, expectedErr: errors.New("diff-tree pipeline command: exit status 128, stderr: " + "\"fatal: ambiguous argument 'refs/heads/does-not-exist': unknown revision or path not in the working tree.\\n" + "Use '--' to separate paths from revisions, like this:\\n" + @@ -123,7 +206,14 @@ func TestDiffTree(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - it := DiffTree(ctx, repo, tc.leftRevision, tc.rightRevision, tc.options...) + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) + repo := localrepo.NewTestRepo(t, cfg, repoProto) + + leftRevision, rightRevision, expectedResults := tc.setup(t, repoPath) + + it := DiffTree(ctx, repo, leftRevision.String(), rightRevision.String(), tc.options...) var results []RevisionResult for it.Next() { @@ -138,7 +228,7 @@ func TestDiffTree(t *testing.T) { } require.Equal(t, tc.expectedErr, err) - require.Equal(t, tc.expectedResults, results) + require.Equal(t, expectedResults, results) }) } } diff --git a/internal/git/gitpipe/ls_tree.go b/internal/git/gitpipe/ls_tree.go index 5d2ad775f..3c21c1eb0 100644 --- a/internal/git/gitpipe/ls_tree.go +++ b/internal/git/gitpipe/ls_tree.go @@ -56,6 +56,14 @@ func LsTree( go func() { defer close(resultChan) + objectHash, err := repo.ObjectHash(ctx) + if err != nil { + sendRevisionResult(ctx, resultChan, RevisionResult{ + err: fmt.Errorf("detecting object hash: %w", err), + }) + return + } + flags := []git.Option{ git.Flag{Name: "-z"}, } @@ -80,7 +88,7 @@ func LsTree( return } - parser := lstree.NewParser(cmd, git.ObjectHashSHA1) + parser := lstree.NewParser(cmd, objectHash) for { entry, err := parser.NextEntry() if err != nil { diff --git a/internal/git/gitpipe/ls_tree_test.go b/internal/git/gitpipe/ls_tree_test.go index 0a90f0fec..e5a7bdccf 100644 --- a/internal/git/gitpipe/ls_tree_test.go +++ b/internal/git/gitpipe/ls_tree_test.go @@ -1,5 +1,3 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( @@ -7,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" @@ -14,127 +13,161 @@ import ( ) func TestLsTree(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ - SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, - }) - repo := localrepo.NewTestRepo(t, cfg, repoProto) - for _, tc := range []struct { - desc string - revision string - options []LsTreeOption - expectedResults []RevisionResult - expectedErr error + desc string + setup func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) + options []LsTreeOption + expectedErr error }{ { - desc: "initial commit", - revision: "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863", - expectedResults: []RevisionResult{ - {OID: "470ad2fcf1e33798f1afc5781d08e60c40f51e7a", ObjectName: []byte(".gitignore")}, - {OID: "50b27c6518be44c42c4d87966ae2481ce895624c", ObjectName: []byte("LICENSE")}, - {OID: "faaf198af3a36dbf41961466703cc1d47c61d051", ObjectName: []byte("README.md")}, + desc: "initial commit", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + blobA := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + blobB := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + blobC := gittest.WriteBlob(t, cfg, repoPath, []byte("c")) + + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: ".gitignore", Mode: "100644", OID: blobA}, + {Path: "LICENSE", Mode: "100644", OID: blobB}, + {Path: "README.md", Mode: "100644", OID: blobC}, + }) + + return tree.Revision(), []RevisionResult{ + {OID: blobA, ObjectName: []byte(".gitignore")}, + {OID: blobB, ObjectName: []byte("LICENSE")}, + {OID: blobC, ObjectName: []byte("README.md")}, + } }, }, { - desc: "includes submodule", - revision: "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", - expectedResults: []RevisionResult{ - {OID: "470ad2fcf1e33798f1afc5781d08e60c40f51e7a", ObjectName: []byte(".gitignore")}, - {OID: "fdaada1754989978413d618ee1fb1c0469d6a664", ObjectName: []byte(".gitmodules")}, - {OID: "c1788657b95998a2f177a4f86d68a60f2a80117f", ObjectName: []byte("CONTRIBUTING.md")}, - {OID: "50b27c6518be44c42c4d87966ae2481ce895624c", ObjectName: []byte("LICENSE")}, - {OID: "faaf198af3a36dbf41961466703cc1d47c61d051", ObjectName: []byte("README.md")}, - {OID: "409f37c4f05865e4fb208c771485f211a22c4c2d", ObjectName: []byte("six")}, + desc: "includes submodule", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + blob := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + commit := gittest.WriteCommit(t, cfg, repoPath) + + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob", Mode: "100644", OID: blob}, + {Path: "submodule", Mode: "160000", OID: commit}, + }) + + return tree.Revision(), []RevisionResult{ + {OID: blob, ObjectName: []byte("blob")}, + {OID: commit, ObjectName: []byte("submodule")}, + } }, }, { - desc: "filter blobs only", - revision: "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", + desc: "filter blobs only", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + blob := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + commit := gittest.WriteCommit(t, cfg, repoPath) + + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "blob", + Mode: "100644", + OID: blob, + }, + { + Path: "submodule", + Mode: "160000", + OID: commit, + }, + { + Path: "subtree", + Mode: "040000", + OID: gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob-in-subtree", Mode: "100644", Content: "something"}, + }), + }, + }) + + return tree.Revision(), []RevisionResult{ + {OID: blob, ObjectName: []byte("blob")}, + } + }, options: []LsTreeOption{ LsTreeWithBlobFilter(), }, - expectedResults: []RevisionResult{ - {OID: "470ad2fcf1e33798f1afc5781d08e60c40f51e7a", ObjectName: []byte(".gitignore")}, - {OID: "fdaada1754989978413d618ee1fb1c0469d6a664", ObjectName: []byte(".gitmodules")}, - {OID: "c1788657b95998a2f177a4f86d68a60f2a80117f", ObjectName: []byte("CONTRIBUTING.md")}, - {OID: "50b27c6518be44c42c4d87966ae2481ce895624c", ObjectName: []byte("LICENSE")}, - {OID: "faaf198af3a36dbf41961466703cc1d47c61d051", ObjectName: []byte("README.md")}, - }, }, { - desc: "empty tree", - revision: "7efb185dd22fd5c51ef044795d62b7847900c341", + desc: "empty tree", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + return gittest.DefaultObjectHash.EmptyTreeOID.Revision(), nil + }, }, { - desc: "non-recursive", - revision: "913c66a37b4a45b9769037c55c2d238bd0942d2e", - expectedResults: []RevisionResult{ - {OID: "fd90a3d2d21d6b4f9bec2c33fb7f49780c55f0d2", ObjectName: []byte(".DS_Store")}, - {OID: "470ad2fcf1e33798f1afc5781d08e60c40f51e7a", ObjectName: []byte(".gitignore")}, - {OID: "fdaada1754989978413d618ee1fb1c0469d6a664", ObjectName: []byte(".gitmodules")}, - {OID: "c74175afd117781cbc983664339a0f599b5bb34e", ObjectName: []byte("CHANGELOG")}, - {OID: "c1788657b95998a2f177a4f86d68a60f2a80117f", ObjectName: []byte("CONTRIBUTING.md")}, - {OID: "50b27c6518be44c42c4d87966ae2481ce895624c", ObjectName: []byte("LICENSE")}, - {OID: "95d9f0a5e7bb054e9dd3975589b8dfc689e20e88", ObjectName: []byte("MAINTENANCE.md")}, - {OID: "bf757025c40c62e6ffa6f11d3819c769a76dbe09", ObjectName: []byte("PROCESS.md")}, - {OID: "faaf198af3a36dbf41961466703cc1d47c61d051", ObjectName: []byte("README.md")}, - {OID: "998707b421c89bd9a3063333f9f728ef3e43d101", ObjectName: []byte("VERSION")}, - {OID: "3c122d2b7830eca25235131070602575cf8b41a1", ObjectName: []byte("encoding")}, - {OID: "ab746f8ad0b84b147290041dc13cc9c7adc52930", ObjectName: []byte("files")}, - {OID: "409f37c4f05865e4fb208c771485f211a22c4c2d", ObjectName: []byte("six")}, + desc: "non-recursive", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + blob := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + subtree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob-in-subtree", Mode: "100644", Content: "something"}, + }) + + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob", Mode: "100644", OID: blob}, + {Path: "subtree", Mode: "040000", OID: subtree}, + }) + + return tree.Revision(), []RevisionResult{ + {OID: blob, ObjectName: []byte("blob")}, + {OID: subtree, ObjectName: []byte("subtree")}, + } }, }, { - desc: "recursive", - revision: "913c66a37b4a45b9769037c55c2d238bd0942d2e", + desc: "recursive", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + blob := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + blobInSubtree := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + { + Path: "blob", + Mode: "100644", + OID: blob, + }, + { + Path: "subtree", + Mode: "040000", + OID: gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob-in-subtree", Mode: "100644", OID: blobInSubtree}, + }), + }, + }) + + return tree.Revision(), []RevisionResult{ + {OID: blob, ObjectName: []byte("blob")}, + {OID: blobInSubtree, ObjectName: []byte("subtree/blob-in-subtree")}, + } + }, options: []LsTreeOption{ LsTreeWithRecursive(), }, - expectedResults: []RevisionResult{ - {OID: "fd90a3d2d21d6b4f9bec2c33fb7f49780c55f0d2", ObjectName: []byte(".DS_Store")}, - {OID: "470ad2fcf1e33798f1afc5781d08e60c40f51e7a", ObjectName: []byte(".gitignore")}, - {OID: "fdaada1754989978413d618ee1fb1c0469d6a664", ObjectName: []byte(".gitmodules")}, - {OID: "c74175afd117781cbc983664339a0f599b5bb34e", ObjectName: []byte("CHANGELOG")}, - {OID: "c1788657b95998a2f177a4f86d68a60f2a80117f", ObjectName: []byte("CONTRIBUTING.md")}, - {OID: "50b27c6518be44c42c4d87966ae2481ce895624c", ObjectName: []byte("LICENSE")}, - {OID: "95d9f0a5e7bb054e9dd3975589b8dfc689e20e88", ObjectName: []byte("MAINTENANCE.md")}, - {OID: "bf757025c40c62e6ffa6f11d3819c769a76dbe09", ObjectName: []byte("PROCESS.md")}, - {OID: "faaf198af3a36dbf41961466703cc1d47c61d051", ObjectName: []byte("README.md")}, - {OID: "998707b421c89bd9a3063333f9f728ef3e43d101", ObjectName: []byte("VERSION")}, - {OID: "8af7f880ce38649fc49f66e3f38857bfbec3f0b7", ObjectName: []byte("encoding/feature-1.txt")}, - {OID: "16ca0b267f82cd2f5ca1157dd162dae98745eab8", ObjectName: []byte("encoding/feature-2.txt")}, - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("encoding/hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("encoding/hotfix-2.txt")}, - {OID: "570f8e1dfe8149c1d17002712310d43dfeb43159", ObjectName: []byte("encoding/russian.rb")}, - {OID: "7a17968582c21c9153ec24c6a9d5f33592ad9103", ObjectName: []byte("encoding/test.txt")}, - {OID: "f3064a3aa9c14277483f690250072e987e2c8356", ObjectName: []byte("encoding/\343\203\206\343\202\271\343\203\210.txt")}, - {OID: "3a26c18b02e843b459732e7ade7ab9a154a1002b", ObjectName: []byte("encoding/\343\203\206\343\202\271\343\203\210.xls")}, - {OID: "60d7a906c2fd9e4509aeb1187b98d0ea7ce827c9", ObjectName: []byte("files/.DS_Store")}, - {OID: "c84b9e90e4bee2666f5cb3688690d9824d88f3ee", ObjectName: []byte("files/html/500.html")}, - {OID: "4a96572d570108366da2cf5aa8213f69b591a2a3", ObjectName: []byte("files/images/logo-black.png")}, - {OID: "bc2ef601a538d69ef99d5bdafa605e63f902e8e4", ObjectName: []byte("files/images/logo-white.png")}, - {OID: "4a393dbfe81113d35d9aedeb5c76f763190c1b2f", ObjectName: []byte("files/js/application.js")}, - {OID: "5f53439ca4b009096571d3c8bc3d09d30e7431b3", ObjectName: []byte("files/js/commit.js.coffee")}, - {OID: "3da5c4ddbaf17d37aea52c485d8bb85b07d77816", ObjectName: []byte("files/markdown/ruby-style-guide.md")}, - {OID: "e2fbafb389911ef68e4da2da7da917f6acb9e86a", ObjectName: []byte("files/ruby/popen.rb")}, - {OID: "d18fc8bf2cecbe21bbbd5da692eaf83e32c48638", ObjectName: []byte("files/ruby/regex.rb")}, - {OID: "6ee41e85cc9bf33c10b690df09ca735b22f3790f", ObjectName: []byte("files/ruby/version_info.rb")}, - {OID: "409f37c4f05865e4fb208c771485f211a22c4c2d", ObjectName: []byte("six")}, - }, }, { - desc: "invalid revision", - revision: "refs/heads/does-not-exist", + desc: "invalid revision", + setup: func(t *testing.T, repoPath string) (git.Revision, []RevisionResult) { + return "refs/heads/does-not-exist", nil + }, expectedErr: errors.New("ls-tree pipeline command: exit status 128, stderr: " + "\"fatal: Not a valid object name refs/heads/does-not-exist\\n\""), }, } { t.Run(tc.desc, func(t *testing.T) { - it := LsTree(ctx, repo, tc.revision, tc.options...) + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) + repo := localrepo.NewTestRepo(t, cfg, repoProto) + + revision, expectedResults := tc.setup(t, repoPath) + + it := LsTree(ctx, repo, revision.String(), tc.options...) var results []RevisionResult for it.Next() { @@ -149,7 +182,7 @@ func TestLsTree(t *testing.T) { } require.Equal(t, tc.expectedErr, err) - require.Equal(t, tc.expectedResults, results) + require.Equal(t, expectedResults, results) }) } } diff --git a/internal/git/gitpipe/pipeline_test.go b/internal/git/gitpipe/pipeline_test.go index 797d1fced..2af0f0496 100644 --- a/internal/git/gitpipe/pipeline_test.go +++ b/internal/git/gitpipe/pipeline_test.go @@ -1,5 +1,3 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( @@ -19,15 +17,31 @@ import ( ) func TestPipeline_revlist(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, }) repo := localrepo.NewTestRepo(t, cfg, repoProto) + blobA := gittest.WriteBlob(t, cfg, repoPath, []byte("blob a")) + blobB := gittest.WriteBlob(t, cfg, repoPath, []byte("b")) + blobC := gittest.WriteBlob(t, cfg, repoPath, []byte("longer blob c")) + + subtree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "subblob", Mode: "100644", OID: blobA}, + }) + tree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "blob", Mode: "100644", OID: blobB}, + {Path: "subtree", Mode: "040000", OID: subtree}, + }) + + commitA := gittest.WriteCommit(t, cfg, repoPath) + commitB := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(commitA), gittest.WithTree(tree), gittest.WithBranch("main")) + for _, tc := range []struct { desc string revisions []string @@ -39,79 +53,81 @@ func TestPipeline_revlist(t *testing.T) { { desc: "single blob", revisions: []string{ - lfsPointer1, + blobA.String(), }, revlistOptions: []RevlistOption{ WithObjects(), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}}, }, }, { desc: "single blob without objects", revisions: []string{ - lfsPointer1, + blobA.String(), }, expectedResults: nil, }, { desc: "multiple blobs", revisions: []string{ - lfsPointer1, - lfsPointer2, - lfsPointer3, + blobA.String(), + blobB.String(), + blobC.String(), }, revlistOptions: []RevlistOption{ WithObjects(), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer3, Type: "blob", Size: 127}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobC, Type: "blob", Size: 13}}}, }, }, { desc: "multiple blobs with filter", revisions: []string{ - lfsPointer1, - lfsPointer2, - lfsPointer3, + blobA.String(), + blobB.String(), + blobC.String(), }, revlistOptions: []RevlistOption{ WithObjects(), WithSkipRevlistResult(func(r *RevisionResult) bool { - return r.OID != lfsPointer2 + return r.OID != blobB }), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}}, }, }, { desc: "tree", revisions: []string{ - "b95c0fad32f4361845f91d9ce4c1721b52b82793", + tree.String(), }, revlistOptions: []RevlistOption{ WithObjects(), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "b95c0fad32f4361845f91d9ce4c1721b52b82793", Type: "tree", Size: 43}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "93e123ac8a3e6a0b600953d7598af629dec7b735", Type: "blob", Size: 59}}, ObjectName: []byte("branch-test.txt")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: tree, Type: "tree", Size: hashDependentObjectSize(66, 90)}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}, ObjectName: []byte("blob")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: subtree, Type: "tree", Size: hashDependentObjectSize(35, 47)}}, ObjectName: []byte("subtree")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "tree without objects", revisions: []string{ - "b95c0fad32f4361845f91d9ce4c1721b52b82793", + tree.String(), }, expectedResults: nil, }, { desc: "tree with blob filter", revisions: []string{ - "b95c0fad32f4361845f91d9ce4c1721b52b82793", + tree.String(), }, revlistOptions: []RevlistOption{ WithObjects(), @@ -122,37 +138,35 @@ func TestPipeline_revlist(t *testing.T) { }), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "93e123ac8a3e6a0b600953d7598af629dec7b735", Type: "blob", Size: 59}}, ObjectName: []byte("branch-test.txt")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}, ObjectName: []byte("blob")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "revision range", revisions: []string{ - "^master~", - "master", + "^" + commitB.String() + "~", + commitB.String(), }, revlistOptions: []RevlistOption{ WithObjects(), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "1e292f8fedd741b75372e19097c76d327140c312", Type: "commit", Size: 388}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "07f8147e8e73aab6c935c296e8cdc5194dee729b", Type: "tree", Size: 780}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "ceb102b8d3f9a95c2eb979213e49f7cc1b23d56e", Type: "tree", Size: 258}}, ObjectName: []byte("files")}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "2132d150328bd9334cc4e62a16a5d998a7e399b9", Type: "tree", Size: 31}}, ObjectName: []byte("files/flat")}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "f3942dc8b824a2c9359e518d48e68f84461bd2f7", Type: "tree", Size: 34}}, ObjectName: []byte("files/flat/path")}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "ea7249055466085d0a6c69951908ef47757e92f4", Type: "tree", Size: 39}}, ObjectName: []byte("files/flat/path/correct")}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "c1c67abbaf91f624347bb3ae96eabe3a1b742478", Type: "commit", Size: 326}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: commitB, Type: "commit", Size: hashDependentObjectSize(225, 273)}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: tree, Type: "tree", Size: hashDependentObjectSize(66, 90)}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}, ObjectName: []byte("blob")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: subtree, Type: "tree", Size: hashDependentObjectSize(35, 47)}}, ObjectName: []byte("subtree")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "revision range without objects", revisions: []string{ - "^master~", - "master", + "^" + commitB.String() + "~", + commitB.String(), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "1e292f8fedd741b75372e19097c76d327140c312", Type: "commit", Size: 388}}}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: "c1c67abbaf91f624347bb3ae96eabe3a1b742478", Type: "commit", Size: 326}}}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: commitB, Type: "commit", Size: hashDependentObjectSize(225, 273)}}}, }, }, { @@ -163,20 +177,19 @@ func TestPipeline_revlist(t *testing.T) { revlistOptions: []RevlistOption{ WithObjects(), WithSkipRevlistResult(func(r *RevisionResult) bool { - // Let through two LFS pointers and a tree. - return r.OID != "b95c0fad32f4361845f91d9ce4c1721b52b82793" && - r.OID != lfsPointer1 && r.OID != lfsPointer2 + // Let through two blobs and a tree. + return r.OID != tree && r.OID != blobA && r.OID != blobB }), }, catfileInfoOptions: []CatfileInfoOption{ WithSkipCatfileInfoResult(func(objectInfo *catfile.ObjectInfo) bool { - // Only let through blobs, so only the two LFS pointers remain. + // Only let through blobs, so only the two blobs remain. return objectInfo.Type != "blob" }), }, expectedResults: []CatfileObjectResult{ - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer1, Type: "blob", Size: 133}}, ObjectName: []byte("files/lfs/lfs_object.iso")}, - {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: lfsPointer2, Type: "blob", Size: 127}}, ObjectName: []byte("another.lfs")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobB, Type: "blob", Size: 1}}, ObjectName: []byte("blob")}, + {Object: &catfile.Object{ObjectInfo: catfile.ObjectInfo{Oid: blobA, Type: "blob", Size: 6}}, ObjectName: []byte("subtree/subblob")}, }, }, { @@ -192,9 +205,9 @@ func TestPipeline_revlist(t *testing.T) { { desc: "mixed valid and invalid revision", revisions: []string{ - lfsPointer1, + blobA.String(), "doesnotexist", - lfsPointer2, + blobB.String(), }, expectedErr: errors.New("rev-list pipeline command: exit status 128, stderr: " + "\"fatal: ambiguous argument 'doesnotexist': unknown revision or path not in the working tree.\\n" + @@ -309,7 +322,7 @@ func TestPipeline_revlist(t *testing.T) { _, err := io.Copy(io.Discard, catfileObjectIter.Result()) require.NoError(t, err) - if i == 3 { + if i == 1 { cancel() } } @@ -368,20 +381,28 @@ func TestPipeline_revlist(t *testing.T) { // We could in theory assert the exact amount of objects, but this would make it // harder than necessary to change the test repo's contents. - require.Greater(t, i, 1000) + require.Equal(t, i, 7) }) } func TestPipeline_forEachRef(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, }) repo := localrepo.NewTestRepo(t, cfg, repoProto) + keepaliveCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithReference("refs/keep-alive/a"), gittest.WithMessage("keepalive")) + mainCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"), gittest.WithMessage("main")) + featureCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("feature"), gittest.WithMessage("feature")) + tag := gittest.WriteTag(t, cfg, repoPath, "v1.0.0", mainCommit.Revision(), gittest.WriteTagConfig{ + Message: "annotated", + }) + catfileCache := catfile.NewCache(cfg) defer catfileCache.Stop() @@ -406,6 +427,22 @@ func TestPipeline_forEachRef(t *testing.T) { content []byte } + expectedRefs := map[git.ReferenceName]object{ + "refs/keep-alive/a": {oid: keepaliveCommit, content: []byte("xx")}, + "refs/heads/main": {oid: mainCommit, content: []byte("xx")}, + "refs/heads/feature": {oid: featureCommit, content: []byte("xx")}, + "refs/tags/v1.0.0": {oid: tag, content: []byte("xx")}, + } + for expectedRef, expectedObject := range expectedRefs { + content, err := repo.ReadObject(ctx, expectedObject.oid) + require.NoError(t, err) + + expectedRefs[expectedRef] = object{ + oid: expectedObject.oid, + content: content, + } + } + objectsByRef := make(map[git.ReferenceName]object) for catfileObjectIter.Next() { result := catfileObjectIter.Result() @@ -420,24 +457,5 @@ func TestPipeline_forEachRef(t *testing.T) { } } require.NoError(t, catfileObjectIter.Err()) - require.Greater(t, len(objectsByRef), 90) - - // We certainly don't want to hard-code all the references, so we just cross-check with the - // localrepo implementation to verify that both return the same data. - refs, err := repo.GetReferences(ctx) - require.NoError(t, err) - require.Equal(t, len(refs), len(objectsByRef)) - - expectedObjectsByRef := make(map[git.ReferenceName]object) - for _, ref := range refs { - oid := git.ObjectID(ref.Target) - content, err := repo.ReadObject(ctx, oid) - require.NoError(t, err) - - expectedObjectsByRef[ref.Name] = object{ - oid: oid, - content: content, - } - } - require.Equal(t, expectedObjectsByRef, objectsByRef) + require.Equal(t, expectedRefs, objectsByRef) } diff --git a/internal/git/gitpipe/revision_test.go b/internal/git/gitpipe/revision_test.go index 7e740c247..723f1316b 100644 --- a/internal/git/gitpipe/revision_test.go +++ b/internal/git/gitpipe/revision_test.go @@ -1,8 +1,7 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( + "bytes" "context" "errors" "testing" @@ -10,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gitlab.com/gitlab-org/gitaly/v15/internal/git" "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/git/localrepo" "gitlab.com/gitlab-org/gitaly/v15/internal/gitaly/config" @@ -19,15 +17,63 @@ import ( ) func TestRevlist(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) cfg := testcfg.Build(t) - repoProto, _ := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ SkipCreationViaService: true, - Seed: gittest.SeedGitLabTest, }) repo := localrepo.NewTestRepo(t, cfg, repoProto) + blobA := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("a"), 133)) + blobB := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("b"), 127)) + blobC := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("c"), 127)) + blobD := gittest.WriteBlob(t, cfg, repoPath, bytes.Repeat([]byte("d"), 129)) + + blob := gittest.WriteBlob(t, cfg, repoPath, []byte("a")) + subblob := gittest.WriteBlob(t, cfg, repoPath, []byte("larger blob")) + + treeA := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "branch-test.txt", Mode: "100644", OID: blob}, + }) + commitA := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithTree(treeA), + gittest.WithCommitterDate(time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC)), + ) + + subtree := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "subblob", Mode: "100644", OID: subblob}, + }) + treeB := gittest.WriteTree(t, cfg, repoPath, []gittest.TreeEntry{ + {Path: "branch-test.txt", Mode: "100644", OID: blob}, + {Path: "subtree", Mode: "040000", OID: subtree}, + }) + commitB := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithParents(commitA), + gittest.WithTree(treeB), + gittest.WithCommitterDate(time.Date(1999, 1, 1, 1, 1, 1, 1, time.UTC)), + gittest.WithAuthorName("custom author"), + ) + + commitBParent := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithParents(commitB), + gittest.WithTree(treeB), + gittest.WithCommitterDate(time.Date(2001, 1, 1, 1, 1, 1, 1, time.UTC)), + ) + commitAParent := gittest.WriteCommit(t, cfg, repoPath, + gittest.WithParents(commitA), + gittest.WithTree(treeA), + gittest.WithCommitterDate(time.Date(2001, 1, 1, 1, 1, 1, 1, time.UTC)), + ) + + mergeCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithParents(commitAParent, commitBParent)) + + tag := gittest.WriteTag(t, cfg, repoPath, "v1.0.0", mergeCommit.Revision(), gittest.WriteTagConfig{ + Message: "annotated tag", + }) + for _, tc := range []struct { desc string revisions []string @@ -38,150 +84,146 @@ func TestRevlist(t *testing.T) { { desc: "single blob", revisions: []string{ - lfsPointer1, + blobA.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, }, }, { desc: "multiple blobs", revisions: []string{ - lfsPointer1, - lfsPointer2, - lfsPointer3, - lfsPointer4, + blobA.String(), + blobB.String(), + blobC.String(), + blobD.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: lfsPointer1}, - {OID: lfsPointer2}, - {OID: lfsPointer3}, - {OID: lfsPointer4}, + {OID: blobA}, + {OID: blobB}, + {OID: blobC}, + {OID: blobD}, }, }, { desc: "multiple blobs without objects", revisions: []string{ - lfsPointer1, - lfsPointer2, - lfsPointer3, - lfsPointer4, + blobA.String(), + blobB.String(), + blobC.String(), + blobD.String(), }, expectedResults: nil, }, { desc: "duplicated blob prints blob once only", revisions: []string{ - lfsPointer1, - lfsPointer1, + blobA.String(), + blobA.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: lfsPointer1}, + {OID: blobA}, }, }, { desc: "tree results in object names", revisions: []string{ - "b95c0fad32f4361845f91d9ce4c1721b52b82793", + treeA.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: "b95c0fad32f4361845f91d9ce4c1721b52b82793"}, - {OID: "93e123ac8a3e6a0b600953d7598af629dec7b735", ObjectName: []byte("branch-test.txt")}, + {OID: treeA}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, }, }, { desc: "tree without objects returns nothing", revisions: []string{ - "b95c0fad32f4361845f91d9ce4c1721b52b82793", + treeA.String(), }, expectedResults: nil, }, { desc: "revision without disabled walk", revisions: []string{ - "refs/heads/master", + commitB.String(), }, options: []RevlistOption{ WithDisabledWalk(), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, + {OID: commitB}, }, }, { desc: "revision range", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + "^" + commitB.String() + "~", + commitB.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "07f8147e8e73aab6c935c296e8cdc5194dee729b"}, - {OID: "ceb102b8d3f9a95c2eb979213e49f7cc1b23d56e", ObjectName: []byte("files")}, - {OID: "2132d150328bd9334cc4e62a16a5d998a7e399b9", ObjectName: []byte("files/flat")}, - {OID: "f3942dc8b824a2c9359e518d48e68f84461bd2f7", ObjectName: []byte("files/flat/path")}, - {OID: "ea7249055466085d0a6c69951908ef47757e92f4", ObjectName: []byte("files/flat/path/correct")}, - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, + {OID: commitB}, + {OID: treeB}, + {OID: subtree, ObjectName: []byte("subtree")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "revision range without objects", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + "^" + commitB.String() + "~", + commitB.String(), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, + {OID: commitB}, }, }, { desc: "revision range without objects with at most one parent", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + mergeCommit.String(), }, options: []RevlistOption{ WithMaxParents(1), }, expectedResults: []RevisionResult{ - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, + {OID: commitAParent}, + {OID: commitBParent}, + {OID: commitA}, + {OID: commitB}, }, }, { desc: "reverse revision range without objects", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + commitB.String(), }, options: []RevlistOption{ WithReverse(), }, expectedResults: []RevisionResult{ - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, + {OID: commitA}, + {OID: commitB}, }, }, { desc: "reverse revision range with objects", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + commitB.String(), }, options: []RevlistOption{ WithReverse(), @@ -190,110 +232,86 @@ func TestRevlist(t *testing.T) { expectedResults: []RevisionResult{ // Note that only commits are listed in reverse, // their referenced objects stay in the same order. - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, - {OID: "07f8147e8e73aab6c935c296e8cdc5194dee729b"}, - {OID: "ceb102b8d3f9a95c2eb979213e49f7cc1b23d56e", ObjectName: []byte("files")}, - {OID: "2132d150328bd9334cc4e62a16a5d998a7e399b9", ObjectName: []byte("files/flat")}, - {OID: "f3942dc8b824a2c9359e518d48e68f84461bd2f7", ObjectName: []byte("files/flat/path")}, - {OID: "ea7249055466085d0a6c69951908ef47757e92f4", ObjectName: []byte("files/flat/path/correct")}, - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, + {OID: commitA}, + {OID: treeA}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, + {OID: commitB}, + {OID: treeB}, + {OID: subtree, ObjectName: []byte("subtree")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "revision range with topo order", revisions: []string{ - // This is one of the smaller examples I've found which reproduces - // different sorting orders between topo- and date-sorting. Expected - // results contain the same object for this and the next test case, - // but ordering is different. - "master", - "^master~5", - "flat-path", + mergeCommit.String(), }, options: []RevlistOption{ WithOrder(OrderTopo), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, - {OID: "7975be0116940bf2ad4321f79d02a55c5f7779aa"}, - {OID: "c84ff944ff4529a70788a5e9003c2b7feae29047"}, - {OID: "60ecb67744cb56576c30214ff52294f8ce2def98"}, - {OID: "55bc176024cfa3baaceb71db584c7e5df900ea65"}, - {OID: "e63f41fe459e62e1228fcef60d7189127aeba95a"}, - {OID: "4a24d82dbca5c11c61556f3b35ca472b7463187e"}, - {OID: "b83d6e391c22777fca1ed3012fce84f633d7fed0"}, - {OID: "498214de67004b1da3d820901307bed2a68a8ef6"}, - // The following commit is sorted differently in the next testcase. - {OID: "ce369011c189f62c815f5971d096b26759bab0d1"}, + // Note that the order here is different from the order in the next + // testcase, where we use date-order. + {OID: mergeCommit}, + {OID: commitBParent}, + {OID: commitB}, + {OID: commitAParent}, + {OID: commitA}, }, }, { desc: "revision range with date order", revisions: []string{ - "master", - "^master~5", - "flat-path", + mergeCommit.String(), }, options: []RevlistOption{ WithOrder(OrderDate), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, - {OID: "7975be0116940bf2ad4321f79d02a55c5f7779aa"}, - {OID: "c84ff944ff4529a70788a5e9003c2b7feae29047"}, - {OID: "60ecb67744cb56576c30214ff52294f8ce2def98"}, - {OID: "55bc176024cfa3baaceb71db584c7e5df900ea65"}, - // The following commit is sorted differently in the previous - // testcase. - {OID: "ce369011c189f62c815f5971d096b26759bab0d1"}, - {OID: "e63f41fe459e62e1228fcef60d7189127aeba95a"}, - {OID: "4a24d82dbca5c11c61556f3b35ca472b7463187e"}, - {OID: "b83d6e391c22777fca1ed3012fce84f633d7fed0"}, - {OID: "498214de67004b1da3d820901307bed2a68a8ef6"}, + {OID: mergeCommit}, + {OID: commitAParent}, + {OID: commitBParent}, + {OID: commitB}, + {OID: commitA}, }, }, { desc: "revision range with dates", revisions: []string{ - "refs/heads/master", + mergeCommit.String(), }, options: []RevlistOption{ - WithBefore(time.Date(2016, 6, 30, 18, 30, 0, 0, time.UTC)), - WithAfter(time.Date(2016, 6, 30, 18, 28, 0, 0, time.UTC)), + WithBefore(time.Date(2000, 12, 1, 1, 1, 1, 1, time.UTC)), + WithAfter(time.Date(1999, 12, 1, 1, 1, 1, 1, time.UTC)), }, expectedResults: []RevisionResult{ - {OID: "6907208d755b60ebeacb2e9dfea74c92c3449a1f"}, - {OID: "c347ca2e140aa667b968e51ed0ffe055501fe4f4"}, + {OID: commitA}, }, }, { desc: "revision range with author", revisions: []string{ - "refs/heads/master", + mergeCommit.String(), }, options: []RevlistOption{ - WithAuthor([]byte("Sytse")), + WithAuthor([]byte("custom author")), }, expectedResults: []RevisionResult{ - {OID: "e56497bb5f03a90a51293fc6d516788730953899"}, + {OID: commitB}, }, }, { desc: "first parent chain", revisions: []string{ - "master", - "^master~4", + mergeCommit.String(), }, options: []RevlistOption{ WithFirstParent(), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "7975be0116940bf2ad4321f79d02a55c5f7779aa"}, - {OID: "60ecb67744cb56576c30214ff52294f8ce2def98"}, - {OID: "e63f41fe459e62e1228fcef60d7189127aeba95a"}, + {OID: mergeCommit}, + {OID: commitAParent}, + {OID: commitA}, }, }, { @@ -305,22 +323,16 @@ func TestRevlist(t *testing.T) { // twice, once without and once with limit. desc: "tree with multiple blobs without limit", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + treeB.String(), }, options: []RevlistOption{ WithObjects(), }, expectedResults: []RevisionResult{ - {OID: "79d5f98270ad677c86a7e1ab2baa922958565135"}, - {OID: "8af7f880ce38649fc49f66e3f38857bfbec3f0b7", ObjectName: []byte("feature-1.txt")}, - {OID: "16ca0b267f82cd2f5ca1157dd162dae98745eab8", ObjectName: []byte("feature-2.txt")}, - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("hotfix-2.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, - {OID: "570f8e1dfe8149c1d17002712310d43dfeb43159", ObjectName: []byte("russian.rb")}, - {OID: "7a17968582c21c9153ec24c6a9d5f33592ad9103", ObjectName: []byte("test.txt")}, - {OID: "f3064a3aa9c14277483f690250072e987e2c8356", ObjectName: []byte("\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt")}, - {OID: "3a26c18b02e843b459732e7ade7ab9a154a1002b", ObjectName: []byte("\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.xls")}, + {OID: treeB}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, + {OID: subtree, ObjectName: []byte("subtree")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, { @@ -328,38 +340,30 @@ func TestRevlist(t *testing.T) { // get less blobs as result. desc: "tree with multiple blobs with limit", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + treeB.String(), }, options: []RevlistOption{ WithObjects(), - WithBlobLimit(10), + WithBlobLimit(5), }, expectedResults: []RevisionResult{ - {OID: "79d5f98270ad677c86a7e1ab2baa922958565135"}, - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("hotfix-2.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, + {OID: treeB}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, + {OID: subtree, ObjectName: []byte("subtree")}, }, }, { desc: "tree with blob object type filter", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + treeB.String(), }, options: []RevlistOption{ WithObjects(), WithObjectTypeFilter(ObjectTypeBlob), }, expectedResults: []RevisionResult{ - {OID: "8af7f880ce38649fc49f66e3f38857bfbec3f0b7", ObjectName: []byte("feature-1.txt")}, - {OID: "16ca0b267f82cd2f5ca1157dd162dae98745eab8", ObjectName: []byte("feature-2.txt")}, - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("hotfix-2.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, - {OID: "570f8e1dfe8149c1d17002712310d43dfeb43159", ObjectName: []byte("russian.rb")}, - {OID: "7a17968582c21c9153ec24c6a9d5f33592ad9103", ObjectName: []byte("test.txt")}, - {OID: "f3064a3aa9c14277483f690250072e987e2c8356", ObjectName: []byte("\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.txt")}, - {OID: "3a26c18b02e843b459732e7ade7ab9a154a1002b", ObjectName: []byte("\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88.xls")}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, { @@ -372,53 +376,48 @@ func TestRevlist(t *testing.T) { WithObjectTypeFilter(ObjectTypeTag), }, expectedResults: []RevisionResult{ - {OID: "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", ObjectName: []byte("v1.0.0")}, - {OID: "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b", ObjectName: []byte("v1.1.0")}, - {OID: "8f03acbcd11c53d9c9468078f32a2622005a4841", ObjectName: []byte("v1.1.1")}, + {OID: tag, ObjectName: []byte("v1.0.0")}, }, }, { - desc: "tree with commit object type filter", + desc: "tree with tree object type filter", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + commitA.String(), }, options: []RevlistOption{ WithObjects(), WithObjectTypeFilter(ObjectTypeTree), }, expectedResults: []RevisionResult{ - {OID: "79d5f98270ad677c86a7e1ab2baa922958565135"}, + {OID: treeA}, }, }, { desc: "tree with commit object type filter", revisions: []string{ - "^refs/heads/master~", - "refs/heads/master", + commitB.String(), }, options: []RevlistOption{ WithObjects(), WithObjectTypeFilter(ObjectTypeCommit), }, expectedResults: []RevisionResult{ - {OID: "1e292f8fedd741b75372e19097c76d327140c312"}, - {OID: "c1c67abbaf91f624347bb3ae96eabe3a1b742478"}, + {OID: commitB}, + {OID: commitA}, }, }, { desc: "tree with object type and blob size filter", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + mergeCommit.String(), }, options: []RevlistOption{ WithObjects(), - WithBlobLimit(10), + WithBlobLimit(5), WithObjectTypeFilter(ObjectTypeBlob), }, expectedResults: []RevisionResult{ - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("hotfix-2.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, }, }, { @@ -434,7 +433,7 @@ func TestRevlist(t *testing.T) { { desc: "mixed valid and invalid revision", revisions: []string{ - lfsPointer1, + blobA.String(), "refs/heads/does-not-exist", }, expectedErr: errors.New("rev-list pipeline command: exit status 128, stderr: " + @@ -445,11 +444,10 @@ func TestRevlist(t *testing.T) { { desc: "skip everything", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + mergeCommit.String(), }, options: []RevlistOption{ WithObjects(), - WithBlobLimit(10), WithObjectTypeFilter(ObjectTypeBlob), WithSkipRevlistResult(func(*RevisionResult) bool { return true }), }, @@ -457,36 +455,32 @@ func TestRevlist(t *testing.T) { { desc: "skip nothing", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + mergeCommit.String(), }, options: []RevlistOption{ WithObjects(), - WithBlobLimit(10), WithObjectTypeFilter(ObjectTypeBlob), WithSkipRevlistResult(func(*RevisionResult) bool { return false }), }, expectedResults: []RevisionResult{ - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "4ae6c5e14452a35d04156277ae63e8356eb17cae", ObjectName: []byte("hotfix-2.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, + {OID: blob, ObjectName: []byte("branch-test.txt")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, { desc: "skip one", revisions: []string{ - "79d5f98270ad677c86a7e1ab2baa922958565135", + mergeCommit.String(), }, options: []RevlistOption{ WithObjects(), - WithBlobLimit(10), WithObjectTypeFilter(ObjectTypeBlob), WithSkipRevlistResult(func(r *RevisionResult) bool { - return string(r.ObjectName) == "hotfix-2.txt" + return bytes.Equal(r.ObjectName, []byte("branch-test.txt")) }), }, expectedResults: []RevisionResult{ - {OID: "0fb47f093f769008049a0b0976ac3fa6d6125033", ObjectName: []byte("hotfix-1.txt")}, - {OID: "b988ffed90cb6a9b7f98a3686a933edb3c5d70c0", ObjectName: []byte("iso8859.txt")}, + {OID: subblob, ObjectName: []byte("subtree/subblob")}, }, }, } { @@ -513,12 +507,12 @@ func TestRevlist(t *testing.T) { t.Run("context cancellation", func(t *testing.T) { ctx, cancel := context.WithCancel(testhelper.Context(t)) - it := Revlist(ctx, repo, []string{"refs/heads/master"}) + it := Revlist(ctx, repo, []string{mergeCommit.String()}) require.True(t, it.Next()) require.NoError(t, it.Err()) require.Equal(t, RevisionResult{ - OID: "1e292f8fedd741b75372e19097c76d327140c312", + OID: mergeCommit, }, it.Result()) cancel() @@ -532,6 +526,8 @@ func TestRevlist(t *testing.T) { } func TestForEachRef(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) readRefs := func(t *testing.T, repo *localrepo.Repo, patterns []string, opts ...ForEachRefOption) []RevisionResult { @@ -546,55 +542,54 @@ func TestForEachRef(t *testing.T) { return results } - cfg, repoProto, _ := testcfg.BuildWithRepo(t) + cfg := testcfg.Build(t) + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) repo := localrepo.NewTestRepo(t, cfg, repoProto) - revisions := make(map[string]git.ObjectID) - for _, reference := range []string{"refs/heads/master", "refs/heads/feature"} { - revision, err := repo.ResolveRevision(ctx, git.Revision(reference)) - require.NoError(t, err) - - revisions[reference] = revision - } + mainCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"), gittest.WithMessage("main")) + featureCommit := gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("feature"), gittest.WithMessage("feature")) + tag := gittest.WriteTag(t, cfg, repoPath, "v1.0.0", featureCommit.Revision(), gittest.WriteTagConfig{ + Message: "annotated tag", + }) t.Run("single fully qualified branch", func(t *testing.T) { require.Equal(t, []RevisionResult{ { - ObjectName: []byte("refs/heads/master"), - OID: revisions["refs/heads/master"], + ObjectName: []byte("refs/heads/main"), + OID: mainCommit, }, - }, readRefs(t, repo, []string{"refs/heads/master"})) + }, readRefs(t, repo, []string{"refs/heads/main"})) }) t.Run("unqualified branch name", func(t *testing.T) { - require.Nil(t, readRefs(t, repo, []string{"master"})) + require.Nil(t, readRefs(t, repo, []string{"main"})) }) t.Run("multiple branches", func(t *testing.T) { require.Equal(t, []RevisionResult{ { ObjectName: []byte("refs/heads/feature"), - OID: revisions["refs/heads/feature"], + OID: featureCommit, }, { - ObjectName: []byte("refs/heads/master"), - OID: revisions["refs/heads/master"], + ObjectName: []byte("refs/heads/main"), + OID: mainCommit, }, - }, readRefs(t, repo, []string{"refs/heads/master", "refs/heads/feature"})) + }, readRefs(t, repo, []string{"refs/heads/main", "refs/heads/feature"})) }) t.Run("branches pattern", func(t *testing.T) { refs := readRefs(t, repo, []string{"refs/heads/*"}) - require.Greater(t, len(refs), 90) - - require.Subset(t, refs, []RevisionResult{ + require.Equal(t, refs, []RevisionResult{ { - ObjectName: []byte("refs/heads/master"), - OID: revisions["refs/heads/master"], + ObjectName: []byte("refs/heads/feature"), + OID: featureCommit, }, { - ObjectName: []byte("refs/heads/feature"), - OID: revisions["refs/heads/feature"], + ObjectName: []byte("refs/heads/main"), + OID: mainCommit, }, }) }) @@ -607,18 +602,31 @@ func TestForEachRef(t *testing.T) { require.Equal(t, refs, []RevisionResult{ { ObjectName: []byte("tag"), - OID: "f4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8", + OID: tag, }, { ObjectName: []byte("peeled"), - OID: "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", + OID: featureCommit, }, }) }) t.Run("multiple patterns", func(t *testing.T) { refs := readRefs(t, repo, []string{"refs/heads/*", "refs/tags/*"}) - require.Greater(t, len(refs), 90) + require.Equal(t, refs, []RevisionResult{ + { + ObjectName: []byte("refs/heads/feature"), + OID: featureCommit, + }, + { + ObjectName: []byte("refs/heads/main"), + OID: mainCommit, + }, + { + ObjectName: []byte("refs/tags/v1.0.0"), + OID: tag, + }, + }) }) t.Run("nonexisting branch", func(t *testing.T) { @@ -637,8 +645,8 @@ func TestForEachRef(t *testing.T) { require.True(t, it.Next()) require.NoError(t, it.Err()) require.Equal(t, RevisionResult{ - OID: "e56497bb5f03a90a51293fc6d516788730953899", - ObjectName: []byte("refs/heads/'test'"), + OID: featureCommit, + ObjectName: []byte("refs/heads/feature"), }, it.Result()) cancel() @@ -652,6 +660,8 @@ func TestForEachRef(t *testing.T) { } func TestForEachRef_options(t *testing.T) { + t.Parallel() + ctx := testhelper.Context(t) for _, tc := range []struct { @@ -704,8 +714,11 @@ func TestForEachRef_options(t *testing.T) { }, }, } { + cfg := testcfg.Build(t) - cfg, repoProto, repoPath := testcfg.BuildWithRepo(t) + repoProto, repoPath := gittest.CreateRepository(ctx, t, cfg, gittest.CreateRepositoryConfig{ + SkipCreationViaService: true, + }) repo := localrepo.NewTestRepo(t, cfg, repoProto) oid := tc.prepare(repoPath, cfg) diff --git a/internal/git/gitpipe/testhelper_test.go b/internal/git/gitpipe/testhelper_test.go index 397c270b3..4bc9f18f7 100644 --- a/internal/git/gitpipe/testhelper_test.go +++ b/internal/git/gitpipe/testhelper_test.go @@ -1,11 +1,10 @@ -//go:build !gitaly_test_sha256 - package gitpipe import ( "testing" "gitlab.com/gitlab-org/gitaly/v15/internal/git" + "gitlab.com/gitlab-org/gitaly/v15/internal/git/gittest" "gitlab.com/gitlab-org/gitaly/v15/internal/testhelper" ) @@ -51,3 +50,10 @@ func (ch *chanObjectIterator) ObjectID() git.ObjectID { func (ch *chanObjectIterator) ObjectName() []byte { return []byte("idontcare") } + +func hashDependentObjectSize(sha1Size, sha256Size int64) int64 { + if gittest.ObjectHashIsSHA256() { + return sha256Size + } + return sha1Size +} |