shithub: furgit

ref: b140f267b02b0a5466ec74427dba775da93d298c
dir: /hybrid_test.go/

View raw version
package furgit

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"testing"
)

func TestTreeNestedDeep(t *testing.T) {
	repoPath, cleanup := setupTestRepo(t)
	defer cleanup()

	workDir, cleanupWork := setupWorkDir(t)
	defer cleanupWork()

	depth := 50
	currentDir := workDir
	for i := 0; i < depth; i++ {
		currentDir = filepath.Join(currentDir, fmt.Sprintf("level%d", i))
		err := os.MkdirAll(currentDir, 0o755)
		if err != nil {
			t.Fatalf("failed to create directory %s: %v", currentDir, err)
		}
	}
	err := os.WriteFile(filepath.Join(currentDir, "deep.txt"), []byte("deep content"), 0o644)
	if err != nil {
		t.Fatalf("failed to create deep.txt: %v", err)
	}

	gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
	treeHash := gitCmd(t, repoPath, nil, "--work-tree="+workDir, "write-tree")

	repo, err := OpenRepository(repoPath)
	if err != nil {
		t.Fatalf("OpenRepository failed: %v", err)
	}
	defer func() {
		_ = repo.Close()
	}()

	hash, _ := repo.ParseHash(treeHash)
	obj, _ := repo.ReadObject(hash)
	tree := obj.(*StoredTree)

	path := make([][]byte, depth+1)
	for i := 0; i < depth; i++ {
		path[i] = []byte(fmt.Sprintf("level%d", i))
	}
	path[depth] = []byte("deep.txt")

	entry, err := tree.EntryRecursive(repo, path)
	if err != nil {
		t.Fatalf("EntryRecursive failed for deep path: %v", err)
	}

	blobObj, _ := repo.ReadObject(entry.ID)
	blob := blobObj.(*StoredBlob)

	if !bytes.Equal(blob.Data, []byte("deep content")) {
		t.Errorf("deep file content: got %q, want %q", blob.Data, "deep content")
	}
}

func TestTreeMixedModes(t *testing.T) {
	repoPath, cleanup := setupTestRepo(t)
	defer cleanup()

	workDir, cleanupWork := setupWorkDir(t)
	defer cleanupWork()

	err := os.WriteFile(filepath.Join(workDir, "normal.txt"), []byte("normal"), 0o644)
	if err != nil {
		t.Fatalf("failed to create normal.txt: %v", err)
	}
	err = os.WriteFile(filepath.Join(workDir, "executable.sh"), []byte("#!/bin/sh\necho test"), 0o755)
	if err != nil {
		t.Fatalf("failed to create executable.sh: %v", err)
	}
	err = os.Symlink("normal.txt", filepath.Join(workDir, "link.txt"))
	if err != nil {
		t.Fatalf("failed to create symlink: %v", err)
	}

	gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
	treeHash := gitCmd(t, repoPath, nil, "--work-tree="+workDir, "write-tree")

	repo, err := OpenRepository(repoPath)
	if err != nil {
		t.Fatalf("OpenRepository failed: %v", err)
	}
	defer func() {
		_ = repo.Close()
	}()

	hash, _ := repo.ParseHash(treeHash)
	obj, _ := repo.ReadObject(hash)
	tree := obj.(*StoredTree)

	modes := make(map[string]FileMode)
	for _, entry := range tree.Entries {
		modes[string(entry.Name)] = entry.Mode
	}

	if modes["normal.txt"] != 0o100644 {
		t.Errorf("normal.txt mode: got %o, want %o", modes["normal.txt"], 0o100644)
	}
	if modes["executable.sh"] != 0o100755 {
		t.Errorf("executable.sh mode: got %o, want %o", modes["executable.sh"], 0o100755)
	}
	if modes["link.txt"] != 0o120000 {
		t.Errorf("link.txt mode: got %o, want %o", modes["link.txt"], 0o120000)
	}
}

