[gitg/wip/commit] Fill in commit stubs



commit f4d7f9ba509e6ec263eb48180ee21ebe1acddaa3
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Fri Jul 5 13:05:01 2013 +0200

    Fill in commit stubs

 gitg/commit/gitg-commit-paned.vala     |    8 +-
 gitg/commit/gitg-commit.vala           |  158 +++++++++++++++++--
 gitg/resources/ui/gitg-commit-paned.ui |    3 +-
 libgitg/gitg-hook.vala                 |   84 +++++++++-
 libgitg/gitg-stage.vala                |  272 +++++++++++++++++++++++++++-----
 5 files changed, 459 insertions(+), 66 deletions(-)
---
diff --git a/gitg/commit/gitg-commit-paned.vala b/gitg/commit/gitg-commit-paned.vala
index 3331d3e..bf5b92a 100644
--- a/gitg/commit/gitg-commit-paned.vala
+++ b/gitg/commit/gitg-commit-paned.vala
@@ -29,8 +29,8 @@ class Paned : Gtk.Paned
        [GtkChild (name = "diff_view")]
        private Gitg.DiffView d_diff_view;
 
-       [GtkChild (name = "label_commit_summary")]
-       private Gtk.Label d_label_commit_summary;
+       [GtkChild (name = "check_button_skip_hooks")]
+       private Gtk.CheckButton d_check_button_skip_hooks;
 
        [GtkChild (name = "button_commit")]
        private Gtk.Button d_button_commit;
@@ -45,9 +45,9 @@ class Paned : Gtk.Paned
                get { return d_diff_view; }
        }
 
-       public Gtk.Label label_commit_summary
+       public bool skip_hooks
        {
-               get { return d_label_commit_summary; }
+               get { return d_check_button_skip_hooks.active; }
        }
 
        public Gtk.Button button_commit
diff --git a/gitg/commit/gitg-commit.vala b/gitg/commit/gitg-commit.vala
index 0c8e8d2..832fd76 100644
--- a/gitg/commit/gitg-commit.vala
+++ b/gitg/commit/gitg-commit.vala
@@ -364,16 +364,10 @@ namespace GitgCommit
 
                                if (staged.length == 0)
                                {
-                                       d_main.label_commit_summary.label = _("No files staged to be 
committed.");
                                        d_main.button_commit.sensitive = false;
                                }
                                else
                                {
-                                       d_main.label_commit_summary.label =
-                                               ngettext(_("1 file staged to be committed."),
-                                                        _("%d files staged to be 
commited.").printf(staged.length),
-                                                        staged.length);
-
                                        d_main.button_commit.sensitive = true;
                                }
                        });
@@ -384,7 +378,10 @@ namespace GitgCommit
                        reload();
                }
 
-               private void do_commit(Dialog dlg)
+               private void do_commit(Dialog         dlg,
+                                      bool           skip_hooks,
+                                      Ggit.Signature author,
+                                      Ggit.Signature committer)
                {
                        var stage = application.repository.stage;
 
@@ -395,14 +392,20 @@ namespace GitgCommit
                                opts |= Gitg.StageCommitOptions.AMEND;
                        }
 
-                       
-
                        if (dlg.sign_off)
                        {
                                opts |= Gitg.StageCommitOptions.SIGN_OFF;
                        }
 
