[gitg] Add monitoring .git for changes and automatic refresh
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg] Add monitoring .git for changes and automatic refresh
- Date: Sun, 9 Aug 2015 11:11:08 +0000 (UTC)
commit f4cd34ed2da0fda298ec5b3f4bdd46992e4a01e7
Author: Jesse van den Kieboom <jessevdk gnome org>
Date: Sun Aug 9 13:10:35 2015 +0200
Add monitoring .git for changes and automatic refresh
https://bugzilla.gnome.org/show_bug.cgi?id=647879
data/org.gnome.gitg.gschema.xml.in.in | 7 +
gitg/Makefile.am | 1 +
gitg/commit/gitg-commit.vala | 48 ++++++
gitg/gitg-recursive-monitor.vala | 191 ++++++++++++++++++++++
gitg/gitg-window.vala | 108 +++++++++++--
gitg/history/gitg-history.vala | 24 +++
gitg/preferences/gitg-preferences-interface.vala | 8 +
gitg/resources/ui/gitg-preferences-interface.ui | 44 +++++
libgitg-ext/gitg-ext-application.vala | 11 ++
9 files changed, 430 insertions(+), 12 deletions(-)
---
diff --git a/data/org.gnome.gitg.gschema.xml.in.in b/data/org.gnome.gitg.gschema.xml.in.in
index 5398928..cf4bfeb 100644
--- a/data/org.gnome.gitg.gschema.xml.in.in
+++ b/data/org.gnome.gitg.gschema.xml.in.in
@@ -53,6 +53,13 @@
Enable the use of gravatar to display user avatars.
</_description>
</key>
+ <key name="enable-monitoring" type="b">
+ <default>true</default>
+ <_summary>Enable Monitoring</_summary>
+ <_description>
+ Automatically update when external changes to .git are detected
+ </_description>
+ </key>
</schema>
<schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.gitg.preferences.history"
path="/org/gnome/gitg/preferences/history/">
<key name="collapse-inactive-lanes" type="i">
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index 3d8026f..97a31dd 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -69,6 +69,7 @@ gitg_gitg_VALASOURCES = \
gitg/gitg-notifications.vala \
gitg/gitg-plugins-engine.vala \
gitg/gitg-popup-menu.vala \
+ gitg/gitg-recursive-monitor.vala \
gitg/gitg-ref-action-copy-name.vala \
gitg/gitg-ref-action-delete.vala \
gitg/gitg-ref-action-fetch.vala \
diff --git a/gitg/commit/gitg-commit.vala b/gitg/commit/gitg-commit.vala
index a30458c..1dcea81 100644
--- a/gitg/commit/gitg-commit.vala
+++ b/gitg/commit/gitg-commit.vala
@@ -26,6 +26,9 @@ namespace GitgCommit
private Paned? d_main;
private bool d_reloading;
private bool d_has_staged;
+ private ulong d_externally_changed_id;
+ private bool d_ignore_external_changes;
+ private Gitg.WhenMapped? d_reload_when_mapped;
private enum UiType
{
@@ -64,6 +67,36 @@ namespace GitgCommit
{
application.bind_property("repository", this,
"repository", BindingFlags.DEFAULT);
+
+ d_externally_changed_id =
application.repository_changed_externally.connect(repository_changed_externally);
+ }
+
+ public override void dispose()
+ {
+ if (d_externally_changed_id != 0)
+ {
+ application.disconnect(d_externally_changed_id);
+ d_externally_changed_id = 0;
+ }
+
+ base.dispose();
+ }
+
+ private void repository_changed_externally(GitgExt.ExternalChangeHint hint)
+ {
+ if (!d_ignore_external_changes)
+ {
+ if (d_main != null && (hint & GitgExt.ExternalChangeHint.INDEX) != 0)
+ {
+ d_reload_when_mapped = new Gitg.WhenMapped(d_main);
+
+ d_reload_when_mapped.update(() => {
+ reload();
+ }, this);
+ }
+ }
+
+ d_ignore_external_changes = false;
}
public string display_name
@@ -168,6 +201,8 @@ namespace GitgCommit
{
stage_submodule.begin(d_current_submodule, commit, (obj, res) => {
stage_submodule.end(res);
+
+ d_ignore_external_changes = true;
reload();
});
}
@@ -281,10 +316,12 @@ namespace GitgCommit
if (item is Gitg.StageStatusFile)
{
+ d_ignore_external_changes = true;
ok = yield stage_file((Gitg.StageStatusFile)item);
}
else if (item is Gitg.StageStatusSubmodule)
{
+ d_ignore_external_changes = true;
ok = yield stage_submodule((Gitg.StageStatusSubmodule)item, null);
}
else
@@ -615,10 +652,12 @@ namespace GitgCommit
if (parents.size != 0)
{
+ d_ignore_external_changes = true;
stage_submodule_at(parents[0] as Gitg.Commit);
}
else
{
+ d_ignore_external_changes = true;
unstage_submodule.begin(d_current_submodule, (obj, res) => {
unstage_submodule.end(res);
reload();
@@ -634,10 +673,12 @@ namespace GitgCommit
if (item is Gitg.StageStatusFile)
{
+ d_ignore_external_changes = true;
ok = yield unstage_file((Gitg.StageStatusFile)item);
}
else if (item is Gitg.StageStatusSubmodule)
{
+ d_ignore_external_changes = true;
ok = yield unstage_submodule((Gitg.StageStatusSubmodule)item);
}
else
@@ -696,6 +737,8 @@ namespace GitgCommit
private void reload()
{
+ d_reload_when_mapped = null;
+
var repository = application.repository;
if (repository == null || d_reloading)
@@ -1034,6 +1077,7 @@ namespace GitgCommit
opts |= Gitg.StageCommitOptions.SKIP_HOOKS;
}
+ d_ignore_external_changes = true;
stage.commit.begin(dlg.pretty_message,
author,
committer,
@@ -1327,6 +1371,7 @@ namespace GitgCommit
{
application.busy = true;
+ d_ignore_external_changes = true;
discard_selection.begin((obj, res) => {
try
{
@@ -1352,6 +1397,7 @@ namespace GitgCommit
{
var staging = d_main.diff_view.unstaged;
+ d_ignore_external_changes = true;
stage_unstage_selection.begin(staging, (obj, res) => {
try
{
@@ -1419,6 +1465,7 @@ namespace GitgCommit
paths[i] = items[i].path;
}
+ d_ignore_external_changes = true;
revert_paths.begin(paths, (o, ret) => {
try
{
@@ -1533,6 +1580,7 @@ namespace GitgCommit
files[i] = application.repository.get_workdir().get_child(items[i].path);
}
+ d_ignore_external_changes = true;
delete_files.begin(files, (o, ret) => {
try
{
diff --git a/gitg/gitg-recursive-monitor.vala b/gitg/gitg-recursive-monitor.vala
new file mode 100644
index 0000000..4d402e5
--- /dev/null
+++ b/gitg/gitg-recursive-monitor.vala
@@ -0,0 +1,191 @@
+namespace Gitg
+{
+
+class RecursiveMonitor : Object
+{
+ class Monitor : Object
+ {
+ public File location;
+ public RecursiveMonitor monitor;
+
+ public Monitor(File location, RecursiveMonitor monitor)
+ {
+ this.location = location;
+ this.monitor = monitor;
+ }
+ }
+
+ public delegate bool FilterFunc(File file);
+
+ private FileMonitor? d_monitor;
+ private Gee.List<Monitor> d_sub_monitors;
+ private uint d_monitor_changed_timeout_id;
+ private FilterFunc? d_filter_func;
+ private Cancellable d_cancellable;
+ private File[] d_changed_files;
+
+ public signal void changed(File[] files);
+
+ public RecursiveMonitor(File location, owned FilterFunc? filter_func = null)
+ {
+ d_filter_func = (owned)filter_func;
+ d_sub_monitors = new Gee.LinkedList<Monitor>();
+
+ try
+ {
+ d_monitor = location.monitor_directory(FileMonitorFlags.NONE);
+ }
+ catch {}
+
+ if (d_monitor != null)
+ {
+ d_monitor.changed.connect(monitor_changed_timeout);
+ }
+
+ d_cancellable = new Cancellable();
+
+ location.enumerate_children_async.begin(FileAttribute.STANDARD_NAME + "," +
FileAttribute.STANDARD_TYPE, FileQueryInfoFlags.NONE, Priority.DEFAULT, d_cancellable, (obj, res) => {
+ FileEnumerator enumerator;
+
+ try
+ {
+ enumerator = location.enumerate_children_async.end(res);
+
+ FileInfo? info;
+
+ while ((info = enumerator.next_file()) != null)
+ {
+ if (info.get_file_type() == FileType.DIRECTORY)
+ {
+ add_submonitor(location.get_child(info.get_name()));
+ }
+ }
+ }
+ catch {}
+ });
+ }
+
+ private void add_submonitor(File location)
+ {
+ if (d_filter_func != null && !d_filter_func(location))
+ {
+ return;
+ }
+
+ var mon = new RecursiveMonitor(location, (l) => {
+ return d_filter_func(l);
+ });
+
+ d_sub_monitors.add(new Monitor(location, mon));
+ mon.changed.connect((files) => { changed_timeout(files); });
+ }
+
+ private void add_submonitor_if_directory(File location)
+ {
+ try
+ {
+ var info = location.query_info(FileAttribute.STANDARD_TYPE, FileQueryInfoFlags.NONE);
+
+ if (info.get_file_type() == FileType.DIRECTORY)
+ {
+ add_submonitor(location);
+ }
+ }
+ catch {}
+ }
+
+ public override void dispose()
+ {
+ cancel();
+ base.dispose();
+ }
+
+ private void remove_submonitor(File location)
+ {
+ foreach (var monitor in d_sub_monitors)
+ {
+ if (location.equal(monitor.location))
+ {
+ d_sub_monitors.remove(monitor);
+ return;
+ }
+ }
+ }
+
+ private void monitor_changed_timeout(File file, File? other_file, FileMonitorEvent event)
+ {
+ if (event == FileMonitorEvent.CREATED)
+ {
+ add_submonitor_if_directory(file);
+ }
+ else if (event == FileMonitorEvent.DELETED)
+ {
+ remove_submonitor(file);
+ }
+ else if (event == FileMonitorEvent.MOVED)
+ {
+ remove_submonitor(file);
+
+ if (other_file != null)
+ {
+ add_submonitor_if_directory(other_file);
+ }
+ }
+
+ changed_timeout(new File[] { file, other_file });
+ }
+
+ private void changed_timeout(File?[] files)
+ {
+ foreach (var f in files)
+ {
+ if (f != null && (d_filter_func == null || d_filter_func(f)))
+ {
+ d_changed_files += f;
+ }
+ }
+
+ if (d_monitor_changed_timeout_id != 0)
+ {
+ return;
+ }
+
+ if (d_changed_files.length > 0)
+ {
+ d_monitor_changed_timeout_id = Timeout.add_seconds(1, () => {
+ d_monitor_changed_timeout_id = 0;
+
+ changed(d_changed_files);
+ d_changed_files = new File[0];
+
+ return false;
+ });
+ }
+ }
+
+ public void cancel()
+ {
+ d_cancellable.cancel();
+
+ if (d_monitor_changed_timeout_id != 0)
+ {
+ Source.remove(d_monitor_changed_timeout_id);
+ d_monitor_changed_timeout_id = 0;
+ }
+
+ foreach (var monitor in d_sub_monitors)
+ {
+ monitor.monitor.cancel();
+ }
+
+ d_sub_monitors.clear();
+
+ if (d_monitor != null)
+ {
+ d_monitor.cancel();
+ d_monitor = null;
+ }
+ }
+}
+
+}
\ No newline at end of file
diff --git a/gitg/gitg-window.vala b/gitg/gitg-window.vala
index aa8ec9d..d7588e2 100644
--- a/gitg/gitg-window.vala
+++ b/gitg/gitg-window.vala
@@ -26,6 +26,7 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
private Settings d_state_settings;
private Settings d_interface_settings;
private Repository? d_repository;
+ private RecursiveMonitor? d_repository_monitor;
private GitgExt.MessageBus d_message_bus;
private string? d_action;
private Gee.HashMap<string, string> d_environment;
@@ -278,6 +279,11 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
0,
"cancel",
0);
+
+ d_interface_settings.bind("enable-monitoring",
+ this,
+ "enable-monitoring",
+ SettingsBindFlags.GET | SettingsBindFlags.SET);
}
protected override bool delete_event(Gdk.EventAny event)
@@ -344,10 +350,7 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
owned get { return d_repository; }
set
{
- d_repository = value;
- d_remote_manager = new RemoteManager(this);
-
- notify_property("repository");
+ set_repository_internal(value);
repository_changed();
}
}
@@ -467,16 +470,98 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
return base.configure_event(event);
}
- private void on_reload_activated()
+ private GitgExt.ExternalChangeHint external_change_hint_from_file(File location)
{
- try
+ var l = d_repository.get_location();
+
+ var refs = l.get_child("refs");
+ var index = l.get_child("index");
+ var head = l.get_child("HEAD");
+
+ if (location.equal(refs) || location.has_prefix(refs) || location.equal(head))
+ {
+ return GitgExt.ExternalChangeHint.REFS;
+ }
+ else if (location.equal(index))
+ {
+ return GitgExt.ExternalChangeHint.INDEX;
+ }
+ else
+ {
+ return GitgExt.ExternalChangeHint.NONE;
+ }
+
+ }
+
+ private bool filter_repository_changes(File location)
+ {
+ return external_change_hint_from_file(location) != GitgExt.ExternalChangeHint.NONE;
+ }
+
+ private void set_repository_internal(Repository? repository)
+ {
+ if (d_repository_monitor != null)
+ {
+ d_repository_monitor.cancel();
+ d_repository_monitor = null;
+ }
+
+ d_repository = repository;
+
+ if (d_repository != null)
+ {
+ update_enable_monitoring();
+ }
+
+ d_remote_manager = new RemoteManager(this);
+ notify_property("repository");
+ }
+
+ private bool d_enable_monitoring;
+
+ public bool enable_monitoring
+ {
+ get
+ {
+ return d_enable_monitoring;
+ }
+
+ set
{
- d_repository = new Gitg.Repository(this.repository.get_location(),
- null);
+ d_enable_monitoring = value;
+ update_enable_monitoring();
+ }
+ }
- d_remote_manager = new RemoteManager(this);
+ private void update_enable_monitoring()
+ {
+ if (d_repository_monitor != null)
+ {
+ d_repository_monitor.cancel();
+ d_repository_monitor = null;
+ }
- notify_property("repository");
+ if (enable_monitoring && d_repository != null)
+ {
+ d_repository_monitor = new RecursiveMonitor(d_repository.get_location(),
filter_repository_changes);
+ d_repository_monitor.changed.connect((files) => {
+ var hint = GitgExt.ExternalChangeHint.NONE;
+
+ foreach (var f in files)
+ {
+ hint |= external_change_hint_from_file(f);
+ }
+
+ repository_changed_externally(hint);
+ });
+ }
+ }
+
+ private void on_reload_activated()
+ {
+ try
+ {
+ set_repository_internal(new Gitg.Repository(this.repository.get_location(), null));
update_title();
}
catch {}
@@ -681,8 +766,7 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
if (ret != null)
{
ret.application = app;
- ret.d_repository = repository;
- ret.d_remote_manager = new RemoteManager(ret);
+ ret.set_repository_internal(repository);
ret.d_action = action;
}
diff --git a/gitg/history/gitg-history.vala b/gitg/history/gitg-history.vala
index cf9a5de..8eedfbb 100644
--- a/gitg/history/gitg-history.vala
+++ b/gitg/history/gitg-history.vala
@@ -46,6 +46,8 @@ namespace GitgHistory
private uint d_walker_update_idle_id;
private ulong d_refs_list_selection_id;
private ulong d_refs_list_changed_id;
+ private ulong d_externally_changed_id;
+ private Gitg.WhenMapped? d_reload_when_mapped;
private Paned d_main;
private Gitg.PopupMenu d_refs_list_popup;
@@ -146,6 +148,20 @@ namespace GitgHistory
"repository", BindingFlags.DEFAULT);
reload_mainline();
+
+ d_externally_changed_id =
application.repository_changed_externally.connect(repository_changed_externally);
+ }
+
+ private void repository_changed_externally(GitgExt.ExternalChangeHint hint)
+ {
+ if (d_main != null && (hint & GitgExt.ExternalChangeHint.REFS) != 0)
+ {
+ d_reload_when_mapped = new Gitg.WhenMapped(d_main);
+
+ d_reload_when_mapped.update(() => {
+ reload();
+ }, this);
+ }
}
public override void dispose()
@@ -168,6 +184,12 @@ namespace GitgHistory
d_walker_update_idle_id = 0;
}
+ if (d_externally_changed_id != 0)
+ {
+ application.disconnect(d_externally_changed_id);
+ d_externally_changed_id = 0;
+ }
+
d_commit_list_model.repository = null;
base.dispose();
}
@@ -351,6 +373,8 @@ namespace GitgHistory
private void reload_mainline()
{
+ d_reload_when_mapped = null;
+
var uniq = new Gee.HashSet<string>();
d_mainline = new string[0];
diff --git a/gitg/preferences/gitg-preferences-interface.vala
b/gitg/preferences/gitg-preferences-interface.vala
index 98b3239..6456050 100644
--- a/gitg/preferences/gitg-preferences-interface.vala
+++ b/gitg/preferences/gitg-preferences-interface.vala
@@ -37,6 +37,9 @@ public class PreferencesInterface : Gtk.Grid, GitgExt.Preferences
[GtkChild (name = "gravatar_enabled")]
private Gtk.CheckButton d_gravatar_enabled;
+ [GtkChild (name = "monitoring_enabled" )]
+ private Gtk.CheckButton d_monitoring_enabled;
+
construct
{
d_settings = new Settings("org.gnome.gitg.preferences.interface");
@@ -66,6 +69,11 @@ public class PreferencesInterface : Gtk.Grid, GitgExt.Preferences
d_gravatar_enabled,
"active",
SettingsBindFlags.GET | SettingsBindFlags.SET);
+
+ d_settings.bind("enable-monitoring",
+ d_monitoring_enabled,
+ "active",
+ SettingsBindFlags.GET | SettingsBindFlags.SET);
}
public override void dispose()
diff --git a/gitg/resources/ui/gitg-preferences-interface.ui b/gitg/resources/ui/gitg-preferences-interface.ui
index e4e7c82..821dc95 100644
--- a/gitg/resources/ui/gitg-preferences-interface.ui
+++ b/gitg/resources/ui/gitg-preferences-interface.ui
@@ -160,6 +160,50 @@
<property name="top_attach">5</property>
</packing>
</child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Monitoring</property>
+ <property name="margin_top">12</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="grid5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_start">12</property>
+ <property name="hexpand">True</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="monitoring_enabled">
+ <property name="label" translatable="yes">Automatically update when external changes to .git
are detected</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="left_attach">0</property>
diff --git a/libgitg-ext/gitg-ext-application.vala b/libgitg-ext/gitg-ext-application.vala
index 88df0ef..39662d2 100644
--- a/libgitg-ext/gitg-ext-application.vala
+++ b/libgitg-ext/gitg-ext-application.vala
@@ -35,6 +35,8 @@ public interface Application : Object
*/
public abstract Gitg.Repository? repository { owned get; set; }
+ public signal void repository_changed_externally(ExternalChangeHint hint);
+
/**
* An application wide message bus over which plugins can communicate.
*/
@@ -81,6 +83,15 @@ public interface Application : Object
public abstract void open_repository(File path);
}
+[Flags]
+public enum ExternalChangeHint
+{
+ NONE = 0,
+
+ REFS = 1 << 0,
+ INDEX = 1 << 1
+}
+
}
// ex:set ts=4 noet:
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]