mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-01 18:27:06 +00:00
Make gittuf hash-algorithm aware so it operates on SHA-256 repositories: - gitinterface.Repository detects its object format (via `git rev-parse --show-object-format`) and exposes GetObjectFormat and a per-repo ZeroHash. GetReference returns the format-appropriate zero hash so ref updates to new refs succeed in SHA-256 repositories. - Commit and tag signing/verification use the `gpgsig-sha256` header for SHA-256 objects (go-git's SignatureSHA256), matching Git's behavior. The verification field is chosen by the object's OID length. Fixes verify-ref failing on Git-signed SHA-256 RSL entries. - git-remote-gittuf negotiates object-format from the repository instead of hardcoding sha1, and uses the repo-appropriate zero hash for ref tips. - Test helpers can create SHA-256 repositories; gitinterface, rsl, policy, and experimental/gittuf suites now run under both formats, plus an end-to-end Git-signed record+verify test. Assisted-by: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Paulo Gomes <paulo@entire.io>
1084 lines
31 KiB
Go
1084 lines
31 KiB
Go
// Copyright The gittuf Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package gitinterface
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestWithFetchDepth(t *testing.T) {
|
|
options := &FetchOptions{}
|
|
WithFetchDepth(1)(options)
|
|
|
|
assert.Equal(t, 1, options.Depth)
|
|
}
|
|
|
|
func TestPushRefSpecRepository(t *testing.T) {
|
|
remoteName := "origin"
|
|
refName := "refs/heads/main"
|
|
refSpecs := fmt.Sprintf("%s:%s", refName, refName)
|
|
|
|
t.Run("assert remote repo does not have object until it is pushed", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check that the tree is not present on the remote repository
|
|
_, err = remoteRepo.GetAllFilesInTree(tree)
|
|
assert.Contains(t, err.Error(), "fatal: not a tree object") // tree doesn't exist
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.PushRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
expectedFiles := map[string]Hash{"foo": emptyBlobHash}
|
|
remoteEntries, err := remoteRepo.GetAllFilesInTree(tree)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, expectedFiles, remoteEntries)
|
|
})
|
|
|
|
t.Run("assert after push that src and dst refs match", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.PushRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
})
|
|
|
|
t.Run("assert no error when there are no updates to push", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.PushRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
|
|
// Push again; nothing to push
|
|
err = localRepo.PushRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
})
|
|
|
|
t.Run("push to non-existent remote", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
|
|
err := localRepo.PushRefSpec("nonexistent", []string{refSpecs})
|
|
assert.ErrorContains(t, err, "unable to push")
|
|
})
|
|
}
|
|
|
|
func TestPushRepositorySHA256(t *testing.T) {
|
|
remoteName := "origin"
|
|
refName := "refs/heads/main"
|
|
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false, WithSHA256Format())
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true, WithSHA256Format())
|
|
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
tree, err := NewTreeBuilder(localRepo).WriteTreeFromEntries([]TreeEntry{NewEntryBlob("foo", emptyBlobHash)})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Push(remoteName, []string{refName})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
require.Nil(t, err)
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
assert.Len(t, localRef.String(), 64)
|
|
}
|
|
|
|
func TestPushRepository(t *testing.T) {
|
|
remoteName := "origin"
|
|
refName := "refs/heads/main"
|
|
|
|
t.Run("assert remote repo does not have object until it is pushed", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check that the tree is not present on the remote repository
|
|
_, err = remoteRepo.GetAllFilesInTree(tree)
|
|
assert.Contains(t, err.Error(), "fatal: not a tree object") // tree doesn't exist
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Push(remoteName, []string{refName})
|
|
assert.Nil(t, err)
|
|
|
|
expectedFiles := map[string]Hash{"foo": emptyBlobHash}
|
|
remoteEntries, err := remoteRepo.GetAllFilesInTree(tree)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, expectedFiles, remoteEntries)
|
|
})
|
|
|
|
t.Run("assert after push that src and dst refs match", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Push(remoteName, []string{refName})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
})
|
|
|
|
t.Run("assert no error when there are no updates to push", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, true)
|
|
|
|
localTreeBuilder := NewTreeBuilder(localRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the local repository
|
|
emptyBlobHash, err := localRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := localTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := localRepo.Commit(tree, refName, "Test commit\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Push(remoteName, []string{refName})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
|
|
// Push again; nothing to push
|
|
err = localRepo.Push(remoteName, []string{refName})
|
|
assert.Nil(t, err)
|
|
})
|
|
|
|
t.Run("push to non-existent remote", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
|
|
err := localRepo.Push("nonexistent", []string{refName})
|
|
assert.ErrorContains(t, err, "unable to push")
|
|
})
|
|
|
|
t.Run("push with invalid ref", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, false)
|
|
|
|
err := localRepo.Push("origin", []string{"nonexistent-ref"})
|
|
assert.ErrorIs(t, err, ErrReferenceNotFound)
|
|
})
|
|
}
|
|
|
|
func TestFetchRefSpecRepository(t *testing.T) {
|
|
remoteName := "origin"
|
|
refName := "refs/heads/main"
|
|
refSpecs := fmt.Sprintf("+%s:%s", refName, refName)
|
|
|
|
t.Run("assert local repo does not have object until fetched", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check that the tree is not present on the local repository
|
|
_, err = localRepo.GetAllFilesInTree(tree)
|
|
assert.Contains(t, err.Error(), "fatal: not a tree object") // tree doesn't exist
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.FetchRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
expectedFiles := map[string]Hash{"foo": emptyBlobHash}
|
|
localEntries, err := localRepo.GetAllFilesInTree(tree)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, expectedFiles, localEntries)
|
|
})
|
|
|
|
t.Run("assert after fetch that both refs match", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.FetchRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
})
|
|
|
|
t.Run("assert no error when there are no updates to fetch", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.FetchRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
|
|
// Fetch again, nothing to fetch
|
|
err = localRepo.FetchRefSpec(remoteName, []string{refSpecs})
|
|
assert.Nil(t, err)
|
|
|
|
newLocalRef, err := localRepo.GetReference(refName)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, localRef, newLocalRef)
|
|
})
|
|
|
|
t.Run("fetch from non-existent remote", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
|
|
err := localRepo.FetchRefSpec("nonexistent", []string{refSpecs})
|
|
assert.ErrorContains(t, err, "unable to fetch")
|
|
})
|
|
|
|
t.Run("fetch with depth", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
require.Nil(t, localRepo.CreateRemote(remoteName, remoteTmpDir))
|
|
|
|
tree, err := NewTreeBuilder(remoteRepo).WriteTreeFromEntries(nil)
|
|
require.Nil(t, err)
|
|
remoteRef, err := remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
require.Nil(t, err)
|
|
|
|
err = localRepo.FetchRefSpec(remoteName, []string{refSpecs}, WithFetchDepth(1))
|
|
require.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, remoteRef, localRef)
|
|
})
|
|
}
|
|
|
|
func TestFetchRepository(t *testing.T) {
|
|
remoteName := "origin"
|
|
refName := "refs/heads/main"
|
|
|
|
t.Run("assert local repo does not have object until fetched", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Check that the tree is not present on the local repository
|
|
_, err = localRepo.GetAllFilesInTree(tree)
|
|
assert.Contains(t, err.Error(), "fatal: not a tree object") // tree doesn't exist
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Fetch(remoteName, []string{refName}, true)
|
|
assert.Nil(t, err)
|
|
|
|
expectedFiles := map[string]Hash{"foo": emptyBlobHash}
|
|
localEntries, err := localRepo.GetAllFilesInTree(tree)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, expectedFiles, localEntries)
|
|
})
|
|
|
|
t.Run("assert after fetch that both refs match", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Fetch(remoteName, []string{refName}, true)
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
})
|
|
|
|
t.Run("assert no error when there are no updates to fetch", func(t *testing.T) {
|
|
// Create local and remote repositories
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
// Create the remote on the local repository
|
|
if err := localRepo.CreateRemote(remoteName, remoteTmpDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Create a tree in the remote repository
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = remoteRepo.Commit(tree, refName, "Test commit\n", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = localRepo.Fetch(remoteName, []string{refName}, true)
|
|
assert.Nil(t, err)
|
|
|
|
localRef, err := localRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
remoteRef, err := remoteRepo.GetReference(refName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
assert.Equal(t, localRef, remoteRef)
|
|
|
|
// Fetch again, nothing to fetch
|
|
err = localRepo.Fetch(remoteName, []string{refName}, true)
|
|
assert.Nil(t, err)
|
|
|
|
newLocalRef, err := localRepo.GetReference(refName)
|
|
require.Nil(t, err)
|
|
assert.Equal(t, localRef, newLocalRef)
|
|
})
|
|
|
|
t.Run("fetch from non-existent remote", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
|
|
err := localRepo.Fetch("nonexistent", []string{refName}, true)
|
|
assert.ErrorContains(t, err, "unable to fetch")
|
|
})
|
|
|
|
t.Run("fetch with invalid ref", func(t *testing.T) {
|
|
localTmpDir := t.TempDir()
|
|
remoteTmpDir := t.TempDir()
|
|
localRepo := CreateTestGitRepository(t, localTmpDir, true)
|
|
_ = CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
err := localRepo.CreateRemote(remoteName, remoteTmpDir)
|
|
require.Nil(t, err)
|
|
|
|
err = localRepo.Fetch(remoteName, []string{"nonexistent-ref"}, true)
|
|
assert.ErrorIs(t, err, ErrReferenceNotFound)
|
|
})
|
|
}
|
|
|
|
func TestFetchObject(t *testing.T) {
|
|
tmpDir1 := t.TempDir()
|
|
upstreamRepo := CreateTestGitRepository(t, tmpDir1, true)
|
|
err := upstreamRepo.SetGitConfig("uploadpack.allowReachableSHA1InWant", "true")
|
|
require.Nil(t, err)
|
|
treeBuilder := NewTreeBuilder(upstreamRepo)
|
|
treeID, err := treeBuilder.WriteTreeFromEntries(nil)
|
|
require.Nil(t, err)
|
|
commitID, err := upstreamRepo.Commit(treeID, "refs/heads/main", "Initial commit\n", false)
|
|
require.Nil(t, err)
|
|
|
|
tmpDir2 := t.TempDir()
|
|
downstreamRepo := CreateTestGitRepository(t, tmpDir2, false)
|
|
err = downstreamRepo.AddRemote("origin", tmpDir1)
|
|
require.Nil(t, err)
|
|
|
|
has := downstreamRepo.HasObject(commitID)
|
|
assert.False(t, has)
|
|
|
|
err = downstreamRepo.FetchObject("origin", commitID)
|
|
assert.Nil(t, err)
|
|
|
|
has = downstreamRepo.HasObject(commitID)
|
|
assert.True(t, has)
|
|
|
|
t.Run("fetch from non-existent remote", func(t *testing.T) {
|
|
err := downstreamRepo.FetchObject("nonexistent", ZeroHash)
|
|
assert.ErrorContains(t, err, "unable to fetch object")
|
|
})
|
|
}
|
|
|
|
func TestCloneAndFetchRepositoryObjectFormat(t *testing.T) {
|
|
for _, objectFormat := range []ObjectFormat{ObjectFormatSHA1, ObjectFormatSHA256} {
|
|
t.Run(string(objectFormat), func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false, WithObjectFormat(objectFormat))
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
tree, err := NewTreeBuilder(remoteRepo).WriteTreeFromEntries([]TreeEntry{NewEntryBlob("foo", emptyBlobHash)})
|
|
require.Nil(t, err)
|
|
|
|
if _, err := remoteRepo.Commit(tree, "refs/heads/main", "Commit to main\n", false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, "refs/heads/main", nil, false)
|
|
require.Nil(t, err)
|
|
|
|
assert.Equal(t, objectFormat, localRepo.GetObjectFormat())
|
|
assert.Equal(t, remoteRepo.ZeroHash(), localRepo.ZeroHash())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCloneAndFetchRepository(t *testing.T) {
|
|
refName := "refs/heads/main"
|
|
anotherRefName := "refs/heads/feature"
|
|
|
|
t.Run("clone and fetch remote repository, verify refs match, not bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otherCommit, err := remoteRepo.Commit(tree, anotherRefName, "Commit to feature", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, refName, []string{anotherRefName}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
localOtherCommit, err := localRepo.GetReference(anotherRefName)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
assert.Equal(t, otherCommit, localOtherCommit)
|
|
|
|
assert.True(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(strings.TrimSuffix(localRepo.gitDirPath, ".git"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "foo", dirEntries[1].Name()) // [0] will be the entry for the .git directory
|
|
})
|
|
|
|
t.Run("clone and fetch remote repository without specifying initial branch, verify refs match, not bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otherCommit, err := remoteRepo.Commit(tree, anotherRefName, "Commit to feature", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, "", []string{anotherRefName}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
localOtherCommit, err := localRepo.GetReference(anotherRefName)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
assert.Equal(t, otherCommit, localOtherCommit)
|
|
|
|
assert.True(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(strings.TrimSuffix(localRepo.gitDirPath, ".git"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "foo", dirEntries[1].Name()) // [0] will be the entry for the .git directory
|
|
})
|
|
|
|
t.Run("clone and fetch remote repository with only one ref, verify refs match, not bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, "", []string{}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
|
|
assert.True(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(strings.TrimSuffix(localRepo.gitDirPath, ".git"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "foo", dirEntries[1].Name()) // [0] will be the entry for the .git directory
|
|
})
|
|
|
|
t.Run("clone and fetch remote repository, verify refs match, bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otherCommit, err := remoteRepo.Commit(tree, anotherRefName, "Commit to feature", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, refName, []string{anotherRefName}, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
localOtherCommit, err := localRepo.GetReference(anotherRefName)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
assert.Equal(t, otherCommit, localOtherCommit)
|
|
|
|
assert.False(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(localRepo.gitDirPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "FETCH_HEAD", dirEntries[0].Name())
|
|
})
|
|
|
|
t.Run("clone and fetch remote repository without specifying initial branch, verify refs match, bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otherCommit, err := remoteRepo.Commit(tree, anotherRefName, "Commit to feature", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, "", []string{anotherRefName}, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
localOtherCommit, err := localRepo.GetReference(anotherRefName)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
assert.Equal(t, otherCommit, localOtherCommit)
|
|
|
|
assert.False(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(localRepo.gitDirPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "FETCH_HEAD", dirEntries[0].Name())
|
|
})
|
|
|
|
t.Run("clone and fetch remote repository with only one ref, verify refs match, bare", func(t *testing.T) {
|
|
remoteTmpDir := t.TempDir()
|
|
localTmpDir := t.TempDir()
|
|
|
|
remoteRepo := CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
remoteTreeBuilder := NewTreeBuilder(remoteRepo)
|
|
|
|
emptyBlobHash, err := remoteRepo.WriteBlob(nil)
|
|
require.Nil(t, err)
|
|
entries := []TreeEntry{NewEntryBlob("foo", emptyBlobHash)}
|
|
|
|
tree, err := remoteTreeBuilder.WriteTreeFromEntries(entries)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mainCommit, err := remoteRepo.Commit(tree, refName, "Commit to main", false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := remoteRepo.SetReference("HEAD", mainCommit); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localRepo, err := CloneAndFetchRepository(remoteTmpDir, localTmpDir, "", []string{}, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
localMainCommit, err := localRepo.GetReference(refName)
|
|
assert.Nil(t, err)
|
|
assert.Equal(t, mainCommit, localMainCommit)
|
|
|
|
assert.False(t, strings.HasSuffix(localRepo.gitDirPath, ".git"))
|
|
dirEntries, err := os.ReadDir(localRepo.gitDirPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "FETCH_HEAD", dirEntries[0].Name())
|
|
})
|
|
|
|
t.Run("miscellaneous error checking", func(t *testing.T) {
|
|
_, err := CloneAndFetchRepository("", "", "", nil, false)
|
|
assert.ErrorContains(t, err, "target directory must be specified")
|
|
|
|
_, err = CloneAndFetchRepository(filepath.Join(t.TempDir(), "missing"), t.TempDir(), "", nil, false)
|
|
assert.ErrorContains(t, err, "unable to clone repository")
|
|
})
|
|
}
|
|
|
|
func TestCreateRemote(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
repo := CreateTestGitRepository(t, tmpDir, false)
|
|
err := repo.CreateRemote("origin", tmpDir)
|
|
assert.Nil(t, err)
|
|
|
|
err = repo.CreateRemote("origin", tmpDir)
|
|
assert.ErrorContains(t, err, "unable to add remote")
|
|
}
|