Git: Avoid main loop blocking in instant blame

Query the author and encoding information asynchronous
after the current repository is changed.

Set the default codec to UTF-8, which should cover most
configurations (i.e. almost never be different).

In case requesting the information takes longer, the blame
mark is already created with the cached information.

In case the author or encoding changed, the blame mark
has to be recreated.

The call to refreshWorkingDirectory() is moved after the
widget checks in setupInstantBlame() to avoid requesting
these information in VCS editors.

Fixes: QTCREATORBUG-29151
Change-Id: I6feccbbed67c877f1015295f630dd63cf3ccf4a0
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Andre Hartmann
2023-05-28 07:29:54 +02:00
committed by André Hartmann
parent d15da2c0f9
commit 419f5416c5
3 changed files with 84 additions and 15 deletions

View File

@@ -822,14 +822,19 @@ FilePaths GitClient::unmanagedFiles(const FilePaths &filePaths) const
return res; return res;
} }
QTextCodec *GitClient::defaultCommitEncoding() const
{
// Set default commit encoding to 'UTF-8', when it's not set,
// to solve displaying error of commit log with non-latin characters.
return QTextCodec::codecForName("UTF-8");
}
QTextCodec *GitClient::encoding(GitClient::EncodingType encodingType, const FilePath &source) const QTextCodec *GitClient::encoding(GitClient::EncodingType encodingType, const FilePath &source) const
{ {
auto codec = [this](const FilePath &workingDirectory, const QString &configVar) { auto codec = [this](const FilePath &workingDirectory, const QString &configVar) {
const QString codecName = readConfigValue(workingDirectory, configVar).trimmed(); const QString codecName = readConfigValue(workingDirectory, configVar).trimmed();
// Set default commit encoding to 'UTF-8', when it's not set,
// to solve displaying error of commit log with non-latin characters.
if (codecName.isEmpty()) if (codecName.isEmpty())
return QTextCodec::codecForName("UTF-8"); return defaultCommitEncoding();
return QTextCodec::codecForName(codecName.toUtf8()); return QTextCodec::codecForName(codecName.toUtf8());
}; };
@@ -2618,11 +2623,10 @@ bool GitClient::readDataFromCommit(const FilePath &repoDirectory, const QString
return true; return true;
} }
Author GitClient::getAuthor(const Utils::FilePath &workingDirectory) Author GitClient::parseAuthor(const QString &authorInfo)
{ {
// The format is: // The format is:
// Joe Developer <joedev@example.com> unixtimestamp +HHMM // Joe Developer <joedev@example.com> unixtimestamp +HHMM
const QString authorInfo = readGitVar(workingDirectory, "GIT_AUTHOR_IDENT");
int lt = authorInfo.lastIndexOf('<'); int lt = authorInfo.lastIndexOf('<');
int gt = authorInfo.lastIndexOf('>'); int gt = authorInfo.lastIndexOf('>');
if (gt == -1 || uint(lt) > uint(gt)) { if (gt == -1 || uint(lt) > uint(gt)) {
@@ -2634,6 +2638,12 @@ Author GitClient::getAuthor(const Utils::FilePath &workingDirectory)
return result; return result;
} }
Author GitClient::getAuthor(const Utils::FilePath &workingDirectory)
{
const QString authorInfo = readGitVar(workingDirectory, "GIT_AUTHOR_IDENT");
return parseAuthor(authorInfo);
}
bool GitClient::getCommitData(const FilePath &workingDirectory, bool GitClient::getCommitData(const FilePath &workingDirectory,
QString *commitTemplate, QString *commitTemplate,
CommitData &commitData, CommitData &commitData,
@@ -3425,21 +3435,33 @@ QString GitClient::readGitVar(const FilePath &workingDirectory, const QString &c
return readOneLine(workingDirectory, {"var", configVar}); return readOneLine(workingDirectory, {"var", configVar});
} }
QString GitClient::readOneLine(const FilePath &workingDirectory, const QStringList &arguments) const static QTextCodec *configFileCodec()
{ {
// Git for Windows always uses UTF-8 for configuration: // Git for Windows always uses UTF-8 for configuration:
// https://github.com/msysgit/msysgit/wiki/Git-for-Windows-Unicode-Support#convert-config-files // https://github.com/msysgit/msysgit/wiki/Git-for-Windows-Unicode-Support#convert-config-files
static QTextCodec *codec = HostOsInfo::isWindowsHost() static QTextCodec *codec = HostOsInfo::isWindowsHost()
? QTextCodec::codecForName("UTF-8") ? QTextCodec::codecForName("UTF-8")
: QTextCodec::codecForLocale(); : QTextCodec::codecForLocale();
return codec;
}
QString GitClient::readOneLine(const FilePath &workingDirectory, const QStringList &arguments) const
{
const CommandResult result = vcsSynchronousExec(workingDirectory, arguments, const CommandResult result = vcsSynchronousExec(workingDirectory, arguments,
RunFlags::NoOutput, vcsTimeoutS(), codec); RunFlags::NoOutput, vcsTimeoutS(),
configFileCodec());
if (result.result() == ProcessResult::FinishedWithSuccess) if (result.result() == ProcessResult::FinishedWithSuccess)
return result.cleanedStdOut().trimmed(); return result.cleanedStdOut().trimmed();
return {}; return {};
} }
void GitClient::readConfigAsync(const FilePath &workingDirectory, const QStringList &arguments,
const CommandHandler &handler) const
{
vcsExecWithHandler(workingDirectory, arguments, this, handler, RunFlags::NoOutput,
configFileCodec());
}
static unsigned parseGitVersion(const QString &output) static unsigned parseGitVersion(const QString &output)
{ {
// cut 'git version 1.6.5.1.sha' // cut 'git version 1.6.5.1.sha'

View File

@@ -79,6 +79,12 @@ public:
}; };
struct Author { struct Author {
bool operator==(const Author &other) const {
return name == other.name && email == other.email;
}
bool operator!=(const Author &other) const {
return !operator==(other);
}
QString name; QString name;
QString email; QString email;
}; };
@@ -343,11 +349,16 @@ public:
Core::IEditor *openShowEditor(const Utils::FilePath &workingDirectory, const QString &ref, Core::IEditor *openShowEditor(const Utils::FilePath &workingDirectory, const QString &ref,
const Utils::FilePath &path, ShowEditor showSetting = ShowEditor::Always); const Utils::FilePath &path, ShowEditor showSetting = ShowEditor::Always);
Author parseAuthor(const QString &authorInfo);
Author getAuthor(const Utils::FilePath &workingDirectory); Author getAuthor(const Utils::FilePath &workingDirectory);
QTextCodec *defaultCommitEncoding() const;
enum EncodingType { EncodingSource, EncodingLogOutput, EncodingCommit, EncodingDefault }; enum EncodingType { EncodingSource, EncodingLogOutput, EncodingCommit, EncodingDefault };
QTextCodec *encoding(EncodingType encodingType, const Utils::FilePath &source = {}) const; QTextCodec *encoding(EncodingType encodingType, const Utils::FilePath &source = {}) const;
void readConfigAsync(const Utils::FilePath &workingDirectory, const QStringList &arguments,
const VcsBase::CommandHandler &handler) const;
private: private:
static GitSettings &settings(); static GitSettings &settings();

View File

@@ -392,6 +392,7 @@ public:
void setupInstantBlame(); void setupInstantBlame();
void instantBlameOnce(); void instantBlameOnce();
void forceInstantBlame();
void instantBlame(); void instantBlame();
void stopInstantBlame(); void stopInstantBlame();
bool refreshWorkingDirectory(const FilePath &workingDirectory); bool refreshWorkingDirectory(const FilePath &workingDirectory);
@@ -705,6 +706,7 @@ GitPluginPrivate::GitPluginPrivate()
m_fileActions.reserve(10); m_fileActions.reserve(10);
m_projectActions.reserve(10); m_projectActions.reserve(10);
m_repositoryActions.reserve(50); m_repositoryActions.reserve(50);
m_codec = GitClient::instance()->defaultCommitEncoding();
Context context(Constants::GIT_CONTEXT); Context context(Constants::GIT_CONTEXT);
@@ -1441,10 +1443,6 @@ void GitPluginPrivate::setupInstantBlame()
return; return;
} }
const Utils::FilePath workingDirectory = GitPlugin::currentState().currentFileTopLevel();
if (!refreshWorkingDirectory(workingDirectory))
return;
const TextEditorWidget *widget = TextEditorWidget::fromEditor(editor); const TextEditorWidget *widget = TextEditorWidget::fromEditor(editor);
if (!widget) if (!widget)
return; return;
@@ -1452,6 +1450,10 @@ void GitPluginPrivate::setupInstantBlame()
if (qobject_cast<const VcsBaseEditorWidget *>(widget)) if (qobject_cast<const VcsBaseEditorWidget *>(widget))
return; // Skip in VCS editors like log or blame return; // Skip in VCS editors like log or blame
const Utils::FilePath workingDirectory = GitPlugin::currentState().currentFileTopLevel();
if (!refreshWorkingDirectory(workingDirectory))
return;
m_blameCursorPosConn = connect(widget, &QPlainTextEdit::cursorPositionChanged, this, m_blameCursorPosConn = connect(widget, &QPlainTextEdit::cursorPositionChanged, this,
[this] { [this] {
if (!settings().instantBlame.value()) { if (!settings().instantBlame.value()) {
@@ -1461,8 +1463,7 @@ void GitPluginPrivate::setupInstantBlame()
m_cursorPositionChangedTimer->start(500); m_cursorPositionChangedTimer->start(500);
}); });
m_lastVisitedEditorLine = -1; forceInstantBlame();
instantBlame();
}; };
connect(&settings().instantBlame, &BoolAspect::valueChanged, this, connect(&settings().instantBlame, &BoolAspect::valueChanged, this,
@@ -1530,6 +1531,11 @@ void GitPluginPrivate::instantBlameOnce()
return; return;
} }
forceInstantBlame();
}
void GitPluginPrivate::forceInstantBlame()
{
m_lastVisitedEditorLine = -1; m_lastVisitedEditorLine = -1;
instantBlame(); instantBlame();
} }
@@ -1596,8 +1602,38 @@ bool GitPluginPrivate::refreshWorkingDirectory(const FilePath &workingDirectory)
return true; return true;
m_workingDirectory = workingDirectory; m_workingDirectory = workingDirectory;
m_author = GitClient::instance()->getAuthor(workingDirectory);
m_codec = GitClient::instance()->encoding(GitClient::EncodingCommit, workingDirectory); const auto commitCodecHandler = [this, workingDirectory](const CommandResult &result) {
QTextCodec *codec = nullptr;
if (result.result() == ProcessResult::FinishedWithSuccess) {
const QString codecName = result.cleanedStdOut().trimmed();
codec = QTextCodec::codecForName(codecName.toUtf8());
} else {
codec = GitClient::instance()->defaultCommitEncoding();
}
if (m_codec != codec) {
m_codec = codec;
forceInstantBlame();
}
};
GitClient::instance()->readConfigAsync(workingDirectory, {"config", "i18n.commitEncoding"},
commitCodecHandler);
const auto authorHandler = [this, workingDirectory](const CommandResult &result) {
if (result.result() == ProcessResult::FinishedWithSuccess) {
const QString authorInfo = result.cleanedStdOut().trimmed();
const Author author = GitClient::instance()->parseAuthor(authorInfo);
if (m_author != author) {
m_author = author;
forceInstantBlame();
}
}
};
GitClient::instance()->readConfigAsync(workingDirectory, {"var", "GIT_AUTHOR_IDENT"},
authorHandler);
return true; return true;
} }