diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp
index 52033ff0bd3e4bbd77c56cb0e86527d8dd8d1b29..ba68b0871005db62c53939f7858b8950c7d57fc6 100644
--- a/src/libs/utils/synchronousprocess.cpp
+++ b/src/libs/utils/synchronousprocess.cpp
@@ -476,8 +476,8 @@ void SynchronousProcess::stdErrReady()
 
 QString SynchronousProcess::convertOutput(const QByteArray &ba) const
 {
-    QString output = d->m_codec ? d->m_codec->toUnicode(ba) : QString::fromLocal8Bit(ba.constData(), ba.size());
-    return output.remove(QLatin1Char('\r'));
+    return normalizeNewlines(d->m_codec ? d->m_codec->toUnicode(ba)
+                                        : QString::fromLocal8Bit(ba.constData(), ba.size()));
 }
 
 void SynchronousProcess::processStdOut(bool emitSignals)
@@ -676,6 +676,25 @@ QString SynchronousProcess::locateBinary(const QString &path, const QString &bin
     return QString();
 }
 
+QString SynchronousProcess::normalizeNewlines(const QString &text)
+{
+    const QChar cr(QLatin1Char('\r'));
+    const QChar lf(QLatin1Char('\n'));
+    QString res;
+    res.reserve(text.size());
+    for (int i = 0, count = text.size(); i < count; ++i) {
+        const QChar c = text.at(i);
+        if (c == cr) {
+            res += lf;
+            if (i + 1 < count && text.at(i + 1) == lf)
+                ++i;
+        } else {
+            res += c;
+        }
+    }
+    return res;
+}
+
 QString SynchronousProcess::locateBinary(const QString &binary)
 {
     const QByteArray path = qgetenv("PATH");
diff --git a/src/libs/utils/synchronousprocess.h b/src/libs/utils/synchronousprocess.h
index 6db6e86841d346f20c156b27a858c00d862436b6..9172d8be58b59a0c23ae7dfa4263bc2caa27c5b0 100644
--- a/src/libs/utils/synchronousprocess.h
+++ b/src/libs/utils/synchronousprocess.h
@@ -138,6 +138,8 @@ public:
     static QString locateBinary(const QString &binary);
     static QString locateBinary(const QString &path, const QString &binary);
 
+    static QString normalizeNewlines(const QString &text);
+
 signals:
     void stdOut(const QByteArray &data, bool firstTime);
     void stdErr(const QByteArray &data, bool firstTime);
diff --git a/src/plugins/coreplugin/outputwindow.cpp b/src/plugins/coreplugin/outputwindow.cpp
index 2c5499571efce3dc3a68ff4712ba74acae7a31ff..8e70dea29b41e45ac34a634982cc570c728d5a0c 100644
--- a/src/plugins/coreplugin/outputwindow.cpp
+++ b/src/plugins/coreplugin/outputwindow.cpp
@@ -33,6 +33,8 @@
 #include "coreconstants.h"
 #include "icore.h"
 
+#include <utils/synchronousprocess.h>
+
 #include <QAction>
 #include <QScrollBar>
 
@@ -200,8 +202,7 @@ void OutputWindow::setMaxLineCount(int count)
 
 void OutputWindow::appendMessage(const QString &output, OutputFormat format)
 {
-    QString out = output;
-    out.remove(QLatin1Char('\r'));
+    const QString out = Utils::SynchronousProcess::normalizeNewlines(output);
     setMaximumBlockCount(m_maxLineCount);
     const bool atBottom = isScrollbarAtBottom();
 
@@ -251,8 +252,7 @@ void OutputWindow::appendMessage(const QString &output, OutputFormat format)
 // TODO rename
 void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format)
 {
-    QString text = textIn;
-    text.remove(QLatin1Char('\r'));
+    const QString text = Utils::SynchronousProcess::normalizeNewlines(textIn);
     if (m_maxLineCount > 0 && document()->blockCount() >= m_maxLineCount)
         return;
     const bool atBottom = isScrollbarAtBottom();
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index 7dab68eba60b16e3b392e380970ca25c3693d263..c7fe23bc01a99f68099ad32c620f769c4527b567 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -744,9 +744,7 @@ Core::IEditor *locateEditor(const char *property, const QString &entry)
 // Return converted command output, remove '\r' read on Windows
 static inline QString commandOutputFromLocal8Bit(const QByteArray &a)
 {
-    QString output = QString::fromLocal8Bit(a);
-    output.remove(QLatin1Char('\r'));
-    return output;
+    return Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(a));
 }
 
 // Return converted command output split into lines
@@ -3398,7 +3396,7 @@ QString GitClient::readConfig(const QString &workingDirectory, const QStringList
                              VcsBasePlugin::SuppressCommandLogging))
         return QString();
     if (Utils::HostOsInfo::isWindowsHost())
-        return QString::fromUtf8(outputText).remove(QLatin1Char('\r'));
+        return Utils::SynchronousProcess::normalizeNewlines(QString::fromUtf8(outputText));
     return commandOutputFromLocal8Bit(outputText);
 }
 
diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp
index 3bb4ea9e55d0730e294482585958c1a32bd594ec..274cd57a651f6ac44121871e0c176722fcd4abd5 100644
--- a/src/plugins/mercurial/mercurialclient.cpp
+++ b/src/plugins/mercurial/mercurialclient.cpp
@@ -187,8 +187,8 @@ QStringList MercurialClient::parentRevisionsSync(const QString &workingDirectory
     QByteArray outputData;
     if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
         return QStringList();
-    QString output = QString::fromLocal8Bit(outputData);
-    output.remove(QLatin1Char('\r'));
+    const QString output = Utils::SynchronousProcess::normalizeNewlines(
+                QString::fromLocal8Bit(outputData));
     /* Looks like: \code
 changeset:   0:031a48610fba
 user: ...
@@ -230,8 +230,7 @@ QString MercurialClient::shortDescriptionSync(const QString &workingDirectory,
     QByteArray outputData;
     if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
         return revision;
-    description = QString::fromLocal8Bit(outputData);
-    description.remove(QLatin1Char('\r'));
+    description = Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(outputData));
     if (description.endsWith(QLatin1Char('\n')))
         description.truncate(description.size() - 1);
     return description;
diff --git a/src/plugins/vcsbase/command.cpp b/src/plugins/vcsbase/command.cpp
index 55eb8b9f5ce3b8a529b2315443cb6b036d5d9e55..ed0291075b9522f33a13c5a7120c8322e8daa20b 100644
--- a/src/plugins/vcsbase/command.cpp
+++ b/src/plugins/vcsbase/command.cpp
@@ -253,15 +253,14 @@ void Command::run()
         exitCode = process->exitCode();
     }
 
-    QString stdOutS = d->m_codec ? d->m_codec->toUnicode(stdOut)
-                                 : QString::fromLocal8Bit(stdOut.constData(), stdOut.size());
-    stdOutS.remove(QLatin1Char('\r'));
-
     d->m_lastExecSuccess = ok;
     d->m_lastExecExitCode = exitCode;
 
-    if (ok)
-        emit output(stdOutS);
+    if (ok) {
+        emit output(Utils::SynchronousProcess::normalizeNewlines(
+                        d->m_codec ? d->m_codec->toUnicode(stdOut)
+                                   : QString::fromLocal8Bit(stdOut.constData(), stdOut.size())));
+    }
 
     if (!error.isEmpty())
         emit errorText(error);
diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp
index 32030abb5e6e5f6349f7669ecaebe7812e4e72f0..99002213031759a66640edc448de638f9bfcfbe3 100644
--- a/src/plugins/vcsbase/vcsbaseclient.cpp
+++ b/src/plugins/vcsbase/vcsbaseclient.cpp
@@ -189,9 +189,8 @@ bool VcsBaseClient::synchronousCreateRepository(const QString &workingDirectory,
     QByteArray outputData;
     if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
         return false;
-    QString output = QString::fromLocal8Bit(outputData);
-    output.remove(QLatin1Char('\r'));
-    ::vcsOutputWindow()->append(output);
+    ::vcsOutputWindow()->append(
+                Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(outputData)));
 
     resetCachedVcsInfo(workingDirectory);
 
diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp
index 703b79f5bbd541842f75ac4ab73a17342ab51c94..9d1905ccc7cf85d418730bc8fa4bca2f775d2c48 100644
--- a/src/plugins/vcsbase/vcsbaseplugin.cpp
+++ b/src/plugins/vcsbase/vcsbaseplugin.cpp
@@ -863,15 +863,15 @@ static SynchronousProcessResponse runVcsFullySynchronously(const QString &workin
                                                             &stdOut, &stdErr, true);
 
     if (!stdErr.isEmpty()) {
-        response.stdErr = (outputCodec ? outputCodec->toUnicode(stdErr) : QString::fromLocal8Bit(stdErr));
-        response.stdErr.remove(QLatin1Char('\r'));
+        response.stdErr = Utils::SynchronousProcess::normalizeNewlines(
+                    outputCodec ? outputCodec->toUnicode(stdErr) : QString::fromLocal8Bit(stdErr));
         if (!(flags & VcsBasePlugin::SuppressStdErrInLogWindow))
             outputWindow->append(response.stdErr);
     }
 
     if (!stdOut.isEmpty()) {
-        response.stdOut = (outputCodec ? outputCodec->toUnicode(stdOut) : QString::fromLocal8Bit(stdOut));
-        response.stdOut.remove(QLatin1Char('\r'));
+        response.stdOut = Utils::SynchronousProcess::normalizeNewlines(
+                    outputCodec ? outputCodec->toUnicode(stdOut) : QString::fromLocal8Bit(stdOut));
         if (flags & VcsBasePlugin::ShowStdOutInLogWindow) {
             if (flags & VcsBasePlugin::SilentOutput)
                 outputWindow->appendSilently(response.stdOut);