[gitg] Add scanning of folders for git repositories



commit 480de9ced04313fb113bd5e2cc015113d8134864
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sun Aug 9 18:08:42 2015 +0200

    Add scanning of folders for git repositories

 gitg/Makefile.am                    |    1 +
 gitg/gitg-dash-view.vala            |  127 +++++++++++++++++++++++++++++++++--
 gitg/gitg-recursive-scanner.vala    |  102 ++++++++++++++++++++++++++++
 gitg/resources/ui/gitg-dash-view.ui |   36 ++++++++--
 4 files changed, 255 insertions(+), 11 deletions(-)
---
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index 874adef..a821c3d 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -70,6 +70,7 @@ gitg_gitg_VALASOURCES =                                               \
        gitg/gitg-plugins-engine.vala                           \
        gitg/gitg-popup-menu.vala                               \
        gitg/gitg-recursive-monitor.vala                        \
+       gitg/gitg-recursive-scanner.vala                        \
        gitg/gitg-ref-action-copy-name.vala                     \
        gitg/gitg-ref-action-delete.vala                        \
        gitg/gitg-ref-action-fetch.vala                         \
diff --git a/gitg/gitg-dash-view.vala b/gitg/gitg-dash-view.vala
index 3ab30fb..87bedc1 100644
--- a/gitg/gitg-dash-view.vala
+++ b/gitg/gitg-dash-view.vala
@@ -21,7 +21,7 @@ namespace Gitg
 {
 
 [GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-dash-view.ui")]
-class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectable, GitgExt.Searchable
+class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectable, GitgExt.Searchable, 
RecursiveScanner
 {
        private const string version = Config.VERSION;
 
@@ -33,6 +33,9 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
        [GtkChild( name = "introduction" )]
        private Gtk.Grid d_introduction;
 
+       [GtkChild( name = "label_scan" )]
+       private Gtk.Label d_label_scan;
+
        [GtkChild( name = "label_profile") ]
        private Gtk.Label d_label_profile;
 
@@ -240,6 +243,9 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                d_repository_list_box.add.connect(update_availability);
                d_repository_list_box.remove.connect(update_availability);
 
+               // Translators: the two %s will be replaced to create a link to perform the scanning action.
+               d_label_scan.label = _("We can also %sscan your home directory%s for git 
repositories.").printf("<a href=\"scan-home\">", "</a>");
+
                // Translators: the two %s will be used to create a link to the author dialog.
                d_label_profile.label = _("In the mean time, you may want to %sset up your git 
profile%s.").printf("<a href=\"setup-profile\">", "</a>");
                update_setup_profile_visibility();
@@ -264,6 +270,19 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
        }
 
        [GtkCallback]
+       private bool scan_home_activated()
+       {
+               var homedir = Environment.get_home_dir();
+
+               if (homedir != null)
+               {
+                       add_repositories_scan(File.new_for_path(homedir));
+               }
+
+               return true;
+       }
+
+       [GtkCallback]
        private bool setup_profile_activated()
        {
                AuthorDetailsDialog.show_global(application as Window);
@@ -409,7 +428,7 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                }
        }
 
-       private void do_add_repository(File location)
+       private void do_add_repository(File location, bool report_errors)
        {
                Repository repo;
 
@@ -419,7 +438,11 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                }
                catch (Error err)
                {
-                       application.show_infobar(_("Failed to add repository"), err.message, 
Gtk.MessageType.ERROR);
+                       if (report_errors)
+                       {
+                               application.show_infobar(_("Failed to add repository"), err.message, 
Gtk.MessageType.ERROR);
+                       }
+
                        return;
                }
 
@@ -484,6 +507,88 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                       location.get_child("refs").query_exists();
        }
 
