diff --git a/src/Dialogs/CloneRepositoryDialog.vala b/src/Dialogs/CloneRepositoryDialog.vala index bdaeee14f..4c7d73f61 100644 --- a/src/Dialogs/CloneRepositoryDialog.vala +++ b/src/Dialogs/CloneRepositoryDialog.vala @@ -25,14 +25,15 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog { public CloneRepositoryDialog (string _suggested_local_folder) { Object ( - transient_for: ((Gtk.Application)(GLib.Application.get_default ())).get_active_window (), - image_icon: new ThemedIcon ("git"), - modal: true, suggested_local_folder: _suggested_local_folder ); } construct { + transient_for = ((Gtk.Application)(GLib.Application.get_default ())).get_active_window (); + image_icon = new ThemedIcon ("git"); + modal = true; + try { name_regex = new Regex (NAME_REGEX, OPTIMIZE, ANCHORED | NOTEMPTY); } catch (RegexError e) { diff --git a/src/Dialogs/CloningProgressDialog.vala b/src/Dialogs/CloningProgressDialog.vala new file mode 100644 index 000000000..29fc341fe --- /dev/null +++ b/src/Dialogs/CloningProgressDialog.vala @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: GPL-2.0-or-later + * SPDX-FileCopyrightText: 2025 elementary, Inc. + * + * Authored by: Jeremy Wootten + */ + +public class Scratch.Dialogs.CloningProgressDialog : Granite.MessageDialog { + + public string remote_uri { get; construct; } + public string local_folder_path { get; construct; } + + public CloningProgressDialog (MainWindow parent, string remote_uri, string local_folder_path) { + Object ( + transient_for: parent, + buttons: Gtk.ButtonsType.NONE, + remote_uri: remote_uri, + local_folder_path: local_folder_path + ); + } + + construct { + image_icon = new ThemedIcon ("git"); + primary_text = _("Cloning a remote repository is in progress"); + secondary_text = _("Source: '%s' Cloning to: '%s'").printf (remote_uri, local_folder_path); + } + + public void update_status (Scratch.Services.CloningStatus status, string? message = null) { + switch (status) { + case START: + return; + case END_SUCCESS: + primary_text = _("Cloning succeeded"); + break; + case END_FAIL: + primary_text = _("Cloning failed"); + break; + } + + secondary_text = message; + add_button (_("Ok"), Gtk.ResponseType.ACCEPT); + } +} diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 5b2a1ace5..999fc99ef 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -275,6 +275,7 @@ namespace Scratch { document_manager = Scratch.Services.DocumentManager.get_instance (); git_manager = Services.GitManager.get_instance (); + git_manager.cloning_status.connect (on_cloning_status_changed); actions = new SimpleActionGroup (); actions.add_action_entries (ACTION_ENTRIES, this); @@ -1043,44 +1044,61 @@ namespace Scratch { } private void action_clone_repo (SimpleAction action, Variant? param) { - var uri = ""; - //By default, create clone in parent of the current project - var local_folder = Path.get_dirname (git_manager.active_project_path); - var local_name = ""; - var clone_dialog = new Dialogs.CloneRepositoryDialog (local_folder); - clone_dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.APPLY) { - uri = clone_dialog.get_source_repository_uri (); - local_folder = clone_dialog.get_local_folder (); - local_name = clone_dialog.get_local_name (); - } + if (cloning_progress_dialog != null) { + critical ("Cloning attempt while cloning in progress"); + // We do not support simultaneous cloning at the moment. + return; + } + var default_folder = git_manager.active_project_path != "" ? + Path.get_dirname (git_manager.active_project_path) : ""; + var clone_dialog = new Dialogs.CloneRepositoryDialog (default_folder); + clone_dialog.response.connect ((res) => { + var uri = clone_dialog.get_source_repository_uri (); + var local_folder = clone_dialog.get_local_folder (); + var local_name = clone_dialog.get_local_name (); + var can_clone = clone_dialog.can_clone; + // MainWindow should provide feedback on cloning progress - close modal dialog now clone_dialog.destroy (); + if (res == Gtk.ResponseType.APPLY && can_clone) { // Should not need second test? + //TODO Show progress while cloning + cloning_progress_dialog = new Scratch.Dialogs.CloningProgressDialog (this, uri, local_folder); + cloning_progress_dialog.response.connect ((res) => { + cloning_progress_dialog.destroy (); + cloning_progress_dialog = null; + }); + cloning_progress_dialog.present (); + + //Need tiimout in order for dialog to be drawn else cloning blocks it. + //TODO Find a more elegant way + Timeout.add (500, () => { + git_manager.clone_repository.begin ( + uri, + Path.build_filename (Path.DIR_SEPARATOR_S, local_folder, local_name), + (obj, res) => { + File? workdir = null; + if (git_manager.clone_repository.end (res, out workdir)) { + debug ("Repository cloned into %s", workdir.get_uri ()); + //TODO Optionally open folder from progress dialog? + open_folder (workdir); + } + } + ); + return Source.REMOVE; + }); + } }); clone_dialog.present (); + } - if (clone_dialog.can_clone) { - //TODO Show progress while cloning - git_manager.clone_repository.begin ( - uri, - Path.build_filename (Path.DIR_SEPARATOR_S, local_folder, local_name), - (obj, res) => { - try { - File? workdir = null; - if (git_manager.clone_repository.end (res, out workdir)) { - debug ("Repository cloned into %s", workdir.get_uri ()); - open_folder (workdir); - //TODO Make active according to dialog checkbox - } - } catch (Error e) { - warning ("Unable to clone '%s'. %s", uri, e.message); - } - } - ); - } else { - //TODO Give feedback - } + private Scratch.Dialogs.CloningProgressDialog? cloning_progress_dialog; + private void on_cloning_status_changed ( + Scratch.Services.CloningStatus status, + string? message = null + ) requires (cloning_progress_dialog != null) { + + cloning_progress_dialog.update_status (status, message); } private void action_collapse_all_folders () { diff --git a/src/Services/GitManager.vala b/src/Services/GitManager.vala index 20097fbbd..89e3aeebb 100644 --- a/src/Services/GitManager.vala +++ b/src/Services/GitManager.vala @@ -19,7 +19,14 @@ */ namespace Scratch.Services { + public enum CloningStatus { + START, + END_SUCCESS, + END_FAIL + } + public class GitManager : Object { + public signal void cloning_status (CloningStatus cloning_status, string? message = null); public ListStore project_liststore { get; private set; } public string active_project_path { get; set; default = "";} @@ -117,9 +124,7 @@ namespace Scratch.Services { string uri, string local_folder, out File? repo_workdir - ) throws Error { - - repo_workdir = null; + ) { var folder_file = File.new_for_path (local_folder); var fetch_options = new Ggit.FetchOptions (); @@ -131,17 +136,38 @@ namespace Scratch.Services { clone_options.set_is_bare (false); clone_options.set_fetch_options (fetch_options); - var new_repo = Ggit.Repository.clone ( - uri, - folder_file, - clone_options - ); + cloning_status (START); + // Gtk.main_iteration (); + var success = false; + File? workdir = null; + Idle.add (() => { + try { + var new_repo = Ggit.Repository.clone ( + uri, + folder_file, + clone_options + ); + + if (new_repo != null) { + workdir = new_repo.get_workdir (); + } + } catch (Error e) { + warning ("Error cloning %s", e.message); + cloning_status (END_FAIL, e.message); + } + + clone_repository.callback (); + return Source.REMOVE; + }); + + yield; //Clone still blocks main thread - if (new_repo != null) { - repo_workdir = new_repo.get_workdir (); + if (workdir != null) { + cloning_status (END_SUCCESS, _("The local repository working directory is '%s'").printf (workdir.get_uri ())); } - return new_repo != null; + repo_workdir = workdir; + return workdir != null; } } } diff --git a/src/meson.build b/src/meson.build index b5a5aca59..696f4e1e9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -22,6 +22,7 @@ code_files = files( 'Dialogs/RestoreConfirmationDialog.vala', 'Dialogs/CloseProjectsConfirmationDialog.vala', 'Dialogs/CloneRepositoryDialog.vala', + 'Dialogs/CloningProgressDialog.vala', 'Dialogs/OverwriteUncommittedConfirmationDialog.vala', 'Dialogs/GlobalSearchDialog.vala', 'Dialogs/NewBranchDialog.vala',