Skip to content

Clone repository: Show progress #1608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 73 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
e1d2e5c
Add clone button to sidebar
jeremypw Mar 26, 2025
ebb8ed1
Sketch out functionality
jeremypw Mar 26, 2025
5200d9d
Fix action, Tweak regex
jeremypw Mar 26, 2025
01d124f
Various corrections
jeremypw Mar 26, 2025
e39eac8
Clone repository
jeremypw Mar 26, 2025
7e3da04
Reformat, local repo name, make active option
jeremypw Apr 3, 2025
3b4ec49
Merge branch 'master' into jeremypw/implement-clone
jeremypw Apr 3, 2025
328c75b
Merge branch 'master' into jeremypw/implement-clone
jeremypw Apr 22, 2025
a267a98
Fix lint
jeremypw Apr 22, 2025
7881aee
Merge branch 'master' into jeremypw/implement-clone
jeremypw May 18, 2025
85b2bb3
Merge branch 'master' into jeremypw/implement-clone
jeremypw May 27, 2025
2251850
Allow narrower sidebar
jeremypw May 27, 2025
15c6fc5
Merge branch 'master' into jeremypw/implement-clone
jeremypw May 28, 2025
ea1a356
Update src/Dialogs/CloneRepositoryDialog.vala
jeremypw May 28, 2025
576ca43
Update src/Dialogs/CloneRepositoryDialog.vala
jeremypw May 28, 2025
9c38d00
Replace local folder path entry with button linked to filechooser
jeremypw May 28, 2025
5287f59
Set mnemonic widget for CloneEntry label
jeremypw May 28, 2025
9f8bf46
Fix construction as advised, add comments
jeremypw May 28, 2025
497bac7
Better variable names
jeremypw May 28, 2025
b4c79ed
Remove activates_default = false;
jeremypw May 28, 2025
55490d2
Rewording
jeremypw May 28, 2025
7a4bf5d
Use "emblem-downloads" icon
jeremypw May 28, 2025
a59c64b
No space before ellipsis
jeremypw May 28, 2025
6138cfa
Do not use Gtk.Button.from_icon_name
jeremypw May 28, 2025
0582c77
More explicit labels
jeremypw May 28, 2025
50293b5
Merge branch 'master' into jeremypw/implement-clone
danirabbit May 28, 2025
ec4b2d0
Simplify
jeremypw May 28, 2025
4dd6f6d
Merge branch 'master' into jeremypw/implement-clone
jeremypw May 28, 2025
ffbb883
Show all only on content_box
jeremypw May 28, 2025
636d871
Use box padding not margin
jeremypw May 28, 2025
7cadd28
CloneRepositoryDialog: design tweaks (#1584)
danirabbit May 30, 2025
2c315a2
Move buttons from actionbar to projectchooser (#1583)
danirabbit May 30, 2025
1bfa1aa
Check URL is in correct form using function not regex
jeremypw Jun 1, 2025
d428416
Merge branch 'master' into jeremypw/implement-clone
jeremypw Jun 4, 2025
3273fe5
Prefill local name; improve NAME_REGEX
jeremypw Jun 4, 2025
67be8d9
Restrict project folder names
jeremypw Jun 4, 2025
273113c
Merge branch 'master' into jeremypw/implement-clone
jeremypw Jun 4, 2025
731a05a
Fix extraneous comment
jeremypw Jun 4, 2025
e3a22b9
Remove set as active checkbox for now
jeremypw Jun 4, 2025
25bbd0e
Use SYNC_CREATE
jeremypw Jun 4, 2025
4d66c36
Fix unneeded try-catch
jeremypw Jun 4, 2025
379ef30
Move cloning into response callback
jeremypw Jun 4, 2025
c2fe76f
Close dialog before cloning starts
jeremypw Jun 4, 2025
22e57b7
Add TODO comment
jeremypw Jun 4, 2025
7d60208
Fix undefined variable
jeremypw Jun 5, 2025
2595107
Handle clone failure with dialog - option to retry
jeremypw Jun 5, 2025
1454a5b
Handle no current active project
jeremypw Jun 5, 2025
1b3232d
Persist projects folder and remote entries
jeremypw Jun 5, 2025
0ae3fb9
Fix lint
jeremypw Jun 5, 2025
e949241
Merge branch 'master' into jeremypw/implement-clone
jeremypw Jun 5, 2025
5536cc4
Merge branch 'master' into jeremypw/implement-clone
leonardo-lemos Jun 13, 2025
784460d
Merge branch 'master' into jeremypw/implement-clone
zeebok Jun 15, 2025
6915fce
Merge branch 'master' into jeremypw/implement-clone
jeremypw Jun 20, 2025
01346c9
Add on accelerator to open a project
jeremypw Jun 20, 2025
4119847
Merge branch 'master' into jeremypw/implement-clone
danirabbit Jun 20, 2025
db0e6f5
Merge branch 'master' into jeremypw/implement-clone
jeremypw Jun 22, 2025
af36e42
Set parent to error dialog, fix spelling
jeremypw Jun 22, 2025
e463c9f
Make clone function async
jeremypw Jun 23, 2025
9d95150
Include success messagedialog
jeremypw Jun 23, 2025
d049990
Only construct parameters in Object ()
jeremypw Jun 23, 2025
1a09d91
Use a thread for cloning, show spinner
jeremypw Jun 23, 2025
fc536e0
Remove unneeded throws Error
jeremypw Jun 23, 2025
49e95a9
Fix some coding issues
jeremypw Jun 24, 2025
e4cf12b
Fix typo
jeremypw Jun 24, 2025
4469d6c
Show spinner on separate page; hide buttons
jeremypw Jun 24, 2025
1afe983
Cleanup comments
jeremypw Jun 24, 2025
6779e66
Reorganise, reinstate default action
jeremypw Jun 24, 2025
ebcfd41
Implement some remote callbacks
jeremypw Jun 24, 2025
ea582db
Merge branch 'master' into implement-clone/remote-callbacks
jeremypw Jun 25, 2025
4e35200
Remove extraneous
jeremypw Jun 25, 2025
17bb019
Merge branch 'master' into implement-clone/remote-callbacks
jeremypw Jul 16, 2025
6f00e24
Merge branch 'master' into implement-clone/remote-callbacks
jeremypw Jul 18, 2025
d5ce749
Show cloning spinner in ChooseProjectButton
jeremypw Jul 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 120 additions & 6 deletions src/Dialogs/CloneRepositoryDialog.vala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
private Gtk.Button clone_button;
private Gtk.Stack stack;
private Gtk.Spinner spinner;
private Gtk.Revealer revealer;
private Gtk.Label message_label;
private Gtk.Label indexing_label;
private Gtk.Label progress_label;
private Gtk.ProgressBar transfer_progress_bar;
private uint total_objects = 0;
private uint received_objects = 0;
private uint indexed_objects = 0;
private size_t received_bytes = 0;
private string remote_message = "";
private uint update_transfer_info_timeout_id = 0;

public bool can_clone { get; private set; default = false; }
public string suggested_local_folder { get; construct; }
Expand All @@ -36,6 +47,10 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
spinner.stop ();
}
}

