From 379ef30ab957777e123dc1bb657f1587d8a67982 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 4 Jun 2025 16:30:46 +0100 Subject: [PATCH 1/4] Move cloning into response callback --- src/MainWindow.vala | 42 +++++++++++++++++++----------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 5b2a1ace5..070ab0ed0 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1049,38 +1049,34 @@ namespace Scratch { var local_name = ""; var clone_dialog = new Dialogs.CloneRepositoryDialog (local_folder); clone_dialog.response.connect ((res) => { - if (res == Gtk.ResponseType.APPLY) { + if (res == Gtk.ResponseType.APPLY && clone_dialog.can_clone) { // Should not need second test? uri = clone_dialog.get_source_repository_uri (); local_folder = clone_dialog.get_local_folder (); local_name = clone_dialog.get_local_name (); + + //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); + } + } + ); } clone_dialog.destroy (); }); 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 void action_collapse_all_folders () { From c2fe76f3a21a09bed37b9231a355fbc329139c55 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 4 Jun 2025 16:39:30 +0100 Subject: [PATCH 2/4] Close dialog before cloning starts --- src/MainWindow.vala | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 070ab0ed0..700d775cc 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1043,17 +1043,15 @@ 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) => { + var uri = clone_dialog.get_source_repository_uri (); + var local_folder = clone_dialog.get_local_folder (); + var local_name = clone_dialog.get_local_name (); + // MainWindow should provide feedback on cloning progress + // Close modal dialog now + clone_dialog.destroy (); if (res == Gtk.ResponseType.APPLY && clone_dialog.can_clone) { // Should not need second test? - uri = clone_dialog.get_source_repository_uri (); - local_folder = clone_dialog.get_local_folder (); - local_name = clone_dialog.get_local_name (); - //TODO Show progress while cloning git_manager.clone_repository.begin ( uri, @@ -1064,7 +1062,6 @@ namespace Scratch { 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); @@ -1072,8 +1069,6 @@ namespace Scratch { } ); } - - clone_dialog.destroy (); }); clone_dialog.present (); From 76960633637cff21cbb367b69471f60139d058cc Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 4 Jun 2025 19:04:56 +0100 Subject: [PATCH 3/4] Implement simplistic cloning feedback with dialog --- src/Dialogs/CloneRepositoryDialog.vala | 7 ++-- src/Dialogs/CloningProgressDialog.vala | 43 ++++++++++++++++++++++ src/MainWindow.vala | 50 +++++++++++++++++++------- src/Services/GitManager.vala | 48 +++++++++++++++++++------ src/meson.build | 1 + 5 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 src/Dialogs/CloningProgressDialog.vala 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 700d775cc..21f6be7e0 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,37 +1044,62 @@ namespace Scratch { } private void action_clone_repo (SimpleAction action, Variant? param) { - var clone_dialog = new Dialogs.CloneRepositoryDialog (local_folder); + 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 (); - // MainWindow should provide feedback on cloning progress - // Close modal dialog now + // MainWindow should provide feedback on cloning progress - close modal dialog now clone_dialog.destroy (); if (res == Gtk.ResponseType.APPLY && clone_dialog.can_clone) { // Should not need second test? //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 { + 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); } - } catch (Error e) { - warning ("Unable to clone '%s'. %s", uri, e.message); } - } - ); + ); + return Source.REMOVE; + }); } }); clone_dialog.present (); } + 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 () { folder_manager_view.collapse_all (); } 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', From 8ce50027e0555c1680eec46f130dec834453b1d4 Mon Sep 17 00:00:00 2001 From: Jeremy Wootten Date: Wed, 4 Jun 2025 19:52:47 +0100 Subject: [PATCH 4/4] Don't reference destroyed object --- src/MainWindow.vala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MainWindow.vala b/src/MainWindow.vala index 21f6be7e0..999fc99ef 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -1057,9 +1057,10 @@ namespace Scratch { 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 && clone_dialog.can_clone) { // Should not need second test? + 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) => {