func TestCommitChain(t *testing.T) {
	repoPath, cleanup := setupTestRepo(t)
	defer cleanup()

	workDir, cleanupWork := setupWorkDir(t)
	defer cleanupWork()

	numCommits := 100
	var commits []string

	for i := 0; i < numCommits; i++ {
		filename := filepath.Join(workDir, fmt.Sprintf("file%d.txt", i))
		err := os.WriteFile(filename, []byte(fmt.Sprintf("content %d", i)), 0o644)
		if err != nil {
			t.Fatalf("failed to create %s: %v", filename, err)
		}
		gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
		gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", fmt.Sprintf("Commit %d", i))
		commitHash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")
		commits = append(commits, commitHash)
	}

	repo, err := OpenRepository(repoPath)
	if err != nil {
		t.Fatalf("OpenRepository failed: %v", err)
	}
	defer func() {
		_ = repo.Close()
	}()

	hash, _ := repo.ParseHash(commits[len(commits)-1])
	for i := numCommits - 1; i >= 0; i-- {
		obj, err := repo.ReadObject(hash)
		if err != nil {
			t.Fatalf("failed to read commit %d: %v", i, err)
		}

		commit, ok := obj.(*StoredCommit)
		if !ok {
			t.Fatalf("expected *StoredCommit at %d, got %T", i, obj)
		}

		expectedMsg := fmt.Sprintf("Commit %d\n", i)
		if !bytes.Equal(commit.Message, []byte(expectedMsg)) {
			t.Errorf("commit %d message: got %q, want %q", i, commit.Message, expectedMsg)
		}

		if i > 0 {
			if len(commit.Parents) != 1 {
				t.Fatalf("commit %d should have 1 parent, got %d", i, len(commit.Parents))
			}
			hash = commit.Parents[0]
		} else {
			if len(commit.Parents) != 0 {
				t.Errorf("first commit should have 0 parents, got %d", len(commit.Parents))
			}
		}
	}
}

func TestMultipleTags(t *testing.T) {
	repoPath, cleanup := setupTestRepo(t)
	defer cleanup()

	workDir, cleanupWork := setupWorkDir(t)
	defer cleanupWork()

	err := os.WriteFile(filepath.Join(workDir, "file.txt"), []byte("content"), 0o644)
	if err != nil {
		t.Fatalf("failed to create file.txt: %v", err)
	}
	gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
	gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", "Tagged commit")
	commitHash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")

	tags := []string{"v1.0.0", "v1.0.1", "v1.1.0", "v2.0.0"}
	for _, tagName := range tags {
		gitCmd(t, repoPath, nil, "tag", "-a", "-m", fmt.Sprintf("Release %s", tagName), tagName, commitHash)
	}

	repo, err := OpenRepository(repoPath)
	if err != nil {
		t.Fatalf("OpenRepository failed: %v", err)
	}
	defer func() {
		_ = repo.Close()
	}()

	for _, tagName := range tags {
		tagHash := gitCmd(t, repoPath, nil, "rev-parse", tagName)
		hash, _ := repo.ParseHash(tagHash)
		obj, err := repo.ReadObject(hash)
		if err != nil {
			t.Errorf("failed to read tag %s: %v", tagName, err)
			continue
		}

		tag, ok := obj.(*StoredTag)
		if !ok {
			t.Errorf("tag %s: expected *StoredTag, got %T", tagName, obj)
			continue
		}

		if !bytes.Equal(tag.Name, []byte(tagName)) {
			t.Errorf("tag name: got %q, want %q", tag.Name, tagName)
		}
	}
}

func TestPackfileAfterMultipleRepacks(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping multiple repack test in short mode")
	}

	repoPath, cleanup := setupTestRepo(t)
	defer cleanup()

	gitCmd(t, repoPath, nil, "config", "gc.auto", "0")

	workDir, cleanupWork := setupWorkDir(t)
	defer cleanupWork()

	for i := 0; i < 5; i++ {
		err := os.WriteFile(filepath.Join(workDir, fmt.Sprintf("file%d.txt", i)), []byte(fmt.Sprintf("content %d", i)), 0o644)
		if err != nil {
			t.Fatalf("failed to create file%d.txt: %v", i, err)
		}
		gitCmd(t, repoPath, nil, "--work-tree="+workDir, "add", ".")
		gitCmd(t, repoPath, nil, "--work-tree="+workDir, "commit", "-m", fmt.Sprintf("Commit %d", i))
		gitCmd(t, repoPath, nil, "repack", "-d")
	}

	gitCmd(t, repoPath, nil, "repack", "-a", "-d")

	repo, err := OpenRepository(repoPath)
	if err != nil {
		t.Fatalf("OpenRepository failed: %v", err)
	}
	defer func() {
		_ = repo.Close()
	}()

	headHash := gitCmd(t, repoPath, nil, "rev-parse", "HEAD")
	hash, _ := repo.ParseHash(headHash)

	obj, err := repo.ReadObject(hash)
	if err != nil {
		t.Fatalf("failed to read HEAD from final packfile: %v", err)
	}

	commit := obj.(*StoredCommit)
	if !bytes.Contains(commit.Message, []byte("Commit 4")) {
		t.Errorf("HEAD commit message incorrect: got %q", commit.Message)
	}
}