Skip to content

Commit

Permalink
Show git status in sidebar (#452)
Browse files Browse the repository at this point in the history
* Add ggit dep and open new projects as repos

* Show git branch name in brackets

* Show an asterisk next to modified items

* Update modified state on file change

* Show plus symbol next to new files

* Use the granite activatable property to display a status icon

* Use new markup property to make the branch label smaller

* Refine text style

* Add git dep to README

* Initial work towards monitoring the git folder

* Fix some bits

* Also show status of staged files

* Remove a test file

* Rate limit the git updates

* Make modified take priority

* Allow per project git folder updates

* refactor

* Rename MainFolder to ProjectFolder for clarity

* Simplify

* Cleanup a little, catch some errors

* Updated copyrights
  • Loading branch information
davidmhewitt authored and cassidyjames committed Aug 29, 2018
1 parent df6691c commit 9859b13
Show file tree
Hide file tree
Showing 9 changed files with 1,670 additions and 51 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ You'll need the following dependencies:
* libeditorconfig-dev
* libgail-3-dev
* libgee-0.8-dev
* libgit2-glib-1.0-dev
* libgtksourceview-3.0-dev >= 3.24
* libgtkspell3-3-dev
* libgranite-dev
Expand Down
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ granite_dep = dependency('granite')
gtksourceview_dep = dependency('gtksourceview-3.0', version: '>=3.24')
peas_dep = dependency('libpeas-1.0')
peasgtk_dep = dependency('libpeas-gtk-1.0')
git_dep = dependency('libgit2-glib-1.0')
fontconfig_dep = dependency('fontconfig')
pangofc_dep = dependency('pangoft2')
posix_dep = meson.get_compiler('vala').find_library('posix')
Expand All @@ -43,6 +44,7 @@ dependencies = [
gtksourceview_dep,
peas_dep,
peasgtk_dep,
git_dep,
fontconfig_dep,
pangofc_dep,
posix_dep,
Expand Down
2 changes: 1 addition & 1 deletion src/FolderManager/FileView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ namespace Scratch.FolderManager {
return;
}

var folder_root = new MainFolderItem (folder, this);
var folder_root = new ProjectFolderItem (folder, this);
this.root.add (folder_root);

folder_root.expanded = expand;
Expand Down
19 changes: 18 additions & 1 deletion src/FolderManager/FolderItem.vala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*-
* Copyright (c) 2017 elementary LLC. (https://elementary.io),
* Copyright (c) 2017-2018 elementary LLC. (https://elementary.io),
* 2013 Julien Spautz <[email protected]>
*
* This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -43,6 +43,7 @@ namespace Scratch.FolderManager {
if (!children_loaded && expanded && n_children <= 1 && file.children.size > 0) {
clear ();
add_children ();
get_root_folder ().update_git_status ();
children_loaded = true;
}
});
Expand Down Expand Up @@ -284,6 +285,22 @@ namespace Scratch.FolderManager {
break;
}
}

get_root_folder ().update_git_status ();
}

private ProjectFolderItem get_root_folder (Granite.Widgets.SourceList.ExpandableItem? start = null) {
if (start == null) {
start = this;
}

if (start is ProjectFolderItem) {
return start as ProjectFolderItem;
} else if (start.parent is ProjectFolderItem) {
return start.parent as ProjectFolderItem;
} else {
return get_root_folder (start.parent);
}
}

protected void add_folder () {
Expand Down
48 changes: 0 additions & 48 deletions src/FolderManager/MainFolderItem.vala

This file was deleted.

206 changes: 206 additions & 0 deletions src/FolderManager/ProjectFolderItem.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*-
* Copyright (c) 2018 elementary LLC. (https://elementary.io),
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3
* as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: David Hewitt <[email protected]>
*/

namespace Scratch.FolderManager {
internal class ProjectFolderItem : FolderItem {
// Minimum time to elapse before querying git folder again (ms)
private const uint GIT_UPDATE_RATE_LIMIT = 300;

public signal void closed ();

// Static source IDs for each instance of a top level folder, ensures we don't check for git updates too much
private static Gee.HashMap<string, uint> git_update_timer_ids;
private string top_level_path;
private Ggit.Repository? git_repo = null;
private GLib.FileMonitor git_monitor;

private static Icon added_icon;
private static Icon modified_icon;

public ProjectFolderItem (File file, FileView view) requires (file.is_valid_directory) {
Object (file: file, view: view);
}

~ProjectFolderItem () {
if (git_monitor != null) {
git_monitor.cancel ();
}
}

static construct {
Ggit.init ();

git_update_timer_ids = new Gee.HashMap<string, uint> ();
added_icon = new ThemedIcon ("user-available");
modified_icon = new ThemedIcon ("user-away");
}

construct {
top_level_path = file.file.get_path () + Path.DIR_SEPARATOR_S;

try {
git_repo = Ggit.Repository.open (file.file);
} catch (Error e) {
debug ("Error opening git repo, means this probably isn't one: %s", e.message);
}

if (git_repo != null) {
update_git_status ();
var git_folder = GLib.File.new_for_path (Path.build_filename (top_level_path, ".git"));
if (git_folder.query_exists ()) {
try {
git_monitor = git_folder.monitor_directory (GLib.FileMonitorFlags.NONE);
git_monitor.changed.connect (() => update_git_status ());
} catch (IOError e) {
warning ("An error occured setting up a file monitor on the git folder: %s", e.message);
}
}
}
}

public override Gtk.Menu? get_context_menu () {
var close_item = new Gtk.MenuItem.with_label (_("Close Folder"));
close_item.activate.connect (() => { closed (); });

var delete_item = new Gtk.MenuItem.with_label (_("Move to Trash"));
delete_item.activate.connect (() => {
closed ();
trash ();
});

var menu = new Gtk.Menu ();
menu.append (close_item);
menu.append (create_submenu_for_new ());
menu.append (delete_item);
menu.show_all ();

return menu;
}

public void update_git_status () {
var uri = file.file.get_uri ();

if (git_update_timer_ids.has_key (uri) && git_update_timer_ids[uri] != 0) {
// Update already queued, ignore this request
return;
}

git_update_timer_ids[uri] = Timeout.add (GIT_UPDATE_RATE_LIMIT, () => {
do_git_update ();
git_update_timer_ids[uri] = 0;
return Source.REMOVE;
});
}

private void do_git_update () {
if (git_repo == null) {
return;
}

try {
var head = git_repo.get_head ();
if (head.is_branch ()) {
var branch = git_repo.get_head () as Ggit.Branch;
markup = "%s <span size='small' weight='normal'>%s</span>".printf (file.name, branch.get_name ());
}
} catch (Error e) {
warning ("An error occured while fetching the current git branch name: %s", e.message);
}

reset_all_children (this);
var options = new Ggit.StatusOptions (Ggit.StatusOption.INCLUDE_UNTRACKED, Ggit.StatusShow.INDEX_AND_WORKDIR, null);
try {
git_repo.file_status_foreach (options, check_each_git_status);
} catch (Error e) {
critical ("Error enumerating git status: %s", e.message);
}
}

private int check_each_git_status (string path, Ggit.StatusFlags status) {
if (Ggit.StatusFlags.WORKING_TREE_MODIFIED in status || Ggit.StatusFlags.INDEX_MODIFIED in status) {
var modified_items = new Gee.ArrayList<Item> ();
find_items (this, path, ref modified_items);
foreach (var modified_item in modified_items) {
modified_item.activatable = modified_icon;
}
} else if (Ggit.StatusFlags.WORKING_TREE_NEW in status || Ggit.StatusFlags.INDEX_NEW in status) {
var new_items = new Gee.ArrayList<Item> ();
find_items (this, path, ref new_items);
foreach (var new_item in new_items) {
// Only show an added indicator on items that aren't already showing modified state
if (new_item.activatable == null) {
new_item.activatable = added_icon;
}
}
}

return 0;
}

private void find_items (Item toplevel_item, string relative_path, ref Gee.ArrayList<Item> items) {
foreach (var child in toplevel_item.children) {
var item = child as Item;
if (item == null) {
continue;
}

var item_relpath = item.path.replace (top_level_path, "");
var parts = item_relpath.split (Path.DIR_SEPARATOR_S);
var search_parts = relative_path.split (Path.DIR_SEPARATOR_S);

if (parts.length > search_parts.length) {
continue;
}

bool match = true;
for (int i = 0; i < parts.length; i++) {
if (parts[i] != search_parts[i]) {
match = false;
break;
}
}

if (match) {
items.add (item);
}

if (item is Granite.Widgets.SourceList.ExpandableItem) {
find_items (item, relative_path, ref items);
}
}
}

private void reset_all_children (Item toplevel_item) {
foreach (var child in toplevel_item.children) {
var item = child as Item;
if (item == null) {
continue;
}

item.name = item.file.name;
item.markup = null;
item.activatable = null;

if (item is Granite.Widgets.SourceList.ExpandableItem) {
reset_all_children (item);
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ code_files = files(
'FolderManager/FileView.vala',
'FolderManager/FolderItem.vala',
'FolderManager/Item.vala',
'FolderManager/MainFolderItem.vala',
'FolderManager/ProjectFolderItem.vala',
'Services/CommentToggler.vala',
'Services/Document.vala',
'Services/FileHandler.vala',
Expand Down
1 change: 1 addition & 0 deletions vapi/libgit2-glib-1.0.deps
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gio-2.0
Loading

0 comments on commit 9859b13

Please sign in to comment.