[gitg/wip/commit] Fill in commit stubs
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gitg/wip/commit] Fill in commit stubs
- Date: Fri, 5 Jul 2013 11:05:23 +0000 (UTC)
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]