diff --git a/build.go b/build.go index 2a480a546..c2e105939 100644 --- a/build.go +++ b/build.go @@ -1293,7 +1293,7 @@ func zipFile(out string, files []archiveFile) { if err != nil { log.Fatal(err) } - bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\n', '\r'}, -1) + bs = bytes.Replace(bs, []byte{'\n'}, []byte{'\r', '\n'}, -1) fh.UncompressedSize = uint32(len(bs)) fh.UncompressedSize64 = uint64(len(bs)) diff --git a/lib/config/config_test.go b/lib/config/config_test.go index ec7db5763..861c57839 100644 --- a/lib/config/config_test.go +++ b/lib/config/config_test.go @@ -594,6 +594,41 @@ func TestNewSaveLoad(t *testing.T) { } } +func TestWindowsLineEndings(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Windows specific") + } + + dir, err := os.MkdirTemp("", "syncthing-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + path := filepath.Join(dir, "config.xml") + os.Remove(path) + defer os.Remove(path) + + intCfg := New(device1) + cfg := wrap(path, intCfg, device1) + defer cfg.stop() + + if err := cfg.Save(); err != nil { + t.Error(err) + } + + bs, err := os.ReadFile(path) + if err != nil { + t.Error(err) + } + + unixLineEndings := bytes.Count(bs, []byte("\n")) + windowsLineEndings := bytes.Count(bs, []byte("\r\n")) + if unixLineEndings == 0 || windowsLineEndings != unixLineEndings { + t.Error("expected there to be a non-zero number of Windows line endings") + } +} + func TestPrepare(t *testing.T) { var cfg Configuration diff --git a/lib/config/wrapper.go b/lib/config/wrapper.go index 4d5c8f93c..4c52b0ce0 100644 --- a/lib/config/wrapper.go +++ b/lib/config/wrapper.go @@ -502,7 +502,7 @@ func (w *wrapper) Save() error { return err } - if err := w.cfg.WriteXML(fd); err != nil { + if err := w.cfg.WriteXML(osutil.LineEndingsWriter(fd)); err != nil { l.Debugln("WriteXML:", err) fd.Close() return err diff --git a/lib/ignore/ignore.go b/lib/ignore/ignore.go index 9b13c1e63..243f178d9 100644 --- a/lib/ignore/ignore.go +++ b/lib/ignore/ignore.go @@ -595,8 +595,9 @@ func WriteIgnores(filesystem fs.Filesystem, path string, content []string) error return err } + wr := osutil.LineEndingsWriter(fd) for _, line := range content { - fmt.Fprintln(fd, line) + fmt.Fprintln(wr, line) } if err := fd.Close(); err != nil { diff --git a/lib/ignore/ignore_test.go b/lib/ignore/ignore_test.go index 9781cbdfa..b1f19c5a9 100644 --- a/lib/ignore/ignore_test.go +++ b/lib/ignore/ignore_test.go @@ -1192,3 +1192,42 @@ func TestEmptyPatterns(t *testing.T) { } } } + +func TestWindowsLineEndings(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skip("Windows specific") + } + + lines := "foo\nbar\nbaz\n" + + dir, err := os.MkdirTemp("", "syncthing-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + ffs := fs.NewFilesystem(fs.FilesystemTypeBasic, dir) + m := New(ffs) + if err := m.Parse(strings.NewReader(lines), ".stignore"); err != nil { + t.Fatal(err) + } + if err := WriteIgnores(ffs, ".stignore", m.Lines()); err != nil { + t.Fatal(err) + } + + fd, err := ffs.Open(".stignore") + if err != nil { + t.Fatal(err) + } + bs, err := io.ReadAll(fd) + fd.Close() + if err != nil { + t.Fatal(err) + } + + unixLineEndings := bytes.Count(bs, []byte("\n")) + windowsLineEndings := bytes.Count(bs, []byte("\r\n")) + if unixLineEndings == 0 || windowsLineEndings != unixLineEndings { + t.Error("expected there to be a non-zero number of Windows line endings") + } +} diff --git a/lib/osutil/replacingwriter.go b/lib/osutil/replacingwriter.go index 71e86fd63..18be86f3e 100644 --- a/lib/osutil/replacingwriter.go +++ b/lib/osutil/replacingwriter.go @@ -9,6 +9,7 @@ package osutil import ( "bytes" "io" + "runtime" ) type ReplacingWriter struct { @@ -46,3 +47,16 @@ func (w ReplacingWriter) Write(bs []byte) (int, error) { return written, err } + +// LineEndingsWriter returns a writer that writes platform-appropriate line +// endings. (This is a no-op on non-Windows platforms.) +func LineEndingsWriter(w io.Writer) io.Writer { + if runtime.GOOS != "windows" { + return w + } + return &ReplacingWriter{ + Writer: w, + From: '\n', + To: []byte{'\r', '\n'}, + } +}