-                       stage.commit.begin(dlg.message, opts, (obj, res) => {
+                       if (skip_hooks)
+                       {
+                               opts |= Gitg.StageCommitOptions.SKIP_HOOKS;
+                       }
+
+                       stage.commit.begin(dlg.message,
+                                          author,
+                                          committer,
+                                          opts, (obj, res) => {
                                try
                                {
                                        var o = stage.commit.end(res);
@@ -415,7 +418,27 @@ namespace GitgCommit
                        });
                }
 
-               private void on_commit_clicked()
+               private async bool pre_commit(Ggit.Signature author)
+               {
+                       try
+                       {
+                               yield application.repository.stage.pre_commit_hook(author);
+                       }
+                       catch (Gitg.StageError e)
+                       {
+                               application.show_infobar("Failed to pass pre-commit",
+                                                        e.message,
+                                                        Gtk.MessageType.ERROR);
+
+                               return false;
+                       }
+
+                       return true;
+               }
+
+               private void run_commit_dialog(bool           skip_hooks,
+                                              Ggit.Signature author,
+                                              Ggit.Signature committer)
                {
                        var dlg = new Dialog();
 
@@ -425,7 +448,7 @@ namespace GitgCommit
                        dlg.response.connect((d, id) => {
                                if (id == Gtk.ResponseType.OK)
                                {
-                                       do_commit(dlg);
+                                       do_commit(dlg, skip_hooks, author, committer);
                                }
                                else
                                {
@@ -436,6 +459,117 @@ namespace GitgCommit
                        dlg.show();
                }
 
+               private Ggit.Signature get_signature(string envname) throws Error
+               {
+                       string? user = null;
+                       string? email = null;
+
+                       var env = application.environment;
+
+                       var nameenv = @"GIT_$(envname)_NAME";
+                       var emailenv = @"GIT_$(envname)_EMAIL";
+
+                       if (env.has_key(nameenv))
+                       {
+                               user = env[nameenv];
+                       }
+
+                       if (env.has_key(emailenv))
+                       {
+                               email = env[emailenv];
+                       }
+
+                       var conf = application.repository.get_config();
+
+                       if (user == null)
+                       {
+                               try
+                               {
+                                       user = conf.get_string("user.name");
+                               } catch {}
+                       }
+
+                       if (email == null)
+                       {
+                               try
+                               {
+                                       email = conf.get_string("user.email");
+                               } catch {}
+                       }
+
+                       return new Ggit.Signature.now(user != null ? user : "",
+                                                     email != null ? email : "");
+               }
+
+               private void on_commit_clicked()
+               {
+                       string? user = null;
+                       string? email = null;
+                       Ggit.Signature? committer = null;
+                       Ggit.Signature? author = null;
+
+                       try
+                       {
+                               committer = get_signature("COMMITTER");
+                               author = get_signature("AUTHOR");
+
+                               user = committer.get_name();
+                               email = committer.get_email();
+
+                               if (user == "")
+                               {
+                                       user = null;
+                               }
+
+                               if (email == "")
+                               {
+                                       email = null;
+                               }
+                       }
+                       catch {}
+
+                       if (user == null || email == null)
+                       {
+                               string secmsg;
+
+                               if (user == null && email == null)
+                               {
+                                       secmsg = _("Your user name and email are not configured yet. Please 
go to the user configuration and provide your name and email.");
+                               }
+                               else if (user == null)
+                               {
+                                       secmsg = _("Your user name is not configured yet. Please go to the 
user configuration and provide your name.");
+                               }
+                               else
+                               {
+                                       secmsg = _("Your email is not configured yet. Please go to the user 
configuration and provide your email.");
+                               }
+                       
+                               // TODO: better to show user info dialog directly or something
+                               application.show_infobar(_("Failed to pass pre-commit"),
+                                                        secmsg,
+                                                        Gtk.MessageType.ERROR);
+
+                               return;
+                       }
+
+                       if (d_main.skip_hooks)
+                       {
+                               run_commit_dialog(true, author, committer);
+                       }
+                       else
+                       {
+                               pre_commit.begin(author, (obj, res) => {
+                                       if (!pre_commit.end(res))
+                                       {
+                                               return;
+                                       }
+
+                                       run_commit_dialog(false, author, committer);
+                               });
+                       }
+               }
+
                private void build_ui()
                {
                        d_main = new Paned();
diff --git a/gitg/resources/ui/gitg-commit-paned.ui b/gitg/resources/ui/gitg-commit-paned.ui
index 5886144..ead2de6 100644
--- a/gitg/resources/ui/gitg-commit-paned.ui
+++ b/gitg/resources/ui/gitg-commit-paned.ui
@@ -61,10 +61,11 @@
                 <property name="visible">True</property>
                 <property name="margin">6</property>
                 <child>
-                  <object class="GtkLabel" id="label_commit_summary">
+                  <object class="GtkCheckButton" id="check_button_skip_hooks">
                     <property name="visible">True</property>
                     <property name="halign">start</property>
                     <property name="hexpand">True</property>
+                    <property name="label" translatable="yes">Skip commit hooks</property>
                   </object>
                   <packing>
                     <property name="pack-type">start</property>
diff --git a/libgitg/gitg-hook.vala b/libgitg/gitg-hook.vala
index 5fda372..fdb5823 100644
--- a/libgitg/gitg-hook.vala
+++ b/libgitg/gitg-hook.vala
@@ -24,7 +24,7 @@ public class Hook : Object
 {
        public Gee.HashMap<string, string> environment { get; set; }
        public string name { get; set; }
-       public string[] arguments { get; set; }
+       private string[] d_arguments;
        public File? working_directory { get; set; }
 
        private string[] d_output;
@@ -44,6 +44,11 @@ public class Hook : Object
                Object(name: name);
        }
 
+       public void add_argument(string arg)
+       {
+               d_arguments += arg;
+       }
+
        private string[]? flat_environment()
        {
                if (environment.size == 0)
@@ -79,7 +84,7 @@ public class Hook : Object
                        {
                                var s = stream.read_line_async.end(res);
 
-                               if (s.length != 0)
+                               if (s != null)
                                {
                                        d_output += s;
 
@@ -99,39 +104,100 @@ public class Hook : Object
                stream_read_async(dstream);
        }
 
+       private File hook_file(Ggit.Repository repository)
+       {
+               var hooksdir = repository.get_location().get_child("hooks");
+               var script = hooksdir.resolve_relative_path(name);
+
+               return script;
+       }
+
+       public bool exists_in(Ggit.Repository repository)
+       {
+               var script = hook_file(repository);
+
+               try
+               {
+                       var info = script.query_info(FileAttribute.ACCESS_CAN_EXECUTE,
+                                                    FileQueryInfoFlags.NONE);
+
+                       return info.get_attribute_boolean(FileAttribute.ACCESS_CAN_EXECUTE);
+               }
+               catch
+               {
+                       return false;
+               }
+       }
+
+       public int run_sync(Ggit.Repository repository) throws SpawnError
+       {
+               var m = new MainLoop();
+               SpawnError? e = null;
+               int status = 0;
+
+               run.begin(repository, (obj, res) => {
+                       try
+                       {
+                               status = run.end(res);
+                       }
+                       catch (SpawnError err)
+                       {
+                               e = err;
+                       }
+
+                       m.quit();
+               });
+
+               m.run();
+
+               if (e != null)
+               {
+                       throw e;
+               }
+
+               return status;
+       }
+
        public async int run(Ggit.Repository repository) throws SpawnError
        {
-               File? wd = working_directory;
                SourceFunc callback = run.callback;
 
                d_output = new string[256];
                d_output.length = 0;
 
-               if (wd == null)
+               File wd;
+
+               if (working_directory == null)
+               {
+                       wd = working_directory;
+               }
+               else
                {
                        wd = repository.get_workdir();
                }
 
-               var hooksdir = repository.get_location().get_child("hooks");
-               var script = hooksdir.resolve_relative_path(name);
-               var args = new string[arguments.length + 1];
+               var script = hook_file(repository);
+               var args = new string[d_arguments.length + 1];
 
                args.length = 0;
 
                args += script.get_path();
 
-               foreach (var a in arguments)
+               foreach (var a in d_arguments)
                {
                        args += a;
                }
 
+               var env = flat_environment();
+
                Pid pid;
+
                int pstdout;
                int pstderr;
 
                Process.spawn_async_with_pipes(wd.get_path(),
                                               args,
-                                              flat_environment(),
+                                              env,
                                               SpawnFlags.DO_NOT_REAP_CHILD,
                                               null,
                                               out pid,
diff --git a/libgitg/gitg-stage.vala b/libgitg/gitg-stage.vala
index e6b2741..0d8cb67 100644
--- a/libgitg/gitg-stage.vala
+++ b/libgitg/gitg-stage.vala
@@ -23,9 +23,17 @@ namespace Gitg
 [Flags]
 public enum StageCommitOptions
 {
-       NONE     = 0,
-       SIGN_OFF = 1 << 0,
-       AMEND    = 1 << 1
+       NONE       = 0,
+       SIGN_OFF   = 1 << 0,
+       AMEND      = 1 << 1,
+       SKIP_HOOKS = 1 << 2
+}
+
+public errordomain StageError
+{
+       PRE_COMMIT_HOOK_FAILED,
+       COMMIT_MSG_HOOK_FAILED,
+       NOTHING_TO_COMMIT
 }
 
 public class Stage : Object
@@ -130,27 +138,19 @@ public class Stage : Object
                });
        }
 
-       private string message_with_sign_off(Ggit.Config conf, string message) throws Error
+       private string message_with_sign_off(string         message,
+                                            Ggit.Signature committer)
        {
-               string? user;
-               string? email;
-
-               user = conf.get_string("user.name");
-               email = conf.get_string("user.email");
-
-               if (user != null && email != null)
-               {
-                       return "%s\nSigned-off-by: %s <%s>\n".printf(message, user, email);
-               }
-               else
-               {
-                       return message;
-               }
+               return "%s\nSigned-off-by: %s <%s>\n".printf(message,
+                                                            committer.get_name(),
+                                                            committer.get_email());
        }
 
-       private string convert_message_to_encoding(Ggit.Config conf, string message)
+       private string convert_message_to_encoding(Ggit.Config conf,
+                                                  string      message,
+                                                  out string? encoding)
        {
-               string? encoding;
+               encoding = null;
 
                try
                {
@@ -158,6 +158,7 @@ public class Stage : Object
                }
                catch
                {
+                       encoding = null;
                        return message;
                }
 
@@ -169,43 +170,237 @@ public class Stage : Object
                        {
                                return convert(message, -1, encoding, "UTF-8");
                        }
-                       catch {}
+                       catch
+                       {
+                               encoding = null;
+                       }
+               }
+               else
+               {
+                       encoding = null;
                }
 
                return message;
        }
 
-       public async Ggit.OId commit(string             message,
-                                    StageCommitOptions options) throws Error
+       private void setup_commit_hook_environment(Gitg.Hook       hook,
+                                                  Ggit.Signature? author)
        {
-               yield thread_index((index) => {
-                       // TODO: run pre-commit hook
+               var wd = d_repository.get_workdir();
+               var gd = d_repository.get_location();
 
-                       // Write tree from index
-                       var tree = index.write_tree();
+               hook.working_directory = wd;
+
+               var gitdir = wd.get_relative_path(gd);
+
+               hook.environment["GIT_DIR"] = gitdir;
+               hook.environment["GIT_INDEX_FILE"] = Path.build_filename(gitdir, "index");
+               hook.environment["GIT_PREFIX"] = ".";
+
+               if (author != null)
+               {
+                       hook.environment["GIT_AUTHOR_NAME"] = author.get_name();
+                       hook.environment["GIT_AUTHOR_EMAIL"] = author.get_email();
+                       hook.environment["GIT_AUTHOR_DATE"] = author.get_email();
+               }
+       }
+
+       public async void pre_commit_hook(Ggit.Signature author) throws StageError
+       {
+               SourceFunc cb = pre_commit_hook.callback;
+               string? errormsg = null;
+
+               yield Async.thread(() => {
+                       // First run the pre-commit hook
+                       var hook = new Gitg.Hook("pre-commit");
+
+                       setup_commit_hook_environment(hook, author);
+
+                       hook.run.begin(d_repository, (obj, res) => {
+                               try
+                               {
+                                       int status = hook.run.end(res);
+
+                                       if (status != 0)
+                                       {
+                                               errormsg = string.joinv("\n", hook.output);
+                                       }
+                               }
+                               catch (SpawnError e) {}
+
+                               cb();
+                       });
+               });
+
+               yield;
+
+               if (errormsg != null)
+               {
+                       throw new StageError.PRE_COMMIT_HOOK_FAILED(errormsg);
+               }
+       }
+
+       private bool has_index_changes()
+       {
+               var opts = Ggit.StatusOption.EXCLUDE_SUBMODULES;
+               var show = Ggit.StatusShow.INDEX_ONLY;
+
+               var options = new Ggit.StatusOptions(opts, show, null);
+               bool has_changes = false;
+
+               try
+               {
+                       d_repository.file_status_foreach(options, (path, flags) => {
+                               has_changes = true;
+                               return -1;
+                       });
+               } catch {}
+
+               return has_changes;
+       }
+
+       private string commit_msg_hook(string         message,
+                                      Ggit.Signature author,
+                                      Ggit.Signature committer) throws StageError
+       {
+               var hook = new Gitg.Hook("commit-msg");
+
+               if (!hook.exists_in(d_repository))
+               {
+                       return message;
+               }
+
+               setup_commit_hook_environment(hook, author);
+
+               var msgfile = d_repository.get_location().get_child("COMMIT_EDITMSG");
+
+               try
+               {
+                       FileUtils.set_contents(msgfile.get_path(), message);
+               }
+               catch { return message; }
 
-                       // TODO: write COMMIT_EDITMSG and run commit-msg hook
+               hook.add_argument(msgfile.get_path());
 
+               int status;
+
+               try
+               {
+                       status = hook.run_sync(d_repository);
+               }
+               catch { return message; }
+
+               if (status != 0)
+               {
+                       throw new StageError.COMMIT_MSG_HOOK_FAILED(string.joinv("\n", hook.output));
+               }
+
+               // Read back the message
+               try
+               {
+                       string newmessage;
+
+                       FileUtils.get_contents(msgfile.get_path(), out newmessage);
+                       return newmessage;
+               }
+               catch (Error e)
+               {
+                       throw new StageError.COMMIT_MSG_HOOK_FAILED(_("Could not read commit message after 
running commit-msg hook: %s").printf(e.message));
+               }
+               finally
+               {
+                       FileUtils.remove(msgfile.get_path());
+               }
+       }
+
+       private void post_commit_hook(Ggit.Signature author)
+       {
+               var hook = new Gitg.Hook("post-commit");
+
+               setup_commit_hook_environment(hook, author);
+
+               hook.run.begin(d_repository, (obj, res) => {
+                       try
+                       {
+                               hook.run.end(res);
+                       } catch {}
+               });
+       }
+
+       private string get_subject(string message)
+       {
+               var nlpos = message.index_of("\n");
+
+               if (nlpos == -1)
+               {
+                       return message;
+               }
+               else
+               {
+                       return message[0:nlpos];
+               }
+       }
+
+       public async Ggit.OId? commit(string             message,
+                                     Ggit.Signature     author,
+                                     Ggit.Signature     committer,
+                                     StageCommitOptions options) throws Error
+       {
+               Ggit.OId? ret = null;
+
+               bool skip_hooks = (options & StageCommitOptions.SKIP_HOOKS) != 0;
+
+               yield thread_index((index) => {
+                       if (!has_index_changes())
+                       {
+                               throw new StageError.NOTHING_TO_COMMIT("Nothing to commit");
+                       }
+
+                       // Write tree from index
                        var conf = d_repository.get_config();
-                       conf.refresh();
 
                        string emsg = message;
 
                        if ((options & StageCommitOptions.SIGN_OFF) != 0)
                        {
-                               emsg = message_with_sign_off(conf, emsg);
+                               emsg = message_with_sign_off(emsg, committer);
+                       }
+
+                       string? encoding;
+
+                       emsg = convert_message_to_encoding(conf, emsg, out encoding);
+
+                       if (!skip_hooks)
+                       {
+                               emsg = commit_msg_hook(emsg, author, committer);
                        }
 
-                       emsg = convert_message_to_encoding(conf, emsg);
+                       var treeoid = index.write_tree();
+                       var head = d_repository.get_head();
+                       var headoid = head.resolve().get_target();
 
-                       // TODO: commit
+                       ret = d_repository.create_commit_from_oids("HEAD",
+                                                                  author,
+                                                                  committer,
+                                                                  encoding,
+                                                                  emsg,
+                                                                  treeoid,
+                                                                  new Ggit.OId[] { headoid });
 
-                       // TODO: update ref with subject of message
+                       if (head.has_reflog())
+                       {
+                               // Update reflog
+                               try
+                               {
+                                       head.create_reflog(ret, committer, get_subject(message));
+                               } catch {}
+                       }
 
-                       // TODO: run post-commit hook
+                       // run post commit
+                       post_commit_hook(author);
                });
 
-               return null;
+               return ret;
        }
 
        /**
@@ -337,12 +532,9 @@ public class Stage : Object
         */
        public async void unstage(File file) throws Error
        {
-               yield thread_index((index) => {
-                       // lookup the tree of HEAD
-                       var head = d_repository.get_head();
-                       var commit = (Ggit.Commit)head.lookup();
-                       var tree = commit.get_tree();
+               var tree = yield get_head_tree();
 
+               yield thread_index((index) => {
                        // get path relative to the repository working directory
                        var wd = d_repository.get_workdir();
                        var path = wd.get_relative_path(file);


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