package loose_test import ( "os" "strings" "testing" "lindenii.org/go/furgit/internal/testgit" "lindenii.org/go/furgit/object/header" "lindenii.org/go/furgit/object/id" "lindenii.org/go/furgit/object/store/loose" "lindenii.org/go/furgit/object/typ" ) // gitOracleObject is one object created by git, // paired with its expected content body and full serialized form. type gitOracleObject struct { name string ty typ.Type id id.ObjectID body []byte raw []byte } // openLooseStore opens a loose store over the repository's objects directory. func openLooseStore(t *testing.T, repo *testgit.Repo) *loose.Loose { t.Helper() repoRoot := repo.Root(t) objectsRoot, err := repoRoot.OpenRoot(".git/objects") if err != nil { _ = repoRoot.Close() t.Fatalf("OpenRoot(.git/objects): %v", err) } _ = repoRoot.Close() t.Cleanup(func() { _ = objectsRoot.Close() }) looseStore, err := loose.New(objectsRoot, repo.ObjectFormat(t)) if err != nil { t.Fatalf("loose.New: %v", err) } return looseStore } // gitOracleObjects builds one object of each kind with git // and precomputes each one's expected content body and full serialized form. func gitOracleObjects(t *testing.T, repo *testgit.Repo) []gitOracleObject { t.Helper() blobID, err := repo.HashObject(t, typ.TypeBlob, strings.NewReader("blob body\n")) if err != nil { t.Fatalf("HashObject(blob): %v", err) } treeID, err := repo.MkTree(t, []testgit.TreeEntry{ {Mode: "100644", Type: typ.TypeBlob, OID: blobID, Name: "file"}, }) if err != nil { t.Fatalf("MkTree: %v", err) } commitID, err := repo.CommitTree(t, treeID, testgit.CommitTreeOptions{Message: "subject\n\nbody"}) if err != nil { t.Fatalf("CommitTree: %v", err) } tagID, err := repo.TagAnnotated(t, "v1", commitID, testgit.TagAnnotatedOptions{Message: "tag message"}) if err != nil { t.Fatalf("TagAnnotated: %v", err) } kinds := []struct { name string ty typ.Type id id.ObjectID }{ {name: "blob", ty: typ.TypeBlob, id: blobID}, {name: "tree", ty: typ.TypeTree, id: treeID}, {name: "commit", ty: typ.TypeCommit, id: commitID}, {name: "tag", ty: typ.TypeTag, id: tagID}, } objects := make([]gitOracleObject, 0, len(kinds)) for _, kind := range kinds { body, err := repo.CatFile(t, kind.ty, kind.id) if err != nil { t.Fatalf("CatFile(%s): %v", kind.name, err) } raw := header.Append(nil, kind.ty, uint64(len(body))) raw = append(raw, body...) objects = append(objects, gitOracleObject{ name: kind.name, ty: kind.ty, id: kind.id, body: body, raw: raw, }) } return objects } // corruptLooseObjectTrailer flips the final byte of a loose object file, // damaging the zlib Adler-32 trailer. func corruptLooseObjectTrailer(t *testing.T, repo *testgit.Repo, objectID id.ObjectID) { t.Helper() root := repo.Root(t) defer func() { _ = root.Close() }() hex := objectID.String() relPath := ".git/objects/" + hex[:2] + "/" + hex[2:] file, err := root.OpenFile(relPath, os.O_RDWR, 0) if err != nil { t.Fatalf("OpenFile(%q): %v", relPath, err) } defer func() { _ = file.Close() }() info, err := file.Stat() if err != nil { t.Fatalf("Stat(%q): %v", relPath, err) } if info.Size() == 0 { t.Fatalf("corrupt trailer on empty file %q", relPath) } last := make([]byte, 1) _, err = file.ReadAt(last, info.Size()-1) if err != nil { t.Fatalf("ReadAt(%q): %v", relPath, err) } last[0] ^= 0xff _, err = file.WriteAt(last, info.Size()-1) if err != nil { t.Fatalf("WriteAt(%q): %v", relPath, err) } }