+       private async bool file_exists_async(File file, Cancellable? cancellable)
+       {
+               try
+               {
+                       return (yield file.query_info_async(FileAttribute.STANDARD_TYPE, 
FileQueryInfoFlags.NONE, Priority.DEFAULT, cancellable)) != null;
+               }
+               catch
+               {
+                       return false;
+               }
+       }
+
+       protected async bool scan_visit_directory(File file, Cancellable? cancellable)
+       {
+               if (cancellable != null && cancellable.is_cancelled())
+               {
+                       return false;
+               }
+
+               // Check for .git
+               if ((yield file_exists_async(file.get_child(".git"), cancellable)))
+               {
+                       do_add_repository(file, false);
+                       return false;
+               }
+
+               // Check for bare
+               if ((yield file_exists_async(file.get_child("objects"), cancellable)) &&
+                   (yield file_exists_async(file.get_child("HEAD"), cancellable)) &&
+                   (yield file_exists_async(file.get_child("refs"), cancellable)))
+               {
+                       do_add_repository(file, false);
+                       return false;
+               }
+
+               return scan_visit_directory_default(file);
+       }
+
+       private void add_repositories_scan(File location)
+       {
+               var dlg = new Gtk.MessageDialog(application as Gtk.Window,
+                                               Gtk.DialogFlags.MODAL,
+                                               Gtk.MessageType.INFO,
+                                               Gtk.ButtonsType.CANCEL,
+                                               _("Scanning for repositories in %s"),
+                                               Utils.replace_home_dir_with_tilde(location));
+
+               dlg.set_default_response(Gtk.ResponseType.CANCEL);
+
+               var cancellable = new Cancellable();
+
+               dlg.response.connect(() => {
+                       cancellable.cancel();
+               });
+
+               uint timeout_id = 0;
+
+               timeout_id = Timeout.add_seconds(1, () => {
+                       if (timeout_id == 0)
+                       {
+                               dlg.destroy();
+                       }
+
+                       timeout_id = 0;
+                       return false;
+               });
+
+               scan.begin(location, cancellable, () => {
+                       if (timeout_id != 0)
+                       {
+                               timeout_id = 0;
+                       }
+                       else
+                       {
+                               dlg.destroy();
+                       }
+               });
+
+               dlg.show();
+               dlg.get_window().set_cursor(new Gdk.Cursor.for_display(get_display(), Gdk.CursorType.WATCH));
+       }
+
        [GtkCallback]
        private void add_repository_clicked()
        {
@@ -493,6 +598,14 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                                                        _("_Cancel"), Gtk.ResponseType.CANCEL,
                                                        _("_Add"), Gtk.ResponseType.OK);
 
+               var scan_all = new Gtk.CheckButton.with_mnemonic(_("_Scan for all git repositories from this 
directory"));
+
+               scan_all.halign = Gtk.Align.END;
+               scan_all.hexpand = true;
+               scan_all.show();
+
+               chooser.extra_widget = scan_all;
+
                chooser.modal = true;
                chooser.set_default_response(Gtk.ResponseType.OK);
 
@@ -506,13 +619,17 @@ class DashView : Gtk.Grid, GitgExt.UIElement, GitgExt.Activity, GitgExt.Selectab
                                        file = chooser.get_current_folder_file();
                                }
 
-                               if (!looks_like_git(file))
+                               if (scan_all.active)
+                               {
+                                       add_repositories_scan(file);
+                               }
+                               else if (!looks_like_git(file))
                                {
                                        query_create_repository(file);
                                }
                                else
                                {
-                                       do_add_repository(file);
+                                       do_add_repository(file, true);
                                }
                        }
 
