Merge lp:~attente/unity-gtk-module/gtk-enable-mnemonics into lp:unity-gtk-module/14.04

Proposed by William Hua
Status: Merged
Approved by: Sebastien Bacher
Approved revision: 322
Merged at revision: 315
Proposed branch: lp:~attente/unity-gtk-module/gtk-enable-mnemonics
Merge into: lp:unity-gtk-module/14.04
Diff against target: 403 lines (+190/-48)
5 files modified
lib/unity-gtk-menu-item-private.h (+1/-0)
lib/unity-gtk-menu-item.c (+92/-19)
lib/unity-gtk-menu-shell-private.h (+20/-18)
lib/unity-gtk-menu-shell.c (+76/-11)
lib/unity-gtk-menu-shell.h (+1/-0)
To merge this branch: bzr merge lp:~attente/unity-gtk-module/gtk-enable-mnemonics
Reviewer Review Type Date Requested Status
Sebastien Bacher Approve
PS Jenkins bot (community) continuous-integration Approve
Allison Karlitskaya (community) Needs Fixing
Review via email: mp+207752@code.launchpad.net

Commit message

Filter out mnemonics when the gtk-enable-mnemonics setting is cleared. Workaround for LP: #1282782.

Description of the change

Filter out mnemonics when the gtk-enable-mnemonics setting is cleared. Workaround for LP: #1282782.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Allison Karlitskaya (desrt) wrote :

trying to dynamically handle the settings changing at runtime is maybe a bit insane.... but assuming that indeed you do want to do this:

unity_gtk_menu_shell_handle_item_notify() only uses the pspec to get the name -- why not just modify this function to take the name directly, removing the need to lookup the pspec...

keeping a gtksettings per menuitem is definitely overkill -- it's exceptionally unlikely that we will ever see more than one gtksettings per program. just use the global one. cache it in a static if you really don't trust that it will never change...

since you will just use the global gtksettings, no need to remove/readd the signal handler every time. also: don't bother with handler ids: they don't really make much of an improvement in performance. just disconnect by funcs/data.

finally: you shouldn't try to do this for every single menu item: only for toplevel ones in menubars.

one unrelated note: your underscore removal algorithm's attempt to be correct on non-ascii utf8 strings is not really necessary. all non-ascii utf8 sequences are composed entirely of non-ascii characters (ie: every byte is > 0x80). it is therefore impossible for '_' to appear in the middle of the sequence. just do the naive thing here and it will be correct.

review: Needs Fixing
Revision history for this message
Allison Karlitskaya (desrt) wrote :

about the strdup: the strlen() + 1 on sized new is not necessary with gstring -- it adds the nul internally. maybe skip GString here entirely since you know that the result string will definitely be smaller than the input.

also: with respect to my previous comments, strchr() is your friend.

316. By William Hua

Pass property name directly.

317. By William Hua

Simplify mnemonic removal.

318. By William Hua

Only honour gtk-enable-mnemonics on top-level shells.

Revision history for this message
William Hua (attente) wrote :

I added the signal handler connection/disconnection in the unity_gtk_menu_shell_set_menu_shell () function because sometimes the GtkMenuShell can be set to NULL, and it makes sense to no longer watch once that happens. If you're worried about it being connected/disconnected more than once, in practice that function is never called with a non-NULL GtkMenuShell except at initialization.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Allison Karlitskaya (desrt) wrote :

This is better, but still not exactly what I had in mind. I wouldn't block on further review bickering at this point, though.

What I was thinking of (and did a relative poor job of explaining) is one global default GtkSettings watch (using gtk_settings_get_default) for the entire thing. When it fires, then you can iterate the toplevel items and make the tweak.

319. By William Hua

Use only one signal handler for notify::gtk-enable-mnemonics.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
320. By William Hua

Reset the label in the label handler.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Allison Karlitskaya (desrt) wrote :

Keeping the global list is really strange -- particularly considering that in practice there will only ever be exactly one menubar. It would have been better to just hook directly up to each (of the one and only) toplevel shell instead of bothering with all of this register stuff...

