Preserve windows line endings in pubspec.lock if they are already there (#2489)

diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 7cff07c..04a7ead 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -7,6 +7,7 @@
 import 'dart:io';
 
 import 'package:collection/collection.dart';
+import 'package:meta/meta.dart';
 // ignore: deprecated_member_use
 import 'package:package_config/packages_file.dart' as packages_file;
 import 'package:path/path.dart' as p;
@@ -743,10 +744,18 @@
   }
 
   /// Saves a list of concrete package versions to the `pubspec.lock` file.
+  ///
+  /// Will use Windows line endings (`\r\n`) if a `pubspec.lock` exists, and
+  /// uses that.
   void _saveLockFile(SolveResult result) {
     _lockFile = result.lockFile;
-    var lockFilePath = root.path('pubspec.lock');
-    writeTextFile(lockFilePath, _lockFile.serialize(root.dir));
+
+    final windowsLineEndings = fileExists(lockFilePath) &&
+        detectWindowsLineEndings(readTextFile(lockFilePath));
+
+    final serialized = _lockFile.serialize(root.dir);
+    writeTextFile(lockFilePath,
+        windowsLineEndings ? serialized.replaceAll('\n', '\r\n') : serialized);
   }
 
   /// If the entrypoint uses the old-style `.pub` cache directory, migrates it
@@ -766,3 +775,22 @@
     renameDir(oldPath, newPath);
   }
 }
+
+/// Returns `true` if the [text] looks like it uses windows line endings.
+///
+/// The heuristic used is to count all `\n` in the text and if stricly more than
+/// half of them are preceded by `\r` we report `true`.
+@visibleForTesting
+bool detectWindowsLineEndings(String text) {
+  var index = -1;
+  var unixNewlines = 0;
+  var windowsNewlines = 0;
+  while ((index = text.indexOf('\n', index + 1)) != -1) {
+    if (index != 0 && text[index - 1] == '\r') {
+      windowsNewlines++;
+    } else {
+      unixNewlines++;
+    }
+  }
+  return windowsNewlines > unixNewlines;
+}
diff --git a/test/get/preserve_lock_file_line_endings_test.dart b/test/get/preserve_lock_file_line_endings_test.dart
new file mode 100644
index 0000000..15502a6
--- /dev/null
+++ b/test/get/preserve_lock_file_line_endings_test.dart
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:pub/src/entrypoint.dart';
+import 'package:test/test.dart';
+import 'package:path/path.dart' as path;
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+Future<void> main() async {
+  test('pub get creates lock file with unix line endings if none exist',
+      () async {
+    await d.appDir().create();
+
+    await pubGet();
+
+    await d
+        .file(path.join(appPath, 'pubspec.lock'),
+            allOf(contains('\n'), isNot(contains('\r\n'))))
+        .validate();
+  });
+
+  test('pub get preserves line endings of lock file', () async {
+    await d.appDir().create();
+
+    await pubGet();
+
+    final lockFile = d.file(path.join(appPath, 'pubspec.lock')).io;
+    lockFile.writeAsStringSync(
+        lockFile.readAsStringSync().replaceAll('\n', '\r\n'));
+    await d.dir(appPath, [d.file('pubspec.lock', contains('\r\n'))]).validate();
+
+    await pubGet();
+
+    await d.dir(appPath, [d.file('pubspec.lock', contains('\r\n'))]).validate();
+  });
+
+  test('windows line endings detection', () {
+    expect(detectWindowsLineEndings(''), false);
+    expect(detectWindowsLineEndings('\n'), false);
+    expect(detectWindowsLineEndings('\r'), false);
+    expect(detectWindowsLineEndings('\r\n'), true);
+    expect(detectWindowsLineEndings('\n\r\n'), false);
+    expect(detectWindowsLineEndings('\r\n\n'), false);
+    expect(detectWindowsLineEndings('\r\n\r\n'), true);
+    expect(detectWindowsLineEndings('\n\n'), false);
+    expect(detectWindowsLineEndings('abcd\n'), false);
+    expect(detectWindowsLineEndings('abcd\nefg'), false);
+    expect(detectWindowsLineEndings('abcd\nefg\n'), false);
+    expect(detectWindowsLineEndings('\r\n'), true);
+    expect(detectWindowsLineEndings('abcd\r\nefg\n'), false);
+    expect(detectWindowsLineEndings('abcd\r\nefg\nhij\r\n'), true);
+    expect(detectWindowsLineEndings('''
+packages:\r
+  bar:\r
+    dependency: transitive\r
+    description: "bar desc"\r
+    source: fake\r
+    version: "1.2.3"\r
+sdks: {}\r
+'''), true);
+    expect(detectWindowsLineEndings('''
+packages:
+  bar:
+    dependency: transitive
+    description: "bar desc"
+    source: fake
+    version: "1.2.3"
+sdks: {}
+'''), false);
+  });
+}