mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-21 17:45:44 -05:00
Merge pull request #715 from GriffinRichards/version-check
Add project version check via git
This commit is contained in:
commit
805b366c89
|
|
@ -345,6 +345,7 @@ public:
|
|||
this->unusedTileCovered = 0x0000;
|
||||
this->unusedTileSplit = 0x0000;
|
||||
this->maxEventsPerGroup = 255;
|
||||
this->forcedMajorVersion = 0;
|
||||
this->globalConstantsFilepaths.clear();
|
||||
this->globalConstants.clear();
|
||||
this->identifiers.clear();
|
||||
|
|
@ -419,6 +420,7 @@ public:
|
|||
QMargins playerViewDistance;
|
||||
QList<uint32_t> warpBehaviors;
|
||||
int maxEventsPerGroup;
|
||||
int forcedMajorVersion;
|
||||
QStringList globalConstantsFilepaths;
|
||||
QMap<QString,QString> globalConstants;
|
||||
|
||||
|
|
|
|||
|
|
@ -351,7 +351,9 @@ private:
|
|||
void refreshCollisionSelector();
|
||||
void setLayoutOnlyMode(bool layoutOnly);
|
||||
|
||||
bool checkProjectSanity();
|
||||
bool isInvalidProject(Project *project);
|
||||
bool checkProjectSanity(Project *project);
|
||||
bool checkProjectVersion(Project *project);
|
||||
bool loadProjectData();
|
||||
bool setProjectUI();
|
||||
void clearProjectUI();
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public:
|
|||
void clearHealLocations();
|
||||
|
||||
bool sanityCheck();
|
||||
int getSupportedMajorVersion(QString *errorOut = nullptr);
|
||||
bool load();
|
||||
|
||||
Map* loadMap(const QString &mapName);
|
||||
|
|
|
|||
|
|
@ -890,6 +890,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) {
|
|||
this->warpBehaviors.append(getConfigUint32(key, s));
|
||||
} else if (key == "max_events_per_group") {
|
||||
this->maxEventsPerGroup = getConfigInteger(key, value, 1, INT_MAX, 255);
|
||||
} else if (key == "forced_major_version") {
|
||||
this->forcedMajorVersion = getConfigInteger(key, value);
|
||||
} else {
|
||||
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
|
||||
}
|
||||
|
|
@ -997,6 +999,7 @@ QMap<QString, QString> ProjectConfig::getKeyValueMap() {
|
|||
warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper());
|
||||
map.insert("warp_behaviors", warpBehaviorStrs.join(","));
|
||||
map.insert("max_events_per_group", QString::number(this->maxEventsPerGroup));
|
||||
map.insert("forced_major_version", QString::number(this->forcedMajorVersion));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@ bool MainWindow::openProject(QString dir, bool initial) {
|
|||
|
||||
// Make sure project looks reasonable before attempting to load it
|
||||
porysplash->showMessage("Verifying project");
|
||||
if (!checkProjectSanity()) {
|
||||
if (isInvalidProject(this->editor->project)) {
|
||||
delete this->editor->project;
|
||||
porysplash->stop();
|
||||
return false;
|
||||
|
|
@ -728,16 +728,20 @@ bool MainWindow::loadProjectData() {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectSanity() {
|
||||
if (editor->project->sanityCheck())
|
||||
bool MainWindow::isInvalidProject(Project *project) {
|
||||
return !(checkProjectSanity(project) && checkProjectVersion(project));
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectSanity(Project *project) {
|
||||
if (project->sanityCheck())
|
||||
return true;
|
||||
|
||||
logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root));
|
||||
logWarn(QString("The directory '%1' failed the project sanity check.").arg(project->root));
|
||||
|
||||
ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash);
|
||||
msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n"
|
||||
"Make sure you selected the correct project directory "
|
||||
"(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root));
|
||||
"(the one used to make your .gba file, e.g. 'pokeemerald').").arg(project->root));
|
||||
auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == tryAnyway) {
|
||||
|
|
@ -748,6 +752,43 @@ bool MainWindow::checkProjectSanity() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MainWindow::checkProjectVersion(Project *project) {
|
||||
QString error;
|
||||
int projectVersion = project->getSupportedMajorVersion(&error);
|
||||
if (projectVersion < 0) {
|
||||
// Failed to identify a supported major version.
|
||||
// We can't draw any conclusions from this, so we don't consider the project to be invalid.
|
||||
QString msg = QStringLiteral("Failed to check project version");
|
||||
logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error));
|
||||
} else {
|
||||
QString msg = QStringLiteral("Successfully checked project version. ");
|
||||
logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion)
|
||||
: QStringLiteral("Too old for any Porymap version")));
|
||||
|
||||
if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) {
|
||||
// We were unable to find the necessary changes for Porymap's current major version.
|
||||
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
|
||||
// Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting.
|
||||
// It is very plausible that the user may have reproduced the required changes in an
|
||||
// unknown commit, rather than merging the required changes directly from the base repo.
|
||||
// In this case the 'projectVersion' may actually be too old to use for their repo.
|
||||
ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash);
|
||||
msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2."
|
||||
"") // TODO: Once we have a wiki or manual page describing breaking changes, link that here.
|
||||
.arg(project->getProjectTitle())
|
||||
.arg(porymapVersion.majorVersion()));
|
||||
auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() != tryAnyway){
|
||||
return false;
|
||||
}
|
||||
// User opted to try with this version anyway. Don't warn them about this version again.
|
||||
projectConfig.forcedMajorVersion = porymapVersion.majorVersion();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::showProjectOpenFailure() {
|
||||
if (!this->isVisible()){
|
||||
// The main window is not visible during the initial project open; the splash screen is busy providing visual feedback.
|
||||
|
|
|
|||
111
src/project.cpp
111
src/project.cpp
|
|
@ -75,6 +75,117 @@ bool Project::sanityCheck() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Porymap projects have no standardized way for Porymap to determine whether they're compatible as of the latest breaking changes.
|
||||
// We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
|
||||
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
|
||||
// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1.
|
||||
int Project::getSupportedMajorVersion(QString *errorOut) {
|
||||
// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check
|
||||
// is not important enough to significantly slow down project launch, we'd rather just timeout.
|
||||
const int timeoutLimit = 500;
|
||||
const int failureVersion = -1;
|
||||
QString gitName = "git";
|
||||
QString gitPath = QStandardPaths::findExecutable(gitName);
|
||||
if (gitPath.isEmpty()) {
|
||||
if (errorOut) *errorOut = QString("Unable to locate %1.").arg(gitName);
|
||||
return failureVersion;
|
||||
}
|
||||
|
||||
QProcess process;
|
||||
process.setWorkingDirectory(this->root);
|
||||
process.setProgram(gitPath);
|
||||
process.setReadChannel(QProcess::StandardOutput);
|
||||
process.setStandardInputFile(QProcess::nullDevice()); // We won't have any writing to do.
|
||||
|
||||
// First we need to know which (if any) known history this project belongs to.
|
||||
// We'll get the root commit, then compare it to the known root commits for the base project repos.
|
||||
static const QStringList args_getRootCommit = { "rev-list", "--max-parents=0", "HEAD" };
|
||||
process.setArguments(args_getRootCommit);
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to identify commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
errorOut->append(QString(": %1").arg(process.errorString()));
|
||||
} else {
|
||||
process.setReadChannel(QProcess::StandardError);
|
||||
QString error = QString(process.readLine()).remove('\n');
|
||||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
}
|
||||
const QString rootCommit = QString(process.readLine()).remove('\n');
|
||||
|
||||
// The keys in this map are the hashes of the root commits for each of the 3 base repos.
|
||||
// The values are a list of pairs, where the first element is a major version number, and the
|
||||
// second element is the hash of the earliest commit that supports that major version.
|
||||
static const QMap<QString, QList<QPair<int, QString>>> historyMap = {
|
||||
// pokeemerald
|
||||
{"33b799c967fd63d04afe82eecc4892f3e45781b3", {
|
||||
{6, "07c897ad48c36b178093bde8ca360823127d812b"}, // TODO: Update to merge commit for pokeemerald's porymap-6 branch
|
||||
{5, "c76beed98990a57c84d3930190fd194abfedf7e8"},
|
||||
{4, "cb5b8da77b9ba6837fcc8c5163bedc5008b12c2c"},
|
||||
{3, "204c431993dad29661a9ff47326787cd0cf381e6"},
|
||||
{2, "cdae0c1444bed98e652c87dc3e3edcecacfef8be"},
|
||||
{1, ""}
|
||||
}},
|
||||
// pokefirered
|
||||
{"670fef77ac4d9116d5fdc28c0da40622919a062b", {
|
||||
{6, "7722e7a92ca5fa69925dcef82f6c89c35ec48171"}, // TODO: Update to merge commit for pokefirered's porymap-6 branch
|
||||
{5, "52591dcee42933d64f60c59276fc13c3bb89c47b"},
|
||||
{4, "200c82e01a94dbe535e6ed8768d8afad4444d4d2"},
|
||||
}},
|
||||
// pokeruby
|
||||
{"1362b60f3467f0894d55e82f3294980b6373021d", {
|
||||
{6, "bc5aeaa64ecad03aa4ab9e1000ba94916276c936"}, // TODO: Update to merge commit for pokeruby's porymap-6 branch
|
||||
{5, "d99cb43736dd1d4ee4820f838cb259d773d8bf25"},
|
||||
{4, "f302fcc134bf354c3655e3423be68fd7a99cb396"},
|
||||
{3, "b4f4d2c0f03462dcdf3492aad27890294600eb2e"},
|
||||
{2, "0e8ccfc4fd3544001f4c25fafd401f7558bdefba"},
|
||||
{1, ""}
|
||||
}},
|
||||
};
|
||||
if (!historyMap.contains(rootCommit)) {
|
||||
// Either this repo does not share history with one of the base repos, or we got some unexpected result.
|
||||
if (errorOut) *errorOut = QStringLiteral("Unrecognized commit history");
|
||||
return failureVersion;
|
||||
}
|
||||
|
||||
// We now know which base repo that the user's repo shares history with.
|
||||
// Next we check to see if it contains the changes required to support particular major versions of Porymap.
|
||||
// We'll start with the most recent major version and work backwards.
|
||||
for (const auto &pair : historyMap.value(rootCommit)) {
|
||||
int versionNum = pair.first;
|
||||
QString commitHash = pair.second;
|
||||
if (commitHash.isEmpty()) {
|
||||
// An empty commit hash means 'consider any point in the history a supported version'
|
||||
return versionNum;
|
||||
}
|
||||
process.setArguments({ "merge-base", "--is-ancestor", commitHash, "HEAD" });
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to search commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
errorOut->append(QString(": %1").arg(process.errorString()));
|
||||
} else {
|
||||
process.setReadChannel(QProcess::StandardError);
|
||||
QString error = QString(process.readLine()).remove('\n');
|
||||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
}
|
||||
if (process.exitCode() == 0) {
|
||||
// Identified a supported major version
|
||||
return versionNum;
|
||||
}
|
||||
}
|
||||
// We recognized the commit history, but it's too old for any version of Porymap to support.
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Project::load() {
|
||||
this->parser.setUpdatesSplashScreen(true);
|
||||
resetFileCache();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user