From c43638eb11d8e6a413b68a6c50aa062afc56b449 Mon Sep 17 00:00:00 2001 From: Matthias Melcher Date: Mon, 10 Aug 2026 00:44:13 +0200 Subject: [PATCH] Fluid: add MErgeBack support for the command line --- fluid/Fluid.cxx | 26 ++++++++- fluid/app/args.cxx | 13 +++++ fluid/app/args.h | 2 + fluid/documentation/src/page_mergeback.dox | 18 +++++++ fluid/message.cxx | 9 ++-- fluid/proj/mergeback.cxx | 63 ++++++++++++++-------- fluid/proj/mergeback.h | 8 ++- 7 files changed, 111 insertions(+), 28 deletions(-) diff --git a/fluid/Fluid.cxx b/fluid/Fluid.cxx index 1f34927de..ce0fbb9e3 100644 --- a/fluid/Fluid.cxx +++ b/fluid/Fluid.cxx @@ -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; } diff --git a/fluid/app/args.cxx b/fluid/app/args.cxx index c1ac0d143..b0f11d85e 100644 --- a/fluid/app/args.cxx +++ b/fluid/app/args.cxx @@ -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]; diff --git a/fluid/app/args.h b/fluid/app/args.h index 8aee2e853..3fe3d99b0 100644 --- a/fluid/app/args.h +++ b/fluid/app/args.h @@ -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. diff --git a/fluid/documentation/src/page_mergeback.dox b/fluid/documentation/src/page_mergeback.dox index 7b3bc1a22..92c05da7f 100644 --- a/fluid/documentation/src/page_mergeback.dox +++ b/fluid/documentation/src/page_mergeback.dox @@ -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. +

MergeBack Command Line Options

+ + 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. +

Things to Keep in Mind

- **Scope of MergeBack:** Currently, MergeBack supports merging changes diff --git a/fluid/message.cxx b/fluid/message.cxx index 09177c90f..8a1442b6e 100644 --- a/fluid/message.cxx +++ b/fluid/message.cxx @@ -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 or - int default_index = (option.size() > 1) ? 1 : 0; for (;;) { int key = read_console_key(); if (key == 27) { printf("\n"); return msg::ESC; } - if (key == '\r' || key == '\n') { printf("\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()); diff --git a/fluid/proj/mergeback.cxx b/fluid/proj/mergeback.cxx index 2cef2f5ae..957077c19 100644 --- a/fluid/proj/mergeback.cxx +++ b/fluid/proj/mergeback.cxx @@ -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 } diff --git a/fluid/proj/mergeback.h b/fluid/proj/mergeback.h index 24c1003ed..3415b992d 100644 --- a/fluid/proj/mergeback.h +++ b/fluid/proj/mergeback.h @@ -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