get {
return spinner.active;
}
}

public CloneRepositoryDialog (string _suggested_local_folder, string _suggested_remote) {
Expand All @@ -45,6 +60,12 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
);
}

~CloneRepositoryDialog () {
if (update_transfer_info_timeout_id > 0) {
Source.remove (update_transfer_info_timeout_id);
}
}

construct {
transient_for = ((Gtk.Application)(GLib.Application.get_default ())).get_active_window ();
image_icon = new ThemedIcon ("git");
Expand Down Expand Up @@ -119,15 +140,31 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
content_box.add (new CloneEntry (_("Location"), folder_chooser_button));
content_box.add (new CloneEntry (_("Name of Clone"), local_project_name_entry));

var cloning_label = new Granite.HeaderLabel (_("Cloning in progress"));
spinner = new Gtk.Spinner ();

var cloning_box = new Gtk.Box (HORIZONTAL, 12) {
var cloning_box = new Gtk.Box (VERTICAL, 24) {
valign = CENTER,
halign = CENTER
};
var spinner_box = new Gtk.Box (HORIZONTAL, 12) {
valign = CENTER,
halign = CENTER
};
cloning_box.add (cloning_label);
cloning_box.add (spinner);
message_label = new Gtk.Label ("");
progress_label = new Gtk.Label ("");
indexing_label = new Gtk.Label ("");
transfer_progress_bar = new Gtk.ProgressBar () {
fraction = 0.0
};

var cloning_label = new Granite.HeaderLabel (_("Cloning in progress"));
spinner = new Gtk.Spinner ();
spinner_box.add (cloning_label);
spinner_box.add (spinner);

cloning_box.add (spinner_box);
cloning_box.add (message_label);
cloning_box.add (transfer_progress_bar);
cloning_box.add (progress_label);
cloning_box.add (indexing_label);

stack = new Gtk.Stack ();
stack.add_named (content_box, "entries");
Expand Down Expand Up @@ -160,6 +197,58 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
}
}