diff --git a/gitg/gitg-recursive-scanner.vala b/gitg/gitg-recursive-scanner.vala
new file mode 100644
index 0000000..490d048
--- /dev/null
+++ b/gitg/gitg-recursive-scanner.vala
@@ -0,0 +1,102 @@
+/*
+ *
+ * Copyright (C) 2015 - Jesse van den Kieboom
+ *
+ * gitg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * gitg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace Gitg
+{
+
+interface RecursiveScanner : Object
+{
+       protected async virtual void scan_visit_file(File file, Cancellable? cancellable)
+       {
+       }
+
+       protected bool scan_visit_directory_default(File file)
+       {
+               return !file.get_basename().has_prefix(".");
+       }
+
+       protected async virtual bool scan_visit_directory(File file, Cancellable? cancellable)
+       {
+               return scan_visit_directory_default(file);
+       }
+
+       public async void scan(File location, Cancellable? cancellable = null)
+       {
+               yield scan_real(location, cancellable, new Gee.HashSet<File>((file) => { return file.hash(); 
}, (file, other) => { return file.equal(other); }));
+       }
+
+       private async void scan_real(File location, Cancellable? cancellable, Gee.HashSet<File> seen)
+       {
+               if (cancellable != null && cancellable.is_cancelled())
+               {
+                       return;
+               }
+
+               FileEnumerator? e;
+
+               try
+               {
+                       e = yield location.enumerate_children_async(FileAttribute.STANDARD_NAME + "," + 
FileAttribute.STANDARD_TYPE,
+                                                               FileQueryInfoFlags.NONE,
+                                                               Priority.DEFAULT,
+                                                               cancellable);
+               } catch { return; }
+
+               while (cancellable == null || !cancellable.is_cancelled())
+               {
+                       List<FileInfo>? files = null;
+
+                       try
+                       {
+                               files = yield e.next_files_async(10, Priority.DEFAULT);
+                       } catch {}
+
+                       if (files == null) {
+                               break;
+                       }
+
+                       foreach (var f in files)
+                       {
+                               var file = location.get_child(f.get_name());
+
+                               if (seen.contains(file))
+                               {
+                                       continue;
+                               }
+
+                               seen.add(file);
+
+                               yield scan_visit_file(file, cancellable);
+
+                               if (f.get_file_type() == FileType.DIRECTORY)
+                               {
+                                       if (!(yield scan_visit_directory(file, cancellable)))
+                                       {
+                                               continue;
+                                       }
+
+                                       yield scan_real(file, cancellable, seen);
+                               }
+                       }
+               }
+       }
+}
+
+}
+
+// ex:ts=4 noet
\ No newline at end of file
diff --git a/gitg/resources/ui/gitg-dash-view.ui b/gitg/resources/ui/gitg-dash-view.ui
index b47e76e..f74b43e 100644
--- a/gitg/resources/ui/gitg-dash-view.ui
+++ b/gitg/resources/ui/gitg-dash-view.ui
@@ -25,7 +25,7 @@
             <property name="left_attach">0</property>
             <property name="top_attach">0</property>
             <property name="width">1</property>
-            <property name="height">2</property>
+            <property name="height">3</property>
           </packing>
         </child>
         <child>
@@ -38,8 +38,8 @@
             <property name="hexpand">False</property>
             <property name="vexpand">True</property>
             <property name="wrap">True</property>
-            <property name="xalign">0</property>
             <property name="max-width-chars">40</property>
+            <property name="xalign">0</property>
           </object>
           <packing>
             <property name="left_attach">1</property>
@@ -49,22 +49,44 @@
           </packing>
         </child>
         <child>
-          <object class="GtkLabel" id="label_profile">
+          <object class="GtkLabel" id="label_scan">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
+            <property name="label"></property>
+            <property name="use_markup">True</property>
             <property name="halign">start</property>
             <property name="valign">start</property>
             <property name="hexpand">False</property>
             <property name="vexpand">True</property>
             <property name="wrap">True</property>
+            <property name="max-width-chars">40</property>
             <property name="xalign">0</property>
+            <signal name="activate-link" handler="scan_home_activated" swapped="no"/>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">1</property>
+            <property name="width">1</property>
+            <property name="height">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label_profile">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">start</property>
+            <property name="valign">start</property>
+            <property name="hexpand">False</property>
+            <property name="vexpand">True</property>
+            <property name="wrap">True</property>
             <property name="max_width_chars">40</property>
             <property name="use_markup">True</property>
+            <property name="xalign">0</property>
             <signal name="activate-link" handler="setup_profile_activated" swapped="no"/>
           </object>
           <packing>
             <property name="left_attach">1</property>
-            <property name="top_attach">1</property>
+            <property name="top_attach">2</property>
             <property name="width">1</property>
             <property name="height">1</property>
           </packing>
@@ -109,7 +131,8 @@
         <child>
           <object class="GtkButton" id="add_repository">
             <property name="visible">True</property>
-            <property name="label" translatable="yes">Add repository</property>
+            <property name="label" translatable="yes">_Add repository</property>
+            <property name="use_underline">True</property>
             <signal name="clicked" handler="add_repository_clicked" swapped="no"/>
           </object>
           <packing>
@@ -119,7 +142,8 @@
         <child>
           <object class="GtkButton" id="clone_repository">
             <property name="visible">True</property>
-            <property name="label" translatable="yes">Clone repository</property>
+            <property name="label" translatable="yes">_Clone repository</property>
+            <property name="use_underline">True</property>
             <signal name="clicked" handler="clone_repository_clicked" swapped="no"/>
           </object>
           <packing>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]