Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0afcbae783 |
@@ -28,7 +28,6 @@ const (
|
||||
testFile1 = "/testdir/file1.txt"
|
||||
testMF = "/testdir/test.mf"
|
||||
testOutput = "/output.mf"
|
||||
testOutputTmp = "/output.mf.tmp"
|
||||
testManifest = "/manifest.mf"
|
||||
testFlagBase = "--base"
|
||||
testFlagNoExtra = "--no-extra-files"
|
||||
@@ -683,7 +682,7 @@ func TestGenerateAtomicWriteNoTempFileOnSuccess(t *testing.T) {
|
||||
assert.True(t, exists, "output file should exist")
|
||||
|
||||
// Verify temp file does NOT exist
|
||||
tmpExists, err := afero.Exists(fs, testOutputTmp)
|
||||
tmpExists, err := afero.Exists(fs, manifestTempPath(testOutput))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should not exist after successful generation")
|
||||
@@ -715,7 +714,7 @@ func TestGenerateAtomicWriteOverwriteWithForce(t *testing.T) {
|
||||
"manifest should be overwritten")
|
||||
|
||||
// Verify temp file does NOT exist
|
||||
tmpExists, err := afero.Exists(fs, testOutputTmp)
|
||||
tmpExists, err := afero.Exists(fs, manifestTempPath(testOutput))
|
||||
require.NoError(t, err)
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should not exist after successful generation")
|
||||
@@ -822,9 +821,8 @@ func TestGenerateDefaultOutputLeftOutOfListing(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestGenerateLeavesLeftoverTempFileOutOfListing runs gen where an
|
||||
// interrupted run left its temp file, index.mf.tmp, beside the output:
|
||||
// the leftover is not listed, and gen does not fail when it overwrites
|
||||
// it.
|
||||
// interrupted run left its temp file beside the output: the leftover is
|
||||
// not listed, and gen does not fail when it overwrites it.
|
||||
func TestGenerateLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -833,7 +831,7 @@ func TestGenerateLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
writeTestFile(t, fs, filepath.Join(root, testFileTxt), "hello")
|
||||
writeTestFile(t, fs, output+".tmp", "part of a manifest")
|
||||
writeTestFile(t, fs, manifestTempPath(output), "part of a manifest")
|
||||
|
||||
opts := testOpts([]string{testApp, cmdGenerate, "-q", "-o", output, root}, fs)
|
||||
require.Equal(t, 0, runCLI(opts), "stderr: %s", testStderr(t, opts))
|
||||
@@ -863,7 +861,7 @@ func TestGenerateAtomicWriteUsesTemp(t *testing.T) {
|
||||
exists, _ := afero.Exists(fs, testOutput)
|
||||
assert.True(t, exists, "output file should exist")
|
||||
|
||||
tmpExists, _ := afero.Exists(fs, testOutputTmp)
|
||||
tmpExists, _ := afero.Exists(fs, manifestTempPath(testOutput))
|
||||
assert.False(t, tmpExists, "temp file should be cleaned up")
|
||||
|
||||
// Verify manifest is valid (not empty)
|
||||
@@ -929,7 +927,7 @@ func TestGenerateAtomicWriteCleansUpOnError(t *testing.T) {
|
||||
"output file should not exist after failed generation (atomic write)")
|
||||
|
||||
// Temp file should also not exist
|
||||
tmpExists, _ := afero.Exists(baseFs, testOutputTmp)
|
||||
tmpExists, _ := afero.Exists(baseFs, manifestTempPath(testOutput))
|
||||
assert.False(t, tmpExists,
|
||||
"temp file should be cleaned up after failed generation")
|
||||
}
|
||||
|
||||
+17
-23
@@ -57,8 +57,7 @@ type freshenEntry struct {
|
||||
type freshenScanner struct {
|
||||
fs afero.Fs
|
||||
absBase string
|
||||
manifestInfo fs.FileInfo // the manifest being freshened
|
||||
tmpInfo fs.FileInfo // its leftover temp file; nil when there is none
|
||||
excluded []fs.FileInfo // files left out of the listing
|
||||
includeDotfiles bool
|
||||
followSymlinks bool
|
||||
showProgress bool
|
||||
@@ -182,11 +181,10 @@ func (s *freshenScanner) walk(path string, info fs.FileInfo, walkErr error) erro
|
||||
info = realInfo
|
||||
}
|
||||
|
||||
// Skip the manifest file itself and a temp file left beside it by an
|
||||
// interrupted run, however their paths are spelled; gen leaves out its
|
||||
// output file the same way. os.SameFile never matches a nil FileInfo.
|
||||
if os.SameFile(info, s.manifestInfo) || os.SameFile(info, s.tmpInfo) {
|
||||
return nil
|
||||
for _, excluded := range s.excluded {
|
||||
if os.SameFile(info, excluded) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
s.scanCount++
|
||||
@@ -311,7 +309,7 @@ func (h *freshenHasher) processEntry(e *freshenEntry) error {
|
||||
func writeFreshenedManifest(
|
||||
ctx context.Context, afs afero.Fs, builder *mfer.Builder, manifestPath string,
|
||||
) error {
|
||||
tmpPath := manifestPath + ".tmp"
|
||||
tmpPath := manifestTempPath(manifestPath)
|
||||
|
||||
outFile, err := afs.Create(tmpPath)
|
||||
if err != nil {
|
||||
@@ -368,33 +366,29 @@ func (mfa *CLIApp) freshenScan(
|
||||
startScan := time.Now()
|
||||
showProgress := ctx.Bool("progress")
|
||||
|
||||
manifestInfo, err := mfa.Fs.Stat(manifestPath)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("freshen: %w", err)
|
||||
}
|
||||
// Leave out the manifest and a temp file left by an interrupted run,
|
||||
// as gen does. A path that cannot be stat'd, normally because no file
|
||||
// is there, needs no leaving out.
|
||||
var excluded []fs.FileInfo
|
||||
|
||||
// The temp file writeFreshenedManifest writes, if an interrupted run
|
||||
// left one. One that cannot be stat'd, normally because it does not
|
||||
// exist, needs no leaving out.
|
||||
var tmpInfo fs.FileInfo
|
||||
|
||||
info, err := mfa.Fs.Stat(manifestPath + ".tmp")
|
||||
if err == nil {
|
||||
tmpInfo = info
|
||||
for _, p := range []string{manifestPath, manifestTempPath(manifestPath)} {
|
||||
info, err := mfa.Fs.Stat(p)
|
||||
if err == nil {
|
||||
excluded = append(excluded, info)
|
||||
}
|
||||
}
|
||||
|
||||
scanner := &freshenScanner{
|
||||
fs: mfa.Fs,
|
||||
absBase: absBase,
|
||||
manifestInfo: manifestInfo,
|
||||
tmpInfo: tmpInfo,
|
||||
excluded: excluded,
|
||||
includeDotfiles: ctx.Bool("include-dotfiles"),
|
||||
followSymlinks: ctx.Bool("follow-symlinks"),
|
||||
showProgress: showProgress,
|
||||
existingByPath: existingByPath,
|
||||
}
|
||||
|
||||
err = afero.Walk(mfa.Fs, absBase, scanner.walk)
|
||||
err := afero.Walk(mfa.Fs, absBase, scanner.walk)
|
||||
|
||||
if showProgress {
|
||||
log.ProgressDone()
|
||||
|
||||
@@ -133,8 +133,8 @@ func TestFreshenLeavesManifestOutOfListing(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestFreshenLeavesLeftoverTempFileOutOfListing freshens a manifest where
|
||||
// an interrupted run left its temp file, index.mf.tmp, beside it: the
|
||||
// leftover is not listed.
|
||||
// an interrupted run left its temp file beside it: the leftover is not
|
||||
// listed.
|
||||
func TestFreshenLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -147,7 +147,7 @@ func TestFreshenLeavesLeftoverTempFileOutOfListing(t *testing.T) {
|
||||
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")
|
||||
writeTestFile(t, fs, manifestTempPath(manifestPath), "part of a manifest")
|
||||
// A new file gives freshen something to write.
|
||||
writeTestFile(t, fs, filepath.Join(root, "added.txt"), "added")
|
||||
|
||||
|
||||
+5
-2
@@ -89,12 +89,15 @@ func (mfa *CLIApp) collectInputPaths(args cli.Args) ([]string, error) {
|
||||
|
||||
// buildScannerOptions constructs scanner options from the CLI flags.
|
||||
func (mfa *CLIApp) buildScannerOptions(ctx *cli.Context) *mfer.ScannerOptions {
|
||||
output := ctx.String("output")
|
||||
opts := &mfer.ScannerOptions{
|
||||
IncludeDotfiles: ctx.Bool("include-dotfiles"),
|
||||
FollowSymLinks: ctx.Bool("follow-symlinks"),
|
||||
IncludeTimestamps: ctx.Bool("include-timestamps"),
|
||||
Fs: mfa.Fs,
|
||||
OutputPath: ctx.String("output"),
|
||||
// Neither a manifest being replaced nor a temp file left by an
|
||||
// interrupted run belongs in the new manifest.
|
||||
ExcludePaths: []string{output, manifestTempPath(output)},
|
||||
}
|
||||
|
||||
// Set seed for deterministic UUID if provided
|
||||
@@ -218,7 +221,7 @@ func (mfa *CLIApp) generateManifestOperation(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
// Create temp file for atomic write
|
||||
tmpPath := outputPath + ".tmp"
|
||||
tmpPath := manifestTempPath(outputPath)
|
||||
|
||||
outFile, err := mfa.Fs.Create(tmpPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -32,6 +32,12 @@ const (
|
||||
defaultManifestName = "index.mf"
|
||||
)
|
||||
|
||||
// manifestTempPath returns the temp file gen and freshen write a manifest
|
||||
// to before renaming it to out.
|
||||
func manifestTempPath(out string) string {
|
||||
return out + ".tmp"
|
||||
}
|
||||
|
||||
// errUnknownCommand indicates an unrecognized command argument.
|
||||
var errUnknownCommand = errors.New("unknown command")
|
||||
|
||||
|
||||
+12
-25
@@ -58,15 +58,8 @@ type ScannerOptions struct {
|
||||
SigningOptions *SigningOptions
|
||||
// Seed, if set, derives a deterministic UUID from this seed.
|
||||
Seed string
|
||||
// OutputPath, if set, is the file the manifest will be written to.
|
||||
// Enumeration leaves out that file and OutputPath + ".tmp", the temp
|
||||
// file mfer gen writes first, if they exist when the scanner is
|
||||
// created, however their paths are spelled (through a symlink, a hard
|
||||
// link, or in different letter case), so neither a manifest being
|
||||
// replaced nor a temp file left by an interrupted run is ever listed.
|
||||
// Files are matched with os.SameFile, which only recognizes files on
|
||||
// the operating system's filesystem.
|
||||
OutputPath string
|
||||
// ExcludePaths lists files to leave out of the listing, matched with os.SameFile.
|
||||
ExcludePaths []string
|
||||
}
|
||||
|
||||
// FileEntry represents a file that has been enumerated.
|
||||
@@ -85,8 +78,7 @@ type Scanner struct {
|
||||
totalBytes FileSize // cached sum of all file sizes
|
||||
options *ScannerOptions
|
||||
fs afero.Fs
|
||||
outputInfo fs.FileInfo // the existing output file; nil when there is none
|
||||
tmpInfo fs.FileInfo // its leftover temp file; nil when there is none
|
||||
excluded []fs.FileInfo // the files named in ExcludePaths that exist
|
||||
}
|
||||
|
||||
// NewScanner creates a new Scanner with default options.
|
||||
@@ -111,17 +103,12 @@ func NewScannerWithOptions(opts *ScannerOptions) *Scanner {
|
||||
fs: fs,
|
||||
}
|
||||
|
||||
if opts.OutputPath != "" {
|
||||
// A file that cannot be stat'd, normally because it does not
|
||||
// exist, needs no leaving out.
|
||||
info, err := s.fs.Stat(opts.OutputPath)
|
||||
// A path that cannot be stat'd, normally because no file is there,
|
||||
// needs no leaving out.
|
||||
for _, p := range opts.ExcludePaths {
|
||||
info, err := s.fs.Stat(p)
|
||||
if err == nil {
|
||||
s.outputInfo = info
|
||||
}
|
||||
|
||||
info, err = s.fs.Stat(opts.OutputPath + ".tmp")
|
||||
if err == nil {
|
||||
s.tmpInfo = info
|
||||
s.excluded = append(s.excluded, info)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,10 +450,10 @@ func (s *Scanner) enumerateFileWithInfo(
|
||||
info = realInfo
|
||||
}
|
||||
|
||||
// Neither the manifest being written nor its temp file is one of the
|
||||
// files it lists. os.SameFile never matches a nil FileInfo.
|
||||
if os.SameFile(info, s.outputInfo) || os.SameFile(info, s.tmpInfo) {
|
||||
return nil
|
||||
for _, excluded := range s.excluded {
|
||||
if os.SameFile(info, excluded) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
entry := &FileEntry{
|
||||
|
||||
Reference in New Issue
Block a user