public Ggit.RemoteCallbacks? get_remote_callbacks () {
update_transfer_info_timeout_id = Timeout.add (200, () => {
if (total_objects > 0) {
transfer_progress_bar.fraction = received_objects / total_objects;
}

indexing_label.label = "%u indexed objects".printf (indexed_objects);
progress_label.label = "%u bytes received".printf ((uint)received_bytes);
message_label.label = remote_message;

if (cloning_in_progress) {
return Source.CONTINUE;
} else {
update_transfer_info_timeout_id = 0;
return Source.REMOVE;
}
});

var cbs = new RemoteCallbacks ();
cbs.progress.connect ((message) => {
remote_message = message;
});
cbs.transfer_progress.connect ((progress) => {
if (progress != null) {
received_objects = progress.get_received_objects ();
received_bytes = progress.get_received_bytes ();

indexed_objects = progress.get_indexed_objects ();
total_objects = progress.get_total_objects ();
}
});
//TODO Decide what to do with completion info. Notification?
cbs.completion.connect ((type) => {
warning ("received completion signal");
switch (type) {
case DOWNLOAD:
warning ("Download completed");
break;
case INDEXING:
warning ("Indexing completed");
break;
case ERROR:
warning ("Transfer ended with error");
break;
default:
assert_not_reached ();
}
});

return cbs;
}

public string get_valid_source_repository_uri () requires (can_clone) {
//TODO Further validation here?
return remote_repository_uri_entry.text;
Expand Down Expand Up @@ -254,4 +343,29 @@ public class Scratch.Dialogs.CloneRepositoryDialog : Granite.MessageDialog {
orientation = VERTICAL;
}
}

