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. Enable the use of gravatar to display user avatars.
</_description> </_description>
</key> </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>
<schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.gitg.preferences.history" path="/org/gnome/gitg/preferences/history/"> <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.gitg.preferences.history" path="/org/gnome/gitg/preferences/history/">
<key name="collapse-inactive-lanes" type="i"> <key name="collapse-inactive-lanes" type="i">

View file

@ -69,6 +69,7 @@ gitg_gitg_VALASOURCES = \
gitg/gitg-notifications.vala \ gitg/gitg-notifications.vala \
gitg/gitg-plugins-engine.vala \ gitg/gitg-plugins-engine.vala \
gitg/gitg-popup-menu.vala \ gitg/gitg-popup-menu.vala \
gitg/gitg-recursive-monitor.vala \
gitg/gitg-ref-action-copy-name.vala \ gitg/gitg-ref-action-copy-name.vala \
gitg/gitg-ref-action-delete.vala \ gitg/gitg-ref-action-delete.vala \
gitg/gitg-ref-action-fetch.vala \ gitg/gitg-ref-action-fetch.vala \

View file

@ -26,6 +26,9 @@ namespace GitgCommit
private Paned? d_main; private Paned? d_main;
private bool d_reloading; private bool d_reloading;
private bool d_has_staged; 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 private enum UiType
{ {
@ -64,6 +67,36 @@ namespace GitgCommit
{ {
application.bind_property("repository", this, application.bind_property("repository", this,
"repository", BindingFlags.DEFAULT); "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 public string display_name
@ -168,6 +201,8 @@ namespace GitgCommit
{ {
stage_submodule.begin(d_current_submodule, commit, (obj, res) => { stage_submodule.begin(d_current_submodule, commit, (obj, res) => {
stage_submodule.end(res); stage_submodule.end(res);
d_ignore_external_changes = true;
reload(); reload();
}); });
} }
@ -281,10 +316,12 @@ namespace GitgCommit
if (item is Gitg.StageStatusFile) if (item is Gitg.StageStatusFile)
{ {
d_ignore_external_changes = true;
ok = yield stage_file((Gitg.StageStatusFile)item); ok = yield stage_file((Gitg.StageStatusFile)item);
} }
else if (item is Gitg.StageStatusSubmodule) else if (item is Gitg.StageStatusSubmodule)
{ {
d_ignore_external_changes = true;
ok = yield stage_submodule((Gitg.StageStatusSubmodule)item, null); ok = yield stage_submodule((Gitg.StageStatusSubmodule)item, null);
} }
else else
@ -615,10 +652,12 @@ namespace GitgCommit
if (parents.size != 0) if (parents.size != 0)
{ {
d_ignore_external_changes = true;
stage_submodule_at(parents[0] as Gitg.Commit); stage_submodule_at(parents[0] as Gitg.Commit);
} }
else else
{ {
d_ignore_external_changes = true;
unstage_submodule.begin(d_current_submodule, (obj, res) => { unstage_submodule.begin(d_current_submodule, (obj, res) => {
unstage_submodule.end(res); unstage_submodule.end(res);
reload(); reload();
@ -634,10 +673,12 @@ namespace GitgCommit
if (item is Gitg.StageStatusFile) if (item is Gitg.StageStatusFile)
{ {
d_ignore_external_changes = true;
ok = yield unstage_file((Gitg.StageStatusFile)item); ok = yield unstage_file((Gitg.StageStatusFile)item);
} }
else if (item is Gitg.StageStatusSubmodule) else if (item is Gitg.StageStatusSubmodule)
{ {
d_ignore_external_changes = true;
ok = yield unstage_submodule((Gitg.StageStatusSubmodule)item); ok = yield unstage_submodule((Gitg.StageStatusSubmodule)item);
} }
else else
@ -696,6 +737,8 @@ namespace GitgCommit
private void reload() private void reload()
{ {
d_reload_when_mapped = null;
var repository = application.repository; var repository = application.repository;
if (repository == null || d_reloading) if (repository == null || d_reloading)
@ -1034,6 +1077,7 @@ namespace GitgCommit
opts |= Gitg.StageCommitOptions.SKIP_HOOKS; opts |= Gitg.StageCommitOptions.SKIP_HOOKS;
} }
d_ignore_external_changes = true;
stage.commit.begin(dlg.pretty_message, stage.commit.begin(dlg.pretty_message,
author, author,
committer, committer,
@ -1327,6 +1371,7 @@ namespace GitgCommit
{ {
application.busy = true; application.busy = true;
d_ignore_external_changes = true;
discard_selection.begin((obj, res) => { discard_selection.begin((obj, res) => {
try try
{ {
@ -1352,6 +1397,7 @@ namespace GitgCommit
{ {
var staging = d_main.diff_view.unstaged; var staging = d_main.diff_view.unstaged;
d_ignore_external_changes = true;
stage_unstage_selection.begin(staging, (obj, res) => { stage_unstage_selection.begin(staging, (obj, res) => {
try try
{ {
@ -1419,6 +1465,7 @@ namespace GitgCommit
paths[i] = items[i].path; paths[i] = items[i].path;
} }
d_ignore_external_changes = true;
revert_paths.begin(paths, (o, ret) => { revert_paths.begin(paths, (o, ret) => {
try try
{ {
@ -1533,6 +1580,7 @@ namespace GitgCommit
files[i] = application.repository.get_workdir().get_child(items[i].path); files[i] = application.repository.get_workdir().get_child(items[i].path);
} }
d_ignore_external_changes = true;
delete_files.begin(files, (o, ret) => { delete_files.begin(files, (o, ret) => {
try 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_state_settings;
private Settings d_interface_settings; private Settings d_interface_settings;
private Repository? d_repository; private Repository? d_repository;
private RecursiveMonitor? d_repository_monitor;
private GitgExt.MessageBus d_message_bus; private GitgExt.MessageBus d_message_bus;
private string? d_action; private string? d_action;
private Gee.HashMap<string, string> d_environment; private Gee.HashMap<string, string> d_environment;
@ -278,6 +279,11 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
0, 0,
"cancel", "cancel",
0); 0);
d_interface_settings.bind("enable-monitoring",
this,
"enable-monitoring",
SettingsBindFlags.GET | SettingsBindFlags.SET);
} }
protected override bool delete_event(Gdk.EventAny event) 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; } owned get { return d_repository; }
set set
{ {
d_repository = value; set_repository_internal(value);
d_remote_manager = new RemoteManager(this);
notify_property("repository");
repository_changed(); repository_changed();
} }
} }
@ -467,16 +470,98 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
return base.configure_event(event); 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() private void on_reload_activated()
{ {
try try
{ {
d_repository = new Gitg.Repository(this.repository.get_location(), set_repository_internal(new Gitg.Repository(this.repository.get_location(), null));
null);
d_remote_manager = new RemoteManager(this);
notify_property("repository");
update_title(); update_title();
} }
catch {} catch {}
@ -681,8 +766,7 @@ public class Window : Gtk.ApplicationWindow, GitgExt.Application, Initable
if (ret != null) if (ret != null)
{ {
ret.application = app; ret.application = app;
ret.d_repository = repository; ret.set_repository_internal(repository);
ret.d_remote_manager = new RemoteManager(ret);
ret.d_action = action; ret.d_action = action;
} }

View file

@ -46,6 +46,8 @@ namespace GitgHistory
private uint d_walker_update_idle_id; private uint d_walker_update_idle_id;
private ulong d_refs_list_selection_id; private ulong d_refs_list_selection_id;
private ulong d_refs_list_changed_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 Paned d_main;
private Gitg.PopupMenu d_refs_list_popup; private Gitg.PopupMenu d_refs_list_popup;
@ -146,6 +148,20 @@ namespace GitgHistory
"repository", BindingFlags.DEFAULT); "repository", BindingFlags.DEFAULT);
reload_mainline(); 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() public override void dispose()
@ -168,6 +184,12 @@ namespace GitgHistory
d_walker_update_idle_id = 0; 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; d_commit_list_model.repository = null;
base.dispose(); base.dispose();
} }
@ -351,6 +373,8 @@ namespace GitgHistory
private void reload_mainline() private void reload_mainline()
{ {
d_reload_when_mapped = null;
var uniq = new Gee.HashSet<string>(); var uniq = new Gee.HashSet<string>();
d_mainline = new string[0]; d_mainline = new string[0];

View file

@ -37,6 +37,9 @@ public class PreferencesInterface : Gtk.Grid, GitgExt.Preferences
[GtkChild (name = "gravatar_enabled")] [GtkChild (name = "gravatar_enabled")]
private Gtk.CheckButton d_gravatar_enabled; private Gtk.CheckButton d_gravatar_enabled;
[GtkChild (name = "monitoring_enabled" )]
private Gtk.CheckButton d_monitoring_enabled;
construct construct
{ {
d_settings = new Settings("org.gnome.gitg.preferences.interface"); d_settings = new Settings("org.gnome.gitg.preferences.interface");
@ -66,6 +69,11 @@ public class PreferencesInterface : Gtk.Grid, GitgExt.Preferences
d_gravatar_enabled, d_gravatar_enabled,
"active", "active",
SettingsBindFlags.GET | SettingsBindFlags.SET); SettingsBindFlags.GET | SettingsBindFlags.SET);
d_settings.bind("enable-monitoring",
d_monitoring_enabled,
"active",
SettingsBindFlags.GET | SettingsBindFlags.SET);
} }
public override void dispose() public override void dispose()

View file

@ -160,6 +160,50 @@
<property name="top_attach">5</property> <property name="top_attach">5</property>
</packing> </packing>
</child> </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> </object>
<packing> <packing>
<property name="left_attach">0</property> <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 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. * 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); public abstract void open_repository(File path);
} }
[Flags]
public enum ExternalChangeHint
{
NONE = 0,
REFS = 1 << 0,
INDEX = 1 << 1
}
} }
// ex:set ts=4 noet: // ex:set ts=4 noet: