mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
Expand Git History actions and branch management
- Add SmartGit-inspired context actions to Git History commits, including checkout, merge, fast-forward merge, cherry-pick, revert, branch creation, tag creation, and copying commit IDs or complete messages. - Order commit actions into consistent operation, ref-creation, and clipboard groups. Hide the redundant Show in Git History action inside detached history tabs while retaining it in the source-control sidebar. - Add a Working Tree / Index context menu with status-aware Commit and Stage actions. Track staged and unstaged availability in the history model and keep it synchronized as repository status changes. - Extend the Git command wrapper with operations for creating branches at commits, creating and deleting tags, deleting remote branches, cherry-picking commits, and reverting commits. - Support deleting tags and remote branches from the Branches view. When deleting a local branch with an upstream, optionally delete its tracked remote branch as part of the operation. - Add confirmation and input dialogs for branch, tag, remote-deletion, and revert workflows. Disable history-changing commit actions while another Git operation is active.
This commit is contained in:
@@ -3,14 +3,17 @@
|
||||
This repository contains two primary components: a core framework (`eepp`) and an application built on top of it (`ecode`).
|
||||
|
||||
## 1. eepp (Core Framework)
|
||||
|
||||
[eepp](https://github.com/SpartanJ/eepp/) is an open-source, cross-platform game and application development framework. It is heavily focused on providing robust technology for rich, hardware-accelerated Graphical User Interfaces (GUIs).
|
||||
|
||||
## 2. ecode (Application)
|
||||
|
||||
[ecode](https://github.com/SpartanJ/ecode/) is a lightweight, multi-platform code editor designed for responsiveness and performance.
|
||||
* **Relationship:** `ecode` is built *using* the `eepp` GUI framework. It acts as the primary real-world consumer of `eepp`.
|
||||
* **Goal:** Development on `ecode` is often used to test, improve, and drive new features in the underlying `eepp` library.
|
||||
|
||||
## Documentation & Code References
|
||||
|
||||
When working on this project, rely on the following resources to understand existing implementations:
|
||||
* **C++ Headers (Primary Reference):** Rely heavily on Doxygen documentation found directly inside the class headers located at `include/eepp/`.
|
||||
* **Basic Documentation:** Found in `docs/articles/`.
|
||||
@@ -26,6 +29,7 @@ in the source. Keep all locale files structurally valid and verify that every su
|
||||
contains the new or renamed key.
|
||||
|
||||
## C++ Virtual Method Style
|
||||
|
||||
Follow the convention already used by the class being edited. In particular, when a class declares
|
||||
virtual methods without the `override` specifier, do not introduce `override` on new methods in that
|
||||
class. Mixing the styles can enable Clang's inconsistent-missing-override warnings for the existing
|
||||
|
||||
@@ -358,6 +358,7 @@ ecode versucht, externes Terminal zu öffnen.</string>
|
||||
<string name="git_checkout">Auschecken</string>
|
||||
<string name="git_checkout_ellipsis">Auschecken...</string>
|
||||
<string name="git_commit">Git-Commit</string>
|
||||
<string name="git_commit_ellipsis">Commit...</string>
|
||||
<string name="git_commit_message">Commit-Nachricht:</string>
|
||||
<string name="git_confirm">Bestätigen</string>
|
||||
<string name="git_confirm_apply_stash">Einen zuvor gespeicherten Stash anwenden?</string>
|
||||
@@ -1266,6 +1267,23 @@ Für sichtbare Änderung ecode neu starten.</string>
|
||||
<string name="git_working_tree_index">Arbeitsbaum / Index</string>
|
||||
<string name="git_working_tree_summary">%zu geändert, %zu vorgemerkt</string>
|
||||
<string name="git_uncommitted_changes">Nicht übernommene Änderungen</string>
|
||||
<string name="git_copy_id">ID kopieren</string>
|
||||
<string name="git_copy_message">Nachricht kopieren</string>
|
||||
<string name="git_create_branch_ellipsis">Branch hinzufügen...</string>
|
||||
<string name="git_add_tag">Tag hinzufügen</string>
|
||||
<string name="git_add_tag_ellipsis">Tag hinzufügen...</string>
|
||||
<string name="git_add_tag_to_commit">Tag zu Commit %s hinzufügen</string>
|
||||
<string name="git_create_branch_at_commit">Branch bei Commit %s erstellen.</string>
|
||||
<string name="git_merge_ellipsis">Zusammenführen...</string>
|
||||
<string name="git_cherry_pick_ellipsis">Cherry-Pick...</string>
|
||||
<string name="git_revert_ellipsis">Rückgängig machen...</string>
|
||||
<string name="git_confirm_revert_commit">Möchten Sie den ausgewählten Commit rückgängig machen? Dadurch wird ein Commit erstellt, der seine Änderungen rückgängig macht.</string>
|
||||
<string name="git_revert_and_commit">Rückgängig & committen</string>
|
||||
<string name="git_revert">Rückgängig machen</string>
|
||||
<string name="git_confirm_tag_delete">Tag '%s' löschen?</string>
|
||||
<string name="git_confirm_remote_branch_delete">Remote-Branch '%s' aus '%s' löschen?</string>
|
||||
<string name="git_delete_tracking_remote">Auch den verfolgten Remote-Branch löschen</string>
|
||||
<string name="git_delete_tag">Tag löschen</string>
|
||||
<string name="git_history_no_additional_commits">Keine weiteren Commits</string>
|
||||
<string name="git_show_in_history">Im Git-Verlauf anzeigen</string>
|
||||
<string name="git_commit_history_title">Git-Commit-Verlauf – %s</string>
|
||||
|
||||
@@ -342,6 +342,7 @@ ecode will try to open an external terminal.</string>
|
||||
<string name="git_checkout">Check Out</string>
|
||||
<string name="git_checkout_ellipsis">Check Out...</string>
|
||||
<string name="git_commit">Git Commit</string>
|
||||
<string name="git_commit_ellipsis">Commit...</string>
|
||||
<string name="git_commit_message">Commit Message:</string>
|
||||
<string name="git_confirm">Confirm</string>
|
||||
<string name="git_confirm_apply_stash">Apply a previously saved stash?</string>
|
||||
@@ -1251,6 +1252,23 @@ Restart ecode to see the changes.</string>
|
||||
<string name="git_working_tree_index">Working Tree / Index</string>
|
||||
<string name="git_working_tree_summary">%zu changed, %zu staged</string>
|
||||
<string name="git_uncommitted_changes">Uncommitted changes</string>
|
||||
<string name="git_copy_id">Copy ID</string>
|
||||
<string name="git_copy_message">Copy Message</string>
|
||||
<string name="git_create_branch_ellipsis">Add Branch...</string>
|
||||
<string name="git_add_tag">Add Tag</string>
|
||||
<string name="git_add_tag_ellipsis">Add Tag...</string>
|
||||
<string name="git_add_tag_to_commit">Add tag to commit %s</string>
|
||||
<string name="git_create_branch_at_commit">Create a branch at commit %s.</string>
|
||||
<string name="git_merge_ellipsis">Merge...</string>
|
||||
<string name="git_cherry_pick_ellipsis">Cherry-Pick...</string>
|
||||
<string name="git_revert_ellipsis">Revert...</string>
|
||||
<string name="git_confirm_revert_commit">Do you want to revert the selected commit? This will create a commit that undoes its changes.</string>
|
||||
<string name="git_revert_and_commit">Revert & Commit</string>
|
||||
<string name="git_revert">Revert</string>
|
||||
<string name="git_confirm_tag_delete">Delete tag '%s'?</string>
|
||||
<string name="git_confirm_remote_branch_delete">Delete remote branch '%s' from '%s'?</string>
|
||||
<string name="git_delete_tracking_remote">Also delete the tracked remote branch</string>
|
||||
<string name="git_delete_tag">Delete Tag</string>
|
||||
<string name="git_history_no_additional_commits">No additional commits</string>
|
||||
<string name="git_show_in_history">Show in Git History</string>
|
||||
<string name="git_commit_history_title">Git Commit History - %s</string>
|
||||
|
||||
@@ -341,6 +341,7 @@ ecode tentera d'ouvrir un terminal externe.</string>
|
||||
<string name="git_checkout">Check Out</string>
|
||||
<string name="git_checkout_ellipsis">Check Out...</string>
|
||||
<string name="git_commit">Git Commit</string>
|
||||
<string name="git_commit_ellipsis">Valider...</string>
|
||||
<string name="git_commit_message">Message de commit :</string>
|
||||
<string name="git_confirm">Confirmer</string>
|
||||
<string name="git_confirm_apply_stash">Appliquer un stash précédemment sauvegardé ?</string>
|
||||
@@ -1250,6 +1251,23 @@ Redémarrer ecode pour voir les changements.</string>
|
||||
<string name="git_working_tree_index">Arbre de travail / Index</string>
|
||||
<string name="git_working_tree_summary">%zu modifié(s), %zu indexé(s)</string>
|
||||
<string name="git_uncommitted_changes">Modifications non validées</string>
|
||||
<string name="git_copy_id">Copier l’identifiant</string>
|
||||
<string name="git_copy_message">Copier le message</string>
|
||||
<string name="git_create_branch_ellipsis">Ajouter une branche...</string>
|
||||
<string name="git_add_tag">Ajouter une étiquette</string>
|
||||
<string name="git_add_tag_ellipsis">Ajouter une étiquette...</string>
|
||||
<string name="git_add_tag_to_commit">Ajouter une étiquette au commit %s</string>
|
||||
<string name="git_create_branch_at_commit">Créer une branche au commit %s.</string>
|
||||
<string name="git_merge_ellipsis">Fusionner...</string>
|
||||
<string name="git_cherry_pick_ellipsis">Cherry-pick...</string>
|
||||
<string name="git_revert_ellipsis">Annuler...</string>
|
||||
<string name="git_confirm_revert_commit">Voulez-vous annuler le commit sélectionné ? Cela créera un commit annulant ses modifications.</string>
|
||||
<string name="git_revert_and_commit">Annuler & valider</string>
|
||||
<string name="git_revert">Annuler</string>
|
||||
<string name="git_confirm_tag_delete">Supprimer l’étiquette '%s' ?</string>
|
||||
<string name="git_confirm_remote_branch_delete">Supprimer la branche distante '%s' de '%s' ?</string>
|
||||
<string name="git_delete_tracking_remote">Supprimer également la branche distante suivie</string>
|
||||
<string name="git_delete_tag">Supprimer l’étiquette</string>
|
||||
<string name="git_history_no_additional_commits">Aucun commit supplémentaire</string>
|
||||
<string name="git_show_in_history">Afficher dans l’historique Git</string>
|
||||
<string name="git_commit_history_title">Historique des commits Git – %s</string>
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
<string name="git_checkout">检查</string>
|
||||
<string name="git_checkout_ellipsis">检查...</string>
|
||||
<string name="git_commit">提交Git</string>
|
||||
<string name="git_commit_ellipsis">提交...</string>
|
||||
<string name="git_commit_message">提交信息:</string>
|
||||
<string name="git_confirm">确认</string>
|
||||
<string name="git_confirm_apply_stash">Apply a previously saved stash?</string>
|
||||
@@ -1055,6 +1056,23 @@ file in the directory tree.</string>
|
||||
<string name="git_working_tree_index">工作树 / 索引</string>
|
||||
<string name="git_working_tree_summary">%zu 个更改,%zu 个已暂存</string>
|
||||
<string name="git_uncommitted_changes">未提交的更改</string>
|
||||
<string name="git_copy_id">复制 ID</string>
|
||||
<string name="git_copy_message">复制提交消息</string>
|
||||
<string name="git_create_branch_ellipsis">添加分支...</string>
|
||||
<string name="git_add_tag">添加标签</string>
|
||||
<string name="git_add_tag_ellipsis">添加标签...</string>
|
||||
<string name="git_add_tag_to_commit">为提交 %s 添加标签</string>
|
||||
<string name="git_create_branch_at_commit">在提交 %s 创建分支。</string>
|
||||
<string name="git_merge_ellipsis">合并...</string>
|
||||
<string name="git_cherry_pick_ellipsis">拣选提交...</string>
|
||||
<string name="git_revert_ellipsis">还原...</string>
|
||||
<string name="git_confirm_revert_commit">要还原所选提交吗? 这将创建一个撤销其更改的提交。</string>
|
||||
<string name="git_revert_and_commit">还原并提交</string>
|
||||
<string name="git_revert">还原</string>
|
||||
<string name="git_confirm_tag_delete">删除标签“%s”吗?</string>
|
||||
<string name="git_confirm_remote_branch_delete">删除远程分支“%s”(远程仓库“%s”)吗?</string>
|
||||
<string name="git_delete_tracking_remote">同时删除跟踪的远程分支</string>
|
||||
<string name="git_delete_tag">删除标签</string>
|
||||
<string name="git_history_no_additional_commits">没有其他提交</string>
|
||||
<string name="git_show_in_history">在 Git 历史记录中显示</string>
|
||||
<string name="git_commit_history_title">Git 提交历史记录 - %s</string>
|
||||
|
||||
@@ -929,6 +929,56 @@ Git::Result Git::createBranch( const std::string& branchName, bool _checkout,
|
||||
return res;
|
||||
}
|
||||
|
||||
Git::Result Git::createBranchAt( const std::string& branchName, const std::string& revision,
|
||||
const std::string& projectDir ) {
|
||||
return gitSimple( String::format( "branch --no-track %s %s", branchName, revision ),
|
||||
projectDir );
|
||||
}
|
||||
|
||||
Git::Result Git::createTag( const std::string& name, const std::string& revision,
|
||||
const std::string& message, const std::string& projectDir ) {
|
||||
std::vector<std::string> args{ "tag" };
|
||||
if ( !message.empty() ) {
|
||||
args.emplace_back( "-a" );
|
||||
args.emplace_back( "-m" );
|
||||
args.emplace_back( message );
|
||||
}
|
||||
args.emplace_back( name );
|
||||
args.emplace_back( revision );
|
||||
Result result;
|
||||
result.returnCode = git( args, projectDir, result.result );
|
||||
return result;
|
||||
}
|
||||
|
||||
Git::Result Git::deleteTag( const std::string& name, const std::string& projectDir ) {
|
||||
Result result;
|
||||
result.returnCode = git( { "tag", "-d", name }, projectDir, result.result );
|
||||
return result;
|
||||
}
|
||||
|
||||
Git::Result Git::deleteRemoteBranch( const std::string& remote, const std::string& branch,
|
||||
const std::string& projectDir ) {
|
||||
Result result;
|
||||
result.returnCode = git( { "push", remote, "--delete", branch }, projectDir, result.result );
|
||||
return result;
|
||||
}
|
||||
|
||||
Git::Result Git::cherryPick( const std::string& revision, const std::string& projectDir ) {
|
||||
Result result;
|
||||
result.returnCode = git( { "cherry-pick", revision }, projectDir, result.result );
|
||||
return result;
|
||||
}
|
||||
|
||||
Git::Result Git::revert( const std::string& revision, bool commit, const std::string& projectDir ) {
|
||||
Result result;
|
||||
std::vector<std::string> args{ "revert" };
|
||||
if ( !commit )
|
||||
args.emplace_back( "--no-commit" );
|
||||
args.emplace_back( revision );
|
||||
result.returnCode = git( args, projectDir, result.result );
|
||||
return result;
|
||||
}
|
||||
|
||||
Git::Result Git::renameBranch( const std::string& branch, const std::string& newName,
|
||||
const std::string& projectDir ) {
|
||||
return gitSimple( String::format( "branch -M %s %s", branch, newName ), projectDir );
|
||||
|
||||
@@ -374,6 +374,21 @@ class Git {
|
||||
Result createBranch( const std::string& branchName, bool checkout = false,
|
||||
const std::string& projectDir = "" );
|
||||
|
||||
Result createBranchAt( const std::string& branchName, const std::string& revision,
|
||||
const std::string& projectDir = "" );
|
||||
|
||||
Result createTag( const std::string& name, const std::string& revision,
|
||||
const std::string& message = "", const std::string& projectDir = "" );
|
||||
|
||||
Result deleteTag( const std::string& name, const std::string& projectDir = "" );
|
||||
|
||||
Result deleteRemoteBranch( const std::string& remote, const std::string& branch,
|
||||
const std::string& projectDir = "" );
|
||||
|
||||
Result cherryPick( const std::string& revision, const std::string& projectDir = "" );
|
||||
|
||||
Result revert( const std::string& revision, bool commit, const std::string& projectDir = "" );
|
||||
|
||||
Result renameBranch( const std::string& branch, const std::string& newName,
|
||||
const std::string& projectDir = "" );
|
||||
|
||||
|
||||
@@ -155,6 +155,8 @@ GitHistoryModel::workingTreeNode( const Git::Status& status, const std::string&
|
||||
}
|
||||
auto item = std::make_unique<Node>();
|
||||
item->type = NodeType::WorkingTree;
|
||||
item->hasWorkingTreeChanges = changed > 0;
|
||||
item->hasStagedChanges = staged > 0;
|
||||
item->subject = mPlugin->i18n( "git_working_tree_index", "Working Tree / Index" );
|
||||
item->message = String::format(
|
||||
mPlugin->i18n( "git_working_tree_summary", "%zu changed, %zu staged" ).toUtf8(), changed,
|
||||
@@ -234,6 +236,8 @@ void GitHistoryModel::setWorkingTreeStatus( const Git::Status& status,
|
||||
} else if ( hasItem && item ) {
|
||||
mRoots.front()->message = std::move( item->message );
|
||||
mRoots.front()->tooltip = std::move( item->tooltip );
|
||||
mRoots.front()->hasWorkingTreeChanges = item->hasWorkingTreeChanges;
|
||||
mRoots.front()->hasStagedChanges = item->hasStagedChanges;
|
||||
}
|
||||
invalidate( Model::DontInvalidateIndexes );
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ class GitHistoryModel : public Model {
|
||||
bool childrenLoaded{ false };
|
||||
bool childrenLoading{ false };
|
||||
bool retryAppend{ false };
|
||||
bool hasWorkingTreeChanges{ false };
|
||||
bool hasStagedChanges{ false };
|
||||
};
|
||||
|
||||
static std::shared_ptr<GitHistoryModel> asModel( GitPlugin* plugin ) {
|
||||
|
||||
@@ -1070,6 +1070,46 @@ void GitPlugin::branchRename( Git::Branch branch ) {
|
||||
}
|
||||
|
||||
void GitPlugin::branchDelete( Git::Branch branch ) {
|
||||
if ( branch.type == Git::RefType::Tag ) {
|
||||
UIMessageBox* msgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL,
|
||||
String::format( i18n( "git_confirm_tag_delete", "Delete tag '%s'?" ).toUtf8(),
|
||||
branch.name ) );
|
||||
msgBox->on( Event::OnConfirm, [this, branch]( auto ) {
|
||||
runAsync( [this, branch] { return mGit->deleteTag( branch.name, repoSelected() ); },
|
||||
false, true );
|
||||
} );
|
||||
msgBox->setTitle( i18n( "git_delete", "Delete" ) );
|
||||
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center();
|
||||
msgBox->showWhenReady();
|
||||
return;
|
||||
}
|
||||
|
||||
auto remoteParts = []( const std::string& remoteBranch ) {
|
||||
const size_t separator = remoteBranch.find( '/' );
|
||||
return std::pair{
|
||||
separator == std::string::npos ? std::string{} : remoteBranch.substr( 0, separator ),
|
||||
separator == std::string::npos ? remoteBranch : remoteBranch.substr( separator + 1 ) };
|
||||
};
|
||||
if ( branch.type == Git::RefType::Remote ) {
|
||||
auto [remote, name] = remoteParts( branch.name );
|
||||
UIMessageBox* msgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL, String::format( i18n( "git_confirm_remote_branch_delete",
|
||||
"Delete remote branch '%s' from '%s'?" )
|
||||
.toUtf8(),
|
||||
name, remote ) );
|
||||
msgBox->on( Event::OnConfirm, [this, remote = std::move( remote ),
|
||||
name = std::move( name )]( auto ) {
|
||||
runAsync( [this, remote,
|
||||
name] { return mGit->deleteRemoteBranch( remote, name, repoSelected() ); },
|
||||
true, true );
|
||||
} );
|
||||
msgBox->setTitle( i18n( "git_delete", "Delete" ) );
|
||||
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center();
|
||||
msgBox->showWhenReady();
|
||||
return;
|
||||
}
|
||||
|
||||
UIMessageBox* msgBox = UIMessageBox::New(
|
||||
UIMessageBox::OK_CANCEL,
|
||||
String::format( i18n( "git_confirm_branch_delete",
|
||||
@@ -1077,9 +1117,28 @@ void GitPlugin::branchDelete( Git::Branch branch ) {
|
||||
.toUtf8(),
|
||||
branch.name ) );
|
||||
|
||||
msgBox->on( Event::OnConfirm, [this, branch]( auto ) {
|
||||
runAsync( [this, branch]() { return mGit->deleteBranch( branch.name, repoSelected() ); },
|
||||
false, true );
|
||||
UICheckBox* deleteRemote = nullptr;
|
||||
if ( !branch.remote.empty() ) {
|
||||
deleteRemote = UICheckBox::New();
|
||||
deleteRemote
|
||||
->setText(
|
||||
i18n( "git_delete_tracking_remote", "Also delete the tracked remote branch" ) )
|
||||
->setLayoutSizePolicy( SizePolicy::WrapContent, SizePolicy::WrapContent )
|
||||
->setLayoutMarginTop( 8 )
|
||||
->setParent( msgBox->getTextBox()->getParent() );
|
||||
deleteRemote->toPosition( 1 );
|
||||
}
|
||||
msgBox->on( Event::OnConfirm, [this, branch, deleteRemote, remoteParts]( auto ) {
|
||||
const bool removeRemote = deleteRemote && deleteRemote->isChecked();
|
||||
runAsync(
|
||||
[this, branch, removeRemote, remoteParts]() {
|
||||
auto result = mGit->deleteBranch( branch.name, repoSelected() );
|
||||
if ( result.fail() || !removeRemote )
|
||||
return result;
|
||||
auto [remote, name] = remoteParts( branch.remote );
|
||||
return mGit->deleteRemoteBranch( remote, name, repoSelected() );
|
||||
},
|
||||
false, true );
|
||||
} );
|
||||
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
|
||||
msgBox->setTitle( i18n( "git_confirm", "Confirm" ) );
|
||||
@@ -2539,23 +2598,180 @@ void GitPlugin::activateHistoryIndex( const ModelIndex& index, bool expand ) {
|
||||
}
|
||||
}
|
||||
|
||||
void GitPlugin::openHistoryMenu( const ModelIndex& index ) {
|
||||
void GitPlugin::openHistoryMenu( const ModelIndex& index, bool showHistoryAction ) {
|
||||
if ( !mHistoryModel )
|
||||
return;
|
||||
const auto* node = mHistoryModel->node( index );
|
||||
if ( !node || node->type != GitHistoryModel::NodeType::Commit )
|
||||
if ( !node )
|
||||
return;
|
||||
if ( node->type == GitHistoryModel::NodeType::WorkingTree ) {
|
||||
if ( !canStartGitOperation() )
|
||||
return;
|
||||
UIPopUpMenu* menu = UIPopUpMenu::New();
|
||||
if ( node->hasStagedChanges ) {
|
||||
menuAdd( menu, "git-commit", i18n( "git_commit_ellipsis", "Commit..." ), "git-commit" );
|
||||
}
|
||||
if ( node->hasWorkingTreeChanges )
|
||||
menuAdd( menu, "git-stage", i18n( "git_stage", "Stage" ), "diff-added" );
|
||||
const std::string repo = repoSelected();
|
||||
menu->on( Event::OnItemClicked, [this, repo]( const Event* event ) {
|
||||
const std::string id = event->getNode()->asType<UIMenuItem>()->getId();
|
||||
if ( id == "git-commit" ) {
|
||||
commit( repo );
|
||||
} else if ( id == "git-stage" ) {
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
Lock l( mGitStatusMutex );
|
||||
const auto found = mGitStatus.files.find( mGit->repoName( repo, true, repo ) );
|
||||
if ( found != mGitStatus.files.end() ) {
|
||||
for ( const auto& file : found->second ) {
|
||||
if ( file.report.type != Git::GitStatusType::Staged )
|
||||
files.emplace_back( file.file );
|
||||
}
|
||||
}
|
||||
}
|
||||
stage( files );
|
||||
}
|
||||
} );
|
||||
menu->showOverMouseCursor();
|
||||
return;
|
||||
}
|
||||
if ( node->type != GitHistoryModel::NodeType::Commit )
|
||||
return;
|
||||
UIPopUpMenu* menu = UIPopUpMenu::New();
|
||||
menuAdd( menu, "git-show-history", i18n( "git_show_in_history", "Show in Git History" ),
|
||||
"history" );
|
||||
if ( showHistoryAction ) {
|
||||
menuAdd( menu, "git-show-history", i18n( "git_show_in_history", "Show in Git History" ),
|
||||
"history" );
|
||||
menu->addSeparator();
|
||||
}
|
||||
if ( canStartGitOperation() ) {
|
||||
menuAdd( menu, "git-checkout", i18n( "git_checkout_ellipsis", "Check Out..." ),
|
||||
"git-fetch" );
|
||||
menuAdd( menu, "git-merge-commit", i18n( "git_merge_ellipsis", "Merge..." ), "git-merge" );
|
||||
menuAdd( menu, "git-fast-forward-commit",
|
||||
i18n( "git_fast_forward_merge", "Fast Forward Merge" ), "git-merge" );
|
||||
menuAdd( menu, "git-cherry-pick", i18n( "git_cherry_pick_ellipsis", "Cherry-Pick..." ),
|
||||
"git-cherry-pick" );
|
||||
menuAdd( menu, "git-revert-commit", i18n( "git_revert_ellipsis", "Revert..." ), "discard" );
|
||||
menu->addSeparator();
|
||||
}
|
||||
menuAdd( menu, "git-create-branch", i18n( "git_create_branch_ellipsis", "Add Branch..." ),
|
||||
"repo-forked" );
|
||||
menuAdd( menu, "git-add-tag", i18n( "git_add_tag_ellipsis", "Add Tag..." ), "tag" );
|
||||
menu->addSeparator();
|
||||
menuAdd( menu, "git-copy-message", i18n( "git_copy_message", "Copy Message" ), "copy" );
|
||||
menuAdd( menu, "git-copy-id", i18n( "git_copy_id", "Copy ID" ), "copy" );
|
||||
const Git::Commit commit = node->commit;
|
||||
menu->on( Event::OnItemClicked, [this, commit]( const Event* event ) {
|
||||
if ( event->getNode()->asType<UIMenuItem>()->getId() == "git-show-history" )
|
||||
const std::string id = event->getNode()->asType<UIMenuItem>()->getId();
|
||||
if ( id == "git-show-history" )
|
||||
showGitHistory( &commit );
|
||||
else if ( id == "git-copy-id" )
|
||||
getUISceneNode()->getWindow()->getClipboard()->setText( commit.hash );
|
||||
else if ( id == "git-copy-message" ) {
|
||||
getUISceneNode()->getWindow()->getClipboard()->setText(
|
||||
commit.subject + ( commit.message.empty() ? "" : "\n\n" + commit.message ) );
|
||||
} else if ( id == "git-create-branch" )
|
||||
createBranchAtCommit( commit );
|
||||
else if ( id == "git-add-tag" )
|
||||
addTag( commit );
|
||||
else if ( id == "git-checkout" )
|
||||
runAsync( [this, commit] { return mGit->checkout( commit.hash, repoSelected() ); },
|
||||
true, true, false, false, false, true );
|
||||
else if ( id == "git-merge-commit" ) {
|
||||
std::string repo = repoSelected();
|
||||
runMergeLikeAsync(
|
||||
[commit, repo = std::move( repo )]( Git& git ) {
|
||||
return git.mergeBranch( commit.hash, false, repo );
|
||||
},
|
||||
repoSelected() );
|
||||
} else if ( id == "git-fast-forward-commit" ) {
|
||||
std::string repo = repoSelected();
|
||||
runMergeLikeAsync(
|
||||
[commit, repo = std::move( repo )]( Git& git ) {
|
||||
return git.mergeBranch( commit.hash, true, repo );
|
||||
},
|
||||
repoSelected() );
|
||||
} else if ( id == "git-cherry-pick" ) {
|
||||
std::string repo = repoSelected();
|
||||
runMergeLikeAsync( [commit, repo = std::move( repo )](
|
||||
Git& git ) { return git.cherryPick( commit.hash, repo ); },
|
||||
repoSelected() );
|
||||
} else if ( id == "git-revert-commit" )
|
||||
revertCommit( commit );
|
||||
} );
|
||||
menu->showOverMouseCursor();
|
||||
}
|
||||
|
||||
bool GitPlugin::canStartGitOperation() {
|
||||
return mGit && mGit->operation( repoSelected() ) == Git::GitOperation::None;
|
||||
}
|
||||
|
||||
void GitPlugin::createBranchAtCommit( const Git::Commit& commit ) {
|
||||
UIMessageBox* box = UIMessageBox::New(
|
||||
UIMessageBox::INPUT,
|
||||
String::format(
|
||||
i18n( "git_create_branch_at_commit", "Create a branch at commit %s." ).toUtf8(),
|
||||
commit.shortHash ) );
|
||||
box->on( Event::OnConfirm, [this, box, commit]( const Event* ) {
|
||||
const std::string name = box->getTextInput()->getText().toUtf8();
|
||||
if ( name.empty() )
|
||||
return;
|
||||
box->closeWindow();
|
||||
runAsync( [this, name,
|
||||
commit] { return mGit->createBranchAt( name, commit.hash, repoSelected() ); },
|
||||
false, true );
|
||||
} );
|
||||
box->setTitle( i18n( "git_add_branch", "Add Branch" ) );
|
||||
box->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center();
|
||||
box->showWhenReady();
|
||||
}
|
||||
|
||||
void GitPlugin::addTag( const Git::Commit& commit ) {
|
||||
UIMessageBox* box = UIMessageBox::New(
|
||||
UIMessageBox::TEXT_EDIT,
|
||||
String::format( i18n( "git_add_tag_to_commit", "Add tag to commit %s" ).toUtf8(),
|
||||
commit.shortHash ) );
|
||||
auto* name = UITextInput::New();
|
||||
name->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent )
|
||||
->setLayoutMargin( Rectf( 0, 4, 0, 4 ) )
|
||||
->setParent( box->getTextEdit()->getParent() );
|
||||
name->toPosition( 1 );
|
||||
box->getTextEdit()->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed );
|
||||
box->getButtonOK()->setText( i18n( "git_add_tag", "Add Tag" ) );
|
||||
box->on( Event::OnConfirm, [this, box, name, commit]( const Event* ) {
|
||||
const std::string tag = name->getText().toUtf8();
|
||||
if ( tag.empty() )
|
||||
return;
|
||||
const std::string message = box->getTextEdit()->getText().toUtf8();
|
||||
box->closeWindow();
|
||||
runAsync( [this, tag, message,
|
||||
commit] { return mGit->createTag( tag, commit.hash, message, repoSelected() ); },
|
||||
false, true );
|
||||
} );
|
||||
box->setTitle( i18n( "git_add_tag", "Add Tag" ) );
|
||||
box->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center();
|
||||
box->showWhenReady();
|
||||
name->setFocus();
|
||||
}
|
||||
|
||||
void GitPlugin::revertCommit( const Git::Commit& commit ) {
|
||||
UIMessageBox* box = UIMessageBox::New(
|
||||
UIMessageBox::YES_NO,
|
||||
i18n( "git_confirm_revert_commit", "Do you want to revert the selected commit?\nThis will "
|
||||
"create a commit that undoes its changes." ) );
|
||||
box->getButtonOK()->setText( i18n( "git_revert_and_commit", "Revert & Commit" ) );
|
||||
box->on( Event::OnConfirm, [this, commit]( const Event* ) {
|
||||
std::string repo = repoSelected();
|
||||
runMergeLikeAsync( [commit, repo = std::move( repo )](
|
||||
Git& git ) { return git.revert( commit.hash, true, repo ); },
|
||||
repoSelected() );
|
||||
} );
|
||||
box->setTitle( i18n( "git_revert", "Revert" ) );
|
||||
box->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center();
|
||||
box->showWhenReady();
|
||||
}
|
||||
|
||||
std::string GitPlugin::detachedHistoryTitle() {
|
||||
String state = String::fromUtf8( mHistoryRevision );
|
||||
if ( mHistoryRefDropDown && mHistoryRefModel ) {
|
||||
@@ -2637,7 +2853,7 @@ void GitPlugin::showGitHistory( const Git::Commit* commit ) {
|
||||
else if ( modelEvent->getModelEventType() == ModelEventType::Open )
|
||||
activateHistoryIndex( modelEvent->getModelIndex(), false );
|
||||
else if ( modelEvent->getModelEventType() == ModelEventType::OpenMenu )
|
||||
openHistoryMenu( modelEvent->getModelIndex() );
|
||||
openHistoryMenu( modelEvent->getModelIndex(), false );
|
||||
} );
|
||||
mDetachedHistory.tree->setOnSelection( [this]( const ModelIndex& index ) {
|
||||
if ( const auto* node = mHistoryModel ? mHistoryModel->node( index ) : nullptr; node ) {
|
||||
@@ -3396,7 +3612,7 @@ void GitPlugin::buildSidePanelTab() {
|
||||
openCommitDetails( node->commit );
|
||||
}
|
||||
} else if ( modelEvent->getModelEventType() == ModelEventType::OpenMenu )
|
||||
openHistoryMenu( modelEvent->getModelIndex() );
|
||||
openHistoryMenu( modelEvent->getModelIndex(), true );
|
||||
} );
|
||||
mHistoryTree->setOnSelection( [this]( const ModelIndex& index ) {
|
||||
if ( !mHistoryModel )
|
||||
@@ -3640,12 +3856,19 @@ void GitPlugin::openBranchMenu( const Git::Branch& branch ) {
|
||||
if ( branch.behind )
|
||||
menuAdd( menu, "git-fast-forward-merge",
|
||||
i18n( "git_fast_forward_merge", "Fast Forward Merge" ) );
|
||||
}
|
||||
if ( branch.type == Git::RefType::Head || branch.type == Git::RefType::Remote ||
|
||||
branch.type == Git::RefType::Tag ) {
|
||||
menu->addSeparator();
|
||||
menuAdd( menu, "git-branch-delete", i18n( "git_delete_branch", "Delete" ), "remove" );
|
||||
menuAdd( menu, "git-branch-delete",
|
||||
branch.type == Git::RefType::Tag ? i18n( "git_delete_tag", "Delete Tag" )
|
||||
: i18n( "git_delete_branch", "Delete" ),
|
||||
"remove" );
|
||||
}
|
||||
|
||||
menuAdd( menu, "git-merge-branch", i18n( "git_merge_branch", "Merge Branch" ),
|
||||
"git-merge" );
|
||||
if ( branch.type != Git::RefType::Tag )
|
||||
menuAdd( menu, "git-merge-branch", i18n( "git_merge_branch", "Merge Branch" ),
|
||||
"git-merge" );
|
||||
menuAdd( menu, "git-create-branch", i18n( "git_create_branch", "Create Branch" ),
|
||||
"repo-forked", { KEY_F7 } );
|
||||
} else {
|
||||
|
||||
@@ -377,15 +377,25 @@ class GitPlugin : public PluginBase {
|
||||
void activateHistoryIndex( const ModelIndex& index, bool expand );
|
||||
|
||||
void openCommitDetails( const Git::Commit& commit );
|
||||
|
||||
void openWorkingTreeDetails( bool detached );
|
||||
|
||||
void showGitHistory( const Git::Commit* commit = nullptr );
|
||||
|
||||
void focusDetachedHistory();
|
||||
|
||||
void openHistoryMenu( const ModelIndex& index );
|
||||
void openHistoryMenu( const ModelIndex& index, bool showHistoryAction );
|
||||
|
||||
void addTag( const Git::Commit& commit );
|
||||
|
||||
void createBranchAtCommit( const Git::Commit& commit );
|
||||
|
||||
void revertCommit( const Git::Commit& commit );
|
||||
|
||||
bool canStartGitOperation();
|
||||
|
||||
void openDetachedCommitDetails( const Git::Commit& commit );
|
||||
|
||||
void ensureDetachedCommitDetailsHost();
|
||||
|
||||
std::string detachedHistoryTitle();
|
||||
@@ -414,6 +424,7 @@ class GitPlugin : public PluginBase {
|
||||
GitConflictSession* conflictSession( const std::string& repoPath );
|
||||
|
||||
GitConflictSession* activeConflictSession();
|
||||
|
||||
bool updateConflictSessions( UnorderedMap<std::string, Git::ConflictState>& conflictStates );
|
||||
|
||||
void menuAdd( UIMenu* menu, const std::string& cmd, const std::string& text,
|
||||
|
||||
Reference in New Issue
Block a user