//Provide for plaintext user password for now
private class RemoteCallbacks : Ggit.RemoteCallbacks {
public string? user { get; construct; }
public string? password { get; construct; }

public RemoteCallbacks (string? user = null, string? password = null) {
Object (
user: user,
password: password
);
}

public override Ggit.Cred? credentials (
string url,
string? username_from_url,
Ggit.Credtype allowed_types
) throws Error {
if (user != null && password != null && Ggit.Credtype.USERPASS_PLAINTEXT in allowed_types) {
return new Ggit.CredPlaintext (user, password);
} else {
return null;
}
}
}
}
10 changes: 6 additions & 4 deletions src/MainWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -1055,22 +1055,25 @@ namespace Scratch {
// Persist last entries (not necessarily valid)
Scratch.settings.set_string ("default-remote", clone_dialog.get_remote ());
Scratch.settings.set_string ("default-projects-folder", clone_dialog.get_projects_folder ());
// Clone dialog show spinner during cloning so keep visible
//TODO Show more information re progress using Ggit callbacks
if (res == Gtk.ResponseType.APPLY && clone_dialog.can_clone) {
clone_dialog.cloning_in_progress = true;
sidebar.cloning_in_progress = true;
var uri = clone_dialog.get_valid_source_repository_uri ();
var target = clone_dialog.get_valid_target ();
var callbacks = clone_dialog.get_remote_callbacks ();
clone_dialog.hide ();
git_manager.clone_repository.begin (
uri,
target,
callbacks,
(obj, res) => {
clone_dialog.cloning_in_progress = false;
sidebar.cloning_in_progress = false;
File? workdir = null;
string? error = null;
if (git_manager.clone_repository.end (res, out workdir, out error)) {
open_folder (workdir);
clone_dialog.destroy ();
//TODO Show toast instead
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
_("Repository %s successfully cloned").printf (uri),
_("Local repository working directory is %s").printf (workdir.get_uri ()),
Expand All @@ -1082,7 +1085,6 @@ namespace Scratch {
message_dialog.response.connect (message_dialog.destroy);
message_dialog.present ();
} else {
clone_dialog.hide ();
var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
_("Unable to clone %s").printf (uri),
error,
Expand Down
3 changes: 2 additions & 1 deletion src/Services/GitManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ namespace Scratch.Services {
public async bool clone_repository (
string uri,
string local_folder,
Ggit.RemoteCallbacks? rcallbacks,
out File? repo_workdir,
out string? error
) {
Expand All @@ -125,7 +126,7 @@ namespace Scratch.Services {
var fetch_options = new Ggit.FetchOptions ();
fetch_options.set_download_tags (Ggit.RemoteDownloadTagsType.UNSPECIFIED);
//TODO Set callbacks for authentification and progress
fetch_options.set_remote_callbacks (null);
fetch_options.set_remote_callbacks (rcallbacks);

var clone_options = new Ggit.CloneOptions ();
clone_options.set_local (Ggit.CloneLocal.AUTO);
Expand Down
17 changes: 12 additions & 5 deletions src/Widgets/ChooseProjectButton.vala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

public class Code.ChooseProjectButton : Gtk.MenuButton {
public bool cloning_in_progress { get; set; }
private const string NO_PROJECT_SELECTED = N_("No Project Selected");
private const string PROJECT_TOOLTIP = N_("Active Git Project: %s");
private Gtk.Label label_widget;
Expand All @@ -35,12 +36,18 @@ public class Code.ChooseProjectButton : Gtk.MenuButton {
xalign = 0.0f
};

var grid = new Gtk.Grid () {
halign = Gtk.Align.START
var cloning_spinner = new Gtk.Spinner ();
bind_property ("cloning-in-progress", cloning_spinner, "active");

var box = new Gtk.Box (HORIZONTAL, 3) {
hexpand = true,
vexpand = false
};
grid.add (img);
grid.add (label_widget);
add (grid);

box.add (img);
box.add (label_widget);
box.add (cloning_spinner);
add (box);

project_listbox = new Gtk.ListBox () {
selection_mode = Gtk.SelectionMode.SINGLE
Expand Down
11 changes: 11 additions & 0 deletions src/Widgets/Sidebar.vala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ public class Code.Sidebar : Gtk.Grid {
public Hdy.HeaderBar headerbar { get; private set; }
public GLib.MenuModel project_menu_model { get; construct; }

// May show progress in different way in future
public bool cloning_in_progress {
get {
return choose_project_button.cloning_in_progress;
}

set {
choose_project_button.cloning_in_progress = value;
}
}

private Gtk.StackSwitcher stack_switcher;

construct {
Expand Down