diff options
-rw-r--r-- | doc/classes/EditorVCSInterface.xml | 263 | ||||
-rw-r--r-- | editor/editor_node.cpp | 6 | ||||
-rw-r--r-- | editor/editor_node.h | 1 | ||||
-rw-r--r-- | editor/editor_vcs_interface.cpp | 385 | ||||
-rw-r--r-- | editor/editor_vcs_interface.h | 141 | ||||
-rw-r--r-- | editor/icons/VcsBranches.svg | 1 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.cpp | 92 | ||||
-rw-r--r-- | editor/plugins/script_editor_plugin.h | 2 | ||||
-rw-r--r-- | editor/plugins/version_control_editor_plugin.cpp | 1626 | ||||
-rw-r--r-- | editor/plugins/version_control_editor_plugin.h | 190 |
10 files changed, 2142 insertions, 565 deletions
diff --git a/doc/classes/EditorVCSInterface.xml b/doc/classes/EditorVCSInterface.xml index 89ba79f7d9..cca6a0591f 100644 --- a/doc/classes/EditorVCSInterface.xml +++ b/doc/classes/EditorVCSInterface.xml @@ -1,97 +1,276 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="EditorVCSInterface" inherits="Object" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Version Control System (VCS) interface which reads and writes to the local VCS in use. + Version Control System (VCS) interface, which reads and writes to the local VCS in use. </brief_description> <description> - Used by the editor to display VCS extracted information in the editor. The implementation of this API is included in VCS addons, which are essentially GDExtension plugins that need to be put into the project folder. These VCS addons are scripts which are attached (on demand) to the object instance of [code]EditorVCSInterface[/code]. All the functions listed below, instead of performing the task themselves, they call the internally defined functions in the VCS addons to provide a plug-n-play experience. + Defines the API that the editor uses to extract information from the underlying VCS. The implementation of this API is included in VCS plugins, which are GDExtension plugins that inherit [EditorVCSInterface] and are attached (on demand) to the singleton instance of [EditorVCSInterface]. Instead of performing the task themselves, all the virtual functions listed below are calling the internally overridden functions in the VCS plugins to provide a plug-n-play experience. A custom VCS plugin is supposed to inherit from [EditorVCSInterface] and override each of these virtual functions. </description> <tutorials> </tutorials> <methods> - <method name="commit"> + <method name="_checkout_branch" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="branch_name" type="String" /> + <description> + Checks out a [code]branch_name[/code] in the VCS. + </description> + </method> + <method name="_commit" qualifiers="virtual"> <return type="void" /> <param index="0" name="msg" type="String" /> <description> - Creates a version commit if the addon is initialized, else returns without doing anything. Uses the files which have been staged previously, with the commit message set to a value as provided as in the argument. + Commits the currently staged changes and applies the commit [code]msg[/code] to the resulting commit. </description> </method> - <method name="get_file_diff"> - <return type="Dictionary[]" /> + <method name="_create_branch" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="branch_name" type="String" /> + <description> + Creates a new branch named [code]branch_name[/code] in the VCS. + </description> + </method> + <method name="_create_remote" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="remote_name" type="String" /> + <param index="1" name="remote_url" type="String" /> + <description> + Creates a new remote destination with name [code]remote_name[/code] and points it to [code]remote_url[/code]. This can be an HTTPS remote or an SSH remote. + </description> + </method> + <method name="_discard_file" qualifiers="virtual"> + <return type="void" /> <param index="0" name="file_path" type="String" /> <description> - Returns an [Array] of [Dictionary] objects containing the diff output from the VCS in use, if a VCS addon is initialized, else returns an empty [Array] object. The diff contents also consist of some contextual lines which provide context to the observed line change in the file. - Each [Dictionary] object has the line diff contents under the keys: - - [code]"content"[/code] to store a [String] containing the line contents - - [code]"status"[/code] to store a [String] which contains [code]"+"[/code] in case the content is a line addition but it stores a [code]"-"[/code] in case of deletion and an empty string in the case the line content is neither an addition nor a deletion. - - [code]"new_line_number"[/code] to store an integer containing the new line number of the line content. - - [code]"line_count"[/code] to store an integer containing the number of lines in the line content. - - [code]"old_line_number"[/code] to store an integer containing the old line number of the line content. - - [code]"offset"[/code] to store the offset of the line change since the first contextual line content. + Discards the changes made in a file present at [code]file_path[/code]. </description> </method> - <method name="get_modified_files_data"> - <return type="Dictionary" /> + <method name="_fetch" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="remote" type="String" /> <description> - Returns a [Dictionary] containing the path of the detected file change mapped to an integer signifying what kind of change the corresponding file has experienced. - The following integer values are being used to signify that the detected file is: - - [code]0[/code]: New to the VCS working directory - - [code]1[/code]: Modified - - [code]2[/code]: Renamed - - [code]3[/code]: Deleted - - [code]4[/code]: Typechanged + Fetches new changes from the remote, but doesn't write changes to the current working directory. Equivalent to [code]git fetch[/code]. </description> </method> - <method name="get_project_name"> + <method name="_get_branch_list" qualifiers="virtual"> + <return type="Array" /> + <description> + Gets an instance of an [Array] of [String]s containing available branch names in the VCS. + </description> + </method> + <method name="_get_current_branch_name" qualifiers="virtual"> <return type="String" /> <description> - Returns the project name of the VCS working directory. + Gets the current branch name defined in the VCS. + </description> + </method> + <method name="_get_diff" qualifiers="virtual"> + <return type="Dictionary[]" /> + <param index="0" name="identifier" type="String" /> + <param index="1" name="area" type="int" /> + <description> + Returns an array of [Dictionary] items (see [method create_diff_file], [method create_diff_hunk], [method create_diff_line], [method add_line_diffs_into_diff_hunk] and [method add_diff_hunks_into_diff_file]), each containing information about a diff. If [code]identifier[/code] is a file path, returns a file diff, and if it is a commit identifier, then returns a commit diff. + </description> + </method> + <method name="_get_line_diff" qualifiers="virtual"> + <return type="Array" /> + <param index="0" name="file_path" type="String" /> + <param index="1" name="text" type="String" /> + <description> + Returns an [Array] of [Dictionary] items (see [method create_diff_hunk]), each containing a line diff between a file at [code]file_path[/code] and the [code]text[/code] which is passed in. + </description> + </method> + <method name="_get_modified_files_data" qualifiers="virtual"> + <return type="Array" /> + <description> + Returns an [Array] of [Dictionary] items (see [method create_status_file]), each containing the status data of every modified file in the project folder. + </description> + </method> + <method name="_get_previous_commits" qualifiers="virtual"> + <return type="Array" /> + <param index="0" name="max_commits" type="int" /> + <description> + Returns an [Array] of [Dictionary] items (see [method create_commit]), each containing the data for a past commit. </description> </method> - <method name="get_vcs_name"> + <method name="_get_remotes" qualifiers="virtual"> + <return type="Array" /> + <description> + Returns an [Array] of [String]s, each containing the name of a remote configured in the VCS. + </description> + </method> + <method name="_get_vcs_name" qualifiers="virtual"> <return type="String" /> <description> - Returns the name of the VCS if the VCS has been initialized, else return an empty string. + Returns the name of the underlying VCS provider. </description> </method> - <method name="initialize"> + <method name="_initialize" qualifiers="virtual"> <return type="bool" /> - <param index="0" name="project_root_path" type="String" /> + <param index="0" name="project_path" type="String" /> <description> - Initializes the VCS addon if not already. Uses the argument value as the path to the working directory of the project. Creates the initial commit if required. Returns [code]true[/code] if no failure occurs, else returns [code]false[/code]. + Initializes the VCS plugin when called from the editor. Returns whether or not the plugin was successfully initialized. A VCS project is initialized at [code]project_path[/code]. </description> </method> - <method name="is_addon_ready"> - <return type="bool" /> + <method name="_pull" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="remote" type="String" /> <description> - Returns [code]true[/code] if the addon is ready to respond to function calls, else returns [code]false[/code]. + Pulls changes from the remote. This can give rise to merge conflicts. </description> </method> - <method name="is_vcs_initialized"> - <return type="bool" /> + <method name="_push" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="remote" type="String" /> + <param index="1" name="force" type="bool" /> + <description> + Pushes changes to the [code]remote[/code]. Optionally, if [code]force[/code] is set to true, a force push will override the change history already present on the remote. + </description> + </method> + <method name="_remove_branch" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="branch_name" type="String" /> <description> - Returns [code]true[/code] if the VCS addon has been initialized, else returns [code]false[/code]. + Remove a branch from the local VCS. </description> </method> - <method name="shut_down"> + <method name="_remove_remote" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="remote_name" type="String" /> + <description> + Remove a remote from the local VCS. + </description> + </method> + <method name="_set_credentials" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="username" type="String" /> + <param index="1" name="password" type="String" /> + <param index="2" name="ssh_public_key_path" type="String" /> + <param index="3" name="ssh_private_key_path" type="String" /> + <param index="4" name="ssh_passphrase" type="String" /> + <description> + Set user credentials in the underlying VCS. [code]username[/code] and [code]password[/code] are used only during HTTPS authentication unless not already mentioned in the remote URL. [code]ssh_public_key_path[/code], [code]ssh_private_key_path[/code], and [code]ssh_passphrase[/code] are only used during SSH authentication. + </description> + </method> + <method name="_shut_down" qualifiers="virtual"> <return type="bool" /> <description> - Shuts down the VCS addon to allow cleanup code to run on call. Returns [code]true[/code] is no failure occurs, else returns [code]false[/code]. + Shuts down VCS plugin instance. Called when the user either closes the editor or shuts down the VCS plugin through the editor UI. </description> </method> - <method name="stage_file"> + <method name="_stage_file" qualifiers="virtual"> <return type="void" /> <param index="0" name="file_path" type="String" /> <description> - Stages the file which should be committed when [method EditorVCSInterface.commit] is called. Argument should contain the absolute path. + Stages the file present at [code]file_path[/code] to the staged area. </description> </method> - <method name="unstage_file"> + <method name="_unstage_file" qualifiers="virtual"> <return type="void" /> <param index="0" name="file_path" type="String" /> <description> - Unstages the file which was staged previously to be committed, so that it is no longer committed when [method EditorVCSInterface.commit] is called. Argument should contain the absolute path. + Unstages the file present at [code]file_path[/code] from the staged area to the unstaged area. + </description> + </method> + <method name="add_diff_hunks_into_diff_file"> + <return type="Dictionary" /> + <param index="0" name="diff_file" type="Dictionary" /> + <param index="1" name="diff_hunks" type="Array" /> + <description> + Helper function to add an array of [code]diff_hunks[/code] into a [code]diff_file[/code]. + </description> + </method> + <method name="add_line_diffs_into_diff_hunk"> + <return type="Dictionary" /> + <param index="0" name="diff_hunk" type="Dictionary" /> + <param index="1" name="line_diffs" type="Array" /> + <description> + Helper function to add an array of [code]line_diffs[/code] into a [code]diff_hunk[/code]. + </description> + </method> + <method name="create_commit"> + <return type="Dictionary" /> + <param index="0" name="msg" type="String" /> + <param index="1" name="author" type="String" /> + <param index="2" name="id" type="String" /> + <param index="3" name="unix_timestamp" type="int" /> + <param index="4" name="offset_minutes" type="int" /> + <description> + Helper function to create a commit [Dictionary] item. [code]msg[/code] is the commit message of the commit. [code]author[/code] is a single human-readable string containing all the author's details, e.g. the email and name configured in the VCS. [code]id[/code] is the identifier of the commit, in whichever format your VCS may provide an identifier to commits. [code]unix_timestamp[/code] is the UTC Unix timestamp of when the commit was created. [code]offset_minutes[/code] is the timezone offset in minutes, recorded from the system timezone where the commit was created. + </description> + </method> + <method name="create_diff_file"> + <return type="Dictionary" /> + <param index="0" name="new_file" type="String" /> + <param index="1" name="old_file" type="String" /> + <description> + Helper function to create a [code]Dictionary[/code] for storing old and new diff file paths. + </description> + </method> + <method name="create_diff_hunk"> + <return type="Dictionary" /> + <param index="0" name="old_start" type="int" /> + <param index="1" name="new_start" type="int" /> + <param index="2" name="old_lines" type="int" /> + <param index="3" name="new_lines" type="int" /> + <description> + Helper function to create a [code]Dictionary[/code] for storing diff hunk data. [code]old_start[/code] is the starting line number in old file. [code]new_start[/code] is the starting line number in new file. [code]old_lines[/code] is the number of lines in the old file. [code]new_lines[/code] is the number of lines in the new file. + </description> + </method> + <method name="create_diff_line"> + <return type="Dictionary" /> + <param index="0" name="new_line_no" type="int" /> + <param index="1" name="old_line_no" type="int" /> + <param index="2" name="content" type="String" /> + <param index="3" name="status" type="String" /> + <description> + Helper function to create a [code]Dictionary[/code] for storing a line diff. [code]new_line_no[/code] is the line number in the new file (can be [code]-1[/code] if the line is deleted). [code]old_line_no[/code] is the line number in the old file (can be [code]-1[/code] if the line is added). [code]content[/code] is the diff text. [code]status[/code] is a single character string which stores the line origin. + </description> + </method> + <method name="create_status_file"> + <return type="Dictionary" /> + <param index="0" name="file_path" type="String" /> + <param index="1" name="change_type" type="int" enum="EditorVCSInterface.ChangeType" /> + <param index="2" name="area" type="int" enum="EditorVCSInterface.TreeArea" /> + <description> + Helper function to create a [code]Dictionary[/code] used by editor to read the status of a file. + </description> + </method> + <method name="popup_error"> + <return type="void" /> + <param index="0" name="msg" type="String" /> + <description> + Pops up an error message in the edior which is shown as coming from the underlying VCS. Use this to show VCS specific error messages. </description> </method> </methods> + <constants> + <constant name="CHANGE_TYPE_NEW" value="0" enum="ChangeType"> + A new file has been added. + </constant> + <constant name="CHANGE_TYPE_MODIFIED" value="1" enum="ChangeType"> + An earlier added file has been modified. + </constant> + <constant name="CHANGE_TYPE_RENAMED" value="2" enum="ChangeType"> + An earlier added file has been renamed. + </constant> + <constant name="CHANGE_TYPE_DELETED" value="3" enum="ChangeType"> + An earlier added file has been deleted. + </constant> + <constant name="CHANGE_TYPE_TYPECHANGE" value="4" enum="ChangeType"> + An earlier added file has been typechanged. + </constant> + <constant name="CHANGE_TYPE_UNMERGED" value="5" enum="ChangeType"> + A file is left unmerged. + </constant> + <constant name="TREE_AREA_COMMIT" value="0" enum="TreeArea"> + A commit is encountered from the commit area. + </constant> + <constant name="TREE_AREA_STAGED" value="1" enum="TreeArea"> + A file is encountered from the staged area. + </constant> + <constant name="TREE_AREA_UNSTAGED" value="2" enum="TreeArea"> + A file is encountered from the unstaged area. + </constant> + </constants> </class> diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index faa2eeb230..b58917aae2 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -420,9 +420,6 @@ void EditorNode::_version_control_menu_option(int p_idx) { case RUN_VCS_SETTINGS: { VersionControlEditorPlugin::get_singleton()->popup_vcs_set_up_dialog(gui_base); } break; - case RUN_VCS_SHUT_DOWN: { - VersionControlEditorPlugin::get_singleton()->shut_down(); - } break; } } @@ -6741,8 +6738,7 @@ EditorNode::EditorNode() { project_menu->add_child(vcs_actions_menu); project_menu->add_submenu_item(TTR("Version Control"), "Version Control"); vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA); - vcs_actions_menu->add_item(TTR("Set Up Version Control"), RUN_VCS_SETTINGS); - vcs_actions_menu->add_item(TTR("Shut Down Version Control"), RUN_VCS_SHUT_DOWN); + vcs_actions_menu->add_item(TTR("Version Control Settings"), RUN_VCS_SETTINGS); project_menu->add_separator(); project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT); diff --git a/editor/editor_node.h b/editor/editor_node.h index 55d448ec2a..c3b4c985cc 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -185,7 +185,6 @@ private: RUN_PROJECT_MANAGER, RUN_VCS_METADATA, RUN_VCS_SETTINGS, - RUN_VCS_SHUT_DOWN, SETTINGS_UPDATE_CONTINUOUSLY, SETTINGS_UPDATE_WHEN_CHANGED, SETTINGS_UPDATE_ALWAYS, diff --git a/editor/editor_vcs_interface.cpp b/editor/editor_vcs_interface.cpp index 53cc8d5b22..0c6c876b2f 100644 --- a/editor/editor_vcs_interface.cpp +++ b/editor/editor_vcs_interface.cpp @@ -30,132 +30,371 @@ #include "editor_vcs_interface.h" +#include "editor_node.h" + +#define UNIMPLEMENTED() ERR_PRINT(vformat("Unimplemented virtual function in EditorVCSInterface based plugin: %s", __func__)) + EditorVCSInterface *EditorVCSInterface::singleton = nullptr; -void EditorVCSInterface::_bind_methods() { - // Proxy end points that act as fallbacks to unavailability of a function in the VCS addon - ClassDB::bind_method(D_METHOD("_initialize", "project_root_path"), &EditorVCSInterface::_initialize); - ClassDB::bind_method(D_METHOD("_is_vcs_initialized"), &EditorVCSInterface::_is_vcs_initialized); - ClassDB::bind_method(D_METHOD("_get_vcs_name"), &EditorVCSInterface::_get_vcs_name); - ClassDB::bind_method(D_METHOD("_shut_down"), &EditorVCSInterface::_shut_down); - ClassDB::bind_method(D_METHOD("_get_project_name"), &EditorVCSInterface::_get_project_name); - ClassDB::bind_method(D_METHOD("_get_modified_files_data"), &EditorVCSInterface::_get_modified_files_data); - ClassDB::bind_method(D_METHOD("_commit", "msg"), &EditorVCSInterface::_commit); - ClassDB::bind_method(D_METHOD("_get_file_diff", "file_path"), &EditorVCSInterface::_get_file_diff); - ClassDB::bind_method(D_METHOD("_stage_file", "file_path"), &EditorVCSInterface::_stage_file); - ClassDB::bind_method(D_METHOD("_unstage_file", "file_path"), &EditorVCSInterface::_unstage_file); +void EditorVCSInterface::popup_error(String p_msg) { + // TRANSLATORS: %s refers to the name of a version control system (e.g. "Git"). + EditorNode::get_singleton()->show_warning(p_msg.strip_edges(), vformat(TTR("%s Error"), get_vcs_name())); +} - ClassDB::bind_method(D_METHOD("is_addon_ready"), &EditorVCSInterface::is_addon_ready); +bool EditorVCSInterface::initialize(String p_project_path) { + bool result = false; + if (!GDVIRTUAL_CALL(_initialize, p_project_path, result)) { + UNIMPLEMENTED(); + return false; + } + return result; +} - // API methods that redirect calls to the proxy end points - ClassDB::bind_method(D_METHOD("initialize", "project_root_path"), &EditorVCSInterface::initialize); - ClassDB::bind_method(D_METHOD("is_vcs_initialized"), &EditorVCSInterface::is_vcs_initialized); - ClassDB::bind_method(D_METHOD("get_modified_files_data"), &EditorVCSInterface::get_modified_files_data); - ClassDB::bind_method(D_METHOD("stage_file", "file_path"), &EditorVCSInterface::stage_file); - ClassDB::bind_method(D_METHOD("unstage_file", "file_path"), &EditorVCSInterface::unstage_file); - ClassDB::bind_method(D_METHOD("commit", "msg"), &EditorVCSInterface::commit); - ClassDB::bind_method(D_METHOD("get_file_diff", "file_path"), &EditorVCSInterface::get_file_diff); - ClassDB::bind_method(D_METHOD("shut_down"), &EditorVCSInterface::shut_down); - ClassDB::bind_method(D_METHOD("get_project_name"), &EditorVCSInterface::get_project_name); - ClassDB::bind_method(D_METHOD("get_vcs_name"), &EditorVCSInterface::get_vcs_name); +void EditorVCSInterface::set_credentials(String p_username, String p_password, String p_ssh_public_key, String p_ssh_private_key, String p_ssh_passphrase) { + if (!GDVIRTUAL_CALL(_set_credentials, p_username, p_password, p_ssh_public_key, p_ssh_private_key, p_ssh_passphrase)) { + UNIMPLEMENTED(); + } } -bool EditorVCSInterface::_initialize(String p_project_root_path) { - WARN_PRINT("Selected VCS addon does not implement an initialization function. This warning will be suppressed."); - return true; +List<String> EditorVCSInterface::get_remotes() { + Array result; + if (!GDVIRTUAL_CALL(_get_remotes, result)) { + UNIMPLEMENTED(); + return {}; + } + + List<String> remotes; + for (int i = 0; i < result.size(); i++) { + remotes.push_back(result[i]); + } + return remotes; } -bool EditorVCSInterface::_is_vcs_initialized() { - return false; +List<EditorVCSInterface::StatusFile> EditorVCSInterface::get_modified_files_data() { + Array result; + if (!GDVIRTUAL_CALL(_get_modified_files_data, result)) { + UNIMPLEMENTED(); + return {}; + } + + List<EditorVCSInterface::StatusFile> status_files; + for (int i = 0; i < result.size(); i++) { + status_files.push_back(_convert_status_file(result[i])); + } + return status_files; } -Dictionary EditorVCSInterface::_get_modified_files_data() { - return Dictionary(); +void EditorVCSInterface::stage_file(String p_file_path) { + if (!GDVIRTUAL_CALL(_stage_file, p_file_path)) { + UNIMPLEMENTED(); + } } -void EditorVCSInterface::_stage_file(String p_file_path) { +void EditorVCSInterface::unstage_file(String p_file_path) { + if (!GDVIRTUAL_CALL(_unstage_file, p_file_path)) { + UNIMPLEMENTED(); + } } -void EditorVCSInterface::_unstage_file(String p_file_path) { +void EditorVCSInterface::discard_file(String p_file_path) { + if (!GDVIRTUAL_CALL(_discard_file, p_file_path)) { + UNIMPLEMENTED(); + } } -void EditorVCSInterface::_commit(String p_msg) { +void EditorVCSInterface::commit(String p_msg) { + if (!GDVIRTUAL_CALL(_commit, p_msg)) { + UNIMPLEMENTED(); + } } -TypedArray<Dictionary> EditorVCSInterface::_get_file_diff(String p_file_path) { - return TypedArray<Dictionary>(); +List<EditorVCSInterface::DiffFile> EditorVCSInterface::get_diff(String p_identifier, TreeArea p_area) { + TypedArray<Dictionary> result; + if (!GDVIRTUAL_CALL(_get_diff, p_identifier, int(p_area), result)) { + UNIMPLEMENTED(); + return {}; + } + + List<DiffFile> diff_files; + for (int i = 0; i < result.size(); i++) { + diff_files.push_back(_convert_diff_file(result[i])); + } + return diff_files; } -bool EditorVCSInterface::_shut_down() { - return false; +List<EditorVCSInterface::Commit> EditorVCSInterface::get_previous_commits(int p_max_commits) { + Array result; + if (!GDVIRTUAL_CALL(_get_previous_commits, p_max_commits, result)) { + UNIMPLEMENTED(); + return {}; + } + + List<EditorVCSInterface::Commit> commits; + for (int i = 0; i < result.size(); i++) { + commits.push_back(_convert_commit(result[i])); + } + return commits; } -String EditorVCSInterface::_get_project_name() { - return String(); +List<String> EditorVCSInterface::get_branch_list() { + Array result; + if (!GDVIRTUAL_CALL(_get_branch_list, result)) { + UNIMPLEMENTED(); + return {}; + } + + List<String> branch_list; + for (int i = 0; i < result.size(); i++) { + branch_list.push_back(result[i]); + } + return branch_list; } -String EditorVCSInterface::_get_vcs_name() { - return ""; +void EditorVCSInterface::create_branch(String p_branch_name) { + if (!GDVIRTUAL_CALL(_create_branch, p_branch_name)) { + UNIMPLEMENTED(); + } } -bool EditorVCSInterface::initialize(String p_project_root_path) { - is_initialized = call("_initialize", p_project_root_path); - return is_initialized; +void EditorVCSInterface::create_remote(String p_remote_name, String p_remote_url) { + if (!GDVIRTUAL_CALL(_create_remote, p_remote_name, p_remote_url)) { + UNIMPLEMENTED(); + } } -bool EditorVCSInterface::is_vcs_initialized() { - return call("_is_vcs_initialized"); +void EditorVCSInterface::remove_branch(String p_branch_name) { + if (!GDVIRTUAL_CALL(_remove_branch, p_branch_name)) { + UNIMPLEMENTED(); + } } -Dictionary EditorVCSInterface::get_modified_files_data() { - return call("_get_modified_files_data"); +void EditorVCSInterface::remove_remote(String p_remote_name) { + if (!GDVIRTUAL_CALL(_remove_remote, p_remote_name)) { + UNIMPLEMENTED(); + } } -void EditorVCSInterface::stage_file(String p_file_path) { - if (is_addon_ready()) { - call("_stage_file", p_file_path); +String EditorVCSInterface::get_current_branch_name() { + String result; + if (!GDVIRTUAL_CALL(_get_current_branch_name, result)) { + UNIMPLEMENTED(); + return ""; } + return result; } -void EditorVCSInterface::unstage_file(String p_file_path) { - if (is_addon_ready()) { - call("_unstage_file", p_file_path); +bool EditorVCSInterface::checkout_branch(String p_branch_name) { + bool result = false; + if (!GDVIRTUAL_CALL(_checkout_branch, p_branch_name, result)) { + UNIMPLEMENTED(); } + return result; } -bool EditorVCSInterface::is_addon_ready() { - return is_initialized; +void EditorVCSInterface::pull(String p_remote) { + if (!GDVIRTUAL_CALL(_pull, p_remote)) { + UNIMPLEMENTED(); + } } -void EditorVCSInterface::commit(String p_msg) { - if (is_addon_ready()) { - call("_commit", p_msg); +void EditorVCSInterface::push(String p_remote, bool p_force) { + if (!GDVIRTUAL_CALL(_push, p_remote, p_force)) { + UNIMPLEMENTED(); } } -TypedArray<Dictionary> EditorVCSInterface::get_file_diff(String p_file_path) { - if (is_addon_ready()) { - return call("_get_file_diff", p_file_path); +void EditorVCSInterface::fetch(String p_remote) { + if (!GDVIRTUAL_CALL(_fetch, p_remote)) { + UNIMPLEMENTED(); } - return TypedArray<Dictionary>(); } -bool EditorVCSInterface::shut_down() { - return call("_shut_down"); +List<EditorVCSInterface::DiffHunk> EditorVCSInterface::get_line_diff(String p_file_path, String p_text) { + Array result; + if (!GDVIRTUAL_CALL(_get_line_diff, p_file_path, p_text, result)) { + UNIMPLEMENTED(); + return {}; + } + + List<DiffHunk> diff_hunks; + for (int i = 0; i < result.size(); i++) { + diff_hunks.push_back(_convert_diff_hunk(result[i])); + } + return diff_hunks; } -String EditorVCSInterface::get_project_name() { - return call("_get_project_name"); +bool EditorVCSInterface::shut_down() { + bool result = false; + if (!GDVIRTUAL_CALL(_shut_down, result)) { + UNIMPLEMENTED(); + return false; + } + return result; } String EditorVCSInterface::get_vcs_name() { - return call("_get_vcs_name"); + String result; + if (!GDVIRTUAL_CALL(_get_vcs_name, result)) { + UNIMPLEMENTED(); + return {}; + } + return result; +} + +Dictionary EditorVCSInterface::create_diff_line(int p_new_line_no, int p_old_line_no, String p_content, String p_status) { + Dictionary diff_line; + diff_line["new_line_no"] = p_new_line_no; + diff_line["old_line_no"] = p_old_line_no; + diff_line["content"] = p_content; + diff_line["status"] = p_status; + + return diff_line; +} + +Dictionary EditorVCSInterface::create_diff_hunk(int p_old_start, int p_new_start, int p_old_lines, int p_new_lines) { + Dictionary diff_hunk; + diff_hunk["new_lines"] = p_new_lines; + diff_hunk["old_lines"] = p_old_lines; + diff_hunk["new_start"] = p_new_start; + diff_hunk["old_start"] = p_old_start; + diff_hunk["diff_lines"] = Array(); + return diff_hunk; +} + +Dictionary EditorVCSInterface::add_line_diffs_into_diff_hunk(Dictionary p_diff_hunk, Array p_line_diffs) { + p_diff_hunk["diff_lines"] = p_line_diffs; + return p_diff_hunk; +} + +Dictionary EditorVCSInterface::create_diff_file(String p_new_file, String p_old_file) { + Dictionary file_diff; + file_diff["new_file"] = p_new_file; + file_diff["old_file"] = p_old_file; + file_diff["diff_hunks"] = Array(); + return file_diff; +} + +Dictionary EditorVCSInterface::create_commit(String p_msg, String p_author, String p_id, int64_t p_unix_timestamp, int64_t p_offset_minutes) { + Dictionary commit_info; + commit_info["message"] = p_msg; + commit_info["author"] = p_author; + commit_info["unix_timestamp"] = p_unix_timestamp; + commit_info["offset_minutes"] = p_offset_minutes; + commit_info["id"] = p_id; + return commit_info; } -EditorVCSInterface::EditorVCSInterface() { +Dictionary EditorVCSInterface::add_diff_hunks_into_diff_file(Dictionary p_diff_file, Array p_diff_hunks) { + p_diff_file["diff_hunks"] = p_diff_hunks; + return p_diff_file; } -EditorVCSInterface::~EditorVCSInterface() { +Dictionary EditorVCSInterface::create_status_file(String p_file_path, ChangeType p_change, TreeArea p_area) { + Dictionary sf; + sf["file_path"] = p_file_path; + sf["change_type"] = p_change; + sf["area"] = p_area; + return sf; +} + +EditorVCSInterface::DiffLine EditorVCSInterface::_convert_diff_line(Dictionary p_diff_line) { + DiffLine d; + d.new_line_no = p_diff_line["new_line_no"]; + d.old_line_no = p_diff_line["old_line_no"]; + d.content = p_diff_line["content"]; + d.status = p_diff_line["status"]; + return d; +} + +EditorVCSInterface::DiffHunk EditorVCSInterface::_convert_diff_hunk(Dictionary p_diff_hunk) { + DiffHunk dh; + dh.new_lines = p_diff_hunk["new_lines"]; + dh.old_lines = p_diff_hunk["old_lines"]; + dh.new_start = p_diff_hunk["new_start"]; + dh.old_start = p_diff_hunk["old_start"]; + Array diff_lines = p_diff_hunk["diff_lines"]; + for (int i = 0; i < diff_lines.size(); i++) { + DiffLine dl = _convert_diff_line(diff_lines[i]); + dh.diff_lines.push_back(dl); + } + return dh; +} + +EditorVCSInterface::DiffFile EditorVCSInterface::_convert_diff_file(Dictionary p_diff_file) { + DiffFile df; + df.new_file = p_diff_file["new_file"]; + df.old_file = p_diff_file["old_file"]; + Array diff_hunks = p_diff_file["diff_hunks"]; + for (int i = 0; i < diff_hunks.size(); i++) { + DiffHunk dh = _convert_diff_hunk(diff_hunks[i]); + df.diff_hunks.push_back(dh); + } + return df; +} + +EditorVCSInterface::Commit EditorVCSInterface::_convert_commit(Dictionary p_commit) { + EditorVCSInterface::Commit c; + c.msg = p_commit["message"]; + c.author = p_commit["author"]; + c.unix_timestamp = p_commit["unix_timestamp"]; + c.offset_minutes = p_commit["offset_minutes"]; + c.id = p_commit["id"]; + return c; +} + +EditorVCSInterface::StatusFile EditorVCSInterface::_convert_status_file(Dictionary p_status_file) { + StatusFile sf; + sf.file_path = p_status_file["file_path"]; + sf.change_type = (ChangeType)(int)p_status_file["change_type"]; + sf.area = (TreeArea)(int)p_status_file["area"]; + return sf; +} + +void EditorVCSInterface::_bind_methods() { + // Proxy end points that implement the VCS specific operations that the editor demands. + GDVIRTUAL_BIND(_initialize, "project_path"); + GDVIRTUAL_BIND(_set_credentials, "username", "password", "ssh_public_key_path", "ssh_private_key_path", "ssh_passphrase"); + GDVIRTUAL_BIND(_get_modified_files_data); + GDVIRTUAL_BIND(_stage_file, "file_path"); + GDVIRTUAL_BIND(_unstage_file, "file_path"); + GDVIRTUAL_BIND(_discard_file, "file_path"); + GDVIRTUAL_BIND(_commit, "msg"); + GDVIRTUAL_BIND(_get_diff, "identifier", "area"); + GDVIRTUAL_BIND(_shut_down); + GDVIRTUAL_BIND(_get_vcs_name); + GDVIRTUAL_BIND(_get_previous_commits, "max_commits"); + GDVIRTUAL_BIND(_get_branch_list); + GDVIRTUAL_BIND(_get_remotes); + GDVIRTUAL_BIND(_create_branch, "branch_name"); + GDVIRTUAL_BIND(_remove_branch, "branch_name"); + GDVIRTUAL_BIND(_create_remote, "remote_name", "remote_url"); + GDVIRTUAL_BIND(_remove_remote, "remote_name"); + GDVIRTUAL_BIND(_get_current_branch_name); + GDVIRTUAL_BIND(_checkout_branch, "branch_name"); + GDVIRTUAL_BIND(_pull, "remote"); + GDVIRTUAL_BIND(_push, "remote", "force"); + GDVIRTUAL_BIND(_fetch, "remote"); + GDVIRTUAL_BIND(_get_line_diff, "file_path", "text"); + + ClassDB::bind_method(D_METHOD("create_diff_line", "new_line_no", "old_line_no", "content", "status"), &EditorVCSInterface::create_diff_line); + ClassDB::bind_method(D_METHOD("create_diff_hunk", "old_start", "new_start", "old_lines", "new_lines"), &EditorVCSInterface::create_diff_hunk); + ClassDB::bind_method(D_METHOD("create_diff_file", "new_file", "old_file"), &EditorVCSInterface::create_diff_file); + ClassDB::bind_method(D_METHOD("create_commit", "msg", "author", "id", "unix_timestamp", "offset_minutes"), &EditorVCSInterface::create_commit); + ClassDB::bind_method(D_METHOD("create_status_file", "file_path", "change_type", "area"), &EditorVCSInterface::create_status_file); + ClassDB::bind_method(D_METHOD("add_diff_hunks_into_diff_file", "diff_file", "diff_hunks"), &EditorVCSInterface::add_diff_hunks_into_diff_file); + ClassDB::bind_method(D_METHOD("add_line_diffs_into_diff_hunk", "diff_hunk", "line_diffs"), &EditorVCSInterface::add_line_diffs_into_diff_hunk); + ClassDB::bind_method(D_METHOD("popup_error", "msg"), &EditorVCSInterface::popup_error); + + BIND_ENUM_CONSTANT(CHANGE_TYPE_NEW); + BIND_ENUM_CONSTANT(CHANGE_TYPE_MODIFIED); + BIND_ENUM_CONSTANT(CHANGE_TYPE_RENAMED); + BIND_ENUM_CONSTANT(CHANGE_TYPE_DELETED); + BIND_ENUM_CONSTANT(CHANGE_TYPE_TYPECHANGE); + BIND_ENUM_CONSTANT(CHANGE_TYPE_UNMERGED); + + BIND_ENUM_CONSTANT(TREE_AREA_COMMIT); + BIND_ENUM_CONSTANT(TREE_AREA_STAGED); + BIND_ENUM_CONSTANT(TREE_AREA_UNSTAGED); } EditorVCSInterface *EditorVCSInterface::get_singleton() { @@ -170,14 +409,14 @@ void EditorVCSInterface::create_vcs_metadata_files(VCSMetadata p_vcs_metadata_ty if (p_vcs_metadata_type == VCSMetadata::GIT) { Ref<FileAccess> f = FileAccess::open(p_dir.path_join(".gitignore"), FileAccess::WRITE); if (f.is_null()) { - ERR_FAIL_MSG(TTR("Couldn't create .gitignore in project path.")); + ERR_FAIL_MSG("Couldn't create .gitignore in project path."); } else { f->store_line("# Godot 4+ specific ignores"); f->store_line(".godot/"); } f = FileAccess::open(p_dir.path_join(".gitattributes"), FileAccess::WRITE); if (f.is_null()) { - ERR_FAIL_MSG(TTR("Couldn't create .gitattributes in project path.")); + ERR_FAIL_MSG("Couldn't create .gitattributes in project path."); } else { f->store_line("# Normalize EOL for all files that Git considers text files."); f->store_line("* text=auto eol=lf"); diff --git a/editor/editor_vcs_interface.h b/editor/editor_vcs_interface.h index d6d7ffa0e9..e23e032ab9 100644 --- a/editor/editor_vcs_interface.h +++ b/editor/editor_vcs_interface.h @@ -32,30 +32,103 @@ #define EDITOR_VCS_INTERFACE_H #include "core/object/class_db.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language_extension.h" #include "core/string/ustring.h" -#include "scene/gui/panel_container.h" +#include "core/variant/type_info.h" class EditorVCSInterface : public Object { GDCLASS(EditorVCSInterface, Object) - bool is_initialized = false; +public: + enum ChangeType { + CHANGE_TYPE_NEW = 0, + CHANGE_TYPE_MODIFIED = 1, + CHANGE_TYPE_RENAMED = 2, + CHANGE_TYPE_DELETED = 3, + CHANGE_TYPE_TYPECHANGE = 4, + CHANGE_TYPE_UNMERGED = 5 + }; + + enum TreeArea { + TREE_AREA_COMMIT = 0, + TREE_AREA_STAGED = 1, + TREE_AREA_UNSTAGED = 2 + }; + + struct DiffLine { + int new_line_no; + int old_line_no; + String content; + String status; + + String old_text; + String new_text; + }; + + struct DiffHunk { + int new_start; + int old_start; + int new_lines; + int old_lines; + List<DiffLine> diff_lines; + }; + + struct DiffFile { + String new_file; + String old_file; + List<DiffHunk> diff_hunks; + }; + + struct Commit { + String author; + String msg; + String id; + int64_t unix_timestamp; + int64_t offset_minutes; + }; + + struct StatusFile { + TreeArea area; + ChangeType change_type; + String file_path; + }; protected: static EditorVCSInterface *singleton; static void _bind_methods(); - // Implemented by addons as end points for the proxy functions - virtual bool _initialize(String p_project_root_path); - virtual bool _is_vcs_initialized(); - virtual Dictionary _get_modified_files_data(); - virtual void _stage_file(String p_file_path); - virtual void _unstage_file(String p_file_path); - virtual void _commit(String p_msg); - virtual TypedArray<Dictionary> _get_file_diff(String p_file_path); - virtual bool _shut_down(); - virtual String _get_project_name(); - virtual String _get_vcs_name(); + DiffLine _convert_diff_line(Dictionary p_diff_line); + DiffHunk _convert_diff_hunk(Dictionary p_diff_hunk); + DiffFile _convert_diff_file(Dictionary p_diff_file); + Commit _convert_commit(Dictionary p_commit); + StatusFile _convert_status_file(Dictionary p_status_file); + + // Proxy endpoints for extensions to implement + GDVIRTUAL1R(bool, _initialize, String); + GDVIRTUAL5(_set_credentials, String, String, String, String, String); + GDVIRTUAL0R(Array, _get_modified_files_data); + GDVIRTUAL1(_stage_file, String); + GDVIRTUAL1(_unstage_file, String); + GDVIRTUAL1(_discard_file, String); + GDVIRTUAL1(_commit, String); + GDVIRTUAL2R(TypedArray<Dictionary>, _get_diff, String, int); + GDVIRTUAL0R(bool, _shut_down); + GDVIRTUAL0R(String, _get_vcs_name); + GDVIRTUAL1R(Array, _get_previous_commits, int); + GDVIRTUAL0R(Array, _get_branch_list); + GDVIRTUAL0R(Array, _get_remotes); + GDVIRTUAL1(_create_branch, String); + GDVIRTUAL1(_remove_branch, String); + GDVIRTUAL2(_create_remote, String, String); + GDVIRTUAL1(_remove_remote, String); + GDVIRTUAL0R(String, _get_current_branch_name); + GDVIRTUAL1R(bool, _checkout_branch, String); + GDVIRTUAL1(_pull, String); + GDVIRTUAL2(_push, String, bool); + GDVIRTUAL1(_fetch, String); + GDVIRTUAL2R(Array, _get_line_diff, String, String); public: static EditorVCSInterface *get_singleton(); @@ -67,22 +140,44 @@ public: }; static void create_vcs_metadata_files(VCSMetadata p_vcs_metadata_type, String &p_dir); - bool is_addon_ready(); - - // Proxy functions to the editor for use - bool initialize(String p_project_root_path); - bool is_vcs_initialized(); - Dictionary get_modified_files_data(); + // Proxies to the editor for use + bool initialize(String p_project_path); + void set_credentials(String p_username, String p_password, String p_ssh_public_key_path, String p_ssh_private_key_path, String p_ssh_passphrase); + List<StatusFile> get_modified_files_data(); void stage_file(String p_file_path); void unstage_file(String p_file_path); + void discard_file(String p_file_path); void commit(String p_msg); - TypedArray<Dictionary> get_file_diff(String p_file_path); + List<DiffFile> get_diff(String p_identifier, TreeArea p_area); bool shut_down(); - String get_project_name(); String get_vcs_name(); + List<Commit> get_previous_commits(int p_max_commits); + List<String> get_branch_list(); + List<String> get_remotes(); + void create_branch(String p_branch_name); + void remove_branch(String p_branch_name); + void create_remote(String p_remote_name, String p_remote_url); + void remove_remote(String p_remote_name); + String get_current_branch_name(); + bool checkout_branch(String p_branch_name); + void pull(String p_remote); + void push(String p_remote, bool p_force); + void fetch(String p_remote); + List<DiffHunk> get_line_diff(String p_file_path, String p_text); - EditorVCSInterface(); - virtual ~EditorVCSInterface(); + // Helper functions to create and convert Dictionary into data structures + Dictionary create_diff_line(int p_new_line_no, int p_old_line_no, String p_content, String p_status); + Dictionary create_diff_hunk(int p_old_start, int p_new_start, int p_old_lines, int p_new_lines); + Dictionary create_diff_file(String p_new_file, String p_old_file); + Dictionary create_commit(String p_msg, String p_author, String p_id, int64_t p_unix_timestamp, int64_t p_offset_minutes); + Dictionary create_status_file(String p_file_path, ChangeType p_change, TreeArea p_area); + Dictionary add_line_diffs_into_diff_hunk(Dictionary p_diff_hunk, Array p_line_diffs); + Dictionary add_diff_hunks_into_diff_file(Dictionary p_diff_file, Array p_diff_hunks); + + void popup_error(String p_msg); }; +VARIANT_ENUM_CAST(EditorVCSInterface::ChangeType); +VARIANT_ENUM_CAST(EditorVCSInterface::TreeArea); + #endif // EDITOR_VCS_INTERFACE_H diff --git a/editor/icons/VcsBranches.svg b/editor/icons/VcsBranches.svg new file mode 100644 index 0000000000..e79019590f --- /dev/null +++ b/editor/icons/VcsBranches.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" fill-rule="nonzero"><path d="m3.755 1.396c-1.599 0-2.914 1.315-2.914 2.913 0 1.599 1.315 2.914 2.914 2.914 1.598 0 2.913-1.315 2.913-2.914 0-1.598-1.315-2.913-2.913-2.913zm0 1.462c.796 0 1.451.655 1.451 1.451 0 .797-.655 1.452-1.451 1.452-.797 0-1.452-.655-1.452-1.452 0-.796.655-1.451 1.452-1.451z"/><path d="m12.073 8.956c-1.599 0-2.914 1.316-2.914 2.914s1.315 2.914 2.914 2.914c1.598 0 2.914-1.316 2.914-2.914s-1.316-2.914-2.914-2.914zm0 1.463c.796 0 1.451.655 1.451 1.451s-.655 1.451-1.451 1.451-1.451-.655-1.451-1.451.655-1.451 1.451-1.451z"/><path d="m12.073 1.396c-1.599 0-2.914 1.315-2.914 2.913 0 1.599 1.315 2.914 2.914 2.914 1.598 0 2.914-1.315 2.914-2.914 0-1.598-1.316-2.913-2.914-2.913zm0 1.462c.796 0 1.451.655 1.451 1.451 0 .797-.655 1.452-1.451 1.452s-1.451-.655-1.451-1.452c0-.796.655-1.451 1.451-1.451z"/></g><path d="m9.159 11.87h-2.491l-2.913-2.914v-1.733" fill="none" stroke="#e0e0e0" stroke-width="1.5"/><path d="m9.159 4.309h-2.491" fill="none" stroke="#e0e0e0" stroke-width="1.5"/></svg> diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index aa1d630372..ad114e022f 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -940,50 +940,6 @@ void ScriptEditor::_resave_scripts(const String &p_str) { disk_changed->hide(); } -void ScriptEditor::_reload_scripts() { - for (int i = 0; i < tab_container->get_tab_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); - if (!se) { - continue; - } - - Ref<Resource> edited_res = se->get_edited_resource(); - - if (edited_res->is_built_in()) { - continue; //internal script, who cares - } - - uint64_t last_date = edited_res->get_last_modified_time(); - uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); - - if (last_date == date) { - continue; - } - - Ref<Script> script = edited_res; - if (script != nullptr) { - Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); - ERR_CONTINUE(!rel_script.is_valid()); - script->set_source_code(rel_script->get_source_code()); - script->set_last_modified_time(rel_script->get_last_modified_time()); - script->reload(true); - } - - Ref<TextFile> text_file = edited_res; - if (text_file != nullptr) { - Error err; - Ref<TextFile> rel_text_file = _load_text_file(text_file->get_path(), &err); - ERR_CONTINUE(!rel_text_file.is_valid()); - text_file->set_text(rel_text_file->get_text()); - text_file->set_last_modified_time(rel_text_file->get_last_modified_time()); - } - se->reload_text(); - } - - disk_changed->hide(); - _update_script_names(); -} - void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); @@ -1077,7 +1033,7 @@ bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) { if (need_reload) { if (!need_ask) { - script_editor->_reload_scripts(); + script_editor->reload_scripts(); need_reload = false; } else { disk_changed->call_deferred(SNAME("popup_centered_ratio"), 0.5); @@ -2588,6 +2544,50 @@ void ScriptEditor::apply_scripts() const { } } +void ScriptEditor::reload_scripts() { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (!se) { + continue; + } + + Ref<Resource> edited_res = se->get_edited_resource(); + + if (edited_res->is_built_in()) { + continue; //internal script, who cares + } + + uint64_t last_date = edited_res->get_last_modified_time(); + uint64_t date = FileAccess::get_modified_time(edited_res->get_path()); + + if (last_date == date) { + continue; + } + + Ref<Script> script = edited_res; + if (script != nullptr) { + Ref<Script> rel_script = ResourceLoader::load(script->get_path(), script->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_CONTINUE(!rel_script.is_valid()); + script->set_source_code(rel_script->get_source_code()); + script->set_last_modified_time(rel_script->get_last_modified_time()); + script->reload(true); + } + + Ref<TextFile> text_file = edited_res; + if (text_file != nullptr) { + Error err; + Ref<TextFile> rel_text_file = _load_text_file(text_file->get_path(), &err); + ERR_CONTINUE(!rel_text_file.is_valid()); + text_file->set_text(rel_text_file->get_text()); + text_file->set_last_modified_time(rel_text_file->get_last_modified_time()); + } + se->reload_text(); + } + + disk_changed->hide(); + _update_script_names(); +} + void ScriptEditor::open_script_create_dialog(const String &p_base_name, const String &p_base_path) { _menu_option(FILE_NEW); script_create_dialog->config(p_base_name, p_base_path); @@ -3918,7 +3918,7 @@ ScriptEditor::ScriptEditor() { vbc->add_child(disk_changed_list); disk_changed_list->set_v_size_flags(SIZE_EXPAND_FILL); - disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::_reload_scripts)); + disk_changed->connect("confirmed", callable_mp(this, &ScriptEditor::reload_scripts)); disk_changed->set_ok_button_text(TTR("Reload")); disk_changed->add_button(TTR("Resave"), !DisplayServer::get_singleton()->get_swap_cancel_ok(), "resave"); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index d1898efb69..a8e6cc6868 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -327,7 +327,6 @@ class ScriptEditor : public PanelContainer { String _get_debug_tooltip(const String &p_text, Node *_se); void _resave_scripts(const String &p_str); - void _reload_scripts(); bool _test_script_times_on_disk(Ref<Resource> p_for_script = Ref<Resource>()); @@ -478,6 +477,7 @@ public: bool toggle_scripts_panel(); bool is_scripts_panel_toggled(); void apply_scripts() const; + void reload_scripts(); void open_script_create_dialog(const String &p_base_name, const String &p_base_path); void open_text_file_create_dialog(const String &p_base_path, const String &p_base_name = ""); Ref<Resource> open_file(const String &p_file); diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 2b55ba64c3..fba760d57f 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -30,25 +30,59 @@ #include "version_control_editor_plugin.h" -#include "core/object/script_language.h" +#include "core/config/project_settings.h" #include "core/os/keyboard.h" +#include "core/os/time.h" #include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/filesystem_dock.h" #include "scene/gui/separator.h" +#define CHECK_PLUGIN_INITIALIZED() \ + ERR_FAIL_COND_MSG(!EditorVCSInterface::get_singleton(), "No VCS plugin is initialized. Select a Version Control Plugin from Project menu."); + VersionControlEditorPlugin *VersionControlEditorPlugin::singleton = nullptr; void VersionControlEditorPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("popup_vcs_set_up_dialog"), &VersionControlEditorPlugin::popup_vcs_set_up_dialog); + ClassDB::bind_method(D_METHOD("_initialize_vcs"), &VersionControlEditorPlugin::_initialize_vcs); + ClassDB::bind_method(D_METHOD("_set_credentials"), &VersionControlEditorPlugin::_set_credentials); + ClassDB::bind_method(D_METHOD("_update_set_up_warning"), &VersionControlEditorPlugin::_update_set_up_warning); + ClassDB::bind_method(D_METHOD("_commit"), &VersionControlEditorPlugin::_commit); + ClassDB::bind_method(D_METHOD("_refresh_stage_area"), &VersionControlEditorPlugin::_refresh_stage_area); + ClassDB::bind_method(D_METHOD("_move_all"), &VersionControlEditorPlugin::_move_all); + ClassDB::bind_method(D_METHOD("_load_diff"), &VersionControlEditorPlugin::_load_diff); + ClassDB::bind_method(D_METHOD("_display_diff"), &VersionControlEditorPlugin::_display_diff); + ClassDB::bind_method(D_METHOD("_item_activated"), &VersionControlEditorPlugin::_item_activated); + ClassDB::bind_method(D_METHOD("_update_branch_create_button"), &VersionControlEditorPlugin::_update_branch_create_button); + ClassDB::bind_method(D_METHOD("_update_remote_create_button"), &VersionControlEditorPlugin::_update_remote_create_button); + ClassDB::bind_method(D_METHOD("_update_commit_button"), &VersionControlEditorPlugin::_update_commit_button); + ClassDB::bind_method(D_METHOD("_refresh_branch_list"), &VersionControlEditorPlugin::_refresh_branch_list); + ClassDB::bind_method(D_METHOD("_set_commit_list_size"), &VersionControlEditorPlugin::_set_commit_list_size); + ClassDB::bind_method(D_METHOD("_refresh_commit_list"), &VersionControlEditorPlugin::_refresh_commit_list); + ClassDB::bind_method(D_METHOD("_refresh_remote_list"), &VersionControlEditorPlugin::_refresh_remote_list); + ClassDB::bind_method(D_METHOD("_ssh_public_key_selected"), &VersionControlEditorPlugin::_ssh_public_key_selected); + ClassDB::bind_method(D_METHOD("_ssh_private_key_selected"), &VersionControlEditorPlugin::_ssh_private_key_selected); + ClassDB::bind_method(D_METHOD("_commit_message_gui_input"), &VersionControlEditorPlugin::_commit_message_gui_input); + ClassDB::bind_method(D_METHOD("_cell_button_pressed"), &VersionControlEditorPlugin::_cell_button_pressed); + ClassDB::bind_method(D_METHOD("_discard_all"), &VersionControlEditorPlugin::_discard_all); + ClassDB::bind_method(D_METHOD("_create_branch"), &VersionControlEditorPlugin::_create_branch); + ClassDB::bind_method(D_METHOD("_create_remote"), &VersionControlEditorPlugin::_create_remote); + ClassDB::bind_method(D_METHOD("_remove_branch"), &VersionControlEditorPlugin::_remove_branch); + ClassDB::bind_method(D_METHOD("_remove_remote"), &VersionControlEditorPlugin::_remove_remote); + ClassDB::bind_method(D_METHOD("_branch_item_selected"), &VersionControlEditorPlugin::_branch_item_selected); + ClassDB::bind_method(D_METHOD("_remote_selected"), &VersionControlEditorPlugin::_remote_selected); + ClassDB::bind_method(D_METHOD("_fetch"), &VersionControlEditorPlugin::_fetch); + ClassDB::bind_method(D_METHOD("_pull"), &VersionControlEditorPlugin::_pull); + ClassDB::bind_method(D_METHOD("_push"), &VersionControlEditorPlugin::_push); + ClassDB::bind_method(D_METHOD("_extra_option_selected"), &VersionControlEditorPlugin::_extra_option_selected); + ClassDB::bind_method(D_METHOD("_update_extra_options"), &VersionControlEditorPlugin::_update_extra_options); + ClassDB::bind_method(D_METHOD("_popup_branch_remove_confirm"), &VersionControlEditorPlugin::_popup_branch_remove_confirm); + ClassDB::bind_method(D_METHOD("_popup_remote_remove_confirm"), &VersionControlEditorPlugin::_popup_remote_remove_confirm); + ClassDB::bind_method(D_METHOD("_popup_file_dialog"), &VersionControlEditorPlugin::_popup_file_dialog); - // Used to track the status of files in the staging area - BIND_ENUM_CONSTANT(CHANGE_TYPE_NEW); - BIND_ENUM_CONSTANT(CHANGE_TYPE_MODIFIED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_RENAMED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_DELETED); - BIND_ENUM_CONSTANT(CHANGE_TYPE_TYPECHANGE); + ClassDB::bind_method(D_METHOD("popup_vcs_set_up_dialog"), &VersionControlEditorPlugin::popup_vcs_set_up_dialog); } void VersionControlEditorPlugin::_create_vcs_metadata_files() { @@ -56,21 +90,25 @@ void VersionControlEditorPlugin::_create_vcs_metadata_files() { EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(metadata_selection->get_selected()), dir); } -void VersionControlEditorPlugin::_selected_a_vcs(int p_id) { - List<StringName> available_addons = get_available_vcs_names(); - const StringName selected_vcs = set_up_choice->get_item_text(p_id); -} +void VersionControlEditorPlugin::_notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + String installed_plugin = GLOBAL_DEF("editor/version_control/plugin_name", ""); + String project_path = GLOBAL_DEF("editor/version_control/project_path", OS::get_singleton()->get_resource_dir()); + project_path_input->set_text(project_path); + bool has_autoload_enable = GLOBAL_DEF("editor/version_control/autoload_on_startup", false); -void VersionControlEditorPlugin::_populate_available_vcs_names() { - static bool called = false; - - if (!called) { - List<StringName> available_addons = get_available_vcs_names(); - for (int i = 0; i < available_addons.size(); i++) { - set_up_choice->add_item(available_addons[i]); + if (installed_plugin != "" && has_autoload_enable) { + if (_load_plugin(installed_plugin, project_path)) { + _set_credentials(); + } } + } +} - called = true; +void VersionControlEditorPlugin::_populate_available_vcs_names() { + set_up_choice->clear(); + for (int i = 0; i < available_plugins.size(); i++) { + set_up_choice->add_item(available_plugins[i]); } } @@ -83,9 +121,8 @@ void VersionControlEditorPlugin::popup_vcs_metadata_dialog() { } void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_base) { - fetch_available_vcs_addon_names(); - List<StringName> available_addons = get_available_vcs_names(); - if (available_addons.size() >= 1) { + fetch_available_vcs_plugin_names(); + if (!available_plugins.is_empty()) { Size2 popup_size = Size2(400, 100); Size2 window_size = p_gui_base->get_viewport_rect().size; popup_size.x = MIN(window_size.x * 0.5, popup_size.x); @@ -95,213 +132,782 @@ void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_ba set_up_dialog->popup_centered_clamped(popup_size * EDSCALE); } else { - EditorNode::get_singleton()->show_warning(TTR("No VCS addons are available."), TTR("Error")); + // TODO: Give info to user on how to fix this error. + EditorNode::get_singleton()->show_warning(TTR("No VCS plugins are available in the project. Install a VCS plugin to use VCS integration features."), TTR("Error")); } } void VersionControlEditorPlugin::_initialize_vcs() { - register_editor(); - - ERR_FAIL_COND_MSG(EditorVCSInterface::get_singleton(), EditorVCSInterface::get_singleton()->get_vcs_name() + " is already active"); + ERR_FAIL_COND_MSG(EditorVCSInterface::get_singleton(), EditorVCSInterface::get_singleton()->get_vcs_name() + " is already active."); const int id = set_up_choice->get_selected_id(); - String selected_addon = set_up_choice->get_item_text(id); + String selected_plugin = set_up_choice->get_item_text(id); - String path = ScriptServer::get_global_class_path(selected_addon); - Ref<Script> script = ResourceLoader::load(path); + if (_load_plugin(selected_plugin, project_path_input->get_text())) { + ProjectSettings::get_singleton()->set("editor/version_control/autoload_on_startup", true); + ProjectSettings::get_singleton()->set("editor/version_control/plugin_name", selected_plugin); + ProjectSettings::get_singleton()->set("editor/version_control/project_path", project_path_input->get_text()); + ProjectSettings::get_singleton()->save(); + } +} - ERR_FAIL_COND_MSG(!script.is_valid(), "VCS Addon path is invalid"); +void VersionControlEditorPlugin::_set_vcs_ui_state(bool p_enabled) { + select_project_path_button->set_disabled(p_enabled); + set_up_dialog->get_ok_button()->set_disabled(!p_enabled); + project_path_input->set_editable(!p_enabled); + set_up_choice->set_disabled(p_enabled); + toggle_vcs_choice->set_pressed_no_signal(p_enabled); +} - EditorVCSInterface *vcs_interface = memnew(EditorVCSInterface); - ScriptInstance *addon_script_instance = script->instance_create(vcs_interface); +void VersionControlEditorPlugin::_set_credentials() { + CHECK_PLUGIN_INITIALIZED(); + + String username = set_up_username->get_text(); + String password = set_up_password->get_text(); + String ssh_public_key = set_up_ssh_public_key_path->get_text(); + String ssh_private_key = set_up_ssh_private_key_path->get_text(); + String ssh_passphrase = set_up_ssh_passphrase->get_text(); + + EditorVCSInterface::get_singleton()->set_credentials( + username, + password, + ssh_public_key, + ssh_private_key, + ssh_passphrase); + + EditorSettings::get_singleton()->set_setting("version_control/username", username); + EditorSettings::get_singleton()->set_setting("version_control/ssh_public_key_path", ssh_public_key); + EditorSettings::get_singleton()->set_setting("version_control/ssh_private_key_path", ssh_private_key); +} - ERR_FAIL_COND_MSG(!addon_script_instance, "Failed to create addon script instance."); +bool VersionControlEditorPlugin::_load_plugin(String p_name, String p_project_path) { + Object *extension_instance = ClassDB::instantiate(p_name); + ERR_FAIL_NULL_V_MSG(extension_instance, false, "Received a nullptr VCS extension instance during construction."); - // The addon is attached as a script to the VCS interface as a proxy end-point - vcs_interface->set_script_and_instance(script, addon_script_instance); + EditorVCSInterface *vcs_plugin = Object::cast_to<EditorVCSInterface>(extension_instance); + ERR_FAIL_NULL_V_MSG(vcs_plugin, false, vformat("Could not cast VCS extension instance to %s.", EditorVCSInterface::get_class_static())); - EditorVCSInterface::set_singleton(vcs_interface); - EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); + String res_dir = project_path_input->get_text(); - String res_dir = OS::get_singleton()->get_resource_dir(); + ERR_FAIL_COND_V_MSG(!vcs_plugin->initialize(res_dir), false, "Could not initialize " + p_name); - ERR_FAIL_COND_MSG(!EditorVCSInterface::get_singleton()->initialize(res_dir), "VCS was not initialized"); + EditorVCSInterface::set_singleton(vcs_plugin); + + register_editor(); + EditorFileSystem::get_singleton()->connect(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); _refresh_stage_area(); + _refresh_commit_list(); + _refresh_branch_list(); + _refresh_remote_list(); + + return true; } -void VersionControlEditorPlugin::_send_commit_msg() { - if (EditorVCSInterface::get_singleton()) { - if (staged_files_count == 0) { - commit_status->set_text(TTR("No files added to stage")); - return; +void VersionControlEditorPlugin::_update_set_up_warning(String p_new_text) { + bool empty_settings = set_up_username->get_text().strip_edges().is_empty() && + set_up_password->get_text().is_empty() && + set_up_ssh_public_key_path->get_text().strip_edges().is_empty() && + set_up_ssh_private_key_path->get_text().strip_edges().is_empty() && + set_up_ssh_passphrase->get_text().is_empty(); + + if (empty_settings) { + set_up_warning_text->add_theme_color_override(SNAME("font_color"), EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + set_up_warning_text->set_text(TTR("Remote settings are empty. VCS features that use the network may not work.")); + } else { + set_up_warning_text->set_text(""); + } +} + +void VersionControlEditorPlugin::_refresh_branch_list() { + CHECK_PLUGIN_INITIALIZED(); + + List<String> branch_list = EditorVCSInterface::get_singleton()->get_branch_list(); + branch_select->clear(); + + branch_select->set_disabled(branch_list.is_empty()); + + String current_branch = EditorVCSInterface::get_singleton()->get_current_branch_name(); + + for (int i = 0; i < branch_list.size(); i++) { + branch_select->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("VcsBranches"), SNAME("EditorIcons")), branch_list[i], i); + + if (branch_list[i] == current_branch) { + branch_select->select(i); } + } +} + +String VersionControlEditorPlugin::_get_date_string_from(int64_t p_unix_timestamp, int64_t p_offset_minutes) const { + return vformat( + "%s %s", + Time::get_singleton()->get_datetime_string_from_unix_time(p_unix_timestamp + p_offset_minutes * 60, true), + Time::get_singleton()->get_offset_string_from_offset_minutes(p_offset_minutes)); +} - EditorVCSInterface::get_singleton()->commit(commit_message->get_text()); +void VersionControlEditorPlugin::_set_commit_list_size(int p_index) { + _refresh_commit_list(); +} - commit_message->set_text(""); - version_control_dock_button->set_pressed(false); - } else { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); +void VersionControlEditorPlugin::_refresh_commit_list() { + CHECK_PLUGIN_INITIALIZED(); + + commit_list->get_root()->clear_children(); + + List<EditorVCSInterface::Commit> commit_info_list = EditorVCSInterface::get_singleton()->get_previous_commits(commit_list_size_button->get_selected_metadata()); + + for (List<EditorVCSInterface::Commit>::Element *e = commit_info_list.front(); e; e = e->next()) { + EditorVCSInterface::Commit commit = e->get(); + TreeItem *item = commit_list->create_item(); + + // Only display the first line of a commit message + int line_ending = commit.msg.find_char('\n'); + String commit_display_msg = commit.msg.substr(0, line_ending); + String commit_date_string = _get_date_string_from(commit.unix_timestamp, commit.offset_minutes); + + Dictionary meta_data; + meta_data[SNAME("commit_id")] = commit.id; + meta_data[SNAME("commit_title")] = commit_display_msg; + meta_data[SNAME("commit_subtitle")] = commit.msg.substr(line_ending).strip_edges(); + meta_data[SNAME("commit_unix_timestamp")] = commit.unix_timestamp; + meta_data[SNAME("commit_author")] = commit.author; + meta_data[SNAME("commit_date_string")] = commit_date_string; + + item->set_text(0, commit_display_msg); + item->set_text(1, commit.author.strip_edges()); + item->set_metadata(0, meta_data); } +} + +void VersionControlEditorPlugin::_refresh_remote_list() { + CHECK_PLUGIN_INITIALIZED(); + + List<String> remotes = EditorVCSInterface::get_singleton()->get_remotes(); + + String current_remote = remote_select->get_selected_metadata(); + remote_select->clear(); - _update_commit_status(); + remote_select->set_disabled(remotes.is_empty()); + + for (int i = 0; i < remotes.size(); i++) { + remote_select->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")), remotes[i], i); + remote_select->set_item_metadata(i, remotes[i]); + + if (remotes[i] == current_remote) { + remote_select->select(i); + } + } +} + +void VersionControlEditorPlugin::_commit() { + CHECK_PLUGIN_INITIALIZED(); + + String msg = commit_message->get_text().strip_edges(); + + ERR_FAIL_COND_MSG(msg.is_empty(), "No commit message was provided."); + + EditorVCSInterface::get_singleton()->commit(msg); + + version_control_dock_button->set_pressed(false); + + commit_message->release_focus(); + commit_button->release_focus(); + commit_message->set_text(""); + + _refresh_stage_area(); + _refresh_commit_list(); + _refresh_branch_list(); + _clear_diff(); +} + +void VersionControlEditorPlugin::_branch_item_selected(int p_index) { + CHECK_PLUGIN_INITIALIZED(); + + String branch_name = branch_select->get_item_text(p_index); + EditorVCSInterface::get_singleton()->checkout_branch(branch_name); + + EditorFileSystem::get_singleton()->scan_changes(); + ScriptEditor::get_singleton()->reload_scripts(); + + _refresh_branch_list(); + _refresh_commit_list(); _refresh_stage_area(); - _clear_file_diff(); + _clear_diff(); + + _update_opened_tabs(); +} + +void VersionControlEditorPlugin::_remote_selected(int p_index) { + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_ssh_public_key_selected(String p_path) { + set_up_ssh_public_key_path->set_text(p_path); +} + +void VersionControlEditorPlugin::_ssh_private_key_selected(String p_path) { + set_up_ssh_private_key_path->set_text(p_path); +} + +void VersionControlEditorPlugin::_popup_file_dialog(Variant p_file_dialog_variant) { + FileDialog *file_dialog = Object::cast_to<FileDialog>(p_file_dialog_variant); + ERR_FAIL_NULL(file_dialog); + + file_dialog->popup_centered_ratio(); +} + +void VersionControlEditorPlugin::_create_branch() { + CHECK_PLUGIN_INITIALIZED(); + + String new_branch_name = branch_create_name_input->get_text().strip_edges(); + + EditorVCSInterface::get_singleton()->create_branch(new_branch_name); + EditorVCSInterface::get_singleton()->checkout_branch(new_branch_name); + + branch_create_name_input->clear(); + _refresh_branch_list(); +} + +void VersionControlEditorPlugin::_create_remote() { + CHECK_PLUGIN_INITIALIZED(); + + String new_remote_name = remote_create_name_input->get_text().strip_edges(); + String new_remote_url = remote_create_url_input->get_text().strip_edges(); + + EditorVCSInterface::get_singleton()->create_remote(new_remote_name, new_remote_url); + + remote_create_name_input->clear(); + remote_create_url_input->clear(); + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_update_branch_create_button(String p_new_text) { + branch_create_ok->set_disabled(p_new_text.strip_edges().is_empty()); +} + +void VersionControlEditorPlugin::_update_remote_create_button(String p_new_text) { + remote_create_ok->set_disabled(p_new_text.strip_edges().is_empty()); +} + +int VersionControlEditorPlugin::_get_item_count(Tree *p_tree) { + if (!p_tree->get_root()) { + return 0; + } + return p_tree->get_root()->get_children().size(); } void VersionControlEditorPlugin::_refresh_stage_area() { - if (EditorVCSInterface::get_singleton()) { - staged_files_count = 0; - clear_stage_area(); - - Dictionary modified_file_paths = EditorVCSInterface::get_singleton()->get_modified_files_data(); - String file_path; - for (int i = 0; i < modified_file_paths.size(); i++) { - file_path = modified_file_paths.get_key_at_index(i); - TreeItem *found = stage_files->search_item_text(file_path, nullptr, true); - if (!found) { - ChangeType change_index = (ChangeType)(int)modified_file_paths.get_value_at_index(i); - String change_text = file_path + " (" + change_type_to_strings[change_index] + ")"; - Color &change_color = change_type_to_color[change_index]; - TreeItem *new_item = stage_files->create_item(stage_files->get_root()); - new_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - new_item->set_text(0, change_text); - new_item->set_metadata(0, file_path); - new_item->set_custom_color(0, change_color); - new_item->set_checked(0, true); - new_item->set_editable(0, true); - } else { - if (found->get_metadata(0) == diff_file_name->get_text()) { - _refresh_file_diff(); - } - } - commit_status->set_text(TTR("New changes detected")); + CHECK_PLUGIN_INITIALIZED(); + + staged_files->get_root()->clear_children(); + unstaged_files->get_root()->clear_children(); + + List<EditorVCSInterface::StatusFile> status_files = EditorVCSInterface::get_singleton()->get_modified_files_data(); + for (List<EditorVCSInterface::StatusFile>::Element *E = status_files.front(); E; E = E->next()) { + EditorVCSInterface::StatusFile sf = E->get(); + if (sf.area == EditorVCSInterface::TREE_AREA_STAGED) { + _add_new_item(staged_files, sf.file_path, sf.change_type); + } else if (sf.area == EditorVCSInterface::TREE_AREA_UNSTAGED) { + _add_new_item(unstaged_files, sf.file_path, sf.change_type); } + } + + staged_files->queue_redraw(); + unstaged_files->queue_redraw(); + + int total_changes = status_files.size(); + String commit_tab_title = TTR("Commit") + (total_changes > 0 ? " (" + itos(total_changes) + ")" : ""); + version_commit_dock->set_name(commit_tab_title); +} + +void VersionControlEditorPlugin::_discard_file(String p_file_path, EditorVCSInterface::ChangeType p_change) { + CHECK_PLUGIN_INITIALIZED(); + + if (p_change == EditorVCSInterface::CHANGE_TYPE_NEW) { + Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); + dir->remove(p_file_path); } else { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu."); + CHECK_PLUGIN_INITIALIZED(); + EditorVCSInterface::get_singleton()->discard_file(p_file_path); } + // FIXIT: The project.godot file shows weird behaviour + EditorFileSystem::get_singleton()->update_file(p_file_path); } -void VersionControlEditorPlugin::_stage_selected() { - if (!EditorVCSInterface::get_singleton()) { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); - return; +void VersionControlEditorPlugin::_discard_all() { + TreeItem *file_entry = unstaged_files->get_root()->get_first_child(); + while (file_entry) { + String file_path = file_entry->get_meta(SNAME("file_path")); + EditorVCSInterface::ChangeType change = (EditorVCSInterface::ChangeType)(int)file_entry->get_meta(SNAME("change_type")); + _discard_file(file_path, change); + + file_entry = file_entry->get_next(); } + _refresh_stage_area(); +} - staged_files_count = 0; - TreeItem *root = stage_files->get_root(); - if (root) { - TreeItem *file_entry = root->get_first_child(); - while (file_entry) { - if (file_entry->is_checked(0)) { - EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - staged_files_count++; - } else { - EditorVCSInterface::get_singleton()->unstage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } +void VersionControlEditorPlugin::_add_new_item(Tree *p_tree, String p_file_path, EditorVCSInterface::ChangeType p_change) { + String change_text = p_file_path + " (" + change_type_to_strings[p_change] + ")"; - file_entry = file_entry->get_next(); - } + TreeItem *new_item = p_tree->create_item(); + new_item->set_text(0, change_text); + new_item->set_icon(0, change_type_to_icon[p_change]); + new_item->set_meta(SNAME("file_path"), p_file_path); + new_item->set_meta(SNAME("change_type"), p_change); + new_item->set_custom_color(0, change_type_to_color[p_change]); + + new_item->add_button(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("File"), SNAME("EditorIcons")), BUTTON_TYPE_OPEN, false, TTR("Open in editor")); + if (p_tree == unstaged_files) { + new_item->add_button(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Close"), SNAME("EditorIcons")), BUTTON_TYPE_DISCARD, false, TTR("Discard changes")); } +} + +void VersionControlEditorPlugin::_fetch() { + CHECK_PLUGIN_INITIALIZED(); - _update_stage_status(); + EditorVCSInterface::get_singleton()->fetch(remote_select->get_selected_metadata()); + _refresh_branch_list(); } -void VersionControlEditorPlugin::_stage_all() { - if (!EditorVCSInterface::get_singleton()) { - WARN_PRINT("No VCS addon is initialized. Select a Version Control Addon from Project menu"); - return; - } +void VersionControlEditorPlugin::_pull() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->pull(remote_select->get_selected_metadata()); + _refresh_stage_area(); + _refresh_branch_list(); + _refresh_commit_list(); + _clear_diff(); + _update_opened_tabs(); +} + +void VersionControlEditorPlugin::_push() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->push(remote_select->get_selected_metadata(), false); +} + +void VersionControlEditorPlugin::_force_push() { + CHECK_PLUGIN_INITIALIZED(); - staged_files_count = 0; - TreeItem *root = stage_files->get_root(); - if (root) { - TreeItem *file_entry = root->get_first_child(); - while (file_entry) { - EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); - file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - file_entry->set_checked(0, true); - staged_files_count++; + EditorVCSInterface::get_singleton()->push(remote_select->get_selected_metadata(), true); +} - file_entry = file_entry->get_next(); +void VersionControlEditorPlugin::_update_opened_tabs() { + Vector<EditorData::EditedScene> open_scenes = EditorNode::get_singleton()->get_editor_data().get_edited_scenes(); + for (int i = 0; i < open_scenes.size(); i++) { + if (open_scenes[i].root == NULL) { + continue; } + EditorNode::get_singleton()->reload_scene(open_scenes[i].path); } +} - _update_stage_status(); +void VersionControlEditorPlugin::_move_all(Object *p_tree) { + Tree *tree = Object::cast_to<Tree>(p_tree); + + TreeItem *file_entry = tree->get_root()->get_first_child(); + while (file_entry) { + _move_item(tree, file_entry); + + file_entry = file_entry->get_next(); + } + _refresh_stage_area(); } -void VersionControlEditorPlugin::_view_file_diff() { +void VersionControlEditorPlugin::_load_diff(Object *p_tree) { + CHECK_PLUGIN_INITIALIZED(); + version_control_dock_button->set_pressed(true); - String file_path = stage_files->get_selected()->get_metadata(0); + Tree *tree = Object::cast_to<Tree>(p_tree); + if (tree == staged_files) { + show_commit_diff_header = false; + String file_path = tree->get_selected()->get_meta(SNAME("file_path")); + diff_title->set_text(TTR("Staged Changes")); + diff_content = EditorVCSInterface::get_singleton()->get_diff(file_path, EditorVCSInterface::TREE_AREA_STAGED); + } else if (tree == unstaged_files) { + show_commit_diff_header = false; + String file_path = tree->get_selected()->get_meta(SNAME("file_path")); + diff_title->set_text(TTR("Unstaged Changes")); + diff_content = EditorVCSInterface::get_singleton()->get_diff(file_path, EditorVCSInterface::TREE_AREA_UNSTAGED); + } else if (tree == commit_list) { + show_commit_diff_header = true; + Dictionary meta_data = tree->get_selected()->get_metadata(0); + String commit_id = meta_data[SNAME("commit_id")]; + String commit_title = meta_data[SNAME("commit_title")]; + diff_title->set_text(commit_title); + diff_content = EditorVCSInterface::get_singleton()->get_diff(commit_id, EditorVCSInterface::TREE_AREA_COMMIT); + } + _display_diff(0); +} + +void VersionControlEditorPlugin::_clear_diff() { + diff->clear(); + diff_content.clear(); + diff_title->set_text(""); +} + +void VersionControlEditorPlugin::_item_activated(Object *p_tree) { + Tree *tree = Object::cast_to<Tree>(p_tree); - _display_file_diff(file_path); + _move_item(tree, tree->get_selected()); + _refresh_stage_area(); +} + +void VersionControlEditorPlugin::_move_item(Tree *p_tree, TreeItem *p_item) { + CHECK_PLUGIN_INITIALIZED(); + + if (p_tree == staged_files) { + EditorVCSInterface::get_singleton()->unstage_file(p_item->get_meta(SNAME("file_path"))); + } else { + EditorVCSInterface::get_singleton()->stage_file(p_item->get_meta(SNAME("file_path"))); + } } -void VersionControlEditorPlugin::_display_file_diff(String p_file_path) { - TypedArray<Dictionary> diff_content = EditorVCSInterface::get_singleton()->get_file_diff(p_file_path); +void VersionControlEditorPlugin::_cell_button_pressed(Object *p_item, int p_column, int p_id, int p_mouse_button_index) { + TreeItem *item = Object::cast_to<TreeItem>(p_item); + String file_path = item->get_meta(SNAME("file_path")); + EditorVCSInterface::ChangeType change = (EditorVCSInterface::ChangeType)(int)item->get_meta(SNAME("change_type")); - diff_file_name->set_text(p_file_path); + if (p_id == BUTTON_TYPE_OPEN && change != EditorVCSInterface::CHANGE_TYPE_DELETED) { + Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (!dir->file_exists(file_path)) { + return; + } + + file_path = "res://" + file_path; + if (ResourceLoader::get_resource_type(file_path) == "PackedScene") { + EditorNode::get_singleton()->open_request(file_path); + } else if (file_path.ends_with(".gd")) { + EditorNode::get_singleton()->load_resource(file_path); + ScriptEditor::get_singleton()->reload_scripts(); + } else { + FileSystemDock::get_singleton()->navigate_to_path(file_path); + } + + } else if (p_id == BUTTON_TYPE_DISCARD) { + _discard_file(file_path, change); + _refresh_stage_area(); + } +} + +void VersionControlEditorPlugin::_display_diff(int p_idx) { + DiffViewType diff_view = (DiffViewType)diff_view_type_select->get_selected(); diff->clear(); - diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("source"), SNAME("EditorFonts"))); + + if (show_commit_diff_header) { + Dictionary meta_data = commit_list->get_selected()->get_metadata(0); + String commit_id = meta_data[SNAME("commit_id")]; + String commit_subtitle = meta_data[SNAME("commit_subtitle")]; + String commit_date = meta_data[SNAME("commit_date")]; + String commit_author = meta_data[SNAME("commit_author")]; + String commit_date_string = meta_data[SNAME("commit_date_string")]; + + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"))); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor"))); + diff->add_text(TTR("Commit:") + " " + commit_id); + diff->add_newline(); + diff->add_text(TTR("Author:") + " " + commit_author); + diff->add_newline(); + diff->add_text(TTR("Date:") + " " + commit_date_string); + diff->add_newline(); + if (!commit_subtitle.is_empty()) { + diff->add_text(TTR("Subtitle:") + " " + commit_subtitle); + diff->add_newline(); + } + diff->add_newline(); + diff->pop(); + diff->pop(); + } + for (int i = 0; i < diff_content.size(); i++) { - Dictionary line_result = diff_content[i]; + EditorVCSInterface::DiffFile diff_file = diff_content[i]; + + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("doc_bold"), SNAME("EditorFonts"))); + diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor"))); + diff->add_text(TTR("File:") + " " + diff_file.new_file); + diff->pop(); + diff->pop(); + + diff->add_newline(); + diff->push_font(EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + for (int j = 0; j < diff_file.diff_hunks.size(); j++) { + EditorVCSInterface::DiffHunk hunk = diff_file.diff_hunks[j]; + + String old_start = String::num_int64(hunk.old_start); + String new_start = String::num_int64(hunk.new_start); + String old_lines = String::num_int64(hunk.old_lines); + String new_lines = String::num_int64(hunk.new_lines); + + diff->append_text("[center]@@ " + old_start + "," + old_lines + " " + new_start + "," + new_lines + " @@[/center]"); + diff->add_newline(); + + switch (diff_view) { + case DIFF_VIEW_TYPE_SPLIT: + _display_diff_split_view(hunk.diff_lines); + break; + case DIFF_VIEW_TYPE_UNIFIED: + _display_diff_unified_view(hunk.diff_lines); + break; + } + diff->add_newline(); + diff->add_newline(); + } + diff->pop(); + + diff->add_newline(); + } +} + +void VersionControlEditorPlugin::_display_diff_split_view(List<EditorVCSInterface::DiffLine> &p_diff_content) { + List<EditorVCSInterface::DiffLine> parsed_diff; + + for (int i = 0; i < p_diff_content.size(); i++) { + EditorVCSInterface::DiffLine diff_line = p_diff_content[i]; + String line = diff_line.content.strip_edges(false, true); + + if (diff_line.new_line_no >= 0 && diff_line.old_line_no >= 0) { + diff_line.new_text = line; + diff_line.old_text = line; + parsed_diff.push_back(diff_line); + } else if (diff_line.new_line_no == -1) { + diff_line.new_text = ""; + diff_line.old_text = line; + parsed_diff.push_back(diff_line); + } else if (diff_line.old_line_no == -1) { + int j = parsed_diff.size() - 1; + while (j >= 0 && parsed_diff[j].new_line_no == -1) { + j--; + } + + if (j == parsed_diff.size() - 1) { + // no lines are modified + diff_line.new_text = line; + diff_line.old_text = ""; + parsed_diff.push_back(diff_line); + } else { + // lines are modified + EditorVCSInterface::DiffLine modified_line = parsed_diff[j + 1]; + modified_line.new_text = line; + modified_line.new_line_no = diff_line.new_line_no; + parsed_diff[j + 1] = modified_line; + } + } + } + + diff->push_table(6); + /* + [cell]Old Line No[/cell] + [cell]prefix[/cell] + [cell]Old Code[/cell] + + [cell]New Line No[/cell] + [cell]prefix[/cell] + [cell]New Line[/cell] + */ + + diff->set_table_column_expand(2, true); + diff->set_table_column_expand(5, true); + + for (int i = 0; i < parsed_diff.size(); i++) { + EditorVCSInterface::DiffLine diff_line = parsed_diff[i]; + + bool has_change = diff_line.status != " "; + static const Color red = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + static const Color green = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + static const Color white = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label")) * Color(1, 1, 1, 0.6); + + if (diff_line.old_line_no >= 0) { + diff->push_cell(); + diff->push_indent(1); + diff->push_color(has_change ? red : white); + diff->add_text(String::num_int64(diff_line.old_line_no)); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? red : white); + diff->add_text(has_change ? "-|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? red : white); + diff->add_text(diff_line.old_text); + diff->pop(); + diff->pop(); - if (line_result["status"] == "+") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor"))); - } else if (line_result["status"] == "-") { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); } else { - diff->push_color(EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label"))); + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); } - diff->add_text((String)line_result["content"]); + if (diff_line.new_line_no >= 0) { + diff->push_cell(); + diff->push_indent(1); + diff->push_color(has_change ? green : white); + diff->add_text(String::num_int64(diff_line.new_line_no)); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? green : white); + diff->add_text(has_change ? "+|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(has_change ? green : white); + diff->add_text(diff_line.new_text); + diff->pop(); + diff->pop(); + } else { + diff->push_cell(); + diff->pop(); - diff->pop(); + diff->push_cell(); + diff->pop(); + + diff->push_cell(); + diff->pop(); + } } diff->pop(); } -void VersionControlEditorPlugin::_refresh_file_diff() { - String open_file = diff_file_name->get_text(); - if (!open_file.is_empty()) { - _display_file_diff(diff_file_name->get_text()); +void VersionControlEditorPlugin::_display_diff_unified_view(List<EditorVCSInterface::DiffLine> &p_diff_content) { + diff->push_table(4); + diff->set_table_column_expand(3, true); + + /* + [cell]Old Line No[/cell] + [cell]New Line No[/cell] + [cell]status[/cell] + [cell]code[/cell] + */ + for (int i = 0; i < p_diff_content.size(); i++) { + EditorVCSInterface::DiffLine diff_line = p_diff_content[i]; + String line = diff_line.content.strip_edges(false, true); + + Color color; + if (diff_line.status == "+") { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + } else if (diff_line.status == "-") { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + } else { + color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Label")); + color *= Color(1, 1, 1, 0.6); + } + + diff->push_cell(); + diff->push_color(color); + diff->push_indent(1); + diff->add_text(diff_line.old_line_no >= 0 ? String::num_int64(diff_line.old_line_no) : ""); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->push_indent(1); + diff->add_text(diff_line.new_line_no >= 0 ? String::num_int64(diff_line.new_line_no) : ""); + diff->pop(); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->add_text(diff_line.status != "" ? diff_line.status + "|" : " |"); + diff->pop(); + diff->pop(); + + diff->push_cell(); + diff->push_color(color); + diff->add_text(line); + diff->pop(); + diff->pop(); } + + diff->pop(); } -void VersionControlEditorPlugin::_clear_file_diff() { - diff->clear(); - diff_file_name->set_text(""); - version_control_dock_button->set_pressed(false); +void VersionControlEditorPlugin::_update_commit_button() { + commit_button->set_disabled(commit_message->get_text().strip_edges().is_empty()); } -void VersionControlEditorPlugin::_update_stage_status() { - String status; - if (staged_files_count == 1) { - status = TTR("Stage contains 1 file"); - } else { - status = vformat(TTR("Stage contains %d files"), staged_files_count); +void VersionControlEditorPlugin::_remove_branch() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->remove_branch(branch_to_remove); + branch_to_remove.clear(); + + _refresh_branch_list(); +} + +void VersionControlEditorPlugin::_remove_remote() { + CHECK_PLUGIN_INITIALIZED(); + + EditorVCSInterface::get_singleton()->remove_remote(remote_to_remove); + remote_to_remove.clear(); + + _refresh_remote_list(); +} + +void VersionControlEditorPlugin::_extra_option_selected(int p_index) { + CHECK_PLUGIN_INITIALIZED(); + + switch ((ExtraOption)p_index) { + case EXTRA_OPTION_FORCE_PUSH: + _force_push(); + break; + case EXTRA_OPTION_CREATE_BRANCH: + branch_create_confirm->popup_centered(); + break; + case EXTRA_OPTION_CREATE_REMOTE: + remote_create_confirm->popup_centered(); + break; } - commit_status->set_text(status); } -void VersionControlEditorPlugin::_update_commit_status() { - String status; - if (staged_files_count == 1) { - status = TTR("Committed 1 file"); - } else { - status = vformat(TTR("Committed %d files"), staged_files_count); +void VersionControlEditorPlugin::_popup_branch_remove_confirm(int p_index) { + branch_to_remove = extra_options_remove_branch_list->get_item_text(p_index); + + branch_remove_confirm->set_text(vformat(TTR("Do you want to remove the %s branch?"), branch_to_remove)); + branch_remove_confirm->popup_centered(); +} + +void VersionControlEditorPlugin::_popup_remote_remove_confirm(int p_index) { + remote_to_remove = extra_options_remove_remote_list->get_item_text(p_index); + + remote_remove_confirm->set_text(vformat(TTR("Do you want to remove the %s remote?"), branch_to_remove)); + remote_remove_confirm->popup_centered(); +} + +void VersionControlEditorPlugin::_update_extra_options() { + extra_options_remove_branch_list->clear(); + for (int i = 0; i < branch_select->get_item_count(); i++) { + extra_options_remove_branch_list->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("VcsBranches"), SNAME("EditorIcons")), branch_select->get_item_text(branch_select->get_item_id(i))); + } + extra_options_remove_branch_list->update_canvas_items(); + + extra_options_remove_remote_list->clear(); + for (int i = 0; i < remote_select->get_item_count(); i++) { + extra_options_remove_remote_list->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")), remote_select->get_item_text(remote_select->get_item_id(i))); } - commit_status->set_text(status); - staged_files_count = 0; + extra_options_remove_remote_list->update_canvas_items(); } -void VersionControlEditorPlugin::_update_commit_button() { - commit_button->set_disabled(commit_message->get_text().strip_edges().is_empty()); +bool VersionControlEditorPlugin::_is_staging_area_empty() { + return staged_files->get_root()->get_child_count() == 0; } void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> &p_event) { @@ -316,266 +922,660 @@ void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> if (k.is_valid() && k->is_pressed()) { if (ED_IS_SHORTCUT("version_control/commit", p_event)) { - if (staged_files_count == 0) { + if (_is_staging_area_empty()) { // Stage all files only when no files were previously staged. - _stage_all(); + _move_all(unstaged_files); } - _send_commit_msg(); + + _commit(); + commit_message->accept_event(); - return; } } } -void VersionControlEditorPlugin::register_editor() { - if (!EditorVCSInterface::get_singleton()) { - EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock); - TabContainer *dock_vbc = (TabContainer *)version_commit_dock->get_parent_control(); - dock_vbc->set_tab_title(dock_vbc->get_tab_idx_from_control(version_commit_dock), TTR("Commit")); - - Button *vc = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock); - set_version_control_tool_button(vc); +void VersionControlEditorPlugin::_toggle_vcs_integration(bool p_toggled) { + if (p_toggled) { + _initialize_vcs(); + } else { + shut_down(); } } -void VersionControlEditorPlugin::fetch_available_vcs_addon_names() { - List<StringName> global_classes; - ScriptServer::get_global_class_list(&global_classes); - - for (int i = 0; i != global_classes.size(); i++) { - String path = ScriptServer::get_global_class_path(global_classes[i]); - Ref<Script> script = ResourceLoader::load(path); - ERR_FAIL_COND(script.is_null()); +void VersionControlEditorPlugin::_project_path_selected(String p_project_path) { + project_path_input->set_text(p_project_path); +} - if (script->get_instance_base_type() == "EditorVCSInterface") { - available_addons.push_back(global_classes[i]); - } - } +void VersionControlEditorPlugin::fetch_available_vcs_plugin_names() { + available_plugins.clear(); + ClassDB::get_direct_inheriters_from_class(EditorVCSInterface::get_class_static(), &available_plugins); } -void VersionControlEditorPlugin::clear_stage_area() { - stage_files->get_root()->clear_children(); +void VersionControlEditorPlugin::register_editor() { + EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock); + + version_control_dock_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock); + + _set_vcs_ui_state(true); } void VersionControlEditorPlugin::shut_down() { - if (EditorVCSInterface::get_singleton()) { - if (EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area))) { - EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); - } - EditorVCSInterface::get_singleton()->shut_down(); - memdelete(EditorVCSInterface::get_singleton()); - EditorVCSInterface::set_singleton(nullptr); + if (!EditorVCSInterface::get_singleton()) { + return; + } - EditorNode::get_singleton()->remove_control_from_dock(version_commit_dock); - EditorNode::get_singleton()->remove_bottom_panel_item(version_control_dock); + if (EditorFileSystem::get_singleton()->is_connected(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area))) { + EditorFileSystem::get_singleton()->disconnect(SNAME("filesystem_changed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); } -} -bool VersionControlEditorPlugin::is_vcs_initialized() const { - return EditorVCSInterface::get_singleton() ? EditorVCSInterface::get_singleton()->is_vcs_initialized() : false; -} + EditorVCSInterface::get_singleton()->shut_down(); + memdelete(EditorVCSInterface::get_singleton()); + EditorVCSInterface::set_singleton(nullptr); + + EditorNode::get_singleton()->remove_control_from_dock(version_commit_dock); + EditorNode::get_singleton()->remove_bottom_panel_item(version_control_dock); -const String VersionControlEditorPlugin::get_vcs_name() const { - return EditorVCSInterface::get_singleton() ? EditorVCSInterface::get_singleton()->get_vcs_name() : ""; + _set_vcs_ui_state(false); } VersionControlEditorPlugin::VersionControlEditorPlugin() { singleton = this; - staged_files_count = 0; version_control_actions = memnew(PopupMenu); metadata_dialog = memnew(ConfirmationDialog); metadata_dialog->set_title(TTR("Create Version Control Metadata")); metadata_dialog->set_min_size(Size2(200, 40)); + metadata_dialog->get_ok_button()->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); version_control_actions->add_child(metadata_dialog); VBoxContainer *metadata_vb = memnew(VBoxContainer); + metadata_dialog->add_child(metadata_vb); + HBoxContainer *metadata_hb = memnew(HBoxContainer); metadata_hb->set_custom_minimum_size(Size2(200, 20)); + metadata_vb->add_child(metadata_hb); + Label *l = memnew(Label); l->set_text(TTR("Create VCS metadata files for:")); metadata_hb->add_child(l); + metadata_selection = memnew(OptionButton); metadata_selection->set_custom_minimum_size(Size2(100, 20)); metadata_selection->add_item("None", (int)EditorVCSInterface::VCSMetadata::NONE); metadata_selection->add_item("Git", (int)EditorVCSInterface::VCSMetadata::GIT); metadata_selection->select((int)EditorVCSInterface::VCSMetadata::GIT); - metadata_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); metadata_hb->add_child(metadata_selection); - metadata_vb->add_child(metadata_hb); + l = memnew(Label); l->set_text(TTR("Existing VCS metadata files will be overwritten.")); metadata_vb->add_child(l); - metadata_dialog->add_child(metadata_vb); set_up_dialog = memnew(AcceptDialog); - set_up_dialog->set_title(TTR("Set Up Version Control")); - set_up_dialog->set_min_size(Size2(400, 100)); + set_up_dialog->set_title(TTR("Local Settings")); + set_up_dialog->set_min_size(Size2(600, 100)); + set_up_dialog->add_cancel_button("Cancel"); + set_up_dialog->set_hide_on_ok(true); version_control_actions->add_child(set_up_dialog); - set_up_ok_button = set_up_dialog->get_ok_button(); - set_up_ok_button->set_text(TTR("Close")); + Button *set_up_apply_button = set_up_dialog->get_ok_button(); + set_up_apply_button->set_text(TTR("Apply")); + set_up_apply_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_set_credentials)); set_up_vbc = memnew(VBoxContainer); set_up_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); set_up_dialog->add_child(set_up_vbc); - set_up_hbc = memnew(HBoxContainer); - set_up_hbc->set_h_size_flags(BoxContainer::SIZE_EXPAND_FILL); + HBoxContainer *set_up_hbc = memnew(HBoxContainer); + set_up_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); set_up_vbc->add_child(set_up_hbc); - set_up_vcs_status = memnew(RichTextLabel); - set_up_vcs_status->set_text(TTR("VCS Addon is not initialized")); - set_up_vbc->add_child(set_up_vcs_status); - - set_up_vcs_label = memnew(Label); - set_up_vcs_label->set_text(TTR("Version Control System")); + Label *set_up_vcs_label = memnew(Label); + set_up_vcs_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_vcs_label->set_text(TTR("VCS Provider")); set_up_hbc->add_child(set_up_vcs_label); set_up_choice = memnew(OptionButton); - set_up_choice->set_h_size_flags(HBoxContainer::SIZE_EXPAND_FILL); - set_up_choice->connect("item_selected", callable_mp(this, &VersionControlEditorPlugin::_selected_a_vcs)); + set_up_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL); set_up_hbc->add_child(set_up_choice); - set_up_init_settings = nullptr; - - set_up_init_button = memnew(Button); - set_up_init_button->set_text(TTR("Initialize")); - set_up_init_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_initialize_vcs)); - set_up_vbc->add_child(set_up_init_button); + HBoxContainer *project_path_hbc = memnew(HBoxContainer); + project_path_hbc->set_h_size_flags(Control::SIZE_FILL); + set_up_vbc->add_child(project_path_hbc); + + Label *project_path_label = memnew(Label); + project_path_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + project_path_label->set_text(TTR("VCS Project Path")); + project_path_hbc->add_child(project_path_label); + + project_path_input = memnew(LineEdit); + project_path_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + project_path_input->set_text(OS::get_singleton()->get_resource_dir()); + project_path_hbc->add_child(project_path_input); + + FileDialog *select_project_path_file_dialog = memnew(FileDialog); + select_project_path_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + select_project_path_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_DIR); + select_project_path_file_dialog->set_show_hidden_files(true); + select_project_path_file_dialog->set_current_dir(OS::get_singleton()->get_resource_dir()); + select_project_path_file_dialog->connect(SNAME("dir_selected"), callable_mp(this, &VersionControlEditorPlugin::_project_path_selected)); + project_path_hbc->add_child(select_project_path_file_dialog); + + select_project_path_button = memnew(Button); + select_project_path_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Folder", "EditorIcons")); + select_project_path_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_file_dialog).bind(select_project_path_file_dialog)); + select_project_path_button->set_tooltip_text(TTR("Select VCS project path")); + project_path_hbc->add_child(select_project_path_button); + + HBoxContainer *toggle_vcs_hbc = memnew(HBoxContainer); + toggle_vcs_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_vbc->add_child(toggle_vcs_hbc); + + Label *toggle_vcs_label = memnew(Label); + toggle_vcs_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + toggle_vcs_label->set_text(TTR("Connect to VCS")); + toggle_vcs_hbc->add_child(toggle_vcs_label); + + toggle_vcs_choice = memnew(CheckButton); + toggle_vcs_choice->set_h_size_flags(Control::SIZE_EXPAND_FILL); + toggle_vcs_choice->set_pressed_no_signal(false); + toggle_vcs_choice->connect(SNAME("toggled"), callable_mp(this, &VersionControlEditorPlugin::_toggle_vcs_integration)); + toggle_vcs_hbc->add_child(toggle_vcs_choice); + + set_up_vbc->add_child(memnew(HSeparator)); + + set_up_settings_vbc = memnew(VBoxContainer); + set_up_settings_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + set_up_vbc->add_child(set_up_settings_vbc); + + Label *remote_login = memnew(Label); + remote_login->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_login->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + remote_login->set_text(TTR("Remote Login")); + set_up_settings_vbc->add_child(remote_login); + + HBoxContainer *set_up_username_input = memnew(HBoxContainer); + set_up_username_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_username_input); + + Label *set_up_username_label = memnew(Label); + set_up_username_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_username_label->set_text(TTR("Username")); + set_up_username_input->add_child(set_up_username_label); + + set_up_username = memnew(LineEdit); + set_up_username->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_username->set_text(EDITOR_DEF("version_control/username", "")); + set_up_username->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_username_input->add_child(set_up_username); + + HBoxContainer *set_up_password_input = memnew(HBoxContainer); + set_up_password_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_password_input); + + Label *set_up_password_label = memnew(Label); + set_up_password_label->set_text(TTR("Password")); + set_up_password_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_password_input->add_child(set_up_password_label); + + set_up_password = memnew(LineEdit); + set_up_password->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_password->set_secret(true); + set_up_password->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_password_input->add_child(set_up_password); + + HBoxContainer *set_up_ssh_public_key_input = memnew(HBoxContainer); + set_up_ssh_public_key_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_public_key_input); + + Label *set_up_ssh_public_key_label = memnew(Label); + set_up_ssh_public_key_label->set_text(TTR("SSH Public Key Path")); + set_up_ssh_public_key_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_input->add_child(set_up_ssh_public_key_label); + + HBoxContainer *set_up_ssh_public_key_input_hbc = memnew(HBoxContainer); + set_up_ssh_public_key_input_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_input->add_child(set_up_ssh_public_key_input_hbc); + + set_up_ssh_public_key_path = memnew(LineEdit); + set_up_ssh_public_key_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_public_key_path->set_text(EDITOR_DEF("version_control/ssh_public_key_path", "")); + set_up_ssh_public_key_path->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_public_key_input_hbc->add_child(set_up_ssh_public_key_path); + + set_up_ssh_public_key_file_dialog = memnew(FileDialog); + set_up_ssh_public_key_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + set_up_ssh_public_key_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + set_up_ssh_public_key_file_dialog->set_show_hidden_files(true); + // TODO: Make this start at the user's home folder + Ref<DirAccess> d = DirAccess::open(OS::get_singleton()->get_system_dir(OS::SYSTEM_DIR_DOCUMENTS)); + d->change_dir("../"); + set_up_ssh_public_key_file_dialog->set_current_dir(d->get_current_dir()); + set_up_ssh_public_key_file_dialog->connect(SNAME("file_selected"), callable_mp(this, &VersionControlEditorPlugin::_ssh_public_key_selected)); + set_up_ssh_public_key_input_hbc->add_child(set_up_ssh_public_key_file_dialog); + + Button *select_public_path_button = memnew(Button); + select_public_path_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Folder", "EditorIcons")); + select_public_path_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_file_dialog).bind(set_up_ssh_public_key_file_dialog)); + select_public_path_button->set_tooltip_text(TTR("Select SSH public key path")); + set_up_ssh_public_key_input_hbc->add_child(select_public_path_button); + + HBoxContainer *set_up_ssh_private_key_input = memnew(HBoxContainer); + set_up_ssh_private_key_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_private_key_input); + + Label *set_up_ssh_private_key_label = memnew(Label); + set_up_ssh_private_key_label->set_text(TTR("SSH Private Key Path")); + set_up_ssh_private_key_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_input->add_child(set_up_ssh_private_key_label); + + HBoxContainer *set_up_ssh_private_key_input_hbc = memnew(HBoxContainer); + set_up_ssh_private_key_input_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_input->add_child(set_up_ssh_private_key_input_hbc); + + set_up_ssh_private_key_path = memnew(LineEdit); + set_up_ssh_private_key_path->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_private_key_path->set_text(EDITOR_DEF("version_control/ssh_private_key_path", "")); + set_up_ssh_private_key_path->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_private_key_input_hbc->add_child(set_up_ssh_private_key_path); + + set_up_ssh_private_key_file_dialog = memnew(FileDialog); + set_up_ssh_private_key_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + set_up_ssh_private_key_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); + set_up_ssh_private_key_file_dialog->set_show_hidden_files(true); + // TODO: Make this start at the user's home folder + set_up_ssh_private_key_file_dialog->set_current_dir(d->get_current_dir()); + set_up_ssh_private_key_file_dialog->connect("file_selected", callable_mp(this, &VersionControlEditorPlugin::_ssh_private_key_selected)); + set_up_ssh_private_key_input_hbc->add_child(set_up_ssh_private_key_file_dialog); + + Button *select_private_path_button = memnew(Button); + select_private_path_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Folder", "EditorIcons")); + select_private_path_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_file_dialog).bind(set_up_ssh_private_key_file_dialog)); + select_private_path_button->set_tooltip_text(TTR("Select SSH private key path")); + set_up_ssh_private_key_input_hbc->add_child(select_private_path_button); + + HBoxContainer *set_up_ssh_passphrase_input = memnew(HBoxContainer); + set_up_ssh_passphrase_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_ssh_passphrase_input); + + Label *set_up_ssh_passphrase_label = memnew(Label); + set_up_ssh_passphrase_label->set_text(TTR("SSH Passphrase")); + set_up_ssh_passphrase_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_passphrase_input->add_child(set_up_ssh_passphrase_label); + + set_up_ssh_passphrase = memnew(LineEdit); + set_up_ssh_passphrase->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_ssh_passphrase->set_secret(true); + set_up_ssh_passphrase->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_set_up_warning)); + set_up_ssh_passphrase_input->add_child(set_up_ssh_passphrase); + + set_up_warning_text = memnew(Label); + set_up_warning_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + set_up_warning_text->set_h_size_flags(Control::SIZE_EXPAND_FILL); + set_up_settings_vbc->add_child(set_up_warning_text); version_commit_dock = memnew(VBoxContainer); version_commit_dock->set_visible(false); + version_commit_dock->set_name(TTR("Commit")); - commit_box_vbc = memnew(VBoxContainer); - commit_box_vbc->set_alignment(VBoxContainer::ALIGNMENT_BEGIN); - commit_box_vbc->set_h_size_flags(VBoxContainer::SIZE_EXPAND_FILL); - commit_box_vbc->set_v_size_flags(VBoxContainer::SIZE_EXPAND_FILL); - version_commit_dock->add_child(commit_box_vbc); + VBoxContainer *unstage_area = memnew(VBoxContainer); + unstage_area->set_v_size_flags(Control::SIZE_EXPAND_FILL); + unstage_area->set_h_size_flags(Control::SIZE_EXPAND_FILL); + version_commit_dock->add_child(unstage_area); - stage_tools = memnew(HSplitContainer); - stage_tools->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); - commit_box_vbc->add_child(stage_tools); + HBoxContainer *unstage_title = memnew(HBoxContainer); + unstage_area->add_child(unstage_title); - staging_area_label = memnew(Label); - staging_area_label->set_h_size_flags(Label::SIZE_EXPAND_FILL); - staging_area_label->set_text(TTR("Staging area")); - stage_tools->add_child(staging_area_label); + Label *unstage_label = memnew(Label); + unstage_label->set_text(TTR("Unstaged Changes")); + unstage_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + unstage_title->add_child(unstage_label); refresh_button = memnew(Button); refresh_button->set_tooltip_text(TTR("Detect new changes")); - refresh_button->set_text(TTR("Refresh")); + refresh_button->set_flat(true); refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); - refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); - stage_tools->add_child(refresh_button); - - stage_files = memnew(Tree); - stage_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); - stage_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); - stage_files->set_columns(1); - stage_files->set_column_title(0, TTR("Changes")); - stage_files->set_column_titles_visible(true); - stage_files->set_allow_reselect(true); - stage_files->set_allow_rmb_select(true); - stage_files->set_select_mode(Tree::SelectMode::SELECT_MULTI); - stage_files->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true); - stage_files->connect("cell_selected", callable_mp(this, &VersionControlEditorPlugin::_view_file_diff)); - stage_files->create_item(); - stage_files->set_hide_root(true); - commit_box_vbc->add_child(stage_files); - - change_type_to_strings[CHANGE_TYPE_NEW] = TTR("New"); - change_type_to_strings[CHANGE_TYPE_MODIFIED] = TTR("Modified"); - change_type_to_strings[CHANGE_TYPE_RENAMED] = TTR("Renamed"); - change_type_to_strings[CHANGE_TYPE_DELETED] = TTR("Deleted"); - change_type_to_strings[CHANGE_TYPE_TYPECHANGE] = TTR("Typechange"); - - change_type_to_color[CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); - change_type_to_color[CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")); - - stage_buttons = memnew(HSplitContainer); - stage_buttons->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); - commit_box_vbc->add_child(stage_buttons); - - stage_selected_button = memnew(Button); - stage_selected_button->set_h_size_flags(Button::SIZE_EXPAND_FILL); - stage_selected_button->set_text(TTR("Stage Selected")); - stage_selected_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_stage_selected)); - stage_buttons->add_child(stage_selected_button); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_stage_area)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_commit_list)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_branch_list)); + refresh_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_remote_list)); + unstage_title->add_child(refresh_button); + + discard_all_button = memnew(Button); + discard_all_button->set_tooltip_text(TTR("Discard all changes")); + discard_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + discard_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_discard_all)); + discard_all_button->set_flat(true); + unstage_title->add_child(discard_all_button); stage_all_button = memnew(Button); - stage_all_button->set_text(TTR("Stage All")); - stage_all_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_stage_all)); - stage_buttons->add_child(stage_all_button); - - commit_box_vbc->add_child(memnew(HSeparator)); + stage_all_button->set_flat(true); + stage_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons"))); + stage_all_button->set_tooltip_text(TTR("Stage all changes")); + unstage_title->add_child(stage_all_button); + + unstaged_files = memnew(Tree); + unstaged_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); + unstaged_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); + unstaged_files->set_select_mode(Tree::SELECT_ROW); + unstaged_files->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(unstaged_files)); + unstaged_files->connect(SNAME("item_activated"), callable_mp(this, &VersionControlEditorPlugin::_item_activated).bind(unstaged_files)); + unstaged_files->connect(SNAME("button_clicked"), callable_mp(this, &VersionControlEditorPlugin::_cell_button_pressed)); + unstaged_files->create_item(); + unstaged_files->set_hide_root(true); + unstage_area->add_child(unstaged_files); + + VBoxContainer *stage_area = memnew(VBoxContainer); + stage_area->set_v_size_flags(Control::SIZE_EXPAND_FILL); + stage_area->set_h_size_flags(Control::SIZE_EXPAND_FILL); + version_commit_dock->add_child(stage_area); + + HBoxContainer *stage_title = memnew(HBoxContainer); + stage_area->add_child(stage_title); + + Label *stage_label = memnew(Label); + stage_label->set_text(TTR("Staged Changes")); + stage_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + stage_title->add_child(stage_label); + + unstage_all_button = memnew(Button); + unstage_all_button->set_flat(true); + unstage_all_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons"))); + unstage_all_button->set_tooltip_text(TTR("Unstage all changes")); + stage_title->add_child(unstage_all_button); + + staged_files = memnew(Tree); + staged_files->set_h_size_flags(Tree::SIZE_EXPAND_FILL); + staged_files->set_v_size_flags(Tree::SIZE_EXPAND_FILL); + staged_files->set_select_mode(Tree::SELECT_ROW); + staged_files->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(staged_files)); + staged_files->connect(SNAME("button_clicked"), callable_mp(this, &VersionControlEditorPlugin::_cell_button_pressed)); + staged_files->connect(SNAME("item_activated"), callable_mp(this, &VersionControlEditorPlugin::_item_activated).bind(staged_files)); + staged_files->create_item(); + staged_files->set_hide_root(true); + stage_area->add_child(staged_files); + + // Editor crashes if bind is null + unstage_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_move_all).bind(staged_files)); + stage_all_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_move_all).bind(unstaged_files)); + + version_commit_dock->add_child(memnew(HSeparator)); + + VBoxContainer *commit_area = memnew(VBoxContainer); + version_commit_dock->add_child(commit_area); + + Label *commit_label = memnew(Label); + commit_label->set_text(TTR("Commit Message")); + commit_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_area->add_child(commit_label); commit_message = memnew(TextEdit); commit_message->set_h_size_flags(Control::SIZE_EXPAND_FILL); commit_message->set_h_grow_direction(Control::GrowDirection::GROW_DIRECTION_BEGIN); commit_message->set_v_grow_direction(Control::GrowDirection::GROW_DIRECTION_END); commit_message->set_custom_minimum_size(Size2(200, 100)); - commit_message->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); - commit_message->connect("text_changed", callable_mp(this, &VersionControlEditorPlugin::_update_commit_button)); - commit_message->connect("gui_input", callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input)); - commit_box_vbc->add_child(commit_message); + commit_message->set_line_wrapping_mode(TextEdit::LINE_WRAPPING_BOUNDARY); + commit_message->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_commit_button)); + commit_message->connect(SNAME("gui_input"), callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input)); + commit_area->add_child(commit_message); + ED_SHORTCUT("version_control/commit", TTR("Commit"), KeyModifierMask::CMD | Key::ENTER); commit_button = memnew(Button); commit_button->set_text(TTR("Commit Changes")); commit_button->set_disabled(true); - commit_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_send_commit_msg)); - commit_box_vbc->add_child(commit_button); - - commit_status = memnew(Label); - commit_status->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); - commit_box_vbc->add_child(commit_status); - - version_control_dock = memnew(PanelContainer); + commit_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_commit)); + commit_area->add_child(commit_button); + + version_commit_dock->add_child(memnew(HSeparator)); + + HBoxContainer *commit_list_hbc = memnew(HBoxContainer); + version_commit_dock->add_child(commit_list_hbc); + + Label *commit_list_label = memnew(Label); + commit_list_label->set_text(TTR("Commit List")); + commit_list_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_list_hbc->add_child(commit_list_label); + + commit_list_size_button = memnew(OptionButton); + commit_list_size_button->set_tooltip_text(TTR("Commit list size")); + commit_list_size_button->add_item("10"); + commit_list_size_button->set_item_metadata(0, 10); + commit_list_size_button->add_item("20"); + commit_list_size_button->set_item_metadata(1, 20); + commit_list_size_button->add_item("30"); + commit_list_size_button->set_item_metadata(2, 30); + commit_list_size_button->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_set_commit_list_size)); + commit_list_hbc->add_child(commit_list_size_button); + + commit_list = memnew(Tree); + commit_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + commit_list->set_v_grow_direction(Control::GrowDirection::GROW_DIRECTION_END); + commit_list->set_custom_minimum_size(Size2(200, 160)); + commit_list->create_item(); + commit_list->set_hide_root(true); + commit_list->set_select_mode(Tree::SELECT_ROW); + commit_list->set_columns(2); // Commit msg, author + commit_list->set_column_custom_minimum_width(0, 40); + commit_list->set_column_custom_minimum_width(1, 20); + commit_list->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_load_diff).bind(commit_list)); + version_commit_dock->add_child(commit_list); + + version_commit_dock->add_child(memnew(HSeparator)); + + HBoxContainer *menu_bar = memnew(HBoxContainer); + menu_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL); + menu_bar->set_v_size_flags(Control::SIZE_FILL); + version_commit_dock->add_child(menu_bar); + + branch_select = memnew(OptionButton); + branch_select->set_tooltip_text(TTR("Branches")); + branch_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_branch_item_selected)); + branch_select->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_branch_list)); + menu_bar->add_child(branch_select); + + branch_create_confirm = memnew(AcceptDialog); + branch_create_confirm->set_title(TTR("Create New Branch")); + branch_create_confirm->set_min_size(Size2(400, 100)); + branch_create_confirm->set_hide_on_ok(true); + version_commit_dock->add_child(branch_create_confirm); + + branch_create_ok = branch_create_confirm->get_ok_button(); + branch_create_ok->set_text(TTR("Create")); + branch_create_ok->set_disabled(true); + branch_create_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_branch)); + + branch_remove_confirm = memnew(AcceptDialog); + branch_remove_confirm->set_title(TTR("Remove Branch")); + branch_remove_confirm->add_cancel_button(); + version_commit_dock->add_child(branch_remove_confirm); + + Button *branch_remove_ok = branch_remove_confirm->get_ok_button(); + branch_remove_ok->set_text(TTR("Remove")); + branch_remove_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_remove_branch)); + + VBoxContainer *branch_create_vbc = memnew(VBoxContainer); + branch_create_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + branch_create_confirm->add_child(branch_create_vbc); + + HBoxContainer *branch_create_hbc = memnew(HBoxContainer); + branch_create_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_vbc->add_child(branch_create_hbc); + + Label *branch_create_name_label = memnew(Label); + branch_create_name_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_name_label->set_text(TTR("Branch Name")); + branch_create_hbc->add_child(branch_create_name_label); + + branch_create_name_input = memnew(LineEdit); + branch_create_name_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + branch_create_name_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_branch_create_button)); + branch_create_hbc->add_child(branch_create_name_input); + + remote_select = memnew(OptionButton); + remote_select->set_tooltip_text(TTR("Remotes")); + remote_select->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_remote_selected)); + remote_select->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_refresh_remote_list)); + menu_bar->add_child(remote_select); + + remote_create_confirm = memnew(AcceptDialog); + remote_create_confirm->set_title(TTR("Create New Remote")); + remote_create_confirm->set_min_size(Size2(400, 100)); + remote_create_confirm->set_hide_on_ok(true); + version_commit_dock->add_child(remote_create_confirm); + + remote_create_ok = remote_create_confirm->get_ok_button(); + remote_create_ok->set_text(TTR("Create")); + remote_create_ok->set_disabled(true); + remote_create_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_create_remote)); + + remote_remove_confirm = memnew(AcceptDialog); + remote_remove_confirm->set_title(TTR("Remove Remote")); + remote_remove_confirm->add_cancel_button(); + version_commit_dock->add_child(remote_remove_confirm); + + Button *remote_remove_ok = remote_remove_confirm->get_ok_button(); + remote_remove_ok->set_text(TTR("Remove")); + remote_remove_ok->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_remove_remote)); + + VBoxContainer *remote_create_vbc = memnew(VBoxContainer); + remote_create_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); + remote_create_confirm->add_child(remote_create_vbc); + + HBoxContainer *remote_create_name_hbc = memnew(HBoxContainer); + remote_create_name_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_vbc->add_child(remote_create_name_hbc); + + Label *remote_create_name_label = memnew(Label); + remote_create_name_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_name_label->set_text(TTR("Remote Name")); + remote_create_name_hbc->add_child(remote_create_name_label); + + remote_create_name_input = memnew(LineEdit); + remote_create_name_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_name_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_remote_create_button)); + remote_create_name_hbc->add_child(remote_create_name_input); + + HBoxContainer *remote_create_hbc = memnew(HBoxContainer); + remote_create_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_vbc->add_child(remote_create_hbc); + + Label *remote_create_url_label = memnew(Label); + remote_create_url_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_url_label->set_text(TTR("Remote URL")); + remote_create_hbc->add_child(remote_create_url_label); + + remote_create_url_input = memnew(LineEdit); + remote_create_url_input->set_h_size_flags(Control::SIZE_EXPAND_FILL); + remote_create_url_input->connect(SNAME("text_changed"), callable_mp(this, &VersionControlEditorPlugin::_update_remote_create_button)); + remote_create_hbc->add_child(remote_create_url_input); + + fetch_button = memnew(Button); + fetch_button->set_flat(true); + fetch_button->set_tooltip_text(TTR("Fetch")); + fetch_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + fetch_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_fetch)); + menu_bar->add_child(fetch_button); + + pull_button = memnew(Button); + pull_button->set_flat(true); + pull_button->set_tooltip_text(TTR("Pull")); + pull_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveDown"), SNAME("EditorIcons"))); + pull_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_pull)); + menu_bar->add_child(pull_button); + + push_button = memnew(Button); + push_button->set_flat(true); + push_button->set_tooltip_text(TTR("Push")); + push_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MoveUp"), SNAME("EditorIcons"))); + push_button->connect(SNAME("pressed"), callable_mp(this, &VersionControlEditorPlugin::_push)); + menu_bar->add_child(push_button); + + extra_options = memnew(MenuButton); + extra_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + extra_options->get_popup()->connect(SNAME("about_to_popup"), callable_mp(this, &VersionControlEditorPlugin::_update_extra_options)); + extra_options->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_extra_option_selected)); + menu_bar->add_child(extra_options); + + extra_options->get_popup()->add_item(TTR("Force Push"), EXTRA_OPTION_FORCE_PUSH); + extra_options->get_popup()->add_separator(); + extra_options->get_popup()->add_item(TTR("Create New Branch"), EXTRA_OPTION_CREATE_BRANCH); + + extra_options_remove_branch_list = memnew(PopupMenu); + extra_options_remove_branch_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_branch_remove_confirm)); + extra_options_remove_branch_list->set_name("Remove Branch"); + extra_options->get_popup()->add_child(extra_options_remove_branch_list); + extra_options->get_popup()->add_submenu_item(TTR("Remove Branch"), "Remove Branch"); + + extra_options->get_popup()->add_separator(); + extra_options->get_popup()->add_item(TTR("Create New Remote"), EXTRA_OPTION_CREATE_REMOTE); + + extra_options_remove_remote_list = memnew(PopupMenu); + extra_options_remove_remote_list->connect(SNAME("id_pressed"), callable_mp(this, &VersionControlEditorPlugin::_popup_remote_remove_confirm)); + extra_options_remove_remote_list->set_name("Remove Remote"); + extra_options->get_popup()->add_child(extra_options_remove_remote_list); + extra_options->get_popup()->add_submenu_item(TTR("Remove Remote"), "Remove Remote"); + + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_NEW] = TTR("New"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = TTR("Modified"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_RENAMED] = TTR("Renamed"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_DELETED] = TTR("Deleted"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = TTR("Typechange"); + change_type_to_strings[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = TTR("Unmerged"); + + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("success_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("error_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("font_color"), SNAME("Editor")); + change_type_to_color[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("warning_color"), SNAME("Editor")); + + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_NEW] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusSuccess"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_MODIFIED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_RENAMED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_TYPECHANGE] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_DELETED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusError"), SNAME("EditorIcons")); + change_type_to_icon[EditorVCSInterface::CHANGE_TYPE_UNMERGED] = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); + + version_control_dock = memnew(VBoxContainer); version_control_dock->set_v_size_flags(Control::SIZE_EXPAND_FILL); version_control_dock->set_custom_minimum_size(Size2(0, 300) * EDSCALE); version_control_dock->hide(); - diff_vbc = memnew(VBoxContainer); - diff_vbc->set_h_size_flags(HBoxContainer::SIZE_FILL); - diff_vbc->set_v_size_flags(HBoxContainer::SIZE_FILL); - version_control_dock->add_child(diff_vbc); - - diff_hbc = memnew(HBoxContainer); - diff_hbc->set_h_size_flags(HBoxContainer::SIZE_FILL); - diff_vbc->add_child(diff_hbc); - - diff_heading = memnew(Label); - diff_heading->set_text(TTR("Status")); + HBoxContainer *diff_heading = memnew(HBoxContainer); + diff_heading->set_h_size_flags(Control::SIZE_EXPAND_FILL); diff_heading->set_tooltip_text(TTR("View file diffs before committing them to the latest version")); - diff_hbc->add_child(diff_heading); + version_control_dock->add_child(diff_heading); - diff_file_name = memnew(Label); - diff_file_name->set_text(TTR("No file diff is active")); - diff_file_name->set_h_size_flags(Label::SIZE_EXPAND_FILL); - diff_file_name->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - diff_hbc->add_child(diff_file_name); + diff_title = memnew(Label); + diff_title->set_h_size_flags(Control::SIZE_EXPAND_FILL); + diff_heading->add_child(diff_title); - diff_refresh_button = memnew(Button); - diff_refresh_button->set_tooltip_text(TTR("Detect changes in file diff")); - diff_refresh_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); - diff_refresh_button->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_refresh_file_diff)); - diff_hbc->add_child(diff_refresh_button); + Label *view = memnew(Label); + view->set_text(TTR("View:")); + diff_heading->add_child(view); + + diff_view_type_select = memnew(OptionButton); + diff_view_type_select->add_item(TTR("Split"), DIFF_VIEW_TYPE_SPLIT); + diff_view_type_select->add_item(TTR("Unified"), DIFF_VIEW_TYPE_UNIFIED); + diff_view_type_select->connect(SNAME("item_selected"), callable_mp(this, &VersionControlEditorPlugin::_display_diff)); + diff_heading->add_child(diff_view_type_select); diff = memnew(RichTextLabel); diff->set_h_size_flags(TextEdit::SIZE_EXPAND_FILL); diff->set_v_size_flags(TextEdit::SIZE_EXPAND_FILL); + diff->set_use_bbcode(true); diff->set_selection_enabled(true); - diff_vbc->add_child(diff); + version_control_dock->add_child(diff); + + _update_set_up_warning(""); } VersionControlEditorPlugin::~VersionControlEditorPlugin() { shut_down(); - memdelete(version_control_dock); memdelete(version_commit_dock); + memdelete(version_control_dock); memdelete(version_control_actions); } diff --git a/editor/plugins/version_control_editor_plugin.h b/editor/plugins/version_control_editor_plugin.h index fa721268ba..3340384a92 100644 --- a/editor/plugins/version_control_editor_plugin.h +++ b/editor/plugins/version_control_editor_plugin.h @@ -33,9 +33,12 @@ #include "editor/editor_plugin.h" #include "editor/editor_vcs_interface.h" -#include "scene/gui/box_container.h" +#include "scene/gui/check_button.h" +#include "scene/gui/container.h" +#include "scene/gui/file_dialog.h" +#include "scene/gui/menu_button.h" #include "scene/gui/rich_text_label.h" -#include "scene/gui/split_container.h" +#include "scene/gui/tab_container.h" #include "scene/gui/text_edit.h" #include "scene/gui/tree.h" @@ -43,79 +46,154 @@ class VersionControlEditorPlugin : public EditorPlugin { GDCLASS(VersionControlEditorPlugin, EditorPlugin) public: - enum ChangeType { - CHANGE_TYPE_NEW = 0, - CHANGE_TYPE_MODIFIED = 1, - CHANGE_TYPE_RENAMED = 2, - CHANGE_TYPE_DELETED = 3, - CHANGE_TYPE_TYPECHANGE = 4 + enum ButtonType { + BUTTON_TYPE_OPEN = 0, + BUTTON_TYPE_DISCARD = 1, + }; + + enum DiffViewType { + DIFF_VIEW_TYPE_SPLIT = 0, + DIFF_VIEW_TYPE_UNIFIED = 1, + }; + + enum ExtraOption { + EXTRA_OPTION_FORCE_PUSH, + EXTRA_OPTION_CREATE_BRANCH, + EXTRA_OPTION_CREATE_REMOTE, }; private: static VersionControlEditorPlugin *singleton; - int staged_files_count; - List<StringName> available_addons; + List<StringName> available_plugins; PopupMenu *version_control_actions = nullptr; ConfirmationDialog *metadata_dialog = nullptr; OptionButton *metadata_selection = nullptr; AcceptDialog *set_up_dialog = nullptr; - VBoxContainer *set_up_vbc = nullptr; - HBoxContainer *set_up_hbc = nullptr; - Label *set_up_vcs_label = nullptr; + CheckButton *toggle_vcs_choice = nullptr; OptionButton *set_up_choice = nullptr; - PanelContainer *set_up_init_settings = nullptr; - Button *set_up_init_button = nullptr; - RichTextLabel *set_up_vcs_status = nullptr; - Button *set_up_ok_button = nullptr; - - HashMap<ChangeType, String> change_type_to_strings; - HashMap<ChangeType, Color> change_type_to_color; + LineEdit *project_path_input = nullptr; + Button *select_project_path_button = nullptr; + VBoxContainer *set_up_vbc = nullptr; + VBoxContainer *set_up_settings_vbc = nullptr; + LineEdit *set_up_username = nullptr; + LineEdit *set_up_password = nullptr; + LineEdit *set_up_ssh_public_key_path = nullptr; + LineEdit *set_up_ssh_private_key_path = nullptr; + LineEdit *set_up_ssh_passphrase = nullptr; + FileDialog *set_up_ssh_public_key_file_dialog = nullptr; + FileDialog *set_up_ssh_private_key_file_dialog = nullptr; + Label *set_up_warning_text = nullptr; + + OptionButton *commit_list_size_button = nullptr; + + AcceptDialog *branch_create_confirm = nullptr; + LineEdit *branch_create_name_input = nullptr; + Button *branch_create_ok = nullptr; + + AcceptDialog *remote_create_confirm = nullptr; + LineEdit *remote_create_name_input = nullptr; + LineEdit *remote_create_url_input = nullptr; + Button *remote_create_ok = nullptr; + + HashMap<EditorVCSInterface::ChangeType, String> change_type_to_strings; + HashMap<EditorVCSInterface::ChangeType, Color> change_type_to_color; + HashMap<EditorVCSInterface::ChangeType, Ref<Texture>> change_type_to_icon; VBoxContainer *version_commit_dock = nullptr; - VBoxContainer *commit_box_vbc = nullptr; - HSplitContainer *stage_tools = nullptr; - Tree *stage_files = nullptr; - TreeItem *new_files = nullptr; - TreeItem *modified_files = nullptr; - TreeItem *renamed_files = nullptr; - TreeItem *deleted_files = nullptr; - TreeItem *typechange_files = nullptr; - Label *staging_area_label = nullptr; - HSplitContainer *stage_buttons = nullptr; + Tree *staged_files = nullptr; + Tree *unstaged_files = nullptr; + Tree *commit_list = nullptr; + + OptionButton *branch_select = nullptr; + Button *branch_remove_button = nullptr; + AcceptDialog *branch_remove_confirm = nullptr; + + Button *fetch_button = nullptr; + Button *pull_button = nullptr; + Button *push_button = nullptr; + OptionButton *remote_select = nullptr; + Button *remote_remove_button = nullptr; + AcceptDialog *remote_remove_confirm = nullptr; + MenuButton *extra_options = nullptr; + PopupMenu *extra_options_remove_branch_list = nullptr; + PopupMenu *extra_options_remove_remote_list = nullptr; + + String branch_to_remove; + String remote_to_remove; + Button *stage_all_button = nullptr; - Button *stage_selected_button = nullptr; + Button *unstage_all_button = nullptr; + Button *discard_all_button = nullptr; Button *refresh_button = nullptr; TextEdit *commit_message = nullptr; Button *commit_button = nullptr; - Label *commit_status = nullptr; - PanelContainer *version_control_dock = nullptr; + VBoxContainer *version_control_dock = nullptr; Button *version_control_dock_button = nullptr; - VBoxContainer *diff_vbc = nullptr; - HBoxContainer *diff_hbc = nullptr; - Button *diff_refresh_button = nullptr; - Label *diff_file_name = nullptr; - Label *diff_heading = nullptr; + Label *diff_title = nullptr; RichTextLabel *diff = nullptr; + OptionButton *diff_view_type_select = nullptr; + bool show_commit_diff_header = false; + List<EditorVCSInterface::DiffFile> diff_content; - void _populate_available_vcs_names(); - void _create_vcs_metadata_files(); - void _selected_a_vcs(int p_id); + void _notification(int p_what); void _initialize_vcs(); - void _send_commit_msg(); + void _set_vcs_ui_state(bool p_enabled); + void _set_credentials(); + void _ssh_public_key_selected(String p_path); + void _ssh_private_key_selected(String p_path); + void _populate_available_vcs_names(); + void _update_remotes_list(); + void _update_set_up_warning(String p_new_text); + void _update_opened_tabs(); + void _update_extra_options(); + + bool _load_plugin(String p_name, String p_project_path); + + void _pull(); + void _push(); + void _force_push(); + void _fetch(); + void _commit(); + void _discard_all(); void _refresh_stage_area(); - void _stage_selected(); - void _stage_all(); - void _view_file_diff(); - void _display_file_diff(String p_file_path); - void _refresh_file_diff(); - void _clear_file_diff(); - void _update_stage_status(); - void _update_commit_status(); + void _refresh_branch_list(); + void _set_commit_list_size(int p_index); + void _refresh_commit_list(); + void _refresh_remote_list(); + void _display_diff(int p_idx); + void _move_all(Object *p_tree); + void _load_diff(Object *p_tree); + void _clear_diff(); + int _get_item_count(Tree *p_tree); + void _item_activated(Object *p_tree); + void _create_branch(); + void _create_remote(); + void _update_branch_create_button(String p_new_text); + void _update_remote_create_button(String p_new_text); + void _branch_item_selected(int p_index); + void _remote_selected(int p_index); + void _remove_branch(); + void _remove_remote(); + void _popup_branch_remove_confirm(int p_index); + void _popup_remote_remove_confirm(int p_index); + void _move_item(Tree *p_tree, TreeItem *p_itme); + void _display_diff_split_view(List<EditorVCSInterface::DiffLine> &p_diff_content); + void _display_diff_unified_view(List<EditorVCSInterface::DiffLine> &p_diff_content); + void _discard_file(String p_file_path, EditorVCSInterface::ChangeType p_change); + void _cell_button_pressed(Object *p_item, int p_column, int p_id, int p_mouse_button_index); + void _add_new_item(Tree *p_tree, String p_file_path, EditorVCSInterface::ChangeType p_change); void _update_commit_button(); void _commit_message_gui_input(const Ref<InputEvent> &p_event); + void _extra_option_selected(int p_index); + bool _is_staging_area_empty(); + String _get_date_string_from(int64_t p_unix_timestamp, int64_t p_offset_minutes) const; + void _create_vcs_metadata_files(); + void _popup_file_dialog(Variant p_file_dialog_variant); + void _toggle_vcs_integration(bool p_toggled); + void _project_path_selected(String p_project_path); friend class EditorVCSInterface; @@ -127,25 +205,15 @@ public: void popup_vcs_metadata_dialog(); void popup_vcs_set_up_dialog(const Control *p_gui_base); - void set_version_control_tool_button(Button *p_button) { version_control_dock_button = p_button; } PopupMenu *get_version_control_actions_panel() const { return version_control_actions; } - VBoxContainer *get_version_commit_dock() const { return version_commit_dock; } - PanelContainer *get_version_control_dock() const { return version_control_dock; } - - List<StringName> get_available_vcs_names() const { return available_addons; } - bool is_vcs_initialized() const; - const String get_vcs_name() const; void register_editor(); - void fetch_available_vcs_addon_names(); - void clear_stage_area(); + void fetch_available_vcs_plugin_names(); void shut_down(); VersionControlEditorPlugin(); ~VersionControlEditorPlugin(); }; -VARIANT_ENUM_CAST(VersionControlEditorPlugin::ChangeType); - #endif // VERSION_CONTROL_EDITOR_PLUGIN_H |