Git plugin: Added stash management.

UITreeView now selects items with right mouse click too.
This commit is contained in:
Martín Lucas Golini
2024-01-28 21:24:53 -03:00
parent eb825f668e
commit d4e859be4b
12 changed files with 409 additions and 102 deletions
+2 -1
View File
@@ -468,7 +468,8 @@ UITableRow* UIAbstractTableView::createRow() {
rowWidget->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
rowWidget->reloadStyle( true, true, true );
rowWidget->on( Event::MouseDown, [this]( const Event* event ) {
if ( !( event->asMouseEvent()->getFlags() & EE_BUTTON_LMASK ) || !isRowSelection() )
if ( !( event->asMouseEvent()->getFlags() & ( EE_BUTTON_LMASK | EE_BUTTON_RMASK ) ) ||
!isRowSelection() )
return;
auto index = event->getNode()->asType<UITableRow>()->getCurIndex();
if ( getUISceneNode()->getWindow()->getInput()->isControlPressed() ) {
+18
View File
@@ -422,6 +422,24 @@ bool UITreeView::isExpanded( const ModelIndex& index ) const {
return getIndexMetadata( index ).open;
}
void UITreeView::setExpanded( const std::vector<ModelIndex>& indexes, bool expanded ) {
if ( !getModel() )
return;
Model& model = *getModel();
for ( const auto& index : indexes ) {
if ( !index.isValid() )
continue;
size_t count = model.rowCount( index );
if ( count )
getIndexMetadata( index ).open = expanded;
}
createOrUpdateColumns( false );
}
void UITreeView::setExpanded( const ModelIndex& index, bool expanded ) {
setExpanded( std::vector{ index }, expanded );
}
void UITreeView::setAllExpanded( const ModelIndex& index, bool expanded ) {
Model& model = *getModel();
size_t count = model.rowCount( index );
-4
View File
@@ -3038,10 +3038,6 @@ void App::initProjectTreeView( std::string path, bool openClean ) {
tab->getTabWidget()->setTabSelected( tab );
}
} else { // ModelEventType::OpenMenu
bool focusOnSelection = mProjectTreeView->getFocusOnSelection();
mProjectTreeView->setFocusOnSelection( false );
mProjectTreeView->getSelection().set( modelEvent->getModelIndex() );
mProjectTreeView->setFocusOnSelection( focusOnSelection );
mSettings->createProjectTreeMenu( FileInfo( path ) );
}
}
+4
View File
@@ -210,6 +210,10 @@ void IconManager::init( UISceneNode* sceneNode, FontTrueType* iconFont, FontTrue
{ "repo-fetch", 0xec1d },
{ "git-fetch", 0xf101 },
{ "git-commit", 0xeafc },
{ "git-stash", 0xec26 },
{ "git-stash-apply", 0xec27 },
{ "git-stash-pop", 0xec28 },
{ "diff-single", 0xec22 },
{ "remove", 0xeb3b },
{ "tag", 0xea66 } };
+58 -15
View File
@@ -214,6 +214,11 @@ Git::Result Git::add( std::vector<std::string> files, const std::string& project
return gitSimple( String::format( "add --force -- %s", asList( files ) ), projectDir );
}
Git::Result Git::stash( std::vector<std::string> files, const std::string& projectDir ) {
return gitSimple( String::format( "stash push --include-untracked -- %s", asList( files ) ),
projectDir );
}
Git::Result Git::restore( const std::string& file, const std::string& projectDir ) {
return gitSimple( String::format( "restore \"%s\"", file ), projectDir );
}
@@ -420,23 +425,39 @@ std::vector<Git::Branch> Git::getAllBranchesAndTags( RefType ref, std::string_vi
std::vector<Branch> branches;
std::string buf;
if ( EXIT_SUCCESS != git( args, projectDir, buf ) )
return branches;
if ( EXIT_SUCCESS == git( args, projectDir, buf ) ) {
branches.reserve( countLines( buf ) );
getSubModules( projectDir );
readAllLines( buf, [&]( const std::string_view& line ) {
auto branch = String::trim( String::trim( line, '\'' ), '\t' );
if ( ( ref & Head ) && String::startsWith( branch, "refs/heads/" ) ) {
branches.emplace_back( parseLocalBranch( branch ) );
} else if ( ( ref & Remote ) && String::startsWith( branch, "refs/remotes/" ) ) {
branches.emplace_back( parseRemoteBranch( branch ) );
} else if ( ( ref & Tag ) && String::startsWith( branch, "refs/tags/" ) ) {
branches.emplace_back( parseTag( branch ) );
}
} );
}
branches.reserve( countLines( buf ) );
readAllLines( buf, [&]( const std::string_view& line ) {
auto branch = String::trim( String::trim( line, '\'' ), '\t' );
if ( ( ref & Head ) && String::startsWith( branch, "refs/heads/" ) ) {
branches.emplace_back( parseLocalBranch( branch ) );
} else if ( ( ref & Remote ) && String::startsWith( branch, "refs/remotes/" ) ) {
branches.emplace_back( parseRemoteBranch( branch ) );
} else if ( ( ref & Tag ) && String::startsWith( branch, "refs/tags/" ) ) {
branches.emplace_back( parseTag( branch ) );
}
} );
if ( ( ref & RefType::Stash ) && EXIT_SUCCESS == git( "stash list", projectDir, buf ) ) {
branches.reserve( branches.size() + countLines( buf ) );
LuaPattern pattern( "(stash@{%d+}):%s(.*)" );
readAllLines( buf, [&]( const std::string_view& line ) {
LuaPattern::Range matches[3];
if ( pattern.matches( line.data(), 0, matches, line.size() ) ) {
std::string id(
line.substr( matches[1].start, matches[1].end - matches[1].start ) );
std::string name(
line.substr( matches[2].start, matches[2].end - matches[2].start ) );
Git::Branch newBranch;
newBranch.type = RefType::Stash;
newBranch.name = std::move( name );
newBranch.remote = std::move( id );
branches.emplace_back( std::move( newBranch ) );
}
} );
}
return branches;
}
@@ -857,4 +878,26 @@ Git::GitStatusReport Git::statusFromShortStatusStr( const std::string_view& stat
return { gitStatus, gitStatusType, gitStatusChar };
}
Git::Result Git::stashPush( std::vector<std::string> files, const std::string& name, bool keepIndex,
const std::string& projectDir ) {
std::string args;
if ( keepIndex )
args += " --keep-index";
if ( !name.empty() )
args += " --message " + name;
return gitSimple(
String::format( "stash push --include-untracked %s -- %s", args, asList( files ) ),
projectDir );
}
Git::Result Git::stashApply( const std::string& stashId, bool restoreIndex,
const std::string& projectDir ) {
return gitSimple( String::format( "stash apply%s %s", restoreIndex ? " --index" : "", stashId ),
projectDir );
}
Git::Result Git::stashDrop( const std::string& stashId, const std::string& projectDir ) {
return gitSimple( String::format( "stash drop %s", stashId ), projectDir );
}
} // namespace ecode
+22 -1
View File
@@ -83,6 +83,13 @@ class Git {
Ignored = 1 << 5,
};
static constexpr auto STATUS_TYPE_ALL =
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Staged ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Changed ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Untracked ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Unmerged ) |
static_cast<std::underlying_type_t<GitStatusType>>( GitStatusType::Ignored );
static constexpr auto ANY = 0;
enum StatusXY : uint16_t {
@@ -179,12 +186,14 @@ class Git {
Head = 0x1,
Remote = 0x2,
Tag = 0x4,
All = 0x7,
Stash = 0x8,
All = 0xF,
};
static constexpr const char* HEAD = "head";
static constexpr const char* REMOTE = "remote";
static constexpr const char* TAG = "tag";
static constexpr const char* STASH = "stash";
static constexpr const char* ALL = "all";
static const char* refTypeToString( RefType type ) {
@@ -195,6 +204,8 @@ class Git {
return REMOTE;
case Tag:
return TAG;
case Stash:
return STASH;
case All:
return ALL;
}
@@ -234,6 +245,8 @@ class Git {
Result add( std::vector<std::string> files, const std::string& projectDir = "" );
Result stash( std::vector<std::string> files, const std::string& projectDir = "" );
Result restore( const std::string& file, const std::string& projectDir = "" );
Result reset( std::vector<std::string> files, const std::string& projectDir = "" );
@@ -320,6 +333,14 @@ class Git {
LogLevel getLogLevel() const { return mLogLevel; }
Result stashPush( std::vector<std::string> files, const std::string& name, bool keepIndex,
const std::string& projectDir = "" );
Result stashApply( const std::string& stashId, bool restoreIndex,
const std::string& projectDir = "" );
Result stashDrop( const std::string& stashId, const std::string& projectDir = "" );
protected:
std::string mGitPath;
std::string mProjectPath;
+16 -2
View File
@@ -12,7 +12,7 @@ size_t GitBranchModel::hashBranches( const std::vector<Git::Branch>& branches )
return hash;
}
std::string GitBranchModel::refTypeToString( Git::RefType type ) {
std::string GitBranchModel::refTypeToString( Git::RefType type ) const {
switch ( type ) {
case Git::RefType::Head:
return mPlugin->i18n( "git_local_branches", "Local Branches" ).toUtf8();
@@ -20,6 +20,8 @@ std::string GitBranchModel::refTypeToString( Git::RefType type ) {
return mPlugin->i18n( "git_remote_branches", "Remote Branches" ).toUtf8();
case Git::RefType::Tag:
return mPlugin->i18n( "git_tags", "Tags" ).toUtf8();
case Git::RefType::Stash:
return mPlugin->i18n( "git_stashes", "Stashes" ).toUtf8();
default:
break;
}
@@ -68,7 +70,10 @@ UIIcon* GitBranchModel::iconFor( const ModelIndex& index ) const {
if ( index.column() == (Int64)treeColumn() ) {
if ( index.hasParent() ) {
Git::Branch* branch = static_cast<Git::Branch*>( index.internalData() );
return mPlugin->findIcon( branch->type == Git::RefType::Tag ? GIT_TAG : GIT_REPO );
return mPlugin->findIcon(
branch->type == Git::RefType::Tag
? GIT_TAG
: ( branch->type == Git::RefType::Stash ? GIT_STASH : GIT_REPO ) );
}
}
return nullptr;
@@ -143,4 +148,13 @@ Git::Branch GitBranchModel::branch( const std::string& name ) const {
return {};
}
ModelIndex GitBranchModel::refTypeIndex( Git::RefType refType ) const {
auto name = refTypeToString( refType );
for ( size_t i = 0; i < mBranches.size(); i++ ) {
if ( mBranches[i].branch == name )
return index( i, 0, {} );
}
return {};
}
} // namespace ecode
@@ -31,7 +31,7 @@ class GitBranchModel : public Model {
std::vector<Git::Branch> data;
};
std::string refTypeToString( Git::RefType type );
std::string refTypeToString( Git::RefType type ) const;
GitBranchModel( std::vector<Git::Branch>&& branches, size_t hash, GitPlugin* gitPlugin );
@@ -57,6 +57,7 @@ class GitBranchModel : public Model {
Git::Branch branch( const std::string& name ) const;
ModelIndex refTypeIndex( Git::RefType refType ) const;
protected:
std::vector<BranchData> mBranches;
GitPlugin* mPlugin{ nullptr };
+258 -73
View File
@@ -11,6 +11,7 @@
#include <eepp/ui/uiiconthememanager.hpp>
#include <eepp/ui/uiloader.hpp>
#include <eepp/ui/uipopupmenu.hpp>
#include <eepp/ui/uiradiobutton.hpp>
#include <eepp/ui/uistackwidget.hpp>
#include <eepp/ui/uistyle.hpp>
#include <eepp/ui/uitextedit.hpp>
@@ -605,18 +606,18 @@ void GitPlugin::branchDelete( Git::Branch branch ) {
msgBox->showWhenReady();
}
void GitPlugin::pull() {
runAsync( [this]() { return mGit->pull( repoSelected() ); }, true, true, true );
void GitPlugin::pull( const std::string& repoPath ) {
runAsync( [this, repoPath]() { return mGit->pull( repoPath ); }, true, true, true );
}
void GitPlugin::push() {
void GitPlugin::push( const std::string& repoPath ) {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
i18n( "git_confirm_push_changes",
"Are you sure you want to push the local changes to the remote server?" ) );
msgBox->on( Event::OnConfirm, [this]( auto ) {
runAsync( [this]() { return mGit->push( repoSelected() ); }, true, true, true, true );
msgBox->on( Event::OnConfirm, [this, repoPath]( auto ) {
runAsync( [this, repoPath]() { return mGit->push( repoPath ); }, true, true, true, true );
} );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->setTitle( i18n( "git_confirm", "Confirm" ) );
@@ -624,8 +625,8 @@ void GitPlugin::push() {
msgBox->showWhenReady();
}
void GitPlugin::fetch() {
runAsync( [this]() { return mGit->fetch( repoSelected() ); }, true, true, true );
void GitPlugin::fetch( const std::string& repoPath ) {
runAsync( [this, repoPath]() { return mGit->fetch( repoPath ); }, true, true, true );
}
void GitPlugin::branchCreate() {
@@ -683,6 +684,16 @@ void GitPlugin::commit( const std::string& repoPath ) {
chkBypassHook->setText( i18n( "git_bypass_hook", "Bypass commit hook" ) );
chkBypassHook->toPosition( 3 );
UICheckBox* chkPush = UICheckBox::New();
chkPush->setLayoutMargin( Rectf( 0, 8, 0, 0 ) )
->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent )
->setLayoutGravity( UI_HALIGN_LEFT | UI_VALIGN_CENTER )
->setClipType( ClipType::None )
->setParent( msgBox->getLayoutCont()->getFirstChild() )
->setId( "git-push-commit" );
chkPush->setText( i18n( "git_push_commit", "Push commit" ) );
chkPush->toPosition( 4 );
if ( !repoPath.empty() ) {
auto branchName = mGitBranches[repoPath];
if ( !branchName.empty() ) {
@@ -700,24 +711,28 @@ void GitPlugin::commit( const std::string& repoPath ) {
}
}
msgBox->on( Event::OnConfirm, [this, msgBox, chkAmmend, chkBypassHook]( const Event* ) {
std::string msg( msgBox->getTextEdit()->getText().toUtf8() );
if ( msg.empty() )
return;
bool ammend = chkAmmend->isChecked();
bool bypassHook = chkBypassHook->isChecked();
msgBox->closeWindow();
runAsync(
[this, msg, ammend, bypassHook, msgBox]() {
auto res = mGit->commit( msg, ammend, bypassHook );
if ( res.success() )
mLastCommitMsg.clear();
else
mLastCommitMsg = msgBox->getTextEdit()->getText();
return res;
},
true, true );
} );
msgBox->on( Event::OnConfirm,
[this, msgBox, chkAmmend, chkBypassHook, chkPush, repoPath]( const Event* ) {
std::string msg( msgBox->getTextEdit()->getText().toUtf8() );
if ( msg.empty() )
return;
bool ammend = chkAmmend->isChecked();
bool bypassHook = chkBypassHook->isChecked();
bool pushCommit = chkPush->isChecked();
msgBox->closeWindow();
runAsync(
[this, msg, ammend, bypassHook, msgBox, pushCommit, repoPath]() {
auto res = mGit->commit( msg, ammend, bypassHook, repoPath );
if ( res.success() ) {
mLastCommitMsg.clear();
if ( pushCommit )
return mGit->push( repoPath );
} else
mLastCommitMsg = msgBox->getTextEdit()->getText();
return res;
},
true, true, true, true );
} );
msgBox->on( Event::OnCancel, [this, msgBox]( const Event* ) {
mLastCommitMsg = msgBox->getTextEdit()->getText();
@@ -840,6 +855,104 @@ void GitPlugin::diff( const std::string& file, bool isStaged ) {
// File operations
// Stash operations
void GitPlugin::stashPush( const std::vector<std::string>& files, const std::string& repoPath ) {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
i18n( "git_stash_push", "Stash Local Changes\nName your stash (optional):" ) );
UIRadioButton* rKeepIndex = UIRadioButton::New();
rKeepIndex->setLayoutMargin( Rectf( 0, 8, 0, 0 ) )
->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent )
->setLayoutGravity( UI_HALIGN_LEFT | UI_VALIGN_CENTER )
->setClipType( ClipType::None )
->setParent( msgBox->getLayoutCont()->getFirstChild() )
->setId( "git-stash-keep-index" );
rKeepIndex->setText( i18n( "git_stash_keep_index", "Keep Index" ) );
rKeepIndex->toPosition( 2 );
UIRadioButton* rKeepWorkingTree = UIRadioButton::New();
rKeepWorkingTree->setLayoutMargin( Rectf( 0, 8, 0, 0 ) )
->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent )
->setLayoutGravity( UI_HALIGN_LEFT | UI_VALIGN_CENTER )
->setClipType( ClipType::None )
->setParent( msgBox->getLayoutCont()->getFirstChild() )
->setId( "git-stash-keep-working-tree" );
rKeepWorkingTree->setText( i18n( "git_stash_keep_working_tree", "Keep Working Tree" ) );
rKeepWorkingTree->toPosition( 3 );
msgBox->on( Event::OnConfirm,
[this, msgBox, rKeepIndex, rKeepWorkingTree, files, repoPath]( const Event* ) {
bool keepIndex = rKeepIndex->isActive();
bool keepWorkingTree = rKeepWorkingTree->isActive();
std::string message = msgBox->getTextInput()->getText().toUtf8();
String::trimInPlace( message );
String::trimInPlace( message, '\n' );
msgBox->closeWindow();
runAsync(
[this, files, keepIndex, keepWorkingTree, repoPath, message]() {
auto res = mGit->stashPush( files, message, keepIndex, repoPath );
if ( res.success() && keepWorkingTree )
mGit->stashApply( "stash@{0}", true, repoPath );
return res;
},
true, true );
} );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->setTitle( i18n( "git_stash_save", "Save Stash" ) );
msgBox->center();
msgBox->showWhenReady();
}
void GitPlugin::stashApply( const Git::Branch& branch ) {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
i18n( "git_confirm_apply_stash", "Apply a previously saved stash?" ).toUtf8() );
UICheckBox* chkIndex = UICheckBox::New();
chkIndex->setLayoutMargin( Rectf( 0, 8, 0, 0 ) )
->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent )
->setLayoutGravity( UI_HALIGN_LEFT | UI_VALIGN_CENTER )
->setClipType( ClipType::None )
->setParent( msgBox->getLayoutCont()->getFirstChild() )
->setId( "git-restore-index" );
chkIndex->setText( i18n( "git_restore_index", "Restore Index" ) );
chkIndex->toPosition( 2 );
chkIndex->setChecked( true );
msgBox->on( Event::OnConfirm, [this, branch, chkIndex]( auto ) {
runAsync(
[this, branch, chkIndex]() {
return mGit->stashApply( branch.remote, chkIndex->isChecked(), repoSelected() );
},
true, true );
} );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->setTitle( i18n( "git_apply_stash_title", "Apply Stash" ) );
msgBox->center();
msgBox->showWhenReady();
}
void GitPlugin::stashDrop( const Git::Branch& branch ) {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
i18n( "git_confirm_drop_stash", "Do you want to drop the selected stash?" ).toUtf8() );
msgBox->on( Event::OnConfirm, [this, branch]( auto ) {
runAsync( [this, branch]() { return mGit->stashDrop( branch.remote, repoSelected() ); },
true, true );
} );
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
msgBox->setTitle( i18n( "git_drop_stash_title", "Drop Stash" ) );
msgBox->center();
msgBox->showWhenReady();
}
// Stash operations
void GitPlugin::onRegister( UICodeEditor* editor ) {
PluginBase::onRegister( editor );
@@ -859,10 +972,10 @@ void GitPlugin::onRegister( UICodeEditor* editor ) {
if ( mTab )
mTab->setTabSelected();
} );
doc.setCommand( "git-pull", [this] { pull(); } );
doc.setCommand( "git-push", [this] { push(); } );
doc.setCommand( "git-fetch", [this] { fetch(); } );
doc.setCommand( "git-commit", [this] { commit(); } );
doc.setCommand( "git-pull", [this] { pull( projectPath() ); } );
doc.setCommand( "git-push", [this] { push( projectPath() ); } );
doc.setCommand( "git-fetch", [this] { fetch( projectPath() ); } );
doc.setCommand( "git-commit", [this] { commit( projectPath() ); } );
}
void GitPlugin::onUnregister( UICodeEditor* editor ) {
@@ -1083,8 +1196,8 @@ void GitPlugin::buildSidePanelTab() {
node->bind( "git_panel_loader", mLoader );
node->bind( "git_repo", mRepoDropDown );
node->find( "branch_pull" )->onClick( [this]( auto ) { pull(); } );
node->find( "branch_push" )->onClick( [this]( auto ) { push(); } );
node->find( "branch_pull" )->onClick( [this]( auto ) { pull( repoSelected() ); } );
node->find( "branch_push" )->onClick( [this]( auto ) { push( repoSelected() ); } );
node->find( "branch_add" )->onClick( [this]( auto ) { branchCreate(); } );
mBranchesTree->setAutoExpandOnSingleColumn( true );
@@ -1101,15 +1214,14 @@ void GitPlugin::buildSidePanelTab() {
static_cast<Git::Branch*>( modelEvent->getModelIndex().internalData() );
switch ( modelEvent->getModelEventType() ) {
case Abstract::ModelEventType::Open: {
checkout( *branch );
case ModelEventType::Open: {
if ( branch->type != Git::RefType::Stash )
checkout( *branch );
else
stashApply( *branch );
break;
}
case Abstract::ModelEventType::OpenMenu: {
bool focusOnSelection = mBranchesTree->getFocusOnSelection();
mBranchesTree->setFocusOnSelection( false );
mBranchesTree->getSelection().set( modelEvent->getModelIndex() );
mBranchesTree->setFocusOnSelection( focusOnSelection );
case ModelEventType::OpenMenu: {
openBranchMenu( *branch );
break;
}
@@ -1165,21 +1277,18 @@ void GitPlugin::buildSidePanelTab() {
return;
auto model = static_cast<const GitStatusModel*>( modelEvent->getModel() );
if ( modelEvent->getModelIndex().internalId() == GitStatusModel::GitFile ) {
const Git::DiffFile* file = model->file( modelEvent->getModelIndex() );
if ( file == nullptr )
return;
switch ( modelEvent->getModelEventType() ) {
case Abstract::ModelEventType::OpenMenu: {
bool focusOnSelection = mStatusTree->getFocusOnSelection();
mStatusTree->setFocusOnSelection( false );
mStatusTree->getSelection().set( modelEvent->getModelIndex() );
mStatusTree->setFocusOnSelection( focusOnSelection );
case ModelEventType::OpenMenu: {
openFileStatusMenu( *file );
break;
}
case Abstract::ModelEventType::Open: {
case ModelEventType::Open: {
diff( file->file, file->report.type == Git::GitStatusType::Staged );
break;
}
@@ -1188,12 +1297,7 @@ void GitPlugin::buildSidePanelTab() {
}
} else if ( modelEvent->getModelIndex().internalId() == GitStatusModel::Status ) {
switch ( modelEvent->getModelEventType() ) {
case Abstract::ModelEventType::OpenMenu: {
bool focusOnSelection = mStatusTree->getFocusOnSelection();
mStatusTree->setFocusOnSelection( false );
mStatusTree->getSelection().set( modelEvent->getModelIndex() );
mStatusTree->setFocusOnSelection( focusOnSelection );
case ModelEventType::OpenMenu: {
const auto* status = model->statusType( modelEvent->getModelIndex() );
if ( status->type == Git::GitStatusType::Staged ||
status->type == Git::GitStatusType::Untracked ||
@@ -1224,11 +1328,11 @@ void GitPlugin::buildSidePanelTab() {
if ( id == "git-commit" ) {
commit( repoPath );
} else if ( id == "git-stage-all" ) {
stage( model->getFiles( mGit->repoName( "" ),
stage( model->getFiles( repoName( repoPath ),
(Uint32)Git::GitStatusType::Untracked |
(Uint32)Git::GitStatusType::Changed ) );
} else if ( id == "git-unstage-all" ) {
unstage( model->getFiles( mGit->repoName( "" ),
unstage( model->getFiles( repoName( repoPath ),
(Uint32)Git::GitStatusType::Staged ) );
}
} );
@@ -1241,6 +1345,65 @@ void GitPlugin::buildSidePanelTab() {
default:
break;
}
} else if ( modelEvent->getModelIndex().internalId() == GitStatusModel::Repo ) {
switch ( modelEvent->getModelEventType() ) {
case ModelEventType::OpenMenu: {
const auto* repo = model->repo( modelEvent->getModelIndex() );
if ( repo == nullptr )
return;
std::string repoName = repo->repo;
std::string repoPath = this->repoPath( repo->repo );
if ( repoPath.empty() && !repo->type.empty() &&
!repo->type.front().files.empty() )
repoPath = mGit->repoPath( repo->type.front().files.front().file );
if ( repoPath.empty() )
return;
UIPopUpMenu* menu = UIPopUpMenu::New();
menu->setId( "git_repo_type_menu" );
if ( repo->hasStatusType( Git::GitStatusType::Staged ) ) {
addMenuItem( menu, "git-commit", "Commit", "git-commit" );
addMenuItem( menu, "git-stage-all", "Stage All" );
}
addMenuItem( menu, "git-fetch", "Fetch", "repo-fetch" );
addMenuItem( menu, "git-pull", "Pull", "repo-pull" );
addMenuItem( menu, "git-push", "Push", "repo-push" );
addMenuItem( menu, "git-stash", "Stash All", "git-stash" );
menu->on( Event::OnItemClicked,
[this, model, repoName, repoPath]( const Event* event ) {
if ( !mGit )
return;
UIMenuItem* item = event->getNode()->asType<UIMenuItem>();
std::string id( item->getId() );
if ( id == "git-stash" ) {
stashPush( model->getFiles( repoName ), repoPath );
} else if ( id == "git-fetch" ) {
fetch( repoPath );
} else if ( id == "git-pull" ) {
pull( repoPath );
} else if ( id == "git-push" ) {
push( repoPath );
} else if ( id == "git-commit" ) {
commit( repoPath );
} else if ( id == "git-stage-all" ) {
stage( model->getFiles(
repoName, (Uint32)Git::GitStatusType::Untracked |
(Uint32)Git::GitStatusType::Changed ) );
}
} );
menu->showOverMouseCursor();
break;
}
default:
break;
}
}
} );
@@ -1264,25 +1427,30 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) {
UIPopUpMenu* menu = UIPopUpMenu::New();
menu->setId( "git_branch_menu" );
addMenuItem( menu, "git-fetch", "Fetch", "repo-fetch" );
if ( branch.type != Git::RefType::Stash ) {
addMenuItem( menu, "git-fetch", "Fetch", "repo-fetch" );
if ( gitBranch() != branch.name ) {
addMenuItem( menu, "git-checkout", "Check Out...", "git-fetch" );
if ( gitBranch() != branch.name ) {
addMenuItem( menu, "git-checkout", "Check Out...", "git-fetch" );
}
if ( branch.type == Git::RefType::Head ) {
addMenuItem( menu, "git-branch-rename", "Rename", "", { KEY_F2 } );
addMenuItem( menu, "git-pull", "Pull", "repo-pull" );
if ( branch.ahead )
addMenuItem( menu, "git-push", "Push", "repo-push" );
if ( branch.behind )
addMenuItem( menu, "git-fast-forward-merge", "Fast Forward Merge" );
menu->addSeparator();
addMenuItem( menu, "git-branch-delete", "Delete", "remove" );
}
addMenuItem( menu, "git-create-branch", "Create Branch", "repo-forked", { KEY_F7 } );
} else {
addMenuItem( menu, "git-stash-apply", "Apply Stash", "git-stash-apply" );
addMenuItem( menu, "git-stash-drop", "Drop Stash", "git-stash-pop" );
}
if ( branch.type == Git::RefType::Head ) {
addMenuItem( menu, "git-branch-rename", "Rename", "", { KEY_F2 } );
addMenuItem( menu, "git-pull", "Pull", "repo-pull" );
if ( branch.ahead )
addMenuItem( menu, "git-push", "Push", "repo-push" );
if ( branch.behind )
addMenuItem( menu, "git-fast-forward-merge", "Fast Forward Merge" );
menu->addSeparator();
addMenuItem( menu, "git-branch-delete", "Delete", "remove" );
}
addMenuItem( menu, "git-create-branch", "Create Branch", "repo-forked", { KEY_F7 } );
menu->on( Event::OnItemClicked, [this, branch]( const Event* event ) {
if ( !mGit )
return;
@@ -1291,19 +1459,23 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) {
if ( id == "git-checkout" ) {
checkout( branch );
} else if ( id == "git-pull" ) {
pull();
pull( repoSelected() );
} else if ( id == "git-push" ) {
push();
push( repoSelected() );
} else if ( id == "git-branch-delete" ) {
branchDelete( branch );
} else if ( id == "git-branch-rename" ) {
branchRename( branch );
} else if ( id == "git-fetch" ) {
fetch();
fetch( repoSelected() );
} else if ( id == "git-fast-forward-merge" ) {
fastForwardMerge( branch );
} else if ( id == "git-create-branch" ) {
branchCreate();
} else if ( id == "git-stash-apply" ) {
stashApply( branch );
} else if ( id == "git-stash-drop" ) {
stashDrop( branch );
}
} );
@@ -1314,8 +1486,8 @@ void GitPlugin::openFileStatusMenu( const Git::DiffFile& file ) {
UIPopUpMenu* menu = UIPopUpMenu::New();
menu->setId( "git_file_status_menu" );
addMenuItem( menu, "git-open-file", "Open File" );
addMenuItem( menu, "git-diff", "Open Diff" );
addMenuItem( menu, "git-open-file", "Open File", "file" );
addMenuItem( menu, "git-diff", "Open Diff", "diff-single" );
if ( file.report.type != Git::GitStatusType::Staged ) {
addMenuItem( menu, "git-stage", "Stage" );
@@ -1387,6 +1559,11 @@ std::string GitPlugin::repoSelected() {
return mRepoSelected;
}
std::string GitPlugin::projectPath() {
Lock l( mRepoMutex );
return mProjectPath;
}
std::string GitPlugin::repoName( const std::string& repoPath ) {
Lock l( mRepoMutex );
for ( const auto& repo : mRepos )
@@ -1395,4 +1572,12 @@ std::string GitPlugin::repoName( const std::string& repoPath ) {
return "";
}
std::string GitPlugin::repoPath( const std::string& repoName ) {
Lock l( mRepoMutex );
for ( const auto& repo : mRepos )
if ( repo.second == repoName )
return repo.first;
return "";
}
} // namespace ecode
+15 -4
View File
@@ -31,6 +31,7 @@ static constexpr const char* GIT_BOLD = "bold";
static constexpr const char* GIT_NOT_BOLD = "notbold";
static constexpr const char* GIT_TAG = "tag";
static constexpr const char* GIT_REPO = "repo";
static constexpr const char* GIT_STASH = "git-stash";
class GitPlugin : public PluginBase {
public:
@@ -154,15 +155,15 @@ class GitPlugin : public PluginBase {
void fastForwardMerge( Git::Branch branch );
void pull();
void pull( const std::string& repoPath );
void push();
void push( const std::string& repoPath );
void fetch();
void fetch( const std::string& repoPath );
void branchCreate();
void commit( const std::string& repoPath = "" );
void commit( const std::string& repoPath );
void stage( const std::vector<std::string>& files );
@@ -192,6 +193,12 @@ class GitPlugin : public PluginBase {
void openFileStatusMenu( const Git::DiffFile& file );
void stashPush( const std::vector<std::string>& files, const std::string& repoPath );
void stashApply( const Git::Branch& branch );
void stashDrop( const Git::Branch& branch );
void runAsync( std::function<Git::Result()> fn, bool updateStatus, bool updateBranches,
bool displaySuccessMsg = false, bool updateBranchesOnError = false );
@@ -201,8 +208,12 @@ class GitPlugin : public PluginBase {
std::string repoSelected();
std::string projectPath();
std::string repoName( const std::string& repoPath );
std::string repoPath( const std::string& repoName );
std::string fixFilePath( const std::string& file );
std::vector<std::string> fixFilePaths( const std::vector<std::string>& files );
+10 -1
View File
@@ -38,6 +38,14 @@ class GitStatusModel : public Model {
struct RepoStatus {
std::string repo;
std::vector<RepoStatusType> type;
bool hasStatusType( Git::GitStatusType statusType ) const {
for ( const auto& t : type ) {
if ( t.type == statusType )
return true;
}
return false;
}
};
enum Column { File, State, Inserted, Removed, RelativeDirectory };
@@ -66,7 +74,8 @@ class GitStatusModel : public Model {
const DiffFile* file( const ModelIndex& index ) const;
std::vector<std::string> getFiles( const std::string& repo, uint32_t statusType ) const;
std::vector<std::string> getFiles( const std::string& repo,
uint32_t statusType = Git::STATUS_TYPE_ALL ) const;
protected:
std::vector<RepoStatus> mStatus;