Skip to content

Commit 3481195

Browse files
jeremypwJeremy Woottenzeebok
authored
Implement checkout of remote branches (#1516)
* Add remote branch submenu to context menu * Checkout remote branch * Update local libgit2-glib-1.0.vapi * Correct action name * Get remote branch checkout working * Simplify * Do not show Remote Branch option when no remote branches * Revert to CheckoutStrategy.FORCE * Actually checkout remote branch as head; DRY * Warn if remote branch not found * Confirm overwriting uncommitted changes when checking out branch * Update POTFILES * Split out confirmation dialog into another PR * Construct local branch menu like remote one * Consistent action names * Fix set upstream --------- Co-authored-by: Jeremy Wootten <jeremy@Proteus-EL07R6-9b3c42bb.localdomain> Co-authored-by: Ryan Kornheisl <ryan@skarva.tech>
1 parent c6353fc commit 3481195

4 files changed

Lines changed: 131 additions & 21 deletions

File tree

src/FolderManager/FileView.vala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public class Scratch.FolderManager.FileView : Code.Widgets.SourceList, Code.Pane
3232
public const string ACTION_DELETE = "delete";
3333
public const string ACTION_NEW_FILE = "new-file";
3434
public const string ACTION_NEW_FOLDER = "new-folder";
35-
public const string ACTION_CHANGE_BRANCH = "change-branch";
35+
public const string ACTION_CHECKOUT_LOCAL_BRANCH = "checkout-local-branch";
36+
public const string ACTION_CHECKOUT_REMOTE_BRANCH = "checkout-remote-branch";
3637
public const string ACTION_CLOSE_FOLDER = "close-folder";
3738
public const string ACTION_CLOSE_OTHER_FOLDERS = "close-other-folders";
3839
public const string ACTION_SET_ACTIVE_PROJECT = "set-active-project";

src/FolderManager/ProjectFolderItem.vala

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ namespace Scratch.FolderManager {
2626

2727
private static Icon added_icon;
2828
private static Icon modified_icon;
29-
private SimpleAction change_branch_action;
29+
private SimpleAction checkout_local_branch_action;
30+
private SimpleAction checkout_remote_branch_action;
3031

3132
public signal void closed ();
3233

@@ -68,16 +69,21 @@ namespace Scratch.FolderManager {
6869
);
6970
}
7071

71-
change_branch_action.set_state (monitored_repo.branch_name);
72+
checkout_local_branch_action.set_state (monitored_repo.branch_name);
7273
}
7374
}
7475

