mirror of
https://github.com/vee1e/gittuf.git
synced 2026-09-05 12:17:06 +00:00
Restructure storage so gittuf verification can run over backends other
than the git binary (e.g. go-git):
- pkg/githash: concrete Git object hash, stdlib-only.
gitinterface.Hash aliases it.
- pkg/gitstore: the single Storer interface (24 methods) that all
storage consumers program against, plus the shared
ErrReferenceNotFound sentinel. *gitinterface.Repository satisfies it
structurally (compile-time asserted). Also defines ConfigKey, the
canonical type for the Git config settings gittuf reads.
- pkg/rsl (from internal/rsl): entry model, codec, and readers over
gitstore.Storer; zero gitinterface/sigstore dependencies. rsl.Hash
aliases githash.Hash; nil is the unset-Hash sentinel and IsZero
matches nil and empty as well as both format zeros (no
object-format-unaware ZeroHash). Entry commits (empty tree on the
RSL ref) are owned by the package; no storer adapter.
- internal/signerverifier/gitobject: verifies commit/tag signatures
over (payload, signature) bytes, Rekor URL as an option. The storage
half is Repository.GetObjectSignature. Removes sigstore, cosign, and
gitsign from gitinterface's dependency tree.
- internal/propagation: propagation workflow, moved off pkg/rsl's
public API (its tuf directive types are internal).
- internal/{attestations,cache,policy}: storage via gitstore.Storer;
tree writing via WriteTree(blobs, subtrees).
Breaking changes to pkg/gitinterface: Repository.VerifySignature and
the verification sentinels are removed (use gitobject.Verify);
ErrReferenceNotFound now aliases gitstore's. Repository.GetGitConfig
(which returned the whole config map) is replaced by
LookupConfig(gitstore.ConfigKey), returning a single setting's value.
Policy resolves the Rekor override from git config once per
verification and extracts signed payloads once per object instead of
per key attempt.
Assisted-by: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Paulo Gomes <paulo@entire.io>
101 lines
3.5 KiB
Go
101 lines
3.5 KiB
Go
// Copyright The gittuf Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package sync
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/gittuf/gittuf/experimental/gittuf"
|
|
rslopts "github.com/gittuf/gittuf/experimental/gittuf/options/rsl"
|
|
"github.com/gittuf/gittuf/internal/cmd"
|
|
"github.com/gittuf/gittuf/pkg/gitinterface"
|
|
"github.com/gittuf/gittuf/pkg/rsl"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSync(t *testing.T) {
|
|
t.Run("no repository", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
currentDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.Chdir(tmpDir))
|
|
defer os.Chdir(currentDir) //nolint:errcheck
|
|
|
|
_, _, _, err = cmd.ExecuteCommandC(New())
|
|
assert.ErrorContains(t, err, "unable to identify git directory")
|
|
})
|
|
|
|
t.Run("no remote", func(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
currentDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.Chdir(tmpDir))
|
|
defer os.Chdir(currentDir) //nolint:errcheck
|
|
|
|
gitinterface.CreateTestGitRepository(t, tmpDir, false)
|
|
|
|
_, _, _, err = cmd.ExecuteCommandC(New(), "custom-remote")
|
|
assert.ErrorContains(t, err, "No such remote")
|
|
})
|
|
|
|
t.Run("diverged refs and overwrite", func(t *testing.T) {
|
|
refName := "refs/heads/main"
|
|
|
|
// 1. Setup Remote Repo
|
|
remoteTmpDir := t.TempDir()
|
|
remoteR := gitinterface.CreateTestGitRepository(t, remoteTmpDir, false)
|
|
|
|
treeBuilder := gitinterface.NewTreeBuilder(remoteR)
|
|
emptyTreeHash, err := treeBuilder.WriteTreeFromEntries(nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = remoteR.Commit(emptyTreeHash, refName, "Remote commit", false)
|
|
require.NoError(t, err)
|
|
|
|
// We need a dummy gittuf repo to record RSL
|
|
remoteRepo, err := gittuf.LoadRepository(remoteTmpDir)
|
|
require.NoError(t, err)
|
|
require.NoError(t, remoteRepo.RecordRSLEntryForReference(t.Context(), refName, false, rslopts.WithRecordLocalOnly()))
|
|
|
|
// 2. Setup Local Repo (Clone remote)
|
|
localTmpDir := filepath.Join(t.TempDir(), "local-sync-test")
|
|
localR, err := gitinterface.CloneAndFetchRepository(remoteTmpDir, localTmpDir, refName, []string{rsl.Ref}, true)
|
|
require.NoError(t, err)
|
|
require.NoError(t, localR.SetGitConfig("user.name", "Jane Doe"))
|
|
require.NoError(t, localR.SetGitConfig("user.email", "jane.doe@example.com"))
|
|
|
|
currentDir, err := os.Getwd()
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.Chdir(localTmpDir))
|
|
defer os.Chdir(currentDir) //nolint:errcheck
|
|
|
|
// 3. Make Remote and Local Diverge
|
|
// Remote Action:
|
|
_, err = remoteRepo.GetGitRepository().Commit(emptyTreeHash, refName, "Another remote commit", false)
|
|
require.NoError(t, err)
|
|
require.NoError(t, remoteRepo.RecordRSLEntryForReference(t.Context(), refName, false, rslopts.WithRecordLocalOnly()))
|
|
|
|
// Local Action:
|
|
localRepo, err := gittuf.LoadRepository(".")
|
|
require.NoError(t, err)
|
|
_, err = localRepo.GetGitRepository().Commit(emptyTreeHash, refName, "Local commit", false)
|
|
require.NoError(t, err)
|
|
require.NoError(t, localRepo.RecordRSLEntryForReference(t.Context(), refName, false, rslopts.WithRecordLocalOnly()))
|
|
|
|
// 4. Test Sync without --overwrite (should catch divergence)
|
|
_, stdOut, _, err := cmd.ExecuteCommandC(New())
|
|
assert.NoError(t, err)
|
|
|
|
outputStr := stdOut.String()
|
|
assert.Contains(t, outputStr, "References have diverged:")
|
|
assert.Contains(t, outputStr, "To apply upstream changes locally, rerun the command with --overwrite")
|
|
|
|
// 5. Test Sync with --overwrite (should successfully overwrite)
|
|
_, _, _, err = cmd.ExecuteCommandC(New(), "--overwrite")
|
|
assert.NoError(t, err)
|
|
})
|
|
}
|