[gitg] Add monitoring .git for changes and automatic refresh



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]