Default the manifest to index.mf and keep it out of its own listing (closes #100)
check / check (push) Failing after 1s

mfer gen now writes index.mf instead of .index.mf, the name fetch
requests and the README calls the standard filename. Given a
directory, check, freshen, list and export look only for index.mf;
.index.mf is no longer recognized. Because index.mf is not hidden, gen
and freshen now leave the manifest they write out of its own listing by
file identity (os.SameFile), so it is recognized however its path is
spelled, through a symlink included, while an ordinary file of the same
name elsewhere in the tree is still listed.

Model: opus-5-5
This commit is contained in:
2026-10-04 09:49:04 +00:00
parent 588c1bae74
commit 29bf99f176
13 changed files with 180 additions and 42 deletions
+33 -4
View File
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"os"
"path/filepath"
"testing"
"time"
@@ -29,7 +30,7 @@ func (s stubFileInfo) IsDir() bool { return false }
func (s stubFileInfo) Sys() any { return nil }
// setupFreshenDir populates /testdir with two files, scans it, and
// writes the resulting manifest to /testdir/.index.mf.
// writes the resulting manifest to /testdir/index.mf.
func setupFreshenDir(t *testing.T, fs afero.Fs) {
t.Helper()
@@ -48,7 +49,7 @@ func setupFreshenDir(t *testing.T, fs afero.Fs) {
// Write manifest to filesystem
require.NoError(t,
afero.WriteFile(fs, "/testdir/.index.mf", manifestBuf.Bytes(), 0o644))
afero.WriteFile(fs, "/testdir/index.mf", manifestBuf.Bytes(), 0o644))
}
func TestFreshenUnchanged(t *testing.T) {
@@ -58,7 +59,7 @@ func TestFreshenUnchanged(t *testing.T) {
setupFreshenDir(t, fs)
// Parse manifest to verify
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/.index.mf")
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/index.mf")
require.NoError(t, err)
assert.Len(t, manifest.Files(), 2)
}
@@ -70,7 +71,7 @@ func TestFreshenWithChanges(t *testing.T) {
setupFreshenDir(t, fs)
// Verify initial manifest has 2 files
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/.index.mf")
manifest, err := mfer.NewManifestFromFile(fs, "/testdir/index.mf")
require.NoError(t, err)
assert.Len(t, manifest.Files(), 2)
@@ -95,6 +96,34 @@ func TestFreshenWithChanges(t *testing.T) {
assert.Equal(t, "modified content2", string(content))
}
// TestFreshenLeavesManifestOutOfListing freshens a manifest kept in a
// subdirectory of the tree it lists: the manifest is not listed, while an
// ordinary file of the same name at the top of the tree is. The manifest
// is recognized by file identity, which needs the real filesystem.
func TestFreshenLeavesManifestOutOfListing(t *testing.T) {
t.Parallel()
root := t.TempDir()
manifestPath := filepath.Join(root, "sub", "listing.mf")
fs := afero.NewOsFs()
require.NoError(t, fs.MkdirAll(filepath.Join(root, "sub"), 0o750))
writeTestFile(t, fs, filepath.Join(root, "readme.txt"), "hello")
writeTestFile(t, fs, filepath.Join(root, "listing.mf"), "an ordinary file")
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", manifestPath, root}, fs)
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
// A new file gives freshen something to write.
writeTestFile(t, fs, filepath.Join(root, "new.txt"), "new")
opts = testOpts([]string{testApp, "freshen", "-q", testFlagBase, root, manifestPath}, fs)
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
assert.ElementsMatch(t, []string{"readme.txt", "listing.mf", "new.txt"},
manifestPaths(t, fs, manifestPath))
}
// TestFreshenRecordEntryMtimePresence pins the behavior of recordEntry
// with respect to MFFilePath.Mtime, which is a message pointer with
// proto3 field presence and may legitimately be absent.