Add monitoring .git for changes and automatic refresh

https://bugzilla.gnome.org/show_bug.cgi?id=647879
This commit is contained in:
Jesse van den Kieboom 2015-08-09 13:10:35 +02:00
parent d3289895a1
commit f4cd34ed2d
9 changed files with 430 additions and 12 deletions

View File

@ -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">

View File

@ -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 \

View File

@ -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
{

View File

@ -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;
}
}
}
}

View File

@ -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 GitgExt.ExternalChangeHint external_change_hint_from_file(File location)
{
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_enable_monitoring = value;
update_enable_monitoring();
}
}
private void update_enable_monitoring()
{
if (d_repository_monitor != null)
{
d_repository_monitor.cancel();
d_repository_monitor = null;
}
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
{
d_repository = new Gitg.Repository(this.repository.get_location(),
null);
d_remote_manager = new RemoteManager(this);
notify_property("repository");
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;
}

View File

@ -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];

View File

@ -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()

View File

@ -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>

View File

@ -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: