mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-18 11:33:23 +03:00
471 lines
14 KiB
C++
471 lines
14 KiB
C++
#ifndef ECODE_GITPLUGIN_HPP
|
|
#define ECODE_GITPLUGIN_HPP
|
|
|
|
#include "../plugin.hpp"
|
|
#include "../pluginmanager.hpp"
|
|
#include "git.hpp"
|
|
#include "githistorymodel.hpp"
|
|
#include <eepp/scene/eventconnection.hpp>
|
|
#include <eepp/scene/mainthreadlifetime.hpp>
|
|
#include <eepp/ui/models/model.hpp>
|
|
#include <eepp/ui/tools/uidiffview.hpp>
|
|
#include <eepp/ui/tools/uimergeview.hpp>
|
|
#include <eepp/ui/uilinearlayout.hpp>
|
|
#include <optional>
|
|
|
|
using namespace EE::UI::Models;
|
|
using namespace EE::UI;
|
|
using namespace EE::Scene;
|
|
|
|
namespace EE::UI {
|
|
class UITreeView;
|
|
class UIDropDownList;
|
|
class UIDropDownModelList;
|
|
class UIStackWidget;
|
|
class UIListBoxItem;
|
|
class UIMenu;
|
|
class UITextView;
|
|
class UISplitter;
|
|
} // namespace EE::UI
|
|
|
|
namespace ecode {
|
|
|
|
class Git;
|
|
class GitBranchModel;
|
|
|
|
class GitHistoryRefModel : public Model {
|
|
public:
|
|
explicit GitHistoryRefModel( std::shared_ptr<GitBranchModel> source, String headLabel );
|
|
|
|
size_t rowCount( const ModelIndex& = {} ) const override { return mSourceIndexes.size() + 1; }
|
|
|
|
size_t columnCount( const ModelIndex& = {} ) const override { return 1; }
|
|
|
|
ModelIndex index( int row, int column, const ModelIndex& parent = {} ) const override;
|
|
|
|
Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const override;
|
|
|
|
std::string revision( size_t index ) const;
|
|
|
|
bool isRevision( size_t index, std::string_view revision ) const;
|
|
|
|
private:
|
|
std::shared_ptr<GitBranchModel> mSource;
|
|
std::vector<ModelIndex> mSourceIndexes;
|
|
String mHeadLabel;
|
|
};
|
|
|
|
static constexpr const char* GIT_EMPTY = "";
|
|
static constexpr const char* GIT_SUCCESS = "success";
|
|
static constexpr const char* GIT_ERROR = "error";
|
|
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";
|
|
static constexpr const char* GIT_STASH_TOOLTIP_CLASS = "git-stash-tooltip";
|
|
|
|
class GitPlugin : public PluginBase {
|
|
public:
|
|
static PluginDefinition Definition() {
|
|
return { "git", "Git", "Git integration", GitPlugin::New, { 0, 2, 0 }, GitPlugin::NewSync };
|
|
}
|
|
|
|
static Plugin* New( PluginManager* pluginManager );
|
|
|
|
static Plugin* NewSync( PluginManager* pluginManager );
|
|
|
|
virtual ~GitPlugin();
|
|
|
|
std::string getId() override { return Definition().id; }
|
|
|
|
std::string getTitle() override { return Definition().name; }
|
|
|
|
std::string getDescription() override { return Definition().description; }
|
|
|
|
bool hasSettingsPage() const override { return true; }
|
|
|
|
void registerSettings( SettingsPage& page ) override;
|
|
|
|
void onSaveState( IniFile* state ) override;
|
|
|
|
void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override;
|
|
|
|
FileSystemListenerOptions getFileSystemListenerOptions() const override;
|
|
|
|
void onRegister( UICodeEditor* ) override;
|
|
|
|
void onUnregister( UICodeEditor* ) override;
|
|
|
|
bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, const Vector2i& position,
|
|
const Uint32& flags ) override;
|
|
|
|
bool onKeyDown( UICodeEditor*, const KeyEvent& ) override;
|
|
|
|
bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) override;
|
|
|
|
std::string gitBranch();
|
|
|
|
std::string statusTypeToString( Git::GitStatusType type );
|
|
|
|
std::vector<std::string> repos();
|
|
|
|
std::unordered_map<std::string, std::string> updateReposBranches();
|
|
|
|
void updateRepos();
|
|
|
|
bool isSilent() const { return mSilent; }
|
|
|
|
protected:
|
|
MainThreadLifetime<GitPlugin> mLifetime;
|
|
|
|
std::shared_ptr<Git> mGit;
|
|
std::unordered_map<std::string, std::string> mGitBranches;
|
|
Git::Status mGitStatus;
|
|
std::vector<std::pair<std::string, std::string>> mRepos;
|
|
UnorderedSet<std::string> mGitStatusFilesCache;
|
|
std::string mProjectPath;
|
|
std::string mRepoSelected;
|
|
std::string mHighlightStyleColor;
|
|
|
|
Time mRefreshFreq{ Seconds( 5 ) };
|
|
bool mGitFound{ false };
|
|
bool mTooltipInfoShowing{ false };
|
|
bool mStatusBarDisplayBranch{ true };
|
|
bool mStatusBarDisplayModifications{ true };
|
|
bool mStatusRecurseSubmodules{ true };
|
|
bool mFileTreeHighlightChanges{ true };
|
|
bool mOldDontAutoHideOnMouseMove{ false };
|
|
bool mOldUsingCustomStyling{ false };
|
|
bool mInitialized{ false };
|
|
bool mSilent{ true };
|
|
Uint32 mOldTextStyle{ 0 };
|
|
Uint32 mOldTextAlign{ 0 };
|
|
Color mOldBackgroundColor;
|
|
UITabWidget* mSidePanel{ nullptr };
|
|
UITab* mTab{ nullptr };
|
|
UIWidget* mTabContents{ nullptr };
|
|
|
|
UILinearLayout* mStatusBar{ nullptr };
|
|
UIPushButton* mStatusButton{ nullptr };
|
|
UITreeView* mBranchesTree{ nullptr };
|
|
UITreeView* mStatusTree{ nullptr };
|
|
UITreeView* mHistoryTree{ nullptr };
|
|
UIDropDownModelList* mHistoryRefDropDown{ nullptr };
|
|
std::shared_ptr<GitHistoryRefModel> mHistoryRefModel;
|
|
std::shared_ptr<GitHistoryModel> mHistoryModel;
|
|
UIDropDownList* mPanelSwicher{ nullptr };
|
|
UIDropDownList* mRepoDropDown{ nullptr };
|
|
UIStackWidget* mStackWidget{ nullptr };
|
|
SmallVector<UIWidget*, 4> mStackMap;
|
|
UIWidget* mGitContentView{ nullptr };
|
|
UIWidget* mGitNoContentView{ nullptr };
|
|
UIWidget* mConflictStateBar{ nullptr };
|
|
UITextView* mConflictStateText{ nullptr };
|
|
UILoader* mLoader{ nullptr };
|
|
std::atomic<int> mRunningUpdateBranches{ 0 };
|
|
std::atomic<int> mRunningHistoryRequests{ 0 };
|
|
std::atomic<Uint64> mHistoryGeneration{ 0 };
|
|
std::string mHistoryRepo;
|
|
std::string mHistoryRevision{ "HEAD" };
|
|
bool mUpdatingHistoryRefs{ false };
|
|
bool mHistoryLoaded{ false };
|
|
struct CommitDetailsState {
|
|
UIWidget* view{ nullptr };
|
|
UITextView* subject{ nullptr };
|
|
UITextView* author{ nullptr };
|
|
UITextView* dateEmail{ nullptr };
|
|
UITextView* message{ nullptr };
|
|
UITextView* status{ nullptr };
|
|
UIPushButton* messageToggle{ nullptr };
|
|
UIPushButton* filesToggle{ nullptr };
|
|
UIPushButton* modeToggle{ nullptr };
|
|
UIPushButton* gitHub{ nullptr };
|
|
UIWidget* diffContainer{ nullptr };
|
|
UIScrollView* diff{ nullptr };
|
|
std::string messageBody;
|
|
std::string url;
|
|
Git::Commit commit;
|
|
std::string repo;
|
|
std::atomic<Uint64> generation{ 0 };
|
|
std::shared_ptr<std::atomic_bool> diffPreparationCancelled;
|
|
EventConnection closeConnection;
|
|
Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified };
|
|
bool messageExpanded{ false };
|
|
bool filesCollapsed{ false };
|
|
bool workingTree{ false };
|
|
|
|
void openCommitDetails( GitPlugin& plugin, const Git::Commit& commit, bool detached,
|
|
bool workingTree = false );
|
|
|
|
void loadCommitFiles( GitPlugin& plugin, bool detached );
|
|
|
|
void cancelDiffPreparation() {
|
|
if ( diffPreparationCancelled )
|
|
diffPreparationCancelled->store( true, std::memory_order_relaxed );
|
|
diffPreparationCancelled.reset();
|
|
}
|
|
|
|
void reset() {
|
|
cancelDiffPreparation();
|
|
++generation;
|
|
view = nullptr;
|
|
subject = nullptr;
|
|
author = nullptr;
|
|
dateEmail = nullptr;
|
|
message = nullptr;
|
|
status = nullptr;
|
|
messageToggle = nullptr;
|
|
filesToggle = nullptr;
|
|
modeToggle = nullptr;
|
|
gitHub = nullptr;
|
|
diffContainer = nullptr;
|
|
diff = nullptr;
|
|
messageBody.clear();
|
|
url.clear();
|
|
commit = {};
|
|
repo.clear();
|
|
viewMode = Tools::UIDiffView::ViewMode::Unified;
|
|
messageExpanded = false;
|
|
filesCollapsed = false;
|
|
workingTree = false;
|
|
}
|
|
};
|
|
struct DetachedHistoryState {
|
|
UISplitter* view{ nullptr };
|
|
UITreeView* tree{ nullptr };
|
|
UIWidget* detailsHost{ nullptr };
|
|
EventConnection closeConnection;
|
|
std::string focusHash;
|
|
CommitDetailsState details;
|
|
|
|
void reset() {
|
|
view = nullptr;
|
|
tree = nullptr;
|
|
detailsHost = nullptr;
|
|
focusHash.clear();
|
|
details.reset();
|
|
}
|
|
};
|
|
CommitDetailsState mCommitDetails;
|
|
DetachedHistoryState mDetachedHistory;
|
|
std::atomic<int> mRunningUpdateStatus{ 0 };
|
|
std::atomic<bool> mPendingForcedStatusUpdate{ false };
|
|
std::shared_ptr<std::atomic<int>> mRunningAsyncTasks{ std::make_shared<std::atomic<int>>( 0 ) };
|
|
Clock mLastBranchesUpdate;
|
|
Mutex mGitBranchMutex;
|
|
Mutex mGitStatusMutex;
|
|
Mutex mGitStatusFileCacheMutex;
|
|
Mutex mRepoMutex;
|
|
Mutex mReposMutex;
|
|
String mLastCommitMsg;
|
|
struct GitConflictSession {
|
|
std::string repoPath;
|
|
std::vector<std::string> files;
|
|
size_t currentFile{ 0 };
|
|
Uint64 generation{ 0 };
|
|
Git::GitOperation operation{ Git::GitOperation::None };
|
|
};
|
|
UnorderedMap<std::string, std::unique_ptr<GitConflictSession>> mConflictSessions;
|
|
std::string mActiveConflictRepo;
|
|
Tools::UIMergeView* mConflictView{ nullptr };
|
|
EventConnection mConflictViewCloseConnection;
|
|
Uint64 mConflictGeneration{ 0 };
|
|
Uint32 mRepositionCbId{ 0 };
|
|
|
|
struct CustomTokenizer {
|
|
SyntaxDefinition def;
|
|
SyntaxColorScheme scheme;
|
|
};
|
|
std::optional<CustomTokenizer> mStatusCustomTokenizer;
|
|
std::optional<CustomTokenizer> mCommitStatusCustomTokenizer;
|
|
std::optional<SyntaxDefinition> mTooltipCustomSyntaxDef;
|
|
Uint32 mModelChangedId{ 0 };
|
|
Uint32 mModelStylerId{ 0 };
|
|
|
|
GitPlugin( PluginManager* pluginManager, bool sync );
|
|
|
|
void load( PluginManager* pluginManager );
|
|
|
|
PluginRequestHandle processMessage( const PluginMessage& msg );
|
|
|
|
void displayTooltip( UICodeEditor* editor, const Git::Blame& blame, const Vector2f& position );
|
|
|
|
void hideTooltip( UICodeEditor* editor );
|
|
|
|
void onRegisterListeners( UICodeEditor*, std::vector<Uint32>& listeners ) override;
|
|
|
|
Color getVarColor( const std::string& var );
|
|
|
|
void blame( UICodeEditor* editor );
|
|
|
|
void checkout( Git::Branch branch );
|
|
|
|
void branchRename( Git::Branch branch );
|
|
|
|
void branchDelete( Git::Branch branch );
|
|
|
|
void branchMerge( Git::Branch branch );
|
|
|
|
void fastForwardMerge( Git::Branch branch );
|
|
|
|
void pull( const std::string& repoPath );
|
|
|
|
void push( const std::string& repoPath );
|
|
|
|
void fetch( const std::string& repoPath );
|
|
|
|
void branchCreate();
|
|
|
|
void commit( const std::string& repoPath, bool mergeCommit = false );
|
|
|
|
void stage( const std::vector<std::string>& files );
|
|
|
|
void unstage( const std::vector<std::string>& files );
|
|
|
|
enum class FileOperation { Stage, Unstage, Discard, RestoreHead };
|
|
|
|
void runFileOperation( std::vector<std::string> files, FileOperation operation );
|
|
|
|
void discard( const std::vector<std::string>& files );
|
|
void discardConflicts( const std::vector<std::string>& files );
|
|
|
|
void discard( const std::string& file );
|
|
|
|
void diff( const Git::DiffMode mode, const std::string& repoPath );
|
|
|
|
void diff( const std::string& file, Git::GitStatusType status );
|
|
|
|
void diff( std::vector<Git::DiffFile> files );
|
|
|
|
void openFile( const std::string& file );
|
|
|
|
void openConflictResolver( const std::string& file );
|
|
|
|
void loadConflictResolverView( std::shared_ptr<Doc::TextDocument> resultDocument,
|
|
Git::ConflictFile conflict, std::string repository,
|
|
std::vector<std::string> files, size_t currentFile,
|
|
Git::GitOperation operation, Uint64 generation );
|
|
|
|
void recreateConflict();
|
|
|
|
void saveAndStageConflict();
|
|
|
|
void openAdjacentConflict( bool next );
|
|
|
|
void continueConflictOperation();
|
|
|
|
void abortConflictOperation();
|
|
|
|
void acceptConflictSide( const std::string& file, bool stage2 );
|
|
|
|
void runAsyncTask( std::function<void()> task );
|
|
|
|
void updateStatus( bool force = false );
|
|
|
|
void updateStatusBarSync();
|
|
|
|
void styleCommitFilesStatus( UITextView* status );
|
|
|
|
void updateUI();
|
|
|
|
void updateUINow( bool force = false );
|
|
|
|
void updateBranches( bool force = false );
|
|
|
|
void ensureHistoryLoaded();
|
|
|
|
void updateHistoryHeader();
|
|
|
|
void updateHistoryRefs( const std::shared_ptr<GitBranchModel>& model );
|
|
|
|
void reloadHistory();
|
|
|
|
void invalidateHistory();
|
|
|
|
void loadHistoryPage( GitHistoryModel::Node* node, Git::HistoryQuery query, bool append );
|
|
|
|
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, 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();
|
|
|
|
void updateDetachedHistoryTitle();
|
|
|
|
void buildSidePanelTab();
|
|
|
|
void updateBranchesUI( std::shared_ptr<GitBranchModel> );
|
|
|
|
void openBranchMenu( const Git::Branch& branch );
|
|
|
|
void openFileStatusMenu( std::vector<Git::DiffFile> files );
|
|
|
|
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,
|
|
bool updateStatusOnError = false, bool historyChanged = false );
|
|
void runMergeLikeAsync( std::function<Git::Result( Git& )> fn, const std::string& repoPath );
|
|
|
|
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,
|
|
const std::string& icon = "",
|
|
const KeyBindings::Shortcut& forcedKeybinding = KeyBindings::Shortcut() );
|
|
|
|
std::string repoSelected();
|
|
|
|
std::string projectPath();
|
|
|
|
std::string repoName( const std::string& repoPath );
|
|
|
|
std::string repoPath( const std::string& repoName );
|
|
|
|
std::string repoFullName( const std::string& repoPath );
|
|
|
|
std::string fixFilePath( const std::string& file );
|
|
|
|
std::vector<std::string> fixFilePaths( const std::vector<std::string>& files );
|
|
|
|
std::optional<Git::Branch> getBranchFromRepoPath( const std::string& repoPath );
|
|
|
|
void initModelStyler();
|
|
|
|
void endModelStyler();
|
|
|
|
void hideSidePanel();
|
|
};
|
|
|
|
} // namespace ecode
|
|
|
|
#endif // ECODE_GITPLUGIN_HPP
|