Add CLI --dry-run option for merge (#3254)

This commit is contained in:
louib 2019-06-18 21:45:24 -04:00 committed by Jonathan White
parent 9e06dc0d5c
commit 84eec03cb7
9 changed files with 98 additions and 35 deletions

View file

@ -39,6 +39,10 @@ const QCommandLineOption Merge::NoPasswordFromOption =
QCommandLineOption(QStringList() << "no-password-from",
QObject::tr("Deactivate password key for the database to merge from."));
const QCommandLineOption Merge::DryRunOption =
QCommandLineOption(QStringList() << "dry-run",
QObject::tr("Only print the changes detected by the merge operation."));
Merge::Merge()
{
name = QString("merge");
@ -46,6 +50,7 @@ Merge::Merge()
options.append(Merge::SameCredentialsOption);
options.append(Merge::KeyFileFromOption);
options.append(Merge::NoPasswordFromOption);
options.append(Merge::DryRunOption);
positionalArguments.append({QString("database2"), QObject::tr("Path of the database to merge from."), QString("")});
}
@ -55,14 +60,18 @@ Merge::~Merge()
int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
{
TextStream outputTextStream(Utils::STDOUT, QIODevice::WriteOnly);
TextStream outputTextStream(parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
QIODevice::WriteOnly);
TextStream errorTextStream(Utils::STDERR, QIODevice::WriteOnly);
const QStringList args = parser->positionalArguments();
QString toDatabasePath = args.at(0);
QString fromDatabasePath = args.at(1);
QSharedPointer<Database> db2;
if (!parser->isSet(Merge::SameCredentialsOption)) {
db2 = Utils::unlockDatabase(args.at(1),
db2 = Utils::unlockDatabase(fromDatabasePath,
!parser->isSet(Merge::NoPasswordFromOption),
parser->value(Merge::KeyFileFromOption),
parser->isSet(Command::QuietOption) ? Utils::DEVNULL : Utils::STDOUT,
@ -73,26 +82,29 @@ int Merge::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer
} else {
db2 = QSharedPointer<Database>::create();
QString errorMessage;
if (!db2->open(args.at(1), database->key(), &errorMessage, false)) {
if (!db2->open(fromDatabasePath, database->key(), &errorMessage, false)) {
errorTextStream << QObject::tr("Error reading merge file:\n%1").arg(errorMessage);
return EXIT_FAILURE;
}
}
Merger merger(db2.data(), database.data());
bool databaseChanged = merger.merge();
QStringList changeList = merger.merge();
if (databaseChanged) {
for (QString mergeChange : changeList) {
outputTextStream << "\t" << mergeChange << endl;
}
if (!changeList.isEmpty() && !parser->isSet(Merge::DryRunOption)) {
QString errorMessage;
if (!database->save(args.at(0), &errorMessage, true, false)) {
if (!database->save(toDatabasePath, &errorMessage, true, false)) {
errorTextStream << QObject::tr("Unable to save database to file : %1").arg(errorMessage) << endl;
return EXIT_FAILURE;
}
if (!parser->isSet(Command::QuietOption)) {
outputTextStream << "Successfully merged the database files." << endl;
}
} else if (!parser->isSet(Command::QuietOption)) {
outputTextStream << "Database was not modified by merge operation." << endl;
outputTextStream << QObject::tr("Successfully merged %1 into %2.").arg(fromDatabasePath, toDatabasePath)
<< endl;
} else {
outputTextStream << QObject::tr("Database was not modified by merge operation.") << endl;
}
return EXIT_SUCCESS;

View file

@ -31,6 +31,7 @@ public:
static const QCommandLineOption SameCredentialsOption;
static const QCommandLineOption KeyFileFromOption;
static const QCommandLineOption NoPasswordFromOption;
static const QCommandLineOption DryRunOption;
};
#endif // KEEPASSXC_MERGE_H

View file

@ -77,6 +77,9 @@ Displays the program version.
.SS "Merge options"
.IP "-d, --dry-run <path>"
Only print the changes detected by the merge operation.
.IP "-f, --key-file-from <path>"
Path of the key file for the second database.

View file

@ -60,7 +60,7 @@ void Merger::resetForcedMergeMode()
m_mode = Group::Default;
}
bool Merger::merge()
QStringList Merger::merge()
{
// Order of merge steps is important - it is possible that we
// create some items before deleting them afterwards
@ -74,9 +74,8 @@ bool Merger::merge()
// At this point we have a list of changes we may want to show the user
if (!changes.isEmpty()) {
m_context.m_targetDb->markAsModified();
return true;
}
return false;
return changes;
}
Merger::ChangeList Merger::mergeGroup(const MergeContext& context)

View file

@ -33,7 +33,7 @@ public:
Merger(const Group* sourceGroup, Group* targetGroup);
void setForcedMergeMode(Group::MergeMode mode);
void resetForcedMergeMode();
bool merge();
QStringList merge();
private:
typedef QString Change;

View file

@ -894,9 +894,9 @@ void DatabaseWidget::mergeDatabase(bool accepted)
}
Merger merger(srcDb.data(), m_db.data());
bool databaseChanged = merger.merge();
QStringList changeList = merger.merge();
if (databaseChanged) {
if (!changeList.isEmpty()) {
showMessage(tr("Successfully merged the database files."), MessageWidget::Information);
} else {
showMessage(tr("Database was not modified by merge operation."), MessageWidget::Information);

View file

@ -401,7 +401,6 @@ MainWindow::MainWindow()
connect(m_ui->actionUserGuide, SIGNAL(triggered()), SLOT(openUserGuide()));
connect(m_ui->actionOnlineHelp, SIGNAL(triggered()), SLOT(openOnlineHelp()));
#ifdef Q_OS_MACOS
setUnifiedTitleAndToolBarOnMac(true);
if (macUtils()->isDarkMode()) {

View file

@ -409,8 +409,8 @@ ShareObserver::Result ShareObserver::importSingedContainerInto(const KeeShareSet
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
const bool changed = merger.merge();
if (changed) {
const QStringList changeList = merger.merge();
if (!changeList.isEmpty()) {
return {reference.path, Result::Success, tr("Successful signed import")};
}
}
@ -425,8 +425,8 @@ ShareObserver::Result ShareObserver::importSingedContainerInto(const KeeShareSet
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
const bool changed = merger.merge();
if (changed) {
const QStringList changeList = merger.merge();
if (!changeList.isEmpty()) {
return {reference.path, Result::Success, tr("Successful signed import")};
}
return {};
@ -496,8 +496,8 @@ ShareObserver::Result ShareObserver::importUnsignedContainerInto(const KeeShareS
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
const bool changed = merger.merge();
if (changed) {
const QStringList changeList = merger.merge();
if (!changeList.isEmpty()) {
return {reference.path, Result::Success, tr("Successful signed import")};
}
}
@ -511,8 +511,8 @@ ShareObserver::Result ShareObserver::importUnsignedContainerInto(const KeeShareS
qPrintable(sourceDb->rootGroup()->name()));
Merger merger(sourceDb->rootGroup(), targetGroup);
merger.setForcedMergeMode(Group::Synchronize);
const bool changed = merger.merge();
if (changed) {
const QStringList changeList = merger.merge();
if (!changeList.isEmpty()) {
return {reference.path, Result::Success, tr("Successful unsigned import")};
}
return {};