From f35cc3d034ece05c6a4df73b6c561759d3a370a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 20 Jan 2024 14:18:27 -0300 Subject: [PATCH] More Git Plugin WIP. Added more operations. --- src/tools/ecode/plugins/git/git.cpp | 115 +++++++++++++---- src/tools/ecode/plugins/git/git.hpp | 18 +++ src/tools/ecode/plugins/git/gitplugin.cpp | 143 +++++++++++++++------- src/tools/ecode/plugins/git/gitplugin.hpp | 4 +- 4 files changed, 208 insertions(+), 72 deletions(-) diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 6f981867a..4c06534db 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -154,9 +154,40 @@ Git::CheckoutResult Git::checkout( const std::string& branch, return res; } +Git::CheckoutResult Git::checkoutAndCreateLocalBranch( const std::string& remoteBranch, + const std::string& newBranch, + const std::string& projectDir ) const { + std::string newBranchName = + newBranch.empty() ? ( remoteBranch.find_last_of( '/' ) != std::string::npos + ? remoteBranch.substr( remoteBranch.find_last_of( '/' ) + 1 ) + : remoteBranch ) + : newBranch; + Git::CheckoutResult res; + std::string buf; + int retCode = + git( String::format( "branch --no-track %s refs/remotes/%s", newBranchName, remoteBranch ), + projectDir, buf ); + if ( retCode != EXIT_SUCCESS ) { + res.returnCode = retCode; + res.result = buf; + return res; + } + + retCode = git( String::format( "branch --set-upstream-to=refs/remotes/%s %s", remoteBranch, + newBranchName ), + projectDir, buf ); + if ( retCode != EXIT_SUCCESS ) { + res.returnCode = retCode; + res.result = buf; + return res; + } + + return checkout( newBranchName, projectDir ); +} + Git::Result Git::add( const std::string& file, const std::string& projectDir ) { std::string buf; - int retCode = git( String::format( "add --force -- %s", file ), projectDir, buf ); + int retCode = git( String::format( "add --force -- \"%s\"", file ), projectDir, buf ); Git::CheckoutResult res; res.returnCode = retCode; res.result = buf; @@ -165,7 +196,7 @@ Git::Result Git::add( const std::string& file, const std::string& projectDir ) { Git::Result Git::restore( const std::string& file, const std::string& projectDir ) { std::string buf; - int retCode = git( String::format( "restore %s", file ), projectDir, buf ); + int retCode = git( String::format( "restore \"%s\"", file ), projectDir, buf ); Git::CheckoutResult res; res.returnCode = retCode; res.result = buf; @@ -174,13 +205,58 @@ Git::Result Git::restore( const std::string& file, const std::string& projectDir Git::Result Git::reset( const std::string& file, const std::string& projectDir ) { std::string buf; - int retCode = git( String::format( "reset -q HEAD -- %s", file ), projectDir, buf ); + int retCode = git( String::format( "reset -q HEAD -- \"%s\"", file ), projectDir, buf ); Git::CheckoutResult res; res.returnCode = retCode; res.result = buf; return res; } +Git::Result Git::deleteBranch( const std::string& branch, const std::string& projectDir ) { + std::string buf; + int retCode = git( String::format( "branch -D %s", branch ), projectDir, buf ); + Git::CheckoutResult res; + res.returnCode = retCode; + res.result = buf; + return res; +} + +Git::CountResult Git::branchHistoryPosition( const std::string& localBranch, + const std::string& remoteBranch, + const std::string& projectDir ) { + std::string buf; + int retCode = git( String::format( "rev-list --count %s..%s", localBranch, remoteBranch ), + projectDir, buf ); + Git::CountResult res; + res.returnCode = retCode; + if ( res.success() ) { + uint64_t count = 0; + if ( String::fromString( count, buf ) ) + res.behind = count; + } else { + res.result = buf; + return res; + } + + retCode = git( String::format( "rev-list --count %s..%s", remoteBranch, localBranch ), + projectDir, buf ); + res.returnCode = retCode; + + if ( res.success() ) { + uint64_t count = 0; + if ( String::fromString( count, buf ) ) + res.ahead = count; + } else { + res.result = buf; + } + + return res; +} + +Git::CountResult Git::branchHistoryPosition( const Branch& branch, const std::string& projectDir ) { + return branchHistoryPosition( branch.name, branch.remote, projectDir ); +} + Git::CheckoutResult Git::checkoutNewBranch( const std::string& newBranch, const std::string& fromBranch, const std::string& projectDir ) { @@ -305,20 +381,18 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) LuaPattern subModulePattern( "^Entering '(.*)'" ); auto parseNumStat = [&s, &buf, &projectDir, this, &subModulePattern]() { - auto lastNL = 0; - auto nextNL = buf.find_first_of( '\n' ); LuaPattern pattern( "(%d+)%s+(%d+)%s+(.+)" ); std::string subModulePath = ""; - while ( nextNL != std::string_view::npos ) { + readAllLines( buf, [&]( const std::string_view& line ) { LuaPattern::Range matches[4]; - if ( subModulePattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { + if ( subModulePattern.matches( line.data(), 0, matches, line.size() ) ) { subModulePath = String::trim( - buf.substr( matches[1].start, matches[1].end - matches[1].start ) ); + line.substr( matches[1].start, matches[1].end - matches[1].start ) ); FileSystem::dirAddSlashAtEnd( subModulePath ); - } else if ( pattern.matches( buf.c_str(), lastNL, matches, nextNL ) ) { - auto inserted = buf.substr( matches[1].start, matches[1].end - matches[1].start ); - auto deleted = buf.substr( matches[2].start, matches[2].end - matches[2].start ); - auto file = buf.substr( matches[3].start, matches[3].end - matches[3].start ); + } else if ( pattern.matches( line.data(), 0, matches, line.size() ) ) { + auto inserted = line.substr( matches[1].start, matches[1].end - matches[1].start ); + auto deleted = line.substr( matches[2].start, matches[2].end - matches[2].start ); + auto file = line.substr( matches[3].start, matches[3].end - matches[3].start ); int inserts; int deletes; if ( String::fromString( inserts, inserted ) && @@ -351,9 +425,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) s.totalDeletions += deletes; } } - lastNL = nextNL + 1; - nextNL = buf.find_first_of( '\n', nextNL + 1 ); - } + } ); }; parseNumStat(); @@ -373,12 +445,9 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) bool modifiedSubmodule = false; auto parseStatus = [&s, &buf, &modifiedSubmodule, &projectDir, this, &subModulePattern]() { - auto lastNL = 0; - auto nextNL = buf.find_first_of( '\n' ); - LuaPattern pattern( "^([mMARTUD?%s][mMARTUD?%s])%s(.*)" ); std::string subModulePath = ""; - while ( nextNL != std::string_view::npos ) { - std::string_view line = std::string_view{ buf }.substr( lastNL, nextNL - lastNL ); + LuaPattern pattern( "^([mMARTUD?%s][mMARTUD?%s])%s(.*)" ); + readAllLines( buf, [&]( const std::string_view& line ) { LuaPattern::Range matches[3]; if ( subModulePattern.matches( line.data(), 0, matches, line.size() ) ) { subModulePath = String::trim( @@ -388,7 +457,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) auto statusStr = line.substr( matches[1].start, matches[1].end - matches[1].start ); auto file = line.substr( matches[2].start, matches[2].end - matches[2].start ); if ( statusStr.size() < 2 ) - continue; + return; Uint16 status = xy( statusStr[0], statusStr[1] ); GitStatus gitStatus = GitStatus::NotSet; @@ -557,9 +626,7 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir ) } } } - lastNL = nextNL + 1; - nextNL = buf.find_first_of( '\n', nextNL + 1 ); - } + } ); }; git( STATUS_CMD, projectDir, buf ); diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 7ba6733d7..e4b235693 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -147,6 +147,11 @@ class Git { std::string branch; }; + struct CountResult : public Result { + int64_t behind{ 0 }; + int64_t ahead{ 0 }; + }; + enum RefType { Head = 0x1, Remote = 0x2, @@ -205,6 +210,15 @@ class Git { Result reset( const std::string& file, const std::string& projectDir = "" ); + Result deleteBranch( const std::string& branch, const std::string& projectDir = "" ); + + CountResult branchHistoryPosition( const std::string& localBranch, + const std::string& remoteBranch, + const std::string& projectDir = "" ); + + CountResult branchHistoryPosition( const Git::Branch& branch, + const std::string& projectDir = "" ); + bool setProjectPath( const std::string& projectPath ); const std::string& getGitPath() const; @@ -219,6 +233,10 @@ class Git { CheckoutResult checkout( const std::string& branch, const std::string& projectDir = "" ) const; + CheckoutResult checkoutAndCreateLocalBranch( const std::string& remoteBranch, + const std::string& newBranch = "", + const std::string& projectDir = "" ) const; + CheckoutResult checkoutNewBranch( const std::string& newBranch, const std::string& fromBranch = "", const std::string& projectDir = "" ); diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 4c7faf973..e149f63c1 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -29,8 +29,8 @@ static const char* GIT_SUCCESS = "success"; static const char* GIT_ERROR = "error"; static const char* GIT_BOLD = "bold"; static const char* GIT_NOT_BOLD = "notbold"; -static const std::string GIT_TAG = "tag"; -static const std::string GIT_REPO = "repo"; +static const char* GIT_TAG = "tag"; +static const char* GIT_REPO = "repo"; std::string GitPlugin::statusTypeToString( Git::GitStatusType type ) { switch ( type ) { @@ -140,7 +140,8 @@ class GitBranchModel : public Model { case ModelRole::Display: { if ( index.internalId() == -1 ) { if ( index.column() == Column::Name ) - return Variant( mBranches[index.row()].branch.c_str() ); + return Variant( String::format( "%s (%zu)", mBranches[index.row()].branch, + mBranches[index.row()].data.size() ) ); return Variant( GIT_EMPTY ); } const Git::Branch& branch = mBranches[index.internalId()].data[index.row()]; @@ -843,22 +844,58 @@ void GitPlugin::blame( UICodeEditor* editor ) { } ); } -void GitPlugin::checkout( const std::string& branch ) { +void GitPlugin::checkout( Git::Branch branch ) { + if ( !mGit ) + return; + + const auto checkOutFn = [this, branch]( bool createLocal ) { + mLoader->setVisible( true ); + mThreadPool->run( [this, branch, createLocal] { + auto result = createLocal ? mGit->checkoutAndCreateLocalBranch( branch.name ) + : mGit->checkout( branch.name ); + if ( result.success() ) { + { + Lock l( mGitBranchMutex ); + mGitBranch = branch.name; + } + if ( mBranchesTree->getModel() ) { + if ( createLocal ) + updateBranches(); + else + mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); + } + } else { + showMessage( LSPMessageType::Warning, result.result ); + } + getUISceneNode()->runOnMainThread( [this] { mLoader->setVisible( false ); } ); + } ); + }; + + if ( branch.type == Git::RefType::Remote ) { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::YES_NO, i18n( "git_create_local_branch", "Create local branch?" ) ); + msgBox->on( Event::OnConfirm, [checkOutFn]( const Event* ) { checkOutFn( true ); } ); + msgBox->on( Event::OnCancel, [checkOutFn]( const Event* ) { checkOutFn( false ); } ); + msgBox->setTitle( i18n( "git_checkout", "Check Out" ) ); + msgBox->center(); + msgBox->showWhenReady(); + return; + } + + checkOutFn( false ); +} + +void GitPlugin::branchDelete( Git::Branch branch ) { if ( !mGit ) return; mLoader->setVisible( true ); mThreadPool->run( [this, branch] { - auto result = mGit->checkout( branch ); - if ( result.success() ) { - { - Lock l( mGitBranchMutex ); - mGitBranch = branch; - } - if ( mBranchesTree->getModel() ) - mBranchesTree->getModel()->invalidate( Model::DontInvalidateIndexes ); - } else { - showMessage( LSPMessageType::Warning, result.result ); + auto res = mGit->deleteBranch( branch.name ); + if ( res.fail() ) { + showMessage( LSPMessageType::Warning, res.result ); + return; } + updateBranches(); getUISceneNode()->runOnMainThread( [this] { mLoader->setVisible( false ); } ); } ); } @@ -904,14 +941,27 @@ void GitPlugin::unstage( const std::string& file ) { void GitPlugin::discard( const std::string& file ) { if ( !mGit ) return; - mLoader->setVisible( true ); - mThreadPool->run( [this, file] { - auto res = mGit->restore( file ); - if ( res.fail() ) - showMessage( LSPMessageType::Warning, res.result ); - getUISceneNode()->runOnMainThread( [this] { mLoader->setVisible( false ); } ); - updateStatus( true ); + + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::OK_CANCEL, + String::format( i18n( "git_confirm_discard_changes", + "Are you sure you want to discard the changes in file: \"%s\"?" ) + .toUtf8(), + file ) ); + + msgBox->on( Event::OnConfirm, [this, file]( auto ) { + mLoader->setVisible( true ); + mThreadPool->run( [this, file] { + auto res = mGit->restore( file ); + if ( res.fail() ) + showMessage( LSPMessageType::Warning, res.result ); + getUISceneNode()->runOnMainThread( [this] { mLoader->setVisible( false ); } ); + updateStatus( true ); + } ); } ); + msgBox->setTitle( i18n( "git_confirm", "Confirm" ) ); + msgBox->center(); + msgBox->showWhenReady(); } void GitPlugin::openFile( const std::string& file ) { @@ -940,7 +990,6 @@ void GitPlugin::onRegister( UICodeEditor* editor ) { mTab->setTabSelected(); } ); doc.setCommand( "git-pull", [this]() { pull(); } ); - doc.setCommand( "git-checkout", [this]() { checkout( mGitBranch ); } ); } void GitPlugin::onUnregister( UICodeEditor* editor ) { @@ -1082,7 +1131,7 @@ void GitPlugin::buildSidePanelTab() { switch ( modelEvent->getModelEventType() ) { case EE::UI::Abstract::ModelEventType::Open: { - checkout( branch->name ); + checkout( *branch ); break; } case EE::UI::Abstract::ModelEventType::OpenMenu: { @@ -1146,23 +1195,25 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) { if ( mGitBranch != branch.name ) { addFn( "git-checkout", "Check Out..." ); + if ( branch.type == Git::RefType::Head ) { + addFn( "git-branch-delete", "Delete" ); + } } else { addFn( "git-pull", "Pull", "repo-pull" ); } - std::string name( branch.name ); - menu->on( Event::OnItemClicked, [this, name]( const Event* event ) { + menu->on( Event::OnItemClicked, [this, branch]( const Event* event ) { if ( !mGit ) return; UIMenuItem* item = event->getNode()->asType(); std::string id( item->getId() ); - mThreadPool->run( [this, id, name]() { - if ( id == "git-checkout" ) { - checkout( name ); - } else if ( id == "git-pull" ) { - pull(); - } - } ); + if ( id == "git-checkout" ) { + checkout( branch ); + } else if ( id == "git-pull" ) { + pull(); + } else if ( id == "git-branch-delete" ) { + branchDelete( branch ); + } } ); menu->showOverMouseCursor(); @@ -1185,28 +1236,26 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) { addFn( "git-stage", "Stage" ); } else { addFn( "git-unstage", "Unstage" ); - - menu->addSeparator(); - - addFn( "git-discard", "Discard" ); } + menu->addSeparator(); + + addFn( "git-discard", "Discard" ); + menu->on( Event::OnItemClicked, [this, file]( const Event* event ) { if ( !mGit ) return; UIMenuItem* item = event->getNode()->asType(); std::string id( item->getId() ); - mThreadPool->run( [this, id, file]() { - if ( id == "git-stage" ) { - stage( file.file ); - } else if ( id == "git-unstage" ) { - unstage( file.file ); - } else if ( id == "git-discard" ) { - discard( file.file ); - } else if ( id == "git-open-file" ) { - openFile( file.file ); - } - } ); + if ( id == "git-stage" ) { + stage( file.file ); + } else if ( id == "git-unstage" ) { + unstage( file.file ); + } else if ( id == "git-discard" ) { + discard( file.file ); + } else if ( id == "git-open-file" ) { + openFile( file.file ); + } } ); menu->showOverMouseCursor(); diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index 00c55a747..40ca32f9e 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -120,7 +120,9 @@ class GitPlugin : public PluginBase { void blame( UICodeEditor* editor ); - void checkout( const std::string& branch ); + void checkout( Git::Branch branch ); + + void branchDelete( Git::Branch branch ); void pull();