Compare commits

Author SHA1 Message Date
sneak 114ba0a29f Disable the deprecated gomodguard linter (closes #116)
check / check (push) Failing after 2s
golangci-lint v2.12 deprecates gomodguard in favour of gomodguard_v2.
With `default: all` both ran, and every lint run printed the
deprecation warning. Disable the old name, the same way wsl is
disabled for wsl_v5; gomodguard_v2 stays enabled. The old linter had
no settings here, so there is nothing to carry over.

Model: opus-5-5
2026-10-04 08:06:44 +00:00
13 changed files with 27 additions and 85 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ vendor.tzst
modcache.tzst modcache.tzst
# Generated manifest files # Generated manifest files
/index.mf .index.mf
# Secrets # Secrets
.env .env
+1
View File
@@ -16,6 +16,7 @@ linters:
- depguard # Dependency allow/block lists - depguard # Dependency allow/block lists
- godot # Requires comments to end with periods - godot # Requires comments to end with periods
- wsl # Deprecated, replaced by wsl_v5 - wsl # Deprecated, replaced by wsl_v5
- gomodguard # Deprecated, replaced by gomodguard_v2
- wrapcheck # Too verbose for internal packages - wrapcheck # Too verbose for internal packages
- varnamelen # Short names like db, id are idiomatic Go - varnamelen # Short names like db, id are idiomatic Go
settings: settings:
-2
View File
@@ -75,7 +75,5 @@ RUN version="${VERSION:-$(git describe --tags --always)}"; \
RUN ldd /mfer 2>&1 | grep -q 'not a dynamic executable' RUN ldd /mfer 2>&1 | grep -q 'not a dynamic executable'
FROM scratch FROM scratch
# scratch has no CA certificates; fetch needs them to verify HTTPS servers.
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /mfer /mfer COPY --from=builder /mfer /mfer
ENTRYPOINT ["/mfer"] ENTRYPOINT ["/mfer"]
+2 -2
View File
@@ -36,12 +36,12 @@ Generate a manifest for a directory tree, verify it later, and fetch a published
tree by URL: tree by URL:
```sh ```sh
# Write index.mf, a manifest of the files under the current directory. # Write .index.mf, a manifest of the files under the current directory.
bin/mfer gen . bin/mfer gen .
# Verify the files on disk against the manifest. Exits nonzero if any file # Verify the files on disk against the manifest. Exits nonzero if any file
# is missing or corrupted. # is missing or corrupted.
bin/mfer check index.mf bin/mfer check .index.mf
# Download and cryptographically verify a tree published over HTTP: mfer # Download and cryptographically verify a tree published over HTTP: mfer
# fetches <url>/index.mf, then downloads every file it lists. # fetches <url>/index.mf, then downloads every file it lists.
+15 -12
View File
@@ -75,22 +75,25 @@ func safeRateUint64(rate float64) uint64 {
return uint64(rate) return uint64(rate)
} }
// findManifest returns the path of the manifest with the default name in // findManifest looks for a manifest file in the given directory.
// dir, or an error if there is none. // It checks for index.mf and .index.mf, returning the first one found.
func findManifest(fs afero.Fs, dir string) (string, error) { func findManifest(fs afero.Fs, dir string) (string, error) {
path := filepath.Join(dir, defaultManifestName) candidates := []string{"index.mf", ".index.mf"}
for _, name := range candidates {
path := filepath.Join(dir, name)
exists, err := afero.Exists(fs, path) exists, err := afero.Exists(fs, path)
if err != nil { if err != nil {
return "", err return "", err
}
if exists {
return path, nil
}
} }
if !exists { return "", fmt.Errorf(
return "", fmt.Errorf("%w in %s (looked for %s)", "%w in %s (looked for index.mf and .index.mf)", errNoManifestFound, dir)
errNoManifestFound, dir, defaultManifestName)
}
return path, nil
} }
// fetchManifestToTemp downloads a manifest URL to a temporary file and // fetchManifestToTemp downloads a manifest URL to a temporary file and
-37
View File
@@ -638,43 +638,6 @@ func TestGenerateFailsWithoutForceWhenOutputExists(t *testing.T) {
assert.Equal(t, "existing", string(content), "original file should be preserved") assert.Equal(t, "existing", string(content), "original file should be preserved")
} }
// TestGenerateLeavesOutputOutOfListing overwrites an output file inside the
// scanned tree with --force: the old file is not listed, while a file named
// index.mf in a subdirectory still is.
func TestGenerateLeavesOutputOutOfListing(t *testing.T) {
t.Parallel()
for name, output := range map[string]string{
"default name": "/testdir/index.mf",
"other name in a subdirectory": "/testdir/sub/listing.mf",
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
fs := afero.NewMemMapFs()
require.NoError(t, fs.MkdirAll("/testdir/sub", 0o755))
writeTestFile(t, fs, "/testdir/readme.txt", "hello")
writeTestFile(t, fs, "/testdir/sub/index.mf", "an ordinary file")
writeTestFile(t, fs, output, "previous manifest")
opts := testOpts([]string{
testApp, cmdGenerate, "-q", "--force", "-o", output, testDir,
}, fs)
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
manifest, err := mfer.NewManifestFromFile(fs, output)
require.NoError(t, err)
paths := make([]string, 0, len(manifest.Files()))
for _, f := range manifest.Files() {
paths = append(paths, f.GetPath())
}
assert.ElementsMatch(t, []string{"readme.txt", "sub/index.mf"}, paths)
})
}
}
func TestGenerateAtomicWriteUsesTemp(t *testing.T) { func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
t.Parallel() t.Parallel()
+1 -1
View File
@@ -74,7 +74,7 @@ func TestNoManifestFoundMessage(t *testing.T) {
_, err := findManifest(afero.NewMemMapFs(), "/tmp/x") _, err := findManifest(afero.NewMemMapFs(), "/tmp/x")
require.ErrorIs(t, err, errNoManifestFound) require.ErrorIs(t, err, errNoManifestFound)
assert.EqualError(t, err, assert.EqualError(t, err,
"no manifest found in /tmp/x (looked for index.mf)") "no manifest found in /tmp/x (looked for index.mf and .index.mf)")
} }
func TestVerifyRequiredSignerMessages(t *testing.T) { func TestVerifyRequiredSignerMessages(t *testing.T) {
+3 -2
View File
@@ -313,7 +313,7 @@ func checkNoSymlinks(p string) error {
// resolveManifestURL takes a URL and returns the manifest URL. // resolveManifestURL takes a URL and returns the manifest URL.
// If the URL already ends with .mf, it's returned as-is. // If the URL already ends with .mf, it's returned as-is.
// Otherwise, the default manifest name is appended. // Otherwise, index.mf is appended.
func resolveManifestURL(inputURL string) (string, error) { func resolveManifestURL(inputURL string) (string, error) {
parsed, err := url.Parse(inputURL) parsed, err := url.Parse(inputURL)
if err != nil { if err != nil {
@@ -330,7 +330,8 @@ func resolveManifestURL(inputURL string) (string, error) {
parsed.Path += "/" parsed.Path += "/"
} }
parsed.Path += defaultManifestName // Append index.mf
parsed.Path += "index.mf"
return parsed.String(), nil return parsed.String(), nil
} }
+1 -1
View File
@@ -157,7 +157,7 @@ func (s *freshenScanner) walk(path string, info fs.FileInfo, walkErr error) erro
} }
// Skip the manifest file itself // Skip the manifest file itself
if relPath == s.manifestBase { if relPath == s.manifestBase || relPath == "."+s.manifestBase {
return nil return nil
} }
-1
View File
@@ -94,7 +94,6 @@ func (mfa *CLIApp) buildScannerOptions(ctx *cli.Context) *mfer.ScannerOptions {
FollowSymLinks: ctx.Bool("follow-symlinks"), FollowSymLinks: ctx.Bool("follow-symlinks"),
IncludeTimestamps: ctx.Bool("include-timestamps"), IncludeTimestamps: ctx.Bool("include-timestamps"),
Fs: mfa.Fs, Fs: mfa.Fs,
OutputPath: ctx.String("output"),
} }
// Set seed for deterministic UUID if provided // Set seed for deterministic UUID if provided
+1 -1
View File
@@ -65,7 +65,7 @@ func (mfa *CLIApp) openManifestReader(pathOrURL string) (io.ReadCloser, error) {
} }
// resolveManifestArg resolves the manifest path from CLI arguments. // resolveManifestArg resolves the manifest path from CLI arguments.
// HTTP(S) URLs are returned as-is. Directories are searched for index.mf. // HTTP(S) URLs are returned as-is. Directories are searched for index.mf/.index.mf.
// If no argument is given, the current directory is searched. // If no argument is given, the current directory is searched.
func (mfa *CLIApp) resolveManifestArg(ctx *cli.Context) (string, error) { func (mfa *CLIApp) resolveManifestArg(ctx *cli.Context) (string, error) {
if ctx.Args().Len() > 0 { if ctx.Args().Len() > 0 {
+1 -6
View File
@@ -24,11 +24,6 @@ const (
flagProgress = "progress" flagProgress = "progress"
manifestArgsUsage = "[manifest file]" manifestArgsUsage = "[manifest file]"
// defaultManifestName is the filename gen writes by default, the one
// looked for when a command is given a directory, and the one fetch
// appends to a directory URL.
defaultManifestName = "index.mf"
) )
// errUnknownCommand indicates an unrecognized command argument. // errUnknownCommand indicates an unrecognized command argument.
@@ -159,7 +154,7 @@ func (mfa *CLIApp) generateCommand() *cli.Command {
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "output", Name: "output",
Value: defaultManifestName, Value: "./.index.mf",
Aliases: []string{"o"}, Aliases: []string{"o"},
Usage: "Specify output filename", Usage: "Specify output filename",
}, },
+1 -19
View File
@@ -57,10 +57,6 @@ type ScannerOptions struct {
SigningOptions *SigningOptions SigningOptions *SigningOptions
// Seed, if set, derives a deterministic UUID from this seed. // Seed, if set, derives a deterministic UUID from this seed.
Seed string Seed string
// OutputPath, if set, is the file the manifest will be written to.
// Enumeration skips that one file, so a manifest written inside the
// scanned tree, or one it replaces there, is never listed.
OutputPath string
} }
// FileEntry represents a file that has been enumerated. // FileEntry represents a file that has been enumerated.
@@ -79,7 +75,6 @@ type Scanner struct {
totalBytes FileSize // cached sum of all file sizes totalBytes FileSize // cached sum of all file sizes
options *ScannerOptions options *ScannerOptions
fs afero.Fs fs afero.Fs
outputPath string // options.OutputPath made absolute; "" when unset
} }
// NewScanner creates a new Scanner with default options. // NewScanner creates a new Scanner with default options.
@@ -98,19 +93,11 @@ func NewScannerWithOptions(opts *ScannerOptions) *Scanner {
fs = afero.NewOsFs() fs = afero.NewOsFs()
} }
s := &Scanner{ return &Scanner{
files: make([]*FileEntry, 0), files: make([]*FileEntry, 0),
options: opts, options: opts,
fs: fs, fs: fs,
} }
if opts.OutputPath != "" {
// Abs fails only when there is no working directory, and then a
// relative output path could not be written either.
s.outputPath, _ = filepath.Abs(opts.OutputPath)
}
return s
} }
// EnumerateFile adds a single file to the scanner, calling stat() to get metadata. // EnumerateFile adds a single file to the scanner, calling stat() to get metadata.
@@ -421,11 +408,6 @@ func (s *Scanner) enumerateFileWithInfo(
// Compute absolute path for file reading // Compute absolute path for file reading
absPath := filepath.Join(basePath, cleanPath) absPath := filepath.Join(basePath, cleanPath)
// The manifest being written is not one of the files it lists.
if absPath == s.outputPath {
return nil
}
// Handle symlinks // Handle symlinks
if info.Mode()&fs.ModeSymlink != 0 { if info.Mode()&fs.ModeSymlink != 0 {
if !s.options.FollowSymLinks { if !s.options.FollowSymLinks {