diff --git a/.clang-format b/.clang-format index a587cbb..0b98b25 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,6 @@ # Defines the Chromium style for automatic reformatting. # http://clang.llvm.org/docs/ClangFormatStyleOptions.html BasedOnStyle: Chromium -Standard: Cpp20 +Standard: c++20 +IndentWidth: 4 +ColumnLimit: 100 diff --git a/scripts/check_clang_format.sh b/scripts/check_clang_format.sh old mode 100644 new mode 100755 diff --git a/scripts/run_clang_format.sh b/scripts/run_clang_format.sh old mode 100644 new mode 100755 diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 89023f9..1b5c5ea 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -1,407 +1,404 @@ #include "OSTreeTUI.hpp" +#include #include #include #include #include +#include #include -#include #include -#include -#include +#include +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border -#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "clip.h" #include "../util/cpplibostree.hpp" -OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector startupBranches): - ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) -{ +OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector startupBranches) + : ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) { using namespace ftxui; - // set all branches as visible and define a branch color - for (const auto& branch : ostreeRepo.getBranches()) { - // if startupBranches are defined, set all as non-visible - visibleBranches[branch] = startupBranches.size() == 0 ? true : false; - std::hash nameHash{}; - branchColorMap[branch] = Color::Palette256((nameHash(branch) + 10) % 256); - } - // if startupBranches are defined, only set those visible - if (startupBranches.size() != 0) { - for (const auto& branch : startupBranches) { - visibleBranches[branch] = true; - } - } - - // - UI ELEMENTS ---------- ---------- - - // COMMIT TREE - refresh_commitComponents(); - - tree = Renderer([&] { - refresh_commitComponents(); - selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); - // TODO check for promotion & pass information if needed - if (inPromotionSelection && promotionBranch.size() != 0) { - std::unordered_map promotionBranchColorMap{}; - for (auto& [str,col] : branchColorMap) { - if (str == promotionBranch) { - promotionBranchColorMap.insert({str,col}); - } else { - promotionBranchColorMap.insert({str,Color::GrayDark}); - } - } - return CommitRender::commitRender(*this, promotionBranchColorMap); - } - return CommitRender::commitRender(*this, branchColorMap); - }); - - commitListComponent = Container::Horizontal({ - tree, - commitList - }); - - // window specific shortcuts - commitListComponent = CatchEvent(commitListComponent, [&](Event event) { - // switch through commits - if ((! inPromotionSelection && event == Event::ArrowUp) || (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { - selectedCommit = std::max(0, static_cast(selectedCommit) - 1); - adjustScrollToSelectedCommit(); - return true; - } - if ((! inPromotionSelection && event == Event::ArrowDown) || (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { - selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); - adjustScrollToSelectedCommit(); - return true; - } - return false; - }); - - // INTERCHANGEABLE VIEW - // info - infoView = Renderer([&] { - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... - if (visibleCommitViewMap.size() <= 0) { - return text(" no commit info available ") | color(Color::RedLight) | bold | center; - } - return CommitInfoManager::renderInfoView(ostreeRepo.getCommitList().at(visibleCommitViewMap.at(selectedCommit))); + // set all branches as visible and define a branch color + for (const auto& branch : ostreeRepo.getBranches()) { + // if startupBranches are defined, set all as non-visible + visibleBranches[branch] = startupBranches.size() == 0 ? true : false; + std::hash nameHash{}; + branchColorMap[branch] = Color::Palette256((nameHash(branch) + 10) % 256); + } + // if startupBranches are defined, only set those visible + if (startupBranches.size() != 0) { + for (const auto& branch : startupBranches) { + visibleBranches[branch] = true; + } + } + + // - UI ELEMENTS ---------- ---------- + + // COMMIT TREE + refresh_commitComponents(); + + tree = Renderer([&] { + refresh_commitComponents(); + selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); + // TODO check for promotion & pass information if needed + if (inPromotionSelection && promotionBranch.size() != 0) { + std::unordered_map promotionBranchColorMap{}; + for (auto& [str, col] : branchColorMap) { + if (str == promotionBranch) { + promotionBranchColorMap.insert({str, col}); + } else { + promotionBranchColorMap.insert({str, Color::GrayDark}); + } + } + return CommitRender::commitRender(*this, promotionBranchColorMap); + } + return CommitRender::commitRender(*this, branchColorMap); + }); + + commitListComponent = Container::Horizontal({tree, commitList}); + + // window specific shortcuts + commitListComponent = CatchEvent(commitListComponent, [&](Event event) { + // switch through commits + if ((!inPromotionSelection && event == Event::ArrowUp) || + (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { + selectedCommit = std::max(0, static_cast(selectedCommit) - 1); + adjustScrollToSelectedCommit(); + return true; + } + if ((!inPromotionSelection && event == Event::ArrowDown) || + (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { + selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); + adjustScrollToSelectedCommit(); + return true; + } + return false; + }); + + // INTERCHANGEABLE VIEW + // info + infoView = Renderer([&] { + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + if (visibleCommitViewMap.size() <= 0) { + return text(" no commit info available ") | color(Color::RedLight) | bold | center; + } + return CommitInfoManager::renderInfoView( + ostreeRepo.getCommitList().at(visibleCommitViewMap.at(selectedCommit))); }); - // filter - filterManager = std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); - filterView = Renderer(filterManager->branchBoxes, [&] { - return filterManager->branchBoxRender(); - }); - filterView = CatchEvent(filterView, [&](Event event) { - if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { - refresh_commitListComoponent(); - } - return false; - }); - - // interchangeable view (composed) - manager = std::unique_ptr(new Manager(*this, infoView, filterView)); - managerRenderer = manager->getManagerRenderer(); - - // FOOTER - footerRenderer = Renderer([&] { - return footer.footerRender(); - }); - - // BUILD MAIN CONTAINER - container = Component(managerRenderer); - container = ResizableSplitLeft(commitListComponent, container, &logSize); - container = ResizableSplitBottom(footerRenderer, container, &footerSize); - - commitListComponent->TakeFocus(); - - // add application shortcuts - mainContainer = CatchEvent(container | border, [&](const Event& event) { - if (event == Event::AltP) { - setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); - } - // copy commit id - if (event == Event::AltC) { - std::string hash = visibleCommitViewMap.at(selectedCommit); - clip::set_text(hash); - notificationText = " Copied Hash " + hash + " "; - return true; - } - // refresh repository - if (event == Event::AltR) { - refresh_repository(); - notificationText = " Refreshed Repository Data "; - return true; - } - // exit - if (event == Event::AltQ) { - screen.ExitLoopClosure()(); - return true; - } - // make commit list focussable - if (event == Event::ArrowLeft && managerRenderer->Focused() && manager->getTabIndex() == 0) { - commitListComponent->TakeFocus(); - return true; - } - return false; - }); + // filter + filterManager = + std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); + filterView = + Renderer(filterManager->branchBoxes, [&] { return filterManager->branchBoxRender(); }); + filterView = CatchEvent(filterView, [&](Event event) { + if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { + refresh_commitListComoponent(); + } + return false; + }); + + // interchangeable view (composed) + manager = std::unique_ptr(new Manager(*this, infoView, filterView)); + managerRenderer = manager->getManagerRenderer(); + + // FOOTER + footerRenderer = Renderer([&] { return footer.footerRender(); }); + + // BUILD MAIN CONTAINER + container = Component(managerRenderer); + container = ResizableSplitLeft(commitListComponent, container, &logSize); + container = ResizableSplitBottom(footerRenderer, container, &footerSize); + + commitListComponent->TakeFocus(); + + // add application shortcuts + mainContainer = CatchEvent(container | border, [&](const Event& event) { + if (event == Event::AltP) { + setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); + } + // copy commit id + if (event == Event::AltC) { + std::string hash = visibleCommitViewMap.at(selectedCommit); + clip::set_text(hash); + notificationText = " Copied Hash " + hash + " "; + return true; + } + // refresh repository + if (event == Event::AltR) { + refresh_repository(); + notificationText = " Refreshed Repository Data "; + return true; + } + // exit + if (event == Event::AltQ) { + screen.ExitLoopClosure()(); + return true; + } + // make commit list focussable + if (event == Event::ArrowLeft && managerRenderer->Focused() && + manager->getTabIndex() == 0) { + commitListComponent->TakeFocus(); + return true; + } + return false; + }); } int OSTreeTUI::run() { - using namespace ftxui; - // footer notification update loader - // Probably not the best solution, having an active wait and should maybe - // only be started, once a notification is set... - bool runSubThreads{true}; - std::thread footerNotificationUpdater([&] { - while (runSubThreads) { - using namespace std::chrono_literals; - // notification is set - if (notificationText != "") { - footer.setContent(notificationText); - screen.Post(Event::Custom); - std::this_thread::sleep_for(2s); - // clear notification - notificationText = ""; - footer.resetContent(); - screen.Post(Event::Custom); - } - std::this_thread::sleep_for(0.2s); - } - }); - - screen.Loop(mainContainer); - runSubThreads = false; - footerNotificationUpdater.join(); - - return EXIT_SUCCESS; + using namespace ftxui; + // footer notification update loader + // Probably not the best solution, having an active wait and should maybe + // only be started, once a notification is set... + bool runSubThreads{true}; + std::thread footerNotificationUpdater([&] { + while (runSubThreads) { + using namespace std::chrono_literals; + // notification is set + if (notificationText != "") { + footer.setContent(notificationText); + screen.Post(Event::Custom); + std::this_thread::sleep_for(2s); + // clear notification + notificationText = ""; + footer.resetContent(); + screen.Post(Event::Custom); + } + std::this_thread::sleep_for(0.2s); + } + }); + + screen.Loop(mainContainer); + runSubThreads = false; + footerNotificationUpdater.join(); + + return EXIT_SUCCESS; } void OSTreeTUI::refresh_commitComponents() { - using namespace ftxui; - - commitComponents.clear(); - int i{0}; - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); - for (auto& hash : visibleCommitViewMap) { - commitComponents.push_back( - CommitRender::CommitComponent(i, hash, *this) - ); - i++; - } - - commitList = commitComponents.size() == 0 - ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) - : Container::Stacked(commitComponents); + using namespace ftxui; + + commitComponents.clear(); + int i{0}; + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + for (auto& hash : visibleCommitViewMap) { + commitComponents.push_back(CommitRender::CommitComponent(i, hash, *this)); + i++; + } + + commitList = + commitComponents.size() == 0 + ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) + : Container::Stacked(commitComponents); } void OSTreeTUI::refresh_commitListComoponent() { - using namespace ftxui; - - commitListComponent->DetachAllChildren(); - refresh_commitComponents(); - Component tmp = Container::Horizontal({ - tree, - commitList - }); - commitListComponent->Add(tmp); + using namespace ftxui; + + commitListComponent->DetachAllChildren(); + refresh_commitComponents(); + Component tmp = Container::Horizontal({tree, commitList}); + commitListComponent->Add(tmp); } bool OSTreeTUI::refresh_repository() { - ostreeRepo.updateData(); - refresh_commitListComoponent(); - return true; + ostreeRepo.updateData(); + refresh_commitListComoponent(); + return true; } bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotionBranch) { - // deactivate promotion mode - if (!active) { - inPromotionSelection = false; - promotionBranch = ""; - promotionHash = ""; - return true; - } - // set promotion mode - if (!inPromotionSelection || hash != promotionHash) { - inPromotionSelection = true; - if (setPromotionBranch) { - promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; - } - promotionHash = hash; - return true; - } - // nothing to update - return false; + // deactivate promotion mode + if (!active) { + inPromotionSelection = false; + promotionBranch = ""; + promotionHash = ""; + return true; + } + // set promotion mode + if (!inPromotionSelection || hash != promotionHash) { + inPromotionSelection = true; + if (setPromotionBranch) { + promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; + } + promotionHash = hash; + return true; + } + // nothing to update + return false; } -bool OSTreeTUI::promoteCommit(std::string hash, std::string branch, std::vector metadataStrings, std::string newSubject, bool keepMetadata) { - bool success = ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); - setPromotionMode(false); - // reload repository - if (success) { - scrollOffset = 0; - selectedCommit = 0; - screen.PostEvent(ftxui::Event::AltR); - } - return success; +bool OSTreeTUI::promoteCommit(std::string hash, + std::string branch, + std::vector metadataStrings, + std::string newSubject, + bool keepMetadata) { + bool success = + ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); + setPromotionMode(false); + // reload repository + if (success) { + scrollOffset = 0; + selectedCommit = 0; + screen.PostEvent(ftxui::Event::AltR); + } + return success; } -std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches) { - - std::vector visibleCommitViewMap{}; - // get filtered commits - visibleCommitViewMap = {}; - for (const auto& commitPair : repo.getCommitList()) { - if (visibleBranches[commitPair.second.branch]) { - visibleCommitViewMap.push_back(commitPair.first); - } - } - // sort by date - std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), [&](const std::string& a, const std::string& b) { - return repo.getCommitList().at(a).timestamp - > repo.getCommitList().at(b).timestamp; - }); - - return visibleCommitViewMap; +std::vector OSTreeTUI::parseVisibleCommitMap( + cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches) { + std::vector visibleCommitViewMap{}; + // get filtered commits + visibleCommitViewMap = {}; + for (const auto& commitPair : repo.getCommitList()) { + if (visibleBranches[commitPair.second.branch]) { + visibleCommitViewMap.push_back(commitPair.first); + } + } + // sort by date + std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), + [&](const std::string& a, const std::string& b) { + return repo.getCommitList().at(a).timestamp > + repo.getCommitList().at(b).timestamp; + }); + + return visibleCommitViewMap; } void OSTreeTUI::adjustScrollToSelectedCommit() { - // try to scroll it to the middle - int windowHeight = screen.dimy() - 4; - int scollOffsetToFitCommitToTop = - selectedCommit * CommitRender::COMMIT_WINDOW_HEIGHT; - int newScroll = scollOffsetToFitCommitToTop + windowHeight / 2 - CommitRender::COMMIT_WINDOW_HEIGHT; - // adjust if on edges - int min = 0; - int max = - windowHeight - static_cast(visibleCommitViewMap.size() - 1) * CommitRender::COMMIT_WINDOW_HEIGHT; - scrollOffset = std::max(newScroll, max); - scrollOffset = std::min(min, newScroll); + // try to scroll it to the middle + int windowHeight = screen.dimy() - 4; + int scollOffsetToFitCommitToTop = -selectedCommit * CommitRender::COMMIT_WINDOW_HEIGHT; + int newScroll = + scollOffsetToFitCommitToTop + windowHeight / 2 - CommitRender::COMMIT_WINDOW_HEIGHT; + // adjust if on edges + int min = 0; + int max = -windowHeight - static_cast(visibleCommitViewMap.size() - 1) * + CommitRender::COMMIT_WINDOW_HEIGHT; + scrollOffset = std::max(newScroll, max); + scrollOffset = std::min(min, newScroll); } // SETTER & non-const GETTER void OSTreeTUI::setPromotionBranch(std::string promotionBranch) { - this->promotionBranch = promotionBranch; + this->promotionBranch = promotionBranch; } void OSTreeTUI::setSelectedCommit(size_t selectedCommit) { - this->selectedCommit = selectedCommit; - adjustScrollToSelectedCommit(); + this->selectedCommit = selectedCommit; + adjustScrollToSelectedCommit(); } std::vector& OSTreeTUI::getColumnToBranchMap() { - return columnToBranchMap; + return columnToBranchMap; } ftxui::ScreenInteractive& OSTreeTUI::getScreen() { - return screen; + return screen; } // GETTER const cpplibostree::OSTreeRepo& OSTreeTUI::getOstreeRepo() const { - return ostreeRepo; + return ostreeRepo; } const size_t& OSTreeTUI::getSelectedCommit() const { - return selectedCommit; + return selectedCommit; } const std::string& OSTreeTUI::getPromotionBranch() const { - return promotionBranch; + return promotionBranch; } const std::unordered_map& OSTreeTUI::getVisibleBranches() const { - return visibleBranches; + return visibleBranches; } const std::vector& OSTreeTUI::getColumnToBranchMap() const { - return columnToBranchMap; + return columnToBranchMap; } const std::vector& OSTreeTUI::getVisibleCommitViewMap() const { - return visibleCommitViewMap; + return visibleCommitViewMap; } const std::unordered_map& OSTreeTUI::getBranchColorMap() const { - return branchColorMap; + return branchColorMap; } const int& OSTreeTUI::getScrollOffset() const { - return scrollOffset; + return scrollOffset; } const bool& OSTreeTUI::getInPromotionSelection() const { - return inPromotionSelection; + return inPromotionSelection; } const std::string& OSTreeTUI::getPromotionHash() const { - return promotionHash; + return promotionHash; } // STATIC int OSTreeTUI::showHelp(const std::string& caller, const std::string& errorMessage) { - using namespace ftxui; - - // define command line options - std::vector> command_options{ - // option, arguments, meaning - {"-h, --help", "", "Show help options. The REPOSITORY_PATH can be omitted"}, - {"-r, --refs", "REF [REF...]", "Specify a list of visible refs at startup if not specified, show all refs"}, - }; - - Elements options {text("Options:")}; - Elements arguments {text("Arguments:")}; - Elements meanings {text("Meaning:")}; - for (const auto& command : command_options) { - options.push_back(text(command.at(0) + " ") | color(Color::GrayLight)); - arguments.push_back(text(command.at(1) + " ") | color(Color::GrayLight)); - meanings.push_back(text(command.at(2) + " ")); - } - - auto helpPage = vbox({ - errorMessage.empty() ? filler() : (text(errorMessage) | bold | color(Color::Red) | flex), - hbox({ - text("Usage: "), - text(caller) | color(Color::GrayLight), - text(" REPOSITORY_PATH") | color(Color::Yellow), - text(" [OPTION...]") | color(Color::Yellow) - }), - text(""), - hbox({ - vbox(options), - vbox(arguments), - vbox(meanings), - }), - text(""), - hbox({ - text("Report bugs at "), - text("Github.com/AP-Sensing/ostree-tui") | hyperlink("https://github.com/AP-Sensing/ostree-tui") - }), - text("") - }); - - auto screen = Screen::Create(Dimension::Fit(helpPage)); - Render(screen, helpPage); - screen.Print(); - std::cout << "\n"; - - return errorMessage.empty(); + using namespace ftxui; + + // define command line options + std::vector> command_options{ + // option, arguments, meaning + {"-h, --help", "", "Show help options. The REPOSITORY_PATH can be omitted"}, + {"-r, --refs", "REF [REF...]", + "Specify a list of visible refs at startup if not specified, show all refs"}, + }; + + Elements options{text("Options:")}; + Elements arguments{text("Arguments:")}; + Elements meanings{text("Meaning:")}; + for (const auto& command : command_options) { + options.push_back(text(command.at(0) + " ") | color(Color::GrayLight)); + arguments.push_back(text(command.at(1) + " ") | color(Color::GrayLight)); + meanings.push_back(text(command.at(2) + " ")); + } + + auto helpPage = vbox( + {errorMessage.empty() ? filler() : (text(errorMessage) | bold | color(Color::Red) | flex), + hbox({text("Usage: "), text(caller) | color(Color::GrayLight), + text(" REPOSITORY_PATH") | color(Color::Yellow), + text(" [OPTION...]") | color(Color::Yellow)}), + text(""), + hbox({ + vbox(options), + vbox(arguments), + vbox(meanings), + }), + text(""), + hbox({text("Report bugs at "), text("Github.com/AP-Sensing/ostree-tui") | + hyperlink("https://github.com/AP-Sensing/ostree-tui")}), + text("")}); + + auto screen = Screen::Create(Dimension::Fit(helpPage)); + Render(screen, helpPage); + screen.Print(); + std::cout << "\n"; + + return errorMessage.empty(); } int OSTreeTUI::showVersion() { - using namespace ftxui; - - auto versionText = text("ostree-tui 0.2.1"); - - auto screen = Screen::Create(Dimension::Fit(versionText)); - Render(screen, versionText); - screen.Print(); - std::cout << "\n"; - - return 0; + using namespace ftxui; + + auto versionText = text("ostree-tui 0.2.1"); + + auto screen = Screen::Create(Dimension::Fit(versionText)); + Render(screen, versionText); + screen.Print(); + std::cout << "\n"; + + return 0; } diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index 0c431b7..a673f34 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -12,7 +12,7 @@ #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "commit.hpp" #include "footer.hpp" @@ -21,20 +21,22 @@ #include "../util/cpplibostree.hpp" class OSTreeTUI { -public: + public: /** * @brief Constructs, builds and assembles all components of the OSTreeTUI. - * + * * @param repo ostree repository (OSTreeRepo) - * @param startupBranches optional list of branches to pre-select at startup (providing nothing will display all branches) - */ - explicit OSTreeTUI (const std::string& repo, const std::vector startupBranches = {}); - + * @param startupBranches optional list of branches to pre-select at startup (providing nothing + * will display all branches) + */ + explicit OSTreeTUI(const std::string& repo, + const std::vector startupBranches = {}); + /** * @brief Runs the OSTreeTUI (starts the ftxui screen loop). - * + * * @return exit code - */ + */ int run(); /// @brief Refresh Level 3: Refreshes the commit components @@ -55,28 +57,34 @@ class OSTreeTUI { bool setPromotionMode(bool active, std::string hash = "", bool setPromotionBranch = true); /** @brief promote a commit - * @param hash - * @param branch + * @param hash + * @param branch * @return promotion success - */ - bool promoteCommit(std::string hash, std::string branch, std::vector metadataStrings = {}, std::string newSubject = "", bool keepMetadata = true); + */ + bool promoteCommit(std::string hash, + std::string branch, + std::vector metadataStrings = {}, + std::string newSubject = "", + bool keepMetadata = true); -private: + private: /** * @brief Calculates all visible commits from an OSTreeRepo and a list of branches. - * + * * @param repo OSTreeRepo * @param visibleBranches Map: branch name -> visible - * @return Complete list of commit hashes in repo, that are part of the given branches + * @return Complete list of commit hashes in repo, that are part of the given branches */ - std::vector parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); + std::vector parseVisibleCommitMap( + cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches); /** * @brief Adjust scroll offset to fit the selected commit */ void adjustScrollToSelectedCommit(); -public: + public: // SETTER void setPromotionBranch(std::string promotionBranch); void setSelectedCommit(size_t selectedCommit); @@ -84,7 +92,7 @@ class OSTreeTUI { // non-const GETTER std::vector& getColumnToBranchMap(); ftxui::ScreenInteractive& getScreen(); - + // GETTER const cpplibostree::OSTreeRepo& getOstreeRepo() const; const size_t& getSelectedCommit() const; @@ -97,27 +105,29 @@ class OSTreeTUI { const bool& getInPromotionSelection() const; const std::string& getPromotionHash() const; -private: + private: // model cpplibostree::OSTreeRepo ostreeRepo; // backend states size_t selectedCommit; - std::unordered_map visibleBranches; // map branch -> visibe - std::vector columnToBranchMap; // map branch -> column in commit-tree (may be merged into one data-structure with visibleBranches) - std::vector visibleCommitViewMap; // map view-index -> commit-hash - std::unordered_map branchColorMap; // map branch -> color - std::string notificationText; // footer notification - + std::unordered_map visibleBranches; // map branch -> visibe + std::vector + columnToBranchMap; // map branch -> column in commit-tree (may be merged into one + // data-structure with visibleBranches) + std::vector visibleCommitViewMap; // map view-index -> commit-hash + std::unordered_map branchColorMap; // map branch -> color + std::string notificationText; // footer notification + // view states int scrollOffset{0}; bool inPromotionSelection{false}; - std::string promotionHash; - std::string promotionBranch; + std::string promotionHash; + std::string promotionBranch; // view constants int logSize{45}; - int footerSize{1}; + int footerSize{1}; // components Footer footer; @@ -126,8 +136,8 @@ class OSTreeTUI { ftxui::ScreenInteractive screen; ftxui::Component mainContainer; ftxui::Components commitComponents; - ftxui::Component commitList; - ftxui::Component tree; + ftxui::Component commitList; + ftxui::Component tree; ftxui::Component commitListComponent; ftxui::Component infoView; ftxui::Component filterView; @@ -135,10 +145,10 @@ class OSTreeTUI { ftxui::Component footerRenderer; ftxui::Component container; -public: + public: /** * @brief Print help page - * + * * @param caller argv[0] * @param errorMessage optional error message to print on top * @return 0, if no error message provided @@ -148,7 +158,7 @@ class OSTreeTUI { /** * @brief Print the application version - * + * * @return int */ static int showVersion(); diff --git a/src/core/commit.cpp b/src/core/commit.cpp index f09a738..1910d45 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -9,16 +9,16 @@ #include #include -#include // for Component, ComponentBase -#include "ftxui/component/component.hpp"// for Make -#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp -#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp -#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe -#include "ftxui/dom/node.hpp" // for Node -#include "ftxui/screen/box.hpp" // for Box +#include // for Component, ComponentBase #include +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include // for ScreenInteractive -#include "ftxui/screen/color.hpp" // for Color +#include "ftxui/component/component.hpp" // for Make +#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp +#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe +#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/screen/box.hpp" // for Box +#include "ftxui/screen/color.hpp" // for Color #include "../util/cpplibostree.hpp" @@ -31,429 +31,431 @@ using namespace ftxui; /// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp Decorator PositionAndSize(int left, int top, int width, int height) { - return [=](Element element) { - element |= size(WIDTH, EQUAL, width); - element |= size(HEIGHT, EQUAL, height); - - auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); - auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); - - return vbox({ - padding_top, - hbox({ - padding_left, - element, - }), - }); - }; + return [=](Element element) { + element |= size(WIDTH, EQUAL, width); + element |= size(HEIGHT, EQUAL, height); + + auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); + auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); + + return vbox({ + padding_top, + hbox({ + padding_left, + element, + }), + }); + }; } -/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp -Element DefaultRenderState(const WindowRenderState& state, ftxui::Color selectedColor = Color::White, bool dimmable = true) { - Element element = state.inner; - if (! dimmable) { - selectedColor = Color::White; - } - if (selectedColor == Color::White & dimmable) { - element |= dim; - } else { - element |= bold; - } - element |= color(selectedColor); - - element = window(text(state.title), element); - element |= clear_under; - - return element; +/// Partially inspired from +/// https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +Element DefaultRenderState(const WindowRenderState& state, + ftxui::Color selectedColor = Color::White, + bool dimmable = true) { + Element element = state.inner; + if (!dimmable) { + selectedColor = Color::White; + } + if (selectedColor == Color::White & dimmable) { + element |= dim; + } else { + element |= bold; + } + element |= color(selectedColor); + + element = window(text(state.title), element); + element |= clear_under; + + return element; } /// Draggable commit window, including ostree-tui logic for overlap detection, etc. -/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +/// Partially inspired from +/// https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp class CommitComponentImpl : public ComponentBase, public WindowOptions { -public: - explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) : - commitPosition(position), - hash(std::move(commit)), - ostreetui(ostreetui), - commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), - newVersion(this->commit.version), - drag_initial_y(position * COMMIT_WINDOW_HEIGHT), - drag_initial_x(1) - { - inner = Renderer([&] { - return vbox({ - text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), - text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast(ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), - }); - }); - simpleCommit = inner; - Add(inner); - - title = hash.substr(0, 8); - top = drag_initial_y; - left = drag_initial_x; - width = COMMIT_WINDOW_WIDTH; - height = COMMIT_WINDOW_HEIGHT; - } - -private: - void resetWindow(bool positionReset = true) { - if (positionReset) { - left() = drag_initial_x; - top() = drag_initial_y; - } - width() = width_initial; - height() = height_initial; - // reset window contents - DetachAllChildren(); - Add(simpleCommit); - } - - void startPromotionWindow() { - left() = std::max(left(),-2); - width() = PROMOTION_WINDOW_WIDTH; - height() = PROMOTION_WINDOW_HEIGHT; - // change inner to promotion layout - simpleCommit = inner; - DetachAllChildren(); - Add(promotionView); - if (! Focused()) { - TakeFocus(); - // manually fix focus issues - // change to proper control layout if possible... - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowUp); - } - } - - void executePromotion() { - // promote on the ostree repo - std::vector metadataStrings; - if (! newVersion.empty()) { - metadataStrings.push_back("version=" + newVersion); - } - ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, true); - resetWindow(); - } - - void cancelPromotion() { - ostreetui.setPromotionMode(false); - resetWindow(); - } - - Element Render() final { - // check if promotion was started not from drag & drop, but from ostreetui - if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { - if (! ostreetui.getPromotionBranch().empty()) { - startPromotionWindow(); - } else { - resetWindow(false); - } - } - - auto element = ComponentBase::Render(); - - const WindowRenderState state = { - element, - title(), - Active(), - drag_ - }; - - if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion - element = render ? render(state) : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), ostreetui.getPromotionHash() != hash); - } else { - element = render ? render(state) : DefaultRenderState(state, Color::White, ostreetui.getPromotionHash() != hash); - } - - // Position and record the drawn area of the window. - element |= reflect(box_window_); - element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); - element |= reflect(box_); - - return element; - } - - bool OnEvent(Event event) final { - if (ComponentBase::OnEvent(event)) { - return true; - } - - if (ostreetui.getInPromotionSelection()) { - // navigate promotion branches - if (event == Event::ArrowLeft) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it - 1) % ostreetui.getColumnToBranchMap().size())); - return true; - } - if (event == Event::ArrowRight) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it + 1) % ostreetui.getColumnToBranchMap().size())); - return true; - } - // promote - if (event == Event::Return) { - executePromotion(); - return true; - } - // cancel - if (event == Event::Escape) { - cancelPromotion(); - return true; - } - } - - if (!event.is_mouse()) { - return false; - } - - if (ostreetui.getInPromotionSelection() && ! drag_) { - return true; - } - - if (event.mouse().button == Mouse::Left) { - // update ostreetui - ostreetui.setSelectedCommit(commitPosition); - } - - mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); - // potentially indicate mouse hover - //if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} - - if (captured_mouse_) { - if (event.mouse().motion == Mouse::Released) { - // reset mouse - captured_mouse_ = nullptr; - // check if position matches branch & do something if it does - if (ostreetui.getPromotionBranch().empty()) { - ostreetui.setPromotionMode(false, hash); - resetWindow(); - } else { - ostreetui.setPromotionMode(true, hash); - } - return true; - } - - if (drag_) { - left() = event.mouse().x - drag_start_x - box_.x_min; - top() = event.mouse().y - drag_start_y - box_.y_min; - // potential promotion - ostreetui.setPromotionMode(true, hash, false); - // calculate which branch currently is hovered over - ostreetui.setPromotionBranch(""); - const int branch_pos = event.mouse().x / 2; - int count{0}; - for (const auto& [branch,visible] : ostreetui.getVisibleBranches()) { - if (visible) { - ++count; - } - if (count == branch_pos) { - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); - break; - } - } - } else { - // not promotion - ostreetui.setPromotionMode(false); - } - - // Clamp the window size. - width() = std::max(width(), static_cast(title().size() + 2)); - height() = std::max(height(), 2); - - return true; - } - - if (!mouse_hover_) { - return false; - } - - if (!CaptureMouse(event)) { - return true; - } - - if (event.mouse().button != Mouse::Left) { - return true; - } - if (event.mouse().motion != Mouse::Pressed) { - return true; - } - - TakeFocus(); - - captured_mouse_ = CaptureMouse(event); - if (!captured_mouse_) { - return true; - } - - drag_start_x = event.mouse().x - left() - box_.x_min; - drag_start_y = event.mouse().y - top() - box_.y_min; - - const bool drag_old = drag_; - drag_ = true; - if (!drag_old && drag_) { // if we start dragging - drag_initial_x = left(); - drag_initial_y = top(); - } - return true; - } - - // window specific members - Box box_; - Box box_window_; - - CapturedMouse captured_mouse_; - int drag_start_x = 0; - int drag_start_y = 0; - - int drag_initial_x; - int drag_initial_y; - int width_initial = COMMIT_WINDOW_WIDTH; - int height_initial = COMMIT_WINDOW_HEIGHT; - - bool mouse_hover_ = false; - bool drag_ = false; - - // ostree-tui specific members - int commitPosition; - std::string hash; - OSTreeTUI& ostreetui; - - // promotion view - cpplibostree::Commit commit; - std::string newSubject; - std::string newVersion; - Component simpleCommit = Renderer([] { - return text("error in commit window creation"); - }); - Component promotionView = Container::Vertical({ - Renderer([&] { - return vbox({ - text(""), - text(" Promote Commit...") | bold, - text(""), - text(" ☐ " + hash.substr(0, 8)) | bold, - }); - }), - Container::Horizontal({ - Renderer([&] { - return text(" ┆ subject: "); - }), - Input(&newSubject, "enter new subject...") | underlined - }), - // render version, if available - commit.version.empty() - ? Renderer([]{return filler();}) - : Container::Horizontal({ - Renderer([&] { - return text(" ┆ version: "); - }), - Input(&newVersion, commit.version) | underlined - }), - Renderer([&] { - return vbox({ - text(" ┆"), - text(" ┆ to branch:"), - text(" ☐ " + ostreetui.getPromotionBranch()) | bold, - text(" │") | bold - }); - }), - Container::Horizontal({ - Button(" Cancel ", [&] { - cancelPromotion(); - }) | color(Color::Red) | flex, - Button(" Promote ", [&] { - executePromotion(); - }) | color(Color::Green) | flex, - }) - }); + public: + explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) + : commitPosition(position), + hash(std::move(commit)), + ostreetui(ostreetui), + commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), + newVersion(this->commit.version), + drag_initial_y(position * COMMIT_WINDOW_HEIGHT), + drag_initial_x(1) { + inner = Renderer([&] { + return vbox({ + text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), + text( + std::format("{:%Y-%m-%d %T %Ez}", + std::chrono::time_point_cast( + ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), + }); + }); + simpleCommit = inner; + Add(inner); + + title = hash.substr(0, 8); + top = drag_initial_y; + left = drag_initial_x; + width = COMMIT_WINDOW_WIDTH; + height = COMMIT_WINDOW_HEIGHT; + } + + private: + void resetWindow(bool positionReset = true) { + if (positionReset) { + left() = drag_initial_x; + top() = drag_initial_y; + } + width() = width_initial; + height() = height_initial; + // reset window contents + DetachAllChildren(); + Add(simpleCommit); + } + + void startPromotionWindow() { + left() = std::max(left(), -2); + width() = PROMOTION_WINDOW_WIDTH; + height() = PROMOTION_WINDOW_HEIGHT; + // change inner to promotion layout + simpleCommit = inner; + DetachAllChildren(); + Add(promotionView); + if (!Focused()) { + TakeFocus(); + // manually fix focus issues + // change to proper control layout if possible... + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowUp); + } + } + + void executePromotion() { + // promote on the ostree repo + std::vector metadataStrings; + if (!newVersion.empty()) { + metadataStrings.push_back("version=" + newVersion); + } + ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, + true); + resetWindow(); + } + + void cancelPromotion() { + ostreetui.setPromotionMode(false); + resetWindow(); + } + + Element Render() final { + // check if promotion was started not from drag & drop, but from ostreetui + if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { + if (!ostreetui.getPromotionBranch().empty()) { + startPromotionWindow(); + } else { + resetWindow(false); + } + } + + auto element = ComponentBase::Render(); + + const WindowRenderState state = {element, title(), Active(), drag_}; + + if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion + element = + render ? render(state) + : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), + ostreetui.getPromotionHash() != hash); + } else { + element = render ? render(state) + : DefaultRenderState(state, Color::White, + ostreetui.getPromotionHash() != hash); + } + + // Position and record the drawn area of the window. + element |= reflect(box_window_); + element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); + element |= reflect(box_); + + return element; + } + + bool OnEvent(Event event) final { + if (ComponentBase::OnEvent(event)) { + return true; + } + + if (ostreetui.getInPromotionSelection()) { + // navigate promotion branches + if (event == Event::ArrowLeft) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), + ostreetui.getColumnToBranchMap().end(), + ostreetui.getPromotionBranch()) - + ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( + (it - 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + if (event == Event::ArrowRight) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), + ostreetui.getColumnToBranchMap().end(), + ostreetui.getPromotionBranch()) - + ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( + (it + 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + // promote + if (event == Event::Return) { + executePromotion(); + return true; + } + // cancel + if (event == Event::Escape) { + cancelPromotion(); + return true; + } + } + + if (!event.is_mouse()) { + return false; + } + + if (ostreetui.getInPromotionSelection() && !drag_) { + return true; + } + + if (event.mouse().button == Mouse::Left) { + // update ostreetui + ostreetui.setSelectedCommit(commitPosition); + } + + mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); + // potentially indicate mouse hover + // if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} + + if (captured_mouse_) { + if (event.mouse().motion == Mouse::Released) { + // reset mouse + captured_mouse_ = nullptr; + // check if position matches branch & do something if it does + if (ostreetui.getPromotionBranch().empty()) { + ostreetui.setPromotionMode(false, hash); + resetWindow(); + } else { + ostreetui.setPromotionMode(true, hash); + } + return true; + } + + if (drag_) { + left() = event.mouse().x - drag_start_x - box_.x_min; + top() = event.mouse().y - drag_start_y - box_.y_min; + // potential promotion + ostreetui.setPromotionMode(true, hash, false); + // calculate which branch currently is hovered over + ostreetui.setPromotionBranch(""); + const int branch_pos = event.mouse().x / 2; + int count{0}; + for (const auto& [branch, visible] : ostreetui.getVisibleBranches()) { + if (visible) { + ++count; + } + if (count == branch_pos) { + ostreetui.setPromotionBranch( + ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); + break; + } + } + } else { + // not promotion + ostreetui.setPromotionMode(false); + } + + // Clamp the window size. + width() = std::max(width(), static_cast(title().size() + 2)); + height() = std::max(height(), 2); + + return true; + } + + if (!mouse_hover_) { + return false; + } + + if (!CaptureMouse(event)) { + return true; + } + + if (event.mouse().button != Mouse::Left) { + return true; + } + if (event.mouse().motion != Mouse::Pressed) { + return true; + } + + TakeFocus(); + + captured_mouse_ = CaptureMouse(event); + if (!captured_mouse_) { + return true; + } + + drag_start_x = event.mouse().x - left() - box_.x_min; + drag_start_y = event.mouse().y - top() - box_.y_min; + + const bool drag_old = drag_; + drag_ = true; + if (!drag_old && drag_) { // if we start dragging + drag_initial_x = left(); + drag_initial_y = top(); + } + return true; + } + + // window specific members + Box box_; + Box box_window_; + + CapturedMouse captured_mouse_; + int drag_start_x = 0; + int drag_start_y = 0; + + int drag_initial_x; + int drag_initial_y; + int width_initial = COMMIT_WINDOW_WIDTH; + int height_initial = COMMIT_WINDOW_HEIGHT; + + bool mouse_hover_ = false; + bool drag_ = false; + + // ostree-tui specific members + int commitPosition; + std::string hash; + OSTreeTUI& ostreetui; + + // promotion view + cpplibostree::Commit commit; + std::string newSubject; + std::string newVersion; + Component simpleCommit = Renderer([] { return text("error in commit window creation"); }); + Component promotionView = Container::Vertical( + {Renderer([&] { + return vbox({ + text(""), + text(" Promote Commit...") | bold, + text(""), + text(" ☐ " + hash.substr(0, 8)) | bold, + }); + }), + Container::Horizontal({Renderer([&] { return text(" ┆ subject: "); }), + Input(&newSubject, "enter new subject...") | underlined}), + // render version, if available + commit.version.empty() + ? Renderer([] { return filler(); }) + : Container::Horizontal({Renderer([&] { return text(" ┆ version: "); }), + Input(&newVersion, commit.version) | underlined}), + Renderer([&] { + return vbox({text(" ┆"), text(" ┆ to branch:"), + text(" ☐ " + ostreetui.getPromotionBranch()) | bold, text(" │") | bold}); + }), + Container::Horizontal({ + Button(" Cancel ", [&] { cancelPromotion(); }) | color(Color::Red) | flex, + Button(" Promote ", [&] { executePromotion(); }) | color(Color::Green) | flex, + })}); }; } // namespace ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui) { - return ftxui::Make(position, commit, ostreetui); + return ftxui::Make(position, commit, ostreetui); } -ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap) { - using namespace ftxui; - - int scrollOffset = ostreetui.getScrollOffset(); - - // check empty commit list - if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { - return color(Color::RedLight, text(" no commits to be shown ") | bold | center); - } - - // stores the dedicated tree-column of each branch, -1 meaning not displayed yet - std::unordered_map usedBranches{}; - for (const auto& branchPair : ostreetui.getVisibleBranches()) { - if (branchPair.second) { - usedBranches[branchPair.first] = -1; - } - } - int nextAvailableSpace = static_cast(usedBranches.size() - 1); - - // - RENDER - - // left tree, right commits - Elements treeElements{}; - Elements commElements{}; - - ostreetui.getColumnToBranchMap().clear(); - for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { - const cpplibostree::Commit commit = ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); - // branch head if it is first branch usage - const std::string relevantBranch = commit.branch; - if (usedBranches.at(relevantBranch) == -1) { - ostreetui.getColumnToBranchMap().push_back(relevantBranch); - usedBranches.at(relevantBranch) = nextAvailableSpace--; - } - // commit - if (scrollOffset++ >= 0) { - treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_NODE, commit, usedBranches, branchColorMap)); - } - for (int i{0}; i < 3; i++) { - if (scrollOffset++ >= 0) { - treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_TREE, commit, usedBranches, branchColorMap)); - } - } - } - std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); - - return hbox({ - vbox(std::move(treeElements)), - vbox(std::move(commElements)) - }); +ftxui::Element commitRender(OSTreeTUI& ostreetui, + const std::unordered_map& branchColorMap) { + using namespace ftxui; + + int scrollOffset = ostreetui.getScrollOffset(); + + // check empty commit list + if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { + return color(Color::RedLight, text(" no commits to be shown ") | bold | center); + } + + // stores the dedicated tree-column of each branch, -1 meaning not displayed yet + std::unordered_map usedBranches{}; + for (const auto& branchPair : ostreetui.getVisibleBranches()) { + if (branchPair.second) { + usedBranches[branchPair.first] = -1; + } + } + int nextAvailableSpace = static_cast(usedBranches.size() - 1); + + // - RENDER - + // left tree, right commits + Elements treeElements{}; + Elements commElements{}; + + ostreetui.getColumnToBranchMap().clear(); + for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { + const cpplibostree::Commit commit = + ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); + // branch head if it is first branch usage + const std::string relevantBranch = commit.branch; + if (usedBranches.at(relevantBranch) == -1) { + ostreetui.getColumnToBranchMap().push_back(relevantBranch); + usedBranches.at(relevantBranch) = nextAvailableSpace--; + } + // commit + if (scrollOffset++ >= 0) { + treeElements.push_back( + addTreeLine(RenderTree::TREE_LINE_NODE, commit, usedBranches, branchColorMap)); + } + for (int i{0}; i < 3; i++) { + if (scrollOffset++ >= 0) { + treeElements.push_back( + addTreeLine(RenderTree::TREE_LINE_TREE, commit, usedBranches, branchColorMap)); + } + } + } + std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); + + return hbox({vbox(std::move(treeElements)), vbox(std::move(commElements))}); } ftxui::Element addTreeLine(const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap) { - using namespace ftxui; - - const std::string relevantBranch = commit.branch; - // create an empty branch tree line - Elements tree(usedBranches.size(), text(COMMIT_NONE)); - - // populate tree with all displayed branches - for (const auto& branch : usedBranches) { - if (branch.second == -1) { - continue; - } - - if (treeLineType == RenderTree::TREE_LINE_TREE || (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && branch.first != relevantBranch)) { - tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); - } else if (treeLineType == RenderTree::TREE_LINE_NODE) { - if (branch.first == relevantBranch) { - tree.at(branch.second) = (text(COMMIT_NODE) | color(branchColorMap.at(branch.first))); - } else { - tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); - } - } + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap) { + using namespace ftxui; + + const std::string relevantBranch = commit.branch; + // create an empty branch tree line + Elements tree(usedBranches.size(), text(COMMIT_NONE)); + + // populate tree with all displayed branches + for (const auto& branch : usedBranches) { + if (branch.second == -1) { + continue; + } + + if (treeLineType == RenderTree::TREE_LINE_TREE || + (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && + branch.first != relevantBranch)) { + tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); + } else if (treeLineType == RenderTree::TREE_LINE_NODE) { + if (branch.first == relevantBranch) { + tree.at(branch.second) = + (text(COMMIT_NODE) | color(branchColorMap.at(branch.first))); + } else { + tree.at(branch.second) = + (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); + } + } } return hbox(std::move(tree)); diff --git a/src/core/commit.hpp b/src/core/commit.hpp index d455132..0f0c18e 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -15,66 +15,65 @@ #include #include -#include "ftxui/component/component_base.hpp" #include +#include "ftxui/component/component_base.hpp" #include "../util/cpplibostree.hpp" class OSTreeTUI; namespace CommitRender { - // UI characters - constexpr std::string COMMIT_NODE {" ☐"}; - constexpr std::string COMMIT_TREE {" │"}; - constexpr std::string COMMIT_NONE {" "}; - // window dimensions - constexpr int COMMIT_WINDOW_HEIGHT {4}; - constexpr int COMMIT_WINDOW_WIDTH {32}; - constexpr int PROMOTION_WINDOW_HEIGHT {COMMIT_WINDOW_HEIGHT + 11}; - constexpr int PROMOTION_WINDOW_WIDTH {COMMIT_WINDOW_WIDTH + 8}; - // render tree types - enum RenderTree : uint8_t { - TREE_LINE_NODE, // ☐ | | - TREE_LINE_TREE, // | | | - TREE_LINE_IGNORE_BRANCH // | | - }; +// UI characters +constexpr std::string COMMIT_NODE{" ☐"}; +constexpr std::string COMMIT_TREE{" │"}; +constexpr std::string COMMIT_NONE{" "}; +// window dimensions +constexpr int COMMIT_WINDOW_HEIGHT{4}; +constexpr int COMMIT_WINDOW_WIDTH{32}; +constexpr int PROMOTION_WINDOW_HEIGHT{COMMIT_WINDOW_HEIGHT + 11}; +constexpr int PROMOTION_WINDOW_WIDTH{COMMIT_WINDOW_WIDTH + 8}; +// render tree types +enum RenderTree : uint8_t { + TREE_LINE_NODE, // ☐ | | + TREE_LINE_TREE, // | | | + TREE_LINE_IGNORE_BRANCH // | | +}; - /** - * @brief Creates a window, containing a hash and some of its details. - * The window has a pre-defined position and snaps back to it, - * after being dragged & let go. When hovering over a branch, - * defined in `columnToBranchMap`, the window expands to a commit - * promotion window. - * To be used with other windows, use a ftxui::Component::Stacked. - * - * @return Component - */ - ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); +/** + * @brief Creates a window, containing a hash and some of its details. + * The window has a pre-defined position and snaps back to it, + * after being dragged & let go. When hovering over a branch, + * defined in `columnToBranchMap`, the window expands to a commit + * promotion window. + * To be used with other windows, use a ftxui::Component::Stacked. + * + * @return Component + */ +ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); - /** - * @brief create a Renderer for the commit section - * - * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info - * @param branchColorMap Map from branch to its display color - * @param selectedCommit Commit that should be marked as selected - * @return ftxui::Element - */ - ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap); +/** + * @brief create a Renderer for the commit section + * + * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info + * @param branchColorMap Map from branch to its display color + * @param selectedCommit Commit that should be marked as selected + * @return ftxui::Element + */ +ftxui::Element commitRender(OSTreeTUI& ostreetui, + const std::unordered_map& branchColorMap); - /** - * @brief build a commit-tree line - * - * @param treeLineType type of commit-tree - * @param commit commit to render / get info from - * @param usedBranches branches to render - * @param branchColorMap branch colors - * @return ftxui::Element commit-tree line - */ - ftxui::Element addTreeLine( - const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap); +/** + * @brief build a commit-tree line + * + * @param treeLineType type of commit-tree + * @param commit commit to render / get info from + * @param usedBranches branches to render + * @param branchColorMap branch colors + * @return ftxui::Element commit-tree line + */ +ftxui::Element addTreeLine(const RenderTree& treeLineType, + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap); -} // namespace CommitRender +} // namespace CommitRender diff --git a/src/core/footer.cpp b/src/core/footer.cpp index ed7ace0..8faf514 100644 --- a/src/core/footer.cpp +++ b/src/core/footer.cpp @@ -3,19 +3,20 @@ #include "footer.hpp" ftxui::Element Footer::footerRender() { - using namespace ftxui; - - return hbox({ - text("OSTree TUI") | bold | hyperlink("https://github.com/AP-Sensing/ostree-tui"), - separator(), - text(content) | (content == DEFAULT_CONTENT ? color(Color::White) : color(Color::YellowLight)), - }); + using namespace ftxui; + + return hbox({ + text("OSTree TUI") | bold | hyperlink("https://github.com/AP-Sensing/ostree-tui"), + separator(), + text(content) | + (content == DEFAULT_CONTENT ? color(Color::White) : color(Color::YellowLight)), + }); } void Footer::resetContent() { - content = DEFAULT_CONTENT; + content = DEFAULT_CONTENT; } void Footer::setContent(std::string content) { - this->content = content; + this->content = content; } diff --git a/src/core/footer.hpp b/src/core/footer.hpp index ba978e0..de3cf89 100644 --- a/src/core/footer.hpp +++ b/src/core/footer.hpp @@ -4,11 +4,10 @@ | keyboard shortcuts info. |___________________________________________________________*/ - class Footer { -public: + public: Footer() = default; - + /// reset footer text to default string void resetContent(); @@ -18,8 +17,9 @@ class Footer { // Setter void setContent(std::string content); -private: - const std::string DEFAULT_CONTENT {" || Alt+Q : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || Alt+P : Promote Commit "}; - std::string content {DEFAULT_CONTENT}; - + private: + const std::string DEFAULT_CONTENT{ + " || Alt+Q : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || Alt+P : Promote " + "Commit "}; + std::string content{DEFAULT_CONTENT}; }; diff --git a/src/core/manager.cpp b/src/core/manager.cpp index f49725b..876ed40 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -1,12 +1,12 @@ #include "manager.hpp" +#include #include #include -#include #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop -#include "ftxui/component/event.hpp" // for Event -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border +#include "ftxui/component/event.hpp" // for Event +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "../util/cpplibostree.hpp" @@ -14,110 +14,91 @@ // Manager -Manager::Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView) : ostreetui(ostreetui) { - using namespace ftxui; - - tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); - - tabContent = Container::Tab({ - infoView, - filterView - }, - &tab_index); - - managerRenderer = Container::Vertical({ - tabSelection, - tabContent, - Renderer([] {return vbox({filler()}) | flex;}), // push elements apart - Renderer([&] { - Elements branches; - for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { - std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); - std::string line = "――☐――― " + branch; - branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); - } - return vbox(branches); - }) - }); +Manager::Manager(OSTreeTUI& ostreetui, + const ftxui::Component& infoView, + const ftxui::Component& filterView) + : ostreetui(ostreetui) { + using namespace ftxui; + + tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); + + tabContent = Container::Tab({infoView, filterView}, &tab_index); + + managerRenderer = Container::Vertical( + {tabSelection, tabContent, + Renderer([] { return vbox({filler()}) | flex; }), // push elements apart + Renderer([&] { + Elements branches; + for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { + std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); + std::string line = "――☐――― " + branch; + branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); + } + return vbox(branches); + })}); } ftxui::Component Manager::getManagerRenderer() { - return managerRenderer; + return managerRenderer; } const int& Manager::getTabIndex() const { - return tab_index; + return tab_index; } // BranchBoxManager -BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches) { +BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches) { using namespace ftxui; - // branch visibility - for (const auto& branch : repo.getBranches()) { - branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)))); - } + // branch visibility + for (const auto& branch : repo.getBranches()) { + branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)))); + } } -ftxui::Element BranchBoxManager::branchBoxRender(){ - using namespace ftxui; - - // branch filter - Elements bfb_elements = { - text(L"branches:") | bold, - filler(), - branchBoxes->Render() | vscroll_indicator | frame | size(HEIGHT, LESS_THAN, 10), - }; - return vbox(bfb_elements); +ftxui::Element BranchBoxManager::branchBoxRender() { + using namespace ftxui; + + // branch filter + Elements bfb_elements = { + text(L"branches:") | bold, + filler(), + branchBoxes->Render() | vscroll_indicator | frame | size(HEIGHT, LESS_THAN, 10), + }; + return vbox(bfb_elements); } // CommitInfoManager ftxui::Element CommitInfoManager::renderInfoView(const cpplibostree::Commit& displayCommit) { - using namespace ftxui; - - // selected commit info - Elements signatures; - for (const auto& signature : displayCommit.signatures) { - std::string ts = std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(signature.timestamp)); - signatures.push_back(vbox({ - hbox({ - text("‣ "), - text(signature.pubkeyAlgorithm) | bold, - text(" signature") - }), - text(" with key ID " + signature.fingerprint), - text(" made " + ts) - })); - } - return vbox({ - text(" Subject:") | color(Color::Green), - paragraph(displayCommit.subject) | color(Color::White), - filler(), - text(" Hash: ") | color(Color::Green), - text(displayCommit.hash), - filler(), - text(" Date: ") | color(Color::Green), - text(std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(displayCommit.timestamp))), - filler(), - // TODO insert version, only if exists - displayCommit.version.empty() - ? filler() - : text(" Version: ") | color(Color::Green), - displayCommit.version.empty() - ? filler() - : text(displayCommit.version), - text(" Parent: ") | color(Color::Green), - text(displayCommit.parent), - filler(), - text(" Checksum: ") | color(Color::Green), - text(displayCommit.contentChecksum), - filler(), - displayCommit.signatures.size() > 0 ? text(" Signatures: ") | color(Color::Green) : text(""), - vbox(signatures), - filler() - }); + using namespace ftxui; + + // selected commit info + Elements signatures; + for (const auto& signature : displayCommit.signatures) { + std::string ts = + std::format("{:%Y-%m-%d %T %Ez}", + std::chrono::time_point_cast(signature.timestamp)); + signatures.push_back( + vbox({hbox({text("‣ "), text(signature.pubkeyAlgorithm) | bold, text(" signature")}), + text(" with key ID " + signature.fingerprint), text(" made " + ts)})); + } + return vbox( + {text(" Subject:") | color(Color::Green), + paragraph(displayCommit.subject) | color(Color::White), filler(), + text(" Hash: ") | color(Color::Green), text(displayCommit.hash), filler(), + text(" Date: ") | color(Color::Green), + text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast( + displayCommit.timestamp))), + filler(), + // TODO insert version, only if exists + displayCommit.version.empty() ? filler() : text(" Version: ") | color(Color::Green), + displayCommit.version.empty() ? filler() : text(displayCommit.version), + text(" Parent: ") | color(Color::Green), text(displayCommit.parent), filler(), + text(" Checksum: ") | color(Color::Green), text(displayCommit.contentChecksum), filler(), + displayCommit.signatures.size() > 0 ? text(" Signatures: ") | color(Color::Green) + : text(""), + vbox(signatures), filler()}); } diff --git a/src/core/manager.hpp b/src/core/manager.hpp index 5e41dba..8719e8c 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -18,16 +18,16 @@ class OSTreeTUI; /// Interchangeable View class Manager { -public: - Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView); + public: + Manager(OSTreeTUI& ostreetui, + const ftxui::Component& infoView, + const ftxui::Component& filterView); -private: + private: OSTreeTUI& ostreetui; int tab_index{0}; - std::vector tab_entries = { - " Info ", " Filter " - }; + std::vector tab_entries = {" Info ", " Filter "}; // because the combination of all interchangeable views is very simple, // we can (in contrast to the other ones) render this one here @@ -35,33 +35,34 @@ class Manager { ftxui::Component tabSelection; ftxui::Component tabContent; -public: + public: ftxui::Component getManagerRenderer(); const int& getTabIndex() const; }; class CommitInfoManager { -public: + public: /** * @brief Build the info view Element. - * + * * @param displayCommit Commit to display the information of. - * @return ftxui::Element + * @return ftxui::Element */ static ftxui::Element renderInfoView(const cpplibostree::Commit& displayCommit); }; class BranchBoxManager { -public: + public: ftxui::Component branchBoxes = ftxui::Container::Vertical({}); -public: - BranchBoxManager(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); - + public: + BranchBoxManager(cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches); + /** * @brief Build the branch box Element. - * - * @return ftxui::Element + * + * @return ftxui::Element */ ftxui::Element branchBoxRender(); }; diff --git a/src/main.cpp b/src/main.cpp index 29eb9be..98d6f66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,56 +6,57 @@ /** * @brief Parse all options listed behind an argument - * + * * @param args argument list * @param find argument (as vector for multiple arg version support, like -h & --help) * @return vector of options */ -std::vector getArgOptions(const std::vector& args, const std::vector& find) { - std::vector out; - for (const auto& arg : find) { - auto argIndex = std::find(args.begin(), args.end(), arg); - if (argIndex == args.end()) { - continue; - } - while (++argIndex != args.end()) { - if ((*argIndex).at(0) == '-') { - // arrived at next argument -> stop - break; - } - out.push_back(*argIndex); - } - } - return out; +std::vector getArgOptions(const std::vector& args, + const std::vector& find) { + std::vector out; + for (const auto& arg : find) { + auto argIndex = std::find(args.begin(), args.end(), arg); + if (argIndex == args.end()) { + continue; + } + while (++argIndex != args.end()) { + if ((*argIndex).at(0) == '-') { + // arrived at next argument -> stop + break; + } + out.push_back(*argIndex); + } + } + return out; } /// Check if argument exists in argument list bool argExists(const std::vector& args, std::string arg) { - return std::find(args.begin(), args.end(), arg) != args.end(); + return std::find(args.begin(), args.end(), arg) != args.end(); } /// main for argument parsing and OSTree TUI call int main(int argc, const char** argv) { - // too few amount of arguments - if (argc <= 1) { - return OSTreeTUI::showHelp(argc <= 0 ? "ostree" : argv[0], "no repository provided"); - } + // too few amount of arguments + if (argc <= 1) { + return OSTreeTUI::showHelp(argc <= 0 ? "ostree" : argv[0], "no repository provided"); + } - std::vector args(argv + 1, argv + argc); - // -h, --help - if (argExists(args,"-h") || argExists(args,"--help")) { - return OSTreeTUI::showHelp(argv[0]); - } - // -v, --version - if (argExists(args,"-v") || argExists(args,"--version")) { - return OSTreeTUI::showVersion(); - } - // assume ostree repository path as first argument - std::string repo = args.at(0); - // -r, --refs - std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); + std::vector args(argv + 1, argv + argc); + // -h, --help + if (argExists(args, "-h") || argExists(args, "--help")) { + return OSTreeTUI::showHelp(argv[0]); + } + // -v, --version + if (argExists(args, "-v") || argExists(args, "--version")) { + return OSTreeTUI::showVersion(); + } + // assume ostree repository path as first argument + std::string repo = args.at(0); + // -r, --refs + std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); - // OSTree TUI - OSTreeTUI ostreetui(repo, startupBranches); - return ostreetui.run(); + // OSTree TUI + OSTreeTUI ostreetui(repo, startupBranches); + return ostreetui.run(); } diff --git a/src/util/cpplibostree.cpp b/src/util/cpplibostree.cpp index e97c4f5..24bcbcb 100644 --- a/src/util/cpplibostree.cpp +++ b/src/util/cpplibostree.cpp @@ -8,329 +8,338 @@ #include #include // C -#include +#include #include #include #include -#include +#include namespace cpplibostree { - OSTreeRepo::OSTreeRepo(std::string path): - repoPath(std::move(path)), - commitList({}), - branches({}) { - updateData(); +OSTreeRepo::OSTreeRepo(std::string path) : repoPath(std::move(path)), commitList({}), branches({}) { + updateData(); +} + +bool OSTreeRepo::updateData() { + // parse branches + std::string branchString = getBranchesAsString(); + std::stringstream bss(branchString); + std::string word; + while (bss >> word) { + branches.push_back(word); } - bool OSTreeRepo::updateData() { - // parse branches - std::string branchString = getBranchesAsString(); - std::stringstream bss(branchString); - std::string word; - while (bss >> word) { - branches.push_back(word); - } + // parse commits + commitList = parseCommitsAllBranches(); - // parse commits - commitList = parseCommitsAllBranches(); + return true; +} - return true; - } +// METHODS - // METHODS - - OstreeRepo* OSTreeRepo::_c() { - // open repo - GError *error{nullptr}; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return nullptr; - } - return repo; +OstreeRepo* OSTreeRepo::_c() { + // open repo + GError* error{nullptr}; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return nullptr; } - - const std::string& OSTreeRepo::getRepoPath() const { - return repoPath; + return repo; +} + +const std::string& OSTreeRepo::getRepoPath() const { + return repoPath; +} + +const CommitList& OSTreeRepo::getCommitList() const { + return commitList; +} + +const std::vector& OSTreeRepo::getBranches() const { + return branches; +} + +bool OSTreeRepo::isCommitSigned(const Commit& commit) { + return commit.signatures.size() > 0; +} + +Commit OSTreeRepo::parseCommit(GVariant* variant, + const std::string& branch, + const std::string& hash) { + Commit commit; + + const gchar* subject{nullptr}; + const gchar* body{nullptr}; + const gchar* version{nullptr}; + guint64 timestamp{0}; + g_autofree char* parent{nullptr}; + + // see OSTREE_COMMIT_GVARIANT_FORMAT + g_variant_get(variant, "(a{sv}aya(say)&s&stayay)", nullptr, nullptr, nullptr, &subject, &body, + ×tamp, nullptr, nullptr); + assert(body); + assert(timestamp); + + // timestamp + timestamp = GUINT64_FROM_BE(timestamp); + commit.timestamp = Timepoint(std::chrono::seconds(timestamp)); + + // parent + parent = ostree_commit_get_parent(variant); + if (parent) { + commit.parent = parent; + } else { + commit.parent = "(no parent)"; } - const CommitList& OSTreeRepo::getCommitList() const { - return commitList; + // content checksum + g_autofree char* contents = ostree_commit_get_content_checksum(variant); + assert(contents); + commit.contentChecksum = contents; + + // version + g_autoptr(GVariant) metadata = NULL; + const char* ret = NULL; + metadata = g_variant_get_child_value(variant, 0); + if (g_variant_lookup(metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &ret)) { + version = g_strdup(ret); + commit.version = version; } - const std::vector& OSTreeRepo::getBranches() const { - return branches; + // subject + if (subject[0]) { + std::string val = subject; + commit.subject = val; + } else { + commit.subject = "(no subject)"; } - bool OSTreeRepo::isCommitSigned(const Commit& commit) { - return commit.signatures.size() > 0; + // body + if (body[0]) { + commit.body = body; } - Commit OSTreeRepo::parseCommit(GVariant *variant, const std::string& branch, const std::string& hash) { - Commit commit; - - const gchar *subject {nullptr}; - const gchar *body {nullptr}; - const gchar *version {nullptr}; - guint64 timestamp {0}; - g_autofree char *parent {nullptr}; - - // see OSTREE_COMMIT_GVARIANT_FORMAT - g_variant_get(variant, "(a{sv}aya(say)&s&stayay)", nullptr, nullptr, nullptr, &subject, &body, ×tamp, nullptr, nullptr); - assert(body); - assert(timestamp); - - // timestamp - timestamp = GUINT64_FROM_BE(timestamp); - commit.timestamp = Timepoint(std::chrono::seconds(timestamp)); - - // parent - parent = ostree_commit_get_parent(variant); - if (parent) { - commit.parent = parent; - } else { - commit.parent = "(no parent)"; - } - - // content checksum - g_autofree char *contents = ostree_commit_get_content_checksum(variant); - assert(contents); - commit.contentChecksum = contents; - - // version - g_autoptr (GVariant) metadata = NULL; - const char *ret = NULL; - metadata = g_variant_get_child_value(variant, 0); - if (g_variant_lookup(metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &ret)) { - version = g_strdup(ret); - commit.version = version; - } - - // subject - if (subject[0]) { - std::string val = subject; - commit.subject = val; - } else { - commit.subject = "(no subject)"; - } - - // body - if (body[0]) { - commit.body = body; - } - - commit.branch = branch; - commit.hash = hash; - - // Signatures ___ refactor into own method - // open repo - GError *error = nullptr; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - assert(repo); - } - // see ostree print_object for reference - g_autoptr(OstreeGpgVerifyResult) result = nullptr; - g_autoptr(GError) local_error = nullptr; - result = ostree_repo_verify_commit_ext (repo, commit.hash.c_str(), nullptr, nullptr, nullptr, - &local_error); - if (g_error_matches (local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE) || local_error != nullptr) { - /* Ignore */ - } else { - assert(result); - guint n_sigs = ostree_gpg_verify_result_count_all (result); - // parse all found signatures - for (guint ii = 0; ii < n_sigs; ii++) { - g_autoptr(GVariant) variant = nullptr; - variant = ostree_gpg_verify_result_get_all (result, ii); - // see ostree_gpg_verify_result_describe_variant for reference - gint64 timestamp {0}; - gint64 exp_timestamp {0}; - gint64 key_exp_timestamp {0}; - gint64 key_exp_timestamp_primary{0}; - const char *fingerprint {nullptr}; - const char *fingerprintPrimary {nullptr}; - const char *pubkey_algo {nullptr}; - const char *user_name {nullptr}; - const char *user_email {nullptr}; - gboolean valid {false}; - gboolean sigExpired {false}; - gboolean keyExpired {false}; - gboolean keyRevoked {false}; - gboolean keyMissing {false}; - - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_VALID, "b", &valid); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sigExpired); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &keyExpired); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &keyRevoked); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &keyMissing); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, "&s", &fingerprint); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, "&s", &fingerprintPrimary); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, "x", ×tamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, "x", &exp_timestamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, "&s", &pubkey_algo); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, "&s", &user_name); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, "&s", &user_email); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, "x", &key_exp_timestamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, "x", &key_exp_timestamp_primary); - - // create signature struct - Signature sig; - - sig.valid = valid; - sig.sigExpired = sigExpired; - sig.keyExpired = keyExpired; - sig.keyRevoked = keyRevoked; - sig.keyMissing = keyMissing; - sig.fingerprint = fingerprint; - sig.fingerprintPrimary = fingerprintPrimary; - sig.timestamp = Timepoint(std::chrono::seconds(timestamp)); - sig.expireTimestamp = Timepoint(std::chrono::seconds(exp_timestamp)); - sig.pubkeyAlgorithm = pubkey_algo; - sig.username = user_name; - sig.usermail = user_email; - sig.keyExpireTimestamp = Timepoint(std::chrono::seconds(key_exp_timestamp)); - sig.keyExpireTimestampPrimary = Timepoint(std::chrono::seconds(key_exp_timestamp_primary)); - - commit.signatures.push_back(std::move(sig)); - } + commit.branch = branch; + commit.hash = hash; + + // Signatures ___ refactor into own method + // open repo + GError* error = nullptr; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + assert(repo); + } + // see ostree print_object for reference + g_autoptr(OstreeGpgVerifyResult) result = nullptr; + g_autoptr(GError) local_error = nullptr; + result = ostree_repo_verify_commit_ext(repo, commit.hash.c_str(), nullptr, nullptr, nullptr, + &local_error); + if (g_error_matches(local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE) || + local_error != nullptr) { + /* Ignore */ + } else { + assert(result); + guint n_sigs = ostree_gpg_verify_result_count_all(result); + // parse all found signatures + for (guint ii = 0; ii < n_sigs; ii++) { + g_autoptr(GVariant) variant = nullptr; + variant = ostree_gpg_verify_result_get_all(result, ii); + // see ostree_gpg_verify_result_describe_variant for reference + gint64 timestamp{0}; + gint64 exp_timestamp{0}; + gint64 key_exp_timestamp{0}; + gint64 key_exp_timestamp_primary{0}; + const char* fingerprint{nullptr}; + const char* fingerprintPrimary{nullptr}; + const char* pubkey_algo{nullptr}; + const char* user_name{nullptr}; + const char* user_email{nullptr}; + gboolean valid{false}; + gboolean sigExpired{false}; + gboolean keyExpired{false}; + gboolean keyRevoked{false}; + gboolean keyMissing{false}; + + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_VALID, "b", &valid); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sigExpired); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &keyExpired); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &keyRevoked); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &keyMissing); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, "&s", &fingerprint); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, "&s", + &fingerprintPrimary); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, "x", ×tamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, "x", + &exp_timestamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, "&s", + &pubkey_algo); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, "&s", &user_name); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, "&s", &user_email); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, "x", + &key_exp_timestamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, "x", + &key_exp_timestamp_primary); + + // create signature struct + Signature sig; + + sig.valid = valid; + sig.sigExpired = sigExpired; + sig.keyExpired = keyExpired; + sig.keyRevoked = keyRevoked; + sig.keyMissing = keyMissing; + sig.fingerprint = fingerprint; + sig.fingerprintPrimary = fingerprintPrimary; + sig.timestamp = Timepoint(std::chrono::seconds(timestamp)); + sig.expireTimestamp = Timepoint(std::chrono::seconds(exp_timestamp)); + sig.pubkeyAlgorithm = pubkey_algo; + sig.username = user_name; + sig.usermail = user_email; + sig.keyExpireTimestamp = Timepoint(std::chrono::seconds(key_exp_timestamp)); + sig.keyExpireTimestampPrimary = + Timepoint(std::chrono::seconds(key_exp_timestamp_primary)); + + commit.signatures.push_back(std::move(sig)); } + } - return commit; + return commit; +} + +// modified log_commit() from +// https://github.com/ostreedev/ostree/blob/main/src/ostree/ot-builtin-log.c#L40 +gboolean OSTreeRepo::parseCommitsRecursive(OstreeRepo* repo, + const gchar* checksum, + GError** error, + CommitList* commitList, + const std::string& branch, + gboolean isRecurse) { + GError* local_error{nullptr}; + + g_autoptr(GVariant) variant = nullptr; + if (!ostree_repo_load_variant(repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, + &local_error)) { + return isRecurse && g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } - // modified log_commit() from https://github.com/ostreedev/ostree/blob/main/src/ostree/ot-builtin-log.c#L40 - gboolean OSTreeRepo::parseCommitsRecursive (OstreeRepo *repo, const gchar *checksum, GError **error, - CommitList *commitList, const std::string& branch, gboolean isRecurse) { - GError *local_error{nullptr}; + commitList->insert({static_cast(checksum), + parseCommit(variant, branch, static_cast(checksum))}); - g_autoptr (GVariant) variant = nullptr; - if (!ostree_repo_load_variant(repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, &local_error)) { - return isRecurse && g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); - } + // parent recursion + g_autofree char* parent = ostree_commit_get_parent(variant); - commitList->insert({ - static_cast(checksum), - parseCommit(variant, branch, static_cast(checksum)) - }); + return !(parent && !parseCommitsRecursive(repo, parent, error, commitList, branch, true)); +} - // parent recursion - g_autofree char *parent = ostree_commit_get_parent(variant); +CommitList OSTreeRepo::parseCommitsOfBranch(const std::string& branch) { + auto ret = CommitList(); - return !(parent && !parseCommitsRecursive(repo, parent, error, commitList, branch, true)); + // open repo + GError* error = nullptr; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return ret; } - CommitList OSTreeRepo::parseCommitsOfBranch(const std::string& branch) { - auto ret = CommitList(); + // recursive commit log + g_autofree char* checksum = nullptr; + if (!ostree_repo_resolve_rev(repo, branch.c_str(), false, &checksum, &error)) { + return ret; + } - // open repo - GError *error = nullptr; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return ret; - } + parseCommitsRecursive(repo, checksum, &error, &ret, branch); - // recursive commit log - g_autofree char *checksum = nullptr; - if (!ostree_repo_resolve_rev(repo, branch.c_str(), false, &checksum, &error)) { - return ret; - } + return ret; +} - parseCommitsRecursive(repo, checksum, &error, &ret, branch); +CommitList OSTreeRepo::parseCommitsAllBranches() { + std::istringstream branches_string(getBranchesAsString()); + std::string branch; - return ret; + CommitList commits_all_branches; + + while (branches_string >> branch) { + auto commits = parseCommitsOfBranch(branch); + commits_all_branches.insert(commits.begin(), commits.end()); } - CommitList OSTreeRepo::parseCommitsAllBranches() { + return commits_all_branches; +} - std::istringstream branches_string(getBranchesAsString()); - std::string branch; +std::string OSTreeRepo::getBranchesAsString() { + std::string branches_str; - CommitList commits_all_branches; + // open repo + GError* error{nullptr}; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - while (branches_string >> branch) { - auto commits = parseCommitsOfBranch(branch); - commits_all_branches.insert(commits.begin(), commits.end()); - } - - return commits_all_branches; + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return ""; } - std::string OSTreeRepo::getBranchesAsString() { - std::string branches_str; - - // open repo - GError *error {nullptr}; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + // get a list of refs + GHashTable* refs_hash{nullptr}; + gboolean result = ostree_repo_list_refs_ext(repo, nullptr, &refs_hash, + OSTREE_REPO_LIST_REFS_EXT_NONE, nullptr, &error); + if (!result) { + g_printerr("Error listing refs: %s\n", error->message); + g_error_free(error); + g_object_unref(repo); + // TODO exit with error + return ""; + } - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return ""; - } + // iterate through the refs + GHashTableIter iter; + gpointer key{nullptr}; + gpointer value{nullptr}; + g_hash_table_iter_init(&iter, refs_hash); + while (g_hash_table_iter_next(&iter, &key, &value)) { + const gchar* ref_name = static_cast(key); + branches_str += " " + std::string(ref_name); + } - // get a list of refs - GHashTable *refs_hash {nullptr}; - gboolean result = ostree_repo_list_refs_ext(repo, nullptr, &refs_hash, OSTREE_REPO_LIST_REFS_EXT_NONE, nullptr, &error); - if (!result) { - g_printerr("Error listing refs: %s\n", error->message); - g_error_free(error); - g_object_unref(repo); - // TODO exit with error - return ""; - } + // free + g_hash_table_unref(refs_hash); - // iterate through the refs - GHashTableIter iter; - gpointer key {nullptr}; - gpointer value {nullptr}; - g_hash_table_iter_init(&iter, refs_hash); - while (g_hash_table_iter_next(&iter, &key, &value)) { - const gchar *ref_name = static_cast(key); - branches_str += " " + std::string(ref_name); - } + return branches_str; +} - // free - g_hash_table_unref(refs_hash); +/// TODO This implementation should not rely on the ostree CLI -> change to libostree usage. +bool OSTreeRepo::promoteCommit(const std::string& hash, + const std::string& newRef, + const std::vector addMetadataStrings, + const std::string& newSubject, + bool keepMetadata) { + if (hash.size() <= 0 || newRef.size() <= 0) { + return false; + } - return branches_str; + std::string command = "ostree commit"; + command += " --repo=" + repoPath; + command += " -b " + newRef; + command += (newSubject.size() <= 0 ? "" : " -s \"" + newSubject + "\""); + command += (keepMetadata ? "" : " --keep-metadata"); + for (auto str : addMetadataStrings) { + command += " --add-metadata-string=\"" + str + "\""; } + command += " --tree=ref=" + hash; - /// TODO This implementation should not rely on the ostree CLI -> change to libostree usage. - bool OSTreeRepo::promoteCommit(const std::string& hash, const std::string& newRef, - const std::vector addMetadataStrings, - const std::string& newSubject, bool keepMetadata) { - if (hash.size() <= 0 || newRef.size() <= 0) { - return false; - } - - std::string command = "ostree commit"; - command += " --repo=" + repoPath; - command += " -b " + newRef; - command += (newSubject.size() <= 0 - ? "" - : " -s \"" + newSubject + "\""); - command += (keepMetadata - ? "" - : " --keep-metadata"); - for (auto str : addMetadataStrings) { - command += " --add-metadata-string=\"" + str + "\""; - } - command += " --tree=ref=" + hash; - - // run as CLI command - std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); - - if (!pipe) { - return false; - } - return true; + // run as CLI command + std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); + + if (!pipe) { + return false; } + return true; +} -} // namespace cpplibostree +} // namespace cpplibostree diff --git a/src/util/cpplibostree.hpp b/src/util/cpplibostree.hpp index ba1b3df..1210ea4 100644 --- a/src/util/cpplibostree.hpp +++ b/src/util/cpplibostree.hpp @@ -13,177 +13,183 @@ #pragma once // C++ +#include #include #include -#include #include #include // C +#include #include #include -#include // external #include #include - namespace cpplibostree { - using Clock = std::chrono::utc_clock; - using Timepoint = std::chrono::time_point; - - struct Signature { - bool valid {false}; - bool sigExpired{true}; - bool keyExpired{true}; - bool keyRevoked{false}; - bool keyMissing{true}; - std::string fingerprint; - std::string fingerprintPrimary; - Timepoint timestamp; - Timepoint expireTimestamp; - std::string pubkeyAlgorithm; - std::string username; - std::string usermail; - Timepoint keyExpireTimestamp; - Timepoint keyExpireTimestampPrimary; - } __attribute__((aligned(128))); - - struct Commit { - std::string hash; - std::string contentChecksum; - std::string subject{"OSTree TUI Error - invalid commit state"}; - std::string body; - std::string version; - Timepoint timestamp; - std::string parent; - std::string branch; - std::vector signatures; - } __attribute__((aligned(128))); - - // map commit hash to commit - using CommitList = std::unordered_map; +using Clock = std::chrono::utc_clock; +using Timepoint = std::chrono::time_point; + +struct Signature { + bool valid{false}; + bool sigExpired{true}; + bool keyExpired{true}; + bool keyRevoked{false}; + bool keyMissing{true}; + std::string fingerprint; + std::string fingerprintPrimary; + Timepoint timestamp; + Timepoint expireTimestamp; + std::string pubkeyAlgorithm; + std::string username; + std::string usermail; + Timepoint keyExpireTimestamp; + Timepoint keyExpireTimestampPrimary; +} __attribute__((aligned(128))); + +struct Commit { + std::string hash; + std::string contentChecksum; + std::string subject{"OSTree TUI Error - invalid commit state"}; + std::string body; + std::string version; + Timepoint timestamp; + std::string parent; + std::string branch; + std::vector signatures; +} __attribute__((aligned(128))); + +// map commit hash to commit +using CommitList = std::unordered_map; + +/** + * @brief OSTreeRepo functions as a C++ wrapper around libostree's OstreeRepo. + * The complete OSTree repository gets parsed into a complete commit list in + * commitList and a list of refs in branches. + */ +class OSTreeRepo { + private: + std::string repoPath; + CommitList commitList; + std::vector branches; + + public: + /** + * @brief Construct a new OSTreeRepo. + * + * @param repoPath Path to the OSTree Repository + */ + explicit OSTreeRepo(std::string repoPath); + + /** + * @brief Return a C-style pointer to a libostree OstreeRepo. This exists, to be + * able to access functions, that have not yet been adapted in this C++ wrapper. + * + * @return OstreeRepo* + */ + OstreeRepo* _c(); + + /// Getter + const std::string& getRepoPath() const; + /// Getter + const CommitList& getCommitList() const; + /// Getter + const std::vector& getBranches() const; + + // Methods + + /** + * @brief Reload the OSTree repository data. + * + * @return true if data was changed during the reload + * @return false if nothing changed + */ + bool updateData(); + + /** + * @brief Check if a certain commit is signed. This simply accesses the + * size() of commit.signatures. + * + * @param commit + * @return true if the commit is signed + * @return false if the commit is not signed + */ + static bool isCommitSigned(const Commit& commit); + + /** + * @brief Parse commits from a ostree log output to a commitList, mapping + * the hashes to commits. + * + * @param branch + * @return std::unordered_map + */ + CommitList parseCommitsOfBranch(const std::string& branch); + + /** + * @brief Performs parseCommitsOfBranch() on all available branches and + * merges all commit lists into one. + * + * @return std::unordered_map + */ + CommitList parseCommitsAllBranches(); + + // read & write access to OSTree repo: + + /** + * @brief Promotes a commit to another branch. Similar to: + * `ostree commit --repo=repo -b newRef -s newSubject --tree=ref=hash` + * + * @param hash hash of the commit to promote + * @param newRef branch to promote to + * @param addMetadataStrings list of metadata strings to add -> KEY=VALUE + * @param newSubject new commit subject, it needed + * @param keepMetadata should new commit keep metadata of old commit + * @return true on success + * @return false on failed promotion + */ + bool promoteCommit(const std::string& hash, + const std::string& newRef, + const std::vector addMetadataStrings, + const std::string& newSubject = "", + bool keepMetadata = true); + + private: + /** + * @brief Get all branches as a single string, separated by spaces. + * + * @return std::string All branch names, separated by spaces + */ + std::string getBranchesAsString(); + + /** + * @brief Parse a libostree GVariant commit to a C++ commit struct. + * + * @param variant pointer to GVariant commit + * @param branch branch of the commit + * @param hash commit hash + * @return Commit struct + */ + Commit parseCommit(GVariant* variant, const std::string& branch, const std::string& hash); /** - * @brief OSTreeRepo functions as a C++ wrapper around libostree's OstreeRepo. - * The complete OSTree repository gets parsed into a complete commit list in - * commitList and a list of refs in branches. + * @brief Parse all commits in a OstreeRepo into a commit vector. + * + * @param repo pointer to libostree Ostree repository + * @param checksum checksum of first commit + * @param error gets set, if an error occurred during parsing + * @param commitList commit list to parse the commits into + * @param branch branch to read the commit from + * @param isRecurse !Do not use!, or set to false. Used only for recursion. + * @return true if parsing was successful + * @return false if an error occurred during parsing */ - class OSTreeRepo { - private: - std::string repoPath; - CommitList commitList; - std::vector branches; - - public: - /** - * @brief Construct a new OSTreeRepo. - * - * @param repoPath Path to the OSTree Repository - */ - explicit OSTreeRepo(std::string repoPath); - - /** - * @brief Return a C-style pointer to a libostree OstreeRepo. This exists, to be - * able to access functions, that have not yet been adapted in this C++ wrapper. - * - * @return OstreeRepo* - */ - OstreeRepo* _c(); - - /// Getter - const std::string& getRepoPath() const; - /// Getter - const CommitList& getCommitList() const; - /// Getter - const std::vector& getBranches() const; - - // Methods - - /** - * @brief Reload the OSTree repository data. - * - * @return true if data was changed during the reload - * @return false if nothing changed - */ - bool updateData(); - - /** - * @brief Check if a certain commit is signed. This simply accesses the - * size() of commit.signatures. - * - * @param commit - * @return true if the commit is signed - * @return false if the commit is not signed - */ - static bool isCommitSigned(const Commit& commit); - - /** - * @brief Parse commits from a ostree log output to a commitList, mapping - * the hashes to commits. - * - * @param branch - * @return std::unordered_map - */ - CommitList parseCommitsOfBranch(const std::string& branch); - - /** - * @brief Performs parseCommitsOfBranch() on all available branches and - * merges all commit lists into one. - * - * @return std::unordered_map - */ - CommitList parseCommitsAllBranches(); - - // read & write access to OSTree repo: - - /** - * @brief Promotes a commit to another branch. Similar to: - * `ostree commit --repo=repo -b newRef -s newSubject --tree=ref=hash` - * - * @param hash hash of the commit to promote - * @param newRef branch to promote to - * @param addMetadataStrings list of metadata strings to add -> KEY=VALUE - * @param newSubject new commit subject, it needed - * @param keepMetadata should new commit keep metadata of old commit - * @return true on success - * @return false on failed promotion - */ - bool promoteCommit(const std::string& hash, const std::string& newRef, const std::vector addMetadataStrings, const std::string& newSubject = "", bool keepMetadata = true); - - private: - /** - * @brief Get all branches as a single string, separated by spaces. - * - * @return std::string All branch names, separated by spaces - */ - std::string getBranchesAsString(); - - /** - * @brief Parse a libostree GVariant commit to a C++ commit struct. - * - * @param variant pointer to GVariant commit - * @param branch branch of the commit - * @param hash commit hash - * @return Commit struct - */ - Commit parseCommit(GVariant *variant, const std::string& branch, const std::string& hash); - - /** - * @brief Parse all commits in a OstreeRepo into a commit vector. - * - * @param repo pointer to libostree Ostree repository - * @param checksum checksum of first commit - * @param error gets set, if an error occurred during parsing - * @param commitList commit list to parse the commits into - * @param branch branch to read the commit from - * @param isRecurse !Do not use!, or set to false. Used only for recursion. - * @return true if parsing was successful - * @return false if an error occurred during parsing - */ - gboolean parseCommitsRecursive (OstreeRepo *repo, const gchar *checksum, GError **error, - CommitList *commitList, const std::string& branch, - gboolean isRecurse = false); - }; - -} // namespace cpplibostree + gboolean parseCommitsRecursive(OstreeRepo* repo, + const gchar* checksum, + GError** error, + CommitList* commitList, + const std::string& branch, + gboolean isRecurse = false); +}; + +} // namespace cpplibostree