This is totally fine as-is, though...

321. By William Hua

Don't use a global linked list.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Allison Karlitskaya (desrt) wrote :

Just one more nag: this check will never fail:

+ if (gtk_enable_mnemonics_name == NULL)
282 + gtk_enable_mnemonics_name = g_intern_static_string ("gtk-enable-mnemonics");
283 +
284 + if (g_param_spec_get_name (pspec) == gtk_enable_mnemonics_name)
285 + {

because you already have a detailed signal connection.

322. By William Hua

Don't check pspec name in detailed signal handler.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Sebastien Bacher (seb128) wrote :

Seems like that's ready to land, thanks guys for the review/iterations!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/unity-gtk-menu-item-private.h'
--- lib/unity-gtk-menu-item-private.h 2013-11-28 18:29:27 +0000
+++ lib/unity-gtk-menu-item-private.h 2014-03-05 17:18:00 +0000
@@ -53,6 +53,7 @@
53 guchar child_shell_valid : 1;53 guchar child_shell_valid : 1;
54 guint item_index;54 guint item_index;
55 UnityGtkAction *action;55 UnityGtkAction *action;
56 gchar *label;
56};57};
5758
58GType unity_gtk_menu_item_get_type (void) G_GNUC_INTERNAL;59GType unity_gtk_menu_item_get_type (void) G_GNUC_INTERNAL;
5960
=== modified file 'lib/unity-gtk-menu-item.c'
--- lib/unity-gtk-menu-item.c 2013-11-28 18:29:27 +0000
+++ lib/unity-gtk-menu-item.c 2014-03-05 17:18:00 +0000
@@ -19,6 +19,7 @@
1919
20#include "unity-gtk-menu-item-private.h"20#include "unity-gtk-menu-item-private.h"
21#include "unity-gtk-action-group-private.h"21#include "unity-gtk-action-group-private.h"
22#include <string.h>
2223
23G_DEFINE_TYPE (UnityGtkMenuItem,24G_DEFINE_TYPE (UnityGtkMenuItem,
24 unity_gtk_menu_item,25 unity_gtk_menu_item,
@@ -307,7 +308,7 @@
307 g_return_if_fail (parent_shell != NULL);308 g_return_if_fail (parent_shell != NULL);
308 g_warn_if_fail (object == menu_item);309 g_warn_if_fail (object == menu_item);
309310
310 unity_gtk_menu_shell_handle_item_notify (parent_shell, item, pspec);311 unity_gtk_menu_shell_handle_item_notify (parent_shell, item, g_param_spec_get_name (pspec));
311}312}
312313
313static void314static void
@@ -368,11 +369,27 @@
368}369}
369370
370static void371static void
372unity_gtk_menu_item_finalize (GObject *object)
373{
374 UnityGtkMenuItem *item;
375
376 g_return_if_fail (UNITY_GTK_IS_MENU_ITEM (object));
377
378 item = UNITY_GTK_MENU_ITEM (object);
379
380 g_free (item->label);
381 item->label = NULL;
382
383 G_OBJECT_CLASS (unity_gtk_menu_item_parent_class)->finalize (object);
384}
385
386static void
371unity_gtk_menu_item_class_init (UnityGtkMenuItemClass *klass)387unity_gtk_menu_item_class_init (UnityGtkMenuItemClass *klass)
372{388{
373 GObjectClass *object_class = G_OBJECT_CLASS (klass);389 GObjectClass *object_class = G_OBJECT_CLASS (klass);
374390
375 object_class->dispose = unity_gtk_menu_item_dispose;391 object_class->dispose = unity_gtk_menu_item_dispose;
392 object_class->finalize = unity_gtk_menu_item_finalize;
376}393}
377394
378static void395static void
@@ -416,7 +433,7 @@
416 GtkWidget *submenu = gtk_menu_item_get_submenu (menu_item);433 GtkWidget *submenu = gtk_menu_item_get_submenu (menu_item);
417434
418 if (submenu != NULL)435 if (submenu != NULL)
419 item->child_shell = unity_gtk_menu_shell_new (GTK_MENU_SHELL (submenu));436 item->child_shell = unity_gtk_menu_shell_new_internal (GTK_MENU_SHELL (submenu));
420 }437 }
421438
422 item->child_shell_valid = TRUE;439 item->child_shell_valid = TRUE;
@@ -461,36 +478,92 @@
461 }478 }
462}479}
463480
481static gchar *
482g_strdup_no_mnemonics (const gchar *str)
483{
484 if (str != NULL)
485 {
486 gchar *string;
487 gchar *out;
488 const gchar *in;
489 gboolean underscore;
490
491 string = g_malloc (strlen (str) + 1);
492 out = string;
493 underscore = FALSE;
494
495 for (in = str; *in != '\0'; in++)
496 {
497 if (*in != '_')
498 {
499 underscore = FALSE;
500 *out++ = *in;
501 }
502 else
503 {
504 if (!underscore)
505 underscore = TRUE;
506 else
507 {
508 /* double underscores are not accelerator markers */
509 underscore = FALSE;
510 *out++ = '_';
511 *out++ = '_';
512 }
513 }
514 }
515
516 /* trailing underscores are not accelerator markers */
517 if (underscore)
518 *out++ = '_';
519
520 *out++ = '\0';
521
522 return string;
523 }
524
525 return NULL;
526}
527
464const gchar *528const gchar *
465unity_gtk_menu_item_get_label (UnityGtkMenuItem *item)529unity_gtk_menu_item_get_label (UnityGtkMenuItem *item)
466{530{
467 const gchar *label;
468
469 g_return_val_if_fail (UNITY_GTK_IS_MENU_ITEM (item), NULL);531 g_return_val_if_fail (UNITY_GTK_IS_MENU_ITEM (item), NULL);
470 g_return_val_if_fail (item->menu_item != NULL, NULL);532 g_return_val_if_fail (item->menu_item != NULL, NULL);
471533
472 label = gtk_menu_item_get_label (item->menu_item);534 if (item->label == NULL)
473
474 if (label != NULL && label[0] != '\0')
475 {535 {
476 if (GTK_IS_IMAGE_MENU_ITEM (item->menu_item))536 const gchar *label = gtk_menu_item_get_label (item->menu_item);
537
538 if (label != NULL && label[0] != '\0')
477 {539 {
478 GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (item->menu_item);540 if (GTK_IS_IMAGE_MENU_ITEM (item->menu_item))
479
480 if (gtk_image_menu_item_get_use_stock (image_menu_item))
481 {541 {
482 GtkStockItem stock_item;542 GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (item->menu_item);
483543
484 if (gtk_stock_lookup (label, &stock_item))544 if (gtk_image_menu_item_get_use_stock (image_menu_item))
485 label = stock_item.label;545 {
546 GtkStockItem stock_item;
547
548 if (gtk_stock_lookup (label, &stock_item))
549 label = stock_item.label;
550 }
486 }551 }
487 }552 }
553
554 if (label == NULL || label[0] == '\0')
555 label = gtk_menu_item_get_nth_label (item->menu_item, 0);
556
557 if (label != NULL && label[0] != '\0')
558 {
559 if (item->parent_shell == NULL || item->parent_shell->has_mnemonics)
560 item->label = g_strdup (label);
561 else
562 item->label = g_strdup_no_mnemonics (label);
563 }
488 }564 }
489565
490 if (label == NULL || label[0] == '\0')566 return item->label;
491 label = gtk_menu_item_get_nth_label (item->menu_item, 0);
492
493 return label != NULL && label[0] != '\0' ? label : NULL;
494}567}
495568
496GIcon *569GIcon *
497570
=== modified file 'lib/unity-gtk-menu-shell-private.h'
--- lib/unity-gtk-menu-shell-private.h 2013-11-28 18:29:27 +0000
+++ lib/unity-gtk-menu-shell-private.h 2014-03-05 17:18:00 +0000
@@ -26,24 +26,26 @@
2626
27G_BEGIN_DECLS27G_BEGIN_DECLS
2828
29UnityGtkMenuItem * unity_gtk_menu_shell_get_item (UnityGtkMenuShell *shell,29UnityGtkMenuShell * unity_gtk_menu_shell_new_internal (GtkMenuShell *menu_shell) G_GNUC_INTERNAL;
30 guint index) G_GNUC_INTERNAL;30
3131UnityGtkMenuItem * unity_gtk_menu_shell_get_item (UnityGtkMenuShell *shell,
32GSequence * unity_gtk_menu_shell_get_visible_indices (UnityGtkMenuShell *shell) G_GNUC_INTERNAL;32 guint index) G_GNUC_INTERNAL;
3333
34GSequence * unity_gtk_menu_shell_get_separator_indices (UnityGtkMenuShell *shell) G_GNUC_INTERNAL;34GSequence * unity_gtk_menu_shell_get_visible_indices (UnityGtkMenuShell *shell) G_GNUC_INTERNAL;
3535
36void unity_gtk_menu_shell_handle_item_notify (UnityGtkMenuShell *shell,36GSequence * unity_gtk_menu_shell_get_separator_indices (UnityGtkMenuShell *shell) G_GNUC_INTERNAL;
37 UnityGtkMenuItem *item,37
38 GParamSpec *pspec) G_GNUC_INTERNAL;38void unity_gtk_menu_shell_handle_item_notify (UnityGtkMenuShell *shell,
3939 UnityGtkMenuItem *item,
40void unity_gtk_menu_shell_activate_item (UnityGtkMenuShell *shell,40 const gchar *property) G_GNUC_INTERNAL;
41 UnityGtkMenuItem *item) G_GNUC_INTERNAL;41
4242void unity_gtk_menu_shell_activate_item (UnityGtkMenuShell *shell,
43void unity_gtk_menu_shell_print (UnityGtkMenuShell *shell,43 UnityGtkMenuItem *item) G_GNUC_INTERNAL;
44 guint indent) G_GNUC_INTERNAL;44
4545void unity_gtk_menu_shell_print (UnityGtkMenuShell *shell,
46gboolean unity_gtk_menu_shell_is_debug (void) G_GNUC_INTERNAL;46 guint indent) G_GNUC_INTERNAL;
47
48gboolean unity_gtk_menu_shell_is_debug (void) G_GNUC_INTERNAL;
4749
48G_END_DECLS50G_END_DECLS
4951
5052
=== modified file 'lib/unity-gtk-menu-shell.c'
--- lib/unity-gtk-menu-shell.c 2014-01-13 15:41:42 +0000
+++ lib/unity-gtk-menu-shell.c 2014-03-05 17:18:00 +0000
@@ -435,6 +435,13 @@
435unity_gtk_menu_shell_handle_item_label (UnityGtkMenuShell *shell,435unity_gtk_menu_shell_handle_item_label (UnityGtkMenuShell *shell,
436 UnityGtkMenuItem *item)436 UnityGtkMenuItem *item)
437{437{
438 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (shell));
439 g_return_if_fail (UNITY_GTK_IS_MENU_ITEM (item));
440 g_warn_if_fail (item->parent_shell == shell);
441
442 g_free (item->label);
443 item->label = NULL;
444
438 unity_gtk_menu_shell_update_item (shell, item);445 unity_gtk_menu_shell_update_item (shell, item);
439}446}
440447
@@ -654,6 +661,41 @@
654 }661 }
655}662}
656663
664static void
665unity_gtk_menu_shell_set_has_mnemonics (UnityGtkMenuShell *shell,
666 gboolean has_mnemonics)
667{
668 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (shell));
669
670 if (has_mnemonics != shell->has_mnemonics)
671 {
672 shell->has_mnemonics = has_mnemonics;
673
674 if (shell->items != NULL)
675 {
676 guint i;
677
678 for (i = 0; i < shell->items->len; i++)
679 unity_gtk_menu_shell_handle_item_label (shell, g_ptr_array_index (shell->items, i));
680 }
681 }
682}
683
684static void
685unity_gtk_menu_shell_handle_settings_notify (GObject *object,
686 GParamSpec *pspec,
687 gpointer user_data)
688{
689 gboolean has_mnemonics;
690
691 g_return_if_fail (GTK_IS_SETTINGS (object));
692 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (user_data));
693
694 g_object_get (GTK_SETTINGS (object), "gtk-enable-mnemonics", &has_mnemonics, NULL);
695
696 unity_gtk_menu_shell_set_has_mnemonics (UNITY_GTK_MENU_SHELL (user_data), has_mnemonics);
697}
698
657static void unity_gtk_menu_shell_clear_menu_shell (UnityGtkMenuShell *shell);699static void unity_gtk_menu_shell_clear_menu_shell (UnityGtkMenuShell *shell);
658700
659static void701static void
@@ -731,13 +773,18 @@
731unity_gtk_menu_shell_dispose (GObject *object)773unity_gtk_menu_shell_dispose (GObject *object)
732{774{
733 UnityGtkMenuShell *shell;775 UnityGtkMenuShell *shell;
776 GtkSettings *settings;
734777
735 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (object));778 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (object));
736779
737 shell = UNITY_GTK_MENU_SHELL (object);780 shell = UNITY_GTK_MENU_SHELL (object);
781 settings = gtk_settings_get_default ();
738782
739 unity_gtk_menu_shell_set_menu_shell (shell, NULL);783 unity_gtk_menu_shell_set_menu_shell (shell, NULL);
740784
785 if (settings != NULL)
786 g_signal_handlers_disconnect_by_func (settings, unity_gtk_menu_shell_handle_settings_notify, shell);
787
741 G_OBJECT_CLASS (unity_gtk_menu_shell_parent_class)->dispose (object);788 G_OBJECT_CLASS (unity_gtk_menu_shell_parent_class)->dispose (object);
742}789}
743790
@@ -806,6 +853,7 @@
806static void853static void
807unity_gtk_menu_shell_init (UnityGtkMenuShell *self)854unity_gtk_menu_shell_init (UnityGtkMenuShell *self)
808{855{
856 self->has_mnemonics = TRUE;
809}857}
810858
811/**859/**
@@ -822,6 +870,23 @@
822unity_gtk_menu_shell_new (GtkMenuShell *menu_shell)870unity_gtk_menu_shell_new (GtkMenuShell *menu_shell)
823{871{
824 UnityGtkMenuShell *shell = g_object_new (UNITY_GTK_TYPE_MENU_SHELL, NULL);872 UnityGtkMenuShell *shell = g_object_new (UNITY_GTK_TYPE_MENU_SHELL, NULL);
873 GtkSettings *settings = gtk_settings_get_default ();
874
875 if (settings != NULL)
876 {
877 g_signal_connect (settings, "notify::gtk-enable-mnemonics", G_CALLBACK (unity_gtk_menu_shell_handle_settings_notify), shell);
878 g_object_get (settings, "gtk-enable-mnemonics", &shell->has_mnemonics, NULL);
879 }
880
881 unity_gtk_menu_shell_set_menu_shell (shell, menu_shell);
882
883 return shell;
884}
885
886UnityGtkMenuShell *
887unity_gtk_menu_shell_new_internal (GtkMenuShell *menu_shell)
888{
889 UnityGtkMenuShell *shell = g_object_new (UNITY_GTK_TYPE_MENU_SHELL, NULL);
825890
826 unity_gtk_menu_shell_set_menu_shell (shell, menu_shell);891 unity_gtk_menu_shell_set_menu_shell (shell, menu_shell);
827892
@@ -899,7 +964,7 @@
899void964void
900unity_gtk_menu_shell_handle_item_notify (UnityGtkMenuShell *shell,965unity_gtk_menu_shell_handle_item_notify (UnityGtkMenuShell *shell,
901 UnityGtkMenuItem *item,966 UnityGtkMenuItem *item,
902 GParamSpec *pspec)967 const gchar *property)
903{968{
904 static const gchar *visible_name;969 static const gchar *visible_name;
905 static const gchar *sensitive_name;970 static const gchar *sensitive_name;
@@ -909,7 +974,7 @@
909 static const gchar *parent_name;974 static const gchar *parent_name;
910 static const gchar *submenu_name;975 static const gchar *submenu_name;
911976
912 const gchar *pspec_name;977 const gchar *name;
913978
914 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (shell));979 g_return_if_fail (UNITY_GTK_IS_MENU_SHELL (shell));
915 g_return_if_fail (UNITY_GTK_IS_MENU_ITEM (item));980 g_return_if_fail (UNITY_GTK_IS_MENU_ITEM (item));
@@ -929,24 +994,24 @@
929 if (submenu_name == NULL)994 if (submenu_name == NULL)
930 submenu_name = g_intern_static_string ("submenu");995 submenu_name = g_intern_static_string ("submenu");
931996
932 pspec_name = g_param_spec_get_name (pspec);997 name = g_intern_string (property);
933998
934 if (unity_gtk_menu_shell_is_debug ())999 if (unity_gtk_menu_shell_is_debug ())
935 g_print ("%s ((%s *) %p, (%s *) %p { \"%s\" }, %s)\n", G_STRFUNC, G_OBJECT_TYPE_NAME (shell), shell, G_OBJECT_TYPE_NAME (item), item, unity_gtk_menu_item_get_label (item), pspec_name);1000 g_print ("%s ((%s *) %p, (%s *) %p { \"%s\" }, %s)\n", G_STRFUNC, G_OBJECT_TYPE_NAME (shell), shell, G_OBJECT_TYPE_NAME (item), item, unity_gtk_menu_item_get_label (item), name);
9361001
937 if (pspec_name == visible_name)1002 if (name == visible_name)
938 unity_gtk_menu_shell_handle_item_visible (shell, item);1003 unity_gtk_menu_shell_handle_item_visible (shell, item);
939 else if (pspec_name == sensitive_name)1004 else if (name == sensitive_name)
940 unity_gtk_menu_shell_handle_item_sensitive (shell, item);1005 unity_gtk_menu_shell_handle_item_sensitive (shell, item);
941 else if (pspec_name == label_name)1006 else if (name == label_name)
942 unity_gtk_menu_shell_handle_item_label (shell, item);1007 unity_gtk_menu_shell_handle_item_label (shell, item);
943 else if (pspec_name == accel_path_name)1008 else if (name == accel_path_name)
944 unity_gtk_menu_shell_handle_item_accel_path (shell, item);1009 unity_gtk_menu_shell_handle_item_accel_path (shell, item);
945 else if (pspec_name == active_name)1010 else if (name == active_name)
946 unity_gtk_menu_shell_handle_item_active (shell, item);1011 unity_gtk_menu_shell_handle_item_active (shell, item);
947 else if (pspec_name == parent_name)1012 else if (name == parent_name)
948 unity_gtk_menu_shell_handle_item_parent (shell, item);1013 unity_gtk_menu_shell_handle_item_parent (shell, item);
949 else if (pspec_name == submenu_name)1014 else if (name == submenu_name)
950 unity_gtk_menu_shell_handle_item_submenu (shell, item);1015 unity_gtk_menu_shell_handle_item_submenu (shell, item);
951}1016}
9521017
9531018
=== modified file 'lib/unity-gtk-menu-shell.h'
--- lib/unity-gtk-menu-shell.h 2013-02-19 11:55:57 +0000
+++ lib/unity-gtk-menu-shell.h 2014-03-05 17:18:00 +0000
@@ -52,6 +52,7 @@
52 /*< private >*/52 /*< private >*/
53 GtkMenuShell *menu_shell;53 GtkMenuShell *menu_shell;
54 gulong menu_shell_insert_handler_id;54 gulong menu_shell_insert_handler_id;
55 gboolean has_mnemonics;
55 GPtrArray *items;56 GPtrArray *items;
56 GPtrArray *sections;57 GPtrArray *sections;
57 GSequence *visible_indices;58 GSequence *visible_indices;

Subscribers

People subscribed via source and target branches