mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-02 11:10:31 +03:00
Fix conflict discard and merge view copy commands
- add Discard actions for conflicted files and conflict groups - restore discarded conflicts from HEAD in both index and working tree - restrict regular discard actions to changed tracked files - preserve default editor commands when replacing an editor document - enable copying from the read-only merge-view side panels - cover conflict restoration and merge-side copying - document build parallelism and function-spacing conventions
This commit is contained in:
@@ -46,6 +46,8 @@ Run this **after** all edits and **before** attempting to compile.
|
|||||||
## Step 2: Compile the Project
|
## Step 2: Compile the Project
|
||||||
To compile the project in debug mode, execute the `make` command, ensuring you point to the correct directory for your current Operating System.
|
To compile the project in debug mode, execute the `make` command, ensuring you point to the correct directory for your current Operating System.
|
||||||
|
|
||||||
|
### Build Parallelism
|
||||||
|
|
||||||
Always use all processors reported by the platform when selecting the parallel job count. On Linux
|
Always use all processors reported by the platform when selecting the parallel job count. On Linux
|
||||||
and other systems with `nproc`, use `-j$(nproc)` exactly; do not substitute an arbitrary fixed value
|
and other systems with `nproc`, use `-j$(nproc)` exactly; do not substitute an arbitrary fixed value
|
||||||
such as `-j4`. Use the platform-equivalent processor-count command where `nproc` is unavailable.
|
such as `-j4`. Use the platform-equivalent processor-count command where `nproc` is unavailable.
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ declarations and unqualified eepp type names, such as `UISplitter`, over repeate
|
|||||||
qualified names such as `EE::UI::UISplitter`. Keep explicit qualification only where it is required
|
qualified names such as `EE::UI::UISplitter`. Keep explicit qualification only where it is required
|
||||||
to resolve ambiguity or avoid importing an unusually broad namespace into an unsuitable scope.
|
to resolve ambiguity or avoid importing an unusually broad namespace into an unsuitable scope.
|
||||||
|
|
||||||
|
## Function Spacing
|
||||||
|
|
||||||
|
Always leave one blank line between consecutive function declarations in headers and between
|
||||||
|
consecutive function definitions in implementation files. This is a project-wide stylistic
|
||||||
|
constraint, including short inline functions.
|
||||||
|
|
||||||
## Control-Statement Braces
|
## Control-Statement Braces
|
||||||
|
|
||||||
Use braces around the body of an `if`, `else`, `for`, `while`, or similar control statement whenever
|
Use braces around the body of an `if`, `else`, `for`, `while`, or similar control statement whenever
|
||||||
|
|||||||
@@ -407,8 +407,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
void addKeyBindsString( const std::map<std::string, std::string>& binds,
|
void addKeyBindsString( const std::map<std::string, std::string>& binds,
|
||||||
const bool& allowLocked = false );
|
const bool& allowLocked = false );
|
||||||
|
|
||||||
void addKeyBinds( const KeyBindings::ShortcutMap& binds,
|
void addKeyBinds( const KeyBindings::ShortcutMap& binds, const bool& allowLocked = false );
|
||||||
const bool& allowLocked = false );
|
|
||||||
|
|
||||||
const bool& getHighlightCurrentLine() const;
|
const bool& getHighlightCurrentLine() const;
|
||||||
|
|
||||||
@@ -907,6 +906,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
|
|||||||
std::shared_ptr<Doc::TextDocument> mDoc;
|
std::shared_ptr<Doc::TextDocument> mDoc;
|
||||||
MainThreadLifetime<UICodeEditor> mAsyncLifetime;
|
MainThreadLifetime<UICodeEditor> mAsyncLifetime;
|
||||||
bool mDirtyEditor{ false };
|
bool mDirtyEditor{ false };
|
||||||
|
bool mAutoRegisterBaseCommands{ true };
|
||||||
bool mDirtyScroll{ false };
|
bool mDirtyScroll{ false };
|
||||||
bool mCursorVisible{ false };
|
bool mCursorVisible{ false };
|
||||||
bool mMouseDown{ false };
|
bool mMouseDown{ false };
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
|
|||||||
mFont( getUISceneNode()->getResourceScope()->findFont( "monospace" ).get() ),
|
mFont( getUISceneNode()->getResourceScope()->findFont( "monospace" ).get() ),
|
||||||
mDoc( std::make_shared<TextDocument>() ),
|
mDoc( std::make_shared<TextDocument>() ),
|
||||||
mAsyncLifetime( this, this ),
|
mAsyncLifetime( this, this ),
|
||||||
|
mAutoRegisterBaseCommands( autoRegisterBaseCommands ),
|
||||||
mDocView( mDoc, mFontStyleConfig,
|
mDocView( mDoc, mFontStyleConfig,
|
||||||
{ .textHints = TextHints::NoKerning, .tabStops = mTabStops } ),
|
{ .textHints = TextHints::NoKerning, .tabStops = mTabStops } ),
|
||||||
mBlinkTime( Seconds( 0.5f ) ),
|
mBlinkTime( Seconds( 0.5f ) ),
|
||||||
@@ -214,7 +215,7 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
|
|||||||
mDoc->registerClient( this );
|
mDoc->registerClient( this );
|
||||||
subscribeScheduledUpdate();
|
subscribeScheduledUpdate();
|
||||||
|
|
||||||
if ( autoRegisterBaseCommands )
|
if ( mAutoRegisterBaseCommands )
|
||||||
registerCommands();
|
registerCommands();
|
||||||
if ( autoRegisterBaseKeybindings )
|
if ( autoRegisterBaseKeybindings )
|
||||||
registerKeybindings();
|
registerKeybindings();
|
||||||
@@ -1066,6 +1067,8 @@ void UICodeEditor::setDocument( std::shared_ptr<TextDocument> doc ) {
|
|||||||
if ( clientsOfTypeCount == 1 || useCount == 1 )
|
if ( clientsOfTypeCount == 1 || useCount == 1 )
|
||||||
onDocumentClosed( mDoc.get() );
|
onDocumentClosed( mDoc.get() );
|
||||||
mDoc = doc;
|
mDoc = doc;
|
||||||
|
if ( mAutoRegisterBaseCommands )
|
||||||
|
mDoc->setSharedRefCommands( getDefaultEditorCommands() );
|
||||||
mDoc->registerClient( this );
|
mDoc->registerClient( this );
|
||||||
mDocView.setDocument( doc );
|
mDocView.setDocument( doc );
|
||||||
onDocumentChanged( oldDocURI );
|
onDocumentChanged( oldDocURI );
|
||||||
@@ -2909,8 +2912,7 @@ void UICodeEditor::addKeyBindsString( const std::map<std::string, std::string>&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UICodeEditor::addKeyBinds( const KeyBindings::ShortcutMap& binds,
|
void UICodeEditor::addKeyBinds( const KeyBindings::ShortcutMap& binds, const bool& allowLocked ) {
|
||||||
const bool& allowLocked ) {
|
|
||||||
mKeyBindings.addKeybinds( binds );
|
mKeyBindings.addKeybinds( binds );
|
||||||
for ( const auto& bind : binds ) {
|
for ( const auto& bind : binds ) {
|
||||||
if ( allowLocked ) {
|
if ( allowLocked ) {
|
||||||
|
|||||||
@@ -101,6 +101,17 @@ UTEST( GitConflict, KeepsMergeOperationAfterAllConflictsAreStaged ) {
|
|||||||
EXPECT_TRUE( state.error.empty() );
|
EXPECT_TRUE( state.error.empty() );
|
||||||
EXPECT_FALSE( state.hasConflicts() );
|
EXPECT_FALSE( state.hasConflicts() );
|
||||||
EXPECT_EQ( Git::GitOperation::Merge, state.operation );
|
EXPECT_EQ( Git::GitOperation::Merge, state.operation );
|
||||||
|
|
||||||
|
ASSERT_EQ( EXIT_SUCCESS, run( { "merge", "--abort" } ) );
|
||||||
|
EXPECT_NE( EXIT_SUCCESS, run( { "merge", "incoming" } ) );
|
||||||
|
state = git.conflictState( temp.path.string(), false );
|
||||||
|
EXPECT_TRUE( state.hasConflicts() );
|
||||||
|
ASSERT_EQ( EXIT_SUCCESS, git.restoreHead( { "conflict.txt" }, temp.path.string() ).returnCode );
|
||||||
|
state = git.conflictState( temp.path.string(), false );
|
||||||
|
EXPECT_FALSE( state.hasConflicts() );
|
||||||
|
std::string restored;
|
||||||
|
ASSERT_TRUE( FileSystem::fileGet( file, restored ) );
|
||||||
|
EXPECT_TRUE( restored == "current\n" );
|
||||||
}
|
}
|
||||||
|
|
||||||
UTEST( GitHistory, PaginatesFirstParentWithoutDuplicates ) {
|
UTEST( GitHistory, PaginatesFirstParentWithoutDuplicates ) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||||
#include <eepp/ui/tools/uimergeview.hpp>
|
#include <eepp/ui/tools/uimergeview.hpp>
|
||||||
#include <eepp/ui/uiapplication.hpp>
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
|
#include <eepp/window/clipboard.hpp>
|
||||||
|
|
||||||
using namespace EE;
|
using namespace EE;
|
||||||
using namespace EE::UI;
|
using namespace EE::UI;
|
||||||
@@ -80,6 +81,10 @@ UTEST( UIMergeView, UsesSharedResultDocumentAndAcceptIsUndoable ) {
|
|||||||
EXPECT_TRUE( view->getRightEditor()->getVerticalScrollBarEnabled() );
|
EXPECT_TRUE( view->getRightEditor()->getVerticalScrollBarEnabled() );
|
||||||
EXPECT_TRUE( view->isToolbarVisible() );
|
EXPECT_TRUE( view->isToolbarVisible() );
|
||||||
EXPECT_TRUE( view->hasCommand( "merge-accept-left" ) );
|
EXPECT_TRUE( view->hasCommand( "merge-accept-left" ) );
|
||||||
|
auto* leftEditor = view->getLeftEditor();
|
||||||
|
leftEditor->getDocument().setSelection( { { 0, 0 }, { 0, 4 } } );
|
||||||
|
leftEditor->getDocument().execute( "copy", leftEditor );
|
||||||
|
EXPECT_STREQ( "left", app.getWindow()->getClipboard()->getText().c_str() );
|
||||||
view->setToolbarVisible( false );
|
view->setToolbarVisible( false );
|
||||||
EXPECT_FALSE( view->isToolbarVisible() );
|
EXPECT_FALSE( view->isToolbarVisible() );
|
||||||
view->setToolbarVisible( true );
|
view->setToolbarVisible( true );
|
||||||
|
|||||||
@@ -862,6 +862,15 @@ Git::Result Git::restore( const std::string& file, const std::string& projectDir
|
|||||||
return gitSimple( String::format( "restore \"%s\"", file ), projectDir );
|
return gitSimple( String::format( "restore \"%s\"", file ), projectDir );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Git::Result Git::restoreHead( const std::vector<std::string>& files,
|
||||||
|
const std::string& projectDir ) {
|
||||||
|
Result result;
|
||||||
|
std::vector<std::string> args{ "restore", "--source=HEAD", "--staged", "--worktree", "--" };
|
||||||
|
args.insert( args.end(), files.begin(), files.end() );
|
||||||
|
result.returnCode = git( args, projectDir, result.result );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Git::Result Git::reset( std::vector<std::string> files, const std::string& projectDir ) {
|
Git::Result Git::reset( std::vector<std::string> files, const std::string& projectDir ) {
|
||||||
return gitSimple( String::format( "reset -q HEAD -- %s", asList( files ) ), projectDir );
|
return gitSimple( String::format( "reset -q HEAD -- %s", asList( files ) ), projectDir );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -359,6 +359,7 @@ class Git {
|
|||||||
Result restore( std::vector<std::string> files, const std::string& projectDir = "" );
|
Result restore( std::vector<std::string> files, const std::string& projectDir = "" );
|
||||||
|
|
||||||
Result restore( const std::string& file, const std::string& projectDir = "" );
|
Result restore( const std::string& file, const std::string& projectDir = "" );
|
||||||
|
Result restoreHead( const std::vector<std::string>& files, const std::string& projectDir = "" );
|
||||||
|
|
||||||
Result reset( std::vector<std::string> files, const std::string& projectDir = "" );
|
Result reset( std::vector<std::string> files, const std::string& projectDir = "" );
|
||||||
|
|
||||||
|
|||||||
@@ -1488,6 +1488,9 @@ void GitPlugin::runFileOperation( std::vector<std::string> files, FileOperation
|
|||||||
case FileOperation::Discard:
|
case FileOperation::Discard:
|
||||||
result = mGit->restore( paths, repoPath );
|
result = mGit->restore( paths, repoPath );
|
||||||
break;
|
break;
|
||||||
|
case FileOperation::RestoreHead:
|
||||||
|
result = mGit->restoreHead( paths, repoPath );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if ( result.fail() )
|
if ( result.fail() )
|
||||||
return result;
|
return result;
|
||||||
@@ -1513,6 +1516,27 @@ void GitPlugin::discard( const std::vector<std::string>& files ) {
|
|||||||
msgBox->showWhenReady();
|
msgBox->showWhenReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GitPlugin::discardConflicts( const std::vector<std::string>& files ) {
|
||||||
|
if ( files.empty() )
|
||||||
|
return;
|
||||||
|
String message =
|
||||||
|
files.size() == 1
|
||||||
|
? String::fromUtf8( String::format(
|
||||||
|
i18n( "git_confirm_discard_changes",
|
||||||
|
"Are you sure you want to discard the changes in file: \"%s\"?" )
|
||||||
|
.toUtf8(),
|
||||||
|
files.front() ) )
|
||||||
|
: i18n( "git_confirm_discard_changes",
|
||||||
|
"Are you sure you want to discard all file changes?" );
|
||||||
|
UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, message );
|
||||||
|
msgBox->on( Event::OnConfirm,
|
||||||
|
[this, files]( auto ) { runFileOperation( files, FileOperation::RestoreHead ); } );
|
||||||
|
msgBox->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } );
|
||||||
|
msgBox->setTitle( i18n( "git_confirm", "Confirm" ) );
|
||||||
|
msgBox->center();
|
||||||
|
msgBox->showWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
void GitPlugin::discard( const std::string& file ) {
|
void GitPlugin::discard( const std::string& file ) {
|
||||||
UIMessageBox* msgBox = UIMessageBox::New(
|
UIMessageBox* msgBox = UIMessageBox::New(
|
||||||
UIMessageBox::OK_CANCEL,
|
UIMessageBox::OK_CANCEL,
|
||||||
@@ -3751,7 +3775,8 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
auto type = status->type;
|
auto type = status->type;
|
||||||
if ( type == Git::GitStatusType::Staged ||
|
if ( type == Git::GitStatusType::Staged ||
|
||||||
type == Git::GitStatusType::Untracked ||
|
type == Git::GitStatusType::Untracked ||
|
||||||
type == Git::GitStatusType::Changed ) {
|
type == Git::GitStatusType::Changed ||
|
||||||
|
type == Git::GitStatusType::Unmerged ) {
|
||||||
std::string repoPath;
|
std::string repoPath;
|
||||||
if ( !status->files.empty() )
|
if ( !status->files.empty() )
|
||||||
repoPath = mGit->repoPath( status->files.front().file );
|
repoPath = mGit->repoPath( status->files.front().file );
|
||||||
@@ -3780,6 +3805,9 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
menuAdd( menu, "git-discard-all",
|
menuAdd( menu, "git-discard-all",
|
||||||
i18n( "git_discard_all", "Discard All" ) );
|
i18n( "git_discard_all", "Discard All" ) );
|
||||||
}
|
}
|
||||||
|
if ( type == Git::GitStatusType::Unmerged )
|
||||||
|
menuAdd( menu, "git-discard-all",
|
||||||
|
i18n( "git_discard_all", "Discard All" ) );
|
||||||
|
|
||||||
menu->on( Event::OnItemClicked, [this, modelShared, repoPath,
|
menu->on( Event::OnItemClicked, [this, modelShared, repoPath,
|
||||||
type]( const Event* event ) {
|
type]( const Event* event ) {
|
||||||
@@ -3797,8 +3825,12 @@ void GitPlugin::buildSidePanelTab() {
|
|||||||
unstage( model->getFiles( repoFullName( repoPath ),
|
unstage( model->getFiles( repoFullName( repoPath ),
|
||||||
(Uint32)Git::GitStatusType::Staged ) );
|
(Uint32)Git::GitStatusType::Staged ) );
|
||||||
} else if ( id == "git-discard-all" ) {
|
} else if ( id == "git-discard-all" ) {
|
||||||
discard( model->getFiles( repoFullName( repoPath ),
|
auto discardFiles = model->getFiles( repoFullName( repoPath ),
|
||||||
(Uint32)Git::GitStatusType::Changed ) );
|
static_cast<Uint32>( type ) );
|
||||||
|
if ( type == Git::GitStatusType::Unmerged )
|
||||||
|
discardConflicts( discardFiles );
|
||||||
|
else
|
||||||
|
discard( discardFiles );
|
||||||
} else if ( id == "git-diff-staged" ) {
|
} else if ( id == "git-diff-staged" ) {
|
||||||
diff( Git::DiffMode::DiffStaged, repoPath );
|
diff( Git::DiffMode::DiffStaged, repoPath );
|
||||||
} else if ( id == "git-diff-changed" ) {
|
} else if ( id == "git-diff-changed" ) {
|
||||||
@@ -4001,19 +4033,31 @@ void GitPlugin::openFileStatusMenu( std::vector<Git::DiffFile> files ) {
|
|||||||
hasUnstaged |= file.report.type != Git::GitStatusType::Staged;
|
hasUnstaged |= file.report.type != Git::GitStatusType::Staged;
|
||||||
hasUnmerged |= file.report.type == Git::GitStatusType::Unmerged;
|
hasUnmerged |= file.report.type == Git::GitStatusType::Unmerged;
|
||||||
}
|
}
|
||||||
if ( hasUnmerged && !multiple ) {
|
if ( hasUnmerged ) {
|
||||||
menuAdd( menu, "git-resolve-conflict", i18n( "git_resolve_conflict", "Resolve Conflict" ),
|
if ( !multiple ) {
|
||||||
"diff-modified" );
|
menuAdd( menu, "git-resolve-conflict",
|
||||||
menuAdd( menu, "git-accept-ours", i18n( "git_accept_ours", "Accept Ours" ) );
|
i18n( "git_resolve_conflict", "Resolve Conflict" ), "diff-modified" );
|
||||||
menuAdd( menu, "git-accept-theirs", i18n( "git_accept_theirs", "Accept Theirs" ) );
|
menuAdd( menu, "git-accept-ours", i18n( "git_accept_ours", "Accept Ours" ) );
|
||||||
menu->on( Event::OnItemClicked, [this, file = files.front()]( const Event* event ) {
|
menuAdd( menu, "git-accept-theirs", i18n( "git_accept_theirs", "Accept Theirs" ) );
|
||||||
|
}
|
||||||
|
menu->addSeparator();
|
||||||
|
menuAdd( menu, "git-discard", i18n( "git_discard", "Discard" ) );
|
||||||
|
menu->on( Event::OnItemClicked, [this, files = std::move( files )]( const Event* event ) {
|
||||||
const std::string id = event->getNode()->asType<UIMenuItem>()->getId();
|
const std::string id = event->getNode()->asType<UIMenuItem>()->getId();
|
||||||
if ( id == "git-resolve-conflict" )
|
if ( id == "git-resolve-conflict" )
|
||||||
openConflictResolver( file.file );
|
openConflictResolver( files.front().file );
|
||||||
else if ( id == "git-accept-ours" )
|
else if ( id == "git-accept-ours" )
|
||||||
acceptConflictSide( file.file, true );
|
acceptConflictSide( files.front().file, true );
|
||||||
else if ( id == "git-accept-theirs" )
|
else if ( id == "git-accept-theirs" )
|
||||||
acceptConflictSide( file.file, false );
|
acceptConflictSide( files.front().file, false );
|
||||||
|
else if ( id == "git-discard" ) {
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
paths.reserve( files.size() );
|
||||||
|
for ( const auto& file : files )
|
||||||
|
if ( file.report.type == Git::GitStatusType::Unmerged )
|
||||||
|
paths.emplace_back( file.file );
|
||||||
|
discardConflicts( paths );
|
||||||
|
}
|
||||||
} );
|
} );
|
||||||
menu->showOverMouseCursor();
|
menu->showOverMouseCursor();
|
||||||
return;
|
return;
|
||||||
@@ -4035,7 +4079,10 @@ void GitPlugin::openFileStatusMenu( std::vector<Git::DiffFile> files ) {
|
|||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
if ( hasUnstaged )
|
const bool hasDiscardable = std::any_of( files.begin(), files.end(), []( const auto& file ) {
|
||||||
|
return file.report.type == Git::GitStatusType::Changed;
|
||||||
|
} );
|
||||||
|
if ( hasDiscardable )
|
||||||
menuAdd( menu, "git-discard", i18n( "git_discard", "Discard" ) );
|
menuAdd( menu, "git-discard", i18n( "git_discard", "Discard" ) );
|
||||||
|
|
||||||
menu->on( Event::OnItemClicked,
|
menu->on( Event::OnItemClicked,
|
||||||
@@ -4058,7 +4105,7 @@ void GitPlugin::openFileStatusMenu( std::vector<Git::DiffFile> files ) {
|
|||||||
unstage( paths );
|
unstage( paths );
|
||||||
} else if ( id == "git-discard" ) {
|
} else if ( id == "git-discard" ) {
|
||||||
for ( const auto& file : files )
|
for ( const auto& file : files )
|
||||||
if ( file.report.type != Git::GitStatusType::Staged )
|
if ( file.report.type == Git::GitStatusType::Changed )
|
||||||
paths.emplace_back( file.file );
|
paths.emplace_back( file.file );
|
||||||
if ( paths.size() == 1 )
|
if ( paths.size() == 1 )
|
||||||
discard( paths.front() );
|
discard( paths.front() );
|
||||||
|
|||||||
@@ -323,11 +323,12 @@ class GitPlugin : public PluginBase {
|
|||||||
|
|
||||||
void unstage( const std::vector<std::string>& files );
|
void unstage( const std::vector<std::string>& files );
|
||||||
|
|
||||||
enum class FileOperation { Stage, Unstage, Discard };
|
enum class FileOperation { Stage, Unstage, Discard, RestoreHead };
|
||||||
|
|
||||||
void runFileOperation( std::vector<std::string> files, FileOperation operation );
|
void runFileOperation( std::vector<std::string> files, FileOperation operation );
|
||||||
|
|
||||||
void discard( const std::vector<std::string>& files );
|
void discard( const std::vector<std::string>& files );
|
||||||
|
void discardConflicts( const std::vector<std::string>& files );
|
||||||
|
|
||||||
void discard( const std::string& file );
|
void discard( const std::string& file );
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user