7576
construct {
7677
monitored_repo = Scratch.Services.GitManager.get_instance ().add_project (this);
7778
notify["name"].connect (branch_or_name_changed);
7879
if (monitored_repo != null) {
79-
change_branch_action = new SimpleAction.stateful (
80-
FileView.ACTION_CHANGE_BRANCH,
80+
checkout_local_branch_action = new SimpleAction.stateful (
81+
FileView.ACTION_CHECKOUT_LOCAL_BRANCH,
82+
GLib.VariantType.STRING,
83+
""
84+
);
85+
checkout_remote_branch_action = new SimpleAction.stateful (
86+
FileView.ACTION_CHECKOUT_REMOTE_BRANCH,
8187
GLib.VariantType.STRING,
8288
""
8389
);
@@ -86,7 +92,8 @@ namespace Scratch.FolderManager {
8692
monitored_repo.file_status_change.connect (() => update_item_status (null));
8793
monitored_repo.update_status_map ();
8894
monitored_repo.branch_changed ();
89-
change_branch_action.activate.connect (handle_change_branch_action);
95+
checkout_local_branch_action.activate.connect (handle_checkout_local_branch_action);
96+
checkout_remote_branch_action.activate.connect (handle_checkout_remote_branch_action);
9097
}
9198
}
9299

@@ -315,20 +322,44 @@ namespace Scratch.FolderManager {
315322

316323
protected GLib.MenuItem create_submenu_for_branch () {
317324
// Ensures that action for relevant project is being used
318-
view.actions.add_action (change_branch_action);
319-
320-
GLib.Menu branch_selection_menu = new GLib.Menu ();
321-
foreach (unowned var branch_name in monitored_repo.get_local_branches ()) {
322-
branch_selection_menu.append (
323-
branch_name,
324-
GLib.Action.print_detailed_name (
325-
FileView.ACTION_PREFIX + FileView.ACTION_CHANGE_BRANCH,
326-
branch_name
327-
)
328-
);
325+
view.actions.add_action (checkout_local_branch_action);
326+
view.actions.add_action (checkout_remote_branch_action);
327+
328+
unowned var local_branches = monitored_repo.get_local_branches ();
329+
var local_branch_submenu = new Menu ();
330+
var local_branch_menu = new Menu ();
331+
if (local_branches.length () > 0) {
332+
local_branch_submenu.append_submenu (_("Local"), local_branch_menu);
333+
foreach (unowned var branch_name in local_branches) {
334+
local_branch_menu.append (
335+
branch_name,
336+
GLib.Action.print_detailed_name (
337+
FileView.ACTION_PREFIX + FileView.ACTION_CHECKOUT_LOCAL_BRANCH,
338+
branch_name
339+
)
340+
);
341+
}
329342
}
330343

331344

345+
unowned var remote_branches = monitored_repo.get_remote_branches ();
346+
var remote_branch_submenu = new Menu ();
347+
var remote_branch_menu = new Menu ();
348+
if (remote_branches.length () > 0) {
349+
remote_branch_submenu.append_submenu (_("Remote"), remote_branch_menu);
350+
foreach (unowned var branch_name in remote_branches) {
351+
remote_branch_menu.append (
352+
branch_name,
353+
GLib.Action.print_detailed_name (
354+
FileView.ACTION_PREFIX + FileView.ACTION_CHECKOUT_REMOTE_BRANCH,
355+
branch_name
356+
)
357+
);
358+
}
359+
360+
361+
}
362+
332363
var new_branch_item = new GLib.MenuItem (
333364
_("New Branch…"),
334365
GLib.Action.print_detailed_name (
@@ -351,22 +382,36 @@ namespace Scratch.FolderManager {
351382
bottom_section.append_item (new_branch_item);
352383

353384
var menu = new GLib.Menu ();
354-
menu.append_section (null, branch_selection_menu);
385+
menu.append_section (null, local_branch_submenu);
386+
menu.append_section (null, remote_branch_submenu);
355387
menu.append_section (null, bottom_section);
356388

357389
var menu_item = new GLib.MenuItem.submenu (_("Branch"), menu);
358390
return menu_item;
359391
}
360392

361-
private void handle_change_branch_action (GLib.Variant? parameter) {
362-
var branch_name = parameter.get_string ();
393+
private void handle_checkout_local_branch_action (GLib.Variant? param) {
394+
var branch_name = param != null ? param.get_string () : "";
363395
try {
364396
monitored_repo.change_local_branch (branch_name);
365397
} catch (GLib.Error e) {
366398
warning ("Failed to change branch to %s. %s", branch_name, e.message);
367399
}
368400
}
369401

402+
private void handle_checkout_remote_branch_action (GLib.Variant? param) {
403+
var branch_name = param != null ? param.get_string () : "";
404+
if (branch_name == "") {
405+
return;
406+
}
407+
408+
try {
409+
monitored_repo.checkout_remote_branch (branch_name);
410+
} catch (GLib.Error e) {
411+
warning ("Failed to change branch to %s. %s", branch_name, e.message);
412+
}
413+
}
414+
370415
public void update_item_status (FolderItem? start_folder) {
371416
if (monitored_repo == null) {
372417
debug ("Ignore non-git folders");

src/Services/MonitoredRepository.vala

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace Scratch.Services {
2929
}
3030

3131
public class MonitoredRepository : Object {
32+
public const string ORIGIN_PREFIX = "origin/";
3233
public Ggit.Repository git_repo { get; set construct; }
3334
public string branch_name {
3435
get {
@@ -67,6 +68,8 @@ namespace Scratch.Services {
6768
// Need to use nullable status in order to pass Flatpak CI.
6869
private Gee.HashMap<string, Ggit.StatusFlags?> file_status_map;
6970

71+
private List<Ggit.Ref> remote_branch_ref_list;
72+
7073
public Gee.Set<Gee.Map.Entry<string, Ggit.StatusFlags?>> non_current_entries {
7174
owned get {
7275
return file_status_map.entries;
@@ -97,6 +100,8 @@ namespace Scratch.Services {
97100
Ggit.StatusShow.INDEX_AND_WORKDIR,
98101
null
99102
);
103+
104+
remote_branch_ref_list = new List<Ggit.Ref> ();
100105
}
101106

102107
public MonitoredRepository (Ggit.Repository _git_repo) {
@@ -168,6 +173,28 @@ namespace Scratch.Services {
168173
return branches;
169174
}
170175

176+
public unowned List<string> get_remote_branches () {
177+
unowned List<string> branch_names = null;
178+
try {
179+
var branch_enumerator = git_repo.enumerate_branches (Ggit.BranchType.REMOTE);
180+
181+
foreach (Ggit.Ref branch_ref in branch_enumerator) {
182+
var remote_name = branch_ref.get_shorthand ();
183+
if (!remote_name.has_suffix ("HEAD") &&
184+
!has_local_branch_name (remote_name.substring (ORIGIN_PREFIX.length))) {
185+
186+
branch_names.append (branch_ref.get_shorthand ());
187+
}
188+
189+
remote_branch_ref_list.append (branch_ref);
190+
}
191+
} catch (Error e) {
192+
warning ("Could not enumerate local branches %s", e.message);
193+
}
194+
195+
return branch_names;
196+
}
197+
171198
public bool has_local_branch_name (string name) {
172199
try {
173200
git_repo.lookup_branch (name, Ggit.BranchType.LOCAL);
@@ -186,11 +213,47 @@ namespace Scratch.Services {
186213
return true;
187214
}
188215

189-
public void change_local_branch (string new_branch_name) throws Error {
216+
public void change_local_branch (string new_branch_name) throws Error
217+
requires (!new_branch_name.has_prefix (ORIGIN_PREFIX)) {
218+
190219
var branch = git_repo.lookup_branch (new_branch_name, Ggit.BranchType.LOCAL);
191220
checkout_branch (branch);
192221
}
193222

223+
public void checkout_remote_branch (string target_shorthand) throws Error
224+
requires (target_shorthand.has_prefix (ORIGIN_PREFIX)) {
225+
226+
Ggit.Ref? branch_ref;
227+
//Assume list is up to date as this is called from context menu
228+
unowned var list_pointer = remote_branch_ref_list.first ();
229+
while (list_pointer.data != null &&
230+
list_pointer.data.get_shorthand () != target_shorthand) {
231+
232+
list_pointer = list_pointer.next;
233+
}
234+
235+
branch_ref = list_pointer.data;
236+
if (branch_ref == null) {
237+
var dialog = new Granite.MessageDialog.with_image_from_icon_name (
238+
_("Remote Branch '%s' not found").printf (target_shorthand),
239+
_("The requested branch was not found in any remote linked to this repository"),
240+
"dialog-warning"
241+
) {
242+
modal = true
243+
};
244+
245+
dialog.response.connect (() => {dialog.destroy ();});
246+
dialog.present ();
247+
return;
248+
}
249+
250+
var commit = branch_ref.lookup ();
251+
var local_name = target_shorthand.substring (ORIGIN_PREFIX.length);
252+
var local_branch = git_repo.create_branch (local_name, commit, NONE) as Ggit.Branch;
253+
checkout_branch (local_branch);
254+
local_branch.set_upstream (target_shorthand);
255+
}
256+
194257
private void checkout_branch (Ggit.Branch new_head_branch, bool confirm = true) {
195258
var new_branch_name = "";
196259
try {

vapi/libgit2-glib-1.0.vapi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ namespace Ggit {
7777
public Ggit.Ref? get_upstream () throws GLib.Error;
7878
public bool is_head () throws GLib.Error;
7979
public Ggit.Branch? move (string new_branch_name, Ggit.CreateFlags flags) throws GLib.Error;
80+
public void set_upstream (string upstream_branch_name) throws GLib.Error;
8081
}
8182
[CCode (cheader_filename = "libgit2-glib/ggit.h", ref_function = "ggit_branch_enumerator_ref", type_id = "ggit_branch_enumerator_get_type ()", unref_function = "ggit_branch_enumerator_unref")]
8283
[Compact]

0 commit comments

Comments
 (0)