Fluid: add MErgeBack support for the command line

This commit is contained in:
Matthias Melcher
2026-08-10 00:44:19 +02:00
parent 4bc72f879f
commit c43638eb11
7 changed files with 111 additions and 28 deletions
+25 -1
View File
@@ -22,6 +22,7 @@
#include "proj/mergeback.h"
#include "app/Menu.h"
#include "app/shell_command.h"
#include "proj/mergeback.h"
#include "proj/undo.h"
#include "io/Project_Reader.h"
#include "io/Project_Writer.h"
@@ -207,6 +208,29 @@ int Application::run(int argc,char **argv) {
proj.strings_file_set = 1;
proj.strings_file_name = args.strings_filename;
}
if (args.mergeback_mode > 0) {
// ANALYSE = 0, INTERACTIVE, APPLY, APPLY_IF_SAFE
// -mb=info
proj::Mergeback::Task task = proj::Mergeback::Task::INFO;
// -mb=ask
if (args.mergeback_mode == 2) task = proj::Mergeback::Task::INTERACTIVE;
// -mb=apply
if (args.mergeback_mode == 3) task = proj::Mergeback::Task::APPLY_IF_SAFE;
int ret = merge_back(
proj,
proj.codefile_path() + proj.codefile_name(),
proj.projectfile_path() + proj.projectfile_name(),
task
);
if (ret < 0) {
printf("\n");
fluid::alert("Fluid", "Operation cancelled.");
exit(1); // error in mergeback
} else if (ret > 0) {
printf("\n");
fluid::message("Fluid", "Mergeback applied.");
}
}
}
if (args.update_file) { // fluid -u
@@ -228,6 +252,7 @@ int Application::run(int argc,char **argv) {
proj.set_modflag(0);
proj.undo.clear();
if (!proj.tree.empty()) mergeback_on_load();
// Set (but do not start) timer callback for external editor updates
ExternalCodeEditor::set_update_timer_callback(external_editor_timer);
@@ -503,7 +528,6 @@ bool Application::open_project_file(const std::string &filename_arg) {
// clear the project and merge a file by the given name
new_project();
bool success = proj.load_or_merge(new_filename);
if (success) mergeback_on_load();
return success;
}
+13
View File
@@ -130,6 +130,19 @@ int Args::arg(int argc, char** argv, int& i) {
Fluid.batch_mode++;
i += 2; return 2;
}
//-mb=info, -mb=ask, -mb=apply
if (strcmp(argv[i], "-mb=info")==0) {
mergeback_mode = 1; // inform
i++; return 1;
}
if (strcmp(argv[i], "-mb=ask")==0) {
mergeback_mode = 2; // ask
i++; return 1;
}
if (strcmp(argv[i], "-mb=apply")==0) {
mergeback_mode = 3; // apply
i++; return 1;
}
#ifndef NDEBUG
if ((i+1 < argc) && (strcmp(argv[i], "--autodoc") == 0)) {
autodoc_path = argv[i+1];
+2
View File
@@ -47,6 +47,8 @@ public:
/// Make all output file paths relative to the .fl project file path
/// instead of the current working directory, if set.
bool project_relative { false }; // fluid -pr
/// 0=ignore, 1=inform, 2=ask, 3=apply
int mergeback_mode { 0 }; // fluid -mb=info, -mb=ask, -mb=apply
/// Constructor.
Args() = default;
// Load args from command line into variables.
@@ -61,6 +61,24 @@
- Fluid will analyze the `.cxx` file, detect changes within the marked
sections, and offer to merge them back into the `.fl` project file.
<H2>MergeBack Command Line Options</H2>
When running in batch mode with `-c` or `-cs`, Fluid offers three command-line
options to control MergeBack. If none of these options is specified, MergeBack
is disabled and the C++ file is overwritten without warning.
- `-mb=info`: Analyze the MergeBack status. If no changes are detected, Fluid
writes the C++ files as requested. If changes are found, Fluid prints an
information message, does not write any files, and exits with status code `1`.
- `-mb=apply`: If changes can be merged back safely, Fluid applies them, writes
the project file, and then overwrites the C++ file. If unsafe changes are
detected that cannot be merged back, Fluid exits with error code `1` without
writing any files. If no changes are detected, Fluid simply writes the C++
files as requested.
- `-mb=ask`: Same as `-mb=apply`, but Fluid asks for confirmation before
overwriting the project file. This option may require a key press to
continue, so do not use it in automated scripts.
<H2>Things to Keep in Mind</H2>
- **Scope of MergeBack:** Currently, MergeBack supports merging changes
+6 -3
View File
@@ -179,20 +179,23 @@ int fluid::choice(const std::string &title, const std::string &message,
printf("%s: %s\n", title.c_str(), message.c_str());
// Write all available options
printf("Options: ");
int default_index = (option.size() > 1) ? 1 : 0;
bool comma = false;
int ix = 0;
for (const auto &opt : option) {
if (comma) printf(", ");
printf("%s[%c]", opt.label.c_str(), opt.key);
int key = (ix==default_index) ? fl_ascii_toupper(opt.key) : fl_ascii_tolower(opt.key);
printf("%s[%c]", opt.label.c_str(), key);
comma = true;
ix++;
}
printf(": ");
fflush(stdout);
// Loop until we receive a supported key, or the user hits <esc> or <return>
int default_index = (option.size() > 1) ? 1 : 0;
for (;;) {
int key = read_console_key();
if (key == 27) { printf("<esc>\n"); return msg::ESC; }
if (key == '\r' || key == '\n') { printf("<return>\n"); return default_index; }
if (key == '\r' || key == '\n') { printf("%s\n", option[default_index].label.c_str()); return default_index; }
for (size_t i = 0; i < option.size(); ++i) {
if (fl_ascii_tolower(key) == fl_ascii_tolower((unsigned char)option[i].key)) {
printf("%s\n", option[i].label.c_str());
+41 -22
View File
@@ -102,7 +102,7 @@ using namespace fluid::proj;
\return -2 if no code file was found
\return see above
*/
int merge_back(Project &proj, const std::string &s, const std::string &p, Mergeback::Task task) {
int fluid::merge_back(Project &proj, const std::string &s, const std::string &p, Mergeback::Task task) {
if (proj.write_mergeback_data) {
Mergeback mergeback(proj);
return mergeback.merge_back(s, p, task);
@@ -180,7 +180,7 @@ std::string Mergeback::read_and_unindent_block(long start, long end) {
\return -1 if the user wants to cancel or an error occurred or an issue was presented
(message or choice dialog was shown)
*/
int Mergeback::ask_user_to_merge(const std::string &code_filename, const std::string &project_filename) {
int Mergeback::ask_user_to_merge(const std::string &code_filename, const std::string &project_filename, bool info_only) {
if (tag_error) {
fluid_message("Comparing\n \"%s\"\nto\n \"%s\"\n\n"
"MergeBack found an error in line %d while reading tags\n"
@@ -225,15 +225,23 @@ int Mergeback::ask_user_to_merge(const std::string &code_filename, const std::st
num_changed_structure, num_possible_override);
return -1;
} else {
msg += "\n\nClick Cancel to abort the MergeBack operation.\n"
"Click Merge to merge all code changes back into\n"
"the open project.";
int c = fluid_choice(msg.c_str(), "Cancel", "Merge", nullptr,
code_filename.c_str(), project_filename.c_str(),
num_changed_code, num_uid_not_found,
num_changed_structure, num_possible_override);
if (c==0) return -1;
return 1;
if (info_only) {
fluid_message(msg.c_str(),
code_filename.c_str(), project_filename.c_str(),
num_changed_code, num_uid_not_found,
num_changed_structure, num_possible_override);
return -1;
} else {
msg += "\n\nClick Cancel to abort the MergeBack operation.\n"
"Click Merge to merge all code changes back into\n"
"the open project.";
int c = fluid_choice(msg.c_str(), "Cancel", "Merge", nullptr,
code_filename.c_str(), project_filename.c_str(),
num_changed_code, num_uid_not_found,
num_changed_structure, num_possible_override);
if (c != 1) return -1;
return 1;
}
}
}
@@ -626,8 +634,7 @@ int Mergeback::apply() {
/** Dispatch the MergeBack into analysis, interactive, or apply directly.
\param[in] s source code filename and path
\param[in] task one of FD_MERGEBACK_ANALYSE, FD_MERGEBACK_INTERACTIVE,
FD_MERGEBACK_APPLY_IF_SAFE, or FD_MERGEBACK_APPLY
\param[in] task one of ANALYSE, INFO, INTERACTIVE, APPLY, or APPLY_IF_SAFE
\return -1 if an error was found in a tag
\return -2 if no code file was found
\return See more at ::merge_back(const std::string &s, int task).
@@ -637,24 +644,32 @@ int Mergeback::merge_back(const std::string &s, const std::string &p, Task task)
code = fl_fopen(s.c_str(), "rb");
if (!code) return -2;
do { // no actual loop, just make sure we close the code file
if (task == Task::ANALYSE) {
if ((task == Task::ANALYSE) || (task == Task::INFO)) {
analyse();
if (tag_error) {ret = -1; break; }
if (num_changed_structure) ret |= 1;
if (num_changed_code) ret |= 2;
if (num_uid_not_found) ret |= 4;
if (num_possible_override) ret |= 8;
if (num_changed_structure) ret |= 2;
if (num_changed_code) ret |= 4;
if (num_uid_not_found) ret |= 8;
if (num_possible_override) ret |= 16;
if (task == Task::ANALYSE) break;
}
if (task == Task::INFO) {
// tell findings
ret = ask_user_to_merge(s, p, true /* info_only */);
break;
}
if (task == Task::INTERACTIVE) {
analyse();
ret = ask_user_to_merge(s, p);
if (ret != 1)
return ret;
break;
task = Task::APPLY; // fall through
}
if (task == Task::APPLY_IF_SAFE) {
analyse();
if (Fluid.batch_mode) {
ask_user_to_merge(s, p, true /* info_only */);
}
if (tag_error || num_changed_structure || num_possible_override) {
ret = -1;
break;
@@ -668,9 +683,13 @@ int Mergeback::merge_back(const std::string &s, const std::string &p, Task task)
if (task == Task::APPLY) {
ret = apply();
if (ret == 1) {
proj_.set_modflag(1);
redraw_browser();
load_panel();
if (Fluid.batch_mode) {
proj_.save();
} else {
proj_.set_modflag(1);
redraw_browser();
load_panel();
}
}
ret = 1; // avoid message box in caller
}
+6 -2
View File
@@ -48,7 +48,7 @@ public:
END_OF_LIST_ = UNUSED_
};
enum class Task {
ANALYSE = 0, INTERACTIVE, APPLY, APPLY_IF_SAFE
ANALYSE = 0, INFO, INTERACTIVE, APPLY, APPLY_IF_SAFE
};
enum Feedback { QUIET = 0, CHATTY = 1 };
protected:
@@ -88,7 +88,7 @@ public:
Mergeback(Project &proj);
~Mergeback();
int merge_back(const std::string &s, const std::string &p, Task task);
int ask_user_to_merge(const std::string &s, const std::string &p);
int ask_user_to_merge(const std::string &s, const std::string &p, bool info_only=false);
int analyse();
int apply();
static void print_tag(FILE *out, Tag prev_type, Tag next_type, uint16_t uid, uint32_t crc);
@@ -98,10 +98,14 @@ public:
extern int merge_back(const std::string &s, const std::string &p, int task);
} // namespace proj
extern int merge_back(Project &proj, const std::string &s, const std::string &p, proj::Mergeback::Task task);
} // namespace fluid
extern void start_auto_mergeback();
extern void mergeback_on_load();
#endif // FLUID_PROJ_MERGEBACK_H