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 out of their own listing both the manifest they
write and a temp file an interrupted run left beside it, by file
identity (os.SameFile), so each is recognized however its path is
spelled, 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 10:54:31 +00:00
parent 0a9963002c
commit 97875a82bc
13 changed files with 259 additions and 44 deletions
+63 -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) {
@@ -59,7 +60,7 @@ func TestFreshenUnchanged(t *testing.T) {
// Parse manifest to verify
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
Path: "/testdir/.index.mf",
Path: "/testdir/index.mf",
Fs: fs,
})
require.NoError(t, err)
@@ -74,7 +75,7 @@ func TestFreshenWithChanges(t *testing.T) {
// Verify initial manifest has 2 files
manifest, err := mfer.NewManifestFromFile(&mfer.ManifestFromFileOptions{
Path: "/testdir/.index.mf",
Path: "/testdir/index.mf",
Fs: fs,
})
require.NoError(t, err)
@@ -101,6 +102,64 @@ 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, testFileTxt), "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, "added.txt"), "added")
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{testFileTxt, "listing.mf", "added.txt"},
manifestPaths(t, fs, manifestPath))
}
// TestFreshenLeavesLeftoverTempFileOutOfListing freshens a manifest where
// an interrupted run left its temp file, index.mf.tmp, beside it: the
// leftover is not listed.
func TestFreshenLeavesLeftoverTempFileOutOfListing(t *testing.T) {
t.Parallel()
root := t.TempDir()
manifestPath := filepath.Join(root, "index.mf")
fs := afero.NewOsFs()
writeTestFile(t, fs, filepath.Join(root, testFileTxt), "hello")
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", manifestPath, root}, fs)
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
writeTestFile(t, fs, manifestPath+".tmp", "part of a manifest")
// A new file gives freshen something to write.
writeTestFile(t, fs, filepath.Join(root, "added.txt"), "added")
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{testFileTxt, "added.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.